Skip to content

Instantly share code, notes, and snippets.

@dmlary
Last active June 23, 2024 07:51
Show Gist options
  • Save dmlary/3822b5cda70e562a2226b3372c584ed8 to your computer and use it in GitHub Desktop.
Save dmlary/3822b5cda70e562a2226b3372c584ed8 to your computer and use it in GitHub Desktop.
minimal example of adding a custom render pipeline in bevy 0.11
/// 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 }
}
}
@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