Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Built-in skybox #8275

Merged
merged 31 commits into from
Apr 2, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5a4ba5c
WIP SkyboxPlugin
JMS55 Mar 30, 2023
28eb2aa
Shader WIP
JMS55 Mar 30, 2023
73f7192
Hook up bind group
JMS55 Mar 30, 2023
4559cbd
Fixes
JMS55 Mar 30, 2023
14ff0c2
Misc refactor
JMS55 Mar 30, 2023
2279f09
Misc fix
JMS55 Mar 30, 2023
addf220
Extract skybox
JMS55 Mar 30, 2023
c952cc6
Shader fixes
JMS55 Mar 30, 2023
121ce48
Fix shader
JMS55 Mar 30, 2023
2ad5eaa
Remove camera translation from skybox
JMS55 Mar 30, 2023
b23e640
Fix skybox shader
JMS55 Mar 31, 2023
9eb67d9
Improve shader
JMS55 Mar 31, 2023
6286b8f
Move skybox module
JMS55 Mar 31, 2023
0b8e3d5
Add doc
JMS55 Mar 31, 2023
9b29b5d
Misc doc
JMS55 Mar 31, 2023
0d57752
Doc tweak
JMS55 Mar 31, 2023
9f147c1
Change visibilities
JMS55 Mar 31, 2023
637a313
Misc
JMS55 Mar 31, 2023
73089f7
Fix imports
JMS55 Mar 31, 2023
00a62df
Combine SkyboxNode and MainOpaquePass3dNode
JMS55 Mar 31, 2023
34a8ee9
Misc removal
JMS55 Mar 31, 2023
6e1bfee
Use the view type shader import
superdump Mar 31, 2023
ec9f0c2
Use a fullscreen triangle to render the skybox
superdump Mar 31, 2023
d00802d
Add documentation to how the fullscreen shader works
superdump Mar 31, 2023
bbbb708
Update the skybox example to use the new Skybox component
superdump Mar 31, 2023
144f88b
Cleanup after rebase
superdump Mar 31, 2023
be50268
Remove debug notes
superdump Mar 31, 2023
f0c7f70
Merge pull request #10 from superdump/skybox-fullscreen-triangle
JMS55 Mar 31, 2023
0d9216c
Update crates/bevy_core_pipeline/src/skybox/skybox.wgsl
JMS55 Apr 2, 2023
b0c1a15
Flip environment map light sampling
JMS55 Apr 2, 2023
36f4939
Remove separator
JMS55 Apr 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions crates/bevy_core_pipeline/src/core_3d/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod graph {
pub const PREPASS: &str = "prepass";
pub const START_MAIN_PASS: &str = "start_main_pass";
pub const MAIN_OPAQUE_PASS: &str = "main_opaque_pass";
pub const SKYBOX: &str = "skybox";
pub const MAIN_TRANSPARENT_PASS: &str = "main_transparent_pass";
pub const END_MAIN_PASS: &str = "end_main_pass";
pub const BLOOM: &str = "bloom";
Expand Down Expand Up @@ -52,6 +53,7 @@ use bevy_utils::{FloatOrd, HashMap};

use crate::{
prepass::{node::PrepassNode, DepthPrepass},
skybox::{SkyboxNode, SkyboxPlugin},
tonemapping::TonemappingNode,
upscaling::UpscalingNode,
};
Expand All @@ -62,6 +64,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) {
Expand All @@ -88,6 +91,7 @@ impl Plugin for Core3dPlugin {

let prepass_node = PrepassNode::new(&mut render_app.world);
let opaque_node_3d = MainOpaquePass3dNode::new(&mut render_app.world);
let skybox = SkyboxNode::new(&mut render_app.world);
let transparent_node_3d = MainTransparentPass3dNode::new(&mut render_app.world);
let tonemapping = TonemappingNode::new(&mut render_app.world);
let upscaling = UpscalingNode::new(&mut render_app.world);
Expand All @@ -97,6 +101,7 @@ impl Plugin for Core3dPlugin {
draw_3d_graph.add_node(graph::node::PREPASS, prepass_node);
draw_3d_graph.add_node(graph::node::START_MAIN_PASS, EmptyNode);
draw_3d_graph.add_node(graph::node::MAIN_OPAQUE_PASS, opaque_node_3d);
draw_3d_graph.add_node(graph::node::SKYBOX, skybox);
draw_3d_graph.add_node(graph::node::MAIN_TRANSPARENT_PASS, transparent_node_3d);
draw_3d_graph.add_node(graph::node::END_MAIN_PASS, EmptyNode);
draw_3d_graph.add_node(graph::node::TONEMAPPING, tonemapping);
Expand All @@ -105,10 +110,8 @@ impl Plugin for Core3dPlugin {

draw_3d_graph.add_node_edge(graph::node::PREPASS, graph::node::START_MAIN_PASS);
draw_3d_graph.add_node_edge(graph::node::START_MAIN_PASS, graph::node::MAIN_OPAQUE_PASS);
draw_3d_graph.add_node_edge(
graph::node::MAIN_OPAQUE_PASS,
graph::node::MAIN_TRANSPARENT_PASS,
);
draw_3d_graph.add_node_edge(graph::node::MAIN_OPAQUE_PASS, graph::node::SKYBOX);
draw_3d_graph.add_node_edge(graph::node::SKYBOX, graph::node::MAIN_TRANSPARENT_PASS);
draw_3d_graph.add_node_edge(
graph::node::MAIN_TRANSPARENT_PASS,
graph::node::END_MAIN_PASS,
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_core_pipeline/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod fullscreen_vertex_shader;
pub mod fxaa;
pub mod msaa_writeback;
pub mod prepass;
pub mod skybox;
mod taa;
pub mod tonemapping;
pub mod upscaling;
Expand Down
261 changes: 261 additions & 0 deletions crates/bevy_core_pipeline/src/skybox/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
mod node;

use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, Assets, 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},
prelude::{shape::Cube, Mesh},
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, ShaderDefVal, ShaderStages,
ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, StencilFaceState,
StencilState, TextureFormat, TextureSampleType, TextureViewDimension, VertexBufferLayout,
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(crate) 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 mesh = Mesh::from(Cube::new(1.0));
let vertex_buffer_layout = mesh.get_mesh_vertex_buffer_layout().layout().clone();
let handle = app.world.resource_mut::<Assets<Mesh>>().add(mesh);

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(SkyboxMesh { handle })
.insert_resource(SkyboxPipeline::new(vertex_buffer_layout, &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.
JMS55 marked this conversation as resolved.
Show resolved Hide resolved
///
/// See also <https://en.wikipedia.org/wiki/Skybox_(video_games)>.
#[derive(Component, ExtractComponent, Clone)]
pub struct Skybox(pub Handle<Image>);

// ----------------------------------------------------------------------------
JMS55 marked this conversation as resolved.
Show resolved Hide resolved

#[derive(Resource)]
struct SkyboxMesh {
handle: Handle<Mesh>,
}
JMS55 marked this conversation as resolved.
Show resolved Hide resolved

#[derive(Resource)]
struct SkyboxPipeline {
bind_group_layout: BindGroupLayout,
vertex_buffer_layout: VertexBufferLayout,
}

impl SkyboxPipeline {
fn new(vertex_buffer_layout: VertexBufferLayout, 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,
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),
vertex_buffer_layout,
}
}
}

#[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 {
let shader_defs = vec![
ShaderDefVal::UInt("MAX_DIRECTIONAL_LIGHTS".to_string(), 1),
ShaderDefVal::UInt("MAX_CASCADES_PER_LIGHT".to_string(), 1),
];

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: shader_defs.clone(),
entry_point: "skybox_vertex".into(),
buffers: vec![self.vertex_buffer_layout.clone()],
},
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,
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)]
struct SkyboxPipelineId(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)]
struct SkyboxBindGroup(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));
}
}
}
Loading