LightingCommon.bslinc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. #include "$ENGINE$\SurfaceData.bslinc"
  2. Technique
  3. : base("LightingCommon")
  4. : inherits("SurfaceData") =
  5. {
  6. Language = "HLSL11";
  7. Pass =
  8. {
  9. Common =
  10. {
  11. // Arbitrary limit, increase if needed
  12. #define MAX_LIGHTS 512
  13. #define PI 3.1415926
  14. #define HALF_PI 1.5707963
  15. struct LightData
  16. {
  17. float3 position;
  18. float attRadius;
  19. float srcRadius;
  20. float3 direction;
  21. float luminance;
  22. float3 spotAngles;
  23. float attRadiusSqrdInv;
  24. float3 color;
  25. float3 shiftedLightPosition;
  26. };
  27. float3 calcMicrofacetFresnelShlick(float3 F0, float LoH)
  28. {
  29. return F0 + (1.0f - F0) * pow(1.0f - LoH, 5.0f);
  30. }
  31. float calcMicrofacetShadowingSmithGGX(float roughness4, float NoV, float NoL)
  32. {
  33. // Note: It's probably better to use the joint shadowing + masking version of this function
  34. // Note: Original GGX G1 multiplied by NoV & NoL (respectively), so that the microfacet function divisor gets canceled out
  35. // Original formula being (ignoring the factor for masking negative directions):
  36. // G1(v) = 2 / (1 + sqrt(1 + roughness^4 * tan^2(v)))
  37. //
  38. // Using trig identities: tan = sin/cos & sin^2 + cos^2 = 1
  39. // G1(v) = 2 / (1 + sqrt(1 + roughness^4 * (1 - cos^2(v))/cos^2(v)))
  40. //
  41. // Multiply by cos(v) so that we cancel out the (NoL * NoV) factor in the microfacet formula divisor
  42. // G1(v) = 2 * cos(v) / (cos^2(v) + sqrt(cos^2 + roughness^4 - roughness^4 * cos^2(v)))
  43. //
  44. // Actually do the cancellation:
  45. // G1(v) = 2 / (cos^2(v) + sqrt(cos^2 + roughness^4 - roughness^4 * cos^2(v)))
  46. //
  47. // Also cancel out the 2 and the 4:
  48. // G1(v) = 1 / (cos^2(v) + sqrt(cos^2 + roughness^4 - roughness^4 * cos^2(v)))
  49. //
  50. // Final equation being:
  51. // G(v, l) = G1(v) * G1(l)
  52. //
  53. // Where cos(v) is NoV or NoL
  54. float g1V = NoV + sqrt(NoV * (NoV - NoV * roughness4) + roughness4);
  55. float g1L = NoL + sqrt(NoL * (NoL - NoL * roughness4) + roughness4);
  56. return rcp(g1V * g1L);
  57. }
  58. float calcMicrofacetDistGGX(float roughness4, float NoH)
  59. {
  60. float d = (NoH * roughness4 - NoH) * NoH + 1.0f;
  61. return roughness4 / (PI * d * d);
  62. }
  63. float3 calcDiffuseLambert(float3 color)
  64. {
  65. return color * (1.0f / PI);
  66. }
  67. float getSpotAttenuation(float3 toLight, LightData lightData)
  68. {
  69. float output = saturate((dot(toLight, -lightData.direction) - lightData.spotAngles.y) * lightData.spotAngles.z);
  70. return output * output;
  71. }
  72. // Window function to ensure the light contribution fades out to 0 at attenuation radius
  73. float getRadialAttenuation(float distance2, LightData lightData)
  74. {
  75. float radialAttenuation = distance2 * lightData.attRadiusSqrdInv;
  76. radialAttenuation *= radialAttenuation;
  77. radialAttenuation = saturate(1.0f - radialAttenuation);
  78. radialAttenuation *= radialAttenuation;
  79. return radialAttenuation;
  80. }
  81. // Calculates illuminance from a non-area point light
  82. float illuminancePointLight(float distance2, float NoL, LightData lightData)
  83. {
  84. return (lightData.luminance * NoL) / max(distance2, 0.01f*0.01f);
  85. }
  86. // Calculates illuminance scale for a sphere or a disc area light, while also handling the case when
  87. // parts of the area light are below the horizon.
  88. // Input NoL must be unclamped.
  89. // Sphere solid angle = arcsin(r / d)
  90. // Right disc solid angle = atan(r / d)
  91. // - To compensate for oriented discs, multiply by dot(diskNormal, -L)
  92. float illuminanceScaleSphereDiskAreaLight(float unclampedNoL, float sinSolidAngleSqrd)
  93. {
  94. // Handles parts of the area light below the surface horizon
  95. // See https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf for reference
  96. float sinSolidAngle = sqrt(sinSolidAngleSqrd);
  97. if(unclampedNoL < sinSolidAngle)
  98. {
  99. // Hermite spline approximation (see reference for exact formula)
  100. unclampedNoL = max(unclampedNoL, -sinSolidAngle);
  101. return ((sinSolidAngle + unclampedNoL) * (sinSolidAngle + unclampedNoL)) / (4 * sinSolidAngle);
  102. }
  103. else
  104. return PI * sinSolidAngleSqrd * saturate(unclampedNoL);
  105. }
  106. // Calculates illuminance from a sphere area light.
  107. float illuminanceSphereAreaLight(float unclampedNoL, float distToLight2, LightData lightData)
  108. {
  109. float radius2 = lightData.srcRadius * lightData.srcRadius;
  110. // Squared sine of the sphere solid angle
  111. float sinSolidAngle2 = radius2 / distToLight2;
  112. // Prevent divide by zero
  113. sinSolidAngle2 = min(sinSolidAngle2, 0.9999f);
  114. return lightData.luminance * illuminanceScaleSphereDiskAreaLight(unclampedNoL, sinSolidAngle2);
  115. }
  116. // Calculates illuminance from a disc area light.
  117. float illuminanceDiscAreaLight(float unclampedNoL, float distToLight2, float3 L, LightData lightData)
  118. {
  119. // Solid angle for right disk = atan (r / d)
  120. // atan (r / d) = asin((r / d)/sqrt((r / d)^2+1))
  121. // sinAngle = (r / d)/sqrt((r / d)^2 + 1)
  122. // sinAngle^2 = (r / d)^2 / (r / d)^2 + 1
  123. // = r^2 / (d^2 + r^2)
  124. float radius2 = lightData.srcRadius * lightData.srcRadius;
  125. // max() to prevent light penetrating object
  126. float sinSolidAngle2 = saturate(radius2 / (radius2 + max(radius2, distToLight2)));
  127. // Multiply by extra term to somewhat handle the case of the oriented disc (formula above only works
  128. // for right discs).
  129. return lightData.luminance * illuminanceScaleSphereDiskAreaLight(unclampedNoL, sinSolidAngle2 * saturate(dot(lightData.direction, -L)));
  130. }
  131. // With microfacet BRDF the BRDF lobe is not centered around the reflected (mirror) direction.
  132. // Because of NoL and shadow-masking terms the lobe gets shifted toward the normal as roughness
  133. // increases. This is called the "off-specular peak". We approximate it using this function.
  134. float3 getSpecularDominantDir(float3 N, float3 R, float roughness)
  135. {
  136. // Note: Try this formula as well:
  137. // float smoothness = 1 - roughness;
  138. // return lerp(N, R, smoothness * (sqrt(smoothness) + roughness));
  139. float r2 = roughness * roughness;
  140. return normalize(lerp(N, R, (1 - r2) * (sqrt(1 - r2) + r2)));
  141. }
  142. float3 getSurfaceShading(float3 V, float3 L, float specLobeEnergy, SurfaceData surfaceData)
  143. {
  144. float3 N = surfaceData.worldNormal.xyz;
  145. float3 H = normalize(V + L);
  146. float LoH = saturate(dot(L, H));
  147. float NoH = saturate(dot(N, H));
  148. float NoV = saturate(dot(N, V));
  149. float NoL = saturate(dot(N, L));
  150. float3 diffuseColor = lerp(surfaceData.albedo.rgb, float3(0.0f, 0.0f, 0.0f), 1.0f - surfaceData.metalness);
  151. // Note: Using a fixed F0 value of 0.04 (plastic) for dielectrics, and using albedo as specular for conductors.
  152. // For more customizability allow the user to provide separate albedo/specular colors for both types.
  153. float3 specularColor = lerp(float3(0.04f, 0.04f, 0.04f), surfaceData.albedo.rgb, surfaceData.metalness);
  154. float3 diffuse = calcDiffuseLambert(diffuseColor);
  155. float roughness = max(surfaceData.roughness, 0.04f); // Prevent NaNs
  156. float roughness2 = roughness * roughness;
  157. float roughness4 = roughness2 * roughness2;
  158. float3 specular = calcMicrofacetFresnelShlick(specularColor, LoH) *
  159. calcMicrofacetDistGGX(roughness4, NoH) *
  160. calcMicrofacetShadowingSmithGGX(roughness4, NoV, NoL);
  161. // Note: Need to add energy conservation between diffuse and specular terms?
  162. return diffuse + specular * specLobeEnergy;
  163. }
  164. StructuredBuffer<LightData> gLights;
  165. #ifdef USE_COMPUTE_INDICES
  166. groupshared uint gLightIndices[MAX_LIGHTS];
  167. #endif
  168. #ifdef USE_LIGHT_GRID_INDICES
  169. Buffer<uint> gLightIndices;
  170. #endif
  171. float4 getDirectLighting(float3 worldPos, float3 V, float3 R, SurfaceData surfaceData, uint4 lightOffsets)
  172. {
  173. float3 N = surfaceData.worldNormal.xyz;
  174. float roughness2 = max(surfaceData.roughness, 0.08f);
  175. roughness2 *= roughness2;
  176. float3 outLuminance = 0;
  177. float alpha = 0.0f;
  178. if(surfaceData.worldNormal.w > 0.0f)
  179. {
  180. // Handle directional lights
  181. for(uint i = 0; i < lightOffsets.x; ++i)
  182. {
  183. LightData lightData = gLights[i];
  184. float3 L = -lightData.direction;
  185. float NoL = saturate(dot(N, L));
  186. float specEnergy = 1.0f;
  187. // Distant disk area light. Calculate its contribution analytically by
  188. // finding the most important (least error) point on the area light and
  189. // use it as a form of importance sampling.
  190. if(lightData.srcRadius > 0)
  191. {
  192. float diskRadius = sin(lightData.srcRadius);
  193. float distanceToDisk = cos(lightData.srcRadius);
  194. // Closest point to disk (approximation for distant disks)
  195. float DoR = dot(L, R);
  196. float3 S = normalize(R - DoR * L);
  197. L = DoR < distanceToDisk ? normalize(distanceToDisk * L + S * diskRadius) : R;
  198. }
  199. float3 surfaceShading = getSurfaceShading(V, L, specEnergy, surfaceData);
  200. float illuminance = lightData.luminance * NoL;
  201. outLuminance += lightData.color * illuminance * surfaceShading;
  202. }
  203. // Handle radial lights
  204. for (uint i = lightOffsets.y; i < lightOffsets.z; ++i)
  205. {
  206. uint lightIdx = gLightIndices[i];
  207. LightData lightData = gLights[lightIdx];
  208. float3 toLight = lightData.position - worldPos;
  209. float distToLightSqrd = dot(toLight, toLight);
  210. float invDistToLight = rsqrt(distToLightSqrd);
  211. float3 L = toLight * invDistToLight;
  212. float NoL = dot(N, L);
  213. float specEnergy = 1.0f;
  214. float illuminance = 0.0f;
  215. // Sphere area light. Calculate its contribution analytically by
  216. // finding the most important (least error) point on the area light and
  217. // use it as a form of importance sampling.
  218. if(lightData.srcRadius > 0)
  219. {
  220. // Calculate illuminance depending on source size, distance and angle
  221. illuminance = illuminanceSphereAreaLight(NoL, distToLightSqrd, lightData);
  222. // Energy conservation:
  223. // We are widening the specular distribution by the sphere's subtended angle,
  224. // so we need to handle the increase in energy. It is not enough just to account
  225. // for the sphere solid angle, since the energy difference is highly dependent on
  226. // specular distribution. By accounting for this energy difference we ensure glossy
  227. // reflections have sharp edges, instead of being too blurry.
  228. // See http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf for reference
  229. float sphereAngle = saturate(lightData.srcRadius * invDistToLight);
  230. specEnergy = roughness2 / saturate(roughness2 + 0.5f * sphereAngle);
  231. specEnergy *= specEnergy;
  232. // Find closest point on sphere to ray
  233. float3 closestPointOnRay = dot(toLight, R) * R;
  234. float3 centerToRay = closestPointOnRay - toLight;
  235. float invDistToRay = rsqrt(dot(centerToRay, centerToRay));
  236. float3 closestPointOnSphere = toLight + centerToRay * saturate(lightData.srcRadius * invDistToRay);
  237. toLight = closestPointOnSphere;
  238. L = normalize(toLight);
  239. }
  240. else
  241. {
  242. NoL = saturate(NoL);
  243. illuminance = illuminancePointLight(distToLightSqrd, NoL, lightData);
  244. }
  245. float attenuation = getRadialAttenuation(distToLightSqrd, lightData);
  246. float3 surfaceShading = getSurfaceShading(V, L, specEnergy, surfaceData);
  247. outLuminance += lightData.color * illuminance * attenuation * surfaceShading;
  248. }
  249. // Handle spot lights
  250. for(uint i = lightOffsets.z; i < lightOffsets.w; ++i)
  251. {
  252. uint lightIdx = gLightIndices[i];
  253. LightData lightData = gLights[lightIdx];
  254. float3 toLight = lightData.position - worldPos;
  255. float distToLightSqrd = dot(toLight, toLight);
  256. float invDistToLight = rsqrt(distToLightSqrd);
  257. float3 L = toLight * invDistToLight;
  258. float NoL = dot(N, L);
  259. float specEnergy = 1.0f;
  260. float illuminance = 0.0f;
  261. float spotAttenuation = 1.0f;
  262. // Disc area light. Calculate its contribution analytically by
  263. // finding the most important (least error) point on the area light and
  264. // use it as a form of importance sampling.
  265. if(lightData.srcRadius > 0)
  266. {
  267. // Calculate illuminance depending on source size, distance and angle
  268. illuminance = illuminanceDiscAreaLight(NoL, distToLightSqrd, L, lightData);
  269. // Energy conservation: Similar case as with radial lights
  270. float rightDiscAngle = saturate(lightData.srcRadius * invDistToLight);
  271. // Account for disc orientation somewhat
  272. float discAngle = rightDiscAngle * saturate(dot(lightData.direction, -L));
  273. specEnergy = roughness2 / saturate(roughness2 + 0.5f * discAngle);
  274. specEnergy *= specEnergy;
  275. // Find closest point on disc to ray
  276. float3 discNormal = -lightData.direction;
  277. float distAlongLightDir = max(dot(R, discNormal), 1e-6f);
  278. float t = dot(toLight, discNormal) / distAlongLightDir;
  279. float3 closestPointOnPlane = R * t; // Relative to shaded world point
  280. float3 centerToRay = closestPointOnPlane - toLight;
  281. float invDistToRay = rsqrt(dot(centerToRay, centerToRay));
  282. float3 closestPointOnDisc = toLight + centerToRay * saturate(lightData.srcRadius * invDistToRay);
  283. toLight = closestPointOnDisc;
  284. L = normalize(toLight);
  285. // Expand spot attenuation by disc radius (not physically based)
  286. float3 toSpotEdge = normalize(lightData.shiftedLightPosition - worldPos);
  287. spotAttenuation = getSpotAttenuation(toSpotEdge, lightData);
  288. // TODO - Spot attenuation fades out the specular highlight in a noticeable way
  289. }
  290. else
  291. {
  292. NoL = saturate(NoL);
  293. illuminance = illuminancePointLight(distToLightSqrd, NoL, lightData);
  294. spotAttenuation = getSpotAttenuation(L, lightData);
  295. }
  296. float radialAttenuation = getRadialAttenuation(distToLightSqrd, lightData);
  297. float attenuation = spotAttenuation * radialAttenuation;
  298. float3 surfaceShading = getSurfaceShading(V, L, specEnergy, surfaceData);
  299. outLuminance += lightData.color * illuminance * attenuation * surfaceShading;
  300. }
  301. // Ambient term for in-editor visualization, not used in actual lighting
  302. outLuminance += surfaceData.albedo.rgb * gAmbientFactor / PI;
  303. alpha = 1.0f;
  304. }
  305. return float4(outLuminance, alpha);
  306. }
  307. };
  308. };
  309. };
  310. Technique
  311. : base("LightingCommon")
  312. : inherits("SurfaceData") =
  313. {
  314. Language = "GLSL";
  315. Pass =
  316. {
  317. Common =
  318. {
  319. #define PI 3.1415926
  320. #define HALF_PI 1.5707963
  321. struct LightData
  322. {
  323. vec3 position;
  324. float attRadius;
  325. float srcRadius;
  326. vec3 direction;
  327. float luminance;
  328. vec3 spotAngles;
  329. float attRadiusSqrdInv;
  330. vec3 color;
  331. vec3 spotDiscPosition;
  332. };
  333. vec3 calcMicrofacetFresnelShlick(vec3 F0, float LoH)
  334. {
  335. return F0 + (1.0f - F0) * pow(1.0f - LoH, 5.0f);
  336. }
  337. float calcMicrofacetShadowingSmithGGX(float roughness, float NoV, float NoL)
  338. {
  339. // Note: It's probably better to use the joint shadowing + masking version of this function
  340. // Note: Pull these multiplies out, since they're used by the distribution function as well?
  341. float roughness2 = roughness * roughness;
  342. float roughness4 = roughness2 * roughness2;
  343. // Note: Original GGX G1 multiplied by NoV & NoL (respectively), so that the microfacet function divisor gets canceled out
  344. // See HLSL code for derivation
  345. float g1V = NoV + sqrt(NoV * (NoV - NoV * roughness4) + roughness4);
  346. float g1L = NoL + sqrt(NoL * (NoL - NoL * roughness4) + roughness4);
  347. return 1.0f / (g1V * g1L);
  348. }
  349. float calcMicrofacetDistGGX(float roughness, float NoH)
  350. {
  351. float roughness2 = roughness * roughness;
  352. float roughness4 = roughness2 * roughness2;
  353. float d = (NoH * roughness4 - NoH) * NoH + 1.0f;
  354. return roughness4 / (PI * d * d);
  355. }
  356. float calcDiffuseLambert(float color)
  357. {
  358. return color * (1.0f / PI);
  359. }
  360. float getSpotAttenuation(vec3 worldPosToLight, vec3 direction, vec3 angles)
  361. {
  362. float atten = clamp((dot(-worldPosToLight, direction) - angles.y) * angles.z, 0.0, 1.0);
  363. return atten * atten;
  364. }
  365. vec3 getDirLightContibution(SurfaceData surfaceData, LightData lightData)
  366. {
  367. vec3 N = surfaceData.worldNormal.xyz;
  368. vec3 L = -lightData.direction;
  369. float NoL = clamp(dot(N, L), 0.0, 1.0); // TODO - Add bias here?
  370. return lightData.color * lightData.luminance * NoL;
  371. }
  372. vec3 getPointLightContribution(vec3 L, vec3 worldPosition, SurfaceData surfaceData, LightData lightData)
  373. {
  374. vec3 N = surfaceData.worldNormal.xyz;
  375. float distanceSqrd = dot(L, L);
  376. float distanceAttenuation = 1/(distanceSqrd + 1);
  377. L = normalize(L);
  378. float NoL = clamp(dot(N, L), 0.0, 1.0); // TODO - Add bias here?
  379. float radiusAttenuation = distanceSqrd * lightData.attRadiusSqrdInv;
  380. radiusAttenuation *= radiusAttenuation;
  381. radiusAttenuation = clamp(1.0f - radiusAttenuation, 0.0, 1.0);
  382. radiusAttenuation *= radiusAttenuation;
  383. float attenuation = distanceAttenuation * radiusAttenuation;
  384. return lightData.color * lightData.luminance * ((NoL * attenuation));
  385. }
  386. vec3 getPointLightContribution(vec3 worldPosition, SurfaceData surfaceData, LightData lightData)
  387. {
  388. vec3 L = lightData.position - worldPosition;
  389. return getPointLightContribution(L, worldPosition, surfaceData, lightData);
  390. }
  391. vec3 getSpotLightContribution(vec3 worldPosition, SurfaceData surfaceData, LightData lightData)
  392. {
  393. vec3 L = lightData.position - worldPosition;
  394. vec3 pointLightContribution = getPointLightContribution(L, worldPosition, surfaceData, lightData);
  395. float spotFalloff = getSpotAttenuation(L, lightData.direction, lightData.spotAngles);
  396. return pointLightContribution * spotFalloff;
  397. }
  398. };
  399. };
  400. };