Last active
June 23, 2024 07:51
-
-
Save dmlary/3822b5cda70e562a2226b3372c584ed8 to your computer and use it in GitHub Desktop.
minimal example of adding a custom render pipeline in bevy 0.11
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// minimal example of adding a custom render pipeline in bevy 0.11. | |
/// | |
/// When this example runs, you should only see a blue screen. There are no | |
/// vertex buffers, or anything else in this example. Effectively it is | |
/// shader-toy written in bevy. | |
/// | |
/// This revision adds a post-processing node to the RenderGraph to | |
/// execute the shader. Thanks to @Jasmine on the bevy discord for | |
/// suggesting I take a second look at the bevy post-processing example | |
/// | |
/// If no messages appear on stdout, set to help debug: | |
/// RUST_LOG="info,wgpu_core=warn,wgpu_hal=warn" | |
/// | |
use bevy::{ | |
asset::ChangeWatcher, | |
core_pipeline::core_3d, | |
ecs::query::QueryItem, | |
prelude::*, | |
render::{ | |
camera::ExtractedCamera, | |
render_graph::{RenderGraphApp, ViewNode, ViewNodeRunner}, | |
render_resource::{ | |
BlendState, CachedRenderPipelineId, ColorTargetState, ColorWrites, FragmentState, | |
MultisampleState, Operations, PipelineCache, PolygonMode, PrimitiveState, | |
PrimitiveTopology, RenderPassColorAttachment, RenderPassDescriptor, | |
RenderPipelineDescriptor, TextureFormat, | |
}, | |
texture::BevyDefault, | |
view::ViewTarget, | |
RenderApp, | |
}, | |
}; | |
use std::time::Duration; | |
fn main() { | |
let mut app = App::new(); | |
// enable hot-loading so our shader gets reloaded when it changes | |
app.add_plugins(( | |
DefaultPlugins.set(AssetPlugin { | |
watch_for_changes: ChangeWatcher::with_delay(Duration::from_millis(200)), | |
..default() | |
}), | |
ShaderToyPlugin, | |
)) | |
.add_systems(Startup, setup) | |
.run(); | |
} | |
fn setup( | |
mut commands: Commands, | |
mut meshes: ResMut<Assets<Mesh>>, | |
mut materials: ResMut<Assets<StandardMaterial>>, | |
) { | |
// add a cube so it's really clear when the shader doesn't run | |
commands.spawn(MaterialMeshBundle { | |
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.5 })), | |
transform: Transform::from_xyz(0.0, 0.5, 0.0), | |
material: materials.add(StandardMaterial::default()), | |
..default() | |
}); | |
// camera | |
commands.spawn(Camera3dBundle { | |
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), | |
..default() | |
}); | |
} | |
struct ShaderToyPlugin; | |
impl Plugin for ShaderToyPlugin { | |
fn build(&self, app: &mut App) { | |
let render_app = app | |
.get_sub_app_mut(RenderApp) | |
.expect("RenderApp should already exist in App"); | |
// add our post-processing render node to the render graph | |
// place it between tonemapping & the end of post-processing shaders | |
render_app | |
.add_render_graph_node::<ViewNodeRunner<ShaderToyRenderNode>>( | |
core_3d::graph::NAME, | |
ShaderToyRenderNode::NAME, | |
) | |
.add_render_graph_edges( | |
core_3d::graph::NAME, | |
&[ | |
core_3d::graph::node::TONEMAPPING, | |
ShaderToyRenderNode::NAME, | |
core_3d::graph::node::END_MAIN_PASS_POST_PROCESSING, | |
], | |
); | |
} | |
fn finish(&self, app: &mut App) { | |
let render_app = app | |
.get_sub_app_mut(RenderApp) | |
.expect("RenderApp should already exist in App"); | |
render_app.init_resource::<ShaderToyPipeline>(); | |
} | |
} | |
#[derive(Debug, Default)] | |
struct ShaderToyRenderNode; | |
impl ShaderToyRenderNode { | |
pub const NAME: &str = "shader_toy"; | |
} | |
impl ViewNode for ShaderToyRenderNode { | |
type ViewQuery = (&'static ExtractedCamera, &'static ViewTarget); | |
fn run( | |
&self, | |
_graph: &mut bevy::render::render_graph::RenderGraphContext, | |
render_context: &mut bevy::render::renderer::RenderContext, | |
(_camera, view_target): QueryItem<Self::ViewQuery>, | |
world: &World, | |
) -> Result<(), bevy::render::render_graph::NodeRunError> { | |
let shader_toy_pipeline = world.resource::<ShaderToyPipeline>(); | |
let pipeline_cache = world.resource::<PipelineCache>(); | |
let pipeline = pipeline_cache | |
.get_render_pipeline(shader_toy_pipeline.pipeline_id) | |
.expect("ShaderToyPipeline should be present in the PipelineCache"); | |
// create a render pass. Note that we don't want to inherit the | |
// color_attachments because then the pipeline Multisample must match | |
// whatever msaa was set to. | |
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { | |
label: Some("shader_toy_pass"), | |
color_attachments: &[Some(RenderPassColorAttachment { | |
view: view_target.main_texture_view(), | |
resolve_target: None, | |
ops: Operations::default(), | |
})], | |
depth_stencil_attachment: None, | |
}); | |
render_pass.set_render_pipeline(pipeline); | |
render_pass.draw(0..4, 0..1); | |
Ok(()) | |
} | |
} | |
#[derive(Debug, Resource)] | |
struct ShaderToyPipeline { | |
pipeline_id: CachedRenderPipelineId, | |
} | |
impl FromWorld for ShaderToyPipeline { | |
fn from_world(world: &mut World) -> Self { | |
let shader = world.resource::<AssetServer>().load("shader_toy.wgsl"); | |
let pipeline_cache = world.resource_mut::<PipelineCache>(); | |
let pipeline_id = pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor { | |
label: Some("shader_toy_pipeline".into()), | |
layout: vec![], | |
push_constant_ranges: Vec::new(), | |
vertex: bevy::render::render_resource::VertexState { | |
shader: shader.clone(), | |
shader_defs: vec![], | |
entry_point: "vertex".into(), | |
buffers: vec![], | |
}, | |
// default does not work here as we're using TriangleStrip | |
primitive: PrimitiveState { | |
topology: PrimitiveTopology::TriangleStrip, | |
strip_index_format: None, | |
front_face: bevy::render::render_resource::FrontFace::Ccw, | |
cull_mode: None, | |
unclipped_depth: false, | |
polygon_mode: PolygonMode::Fill, | |
conservative: false, | |
}, | |
depth_stencil: None, | |
multisample: MultisampleState::default(), | |
fragment: Some(FragmentState { | |
shader, | |
shader_defs: vec![], | |
entry_point: "fragment".into(), | |
targets: vec![Some(ColorTargetState { | |
format: TextureFormat::bevy_default(), | |
blend: Some(BlendState::ALPHA_BLENDING), | |
write_mask: ColorWrites::ALL, | |
})], | |
}), | |
}); | |
Self { pipeline_id } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@vertex | |
fn vertex(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4<f32> { | |
var grid_plane = array<vec4<f32>, 4>( | |
vec4<f32>(-1., -1., 1., 1.), | |
vec4<f32>(-1., 1., 1., 1.), | |
vec4<f32>(1., -1., 1., 1.), | |
vec4<f32>(1., 1., 1., 1.) | |
); | |
return grid_plane[in_vertex_index]; | |
} | |
@fragment | |
fn fragment(@builtin(position) in: vec4<f32>) -> @location(0) vec4<f32> { | |
return vec4<f32>(0.1, 0.2, 0.3, 1.0); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment