- name
- forge-pbr-shading
- description
- Add Cook-Torrance PBR shading with GGX, Schlick-GGX, and Schlick Fresnel to an SDL GPU project alongside forge_scene.h
Add physically-based rendering to an SDL GPU project. Implements a
Cook-Torrance microfacet BRDF fragment shader that uses the same texture
bindings and uniform layout as `forge_scene.h`'s built-in model pipeline,
enabling side-by-side comparison with Blinn-Phong.
## When to use
- Replacing Blinn-Phong with physically-based shading
- Adding metallic-roughness material support
- Comparing different lighting models on the same geometry
- Creating a custom pipeline that coexists with forge_scene.h
## Key API calls
- `forge_scene_create_shader()` — create vertex/fragment shaders from bytecode
- `SDL_CreateGPUGraphicsPipeline()` — create custom pipeline matching model vertex layout
- `SDL_BindGPUFragmentSamplers()` — bind per-material textures with fallbacks
- `SDL_PushGPUFragmentUniformData()` — push material uniform data to fragment shader
- `forge_scene_load_model()` — load pipeline-processed model
- `forge_scene_draw_model()` — draw with built-in Blinn-Phong for comparison
## Correct order
1. Load model via `forge_scene_load_model()`
2. Create PBR fragment shader from compiled bytecode
3. Create vertex shader from `scene_model_vert_spirv/dxil/msl` (available
via `FORGE_SCENE_IMPLEMENTATION`)
4. Create pipeline with model vertex layout using `sizeof(ForgeSceneModelVertex)`
for pitch and `offsetof()` for attribute offsets
5. Release shaders after pipeline creation
6. Per frame: bind custom pipeline, iterate model submeshes, push uniforms
with overridden PBR parameters, bind textures, draw
## Cook-Torrance BRDF components
```hlsl
/* GGX Normal Distribution */
float D = alpha2 / (PI * pow(NdotH * NdotH * (alpha2 - 1.0) + 1.0, 2.0));
/* Schlick-GGX Geometry (Smith method) */
float k = (roughness + 1.0) * (roughness + 1.0) / 8.0;
float G = (NdotV / (NdotV * (1-k) + k)) * (NdotL / (NdotL * (1-k) + k));
/* Schlick Fresnel */
float3 F = F0 + (1.0 - F0) * pow(1.0 - VdotH, 5.0);
/* Full specular: D * G * F / (4 * NdotV * NdotL) */
/* Energy-conserving diffuse: (1 - F) * (1 - metallic) * albedo / PI */
```
## Common mistakes
- **F0 for metals must use albedo color** — `lerp(0.04, albedo, metallic)`,
not a fixed 0.04 for all materials
- **alpha = roughness^2, not roughness** — GGX uses squared roughness
- **k differs for direct vs IBL lighting** — direct: `(r+1)^2/8`,
IBL: `r^2/2`
- **Divide-by-zero guard** — clamp NdotV above zero (e.g. 0.001) in the
Cook-Torrance denominator
- **Diffuse must divide by pi** — without this, the surface reflects more
energy than it receives
## Reference
See [Lesson 51 — PBR Shading Model](../../../lessons/gpu/51-pbr-shading/)
for the full implementation.
Voir sur GitHub