# Model Shader Layout
This document describes the public custom 3D model shader contract.
`spottedcat` now treats custom model shaders as full WGSL sources. There is no hook-injection path for `Image` or `Model` shaders.
## Registration
For the highest control, generate a starter WGSL with:
```rust
let wgsl = spottedcat::model_shader_template();
```
Edit that template, then register it with:
Register a custom model shader with:
```rust
let shader_id = spottedcat::register_model_shader(ctx, wgsl_source);
```
The recommended path is the limited template API:
```rust
let shader_id = spottedcat::register_model_shader_template(
ctx,
spottedcat::ModelShaderTemplate::new()
.with_shared("fn tint(c: vec3<f32>) -> vec3<f32> { return c * vec3<f32>(0.8, 0.9, 1.0); }")
.with_fragment_body("return vec4<f32>(tint(src.rgb), src.a * model_globals.extra.x);"),
);
```
Supported slots:
1. `shared`
- helper functions, constants, and shared WGSL declarations
2. `fragment_body`
- the body inserted into `fs_main` after `src` is prepared
The source must define these entry points:
1. `vs_main`
2. `vs_main_instanced`
3. `fs_main`
`vs_main` is used for normal model draws.
`vs_main_instanced` is used for instanced model draws.
`fs_main` is shared by both pipelines.
## Pipeline Contract
These parts remain engine-defined:
1. Pipeline layout and bind groups
2. Vertex buffer layout
3. Instanced vertex layout
4. Depth format and color target format
5. Primitive topology and culling
6. `ShaderOpts` payload shape
## Bind Groups
### `@group(0)`: model, scene, user globals
```wgsl
struct ModelGlobals {
mvp: mat4x4<f32>,
model: mat4x4<f32>,
extra: vec4<f32>,
albedo_uv: vec4<f32>,
pbr_uv: vec4<f32>,
normal_uv: vec4<f32>,
ao_uv: vec4<f32>,
emissive_uv: vec4<f32>,
};
struct Light {
position: vec4<f32>,
color: vec4<f32>,
};
struct SceneGlobals {
camera_pos: vec4<f32>,
camera_right: vec4<f32>,
camera_up: vec4<f32>,
camera_forward: vec4<f32>,
projection_params: vec4<f32>,
ambient_color: vec4<f32>,
fog_color: vec4<f32>,
fog_distance: vec4<f32>,
fog_height: vec4<f32>,
fog_params: vec4<f32>,
fog_background_zenith: vec4<f32>,
fog_background_horizon: vec4<f32>,
fog_background_nadir: vec4<f32>,
fog_background_params: vec4<f32>,
fog_sampling: vec4<f32>,
lights: array<Light, 4>,
light_view_proj: mat4x4<f32>,
};
@group(0) @binding(0) var<uniform> model_globals: ModelGlobals;
@group(0) @binding(1) var<uniform> scene: SceneGlobals;
@group(0) @binding(2) var<uniform> user_globals: array<vec4<f32>, 16>;
```
`user_globals` is populated from `ShaderOpts`.
### `@group(1)`: material textures
```wgsl
@group(1) @binding(0) var t_albedo: texture_2d<f32>;
@group(1) @binding(1) var s_sampler: sampler;
@group(1) @binding(2) var t_pbr: texture_2d<f32>;
@group(1) @binding(3) var t_normal: texture_2d<f32>;
@group(1) @binding(4) var t_ao: texture_2d<f32>;
@group(1) @binding(5) var t_emissive: texture_2d<f32>;
```
### `@group(2)`: bones
```wgsl
@group(2) @binding(0) var<uniform> bone_matrices: array<mat4x4<f32>, 256>;
```
### `@group(3)`: environment and shadow resources
```wgsl
@group(3) @binding(0) var t_shadow: texture_depth_2d;
@group(3) @binding(1) var s_shadow: sampler_comparison;
@group(3) @binding(2) var t_irradiance: texture_cube<f32>;
@group(3) @binding(3) var t_prefiltered: texture_cube<f32>;
@group(3) @binding(4) var t_brdf_lut: texture_2d<f32>;
@group(3) @binding(5) var s_ibl: sampler;
```
## Vertex Inputs
### Standard draw vertex input
```wgsl
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) uv: vec2<f32>,
@location(2) normal: vec3<f32>,
@location(3) joint_indices: vec4<u32>,
@location(4) joint_weights: vec4<f32>,
@location(9) tangent: vec3<f32>,
};
```
### Instanced draw vertex input
```wgsl
struct VertexInputInstanced {
@location(0) position: vec3<f32>,
@location(1) uv: vec2<f32>,
@location(2) normal: vec3<f32>,
@location(3) joint_indices: vec4<u32>,
@location(4) joint_weights: vec4<f32>,
@location(9) tangent: vec3<f32>,
@location(5) instance_mat_0: vec4<f32>,
@location(6) instance_mat_1: vec4<f32>,
@location(7) instance_mat_2: vec4<f32>,
@location(8) instance_mat_3: vec4<f32>,
};
```
## Vertex Output
Both vertex entry points should return the same varyings expected by `fs_main`.
Typical shape:
```wgsl
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) uv: vec2<f32>,
@location(1) normal: vec3<f32>,
@location(2) world_pos: vec3<f32>,
@location(3) tangent: vec3<f32>,
@location(4) shadow_pos: vec3<f32>,
};
```
## Notes
1. Standard and instanced shaders must be kept in the same WGSL source.
2. `spottedcat::model_shader_template()` returns a ready-to-edit starting point for the current contract.
3. `spottedcat::ModelShaderTemplate` is the recommended higher-level path for common material customizations.
4. `examples/metal_sphere.rs` shows the recommended template-based path.
5. `examples/advanced_model_shader_full.rs` shows the advanced full-WGSL path.