| 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
- Load model via
forge_scene_load_model()
- Create PBR fragment shader from compiled bytecode
- Create vertex shader from
scene_model_vert_spirv/dxil/msl (available
via FORGE_SCENE_IMPLEMENTATION)
- Create pipeline with model vertex layout using
sizeof(ForgeSceneModelVertex)
for pitch and offsetof() for attribute offsets
- Release shaders after pipeline creation
- Per frame: bind custom pipeline, iterate model submeshes, push uniforms
with overridden PBR parameters, bind textures, draw
Cook-Torrance BRDF components
/* 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
for the full implementation.