No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BRDF.hlsl 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. #ifndef UNIVERSAL_BRDF_INCLUDED
  2. #define UNIVERSAL_BRDF_INCLUDED
  3. #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/BSDF.hlsl"
  4. #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl"
  5. #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Deprecated.hlsl"
  6. #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceData.hlsl"
  7. #define kDielectricSpec half4(0.04, 0.04, 0.04, 1.0 - 0.04) // standard dielectric reflectivity coef at incident angle (= 4%)
  8. struct BRDFData
  9. {
  10. half3 albedo;
  11. half3 diffuse;
  12. half3 specular;
  13. half reflectivity;
  14. half perceptualRoughness;
  15. half roughness;
  16. half roughness2;
  17. half grazingTerm;
  18. // We save some light invariant BRDF terms so we don't have to recompute
  19. // them in the light loop. Take a look at DirectBRDF function for detailed explaination.
  20. half normalizationTerm; // roughness * 4.0 + 2.0
  21. half roughness2MinusOne; // roughness^2 - 1.0
  22. };
  23. half ReflectivitySpecular(half3 specular)
  24. {
  25. return Max3(specular.r, specular.g, specular.b);
  26. }
  27. half OneMinusReflectivityMetallic(half metallic)
  28. {
  29. // We'll need oneMinusReflectivity, so
  30. // 1-reflectivity = 1-lerp(dielectricSpec, 1, metallic) = lerp(1-dielectricSpec, 0, metallic)
  31. // store (1-dielectricSpec) in kDielectricSpec.a, then
  32. // 1-reflectivity = lerp(alpha, 0, metallic) = alpha + metallic*(0 - alpha) =
  33. // = alpha - metallic * alpha
  34. half oneMinusDielectricSpec = kDielectricSpec.a;
  35. return oneMinusDielectricSpec - metallic * oneMinusDielectricSpec;
  36. }
  37. half MetallicFromReflectivity(half reflectivity)
  38. {
  39. half oneMinusDielectricSpec = kDielectricSpec.a;
  40. return (reflectivity - kDielectricSpec.r) / oneMinusDielectricSpec;
  41. }
  42. inline void InitializeBRDFDataDirect(half3 albedo, half3 diffuse, half3 specular, half reflectivity, half oneMinusReflectivity, half smoothness, inout half alpha, out BRDFData outBRDFData)
  43. {
  44. outBRDFData = (BRDFData)0;
  45. outBRDFData.albedo = albedo;
  46. outBRDFData.diffuse = diffuse;
  47. outBRDFData.specular = specular;
  48. outBRDFData.reflectivity = reflectivity;
  49. outBRDFData.perceptualRoughness = PerceptualSmoothnessToPerceptualRoughness(smoothness);
  50. outBRDFData.roughness = max(PerceptualRoughnessToRoughness(outBRDFData.perceptualRoughness), HALF_MIN_SQRT);
  51. outBRDFData.roughness2 = max(outBRDFData.roughness * outBRDFData.roughness, HALF_MIN);
  52. outBRDFData.grazingTerm = saturate(smoothness + reflectivity);
  53. outBRDFData.normalizationTerm = outBRDFData.roughness * half(4.0) + half(2.0);
  54. outBRDFData.roughness2MinusOne = outBRDFData.roughness2 - half(1.0);
  55. // Input is expected to be non-alpha-premultiplied while ROP is set to pre-multiplied blend.
  56. // We use input color for specular, but (pre-)multiply the diffuse with alpha to complete the standard alpha blend equation.
  57. // In shader: Cs' = Cs * As, in ROP: Cs' + Cd(1-As);
  58. // i.e. we only alpha blend the diffuse part to background (transmittance).
  59. #if defined(_ALPHAPREMULTIPLY_ON)
  60. // TODO: would be clearer to multiply this once to accumulated diffuse lighting at end instead of the surface property.
  61. outBRDFData.diffuse *= alpha;
  62. #endif
  63. }
  64. // Legacy: do not call, will not correctly initialize albedo property.
  65. inline void InitializeBRDFDataDirect(half3 diffuse, half3 specular, half reflectivity, half oneMinusReflectivity, half smoothness, inout half alpha, out BRDFData outBRDFData)
  66. {
  67. InitializeBRDFDataDirect(half3(0.0, 0.0, 0.0), diffuse, specular, reflectivity, oneMinusReflectivity, smoothness, alpha, outBRDFData);
  68. }
  69. // Initialize BRDFData for material, managing both specular and metallic setup using shader keyword _SPECULAR_SETUP.
  70. inline void InitializeBRDFData(half3 albedo, half metallic, half3 specular, half smoothness, inout half alpha, out BRDFData outBRDFData)
  71. {
  72. #ifdef _SPECULAR_SETUP
  73. half reflectivity = ReflectivitySpecular(specular);
  74. half oneMinusReflectivity = half(1.0) - reflectivity;
  75. half3 brdfDiffuse = albedo * oneMinusReflectivity;
  76. half3 brdfSpecular = specular;
  77. #else
  78. half oneMinusReflectivity = OneMinusReflectivityMetallic(metallic);
  79. half reflectivity = half(1.0) - oneMinusReflectivity;
  80. half3 brdfDiffuse = albedo * oneMinusReflectivity;
  81. half3 brdfSpecular = lerp(kDielectricSpec.rgb, albedo, metallic);
  82. #endif
  83. InitializeBRDFDataDirect(albedo, brdfDiffuse, brdfSpecular, reflectivity, oneMinusReflectivity, smoothness, alpha, outBRDFData);
  84. }
  85. inline void InitializeBRDFData(inout SurfaceData surfaceData, out BRDFData brdfData)
  86. {
  87. InitializeBRDFData(surfaceData.albedo, surfaceData.metallic, surfaceData.specular, surfaceData.smoothness, surfaceData.alpha, brdfData);
  88. }
  89. half3 ConvertF0ForClearCoat15(half3 f0)
  90. {
  91. return ConvertF0ForAirInterfaceToF0ForClearCoat15Fast(f0);
  92. }
  93. inline void InitializeBRDFDataClearCoat(half clearCoatMask, half clearCoatSmoothness, inout BRDFData baseBRDFData, out BRDFData outBRDFData)
  94. {
  95. outBRDFData = (BRDFData)0;
  96. outBRDFData.albedo = half(1.0);
  97. // Calculate Roughness of Clear Coat layer
  98. outBRDFData.diffuse = kDielectricSpec.aaa; // 1 - kDielectricSpec
  99. outBRDFData.specular = kDielectricSpec.rgb;
  100. outBRDFData.reflectivity = kDielectricSpec.r;
  101. outBRDFData.perceptualRoughness = PerceptualSmoothnessToPerceptualRoughness(clearCoatSmoothness);
  102. outBRDFData.roughness = max(PerceptualRoughnessToRoughness(outBRDFData.perceptualRoughness), HALF_MIN_SQRT);
  103. outBRDFData.roughness2 = max(outBRDFData.roughness * outBRDFData.roughness, HALF_MIN);
  104. outBRDFData.normalizationTerm = outBRDFData.roughness * half(4.0) + half(2.0);
  105. outBRDFData.roughness2MinusOne = outBRDFData.roughness2 - half(1.0);
  106. outBRDFData.grazingTerm = saturate(clearCoatSmoothness + kDielectricSpec.x);
  107. // Modify Roughness of base layer using coat IOR
  108. half ieta = lerp(1.0h, CLEAR_COAT_IETA, clearCoatMask);
  109. half coatRoughnessScale = Sq(ieta);
  110. half sigma = RoughnessToVariance(PerceptualRoughnessToRoughness(baseBRDFData.perceptualRoughness));
  111. baseBRDFData.perceptualRoughness = RoughnessToPerceptualRoughness(VarianceToRoughness(sigma * coatRoughnessScale));
  112. // Recompute base material for new roughness, previous computation should be eliminated by the compiler (as it's unused)
  113. baseBRDFData.roughness = max(PerceptualRoughnessToRoughness(baseBRDFData.perceptualRoughness), HALF_MIN_SQRT);
  114. baseBRDFData.roughness2 = max(baseBRDFData.roughness * baseBRDFData.roughness, HALF_MIN);
  115. baseBRDFData.normalizationTerm = baseBRDFData.roughness * 4.0h + 2.0h;
  116. baseBRDFData.roughness2MinusOne = baseBRDFData.roughness2 - 1.0h;
  117. // Darken/saturate base layer using coat to surface reflectance (vs. air to surface)
  118. baseBRDFData.specular = lerp(baseBRDFData.specular, ConvertF0ForClearCoat15(baseBRDFData.specular), clearCoatMask);
  119. // TODO: what about diffuse? at least in specular workflow diffuse should be recalculated as it directly depends on it.
  120. }
  121. BRDFData CreateClearCoatBRDFData(SurfaceData surfaceData, inout BRDFData brdfData)
  122. {
  123. BRDFData brdfDataClearCoat = (BRDFData)0;
  124. #if defined(_CLEARCOAT) || defined(_CLEARCOATMAP)
  125. // base brdfData is modified here, rely on the compiler to eliminate dead computation by InitializeBRDFData()
  126. InitializeBRDFDataClearCoat(surfaceData.clearCoatMask, surfaceData.clearCoatSmoothness, brdfData, brdfDataClearCoat);
  127. #endif
  128. return brdfDataClearCoat;
  129. }
  130. // Computes the specular term for EnvironmentBRDF
  131. half3 EnvironmentBRDFSpecular(BRDFData brdfData, half fresnelTerm)
  132. {
  133. float surfaceReduction = 1.0 / (brdfData.roughness2 + 1.0);
  134. return half3(surfaceReduction * lerp(brdfData.specular, brdfData.grazingTerm, fresnelTerm));
  135. }
  136. half3 EnvironmentBRDF(BRDFData brdfData, half3 indirectDiffuse, half3 indirectSpecular, half fresnelTerm)
  137. {
  138. half3 c = indirectDiffuse * brdfData.diffuse;
  139. c += indirectSpecular * EnvironmentBRDFSpecular(brdfData, fresnelTerm);
  140. return c;
  141. }
  142. // Environment BRDF without diffuse for clear coat
  143. half3 EnvironmentBRDFClearCoat(BRDFData brdfData, half clearCoatMask, half3 indirectSpecular, half fresnelTerm)
  144. {
  145. float surfaceReduction = 1.0 / (brdfData.roughness2 + 1.0);
  146. return indirectSpecular * EnvironmentBRDFSpecular(brdfData, fresnelTerm) * clearCoatMask;
  147. }
  148. // Computes the scalar specular term for Minimalist CookTorrance BRDF
  149. // NOTE: needs to be multiplied with reflectance f0, i.e. specular color to complete
  150. half DirectBRDFSpecular(BRDFData brdfData, half3 normalWS, half3 lightDirectionWS, half3 viewDirectionWS)
  151. {
  152. float3 lightDirectionWSFloat3 = float3(lightDirectionWS);
  153. float3 halfDir = SafeNormalize(lightDirectionWSFloat3 + float3(viewDirectionWS));
  154. float NoH = saturate(dot(float3(normalWS), halfDir));
  155. half LoH = half(saturate(dot(lightDirectionWSFloat3, halfDir)));
  156. // GGX Distribution multiplied by combined approximation of Visibility and Fresnel
  157. // BRDFspec = (D * V * F) / 4.0
  158. // D = roughness^2 / ( NoH^2 * (roughness^2 - 1) + 1 )^2
  159. // V * F = 1.0 / ( LoH^2 * (roughness + 0.5) )
  160. // See "Optimizing PBR for Mobile" from Siggraph 2015 moving mobile graphics course
  161. // https://community.arm.com/events/1155
  162. // Final BRDFspec = roughness^2 / ( NoH^2 * (roughness^2 - 1) + 1 )^2 * (LoH^2 * (roughness + 0.5) * 4.0)
  163. // We further optimize a few light invariant terms
  164. // brdfData.normalizationTerm = (roughness + 0.5) * 4.0 rewritten as roughness * 4.0 + 2.0 to a fit a MAD.
  165. float d = NoH * NoH * brdfData.roughness2MinusOne + 1.00001f;
  166. half LoH2 = LoH * LoH;
  167. half specularTerm = brdfData.roughness2 / ((d * d) * max(0.1h, LoH2) * brdfData.normalizationTerm);
  168. // On platforms where half actually means something, the denominator has a risk of overflow
  169. // clamp below was added specifically to "fix" that, but dx compiler (we convert bytecode to metal/gles)
  170. // sees that specularTerm have only non-negative terms, so it skips max(0,..) in clamp (leaving only min(100,...))
  171. #if REAL_IS_HALF
  172. specularTerm = specularTerm - HALF_MIN;
  173. // Update: Conservative bump from 100.0 to 1000.0 to better match the full float specular look.
  174. // Roughly 65504.0 / 32*2 == 1023.5,
  175. // or HALF_MAX / ((mobile) MAX_VISIBLE_LIGHTS * 2),
  176. // to reserve half of the per light range for specular and half for diffuse + indirect + emissive.
  177. specularTerm = clamp(specularTerm, 0.0, 1000.0); // Prevent FP16 overflow on mobiles
  178. #endif
  179. return specularTerm;
  180. }
  181. // Based on Minimalist CookTorrance BRDF
  182. // Implementation is slightly different from original derivation: http://www.thetenthplanet.de/archives/255
  183. //
  184. // * NDF [Modified] GGX
  185. // * Modified Kelemen and Szirmay-Kalos for Visibility term
  186. // * Fresnel approximated with 1/LdotH
  187. half3 DirectBDRF(BRDFData brdfData, half3 normalWS, half3 lightDirectionWS, half3 viewDirectionWS, bool specularHighlightsOff)
  188. {
  189. // Can still do compile-time optimisation.
  190. // If no compile-time optimized, extra overhead if branch taken is around +2.5% on some untethered platforms, -10% if not taken.
  191. [branch] if (!specularHighlightsOff)
  192. {
  193. half specularTerm = DirectBRDFSpecular(brdfData, normalWS, lightDirectionWS, viewDirectionWS);
  194. half3 color = brdfData.diffuse + specularTerm * brdfData.specular;
  195. return color;
  196. }
  197. else
  198. return brdfData.diffuse;
  199. }
  200. // Based on Minimalist CookTorrance BRDF
  201. // Implementation is slightly different from original derivation: http://www.thetenthplanet.de/archives/255
  202. //
  203. // * NDF [Modified] GGX
  204. // * Modified Kelemen and Szirmay-Kalos for Visibility term
  205. // * Fresnel approximated with 1/LdotH
  206. half3 DirectBRDF(BRDFData brdfData, half3 normalWS, half3 lightDirectionWS, half3 viewDirectionWS)
  207. {
  208. #ifndef _SPECULARHIGHLIGHTS_OFF
  209. return brdfData.diffuse + DirectBRDFSpecular(brdfData, normalWS, lightDirectionWS, viewDirectionWS) * brdfData.specular;
  210. #else
  211. return brdfData.diffuse;
  212. #endif
  213. }
  214. #endif