#Render a mesh with a custom material?

1 messages · Page 1 of 1 (latest)

chrome gyro
#

I want to use my own vertex/fragment shader to render a mesh. I already have the mesh, and can successfully render the mesh using a PbrBundle, but I can't figure out how to render it using my own shaders.

velvet hearth
#

Take a look at the custom_material.rs example.

chrome gyro
#

I saw that. I tried it out, and it had no effect.

#[derive(AsBindGroup, Debug, Clone, Asset, TypePath)]
struct VoxelMaterial {
}

impl Material for VoxelMaterial {
    fn vertex_shader() -> bevy::render::render_resource::ShaderRef {
        "shaders/voxel/voxel.wgsl".into()
    }

    fn fragment_shader() -> bevy::render::render_resource::ShaderRef {
        "shaders/voxel/voxel.wgsl".into()
    }

    fn alpha_mode(&self) -> AlphaMode {
        AlphaMode::Blend
    }
}
commands.spawn((
    MaterialMeshBundle {
        mesh: meshes.add(mesh),
        material: materials.add(VoxelMaterial {  }),
        transform: Transform::from_xyz(0.0, 0.0, 0.0),
        ..default()
    },
    cleanup::Menu
));
#

The same mesh will render if I put it into a PbrBundle.

#

Here's my shader code:
Vertex

struct VertexInput {
    @location(0) position: vec3<f32>,
};

struct VertexOutput {
    @builtin(position) position: vec4<f32>,
    @location(0) color: vec3<f32>,
};

@vertex
fn main(input: VertexInput) -> VertexOutput {
    var output: VertexOutput;
    output.position = vec4<f32>(input.position, 1.0);
    output.color = vec3<f32>(1.0, 0.0, 0.0);
    return output;
}

Fragment

struct FragmentInput {
    @location(0) color: vec3<f32>,
};

@fragment
fn main(input: FragmentInput) -> @location(0) vec4<f32> {
    return vec4<f32>(1.0, 0.0, 0.0, 1.0);
}
velvet hearth
#

Can you share a picture of the result?

chrome gyro
#

There is no result. Nothing renders.

#

If I change it to a PbrBundle, it does render.

#

Like I said, nothing.

chrome gyro
#

After doing some thinking, I've come to realize that I should probably transform the coordinates from world space to screenspace in the vertex shader, but I don't know WGSL so I'm not sure how to do that.

velvet hearth
#

Ah, yea, that would be it. You'd want a vertex that had an instance_index and then transformed it to clipspace (a kind of screenspace) like this:

struct Vertex {
    @builtin(instance_index) instance_index: u32,
    @location(0) position: vec3<f32>,
};

@vertex
fn vertex(vertex: Vertex) -> VertexOutput {
    var out: VertexOutput;
    let model = mesh2d_functions::get_model_matrix(vertex.instance_index);
    out.position = mesh2d_functions::mesh2d_position_local_to_clip(model, vec4<f32>(vertex.position, 1.0));
}
chrome gyro
#

Why mesh2d?

#

What if I'm doing 3D?

velvet hearth
#

Not mesh2d for you, sorry.

#

just mesh_functions for 3d.

chrome gyro
#
#import bevy_pbr::mesh_functions

?

velvet hearth
#

yep.

chrome gyro
#

Okay, here's the new code.

#import bevy_pbr::view_transformations::position_world_to_clip
#import bevy_pbr::mesh_functions

struct VertexInput {
    @builtin(instance_index) instance_index: u32,
    @location(0) position: vec3<f32>,
    @location(1) uv: vec2<f32>,
};

struct VertexOutput {
    @builtin(position) position: vec4<f32>,
    @location(0) uv: vec2<f32>,
};

@vertex
fn vertex(input: VertexInput) -> VertexOutput {
    var model = mesh_functions::get_model_matrix(input.instance_index);
    var output: VertexOutput;
    var position = mesh_functions::mesh_position_local_to_world(
        model,
        vec4<f32>(input.position, 1.0)
    );
    output.position = position_world_to_clip(position.xyz);
    output.uv = input.uv;
    return output;
}
#

That's what I was able to figure out on my own, but it still doesn't work.

velvet hearth
#

comment out this line: output.position = position_world_to_clip(position.xyz);

#

output.position = mesh_functions…

#

add that.

chrome gyro
#
@vertex
fn vertex(input: VertexInput) -> VertexOutput {
    var model = mesh_functions::get_model_matrix(input.instance_index);
    var output: VertexOutput;
    output.position = mesh_functions::mesh_position_local_to_world(
        model,
        vec4<f32>(input.position, 1.0)
    );
    // output.position = position_world_to_clip(position.xyz);
    output.uv = input.uv;
    return output;
}

Like that?

velvet hearth
#

Oh, I see, that could maybe work. local_to_clip instead of local_to_world.

chrome gyro
#

mesh_functions::local_to_clip?

velvet hearth
#

mesh_position_local_to_clip

chrome gyro
#

Where do I find documentation for these functions?

#

I don't even know if bevy is using my shader. I could literally mangle the path and bevy won't complain.

#

So I'm finding it hard to narrow down exactly what is going wrong.

velvet hearth
#

Try running with cargo run --features bevy/file_watcher. that will reload your shader when it changes.

chrome gyro
#

I don't even know if it's loading my shader in the first place.

#

Shouldn't bevy throw an error if the file isn't found?

velvet hearth
#

Unlike the rust API, the shader API is harder to find documentation for. I'd suggest just cloning bevy's repo and searching for the wgsl files you're importing.

#

Yes. Where do you load your file?

chrome gyro
#
impl Material for VoxelMaterial {
    fn vertex_shader() -> bevy::render::render_resource::ShaderRef {
        "shaders/voxel/voxel_vert.wgsl".into()
    }

    fn fragment_shader() -> bevy::render::render_resource::ShaderRef {
        "shaders/voxel/voxel_frag.wgsl".into()
    }

    fn alpha_mode(&self) -> AlphaMode {
        AlphaMode::Blend
    }
}
#

Then I use

commands.spawn((
    MaterialMeshBundle {
        mesh: meshes.add(mesh),
        material: materials.add(VoxelMaterial {  }),
        transform: Transform::from_xyz(0.0, 0.0, 0.0),
        ..default()
    },
    cleanup::Menu
));

To spawn the mesh.

#
mesh.insert_attribute(MeshVertexAttribute::new("position", 0, VertexFormat::Float32x3), vertices);
mesh.insert_attribute(MeshVertexAttribute::new("uv", 1, VertexFormat::Float32x2), uvs);
mesh.insert_indices(indices);
#

Building the mesh.

#
let mut mesh = bevy::prelude::Mesh::new(bevy::render::mesh::PrimitiveTopology::TriangleList, RenderAssetUsages::RENDER_WORLD);
let vertices: Vec<_> = [
    vec3(0.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0),
    vec3(0.0, 0.0, 1.0), vec3(1.0, 0.0, 1.0),
].into_iter().map(|v| v + vec3(-0.5, 0.0, -0.5)).collect();
let uvs = vec![
    vec2(0.0, 0.0), vec2(0.0625, 0.0),
    vec2(0.0, 0.0625), vec2(0.0625, 0.0625),
];
let indices = Indices::U32(vec![0, 2, 1, 1, 2, 3]);
#

Sorry for the weird order of the code.

velvet hearth
#

No, it's fine. It's helpful.

#

Well, do this. Change your fragment_shader() file path to a file that isn't there. Confirm that bevy complains.

#

Write some garbage "XLKJLKSJD" into your shader; confirm that bevy complains.

chrome gyro
#

I did change it to a file that wasn't there. As far as I could tell, bevy doesn't complain. I didn't see any errors in the terminal. Is there some sort of logging I should be enabling?

velvet hearth
#

Are you adding DefaultPlugins first?

chrome gyro
#

Yeah.

velvet hearth
#

When you run it, it should spew a bunch of INFO logging. Do you see that?

#
2024-06-14T03:31:01.972069Z  INFO bevy_render::renderer: AdapterInfo { name: "AMD Radeon Pro Vega 64", vendor: 0, device: 0, device_type: DiscreteGpu, driver: "", driver_info: "", backend: Metal }
2024-06-14T03:31:02.704866Z  INFO bevy_winit::system: Creating new window "App" (0v1)
...
#

Copy and paste your main function, if you don't mind.

chrome gyro
#

Yeah, I see that logging.

#
fn main() {
    // TODO: Read from configuration file.
    App::new()
        .add_plugins(DefaultPlugins.set(WindowPlugin {
            primary_window: Some(bevy::window::Window {
                title: "Unvoga".into(),
                resolution: (1280.0, 720.0).into(),
                present_mode: PresentMode::AutoVsync,
                prevent_default_event_handling: false,
                ..default()
            }),
            ..default()
        }))
        .add_plugins(EguiPlugin)
        .insert_resource(game::settings::UnvogaSettings::default())
        .insert_state(GameState::MainMenu)
        .add_systems(Update, main_menu.run_if(in_state(GameState::MainMenu)))
        .add_systems(OnExit(GameState::LoadingScreen), cleanup_system::<cleanup::LoadingScreen>)
        .add_systems(OnEnter(GameState::MainMenu), on_enter_main_menu)
        .add_systems(OnExit(GameState::MainMenu), cleanup_system::<cleanup::Menu>)
        .add_systems(OnEnter(GameState::SinglePlayer), on_enter_singleplayer)
        .add_systems(OnExit(GameState::SinglePlayer), cleanup_system::<cleanup::SinglePlayer>)
        .insert_resource(DebugFlag::<EnteredMainMenu>::new())
        .insert_resource(Assets::<VoxelMaterial>::default())
        .insert_resource(ClearColor(Color::rgb(0.2,0.2,0.2)))
        .insert_resource(Msaa::Off)
        .run();
}
velvet hearth
#

When I fudge my shader path I get this: 2024-06-14T04:04:11.550166Z ERROR bevy_asset::server: Path not found: bevy_water/underwater2.wgsl

#

But it doesn't panic. It keeps going but doesn't render that object.

chrome gyro
#

Yeah, I'm not even getting that.

velvet hearth
#

Oh, do you have this? .add_plugins(MaterialPlugin::<VoxelMaterial>::default())

chrome gyro
#

No, I don't.

velvet hearth
#

When I put garbage in my shader I get this:

2024-06-14T04:05:14.418854Z ERROR bevy_render::render_resource::pipeline_cache: failed to process shader:
error: expected global item ('struct', 'const', 'var', 'alias', ';', 'fn') or the end of the file, found 'laskjdflkjasd'
   ┌─ embedded://bevy_water/underwater.wgsl:36:1
   │
36 │ laskjdflkjasd
   │ ^^^^^^^^^^^^^ expected global item ('struct', 'const', 'var', 'alias', ';', 'fn') or the end of the file
   │
   = expected global item ('struct', 'const', 'var', 'alias', ';', 'fn') or the end of the file, found 'laskjdflkjasd'
#

Try that.

chrome gyro
#

Woohoo! It's working now. Thanks!

velvet hearth
#

I think your original shader code would have worked too.

chrome gyro
#

I'm not sure how to mark this as resolved.

velvet hearth
#

Not sure either. Saying it works is good enough for me. It’s good to leave here though. Good breadcrumbs for everybody else.

chrome gyro
#

I figured it out. I had to edit the tags.

#

Thanks again!

velvet hearth
#

You’re welcome.

hollow vessel
hollow vessel
# chrome gyro Where do I find documentation for these functions?

you have to check the source unfortunately 😦 this project has a script that at least slaps all the functions into one markdown doc for you https://github.com/alphastrata/shadplay/blob/main/scripts/make-bevy-shaderdoc.py which maybe? makes it more searchable

GitHub

Real-time wgsl visualisation tooling for educating oneself in the art of shader programming - alphastrata/shadplay