Built-in skybox (#8275)
# Objective - Closes https://github.com/bevyengine/bevy/issues/8008 ## Solution - Add a skybox plugin that renders a fullscreen triangle, and then modifies the vertices in a vertex shader to enforce that it renders as a skybox background. - Skybox is run at the end of MainOpaquePass3dNode. - In the future, it would be nice to get something like bevy_atmosphere built-in, and have a default skybox+environment map light. --- ## Changelog - Added `Skybox`. - `EnvironmentMapLight` now renders in the correct orientation. ## Migration Guide - Flip `EnvironmentMapLight` maps if needed to match how they previously rendered (which was backwards). --------- Co-authored-by: Robert Swain <robert.swain@gmail.com> Co-authored-by: robtfm <50659922+robtfm@users.noreply.github.com>
This commit is contained in:
parent
7a9e77c79c
commit
f0f5d79917
@ -2,15 +2,18 @@ use crate::{
|
||||
clear_color::{ClearColor, ClearColorConfig},
|
||||
core_3d::{Camera3d, Opaque3d},
|
||||
prepass::{DepthPrepass, MotionVectorPrepass, NormalPrepass},
|
||||
skybox::{SkyboxBindGroup, SkyboxPipelineId},
|
||||
};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_render::{
|
||||
camera::ExtractedCamera,
|
||||
render_graph::{Node, NodeRunError, RenderGraphContext},
|
||||
render_phase::RenderPhase,
|
||||
render_resource::{LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor},
|
||||
render_resource::{
|
||||
LoadOp, Operations, PipelineCache, RenderPassDepthStencilAttachment, RenderPassDescriptor,
|
||||
},
|
||||
renderer::RenderContext,
|
||||
view::{ExtractedView, ViewDepthTexture, ViewTarget},
|
||||
view::{ExtractedView, ViewDepthTexture, ViewTarget, ViewUniformOffset},
|
||||
};
|
||||
#[cfg(feature = "trace")]
|
||||
use bevy_utils::tracing::info_span;
|
||||
@ -30,6 +33,9 @@ pub struct MainOpaquePass3dNode {
|
||||
Option<&'static DepthPrepass>,
|
||||
Option<&'static NormalPrepass>,
|
||||
Option<&'static MotionVectorPrepass>,
|
||||
Option<&'static SkyboxPipelineId>,
|
||||
Option<&'static SkyboxBindGroup>,
|
||||
&'static ViewUniformOffset,
|
||||
),
|
||||
With<ExtractedView>,
|
||||
>,
|
||||
@ -64,7 +70,10 @@ impl Node for MainOpaquePass3dNode {
|
||||
depth,
|
||||
depth_prepass,
|
||||
normal_prepass,
|
||||
motion_vector_prepass
|
||||
motion_vector_prepass,
|
||||
skybox_pipeline,
|
||||
skybox_bind_group,
|
||||
view_uniform_offset,
|
||||
)) = self.query.get_manual(world, view_entity) else {
|
||||
// No window
|
||||
return Ok(());
|
||||
@ -75,6 +84,7 @@ impl Node for MainOpaquePass3dNode {
|
||||
#[cfg(feature = "trace")]
|
||||
let _main_opaque_pass_3d_span = info_span!("main_opaque_pass_3d").entered();
|
||||
|
||||
// Setup render pass
|
||||
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
|
||||
label: Some("main_opaque_pass_3d"),
|
||||
// NOTE: The opaque pass loads the color
|
||||
@ -115,12 +125,26 @@ impl Node for MainOpaquePass3dNode {
|
||||
render_pass.set_camera_viewport(viewport);
|
||||
}
|
||||
|
||||
// Opaque draws
|
||||
opaque_phase.render(&mut render_pass, world, view_entity);
|
||||
|
||||
// Alpha draws
|
||||
if !alpha_mask_phase.items.is_empty() {
|
||||
alpha_mask_phase.render(&mut render_pass, world, view_entity);
|
||||
}
|
||||
|
||||
// Draw the skybox using a fullscreen triangle
|
||||
if let (Some(skybox_pipeline), Some(skybox_bind_group)) =
|
||||
(skybox_pipeline, skybox_bind_group)
|
||||
{
|
||||
let pipeline_cache = world.resource::<PipelineCache>();
|
||||
if let Some(pipeline) = pipeline_cache.get_render_pipeline(skybox_pipeline.0) {
|
||||
render_pass.set_render_pipeline(pipeline);
|
||||
render_pass.set_bind_group(0, &skybox_bind_group.0, &[view_uniform_offset.offset]);
|
||||
render_pass.draw(0..3, 0..1);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ use bevy_utils::{FloatOrd, HashMap};
|
||||
|
||||
use crate::{
|
||||
prepass::{node::PrepassNode, DepthPrepass},
|
||||
skybox::SkyboxPlugin,
|
||||
tonemapping::TonemappingNode,
|
||||
upscaling::UpscalingNode,
|
||||
};
|
||||
@ -62,6 +63,7 @@ impl Plugin for Core3dPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.register_type::<Camera3d>()
|
||||
.register_type::<Camera3dDepthLoadOp>()
|
||||
.add_plugin(SkyboxPlugin)
|
||||
.add_plugin(ExtractComponentPlugin::<Camera3d>::default());
|
||||
|
||||
let render_app = match app.get_sub_app_mut(RenderApp) {
|
||||
|
@ -7,8 +7,26 @@ struct FullscreenVertexOutput {
|
||||
uv: vec2<f32>,
|
||||
};
|
||||
|
||||
// This vertex shader produces the following, when drawn using indices 0..3:
|
||||
//
|
||||
// 1 | 0-----x.....2
|
||||
// 0 | | s | . ´
|
||||
// -1 | x_____x´
|
||||
// -2 | : .´
|
||||
// -3 | 1´
|
||||
// +---------------
|
||||
// -1 0 1 2 3
|
||||
//
|
||||
// The axes are clip-space x and y. The region marked s is the visible region.
|
||||
// The digits in the corners of the right-angled triangle are the vertex
|
||||
// indices.
|
||||
//
|
||||
// The top-left has UV 0,0, the bottom-left has 0,2, and the top-right has 2,0.
|
||||
// This means that the UV gets interpolated to 1,1 at the bottom-right corner
|
||||
// of the clip-space rectangle that is at 1,-1 in clip space.
|
||||
@vertex
|
||||
fn fullscreen_vertex_shader(@builtin(vertex_index) vertex_index: u32) -> FullscreenVertexOutput {
|
||||
// See the explanation above for how this works
|
||||
let uv = vec2<f32>(f32(vertex_index >> 1u), f32(vertex_index & 1u)) * 2.0;
|
||||
let clip_position = vec4<f32>(uv * vec2<f32>(2.0, -2.0) + vec2<f32>(-1.0, 1.0), 0.0, 1.0);
|
||||
|
||||
|
@ -7,10 +7,13 @@ pub mod fullscreen_vertex_shader;
|
||||
pub mod fxaa;
|
||||
pub mod msaa_writeback;
|
||||
pub mod prepass;
|
||||
mod skybox;
|
||||
mod taa;
|
||||
pub mod tonemapping;
|
||||
pub mod upscaling;
|
||||
|
||||
pub use skybox::Skybox;
|
||||
|
||||
/// Experimental features that are not yet finished. Please report any issues you encounter!
|
||||
pub mod experimental {
|
||||
pub mod taa {
|
||||
|
238
crates/bevy_core_pipeline/src/skybox/mod.rs
Normal file
238
crates/bevy_core_pipeline/src/skybox/mod.rs
Normal file
@ -0,0 +1,238 @@
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::{load_internal_asset, Handle, HandleUntyped};
|
||||
use bevy_ecs::{
|
||||
prelude::{Component, Entity},
|
||||
query::With,
|
||||
schedule::IntoSystemConfigs,
|
||||
system::{Commands, Query, Res, ResMut, Resource},
|
||||
};
|
||||
use bevy_reflect::TypeUuid;
|
||||
use bevy_render::{
|
||||
extract_component::{ExtractComponent, ExtractComponentPlugin},
|
||||
render_asset::RenderAssets,
|
||||
render_resource::{
|
||||
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
|
||||
BindGroupLayoutEntry, BindingResource, BindingType, BlendState, BufferBindingType,
|
||||
CachedRenderPipelineId, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState,
|
||||
DepthStencilState, FragmentState, MultisampleState, PipelineCache, PrimitiveState,
|
||||
RenderPipelineDescriptor, SamplerBindingType, Shader, ShaderStages, ShaderType,
|
||||
SpecializedRenderPipeline, SpecializedRenderPipelines, StencilFaceState, StencilState,
|
||||
TextureFormat, TextureSampleType, TextureViewDimension, VertexState,
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
texture::{BevyDefault, Image},
|
||||
view::{ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniforms},
|
||||
Render, RenderApp, RenderSet,
|
||||
};
|
||||
|
||||
const SKYBOX_SHADER_HANDLE: HandleUntyped =
|
||||
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 55594763423201);
|
||||
|
||||
pub struct SkyboxPlugin;
|
||||
|
||||
impl Plugin for SkyboxPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
load_internal_asset!(app, SKYBOX_SHADER_HANDLE, "skybox.wgsl", Shader::from_wgsl);
|
||||
|
||||
app.add_plugin(ExtractComponentPlugin::<Skybox>::default());
|
||||
|
||||
let render_app = match app.get_sub_app_mut(RenderApp) {
|
||||
Ok(render_app) => render_app,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let render_device = render_app.world.resource::<RenderDevice>().clone();
|
||||
|
||||
render_app
|
||||
.insert_resource(SkyboxPipeline::new(&render_device))
|
||||
.init_resource::<SpecializedRenderPipelines<SkyboxPipeline>>()
|
||||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
prepare_skybox_pipelines.in_set(RenderSet::Prepare),
|
||||
queue_skybox_bind_groups.in_set(RenderSet::Queue),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a skybox to a 3D camera, based on a cubemap texture.
|
||||
///
|
||||
/// Note that this component does not (currently) affect the scene's lighting.
|
||||
/// To do so, use `EnvironmentMapLight` alongside this component.
|
||||
///
|
||||
/// See also <https://en.wikipedia.org/wiki/Skybox_(video_games)>.
|
||||
#[derive(Component, ExtractComponent, Clone)]
|
||||
pub struct Skybox(pub Handle<Image>);
|
||||
|
||||
#[derive(Resource)]
|
||||
struct SkyboxPipeline {
|
||||
bind_group_layout: BindGroupLayout,
|
||||
}
|
||||
|
||||
impl SkyboxPipeline {
|
||||
fn new(render_device: &RenderDevice) -> Self {
|
||||
let bind_group_layout_descriptor = BindGroupLayoutDescriptor {
|
||||
label: Some("skybox_bind_group_layout"),
|
||||
entries: &[
|
||||
BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Texture {
|
||||
sample_type: TextureSampleType::Float { filterable: true },
|
||||
view_dimension: TextureViewDimension::Cube,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Sampler(SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: BindingType::Buffer {
|
||||
ty: BufferBindingType::Uniform,
|
||||
has_dynamic_offset: true,
|
||||
min_binding_size: Some(ViewUniform::min_size()),
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
Self {
|
||||
bind_group_layout: render_device
|
||||
.create_bind_group_layout(&bind_group_layout_descriptor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
|
||||
struct SkyboxPipelineKey {
|
||||
hdr: bool,
|
||||
samples: u32,
|
||||
}
|
||||
|
||||
impl SpecializedRenderPipeline for SkyboxPipeline {
|
||||
type Key = SkyboxPipelineKey;
|
||||
|
||||
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
|
||||
RenderPipelineDescriptor {
|
||||
label: Some("skybox_pipeline".into()),
|
||||
layout: vec![self.bind_group_layout.clone()],
|
||||
push_constant_ranges: Vec::new(),
|
||||
vertex: VertexState {
|
||||
shader: SKYBOX_SHADER_HANDLE.typed(),
|
||||
shader_defs: Vec::new(),
|
||||
entry_point: "skybox_vertex".into(),
|
||||
buffers: Vec::new(),
|
||||
},
|
||||
primitive: PrimitiveState::default(),
|
||||
depth_stencil: Some(DepthStencilState {
|
||||
format: TextureFormat::Depth32Float,
|
||||
depth_write_enabled: false,
|
||||
depth_compare: CompareFunction::GreaterEqual,
|
||||
stencil: StencilState {
|
||||
front: StencilFaceState::IGNORE,
|
||||
back: StencilFaceState::IGNORE,
|
||||
read_mask: 0,
|
||||
write_mask: 0,
|
||||
},
|
||||
bias: DepthBiasState {
|
||||
constant: 0,
|
||||
slope_scale: 0.0,
|
||||
clamp: 0.0,
|
||||
},
|
||||
}),
|
||||
multisample: MultisampleState {
|
||||
count: key.samples,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
shader: SKYBOX_SHADER_HANDLE.typed(),
|
||||
shader_defs: Vec::new(),
|
||||
entry_point: "skybox_fragment".into(),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
format: if key.hdr {
|
||||
ViewTarget::TEXTURE_FORMAT_HDR
|
||||
} else {
|
||||
TextureFormat::bevy_default()
|
||||
},
|
||||
blend: Some(BlendState::REPLACE),
|
||||
write_mask: ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct SkyboxPipelineId(pub CachedRenderPipelineId);
|
||||
|
||||
fn prepare_skybox_pipelines(
|
||||
mut commands: Commands,
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
mut pipelines: ResMut<SpecializedRenderPipelines<SkyboxPipeline>>,
|
||||
pipeline: Res<SkyboxPipeline>,
|
||||
msaa: Res<Msaa>,
|
||||
views: Query<(Entity, &ExtractedView), With<Skybox>>,
|
||||
) {
|
||||
for (entity, view) in &views {
|
||||
let pipeline_id = pipelines.specialize(
|
||||
&pipeline_cache,
|
||||
&pipeline,
|
||||
SkyboxPipelineKey {
|
||||
hdr: view.hdr,
|
||||
samples: msaa.samples(),
|
||||
},
|
||||
);
|
||||
|
||||
commands
|
||||
.entity(entity)
|
||||
.insert(SkyboxPipelineId(pipeline_id));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct SkyboxBindGroup(pub BindGroup);
|
||||
|
||||
fn queue_skybox_bind_groups(
|
||||
mut commands: Commands,
|
||||
pipeline: Res<SkyboxPipeline>,
|
||||
view_uniforms: Res<ViewUniforms>,
|
||||
images: Res<RenderAssets<Image>>,
|
||||
render_device: Res<RenderDevice>,
|
||||
views: Query<(Entity, &Skybox)>,
|
||||
) {
|
||||
for (entity, skybox) in &views {
|
||||
if let (Some(skybox), Some(view_uniforms)) =
|
||||
(images.get(&skybox.0), view_uniforms.uniforms.binding())
|
||||
{
|
||||
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
|
||||
label: Some("skybox_bind_group"),
|
||||
layout: &pipeline.bind_group_layout,
|
||||
entries: &[
|
||||
BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: BindingResource::TextureView(&skybox.texture_view),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: BindingResource::Sampler(&skybox.sampler),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: view_uniforms,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
commands.entity(entity).insert(SkyboxBindGroup(bind_group));
|
||||
}
|
||||
}
|
||||
}
|
52
crates/bevy_core_pipeline/src/skybox/skybox.wgsl
Normal file
52
crates/bevy_core_pipeline/src/skybox/skybox.wgsl
Normal file
@ -0,0 +1,52 @@
|
||||
#import bevy_render::view
|
||||
|
||||
@group(0) @binding(0)
|
||||
var skybox: texture_cube<f32>;
|
||||
@group(0) @binding(1)
|
||||
var skybox_sampler: sampler;
|
||||
@group(0) @binding(2)
|
||||
var<uniform> view: View;
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) world_position: vec3<f32>,
|
||||
};
|
||||
|
||||
// 3 | 2.
|
||||
// 2 | : `.
|
||||
// 1 | x-----x.
|
||||
// 0 | | s | `.
|
||||
// -1 | 0-----x.....1
|
||||
// +---------------
|
||||
// -1 0 1 2 3
|
||||
//
|
||||
// The axes are clip-space x and y. The region marked s is the visible region.
|
||||
// The digits in the corners of the right-angled triangle are the vertex
|
||||
// indices.
|
||||
@vertex
|
||||
fn skybox_vertex(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
|
||||
// See the explanation above for how this works.
|
||||
let clip_position = vec4(
|
||||
f32(vertex_index & 1u),
|
||||
f32((vertex_index >> 1u) & 1u),
|
||||
0.25,
|
||||
0.5
|
||||
) * 4.0 - vec4(1.0);
|
||||
// Use the position on the near clipping plane to avoid -inf world position
|
||||
// because the far plane of an infinite reverse projection is at infinity.
|
||||
// NOTE: The clip position has a w component equal to 1.0 so we don't need
|
||||
// to apply a perspective divide to it before inverse-projecting it.
|
||||
let world_position_homogeneous = view.inverse_view_proj * vec4(clip_position.xy, 1.0, 1.0);
|
||||
let world_position = world_position_homogeneous.xyz / world_position_homogeneous.w;
|
||||
|
||||
return VertexOutput(clip_position, world_position);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn skybox_fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
// The skybox cubemap is sampled along the direction from the camera world
|
||||
// position, to the fragment world position on the near clipping plane
|
||||
let ray_direction = in.world_position - view.world_position;
|
||||
// cube maps are left-handed so we negate the z coordinate
|
||||
return textureSample(skybox, skybox_sampler, ray_direction * vec3(1.0, 1.0, -1.0));
|
||||
}
|
@ -21,8 +21,8 @@ fn environment_map_light(
|
||||
// Technically we could use textureNumLevels(environment_map_specular) - 1 here, but we use a uniform
|
||||
// because textureNumLevels() does not work on WebGL2
|
||||
let radiance_level = perceptual_roughness * f32(lights.environment_map_smallest_specular_mip_level);
|
||||
let irradiance = textureSample(environment_map_diffuse, environment_map_sampler, N).rgb;
|
||||
let radiance = textureSampleLevel(environment_map_specular, environment_map_sampler, R, radiance_level).rgb;
|
||||
let irradiance = textureSample(environment_map_diffuse, environment_map_sampler, vec3(N.xy, -N.z)).rgb;
|
||||
let radiance = textureSampleLevel(environment_map_specular, environment_map_sampler, vec3(R.xy, -R.z), radiance_level).rgb;
|
||||
|
||||
// Multiscattering approximation: https://www.jcgt.org/published/0008/01/03/paper.pdf
|
||||
// Useful reference: https://bruop.github.io/ibl
|
||||
|
@ -4,22 +4,13 @@ use std::f32::consts::PI;
|
||||
|
||||
use bevy::{
|
||||
asset::LoadState,
|
||||
core_pipeline::Skybox,
|
||||
input::mouse::MouseMotion,
|
||||
pbr::{MaterialPipeline, MaterialPipelineKey},
|
||||
prelude::*,
|
||||
reflect::TypeUuid,
|
||||
render::{
|
||||
mesh::MeshVertexBufferLayout,
|
||||
render_asset::RenderAssets,
|
||||
render_resource::{
|
||||
AsBindGroup, AsBindGroupError, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
|
||||
BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType,
|
||||
OwnedBindingResource, PreparedBindGroup, RenderPipelineDescriptor, SamplerBindingType,
|
||||
ShaderRef, ShaderStages, SpecializedMeshPipelineError, TextureSampleType,
|
||||
TextureViewDescriptor, TextureViewDimension,
|
||||
},
|
||||
render_resource::{TextureViewDescriptor, TextureViewDimension},
|
||||
renderer::RenderDevice,
|
||||
texture::{CompressedImageFormats, FallbackImage},
|
||||
texture::CompressedImageFormats,
|
||||
},
|
||||
};
|
||||
|
||||
@ -45,7 +36,6 @@ const CUBEMAPS: &[(&str, CompressedImageFormats)] = &[
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_plugin(MaterialPlugin::<CubemapMaterial>::default())
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(
|
||||
Update,
|
||||
@ -86,6 +76,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
..default()
|
||||
},
|
||||
CameraController::default(),
|
||||
Skybox(skybox_handle.clone()),
|
||||
));
|
||||
|
||||
// ambient light
|
||||
@ -145,13 +136,10 @@ fn cycle_cubemap_asset(
|
||||
}
|
||||
|
||||
fn asset_loaded(
|
||||
mut commands: Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
mut images: ResMut<Assets<Image>>,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut cubemap_materials: ResMut<Assets<CubemapMaterial>>,
|
||||
mut cubemap: ResMut<Cubemap>,
|
||||
cubes: Query<&Handle<CubemapMaterial>>,
|
||||
mut skyboxes: Query<&mut Skybox>,
|
||||
) {
|
||||
if !cubemap.is_loaded
|
||||
&& asset_server.get_load_state(cubemap.image_handle.clone_weak()) == LoadState::Loaded
|
||||
@ -170,22 +158,8 @@ fn asset_loaded(
|
||||
});
|
||||
}
|
||||
|
||||
// spawn cube
|
||||
let mut updated = false;
|
||||
for handle in cubes.iter() {
|
||||
if let Some(material) = cubemap_materials.get_mut(handle) {
|
||||
updated = true;
|
||||
material.base_color_texture = Some(cubemap.image_handle.clone_weak());
|
||||
}
|
||||
}
|
||||
if !updated {
|
||||
commands.spawn(MaterialMeshBundle::<CubemapMaterial> {
|
||||
mesh: meshes.add(Mesh::from(shape::Cube { size: 10000.0 })),
|
||||
material: cubemap_materials.add(CubemapMaterial {
|
||||
base_color_texture: Some(cubemap.image_handle.clone_weak()),
|
||||
}),
|
||||
..default()
|
||||
});
|
||||
for mut skybox in &mut skyboxes {
|
||||
skybox.0 = cubemap.image_handle.clone();
|
||||
}
|
||||
|
||||
cubemap.is_loaded = true;
|
||||
@ -201,97 +175,6 @@ fn animate_light_direction(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, TypeUuid)]
|
||||
#[uuid = "9509a0f8-3c05-48ee-a13e-a93226c7f488"]
|
||||
struct CubemapMaterial {
|
||||
base_color_texture: Option<Handle<Image>>,
|
||||
}
|
||||
|
||||
impl Material for CubemapMaterial {
|
||||
fn fragment_shader() -> ShaderRef {
|
||||
"shaders/cubemap_unlit.wgsl".into()
|
||||
}
|
||||
|
||||
fn specialize(
|
||||
_pipeline: &MaterialPipeline<Self>,
|
||||
descriptor: &mut RenderPipelineDescriptor,
|
||||
_layout: &MeshVertexBufferLayout,
|
||||
_key: MaterialPipelineKey<Self>,
|
||||
) -> Result<(), SpecializedMeshPipelineError> {
|
||||
descriptor.primitive.cull_mode = None;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsBindGroup for CubemapMaterial {
|
||||
type Data = ();
|
||||
|
||||
fn as_bind_group(
|
||||
&self,
|
||||
layout: &BindGroupLayout,
|
||||
render_device: &RenderDevice,
|
||||
images: &RenderAssets<Image>,
|
||||
_fallback_image: &FallbackImage,
|
||||
) -> Result<PreparedBindGroup<Self::Data>, AsBindGroupError> {
|
||||
let base_color_texture = self
|
||||
.base_color_texture
|
||||
.as_ref()
|
||||
.ok_or(AsBindGroupError::RetryNextUpdate)?;
|
||||
let image = images
|
||||
.get(base_color_texture)
|
||||
.ok_or(AsBindGroupError::RetryNextUpdate)?;
|
||||
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
|
||||
entries: &[
|
||||
BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: BindingResource::TextureView(&image.texture_view),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: BindingResource::Sampler(&image.sampler),
|
||||
},
|
||||
],
|
||||
label: Some("cubemap_texture_material_bind_group"),
|
||||
layout,
|
||||
});
|
||||
|
||||
Ok(PreparedBindGroup {
|
||||
bind_group,
|
||||
bindings: vec![
|
||||
OwnedBindingResource::TextureView(image.texture_view.clone()),
|
||||
OwnedBindingResource::Sampler(image.sampler.clone()),
|
||||
],
|
||||
data: (),
|
||||
})
|
||||
}
|
||||
|
||||
fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout {
|
||||
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
// Cubemap Base Color Texture
|
||||
BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Texture {
|
||||
multisampled: false,
|
||||
sample_type: TextureSampleType::Float { filterable: true },
|
||||
view_dimension: TextureViewDimension::Cube,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// Cubemap Base Color Texture Sampler
|
||||
BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Sampler(SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct CameraController {
|
||||
pub enabled: bool,
|
||||
|
Loading…
Reference in New Issue
Block a user