BevyでのShader勉強のメモ。Bevy は WebGPU (wgpu クレート) を使っているのでそっちも理解する必要がある。

  • WebGPUの概観をつかむため、 WebGPU のチュートリアルの"基本" 読み、
  • Rustでの扱い方を学ぶため wgpu チュートリアル の Beginner / The Depth Buffer まで読んだ
  • そろそろBevy上でシェーダ触るか、ということで公式Examplesを写経し、何をどのように触れるのかを理解する ←今ここ

公式のExamplesを写経して理解したことをメモる

Animated

独自のフラグメントシェーダを適用する例。シェーダへの固有のInputなし。 フラグメントシェーダ内では mesh_view_bindings を通して時間を受け取っている。この時間を使ってアニメーションする。

独自のフラグメントシェーダを適用するために、 bevy::pbr::Material トレイトを実装する。これはどのようにレンダリングされるかを表現するトレイト。 レンダリングの対象となるメッシュを持つ Mesh3d コンポーネントに対し、 MaterialMeshMaterial3d コンポーネントに包んで対応付けする。これを MaterialPlugin のシステムがレンダリングしてくれる。

MaterialAsBindGroup をともに実装する必要がある。

bevy::render::render_resource::AsBindGroup トレイト&deriveマクロはBindGroupを表現するトレイトと、それを宣言的に実装するderiveマクロ。シェーダに渡したいリソースを表現する。

StandardMaterial などはこれらを実装している。

サンプルのフラグメントシェーダ内にて mesh_view_bindings::globals#import ( naga_oil ? ) している。これは WGSL 自体の機能ではないらしい。LSPの支援を得ようとすると設定に苦労しそう。

WESL が使えるようなので、そちらを選択する手もあるかも? 'wgsl-analyzer' は WESLも experimentalだがサポートしているとか。あとで写経してみる。

WESL

シェーダ言語を WESL とする例。 import@if の例が入っている。

import

wesl から別の wesl を import するメカニズム。

custom_material.wesl にて util.wesl から import した関数を呼びだしている。 custom_material.wesl

import super::util::make_polka_dots;
// 中略
fn fragment(
    mesh: VertexOutput,
) -> @location(0) vec4<f32> {
    return make_polka_dots(mesh.uv, material.time.x);
}

Rust側にて事前に util.wesl をロードしておく必要がある。例ではロード後、解放されないようリソースにハンドルを保持している。 main.rs

impl Plugin for CustomMaterialPlugin {
    fn build(&self, app: &mut App) {
        let handle = app
            .world_mut()
            .resource_mut::<AssetServer>()
            .load::<Shader>("shaders/util.wesl");
        app.insert_resource(UtilityShader(handle));
    }
}

なお、事前のロードを削除してみると custom_material.wesl ロード時に ModuleNotFound エラーが発生する。

thread 'Async Compute Task Pool (0)' (29112) panicked at /home/mori/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_shader-0.18.0/src/shader_cache.rs:238:30:
called `Result::unwrap()` on an `Err` value: Error(Diagnostic { error: ResolveError(ModuleNotFound(ModulePath { origin: Absolute, components: ["shaders", "util"] }, "Invalid asset id")), detail: Detail { source: None, output: None, module_path: Some(ModulePath { origin: Absolute, components: ["shaders", "custom_material"] }), display_name: None, declaration: None, span: None } })

@if

Conditional Translation : WESLのtranslator にパラメタを渡し、1つのweslコードから複数のバージョンの出力コードを得る方法らしい。 Rust の #[cfg(feature = "...")] 構文に似たもの、とのこと。

WESL の translaator がどのタイミングで動作するものかが気になるところ。 ちゃんと確認して無いが、 RenderPipelineDescriptor にてパラメタを渡している点をみるに、 レンダーパスを生成~描画する度に translation する機会があるとみてよさそうである。

例では、Spaceキー押下により util.wesl の関数の動作を切り替えている。 util.wesl

    @if(!PARTY_MODE) {
        let color1 = vec3<f32>(1.0, 0.4, 0.8);  // pink
        let color2 = vec3<f32>(0.6, 0.2, 1.0);  // purple
        dot_color = mix(color1, color2, is_even);
        is_dot = step(dist_from_center, 0.3);
    } @else {
    // 略

Rust側はなかなか面倒そう? カスタムしたマテリアルの Material::specialize() にてRenderPipelineDescriptor を書き換え、そこでパラメタを渡している。

main.rs

impl Material for CustomMaterial {
    // 略
    fn specialize(
        _pipeline: &MaterialPipeline,
        descriptor: &mut RenderPipelineDescriptor,
        _layout: &MeshVertexBufferLayoutRef,
        key: MaterialPipelineKey<Self>,
    ) -> Result<(), SpecializedMeshPipelineError> {
        let fragment = descriptor.fragment.as_mut().unwrap();
        fragment.shader_defs.push(ShaderDefVal::Bool(
            "PARTY_MODE".to_string(),
            key.bind_group_data.party_mode,
        ));
        Ok(())
    }
}

key.bind_group_data には Materialbind_group_data() にて作成したものが入っている。 これを使って CustomMaterial のフィールドの値を渡している。

main.rs : #[bind_gropu_data(CustomMaterialKey)] を指定し、 &CustomMaterial から CustomMaterialKey を生成可能にすることで上記 key として渡せる。

// This is the struct that will be passed to your shader
#[derive(Asset, TypePath, AsBindGroup, Clone)]
#[bind_group_data(CustomMaterialKey)]
struct CustomMaterial {
    // Needed for 16 bit alignment in WebGL2
    #[uniform(0)]
    time: Vec4,
    party_mode: bool,
}

#[repr(C)]
#[derive(Eq, PartialEq, Hash, Copy, Clone)]
struct CustomMaterialKey {
    party_mode: bool,
}

impl From<&CustomMaterial> for CustomMaterialKey {
    fn from(material: &CustomMaterial) -> Self {
        Self {
            party_mode: material.party_mode,
        }
    }
}

Extended material

Post processing - Custom render pass

Instancing

Render depth to texture

#3 Custom vertex attribute

Custom render phase

Custom phase item

TODO:

  • WESL の translation の機会は?