Upgrade to Bevy v0.12 (#12)

* Migrate `System::pipe(...)` calls to `System::map(...)`
* Migrate `EventReader::iter()` and `EventReader::into_iter()` calls to
  `EventReader::read()`
* Migrate asset handling
* Generate `*.meta` files for assets
* Migrate a shader import
* Fix emissive materials double-applying their emissive strength
* Fix non-normalized bone weights resulting in incorrect vertex positions
* Set shadow filtering to `Hardware2x2` to mitigate increase in shader
  compilation times
This commit is contained in:
Danny McGee
2023-11-22 23:05:42 -05:00
committed by GitHub
parent 8bf4abc3a4
commit 155272a5ba
148 changed files with 15479 additions and 16519 deletions
+3
View File
@@ -6,4 +6,7 @@
"rust-analyzer.cargo.features": [
"wasm"
],
"files.associations": {
"*.meta": "ron"
}
}
Generated
+329 -266
View File
File diff suppressed because it is too large Load Diff
+2 -5
View File
@@ -1,12 +1,9 @@
[workspace]
resolver = "2"
members = [
"./bevy_mod_outline",
"./studio"
]
members = ["./studio"]
[workspace.dependencies.bevy]
version = "0.11.3"
version = "0.12.0"
features = ["tonemapping_luts"]
[profile.dev-custom]
-137
View File
@@ -1,137 +0,0 @@
# Changelog
## bevy_mod_outline 0.5.0 (2023-08-14)
### Changed
- Updated Bevy dependency to 0.11. (@ramirezmike and @zainthemaynnn)
- Removed panic if specialising mesh pipeline fails. (@arjo129)
## bevy_mod_outline 0.4.2 (2023-05-30)
### Fixed
- Fixed failures to propagate ComputedOutlineDepth when needed.
- Fixed Z-fighting between overlay and stencil with OpenGL wgpu back-end.
## bevy_mod_outline 0.4.1 (2023-04-11)
### Fixed
- Fixed panic if mesh has unused vertex indices.
- Fixed panic if the DepthMode hasn't propagated before rendering.
### Changed
- Changed normal weighting to use vertex rather than face normals if available.
## bevy_mod_outline 0.4.0 (2023-03-22)
### Added
- Added enabled flag to OutlineStencil.
- Added hollow example.
### Fixed
- Fixed outline depth propagating when inheritance not enabled.
### Changed
- Updated Bevy dependency to 0.10.
- Changed outline normal generator to use face normals.
## bevy_mod_outline 0.3.5 (2023-03-08)
### Fixed
- Fixed regression breaking SetOutlineDepth::Real.
## bevy_mod_outline 0.3.4 (2023-03-08)
### Fixed
- Fixed texture format error when HDR is enabled.
- Fixed bad clipping of outlines behind the camera.
## bevy_mod_outline 0.3.3 (2023-02-21)
### Fixed
- Fixed SetOutlineDepth being ignored in some cases (@mxgrey).
- Fixed defaulting to SetOutlineDepth::Real in some cases.
- Fixed missing examples from README.
## bevy_mod_outline 0.3.2 (2023-01-15)
### Added
- Added support for (Outline)RenderLayers components (@mxgrey).
- Added render_layers example.
## bevy_mod_outline 0.3.1 (2023-01-05)
### Added
- Added Lerp impls for OutlineVolume and OutlineStencil.
- Added Real mode to SetOutlineDepth enum.
## bevy_mod_outline 0.3.0 (2022-11-22)
### Added
- Added ComputedOutlineDepth, SetOutlineDepth, and InheritOutlineDepth.
- Added offset field to OutlineStencil.
- Added pieces example.
### Removed
- Removed align16 feature.
### Fixed
- Fixed errant debug println in AutoGenerateOutlineNormalsPlugin.
### Changed
- Updated Bevy dependency to 0.9.
- Renamed Outline component to OutlineVolume.
## bevy_mod_outline 0.2.5 (2023-01-14)
### Added
- Added support for (Outline)RenderLayers components (@mxgrey) (back-port from 0.3.2).
- Added render_layers example (back-port from 0.3.2).
### Fixed
- Fixed errant debug println in AutoGenerateOutlineNormalsPlugin (back-port from 0.3.0).
## bevy_mod_outline 0.2.4 (2022-10-12)
### Fixed
- Fixed adding outlines to jointed (skinned) meshes.
## bevy_mod_outline 0.2.3 (2022-08-28)
### Added
- Added AutoGenerateOutlineNormalsPlugin.
## bevy_mod_outline 0.2.2 (2022-08-23)
### Added
- Added a feature flag to control the dependency on bevy_ui (@Shatur).
- Added a feature flag to control uniform struct alignment.
### Fixed
- Fixed compilation on 32-bit platforms.
- Fixed insufficient alignment causing errors with WebGL.
### Changed
- Removed dependency on bevy's monolithic render feature flag (@Shatur).
## bevy_mod_outline 0.2.1 (2022-08-10)
### Added
- Added a constant to expose the outline pass node name.
### Fixed
- Fixed outlines causing UI to disappear when MSAA is enabled.
- Fixed bad derive allowing OutlineBundle to be inserted as a component.
## bevy_mod_outline 0.2.0 (2022-08-09)
### Added
- Added support for outline normals.
### Changed
- Updated Bevy dependency to 0.8.
- Changed from rendering in main pass to separate pass.
- Changed from using asset handles to plain components.
## bevy_mod_outline 0.1.0 (2022-06-14)
- Initial release
-37
View File
@@ -1,37 +0,0 @@
[package]
name = "bevy_mod_outline"
version = "0.5.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "A mesh outlining plugin for Bevy."
readme = "README.md"
homepage = "https://github.com/komadori/bevy_mod_outline/"
repository = "https://github.com/komadori/bevy_mod_outline/"
keywords = ["gamedev", "bevy", "outline"]
categories = ["game-engines", "rendering"]
[dependencies]
bevy = { version = "0.11", default-features = false, features = [
"bevy_asset",
"bevy_render",
"bevy_pbr",
"ktx2",
"tonemapping_luts",
"zstd",
"bevy_core_pipeline",
] }
bitfield = "0.14"
interpolation = "0.2"
thiserror = "1.0"
wgpu-types = "0.16.1"
[dev-dependencies]
bevy = { version = "0.11", default-features = false, features = [
"animation",
"bevy_gltf",
"bevy_pbr",
"bevy_scene",
"bevy_winit",
"png",
"x11",
] }
-202
View File
@@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-23
View File
@@ -1,23 +0,0 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
-96
View File
@@ -1,96 +0,0 @@
> **NOTE**: This is a temporary fork of [bevy_mod_outline](https://github.com/komandori/bevy_mod_outline)
> to fix [this issue](https://github.com/komadori/bevy_mod_outline/issues/19).
>
> This can be removed and replaced with the crates.io import once the issue is
> fixed upstream.
# bevy_mod_outline
[![crates.io](https://img.shields.io/crates/v/bevy_mod_outline.svg)](https://crates.io/crates/bevy_mod_outline)
[![docs](https://docs.rs/bevy_mod_outline/badge.svg)](https://docs.rs/bevy_mod_outline)
![Screenshot of bevy_mod_outline's shapes example](https://github.com/bevyengine/bevy-assets/blob/main/Assets/3D/bevy_mod_outline.png?raw=true)
This crate provides a Bevy plugin for drawing outlines around meshes using the
vertex extrusion method.
## Dependency
```toml
[dependencies]
bevy_mod_outline = "0.5"
```
## Examples
A rotating cube and torus with opaque and transparent outlines.
```shell
cargo run --example shapes
```
Multiple intersecting meshes sharing an outline plane. The outline stencil is offset to create
a gap between the object and its outline.
```shell
cargo run --example pieces
```
Many instances of the same mesh, with two different outline configurations, flying towards the
camera.
```shell
cargo run --example flying_objects
```
An outlined torus viewed through four cameras with different combinations of render layers
enabled.
```shell
cargo run --example render_layers
```
An animated jointed glTF model with an outline.
```shell
cargo run --example animated_fox
```
A glTF model with pre-baked outline normals.
```shell
cargo run --example hollow
```
An animated morphing glTF model with an outline.
```shell
cargo run --example morph_targets
```
## Versions
| This Version | Bevy version |
|--------------|--------------|
| 0.5.x | 0.11.x |
| 0.4.x | 0.10.x |
| 0.3.x | 0.9.x |
| 0.2.x | 0.8.x |
| 0.1.x | 0.7.x |
## Features
- `bevy_ui` _(default)_ - Adds a render graph edge to prevent clashing with the
UI. This adds a dependency on the `bevy_ui` crate and can be disabled if it is
not used.
## Licence
This crate is licensed under the Apache License, Version 2.0 (see
LICENCE-APACHE or <http://www.apache.org/licenses/LICENSE-2.0>) or the MIT
licence (see LICENCE-MIT or <http://opensource.org/licenses/MIT>), at your
option.
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.
-30
View File
@@ -1,30 +0,0 @@
{
"name": "bevy_mod_outline",
"$schema": "../node_modules/nx/schemas/project-schema.json",
"projectType": "library",
"sourceRoot": "./bevy_mod_outline/src",
"targets": {
"build": {
"executor": "@nxrs/cargo:build",
"options": {},
"configurations": {
"production": {
"release": true
}
}
},
"test": {
"executor": "@nxrs/cargo:test",
"options": {}
},
"lint": {
"executor": "@nxrs/cargo:clippy",
"options": {
"fix": false,
"failOnWarnings": true,
"noDeps": true
}
}
},
"tags": []
}
-108
View File
@@ -1,108 +0,0 @@
use bevy::prelude::*;
use crate::uniforms::DepthMode;
/// A component for storing the computed depth at which the outline lies.
#[derive(Clone, Component, Default)]
pub struct ComputedOutlineDepth {
pub(crate) world_origin: Vec3,
pub(crate) depth_mode: DepthMode,
pub(crate) inherited: Option<Entity>,
}
/// A component which specifies how the outline depth should be computed.
#[derive(Clone, Component)]
#[non_exhaustive]
pub enum SetOutlineDepth {
/// A flat plane facing the camera and intersecting the specified point in model-space.
Flat { model_origin: Vec3 },
/// Real model-space.
Real,
}
/// A component which specifies that this outline lies at the same depth as its parent.
#[derive(Clone, Component, Default)]
pub struct InheritOutlineDepth;
#[allow(clippy::type_complexity)]
pub(crate) fn compute_outline_depth(
mut root_query: Query<
(
Entity,
&mut ComputedOutlineDepth,
&GlobalTransform,
Changed<GlobalTransform>,
Option<(&SetOutlineDepth, Changed<SetOutlineDepth>)>,
Option<&Children>,
),
Without<InheritOutlineDepth>,
>,
mut computed_query: Query<&mut ComputedOutlineDepth, With<InheritOutlineDepth>>,
child_query: Query<&Children>,
) {
for (entity, mut computed, transform, changed_transform, set_depth, children) in
root_query.iter_mut()
{
let changed = !computed.depth_mode.is_valid()
|| computed.inherited.is_some()
|| changed_transform
|| set_depth.filter(|(_, c)| *c).is_some();
if changed {
let (origin, depth_mode) = if let Some((sd, _)) = set_depth {
match sd {
SetOutlineDepth::Flat {
model_origin: origin,
} => (*origin, DepthMode::Flat),
SetOutlineDepth::Real => (Vec3::NAN, DepthMode::Real),
}
} else {
(Vec3::ZERO, DepthMode::Flat)
};
let matrix = transform.compute_matrix();
computed.world_origin = matrix.project_point3(origin);
computed.depth_mode = depth_mode;
computed.inherited = None;
}
if let Some(cs) = children {
for child in cs.iter() {
propagate_outline_depth(
&computed,
changed,
entity,
*child,
&mut computed_query,
&child_query,
);
}
}
}
}
fn propagate_outline_depth(
root_computed: &ComputedOutlineDepth,
mut changed: bool,
parent: Entity,
entity: Entity,
computed_query: &mut Query<&mut ComputedOutlineDepth, With<InheritOutlineDepth>>,
child_query: &Query<&Children>,
) {
if let Ok(mut computed) = computed_query.get_mut(entity) {
changed |= !computed.depth_mode.is_valid() | (computed.inherited != Some(parent));
if changed {
*computed = root_computed.clone();
computed.inherited = Some(parent);
}
if let Ok(cs) = child_query.get(entity) {
for child in cs.iter() {
propagate_outline_depth(
root_computed,
changed,
entity,
*child,
computed_query,
child_query,
);
}
}
}
}
-194
View File
@@ -1,194 +0,0 @@
use bevy::pbr::{DrawMesh, SetMeshBindGroup, SetMeshViewBindGroup};
use bevy::prelude::*;
use bevy::render::render_asset::RenderAssets;
use bevy::render::render_phase::{DrawFunctions, RenderPhase, SetItemPipeline};
use bevy::render::render_resource::{PipelineCache, SpecializedMeshPipelines};
use bevy::render::renderer::RenderAdapterInfo;
use bevy::render::view::{ExtractedView, RenderLayers};
use wgpu_types::Backend;
use crate::node::{OpaqueOutline, StencilOutline, TransparentOutline};
use crate::pipeline::{OutlinePipeline, PassType, PipelineKey};
use crate::uniforms::{
DepthMode, OutlineFragmentUniform, OutlineStencilFlags, OutlineStencilUniform,
OutlineVolumeFlags, OutlineVolumeUniform, SetOutlineStencilBindGroup,
SetOutlineVolumeBindGroup,
};
use crate::view_uniforms::SetOutlineViewBindGroup;
use crate::OutlineRenderLayers;
pub(crate) type DrawStencil = (
SetItemPipeline,
SetMeshViewBindGroup<0>,
SetMeshBindGroup<1>,
SetOutlineViewBindGroup<2>,
SetOutlineStencilBindGroup<3>,
DrawMesh,
);
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
pub(crate) fn queue_outline_stencil_mesh(
stencil_draw_functions: Res<DrawFunctions<StencilOutline>>,
stencil_pipeline: Res<OutlinePipeline>,
msaa: Res<Msaa>,
mut pipelines: ResMut<SpecializedMeshPipelines<OutlinePipeline>>,
pipeline_cache: Res<PipelineCache>,
render_meshes: Res<RenderAssets<Mesh>>,
adapter_info: Res<RenderAdapterInfo>,
material_meshes: Query<(
Entity,
&Handle<Mesh>,
&OutlineStencilUniform,
&OutlineStencilFlags,
&OutlineRenderLayers,
)>,
mut views: Query<(
&ExtractedView,
&mut RenderPhase<StencilOutline>,
Option<&RenderLayers>,
)>,
) {
let draw_stencil = stencil_draw_functions
.read()
.get_id::<DrawStencil>()
.unwrap();
let base_key = PipelineKey::new()
.with_msaa(*msaa)
.with_pass_type(PassType::Stencil)
.with_opengl_workaround(adapter_info.0.backend == Backend::Gl);
for (view, mut stencil_phase, view_mask) in views.iter_mut() {
let rangefinder = view.rangefinder3d();
let view_mask = view_mask.copied().unwrap_or_default();
for (entity, mesh_handle, stencil_uniform, stencil_flags, outline_mask) in
material_meshes.iter()
{
if !view_mask.intersects(outline_mask) {
continue; // Layer not enabled
}
if stencil_flags.depth_mode == DepthMode::Invalid {
continue; // DepthMode not propagated
}
let Some(mesh) = render_meshes.get(mesh_handle) else {
continue; // No mesh
};
let key = base_key
.with_primitive_topology(mesh.primitive_topology)
.with_depth_mode(stencil_flags.depth_mode)
.with_offset_zero(stencil_uniform.offset == 0.0)
.with_morph_targets(mesh.morph_targets.is_some());
let Ok(pipeline) =
pipelines.specialize(&pipeline_cache, &stencil_pipeline, key, &mesh.layout)
else {
continue; // No pipeline
};
let distance = rangefinder.distance(&Mat4::from_translation(stencil_uniform.origin));
stencil_phase.add(StencilOutline {
entity,
pipeline,
draw_function: draw_stencil,
distance,
});
}
}
}
pub(crate) type DrawOutline = (
SetItemPipeline,
SetMeshViewBindGroup<0>,
SetMeshBindGroup<1>,
SetOutlineViewBindGroup<2>,
SetOutlineVolumeBindGroup<3>,
DrawMesh,
);
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
pub(crate) fn queue_outline_volume_mesh(
opaque_draw_functions: Res<DrawFunctions<OpaqueOutline>>,
transparent_draw_functions: Res<DrawFunctions<TransparentOutline>>,
outline_pipeline: Res<OutlinePipeline>,
msaa: Res<Msaa>,
mut pipelines: ResMut<SpecializedMeshPipelines<OutlinePipeline>>,
pipeline_cache: Res<PipelineCache>,
render_meshes: Res<RenderAssets<Mesh>>,
adapter_info: Res<RenderAdapterInfo>,
material_meshes: Query<(
Entity,
&Handle<Mesh>,
&OutlineVolumeUniform,
&OutlineVolumeFlags,
&OutlineFragmentUniform,
&OutlineRenderLayers,
)>,
mut views: Query<(
&ExtractedView,
&mut RenderPhase<OpaqueOutline>,
&mut RenderPhase<TransparentOutline>,
Option<&RenderLayers>,
)>,
) {
let draw_opaque_outline = opaque_draw_functions
.read()
.get_id::<DrawOutline>()
.unwrap();
let draw_transparent_outline = transparent_draw_functions
.read()
.get_id::<DrawOutline>()
.unwrap();
let base_key = PipelineKey::new()
.with_msaa(*msaa)
.with_opengl_workaround(adapter_info.0.backend == Backend::Gl);
for (view, mut opaque_phase, mut transparent_phase, view_mask) in views.iter_mut() {
let view_mask = view_mask.copied().unwrap_or_default();
let rangefinder = view.rangefinder3d();
for (entity, mesh_handle, volume_uniform, volume_flags, fragment_uniform, outline_mask) in
material_meshes.iter()
{
if !view_mask.intersects(outline_mask) {
continue; // Layer not enabled
}
if volume_flags.depth_mode == DepthMode::Invalid {
continue; // DepthMode not propagated
}
let Some(mesh) = render_meshes.get(mesh_handle) else {
continue; // No mesh
};
let transparent = fragment_uniform.colour[3] < 1.0;
let key = base_key
.with_primitive_topology(mesh.primitive_topology)
.with_pass_type(if transparent {
PassType::Transparent
} else {
PassType::Opaque
})
.with_depth_mode(volume_flags.depth_mode)
.with_offset_zero(volume_uniform.offset == 0.0)
.with_hdr_format(view.hdr)
.with_morph_targets(mesh.morph_targets.is_some());
let Ok(pipeline) =
pipelines.specialize(&pipeline_cache, &outline_pipeline, key, &mesh.layout)
else {
continue; // No pipeline
};
let distance = rangefinder.distance(&Mat4::from_translation(volume_uniform.origin));
if transparent {
transparent_phase.add(TransparentOutline {
entity,
pipeline,
draw_function: draw_transparent_outline,
distance,
});
} else {
opaque_phase.add(OpaqueOutline {
entity,
pipeline,
draw_function: draw_opaque_outline,
distance,
});
}
}
}
}
-32
View File
@@ -1,32 +0,0 @@
struct FragmentOutput {
@location(0) colour: vec4<f32>,
#ifdef OPENGL_WORKAROUND
@builtin(frag_depth) frag_depth: f32,
#endif
};
struct OutlineFragmentUniform {
@align(16)
colour: vec4<f32>,
};
#ifdef VOLUME
@group(3) @binding(1)
var<uniform> fstage: OutlineFragmentUniform;
#endif
@fragment
#ifdef OPENGL_WORKAROUND
fn fragment(@location(0) normalised_depth: f32) -> FragmentOutput {
#else
fn fragment() -> FragmentOutput {
#endif
var out: FragmentOutput;
#ifdef VOLUME
out.colour = fstage.colour;
#endif
#ifdef OPENGL_WORKAROUND
out.frag_depth = normalised_depth;
#endif
return out;
}
-173
View File
@@ -1,173 +0,0 @@
use bevy::{
prelude::*,
render::{
mesh::{Indices, VertexAttributeValues},
render_resource::{PrimitiveTopology, VertexFormat},
},
utils::{FloatOrd, HashMap, HashSet},
};
use crate::ATTRIBUTE_OUTLINE_NORMAL;
enum IndexIterator<'a> {
ExplicitU16(std::slice::Iter<'a, u16>),
ExplicitU32(std::slice::Iter<'a, u32>),
Implicit(std::ops::Range<usize>),
}
impl<'a> From<&'a Mesh> for IndexIterator<'a> {
fn from(value: &'a Mesh) -> Self {
match value.indices() {
Some(Indices::U16(vec)) => IndexIterator::ExplicitU16(vec.iter()),
Some(Indices::U32(vec)) => IndexIterator::ExplicitU32(vec.iter()),
None => IndexIterator::Implicit(0..value.count_vertices()),
}
}
}
impl Iterator for IndexIterator<'_> {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
match self {
IndexIterator::ExplicitU16(iter) => iter.next().map(|val| *val as usize),
IndexIterator::ExplicitU32(iter) => iter.next().map(|val| *val as usize),
IndexIterator::Implicit(iter) => iter.next(),
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self {
IndexIterator::ExplicitU16(iter) => iter.size_hint(),
IndexIterator::ExplicitU32(iter) => iter.size_hint(),
IndexIterator::Implicit(iter) => iter.size_hint(),
}
}
}
impl ExactSizeIterator for IndexIterator<'_> {}
/// Failed to generate outline normals for the mesh.
#[derive(thiserror::Error, Debug)]
pub enum GenerateOutlineNormalsError {
#[error("unsupported primitive topology '{0:?}'")]
UnsupportedPrimitiveTopology(PrimitiveTopology),
#[error("missing vertex attributes '{0}'")]
MissingVertexAttribute(&'static str),
#[error("the '{0}' vertex attribute should have {1:?} format, but had {2:?} format")]
InvalidVertexAttributeFormat(&'static str, VertexFormat, VertexFormat),
}
/// Extension methods for [`Mesh`].
pub trait OutlineMeshExt {
/// Generates outline normals for the mesh.
///
/// Vertex extrusion only works for meshes with smooth surface normals. Hard edges cause
/// visual artefacts. This function generates faux-smooth normals for outlining purposes
/// by grouping vertices by their position and averaging the normals at each point. These
/// outline normals are then inserted as a separate vertex attribute so that the regular
/// normals remain untouched. However, insofar as the outline normals are not
/// perpendicular to the surface of the mesh, this technique may result in non-uniform
/// outline thickness.
///
/// This function only supports meshes with TriangleList topology.
fn generate_outline_normals(&mut self) -> Result<(), GenerateOutlineNormalsError>;
}
impl OutlineMeshExt for Mesh {
fn generate_outline_normals(&mut self) -> Result<(), GenerateOutlineNormalsError> {
if self.primitive_topology() != PrimitiveTopology::TriangleList {
return Err(GenerateOutlineNormalsError::UnsupportedPrimitiveTopology(
self.primitive_topology(),
));
}
let positions = match self.attribute(Mesh::ATTRIBUTE_POSITION).ok_or(
GenerateOutlineNormalsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION.name),
)? {
VertexAttributeValues::Float32x3(p) => Ok(p),
v => Err(GenerateOutlineNormalsError::InvalidVertexAttributeFormat(
Mesh::ATTRIBUTE_POSITION.name,
VertexFormat::Float32x3,
v.into(),
)),
}?;
let normals = match self.attribute(Mesh::ATTRIBUTE_NORMAL) {
Some(VertexAttributeValues::Float32x3(p)) => Some(p),
_ => None,
};
let mut map = HashMap::<[FloatOrd; 3], Vec3>::with_capacity(positions.len());
let mut it = IndexIterator::from(&*self);
while let (Some(i0), Some(i1), Some(i2)) = (it.next(), it.next(), it.next()) {
for (j0, j1, j2) in [(i0, i1, i2), (i1, i2, i0), (i2, i0, i1)] {
let p0 = Vec3::from(positions[j0]);
let p1 = Vec3::from(positions[j1]);
let p2 = Vec3::from(positions[j2]);
let angle = (p1 - p0).angle_between(p2 - p0);
let n = map
.entry([FloatOrd(p0.x), FloatOrd(p0.y), FloatOrd(p0.z)])
.or_default();
*n += angle
* if let Some(ns) = normals {
// Use vertex normal
Vec3::from(ns[j0])
} else {
// Calculate face normal
(p1 - p0).cross(p2 - p0).normalize_or_zero()
};
}
}
let mut outlines = Vec::with_capacity(positions.len());
for p in positions.iter() {
let key = [FloatOrd(p[0]), FloatOrd(p[1]), FloatOrd(p[2])];
outlines.push(
map.get(&key)
.copied()
.unwrap_or(Vec3::ZERO)
.normalize_or_zero()
.to_array(),
);
}
self.insert_attribute(
ATTRIBUTE_OUTLINE_NORMAL,
VertexAttributeValues::Float32x3(outlines),
);
Ok(())
}
}
fn auto_generate_outline_normals(
mut meshes: ResMut<Assets<Mesh>>,
mut events: EventReader<'_, '_, AssetEvent<Mesh>>,
mut squelch: Local<HashSet<Handle<Mesh>>>,
) {
for event in events.iter() {
match event {
AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {
if squelch.contains(handle) {
// Suppress modification events created by this system
squelch.remove(handle);
} else if let Some(mesh) = meshes.get_mut(handle) {
let _ = mesh.generate_outline_normals();
squelch.insert(handle.clone_weak());
}
}
AssetEvent::Removed { handle } => {
squelch.remove(handle);
}
}
}
}
/// Automatically runs [`generate_outline_normals`](OutlineMeshExt::generate_outline_normals)
/// on every mesh.
///
/// This is provided as a convenience for simple projects. It runs the outline normal
/// generator every time a mesh asset is created or modified without consideration for
/// whether this is necessary or appropriate.
pub struct AutoGenerateOutlineNormalsPlugin;
impl Plugin for AutoGenerateOutlineNormalsPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, auto_generate_outline_normals);
}
}
-274
View File
@@ -1,274 +0,0 @@
//! This crate provides a Bevy plugin, [`OutlinePlugin`], and associated components for
//! rendering outlines around meshes using the vertex extrusion method.
//!
//! Outlines are rendered in a seperate pass following the main 3D pass. The effect of this
//! pass is to present the outlines in depth sorted order according to the model translation
//! of each mesh. This ensures that outlines are not clipped by other geometry.
//!
//! The [`OutlineVolume`] component will, by itself, cover the original object entirely with
//! the outline colour. The [`OutlineStencil`] component must also be added to prevent the body
//! of an object from being filled it. This must be added to any entity which needs to appear on
//! top of an outline.
//!
//! The [`OutlineBundle`] and [`OutlineStencilBundle`] bundles can be used to add the right
//! components, including the required [`ComputedOutlineDepth`] component. Optionally, the
//! [`SetOutlineDepth`] and [`InheritOutlineDepth`] components may also be added to control the
//! depth ordering of outlines.
//!
//! Vertex extrusion works best with meshes that have smooth surfaces. To avoid visual
//! artefacts when outlining meshes with hard edges, see the
//! [`OutlineMeshExt::generate_outline_normals`] function and the
//! [`AutoGenerateOutlineNormalsPlugin`].
use bevy::asset::load_internal_asset;
use bevy::prelude::*;
use bevy::render::extract_component::{
ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
};
use bevy::render::mesh::MeshVertexAttribute;
use bevy::render::render_graph::RenderGraph;
use bevy::render::render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions};
use bevy::render::render_resource::{SpecializedMeshPipelines, VertexFormat};
use bevy::render::view::RenderLayers;
use bevy::render::{Render, RenderApp, RenderSet};
use bevy::transform::TransformSystem;
use interpolation::Lerp;
use crate::draw::{
queue_outline_stencil_mesh, queue_outline_volume_mesh, DrawOutline, DrawStencil,
};
use crate::node::{OpaqueOutline, OutlineNode, StencilOutline, TransparentOutline};
use crate::pipeline::{OutlinePipeline, FRAGMENT_SHADER_HANDLE, OUTLINE_SHADER_HANDLE};
use crate::uniforms::{
extract_outline_stencil_uniforms, extract_outline_volume_uniforms,
queue_outline_stencil_bind_group, queue_outline_volume_bind_group, OutlineFragmentUniform,
OutlineStencilUniform, OutlineVolumeUniform,
};
use crate::view_uniforms::{
extract_outline_view_uniforms, queue_outline_view_bind_group, OutlineViewUniform,
};
mod computed;
mod draw;
mod generate;
mod node;
mod pipeline;
mod uniforms;
mod view_uniforms;
pub use computed::*;
pub use generate::*;
// See https://alexanderameye.github.io/notes/rendering-outlines/
/// The direction to extrude the vertex when rendering the outline.
pub const ATTRIBUTE_OUTLINE_NORMAL: MeshVertexAttribute =
MeshVertexAttribute::new("Outline_Normal", 1585570526, VertexFormat::Float32x3);
/// Name of the render graph node which draws the outlines.
///
/// This node runs after the main 3D passes and before the UI pass. The name can be used to
/// add additional constraints on node execution order with respect to other passes.
pub const OUTLINE_PASS_NODE_NAME: &str = "bevy_mod_outline_node";
/// A component for stenciling meshes during outline rendering.
#[derive(Clone, Component)]
pub struct OutlineStencil {
/// Enable rendering of the stencil
pub enabled: bool,
/// Offset of the stencil in logical pixels
pub offset: f32,
}
impl Default for OutlineStencil {
fn default() -> Self {
OutlineStencil {
enabled: true,
offset: 0.0,
}
}
}
fn lerp_bool(this: bool, other: bool, scalar: f32) -> bool {
if scalar <= 0.0 {
this
} else if scalar >= 1.0 {
other
} else {
this | other
}
}
impl Lerp for OutlineStencil {
type Scalar = f32;
fn lerp(&self, other: &Self, scalar: &Self::Scalar) -> Self {
OutlineStencil {
enabled: lerp_bool(self.enabled, other.enabled, *scalar),
offset: self.offset.lerp(&other.offset, scalar),
}
}
}
/// A component for rendering outlines around meshes.
#[derive(Clone, Component, Default)]
pub struct OutlineVolume {
/// Enable rendering of the outline
pub visible: bool,
/// Width of the outline in logical pixels
pub width: f32,
/// Colour of the outline
pub colour: Color,
}
impl Lerp for OutlineVolume {
type Scalar = f32;
fn lerp(&self, other: &Self, scalar: &Self::Scalar) -> Self {
OutlineVolume {
visible: lerp_bool(self.visible, other.visible, *scalar),
width: self.width.lerp(&other.width, scalar),
colour: {
let [r, g, b, a] = self
.colour
.as_linear_rgba_f32()
.lerp(&other.colour.as_linear_rgba_f32(), scalar);
Color::rgba_linear(r, g, b, a)
},
}
}
}
/// A component for specifying what layer(s) the outline should be rendered for.
#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut, Default)]
pub struct OutlineRenderLayers(pub RenderLayers);
impl ExtractComponent for OutlineRenderLayers {
type Query = (
Option<&'static OutlineRenderLayers>,
Option<&'static RenderLayers>,
);
type Filter = Or<(With<OutlineVolume>, With<OutlineStencil>)>;
type Out = Self;
fn extract_component(
(outline_mask, object_mask): (Option<&OutlineRenderLayers>, Option<&RenderLayers>),
) -> Option<Self> {
Some(
outline_mask
.copied()
.or_else(|| object_mask.copied().map(OutlineRenderLayers))
.unwrap_or_default(),
)
}
}
/// A bundle for rendering stenciled outlines around meshes.
#[derive(Bundle, Clone, Default)]
pub struct OutlineBundle {
pub outline: OutlineVolume,
pub stencil: OutlineStencil,
pub plane: ComputedOutlineDepth,
}
/// A bundle for stenciling meshes in the outlining pass.
#[derive(Bundle, Clone, Default)]
pub struct OutlineStencilBundle {
pub stencil: OutlineStencil,
pub plane: ComputedOutlineDepth,
}
/// Adds support for rendering outlines.
pub struct OutlinePlugin;
impl Plugin for OutlinePlugin {
fn build(&self, app: &mut App) {
load_internal_asset!(
app,
OUTLINE_SHADER_HANDLE,
"outline.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
FRAGMENT_SHADER_HANDLE,
"fragment.wgsl",
Shader::from_wgsl
);
app.add_plugins((
ExtractComponentPlugin::<OutlineRenderLayers>::default(),
UniformComponentPlugin::<OutlineStencilUniform>::default(),
UniformComponentPlugin::<OutlineVolumeUniform>::default(),
UniformComponentPlugin::<OutlineFragmentUniform>::default(),
UniformComponentPlugin::<OutlineViewUniform>::default(),
))
.add_systems(
PostUpdate,
compute_outline_depth.after(TransformSystem::TransformPropagate),
)
.sub_app_mut(RenderApp)
.init_resource::<DrawFunctions<StencilOutline>>()
.init_resource::<DrawFunctions<OpaqueOutline>>()
.init_resource::<DrawFunctions<TransparentOutline>>()
.init_resource::<SpecializedMeshPipelines<OutlinePipeline>>()
.add_render_command::<StencilOutline, DrawStencil>()
.add_render_command::<OpaqueOutline, DrawOutline>()
.add_render_command::<TransparentOutline, DrawOutline>()
.add_systems(ExtractSchedule, extract_outline_view_uniforms)
.add_systems(ExtractSchedule, extract_outline_stencil_uniforms)
.add_systems(ExtractSchedule, extract_outline_volume_uniforms)
.add_systems(
Render,
sort_phase_system::<StencilOutline>.in_set(RenderSet::PhaseSort),
)
.add_systems(
Render,
sort_phase_system::<OpaqueOutline>.in_set(RenderSet::PhaseSort),
)
.add_systems(
Render,
sort_phase_system::<TransparentOutline>.in_set(RenderSet::PhaseSort),
)
.add_systems(
Render,
queue_outline_view_bind_group.in_set(RenderSet::Queue),
)
.add_systems(
Render,
queue_outline_stencil_bind_group.in_set(RenderSet::Queue),
)
.add_systems(
Render,
queue_outline_volume_bind_group.in_set(RenderSet::Queue),
)
.add_systems(Render, queue_outline_stencil_mesh.in_set(RenderSet::Queue))
.add_systems(Render, queue_outline_volume_mesh.in_set(RenderSet::Queue));
let world = &mut app.sub_app_mut(RenderApp).world;
let node = OutlineNode::new(world);
let mut graph = world.resource_mut::<RenderGraph>();
let draw_3d_graph = graph
.get_sub_graph_mut(bevy::core_pipeline::core_3d::graph::NAME)
.unwrap();
draw_3d_graph.add_node(OUTLINE_PASS_NODE_NAME, node);
// Run after main 3D pass, but before UI psss
draw_3d_graph.add_node_edge(
bevy::core_pipeline::core_3d::graph::node::END_MAIN_PASS,
OUTLINE_PASS_NODE_NAME,
);
#[cfg(feature = "bevy_ui")]
draw_3d_graph.add_node_edge(
OUTLINE_PASS_NODE_NAME,
bevy::ui::draw_ui_graph::node::UI_PASS,
);
}
fn finish(&self, app: &mut App) {
app.sub_app_mut(RenderApp)
.init_resource::<OutlinePipeline>();
}
}
-227
View File
@@ -1,227 +0,0 @@
use std::cmp::Reverse;
use bevy::ecs::system::lifetimeless::Read;
use bevy::prelude::*;
use bevy::render::camera::ExtractedCamera;
use bevy::render::render_graph::NodeRunError;
use bevy::render::render_phase::{
CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem, RenderPhase,
};
use bevy::render::render_resource::{
CachedRenderPipelineId, LoadOp, Operations, RenderPassDepthStencilAttachment,
RenderPassDescriptor,
};
use bevy::render::view::{ExtractedView, ViewDepthTexture, ViewTarget};
use bevy::render::{
render_graph::{Node, RenderGraphContext},
renderer::RenderContext,
};
use bevy::utils::FloatOrd;
pub(crate) struct StencilOutline {
pub distance: f32,
pub pipeline: CachedRenderPipelineId,
pub entity: Entity,
pub draw_function: DrawFunctionId,
}
impl PhaseItem for StencilOutline {
type SortKey = Reverse<FloatOrd>;
#[inline]
fn entity(&self) -> Entity {
self.entity
}
fn sort_key(&self) -> Self::SortKey {
Reverse(FloatOrd(self.distance))
}
fn draw_function(&self) -> bevy::render::render_phase::DrawFunctionId {
self.draw_function
}
}
impl CachedRenderPipelinePhaseItem for StencilOutline {
#[inline]
fn cached_pipeline(&self) -> CachedRenderPipelineId {
self.pipeline
}
}
pub(crate) struct OpaqueOutline {
pub distance: f32,
pub pipeline: CachedRenderPipelineId,
pub entity: Entity,
pub draw_function: DrawFunctionId,
}
impl PhaseItem for OpaqueOutline {
type SortKey = Reverse<FloatOrd>;
#[inline]
fn entity(&self) -> Entity {
self.entity
}
fn sort_key(&self) -> Self::SortKey {
Reverse(FloatOrd(self.distance))
}
fn draw_function(&self) -> bevy::render::render_phase::DrawFunctionId {
self.draw_function
}
}
impl CachedRenderPipelinePhaseItem for OpaqueOutline {
#[inline]
fn cached_pipeline(&self) -> CachedRenderPipelineId {
self.pipeline
}
}
pub(crate) struct TransparentOutline {
pub distance: f32,
pub pipeline: CachedRenderPipelineId,
pub entity: Entity,
pub draw_function: DrawFunctionId,
}
impl PhaseItem for TransparentOutline {
type SortKey = FloatOrd;
#[inline]
fn entity(&self) -> Entity {
self.entity
}
fn sort_key(&self) -> Self::SortKey {
FloatOrd(self.distance)
}
fn draw_function(&self) -> bevy::render::render_phase::DrawFunctionId {
self.draw_function
}
}
impl CachedRenderPipelinePhaseItem for TransparentOutline {
#[inline]
fn cached_pipeline(&self) -> CachedRenderPipelineId {
self.pipeline
}
}
#[allow(clippy::type_complexity)]
pub(crate) struct OutlineNode {
query: QueryState<
(
Read<ExtractedCamera>,
Read<RenderPhase<StencilOutline>>,
Read<RenderPhase<OpaqueOutline>>,
Read<RenderPhase<TransparentOutline>>,
Read<Camera3d>,
Read<ViewTarget>,
Read<ViewDepthTexture>,
),
With<ExtractedView>,
>,
}
impl OutlineNode {
pub(crate) fn new(world: &mut World) -> Self {
Self {
query: world.query_filtered(),
}
}
}
impl Node for OutlineNode {
fn update(&mut self, world: &mut World) {
self.query.update_archetypes(world);
}
fn run(
&self,
graph: &mut RenderGraphContext,
render_context: &mut RenderContext,
world: &World,
) -> Result<(), NodeRunError> {
let view_entity = graph.view_entity();
let (camera, stencil_phase, opaque_phase, transparent_phase, camera_3d, target, depth) =
match self.query.get_manual(world, view_entity) {
Ok(query) => query,
Err(_) => {
return Ok(());
} // No window
};
// If drawing anything, run stencil pass to clear the depth buffer
if !opaque_phase.items.is_empty() || !transparent_phase.items.is_empty() {
let pass_descriptor = RenderPassDescriptor {
label: Some("outline_stencil_pass"),
color_attachments: &[],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &depth.view,
depth_ops: Some(Operations {
load: camera_3d.depth_load_op.clone().into(),
store: true,
}),
stencil_ops: None,
}),
};
let mut tracked_pass = render_context.begin_tracked_render_pass(pass_descriptor);
if let Some(viewport) = camera.viewport.as_ref() {
tracked_pass.set_camera_viewport(viewport);
}
stencil_phase.render(&mut tracked_pass, world, view_entity);
}
if !opaque_phase.items.is_empty() {
let pass_descriptor = RenderPassDescriptor {
label: Some("outline_opaque_pass"),
color_attachments: &[Some(target.get_color_attachment(Operations {
load: LoadOp::Load,
store: true,
}))],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &depth.view,
depth_ops: Some(Operations {
load: LoadOp::Load,
store: true,
}),
stencil_ops: None,
}),
};
let mut tracked_pass = render_context.begin_tracked_render_pass(pass_descriptor);
if let Some(viewport) = camera.viewport.as_ref() {
tracked_pass.set_camera_viewport(viewport);
}
opaque_phase.render(&mut tracked_pass, world, view_entity);
}
if !transparent_phase.items.is_empty() {
let pass_descriptor = RenderPassDescriptor {
label: Some("outline_transparent_pass"),
color_attachments: &[Some(target.get_color_attachment(Operations {
load: LoadOp::Load,
store: true,
}))],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &depth.view,
depth_ops: Some(Operations {
load: LoadOp::Load,
store: true,
}),
stencil_ops: None,
}),
};
let mut tracked_pass = render_context.begin_tracked_render_pass(pass_descriptor);
if let Some(viewport) = camera.viewport.as_ref() {
tracked_pass.set_camera_viewport(viewport);
}
transparent_phase.render(&mut tracked_pass, world, view_entity);
}
Ok(())
}
}
-114
View File
@@ -1,114 +0,0 @@
#import bevy_render::view View
#import bevy_pbr::mesh_types Mesh
#import bevy_pbr::mesh_types SkinnedMesh
struct Vertex {
#ifdef VERTEX_POSITIONS
@location(0) position: vec3<f32>,
#endif
#ifndef OFFSET_ZERO
@location(1) outline_normal: vec3<f32>,
#endif
#ifdef SKINNED
@location(5) joint_indices: vec4<u32>,
@location(6) joint_weights: vec4<f32>,
#endif
#ifdef MORPH_TARGETS
@builtin(vertex_index) index: u32,
#endif
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
#ifdef OPENGL_WORKAROUND
@location(0) normalised_depth: f32,
#endif
};
struct OutlineViewUniform {
@align(16)
scale: vec2<f32>,
};
struct OutlineVertexUniform {
@align(16)
origin: vec3<f32>,
offset: f32,
};
@group(0) @binding(0)
var<uniform> view: View;
@group(1) @binding(0)
var<uniform> mesh: Mesh;
#import bevy_pbr::skinning
#import bevy_pbr::morph
@group(2) @binding(0)
var<uniform> view_uniform: OutlineViewUniform;
@group(3) @binding(0)
var<uniform> vstage: OutlineVertexUniform;
#ifdef MORPH_TARGETS
fn morph_vertex(vertex_in: Vertex) -> Vertex {
var vertex = vertex_in;
let weight_count = bevy_pbr::morph::layer_count();
for (var i: u32 = 0u; i < weight_count; i ++) {
let weight = bevy_pbr::morph::weight_at(i);
if weight == 0.0 {
continue;
}
vertex.position += weight * bevy_pbr::morph::morph(vertex.index, bevy_pbr::morph::position_offset, i);
}
return vertex;
}
#endif
fn mat4to3(m: mat4x4<f32>) -> mat3x3<f32> {
return mat3x3<f32>(
m[0].xyz, m[1].xyz, m[2].xyz
);
}
fn model_origin_z(plane: vec3<f32>, view_proj: mat4x4<f32>) -> f32 {
var proj_zw = mat4x2<f32>(
view_proj[0].zw, view_proj[1].zw,
view_proj[2].zw, view_proj[3].zw);
var zw = proj_zw * vec4<f32>(plane, 1.0);
return zw.x / zw.y;
}
@vertex
fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
#ifdef MORPH_TARGETS
var vertex = morph_vertex(vertex_no_morph);
#else
var vertex = vertex_no_morph;
#endif
#ifdef SKINNED
let model = bevy_pbr::skinning::skin_model(vertex.joint_indices, vertex.joint_weights);
#else
let model = mesh.model;
#endif
let clip_pos = view.view_proj * (model * vec4<f32>(vertex.position, 1.0));
#ifdef FLAT_DEPTH
let out_z = model_origin_z(vstage.origin, view.view_proj) * clip_pos.w;
#else
let out_z = clip_pos.z;
#endif
#ifdef OFFSET_ZERO
let out_xy = clip_pos.xy;
#else
let clip_norm = mat4to3(view.view_proj) * (mat4to3(model) * vertex.outline_normal);
let ndc_delta = vstage.offset * normalize(clip_norm.xy) * view_uniform.scale * clip_pos.w;
let out_xy = clip_pos.xy + ndc_delta;
#endif
var out: VertexOutput;
out.position = vec4<f32>(out_xy, out_z, clip_pos.w);
#ifdef OPENGL_WORKAROUND
out.normalised_depth = 10.0 + 0.5 * (out_z / clip_pos.w);
#endif
return out;
}
-369
View File
@@ -1,369 +0,0 @@
use std::borrow::Cow;
use bevy::pbr::{setup_morph_and_skinning_defs, MeshPipelineKey};
use bevy::prelude::*;
use bevy::reflect::TypeUuid;
use bevy::render::render_resource::{
BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BlendState,
BufferBindingType, BufferSize, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState,
DepthStencilState, Face, FragmentState, FrontFace, MultisampleState, PolygonMode,
PrimitiveState, PrimitiveTopology, ShaderDefVal, ShaderSize, ShaderStages, StencilState,
TextureFormat, VertexState,
};
use bevy::render::renderer::RenderDevice;
use bevy::render::texture::BevyDefault;
use bevy::render::view::ViewTarget;
use bevy::{
pbr::MeshPipeline,
render::{
mesh::MeshVertexBufferLayout,
render_resource::{
RenderPipelineDescriptor, SpecializedMeshPipeline, SpecializedMeshPipelineError,
},
},
};
use bitfield::{bitfield_bitrange, bitfield_fields};
use crate::uniforms::{
DepthMode, OutlineFragmentUniform, OutlineStencilUniform, OutlineVolumeUniform,
};
use crate::view_uniforms::OutlineViewUniform;
use crate::ATTRIBUTE_OUTLINE_NORMAL;
pub(crate) const OUTLINE_SHADER_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 2101625026478770097);
pub(crate) const FRAGMENT_SHADER_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 12033806834125368121);
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum PassType {
Stencil = 1,
Opaque = 2,
Transparent = 3,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PipelineKey(u32);
bitfield_bitrange! {struct PipelineKey(u32)}
impl PipelineKey {
bitfield_fields! {
u32;
msaa_samples_minus_one, set_msaa_samples_minus_one: 5, 0;
primitive_topology_int, set_primitive_topology_int: 8, 6;
pass_type_int, set_pass_type_int: 10, 9;
depth_mode_int, set_depth_mode_int: 12, 11;
pub offset_zero, set_offset_zero: 13;
pub hdr_format, set_hdr_format: 14;
pub opengl_workaround, set_opengl_workaround: 15;
pub morph_targets, set_morph_targets: 16;
}
pub(crate) fn new() -> Self {
PipelineKey(0)
}
pub(crate) fn with_msaa(mut self, msaa: Msaa) -> Self {
self.set_msaa_samples_minus_one(msaa as u32 - 1);
self
}
pub(crate) fn msaa(&self) -> Msaa {
match self.msaa_samples_minus_one() + 1 {
x if x == Msaa::Off as u32 => Msaa::Off,
x if x == Msaa::Sample2 as u32 => Msaa::Sample2,
x if x == Msaa::Sample4 as u32 => Msaa::Sample4,
x if x == Msaa::Sample8 as u32 => Msaa::Sample8,
x => panic!("Invalid value for Msaa: {}", x),
}
}
pub(crate) fn with_primitive_topology(mut self, primitive_topology: PrimitiveTopology) -> Self {
self.set_primitive_topology_int(primitive_topology as u32);
self
}
pub(crate) fn primitive_topology(&self) -> PrimitiveTopology {
match self.primitive_topology_int() {
x if x == PrimitiveTopology::PointList as u32 => PrimitiveTopology::PointList,
x if x == PrimitiveTopology::LineList as u32 => PrimitiveTopology::LineList,
x if x == PrimitiveTopology::LineStrip as u32 => PrimitiveTopology::LineStrip,
x if x == PrimitiveTopology::TriangleList as u32 => PrimitiveTopology::TriangleList,
x if x == PrimitiveTopology::TriangleStrip as u32 => PrimitiveTopology::TriangleStrip,
x => panic!("Invalid value for PrimitiveTopology: {}", x),
}
}
pub(crate) fn with_pass_type(mut self, pass_type: PassType) -> Self {
self.set_pass_type_int(pass_type as u32);
self
}
pub(crate) fn pass_type(&self) -> PassType {
match self.pass_type_int() {
x if x == PassType::Stencil as u32 => PassType::Stencil,
x if x == PassType::Opaque as u32 => PassType::Opaque,
x if x == PassType::Transparent as u32 => PassType::Transparent,
x => panic!("Invalid value for PassType: {}", x),
}
}
pub(crate) fn with_depth_mode(mut self, depth_mode: DepthMode) -> Self {
self.set_depth_mode_int(depth_mode as u32);
self
}
pub(crate) fn depth_mode(&self) -> DepthMode {
match self.depth_mode_int() {
x if x == DepthMode::Flat as u32 => DepthMode::Flat,
x if x == DepthMode::Real as u32 => DepthMode::Real,
x => panic!("Invalid value for DepthMode: {}", x),
}
}
pub(crate) fn with_offset_zero(mut self, offset_zero: bool) -> Self {
self.set_offset_zero(offset_zero);
self
}
pub(crate) fn with_hdr_format(mut self, hdr_format: bool) -> Self {
self.set_hdr_format(hdr_format);
self
}
pub(crate) fn with_opengl_workaround(mut self, opengl_workaround: bool) -> Self {
self.set_opengl_workaround(opengl_workaround);
self
}
pub(crate) fn with_morph_targets(mut self, morph_targets: bool) -> Self {
self.set_morph_targets(morph_targets);
self
}
}
impl From<PipelineKey> for MeshPipelineKey {
fn from(key: PipelineKey) -> Self {
if key.morph_targets() {
MeshPipelineKey::empty() | MeshPipelineKey::MORPH_TARGETS
} else {
MeshPipelineKey::empty()
}
}
}
#[derive(Resource)]
pub(crate) struct OutlinePipeline {
mesh_pipeline: MeshPipeline,
pub outline_view_bind_group_layout: BindGroupLayout,
pub outline_stencil_bind_group_layout: BindGroupLayout,
pub outline_volume_bind_group_layout: BindGroupLayout,
}
impl FromWorld for OutlinePipeline {
fn from_world(world: &mut World) -> Self {
let world = world.cell();
let mesh_pipeline = world.get_resource::<MeshPipeline>().unwrap().clone();
let render_device = world.get_resource::<RenderDevice>().unwrap();
let outline_view_bind_group_layout =
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: Some("outline_view_bind_group_layout"),
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: BufferSize::new(OutlineViewUniform::SHADER_SIZE.get()),
},
count: None,
}],
});
let outline_volume_bind_group_layout =
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: Some("outline_volume_bind_group_layout"),
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: BufferSize::new(
OutlineVolumeUniform::SHADER_SIZE.get(),
),
},
count: None,
},
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: BufferSize::new(
OutlineFragmentUniform::SHADER_SIZE.get(),
),
},
count: None,
},
],
});
let outline_stencil_bind_group_layout =
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: Some("outline_stencil_bind_group_layout"),
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: BufferSize::new(OutlineStencilUniform::SHADER_SIZE.get()),
},
count: None,
}],
});
OutlinePipeline {
mesh_pipeline,
outline_view_bind_group_layout,
outline_stencil_bind_group_layout,
outline_volume_bind_group_layout,
}
}
}
impl SpecializedMeshPipeline for OutlinePipeline {
type Key = PipelineKey;
fn specialize(
&self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let mut targets = vec![];
let mut vertex_defs = vec!["MESH_BINDGROUP_1".into()];
let mut fragment_defs = vec![];
let mut buffer_attrs = Vec::new();
if layout.contains(Mesh::ATTRIBUTE_POSITION) {
vertex_defs.push("VERTEX_POSITIONS".into());
buffer_attrs.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0));
}
let mut bind_layouts = vec![if key.msaa() == Msaa::Off {
self.mesh_pipeline.view_layout.clone()
} else {
self.mesh_pipeline.view_layout_multisampled.clone()
}];
bind_layouts.push(setup_morph_and_skinning_defs(
&self.mesh_pipeline.mesh_layouts,
layout,
5,
&key.into(),
&mut vertex_defs,
&mut buffer_attrs,
));
bind_layouts.push(self.outline_view_bind_group_layout.clone());
let cull_mode;
if key.depth_mode() == DepthMode::Flat {
vertex_defs.push(ShaderDefVal::from("FLAT_DEPTH"));
cull_mode = Some(Face::Back);
} else if key.pass_type() == PassType::Stencil {
cull_mode = Some(Face::Back);
} else {
cull_mode = Some(Face::Front);
}
if key.offset_zero() {
vertex_defs.push(ShaderDefVal::from("OFFSET_ZERO"));
} else {
buffer_attrs.push(
if layout.contains(ATTRIBUTE_OUTLINE_NORMAL) {
ATTRIBUTE_OUTLINE_NORMAL
} else {
Mesh::ATTRIBUTE_NORMAL
}
.at_shader_location(1),
);
}
match key.pass_type() {
PassType::Stencil => {
bind_layouts.push(self.outline_stencil_bind_group_layout.clone());
}
PassType::Opaque | PassType::Transparent => {
fragment_defs.push(ShaderDefVal::from("VOLUME"));
targets.push(Some(ColorTargetState {
format: if key.hdr_format() {
ViewTarget::TEXTURE_FORMAT_HDR
} else {
TextureFormat::bevy_default()
},
blend: Some(if key.pass_type() == PassType::Transparent {
BlendState::ALPHA_BLENDING
} else {
BlendState::REPLACE
}),
write_mask: ColorWrites::ALL,
}));
bind_layouts.push(self.outline_volume_bind_group_layout.clone());
}
}
if key.opengl_workaround() {
let val = ShaderDefVal::from("OPENGL_WORKAROUND");
vertex_defs.push(val.clone());
fragment_defs.push(val);
}
let buffers = vec![layout.get_layout(&buffer_attrs)?];
Ok(RenderPipelineDescriptor {
vertex: VertexState {
shader: OUTLINE_SHADER_HANDLE.typed::<Shader>(),
entry_point: "vertex".into(),
shader_defs: vertex_defs,
buffers,
},
fragment: Some(FragmentState {
shader: FRAGMENT_SHADER_HANDLE.typed::<Shader>(),
shader_defs: fragment_defs,
entry_point: "fragment".into(),
targets,
}),
layout: bind_layouts,
primitive: PrimitiveState {
front_face: FrontFace::Ccw,
cull_mode,
unclipped_depth: false,
polygon_mode: PolygonMode::Fill,
conservative: false,
topology: key.primitive_topology(),
strip_index_format: None,
},
depth_stencil: Some(DepthStencilState {
format: TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: CompareFunction::Greater,
stencil: StencilState::default(),
bias: if key.depth_mode() == DepthMode::Flat && key.pass_type() == PassType::Stencil
{
DepthBiasState {
// Values determined empirically
constant: 3,
slope_scale: 1.0,
..default()
}
} else {
default()
},
}),
multisample: MultisampleState {
count: key.msaa().samples(),
mask: !0,
alpha_to_coverage_enabled: false,
},
push_constant_ranges: default(),
label: Some(Cow::Borrowed("outline_pipeline")),
})
}
}
-212
View File
@@ -1,212 +0,0 @@
use bevy::{
ecs::system::{
lifetimeless::{Read, SRes},
SystemParamItem,
},
prelude::*,
render::{
extract_component::{ComponentUniforms, DynamicUniformIndex},
render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass},
render_resource::{BindGroup, BindGroupDescriptor, BindGroupEntry, ShaderType},
renderer::RenderDevice,
Extract,
},
};
use crate::{
node::StencilOutline, pipeline::OutlinePipeline, ComputedOutlineDepth, OutlineStencil,
OutlineVolume,
};
#[derive(Clone, Component, ShaderType)]
pub(crate) struct OutlineStencilUniform {
#[align(16)]
pub origin: Vec3,
pub offset: f32,
}
#[derive(Clone, Component, ShaderType)]
pub(crate) struct OutlineVolumeUniform {
#[align(16)]
pub origin: Vec3,
pub offset: f32,
}
#[derive(Clone, Component, ShaderType)]
pub(crate) struct OutlineFragmentUniform {
#[align(16)]
pub colour: Vec4,
}
#[derive(Clone, Copy, Default, PartialEq)]
pub(crate) enum DepthMode {
#[default]
Invalid = 0,
Flat = 1,
Real = 2,
}
impl DepthMode {
pub fn is_valid(&self) -> bool {
*self != DepthMode::Invalid
}
}
#[derive(Component)]
pub(crate) struct OutlineStencilFlags {
pub depth_mode: DepthMode,
}
#[derive(Component)]
pub(crate) struct OutlineVolumeFlags {
pub depth_mode: DepthMode,
}
#[derive(Resource)]
pub(crate) struct OutlineStencilBindGroup {
pub bind_group: BindGroup,
}
#[derive(Resource)]
pub(crate) struct OutlineVolumeBindGroup {
pub bind_group: BindGroup,
}
pub(crate) fn extract_outline_stencil_uniforms(
mut commands: Commands,
query: Extract<Query<(Entity, &OutlineStencil, &ComputedOutlineDepth)>>,
) {
for (entity, stencil, computed) in query.iter() {
if !stencil.enabled {
continue;
}
commands
.get_or_spawn(entity)
.insert(OutlineStencilUniform {
origin: computed.world_origin,
offset: stencil.offset,
})
.insert(OutlineStencilFlags {
depth_mode: computed.depth_mode,
});
}
}
pub(crate) fn extract_outline_volume_uniforms(
mut commands: Commands,
query: Extract<Query<(Entity, &OutlineVolume, &ComputedOutlineDepth)>>,
) {
for (entity, outline, computed) in query.iter() {
if !outline.visible || outline.colour.a() == 0.0 {
continue;
}
commands
.get_or_spawn(entity)
.insert(OutlineVolumeUniform {
origin: computed.world_origin,
offset: outline.width,
})
.insert(OutlineFragmentUniform {
colour: outline.colour.as_linear_rgba_f32().into(),
})
.insert(OutlineVolumeFlags {
depth_mode: computed.depth_mode,
});
}
}
pub(crate) fn queue_outline_stencil_bind_group(
mut commands: Commands,
render_device: Res<RenderDevice>,
outline_pipeline: Res<OutlinePipeline>,
vertex: Res<ComponentUniforms<OutlineStencilUniform>>,
) {
if let Some(vertex_binding) = vertex.binding() {
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
entries: &[BindGroupEntry {
binding: 0,
resource: vertex_binding.clone(),
}],
label: Some("outline_stencil_bind_group"),
layout: &outline_pipeline.outline_stencil_bind_group_layout,
});
commands.insert_resource(OutlineStencilBindGroup { bind_group });
}
}
pub(crate) fn queue_outline_volume_bind_group(
mut commands: Commands,
render_device: Res<RenderDevice>,
outline_pipeline: Res<OutlinePipeline>,
vertex: Res<ComponentUniforms<OutlineVolumeUniform>>,
fragment: Res<ComponentUniforms<OutlineFragmentUniform>>,
) {
if let (Some(vertex_binding), Some(fragment_binding)) = (vertex.binding(), fragment.binding()) {
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
entries: &[
BindGroupEntry {
binding: 0,
resource: vertex_binding.clone(),
},
BindGroupEntry {
binding: 1,
resource: fragment_binding.clone(),
},
],
label: Some("outline_volume_bind_group"),
layout: &outline_pipeline.outline_volume_bind_group_layout,
});
commands.insert_resource(OutlineVolumeBindGroup { bind_group });
}
}
pub(crate) struct SetOutlineStencilBindGroup<const I: usize>();
impl<const I: usize> RenderCommand<StencilOutline> for SetOutlineStencilBindGroup<I> {
type ViewWorldQuery = ();
type ItemWorldQuery = Read<DynamicUniformIndex<OutlineStencilUniform>>;
type Param = SRes<OutlineStencilBindGroup>;
fn render<'w>(
_item: &StencilOutline,
_view_data: (),
entity_data: &DynamicUniformIndex<OutlineStencilUniform>,
bind_group: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
pass.set_bind_group(
I,
&bind_group.into_inner().bind_group,
&[entity_data.index()],
);
RenderCommandResult::Success
}
}
pub(crate) struct SetOutlineVolumeBindGroup<const I: usize>();
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetOutlineVolumeBindGroup<I> {
type ViewWorldQuery = ();
type ItemWorldQuery = (
Read<DynamicUniformIndex<OutlineVolumeUniform>>,
Read<DynamicUniformIndex<OutlineFragmentUniform>>,
);
type Param = SRes<OutlineVolumeBindGroup>;
fn render<'w>(
_item: &P,
_view_data: (),
entity_data: (
&DynamicUniformIndex<OutlineVolumeUniform>,
&DynamicUniformIndex<OutlineFragmentUniform>,
),
bind_group: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let (vertex, fragment) = entity_data;
pass.set_bind_group(
I,
&bind_group.into_inner().bind_group,
&[vertex.index(), fragment.index()],
);
RenderCommandResult::Success
}
}
-87
View File
@@ -1,87 +0,0 @@
use bevy::ecs::system::lifetimeless::{Read, SRes};
use bevy::ecs::system::SystemParamItem;
use bevy::prelude::*;
use bevy::render::extract_component::{ComponentUniforms, DynamicUniformIndex};
use bevy::render::render_phase::{
PhaseItem, RenderCommand, RenderCommandResult, RenderPhase, TrackedRenderPass,
};
use bevy::render::render_resource::ShaderType;
use bevy::render::render_resource::{BindGroup, BindGroupDescriptor, BindGroupEntry};
use bevy::render::renderer::RenderDevice;
use bevy::render::view::RenderLayers;
use bevy::render::Extract;
use crate::node::{OpaqueOutline, StencilOutline, TransparentOutline};
use crate::pipeline::OutlinePipeline;
#[derive(Clone, Component, ShaderType)]
pub(crate) struct OutlineViewUniform {
#[align(16)]
scale: Vec2,
}
#[derive(Resource)]
pub(crate) struct OutlineViewBindGroup {
bind_group: BindGroup,
}
#[allow(clippy::type_complexity)]
pub(crate) fn extract_outline_view_uniforms(
mut commands: Commands,
query: Extract<Query<(Entity, &Camera, Option<&RenderLayers>), With<Camera3d>>>,
) {
for (entity, camera, view_mask) in query.iter() {
if !camera.is_active {
continue;
}
if let Some(size) = camera.logical_viewport_size() {
let mut entity_commands = commands.get_or_spawn(entity);
entity_commands
.insert(OutlineViewUniform { scale: 2.0 / size })
.insert(RenderPhase::<StencilOutline>::default())
.insert(RenderPhase::<OpaqueOutline>::default())
.insert(RenderPhase::<TransparentOutline>::default());
if let Some(view_mask) = view_mask {
entity_commands.insert(*view_mask);
}
}
}
}
pub(crate) fn queue_outline_view_bind_group(
mut commands: Commands,
render_device: Res<RenderDevice>,
outline_pipeline: Res<OutlinePipeline>,
view_uniforms: Res<ComponentUniforms<OutlineViewUniform>>,
) {
if let Some(view_binding) = view_uniforms.binding() {
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
entries: &[BindGroupEntry {
binding: 0,
resource: view_binding.clone(),
}],
label: Some("outline_view_bind_group"),
layout: &outline_pipeline.outline_view_bind_group_layout,
});
commands.insert_resource(OutlineViewBindGroup { bind_group });
}
}
pub(crate) struct SetOutlineViewBindGroup<const I: usize>();
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetOutlineViewBindGroup<I> {
type ViewWorldQuery = Read<DynamicUniformIndex<OutlineViewUniform>>;
type ItemWorldQuery = ();
type Param = SRes<OutlineViewBindGroup>;
fn render<'w>(
_item: &P,
view_data: &DynamicUniformIndex<OutlineViewUniform>,
_entity_data: (),
bind_group: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
pass.set_bind_group(I, &bind_group.into_inner().bind_group, &[view_data.index()]);
RenderCommandResult::Success
}
}
@@ -22,39 +22,40 @@ function getFiles(dir, files_): string[] {
}
const mimeTypes = {
".html": "text/html",
".js": "text/javascript",
".7z": "application/x-7z-compressed",
".bin": "application/octet-stream",
".css": "text/css",
".png": "image/png",
".jpg": "image/jpeg",
".eot": "font/eot",
".gif": "image/gif",
".gltf": "model/gltf+json",
".gz": "application/gzip",
".hdr": "image/vnd.radiance",
".html": "text/html",
".ico": "image/x-icon",
".jpg": "image/jpeg",
".js": "text/javascript",
".json": "application/json",
".ktx": "image/ktx",
".ktx2": "image/ktx",
".svg": "image/svg+xml",
".json": "application/json",
".woff": "font/woff",
".woff2": "font/woff2",
".ttf": "font/ttf",
".eot": "font/eot",
".otf": "font/otf",
".wasm": "application/wasm",
".meta": "text/plain",
".mjs": "text/javascript",
".txt": "text/plain",
".xml": "text/xml",
".mp3": "audio/mpeg",
".mp4": "video/mp4",
".otf": "font/otf",
".png": "image/png",
".rar": "application/x-rar-compressed",
".svg": "image/svg+xml",
".tiff": "image/tiff",
".ttf": "font/ttf",
".txt": "text/plain",
".wasm": "application/wasm",
".webm": "video/webm",
".webp": "image/webp",
".wgsl": "text/wgsl",
".ico": "image/x-icon",
".tiff": "image/tiff",
".gz": "application/gzip",
".woff": "font/woff",
".woff2": "font/woff2",
".xml": "text/xml",
".zip": "application/zip",
".rar": "application/x-rar-compressed",
".7z": "application/x-7z-compressed",
".bin": "application/octet-stream",
}
export function ServerMiddleWare(server: ViteDevServer, assets: string[] = []) {
+5 -8
View File
@@ -13,10 +13,10 @@ name = "studio-standalone"
path = "src/main.rs"
[dependencies]
bevy-inspector-egui = "0.20.0"
bevy_mod_raycast = "0.15.0"
bevy_rapier3d = "0.22.0"
# bevy_mod_outline = "0.5.0"
bevy-inspector-egui = "0.21.0"
bevy_mod_raycast = "0.16.0"
bevy_rapier3d = "0.23.0"
bevy_mod_outline = "0.6.0"
bitflags = "2.4.0"
console_error_panic_hook = { version = "0.1.7", optional = true }
js-sys = { version = "0.3.65", optional = true }
@@ -47,11 +47,8 @@ wasm = [
[dependencies.bevy]
workspace = true
[dependencies.bevy_mod_outline]
path = "../bevy_mod_outline"
[dependencies.bevy_mod_picking]
version = "0.16.0"
version = "0.17.0"
default-features = false
features = [
"backend_bevy_ui",
@@ -0,0 +1,7 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_gltf::loader::GltfLoader",
settings: (),
),
)
Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
(
meta_format_version: "1.0",
asset: Ignore,
)
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_gltf::loader::GltfLoader",
settings: (),
),
)
+2 -2
View File
@@ -1,4 +1,4 @@
#import bevy_pbr::mesh_vertex_output MeshVertexOutput
#import bevy_pbr::forward_io::VertexOutput
fn inv_lerp(a: f32, b: f32, v: f32) -> f32 {
if (a == b) { return 1.0; }
@@ -32,7 +32,7 @@ fn grid_lines(pos: vec4<f32>, depth: f32, scale: f32, depth_min: f32, depth_max:
}
@fragment
fn fragment(in: MeshVertexOutput) -> @location(0) vec4<f32> {
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
let depth = in.position.z;
let pos = in.world_position;
@@ -0,0 +1,7 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::render_resource::shader::ShaderLoader",
settings: (),
),
)
+4
View File
@@ -0,0 +1,4 @@
(
meta_format_version: "1.0",
asset: Ignore,
)
@@ -0,0 +1,7 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::hdr_texture_loader::HdrTextureLoader",
settings: (),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,7 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::hdr_texture_loader::HdrTextureLoader",
settings: (),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,7 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::hdr_texture_loader::HdrTextureLoader",
settings: (),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,7 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::hdr_texture_loader::HdrTextureLoader",
settings: (),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,7 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::hdr_texture_loader::HdrTextureLoader",
settings: (),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,7 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::hdr_texture_loader::HdrTextureLoader",
settings: (),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)
@@ -0,0 +1,11 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_render::texture::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
),
),
)

Some files were not shown because too many files have changed in this diff Show More