Physically Based Rendering (PBR)
Build metallic and dielectric materials with Cook-Torrance lighting, Fresnel, and the bundled PBR module.
Remix the technique
Use the playground as a lab: swap constants, trim passes, and see which parts of the effect are structural versus just polish.
Open Technique LabPhysically Based Rendering models how light interacts with a surface instead of faking a highlight shape per material. In BWSL that usually means a Cook-Torrance specular term, energy-conserving diffuse, and a small material parameter set: albedo, roughness, metallic, and ao.
The live demos below keep the math inline so you can tweak it directly. In production shaders, the same workflow maps cleanly to the bundled PBR module.
Metallic and Roughness
This sphere sweeps roughness from left to right and metallic from bottom to top so you can see how the specular lobe tightens and the base color moves from diffuse to reflective.
Fresnel and Edge Response
At grazing angles the specular term rises sharply. That edge lift is what makes polished materials feel solid instead of flat.
Material Split
The same lighting model can describe matte paint, brushed metal, or a polished conductor just by changing the material inputs.
Using the Bundled Module
The docs site ships a PBR module with the common BRDF helpers already implemented. In a normal pipeline you can import it and hand it a material struct plus your world-space normal, view direction, and light direction:
pipeline PBRSurface {
import PBR
attributes {
position: float3
normal: float3
texcoord: float2
}
pass "Main" {
use attributes { position, normal, texcoord }
vertex {
float4 worldPos = resources.modelMatrix * float4(attributes.position, 1.0);
output.position = resources.projectionMatrix * resources.viewMatrix * worldPos;
output.worldPos = worldPos.xyz;
output.normal = normalize(resources.normalMatrix * attributes.normal);
output.uv = attributes.texcoord;
}
fragment {
PBR::PBRMaterial mat;
mat.albedo = float3(0.92, 0.64, 0.22);
mat.roughness = 0.28;
mat.metallic = 0.9;
mat.ao = 1.0;
float3 N = normalize(input.normal);
float3 V = normalize(resources.cameraPosition - input.worldPos);
float3 L = normalize(resources.lightPosition - input.worldPos);
float3 radiance = resources.lightColor * 2.0;
float3 color = PBR::calculateDirectLighting(mat, N, V, L, radiance);
color = color + mat.albedo * 0.04;
output.color = float4(color, 1.0);
}
}
}
Keep Inputs in World Space
The BRDF only behaves predictably when N, V, and L are all normalized in the same space. World space is usually the easiest choice for material docs and debugging.