DirectLighting.bslinc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. #include "$ENGINE$\SurfaceData.bslinc"
  2. mixin LightData
  3. {
  4. code
  5. {
  6. // Note: Size must be multiple of largest element, because of std430 rules
  7. struct LightData
  8. {
  9. float3 position;
  10. float boundRadius;
  11. float3 direction;
  12. float luminance;
  13. float3 spotAngles;
  14. float attRadiusSqrdInv;
  15. float3 color;
  16. float srcRadius;
  17. float3 shiftedLightPosition;
  18. float padding;
  19. };
  20. };
  21. };
  22. mixin DirectLightingCommon
  23. {
  24. mixin LightData;
  25. mixin SurfaceData;
  26. code
  27. {
  28. #define PI 3.1415926
  29. #define HALF_PI 1.5707963
  30. float3 calcMicrofacetFresnelShlick(float3 F0, float LoH)
  31. {
  32. return F0 + (1.0f - F0) * pow(1.0f - LoH, 5.0f);
  33. }
  34. float calcMicrofacetShadowingSmithGGX(float roughness4, float NoV, float NoL)
  35. {
  36. // Note: It's probably better to use the joint shadowing + masking version of this function
  37. // Note: Original GGX G1 multiplied by NoV & NoL (respectively), so that the microfacet function divisor gets canceled out
  38. // Original formula being (ignoring the factor for masking negative directions):
  39. // G1(v) = 2 / (1 + sqrt(1 + roughness^4 * tan^2(v)))
  40. //
  41. // Using trig identities: tan = sin/cos & sin^2 + cos^2 = 1
  42. // G1(v) = 2 / (1 + sqrt(1 + roughness^4 * (1 - cos^2(v))/cos^2(v)))
  43. //
  44. // Multiply by cos(v) so that we cancel out the (NoL * NoV) factor in the microfacet formula divisor
  45. // G1(v) = 2 * cos(v) / (cos^2(v) + sqrt(cos^2 + roughness^4 - roughness^4 * cos^2(v)))
  46. //
  47. // Actually do the cancellation:
  48. // G1(v) = 2 / (cos^2(v) + sqrt(cos^2 + roughness^4 - roughness^4 * cos^2(v)))
  49. //
  50. // Also cancel out the 2 and the 4:
  51. // G1(v) = 1 / (cos^2(v) + sqrt(cos^2 + roughness^4 - roughness^4 * cos^2(v)))
  52. //
  53. // Final equation being:
  54. // G(v, l) = G1(v) * G1(l)
  55. //
  56. // Where cos(v) is NoV or NoL
  57. float g1V = NoV + sqrt(NoV * (NoV - NoV * roughness4) + roughness4);
  58. float g1L = NoL + sqrt(NoL * (NoL - NoL * roughness4) + roughness4);
  59. return rcp(g1V * g1L);
  60. }
  61. float calcMicrofacetDistGGX(float roughness4, float NoH)
  62. {
  63. float d = (NoH * roughness4 - NoH) * NoH + 1.0f;
  64. return roughness4 / (PI * d * d);
  65. }
  66. float3 calcDiffuseLambert(float3 color)
  67. {
  68. return color * (1.0f / PI);
  69. }
  70. float getSpotAttenuation(float3 toLight, LightData lightData)
  71. {
  72. float output = saturate((dot(toLight, -lightData.direction) - lightData.spotAngles.y) * lightData.spotAngles.z);
  73. return output * output;
  74. }
  75. // Window function to ensure the light contribution fades out to 0 at attenuation radius
  76. float getRadialAttenuation(float distance2, LightData lightData)
  77. {
  78. float radialAttenuation = distance2 * lightData.attRadiusSqrdInv;
  79. radialAttenuation *= radialAttenuation;
  80. radialAttenuation = saturate(1.0f - radialAttenuation);
  81. radialAttenuation *= radialAttenuation;
  82. return radialAttenuation;
  83. }
  84. // Calculates illuminance from a non-area point light
  85. float illuminancePointLight(float distance2, float NoL, LightData lightData)
  86. {
  87. return (lightData.luminance * NoL) / max(distance2, 0.01f*0.01f);
  88. }
  89. // Calculates illuminance scale for a sphere or a disc area light, while also handling the case when
  90. // parts of the area light are below the horizon.
  91. // Input NoL must be unclamped.
  92. // Sphere solid angle = arcsin(r / d)
  93. // Right disc solid angle = atan(r / d)
  94. // - To compensate for oriented discs, multiply by dot(diskNormal, -L)
  95. float illuminanceScaleSphereDiskAreaLight(float unclampedNoL, float sinSolidAngleSqrd)
  96. {
  97. // Handles parts of the area light below the surface horizon
  98. // See https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf for reference
  99. float sinSolidAngle = sqrt(sinSolidAngleSqrd);
  100. // TODO - Below horizon handling disabled as it currently outputs incorrect values, need to find a better approximation or just use the reference implementation
  101. //if(unclampedNoL < sinSolidAngle)
  102. //{
  103. // // Hermite spline approximation (see reference for exact formula)
  104. // unclampedNoL = max(unclampedNoL, -sinSolidAngle);
  105. // return ((sinSolidAngle + unclampedNoL) * (sinSolidAngle + unclampedNoL)) / (4 * sinSolidAngle);
  106. //}
  107. //else
  108. return PI * sinSolidAngleSqrd * saturate(unclampedNoL);
  109. }
  110. // Calculates illuminance from a sphere area light.
  111. float illuminanceSphereAreaLight(float unclampedNoL, float distToLight2, LightData lightData)
  112. {
  113. float radius2 = lightData.srcRadius * lightData.srcRadius;
  114. // Squared sine of the sphere solid angle
  115. float sinSolidAngle2 = radius2 / distToLight2;
  116. // Prevent divide by zero
  117. sinSolidAngle2 = min(sinSolidAngle2, 0.9999f);
  118. return lightData.luminance * illuminanceScaleSphereDiskAreaLight(unclampedNoL, sinSolidAngle2);
  119. }
  120. // Calculates illuminance from a disc area light.
  121. float illuminanceDiscAreaLight(float unclampedNoL, float distToLight2, float3 L, LightData lightData)
  122. {
  123. // Solid angle for right disk = atan (r / d)
  124. // atan (r / d) = asin((r / d)/sqrt((r / d)^2+1))
  125. // sinAngle = (r / d)/sqrt((r / d)^2 + 1)
  126. // sinAngle^2 = (r / d)^2 / (r / d)^2 + 1
  127. // = r^2 / (d^2 + r^2)
  128. float radius2 = lightData.srcRadius * lightData.srcRadius;
  129. // max() to prevent light penetrating object
  130. float sinSolidAngle2 = saturate(radius2 / (radius2 + max(radius2, distToLight2)));
  131. // Multiply by extra term to somewhat handle the case of the oriented disc (formula above only works
  132. // for right discs).
  133. return lightData.luminance * illuminanceScaleSphereDiskAreaLight(unclampedNoL, sinSolidAngle2 * saturate(dot(lightData.direction, -L)));
  134. }
  135. // With microfacet BRDF the BRDF lobe is not centered around the reflected (mirror) direction.
  136. // Because of NoL and shadow-masking terms the lobe gets shifted toward the normal as roughness
  137. // increases. This is called the "off-specular peak". We approximate it using this function.
  138. float3 getSpecularDominantDir(float3 N, float3 R, float roughness)
  139. {
  140. // Note: Try this formula as well:
  141. // float smoothness = 1 - roughness;
  142. // return lerp(N, R, smoothness * (sqrt(smoothness) + roughness));
  143. float r2 = roughness * roughness;
  144. return normalize(lerp(N, R, (1 - r2) * (sqrt(1 - r2) + r2)));
  145. }
  146. };
  147. };
  148. mixin StandardBRDF
  149. {
  150. code
  151. {
  152. float3 evaluateStandardBRDF(float3 V, float3 L, float specLobeEnergy, SurfaceData surfaceData)
  153. {
  154. float3 N = surfaceData.worldNormal.xyz;
  155. float3 H = normalize(V + L);
  156. float LoH = saturate(dot(L, H));
  157. float NoH = saturate(dot(N, H));
  158. float NoV = saturate(dot(N, V));
  159. float NoL = saturate(dot(N, L));
  160. float3 diffuseColor = lerp(surfaceData.albedo.rgb, float3(0.0f, 0.0f, 0.0f), surfaceData.metalness);
  161. // Note: Using a fixed F0 value of 0.04 (plastic) for dielectrics, and using albedo as specular for conductors.
  162. // For more customizability allow the user to provide separate albedo/specular colors for both types.
  163. float3 specularColor = lerp(float3(0.04f, 0.04f, 0.04f), surfaceData.albedo.rgb, surfaceData.metalness);
  164. float3 diffuse = calcDiffuseLambert(diffuseColor);
  165. float roughness = max(surfaceData.roughness, 0.04f); // Prevent NaNs
  166. float roughness2 = roughness * roughness;
  167. float roughness4 = roughness2 * roughness2;
  168. float3 specular = calcMicrofacetFresnelShlick(specularColor, LoH) *
  169. calcMicrofacetDistGGX(roughness4, NoH) *
  170. calcMicrofacetShadowingSmithGGX(roughness4, NoV, NoL);
  171. // Note: Need to add energy conservation between diffuse and specular terms?
  172. return diffuse + specular * specLobeEnergy;
  173. }
  174. };
  175. };
  176. mixin LuminanceSpot
  177. {
  178. code
  179. {
  180. float3 getLuminanceSpot(LightData lightData, float3 worldPos, float3 V, float3 R, float roughness2, SurfaceData surfaceData)
  181. {
  182. float3 N = surfaceData.worldNormal.xyz;
  183. float3 toLight = lightData.position - worldPos;
  184. float distToLightSqrd = dot(toLight, toLight);
  185. float invDistToLight = rsqrt(distToLightSqrd);
  186. float3 L = toLight * invDistToLight;
  187. float NoL = dot(N, L);
  188. float specEnergy = 1.0f;
  189. float illuminance = 0.0f;
  190. float spotAttenuation = 1.0f;
  191. // Disc area light. Calculate its contribution analytically by
  192. // finding the most important (least error) point on the area light and
  193. // use it as a form of importance sampling.
  194. if(lightData.srcRadius > 0)
  195. {
  196. // Calculate illuminance depending on source size, distance and angle
  197. illuminance = illuminanceDiscAreaLight(NoL, distToLightSqrd, L, lightData);
  198. // Energy conservation: Similar case as with radial lights
  199. float rightDiscAngle = saturate(lightData.srcRadius * invDistToLight);
  200. // Account for disc orientation somewhat
  201. float discAngle = rightDiscAngle * saturate(dot(lightData.direction, -L));
  202. specEnergy = roughness2 / saturate(roughness2 + 0.5f * discAngle);
  203. specEnergy *= specEnergy;
  204. // Find closest point on disc to ray
  205. float3 discNormal = -lightData.direction;
  206. float distAlongLightDir = max(dot(R, discNormal), 1e-6f);
  207. float t = dot(toLight, discNormal) / distAlongLightDir;
  208. float3 closestPointOnPlane = R * t; // Relative to shaded world point
  209. float3 centerToRay = closestPointOnPlane - toLight;
  210. float invDistToRay = rsqrt(dot(centerToRay, centerToRay));
  211. float3 closestPointOnDisc = toLight + centerToRay * saturate(lightData.srcRadius * invDistToRay);
  212. toLight = closestPointOnDisc;
  213. L = normalize(toLight);
  214. // Expand spot attenuation by disc radius (not physically based)
  215. float3 toSpotEdge = normalize(lightData.shiftedLightPosition - worldPos);
  216. spotAttenuation = getSpotAttenuation(toSpotEdge, lightData);
  217. // TODO - Spot attenuation fades out the specular highlight in a noticeable way
  218. }
  219. else
  220. {
  221. NoL = saturate(NoL);
  222. illuminance = illuminancePointLight(distToLightSqrd, NoL, lightData);
  223. spotAttenuation = getSpotAttenuation(L, lightData);
  224. }
  225. float radialAttenuation = getRadialAttenuation(distToLightSqrd, lightData);
  226. float attenuation = spotAttenuation * radialAttenuation;
  227. float3 surfaceShading = evaluateStandardBRDF(V, L, specEnergy, surfaceData);
  228. return lightData.color * illuminance * attenuation * surfaceShading;
  229. }
  230. };
  231. };
  232. mixin LuminanceRadial
  233. {
  234. code
  235. {
  236. float3 getLuminanceRadial(LightData lightData, float3 worldPos, float3 V, float3 R, float roughness2, SurfaceData surfaceData)
  237. {
  238. float3 N = surfaceData.worldNormal.xyz;
  239. float3 toLight = lightData.position - worldPos;
  240. float distToLightSqrd = dot(toLight, toLight);
  241. float invDistToLight = rsqrt(distToLightSqrd);
  242. float3 L = toLight * invDistToLight;
  243. float NoL = dot(N, L);
  244. float specEnergy = 1.0f;
  245. float illuminance = 0.0f;
  246. // Sphere area light. Calculate its contribution analytically by
  247. // finding the most important (least error) point on the area light and
  248. // use it as a form of importance sampling.
  249. if(lightData.srcRadius > 0)
  250. {
  251. // Calculate illuminance depending on source size, distance and angle
  252. illuminance = illuminanceSphereAreaLight(NoL, distToLightSqrd, lightData);
  253. // Energy conservation:
  254. // We are widening the specular distribution by the sphere's subtended angle,
  255. // so we need to handle the increase in energy. It is not enough just to account
  256. // for the sphere solid angle, since the energy difference is highly dependent on
  257. // specular distribution. By accounting for this energy difference we ensure glossy
  258. // reflections have sharp edges, instead of being too blurry.
  259. // See http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf for reference
  260. float sphereAngle = saturate(lightData.srcRadius * invDistToLight);
  261. specEnergy = roughness2 / saturate(roughness2 + 0.5f * sphereAngle);
  262. specEnergy *= specEnergy;
  263. // Find closest point on sphere to ray
  264. float3 closestPointOnRay = dot(toLight, R) * R;
  265. float3 centerToRay = closestPointOnRay - toLight;
  266. float invDistToRay = rsqrt(dot(centerToRay, centerToRay));
  267. float3 closestPointOnSphere = toLight + centerToRay * saturate(lightData.srcRadius * invDistToRay);
  268. toLight = closestPointOnSphere;
  269. L = normalize(toLight);
  270. }
  271. else
  272. {
  273. NoL = saturate(NoL);
  274. illuminance = illuminancePointLight(distToLightSqrd, NoL, lightData);
  275. }
  276. float attenuation = getRadialAttenuation(distToLightSqrd, lightData);
  277. float3 surfaceShading = evaluateStandardBRDF(V, L, specEnergy, surfaceData);
  278. return lightData.color * illuminance * attenuation * surfaceShading;
  279. }
  280. };
  281. };
  282. mixin LuminanceDirectional
  283. {
  284. code
  285. {
  286. float3 getLuminanceDirectional(LightData lightData, float3 worldPos, float3 V, float3 R, SurfaceData surfaceData)
  287. {
  288. float3 N = surfaceData.worldNormal.xyz;
  289. float3 L = -lightData.direction;
  290. float NoL = saturate(dot(N, L));
  291. float specEnergy = 1.0f;
  292. // Distant disk area light. Calculate its contribution analytically by
  293. // finding the most important (least error) point on the area light and
  294. // use it as a form of importance sampling.
  295. if(lightData.srcRadius > 0)
  296. {
  297. float diskRadius = sin(lightData.srcRadius);
  298. float distanceToDisk = cos(lightData.srcRadius);
  299. // Closest point to disk (approximation for distant disks)
  300. float DoR = dot(L, R);
  301. float3 S = normalize(R - DoR * L);
  302. L = DoR < distanceToDisk ? normalize(distanceToDisk * L + S * diskRadius) : R;
  303. }
  304. float3 surfaceShading = evaluateStandardBRDF(V, L, specEnergy, surfaceData);
  305. float illuminance = lightData.luminance * NoL;
  306. return lightData.color * illuminance * surfaceShading;
  307. }
  308. };
  309. };
  310. mixin DirectLighting
  311. {
  312. mixin DirectLightingCommon;
  313. mixin StandardBRDF;
  314. mixin LuminanceDirectional;
  315. mixin LuminanceRadial;
  316. mixin LuminanceSpot;
  317. };
  318. // Hackish way of "instantiating" two versions of a mixin (to be removed when template/specialization support is added)
  319. #include "$ENGINE$\DirectLightAccumulator.bslinc"
  320. #define USE_UNIFORM_BUFFER
  321. #include "$ENGINE$\DirectLightAccumulator.bslinc"
  322. #undef USE_UNIFORM_BUFFER