Created
September 26, 2025 22:39
-
-
Save Pikachuxxxx/786458e07098040f90a49a7151021a17 to your computer and use it in GitHub Desktop.
Disney BRDF PBR (clear coat, sheen etc.)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Disney BRDF Parameter Structure | |
| struct DisneyBRDFParams | |
| { | |
| float3 baseColor; // Base color (albedo) | |
| float metallic; // Metallic parameter [0, 1] | |
| float subsurface; // Subsurface scattering amount [0, 1] | |
| float specular; // Specular amount [0, 1] | |
| float roughness; // Surface roughness [0, 1] | |
| float specularTint; // Specular tint toward base color [0, 1] | |
| float anisotropic; // Anisotropy amount [0, 1] | |
| float sheen; // Sheen amount [0, 1] | |
| float sheenTint; // Sheen tint toward base color [0, 1] | |
| float clearcoat; // Clearcoat amount [0, 1] | |
| float clearcoatGloss;// Clearcoat glossiness [0, 1] | |
| }; | |
| // Helper functions | |
| float SchlickFresnel(float u) | |
| { | |
| float m = clamp(1.0 - u, 0.0, 1.0); | |
| float m2 = m * m; | |
| return m2 * m2 * m; // m^5 | |
| } | |
| float GTR1(float NdotH, float a) | |
| { | |
| if (a >= 1.0) return 1.0 / PI; | |
| float a2 = a * a; | |
| float t = 1.0 + (a2 - 1.0) * NdotH * NdotH; | |
| return (a2 - 1.0) / (PI * log(a2) * t); | |
| } | |
| float GTR2(float NdotH, float a) | |
| { | |
| float a2 = a * a; | |
| float t = 1.0 + (a2 - 1.0) * NdotH * NdotH; | |
| return a2 / (PI * t * t); | |
| } | |
| float GTR2_aniso(float NdotH, float HdotX, float HdotY, float ax, float ay) | |
| { | |
| return 1.0 / (PI * ax * ay * pow(pow(HdotX / ax, 2.0) + pow(HdotY / ay, 2.0) + NdotH * NdotH, 2.0)); | |
| } | |
| float SmithG_GGX(float NdotV, float alphaG) | |
| { | |
| float a = alphaG * alphaG; | |
| float b = NdotV * NdotV; | |
| return 1.0 / (NdotV + sqrt(a + b - a * b)); | |
| } | |
| float SmithG_GGX_aniso(float NdotV, float VdotX, float VdotY, float ax, float ay) | |
| { | |
| return 1.0 / (NdotV + sqrt(pow(VdotX * ax, 2.0) + pow(VdotY * ay, 2.0) + NdotV * NdotV)); | |
| } | |
| // Disney BRDF calculation function | |
| float3 DisneyBRDF(float3 N, float3 L, float3 V, float3 X, float3 Y, DisneyBRDFParams params) | |
| { | |
| float3 H = normalize(L + V); | |
| float NdotL = max(dot(N, L), 0.0); | |
| float NdotV = max(dot(N, V), 0.0); | |
| float NdotH = max(dot(N, H), 0.0); | |
| float LdotH = max(dot(L, H), 0.0); | |
| float HdotX = dot(H, X); | |
| float HdotY = dot(H, Y); | |
| float VdotX = dot(V, X); | |
| float VdotY = dot(V, Y); | |
| if (NdotL < 0.0 || NdotV < 0.0) return float3(0.0, 0.0, 0.0); | |
| // Diffuse fresnel - go from 1 at normal incidence to 0.5 at grazing | |
| float FL = SchlickFresnel(NdotL); | |
| float FV = SchlickFresnel(NdotV); | |
| float Fd90 = 0.5 + 2.0 * LdotH * LdotH * params.roughness; | |
| float Fd = lerp(1.0, Fd90, FL) * lerp(1.0, Fd90, FV); | |
| // Based on Fresnel specular reflection at grazing angle | |
| float Fss90 = LdotH * LdotH * params.roughness; | |
| float Fss = lerp(1.0, Fss90, FL) * lerp(1.0, Fss90, FV); | |
| float ss = 1.25 * (Fss * (1.0 / (NdotL + NdotV) - 0.5) + 0.5); | |
| // Specular | |
| float aspect = sqrt(1.0 - params.anisotropic * 0.9); | |
| float ax = max(0.001, pow(params.roughness, 2.0) / aspect); | |
| float ay = max(0.001, pow(params.roughness, 2.0) * aspect); | |
| float Ds = GTR2_aniso(NdotH, HdotX, HdotY, ax, ay); | |
| float FH = SchlickFresnel(LdotH); | |
| float3 Ctint = params.baseColor / (params.baseColor.r + params.baseColor.g + params.baseColor.b > 0.0 ? | |
| (params.baseColor.r + params.baseColor.g + params.baseColor.b) / 3.0 : 1.0); | |
| float3 Cspec0 = lerp(params.specular * 0.08 * lerp(float3(1.0, 1.0, 1.0), Ctint, params.specularTint), | |
| params.baseColor, params.metallic); | |
| float3 Fs = lerp(Cspec0, float3(1.0, 1.0, 1.0), FH); | |
| float Gs = SmithG_GGX_aniso(NdotL, dot(L, X), dot(L, Y), ax, ay) * | |
| SmithG_GGX_aniso(NdotV, VdotX, VdotY, ax, ay); | |
| // Sheen | |
| float3 Fsheen = FH * params.sheen * lerp(float3(1.0, 1.0, 1.0), Ctint, params.sheenTint); | |
| // Clearcoat (ior = 1.5 -> F0 = 0.04) | |
| float Dr = GTR1(NdotH, lerp(0.1, 0.001, params.clearcoatGloss)); | |
| float Fr = lerp(0.04, 1.0, FH); | |
| float Gr = SmithG_GGX(NdotL, 0.25) * SmithG_GGX(NdotV, 0.25); | |
| // Diffuse, subsurface, specular, sheen, clearcoat components | |
| float3 diffuse = (1.0 / PI) * params.baseColor * (1.0 - params.metallic) * | |
| (Fd * (1.0 - params.subsurface) + ss * params.subsurface); | |
| float3 specular = Gs * Fs * Ds; | |
| float3 sheen = Fsheen; | |
| float3 clearcoat = 0.25 * params.clearcoat * Gr * Fr * Dr; | |
| return diffuse + specular + sheen + clearcoat; | |
| } | |
| // Main lighting calculation using the Disney BRDF | |
| float3 CalculatePBRLighting(float3 worldPos, float3 normal, DisneyBRDFParams material) | |
| { | |
| // Calculate tangent space basis | |
| float3 N = normalize(normal); | |
| float3 V = normalize(CameraPosition - worldPos); | |
| // Create orthonormal basis (Frisvad method) | |
| float3 X, Y; | |
| if (N.z < -0.999999) { | |
| X = float3(0.0, -1.0, 0.0); | |
| Y = float3(-1.0, 0.0, 0.0); | |
| } else { | |
| float a = 1.0 / (1.0 + N.z); | |
| float b = -N.x * N.y * a; | |
| X = float3(1.0 - N.x * N.x * a, b, -N.x); | |
| Y = float3(b, 1.0 - N.y * N.y * a, -N.y); | |
| } | |
| // Initialize lighting result | |
| float3 finalColor = float3(0.0, 0.0, 0.0); | |
| // For each light source | |
| for (int i = 0; i < LightCount; i++) | |
| { | |
| float3 L = normalize(Lights[i].position - worldPos); | |
| float distance = length(Lights[i].position - worldPos); | |
| float attenuation = 1.0 / (distance * distance); | |
| float3 radiance = Lights[i].color * attenuation; | |
| // Calculate Disney BRDF for this light | |
| float3 brdf = DisneyBRDF(N, L, V, X, Y, material); | |
| // Add contribution from this light | |
| float NdotL = max(dot(N, L), 0.0); | |
| finalColor += brdf * radiance * NdotL; | |
| } | |
| // Add ambient lighting | |
| float3 ambient = float3(0.03, 0.03, 0.03) * material.baseColor * (1.0 - material.metallic); | |
| finalColor += ambient; | |
| // Add Image-based lighting (IBL) | |
| finalColor += CalculateIBL(N, V, X, Y, material); | |
| return finalColor; | |
| } | |
| // Example usage in a pixel shader | |
| float4 PSMain(PSInput input) : SV_TARGET | |
| { | |
| // Sample material textures | |
| float3 albedo = AlbedoTexture.Sample(SamplerState, input.TexCoord).rgb; | |
| float4 materialParams = MaterialParamsTexture.Sample(SamplerState, input.TexCoord); | |
| float3 normal = NormalFromNormalMap(NormalTexture.Sample(SamplerState, input.TexCoord).rgb, input); | |
| // Setup Disney BRDF parameters | |
| DisneyBRDFParams material; | |
| material.baseColor = albedo; | |
| material.metallic = materialParams.r; | |
| material.subsurface = materialParams.g; | |
| material.specular = 0.5; | |
| material.roughness = materialParams.b; | |
| material.specularTint = 0.0; | |
| material.anisotropic = 0.0; | |
| material.sheen = 0.0; | |
| material.sheenTint = 0.5; | |
| material.clearcoat = materialParams.a; | |
| material.clearcoatGloss = 0.0; | |
| // Calculate lighting using Disney BRDF | |
| float3 color = CalculatePBRLighting(input.WorldPos, normal, material); | |
| // Tone mapping and gamma correction | |
| color = color / (color + float3(1.0, 1.0, 1.0)); | |
| color = pow(color, float3(1.0/2.2, 1.0/2.2, 1.0/2.2)); | |
| return float4(color, 1.0); | |
| } | |
| //--------------------------------------------------- | |
| // NEW CODE: IBL IMPLEMENTATION FOR DISNEY BRDF | |
| //--------------------------------------------------- | |
| // IBL Textures and samplers | |
| TextureCube EnvironmentMap; | |
| TextureCube IrradianceMap; | |
| Texture2D BRDFLut; | |
| SamplerState EnvMapSampler; | |
| SamplerState BRDFSampler; | |
| // Calculate IBL contribution for Disney BRDF | |
| float3 CalculateIBL(float3 N, float3 V, float3 X, float3 Y, DisneyBRDFParams params) | |
| { | |
| float NdotV = max(dot(N, V), 0.0); | |
| float3 R = reflect(-V, N); | |
| // Extract material parameters needed for IBL | |
| float roughness = params.roughness; | |
| float metallic = params.metallic; | |
| float3 baseColor = params.baseColor; | |
| // Calculate diffuse IBL contribution | |
| float3 diffuseIBL = IrradianceMap.Sample(EnvMapSampler, N).rgb * baseColor * (1.0 - metallic); | |
| // Calculate specular IBL contribution using split-sum approximation | |
| float lod = roughness * 6.0; // Assuming 6 mip levels | |
| float3 prefilteredColor = EnvironmentMap.SampleLevel(EnvMapSampler, R, lod).rgb; | |
| float2 envBRDF = BRDFLut.Sample(BRDFSampler, float2(NdotV, roughness)).rg; | |
| // Calculate F0 (specular reflectance at normal incidence) based on Disney BRDF | |
| float3 Ctint = baseColor / (baseColor.r + baseColor.g + baseColor.b > 0.0 ? | |
| (baseColor.r + baseColor.g + baseColor.b) / 3.0 : 1.0); | |
| float3 Cspec0 = lerp(params.specular * 0.08 * lerp(float3(1.0, 1.0, 1.0), Ctint, params.specularTint), | |
| baseColor, metallic); | |
| // Specular IBL | |
| float3 specularIBL = prefilteredColor * (Cspec0 * envBRDF.x + envBRDF.y); | |
| // Anisotropic IBL approximation (optional) | |
| if (params.anisotropic > 0.0) | |
| { | |
| float aspect = sqrt(1.0 - params.anisotropic * 0.9); | |
| float ax = max(0.001, pow(roughness, 2.0) / aspect); | |
| float ay = max(0.001, pow(roughness, 2.0) * aspect); | |
| // Create anisotropic roughness view-dependent bent normal | |
| // This is a simple approximation - more accurate methods exist but are more complex | |
| float3 anisotropicDirection = params.anisotropic * (dot(X, V) * X + dot(Y, V) * Y); | |
| float3 bentNormal = normalize(N + anisotropicDirection * (ax - ay) * 0.5); | |
| // Sample environment with bent normal | |
| float3 bentR = reflect(-V, bentNormal); | |
| float3 anisotropicPrefilteredColor = EnvironmentMap.SampleLevel(EnvMapSampler, bentR, lod).rgb; | |
| // Blend based on anisotropy strength | |
| prefilteredColor = lerp(prefilteredColor, anisotropicPrefilteredColor, params.anisotropic); | |
| specularIBL = prefilteredColor * (Cspec0 * envBRDF.x + envBRDF.y); | |
| } | |
| // Clearcoat IBL contribution | |
| float3 clearcoatIBL = float3(0, 0, 0); | |
| if (params.clearcoat > 0.0) | |
| { | |
| float clearcoatRoughness = lerp(0.1, 0.001, params.clearcoatGloss); | |
| float clearcoatLod = clearcoatRoughness * 6.0; | |
| float3 clearcoatRadiance = EnvironmentMap.SampleLevel(EnvMapSampler, R, clearcoatLod).rgb; | |
| // Fixed F0 = 0.04 for clearcoat layer (IOR = 1.5) | |
| float clearcoatFresnel = lerp(0.04, 1.0, SchlickFresnel(NdotV)); | |
| clearcoatIBL = clearcoatRadiance * clearcoatFresnel * params.clearcoat * 0.25; | |
| } | |
| // Sheen IBL contribution (optional, simplified) | |
| float3 sheenIBL = float3(0, 0, 0); | |
| if (params.sheen > 0.0) | |
| { | |
| // Sheen works best with high roughness (fabric-like) | |
| float sheenLod = 4.0; // Higher mip level for sheen | |
| float3 sheenRadiance = IrradianceMap.Sample(EnvMapSampler, N).rgb; // Use irradiance map for diffuse-like behavior | |
| float3 sheenTint = lerp(float3(1.0, 1.0, 1.0), Ctint, params.sheenTint); | |
| sheenIBL = sheenRadiance * params.sheen * sheenTint * (1.0 - metallic) * SchlickFresnel(NdotV); | |
| } | |
| // Combine all IBL components | |
| return diffuseIBL + specularIBL + clearcoatIBL + sheenIBL; | |
| } | |
| // Helper function to generate the BRDF LUT texture (run once at initialization) | |
| void GenerateBRDFLut(RWTexture2D<float2> BRDFLutOutput) | |
| { | |
| uint2 pixel = DispatchThreadID.xy; | |
| uint width, height; | |
| BRDFLutOutput.GetDimensions(width, height); | |
| float NdotV = (pixel.x + 0.5) / width; | |
| float roughness = (pixel.y + 0.5) / height; | |
| // Ensure valid input range | |
| NdotV = max(NdotV, 0.001); | |
| // GGX/Trowbridge-Reitz precomputed DFG term for IBL | |
| float3 V; | |
| V.x = sqrt(1.0 - NdotV * NdotV); // sin | |
| V.y = 0.0; | |
| V.z = NdotV; // cos | |
| float3 N = float3(0.0, 0.0, 1.0); | |
| float A = 0.0; | |
| float B = 0.0; | |
| const uint SAMPLE_COUNT = 1024; | |
| for (uint i = 0; i < SAMPLE_COUNT; ++i) | |
| { | |
| // Sample using Hammersley sequence | |
| float2 Xi = Hammersley(i, SAMPLE_COUNT); | |
| // Sample with GGX importance sampling | |
| float3 H = ImportanceSampleGGX(Xi, N, roughness); | |
| float3 L = 2.0 * dot(V, H) * H - V; | |
| float NdotL = max(L.z, 0.0); | |
| if (NdotL > 0.0) | |
| { | |
| float NdotH = max(H.z, 0.0); | |
| float VdotH = max(dot(V, H), 0.0); | |
| // GGX BRDF | |
| float G = SmithG_GGX(NdotV, roughness) * SmithG_GGX(NdotL, roughness); | |
| float G_Vis = (G * VdotH) / (NdotH * NdotV); | |
| float Fc = pow(1.0 - VdotH, 5.0); | |
| A += (1.0 - Fc) * G_Vis; | |
| B += Fc * G_Vis; | |
| } | |
| } | |
| // Average values | |
| A /= SAMPLE_COUNT; | |
| B /= SAMPLE_COUNT; | |
| // Output to LUT | |
| BRDFLutOutput[pixel] = float2(A, B); | |
| } | |
| // Hammersley low-discrepancy sequence for Monte Carlo integration | |
| float2 Hammersley(uint i, uint N) | |
| { | |
| float2 result; | |
| result.x = float(i) / float(N); | |
| // Radical inverse based on digital bit reversal | |
| uint bits = i; | |
| bits = (bits << 16) | (bits >> 16); | |
| bits = ((bits & 0x55555555) << 1) | ((bits & 0xAAAAAAAA) >> 1); | |
| bits = ((bits & 0x33333333) << 2) | ((bits & 0xCCCCCCCC) >> 2); | |
| bits = ((bits & 0x0F0F0F0F) << 4) | ((bits & 0xF0F0F0F0) >> 4); | |
| bits = ((bits & 0x00FF00FF) << 8) | ((bits & 0xFF00FF00) >> 8); | |
| result.y = float(bits) * 2.3283064365386963e-10; // 0x100000000 | |
| return result; | |
| } | |
| // Generate a sample in the hemisphere based on the GGX distribution | |
| float3 ImportanceSampleGGX(float2 Xi, float3 N, float roughness) | |
| { | |
| float a = roughness * roughness; | |
| // Sample in spherical coordinates | |
| float phi = 2.0 * PI * Xi.x; | |
| float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)); | |
| float sinTheta = sqrt(1.0 - cosTheta * cosTheta); | |
| // Convert to cartesian coordinates | |
| float3 H; | |
| H.x = cos(phi) * sinTheta; | |
| H.y = sin(phi) * sinTheta; | |
| H.z = cosTheta; | |
| // From tangent-space to world-space | |
| float3 up = abs(N.z) < 0.999 ? float3(0.0, 0.0, 1.0) : float3(1.0, 0.0, 0.0); | |
| float3 tangent = normalize(cross(up, N)); | |
| float3 bitangent = cross(N, tangent); | |
| float3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z; | |
| return normalize(sampleVec); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment