BRDF.hlsli 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. #define PI 3.1415
  2. #define PI_X2 6.2831
  3. #define PI_X4 12.5663
  4. static const float InvPi = 1.0f / PI;
  5. half GetLuminance(half3 color)
  6. {
  7. return dot(color, half3(0.2126h, 0.7152h, 0.0722h));
  8. }
  9. half SmoothnessToRoughness(half smoothness)
  10. {
  11. return (1.0f - smoothness) * (1.0f - smoothness);
  12. }
  13. half RoughnessToSmoothness(half roughness)
  14. {
  15. return 1.0f - sqrt(roughness);
  16. }
  17. half BlinnBRDF(half3 N, half3 V, half3 L, half Gloss)
  18. {
  19. half3 H = normalize(V + L);
  20. // Compute perceptually linear exponent in range 2-2048
  21. half power = exp2(10.h * Gloss + 1.h);
  22. half fNormFactor = power * (1.0 / 8.0) + (2.0 / 8.0);
  23. return fNormFactor * pow(saturate(dot(N, H)), power);
  24. }
  25. half3 SpecularBRDF(half3 N, half3 V, half3 L, half m, half3 f0, half NormalizationFactor)
  26. {
  27. half epsilon = 1e-5h; // epsilon to prevent division by 0
  28. half m2 = m * m;
  29. half3 H = normalize(V + L);
  30. // GGX NDF
  31. half NdotH = saturate(dot(N, H));
  32. half spec = (NdotH * m2 - NdotH) * NdotH + 1;
  33. spec = m2 / max((spec * spec) * NormalizationFactor, epsilon);
  34. // Correlated Smith Visibility Term (including Cook-Torrance denominator)
  35. half NdotL = saturate(dot(N, L));
  36. half NdotV = abs(dot(N, V)) + epsilon;
  37. half Gv = NdotL * sqrt((-NdotV * m2 + NdotV) * NdotV + m2);
  38. half Gl = NdotV * sqrt((-NdotL * m2 + NdotL) * NdotL + m2);
  39. spec *= 0.5h / max(Gv + Gl, epsilon);
  40. // Fresnel (Schlick approximation)
  41. half f90 = saturate(dot(f0, 0.33333h) / 0.02h); // Assume micro-occlusion when reflectance is below 2%
  42. half3 fresnel = lerp(f0, f90, pow(1 - saturate(dot(L, H)), 5));
  43. return fresnel * spec;
  44. }
  45. half3 SpecularBRDF(half3 N, half3 V, half3 L, half Gloss, half3 SpecCol)
  46. {
  47. half m = max(SmoothnessToRoughness(Gloss), 0.001); // Prevent highlights from getting too tiny without area lights
  48. return SpecularBRDF(N, V, L, m, SpecCol, 1.0f);
  49. }
  50. ////////////////////////////////////////////////////////////////////////////
  51. // Anisotropic Kajiya Kay model
  52. half KajiyaKayAnisotropic(half3 T, half3 H, half Exp)
  53. {
  54. half TdotH = dot(T, H);
  55. half fSpec = sqrt(max(1.0 - TdotH * TdotH, 0.01));
  56. return pow(fSpec, Exp);
  57. }
  58. #define AREA_LIGHT_SPHERE 0
  59. #define AREA_LIGHT_RECTANGLE 1
  60. float SphereNormalization(float len, float lightSize, float m)
  61. {
  62. // Compute the normalization factors.
  63. // Note: just using sphere normalization (todo: come up with proper disk/plane normalization)
  64. float dist = saturate(lightSize / len);
  65. float normFactor = m / saturate(m + 0.5 * dist);
  66. return normFactor * normFactor;
  67. }
  68. float3 SphereLight(float3 R, float3 L, float m, half4x4 mAreaLightMatr)
  69. {
  70. // Intersect the sphere.
  71. float3 centerDir = L - dot(L, R) * R;
  72. L = L - centerDir * saturate(mAreaLightMatr[3].y / (length(centerDir) + 1e-6));
  73. return L.xyz;
  74. }
  75. float3 RectangleLight(float3 R, float3 L, float m, half4x4 mAreaLightMatr)
  76. {
  77. // Intersect the plane.
  78. float RdotN = dot(mAreaLightMatr[0].xyz, R.xyz) + 1e-6;
  79. float intersectLen = dot(mAreaLightMatr[0].xyz, L) / RdotN;
  80. float3 I = R.xyz * intersectLen - L.xyz;
  81. // Project the intersection to 2D and clamp it to the light radius to get a point inside or on the edge of the light.
  82. half2 lightPos2D = half2(dot(I.xyz, mAreaLightMatr[1].xyz), dot(I.xyz, mAreaLightMatr[2].xyz));
  83. half2 lightClamp2D = clamp(lightPos2D, -mAreaLightMatr[3].xy, mAreaLightMatr[3].xy);
  84. // New light direction.
  85. L = L + (mAreaLightMatr[1].xyz * lightClamp2D.x) + mAreaLightMatr[2].xyz * lightClamp2D.y;
  86. return L.xyz;
  87. }
  88. half4 AreaLightIntersection(float3 N, float3 V, float3 L, float m, half4x4 areaLightMatrix, int lightType)
  89. {
  90. half4 lightVec = half4(L.xyz, 1.0f);
  91. float3 R = reflect(V, N);
  92. [branch] if (lightType == AREA_LIGHT_RECTANGLE)
  93. lightVec.xyz = RectangleLight(R, L, m, areaLightMatrix);
  94. else
  95. lightVec.xyz = SphereLight(R, L, m, areaLightMatrix);
  96. // Normalize.
  97. float len = max(length(lightVec.xyz), 1e-6);
  98. lightVec.xyz /= len;
  99. // Energy normalization
  100. lightVec.w = SphereNormalization(len, lightType == AREA_LIGHT_RECTANGLE ? length(areaLightMatrix[3].xy) : areaLightMatrix[3].y, m);
  101. return lightVec;
  102. }
  103. float3 AreaLightGGX(float3 N, float3 V, float3 L, float Gloss, float3 SpecCol, half4x4 areaLightMatrix, int lightType)
  104. {
  105. float m = max(SmoothnessToRoughness(Gloss), 0.001);
  106. half4 lightVec = AreaLightIntersection(N, V, L, m, areaLightMatrix, lightType);
  107. return SpecularBRDF(N, V, lightVec.xyz, m, SpecCol, lightVec.w);
  108. }
  109. // Diffuse BRDFs
  110. float3 ComputeNearestLightOnRectangle(float3 vPosition, float3 vLightPoint, float4x4 mAreaLightMatr)
  111. {
  112. // Calculate light space plane.
  113. float3 vLightDir = dot(mAreaLightMatr[0].xyz, vLightPoint.xyz) * mAreaLightMatr[0].xyz - vLightPoint.xyz;
  114. // Calculate the nearest point.
  115. half2 vSurfArea = float2(dot(vLightDir.xyz, mAreaLightMatr[1].xyz), dot(vLightDir.xyz, mAreaLightMatr[2].xyz));
  116. half2 vSurfAreaClamp = clamp(vSurfArea.xy, -mAreaLightMatr[3].xy, mAreaLightMatr[3].xy); // 1 alu
  117. float3 vNearestPoint = mAreaLightMatr[1].xyz * vSurfAreaClamp.x + (mAreaLightMatr[2].xyz * vSurfAreaClamp.y);
  118. return vLightPoint.xyz + vNearestPoint.xyz;
  119. }
  120. float OrenNayarBRDF(float3 N, float3 V, float3 L, float Gloss, float NdotL)
  121. {
  122. float m = SmoothnessToRoughness(Gloss);
  123. m *= m * m; // Map GGX to Oren-Nayar roughness (purely empirical remapping)
  124. // Approximation of the full quality Oren-Nayar model
  125. float s = dot(L, V) - dot(N, L) * dot(N, V);
  126. float t = s <= 0 ? 1 : max(max(dot(N, L), dot(N, V)), 1e-6);
  127. float A = 1.0h / (1.0h + (0.5h - 2.0h / (3.0h * PI)) * m);
  128. float B = m * A;
  129. return NdotL * max(A + B * (s / t), 0);
  130. }
  131. float BurleyBRDF(float NdotL, float NdotV, float VdotH, float roughness)
  132. {
  133. NdotV = max(NdotV, 0.1); // Prevent overly dark edges
  134. // Burley BRDF with renormalization to conserve energy
  135. float energyBias = 0.5 * roughness;
  136. float energyFactor = lerp(1, 1 / 1.51, roughness);
  137. float fd90 = energyBias + 2.0 * VdotH * VdotH * roughness;
  138. float scatterL = lerp(1, fd90, pow(1 - NdotL, 5));
  139. float scatterV = lerp(1, fd90, pow(1 - NdotV, 5));
  140. return scatterL * scatterV * energyFactor * NdotL;
  141. }
  142. float DiffuseBRDF(float3 N, float3 V, float3 L, float Gloss, float NdotL)
  143. {
  144. // TODO: Share computations with Specular BRDF
  145. float m = SmoothnessToRoughness(min(Gloss, 1));
  146. float VdotH = saturate(dot(V, normalize(V + L)));
  147. float NdotV = abs(dot(N, V)) + 1e-5h;
  148. // Burley BRDF with renormalization to conserve energy
  149. return BurleyBRDF(NdotL, NdotV, VdotH, m);
  150. }