|
|
@@ -1,379 +1,376 @@
|
|
|
#include "$ENGINE$\SurfaceData.bslinc"
|
|
|
|
|
|
-Technique
|
|
|
- : base("LightingCommon")
|
|
|
- : inherits("SurfaceData") =
|
|
|
+mixin LightingCommon
|
|
|
{
|
|
|
- Pass =
|
|
|
+ mixin SurfaceData;
|
|
|
+
|
|
|
+ code
|
|
|
{
|
|
|
- Common =
|
|
|
+ // Arbitrary limit, increase if needed
|
|
|
+ #define MAX_LIGHTS 512
|
|
|
+
|
|
|
+ #define PI 3.1415926
|
|
|
+ #define HALF_PI 1.5707963
|
|
|
+
|
|
|
+ // Note: Size must be multiple of largest element, because of std430 rules
|
|
|
+ struct LightData
|
|
|
{
|
|
|
- // Arbitrary limit, increase if needed
|
|
|
- #define MAX_LIGHTS 512
|
|
|
+ float3 position;
|
|
|
+ float attRadius;
|
|
|
+ float3 direction;
|
|
|
+ float luminance;
|
|
|
+ float3 spotAngles;
|
|
|
+ float attRadiusSqrdInv;
|
|
|
+ float3 color;
|
|
|
+ float srcRadius;
|
|
|
+ float3 shiftedLightPosition;
|
|
|
+ float padding;
|
|
|
+ };
|
|
|
|
|
|
- #define PI 3.1415926
|
|
|
- #define HALF_PI 1.5707963
|
|
|
-
|
|
|
- // Note: Size must be multiple of largest element, because of std430 rules
|
|
|
- struct LightData
|
|
|
- {
|
|
|
- float3 position;
|
|
|
- float attRadius;
|
|
|
- float3 direction;
|
|
|
- float luminance;
|
|
|
- float3 spotAngles;
|
|
|
- float attRadiusSqrdInv;
|
|
|
- float3 color;
|
|
|
- float srcRadius;
|
|
|
- float3 shiftedLightPosition;
|
|
|
- float padding;
|
|
|
- };
|
|
|
-
|
|
|
- float3 calcMicrofacetFresnelShlick(float3 F0, float LoH)
|
|
|
- {
|
|
|
- return F0 + (1.0f - F0) * pow(1.0f - LoH, 5.0f);
|
|
|
- }
|
|
|
+ float3 calcMicrofacetFresnelShlick(float3 F0, float LoH)
|
|
|
+ {
|
|
|
+ return F0 + (1.0f - F0) * pow(1.0f - LoH, 5.0f);
|
|
|
+ }
|
|
|
|
|
|
- float calcMicrofacetShadowingSmithGGX(float roughness4, float NoV, float NoL)
|
|
|
- {
|
|
|
- // Note: It's probably better to use the joint shadowing + masking version of this function
|
|
|
+ float calcMicrofacetShadowingSmithGGX(float roughness4, float NoV, float NoL)
|
|
|
+ {
|
|
|
+ // Note: It's probably better to use the joint shadowing + masking version of this function
|
|
|
|
|
|
- // Note: Original GGX G1 multiplied by NoV & NoL (respectively), so that the microfacet function divisor gets canceled out
|
|
|
- // Original formula being (ignoring the factor for masking negative directions):
|
|
|
- // G1(v) = 2 / (1 + sqrt(1 + roughness^4 * tan^2(v)))
|
|
|
- //
|
|
|
- // Using trig identities: tan = sin/cos & sin^2 + cos^2 = 1
|
|
|
- // G1(v) = 2 / (1 + sqrt(1 + roughness^4 * (1 - cos^2(v))/cos^2(v)))
|
|
|
- //
|
|
|
- // Multiply by cos(v) so that we cancel out the (NoL * NoV) factor in the microfacet formula divisor
|
|
|
- // G1(v) = 2 * cos(v) / (cos^2(v) + sqrt(cos^2 + roughness^4 - roughness^4 * cos^2(v)))
|
|
|
- //
|
|
|
- // Actually do the cancellation:
|
|
|
- // G1(v) = 2 / (cos^2(v) + sqrt(cos^2 + roughness^4 - roughness^4 * cos^2(v)))
|
|
|
- //
|
|
|
- // Also cancel out the 2 and the 4:
|
|
|
- // G1(v) = 1 / (cos^2(v) + sqrt(cos^2 + roughness^4 - roughness^4 * cos^2(v)))
|
|
|
- //
|
|
|
- // Final equation being:
|
|
|
- // G(v, l) = G1(v) * G1(l)
|
|
|
- //
|
|
|
- // Where cos(v) is NoV or NoL
|
|
|
-
|
|
|
- float g1V = NoV + sqrt(NoV * (NoV - NoV * roughness4) + roughness4);
|
|
|
- float g1L = NoL + sqrt(NoL * (NoL - NoL * roughness4) + roughness4);
|
|
|
- return rcp(g1V * g1L);
|
|
|
- }
|
|
|
-
|
|
|
- float calcMicrofacetDistGGX(float roughness4, float NoH)
|
|
|
- {
|
|
|
- float d = (NoH * roughness4 - NoH) * NoH + 1.0f;
|
|
|
- return roughness4 / (PI * d * d);
|
|
|
- }
|
|
|
+ // Note: Original GGX G1 multiplied by NoV & NoL (respectively), so that the microfacet function divisor gets canceled out
|
|
|
+ // Original formula being (ignoring the factor for masking negative directions):
|
|
|
+ // G1(v) = 2 / (1 + sqrt(1 + roughness^4 * tan^2(v)))
|
|
|
+ //
|
|
|
+ // Using trig identities: tan = sin/cos & sin^2 + cos^2 = 1
|
|
|
+ // G1(v) = 2 / (1 + sqrt(1 + roughness^4 * (1 - cos^2(v))/cos^2(v)))
|
|
|
+ //
|
|
|
+ // Multiply by cos(v) so that we cancel out the (NoL * NoV) factor in the microfacet formula divisor
|
|
|
+ // G1(v) = 2 * cos(v) / (cos^2(v) + sqrt(cos^2 + roughness^4 - roughness^4 * cos^2(v)))
|
|
|
+ //
|
|
|
+ // Actually do the cancellation:
|
|
|
+ // G1(v) = 2 / (cos^2(v) + sqrt(cos^2 + roughness^4 - roughness^4 * cos^2(v)))
|
|
|
+ //
|
|
|
+ // Also cancel out the 2 and the 4:
|
|
|
+ // G1(v) = 1 / (cos^2(v) + sqrt(cos^2 + roughness^4 - roughness^4 * cos^2(v)))
|
|
|
+ //
|
|
|
+ // Final equation being:
|
|
|
+ // G(v, l) = G1(v) * G1(l)
|
|
|
+ //
|
|
|
+ // Where cos(v) is NoV or NoL
|
|
|
|
|
|
- float3 calcDiffuseLambert(float3 color)
|
|
|
- {
|
|
|
- return color * (1.0f / PI);
|
|
|
- }
|
|
|
-
|
|
|
- float getSpotAttenuation(float3 toLight, LightData lightData)
|
|
|
- {
|
|
|
- float output = saturate((dot(toLight, -lightData.direction) - lightData.spotAngles.y) * lightData.spotAngles.z);
|
|
|
- return output * output;
|
|
|
- }
|
|
|
+ float g1V = NoV + sqrt(NoV * (NoV - NoV * roughness4) + roughness4);
|
|
|
+ float g1L = NoL + sqrt(NoL * (NoL - NoL * roughness4) + roughness4);
|
|
|
+ return rcp(g1V * g1L);
|
|
|
+ }
|
|
|
+
|
|
|
+ float calcMicrofacetDistGGX(float roughness4, float NoH)
|
|
|
+ {
|
|
|
+ float d = (NoH * roughness4 - NoH) * NoH + 1.0f;
|
|
|
+ return roughness4 / (PI * d * d);
|
|
|
+ }
|
|
|
+
|
|
|
+ float3 calcDiffuseLambert(float3 color)
|
|
|
+ {
|
|
|
+ return color * (1.0f / PI);
|
|
|
+ }
|
|
|
+
|
|
|
+ float getSpotAttenuation(float3 toLight, LightData lightData)
|
|
|
+ {
|
|
|
+ float output = saturate((dot(toLight, -lightData.direction) - lightData.spotAngles.y) * lightData.spotAngles.z);
|
|
|
+ return output * output;
|
|
|
+ }
|
|
|
|
|
|
- // Window function to ensure the light contribution fades out to 0 at attenuation radius
|
|
|
- float getRadialAttenuation(float distance2, LightData lightData)
|
|
|
- {
|
|
|
- float radialAttenuation = distance2 * lightData.attRadiusSqrdInv;
|
|
|
- radialAttenuation *= radialAttenuation;
|
|
|
- radialAttenuation = saturate(1.0f - radialAttenuation);
|
|
|
- radialAttenuation *= radialAttenuation;
|
|
|
-
|
|
|
- return radialAttenuation;
|
|
|
- }
|
|
|
-
|
|
|
- // Calculates illuminance from a non-area point light
|
|
|
- float illuminancePointLight(float distance2, float NoL, LightData lightData)
|
|
|
- {
|
|
|
- return (lightData.luminance * NoL) / max(distance2, 0.01f*0.01f);
|
|
|
- }
|
|
|
+ // Window function to ensure the light contribution fades out to 0 at attenuation radius
|
|
|
+ float getRadialAttenuation(float distance2, LightData lightData)
|
|
|
+ {
|
|
|
+ float radialAttenuation = distance2 * lightData.attRadiusSqrdInv;
|
|
|
+ radialAttenuation *= radialAttenuation;
|
|
|
+ radialAttenuation = saturate(1.0f - radialAttenuation);
|
|
|
+ radialAttenuation *= radialAttenuation;
|
|
|
|
|
|
- // Calculates illuminance scale for a sphere or a disc area light, while also handling the case when
|
|
|
- // parts of the area light are below the horizon.
|
|
|
- // Input NoL must be unclamped.
|
|
|
- // Sphere solid angle = arcsin(r / d)
|
|
|
- // Right disc solid angle = atan(r / d)
|
|
|
- // - To compensate for oriented discs, multiply by dot(diskNormal, -L)
|
|
|
- float illuminanceScaleSphereDiskAreaLight(float unclampedNoL, float sinSolidAngleSqrd)
|
|
|
+ return radialAttenuation;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Calculates illuminance from a non-area point light
|
|
|
+ float illuminancePointLight(float distance2, float NoL, LightData lightData)
|
|
|
+ {
|
|
|
+ return (lightData.luminance * NoL) / max(distance2, 0.01f*0.01f);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Calculates illuminance scale for a sphere or a disc area light, while also handling the case when
|
|
|
+ // parts of the area light are below the horizon.
|
|
|
+ // Input NoL must be unclamped.
|
|
|
+ // Sphere solid angle = arcsin(r / d)
|
|
|
+ // Right disc solid angle = atan(r / d)
|
|
|
+ // - To compensate for oriented discs, multiply by dot(diskNormal, -L)
|
|
|
+ float illuminanceScaleSphereDiskAreaLight(float unclampedNoL, float sinSolidAngleSqrd)
|
|
|
+ {
|
|
|
+ // Handles parts of the area light below the surface horizon
|
|
|
+ // See https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf for reference
|
|
|
+ float sinSolidAngle = sqrt(sinSolidAngleSqrd);
|
|
|
+ if(unclampedNoL < sinSolidAngle)
|
|
|
{
|
|
|
- // Handles parts of the area light below the surface horizon
|
|
|
- // See https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf for reference
|
|
|
- float sinSolidAngle = sqrt(sinSolidAngleSqrd);
|
|
|
- if(unclampedNoL < sinSolidAngle)
|
|
|
- {
|
|
|
- // Hermite spline approximation (see reference for exact formula)
|
|
|
- unclampedNoL = max(unclampedNoL, -sinSolidAngle);
|
|
|
- return ((sinSolidAngle + unclampedNoL) * (sinSolidAngle + unclampedNoL)) / (4 * sinSolidAngle);
|
|
|
- }
|
|
|
- else
|
|
|
- return PI * sinSolidAngleSqrd * saturate(unclampedNoL);
|
|
|
+ // Hermite spline approximation (see reference for exact formula)
|
|
|
+ unclampedNoL = max(unclampedNoL, -sinSolidAngle);
|
|
|
+ return ((sinSolidAngle + unclampedNoL) * (sinSolidAngle + unclampedNoL)) / (4 * sinSolidAngle);
|
|
|
}
|
|
|
+ else
|
|
|
+ return PI * sinSolidAngleSqrd * saturate(unclampedNoL);
|
|
|
+ }
|
|
|
|
|
|
- // Calculates illuminance from a sphere area light.
|
|
|
- float illuminanceSphereAreaLight(float unclampedNoL, float distToLight2, LightData lightData)
|
|
|
- {
|
|
|
- float radius2 = lightData.srcRadius * lightData.srcRadius;
|
|
|
-
|
|
|
- // Squared sine of the sphere solid angle
|
|
|
- float sinSolidAngle2 = radius2 / distToLight2;
|
|
|
-
|
|
|
- // Prevent divide by zero
|
|
|
- sinSolidAngle2 = min(sinSolidAngle2, 0.9999f);
|
|
|
-
|
|
|
- return lightData.luminance * illuminanceScaleSphereDiskAreaLight(unclampedNoL, sinSolidAngle2);
|
|
|
- }
|
|
|
+ // Calculates illuminance from a sphere area light.
|
|
|
+ float illuminanceSphereAreaLight(float unclampedNoL, float distToLight2, LightData lightData)
|
|
|
+ {
|
|
|
+ float radius2 = lightData.srcRadius * lightData.srcRadius;
|
|
|
|
|
|
- // Calculates illuminance from a disc area light.
|
|
|
- float illuminanceDiscAreaLight(float unclampedNoL, float distToLight2, float3 L, LightData lightData)
|
|
|
- {
|
|
|
- // Solid angle for right disk = atan (r / d)
|
|
|
- // atan (r / d) = asin((r / d)/sqrt((r / d)^2+1))
|
|
|
- // sinAngle = (r / d)/sqrt((r / d)^2 + 1)
|
|
|
- // sinAngle^2 = (r / d)^2 / (r / d)^2 + 1
|
|
|
- // = r^2 / (d^2 + r^2)
|
|
|
+ // Squared sine of the sphere solid angle
|
|
|
+ float sinSolidAngle2 = radius2 / distToLight2;
|
|
|
+
|
|
|
+ // Prevent divide by zero
|
|
|
+ sinSolidAngle2 = min(sinSolidAngle2, 0.9999f);
|
|
|
|
|
|
- float radius2 = lightData.srcRadius * lightData.srcRadius;
|
|
|
-
|
|
|
- // max() to prevent light penetrating object
|
|
|
- float sinSolidAngle2 = saturate(radius2 / (radius2 + max(radius2, distToLight2)));
|
|
|
-
|
|
|
- // Multiply by extra term to somewhat handle the case of the oriented disc (formula above only works
|
|
|
- // for right discs).
|
|
|
- return lightData.luminance * illuminanceScaleSphereDiskAreaLight(unclampedNoL, sinSolidAngle2 * saturate(dot(lightData.direction, -L)));
|
|
|
- }
|
|
|
+ return lightData.luminance * illuminanceScaleSphereDiskAreaLight(unclampedNoL, sinSolidAngle2);
|
|
|
+ }
|
|
|
|
|
|
- // With microfacet BRDF the BRDF lobe is not centered around the reflected (mirror) direction.
|
|
|
- // Because of NoL and shadow-masking terms the lobe gets shifted toward the normal as roughness
|
|
|
- // increases. This is called the "off-specular peak". We approximate it using this function.
|
|
|
- float3 getSpecularDominantDir(float3 N, float3 R, float roughness)
|
|
|
- {
|
|
|
- // Note: Try this formula as well:
|
|
|
- // float smoothness = 1 - roughness;
|
|
|
- // return lerp(N, R, smoothness * (sqrt(smoothness) + roughness));
|
|
|
+ // Calculates illuminance from a disc area light.
|
|
|
+ float illuminanceDiscAreaLight(float unclampedNoL, float distToLight2, float3 L, LightData lightData)
|
|
|
+ {
|
|
|
+ // Solid angle for right disk = atan (r / d)
|
|
|
+ // atan (r / d) = asin((r / d)/sqrt((r / d)^2+1))
|
|
|
+ // sinAngle = (r / d)/sqrt((r / d)^2 + 1)
|
|
|
+ // sinAngle^2 = (r / d)^2 / (r / d)^2 + 1
|
|
|
+ // = r^2 / (d^2 + r^2)
|
|
|
+
|
|
|
+ float radius2 = lightData.srcRadius * lightData.srcRadius;
|
|
|
|
|
|
- float r2 = roughness * roughness;
|
|
|
- return normalize(lerp(N, R, (1 - r2) * (sqrt(1 - r2) + r2)));
|
|
|
- }
|
|
|
+ // max() to prevent light penetrating object
|
|
|
+ float sinSolidAngle2 = saturate(radius2 / (radius2 + max(radius2, distToLight2)));
|
|
|
|
|
|
- float3 getSurfaceShading(float3 V, float3 L, float specLobeEnergy, SurfaceData surfaceData)
|
|
|
- {
|
|
|
- float3 N = surfaceData.worldNormal.xyz;
|
|
|
+ // Multiply by extra term to somewhat handle the case of the oriented disc (formula above only works
|
|
|
+ // for right discs).
|
|
|
+ return lightData.luminance * illuminanceScaleSphereDiskAreaLight(unclampedNoL, sinSolidAngle2 * saturate(dot(lightData.direction, -L)));
|
|
|
+ }
|
|
|
+
|
|
|
+ // With microfacet BRDF the BRDF lobe is not centered around the reflected (mirror) direction.
|
|
|
+ // Because of NoL and shadow-masking terms the lobe gets shifted toward the normal as roughness
|
|
|
+ // increases. This is called the "off-specular peak". We approximate it using this function.
|
|
|
+ float3 getSpecularDominantDir(float3 N, float3 R, float roughness)
|
|
|
+ {
|
|
|
+ // Note: Try this formula as well:
|
|
|
+ // float smoothness = 1 - roughness;
|
|
|
+ // return lerp(N, R, smoothness * (sqrt(smoothness) + roughness));
|
|
|
+
|
|
|
+ float r2 = roughness * roughness;
|
|
|
+ return normalize(lerp(N, R, (1 - r2) * (sqrt(1 - r2) + r2)));
|
|
|
+ }
|
|
|
+
|
|
|
+ float3 getSurfaceShading(float3 V, float3 L, float specLobeEnergy, SurfaceData surfaceData)
|
|
|
+ {
|
|
|
+ float3 N = surfaceData.worldNormal.xyz;
|
|
|
|
|
|
- float3 H = normalize(V + L);
|
|
|
- float LoH = saturate(dot(L, H));
|
|
|
- float NoH = saturate(dot(N, H));
|
|
|
- float NoV = saturate(dot(N, V));
|
|
|
- float NoL = saturate(dot(N, L));
|
|
|
-
|
|
|
- float3 diffuseColor = lerp(surfaceData.albedo.rgb, float3(0.0f, 0.0f, 0.0f), 1.0f - surfaceData.metalness);
|
|
|
-
|
|
|
- // Note: Using a fixed F0 value of 0.04 (plastic) for dielectrics, and using albedo as specular for conductors.
|
|
|
- // For more customizability allow the user to provide separate albedo/specular colors for both types.
|
|
|
- float3 specularColor = lerp(float3(0.04f, 0.04f, 0.04f), surfaceData.albedo.rgb, surfaceData.metalness);
|
|
|
-
|
|
|
- float3 diffuse = calcDiffuseLambert(diffuseColor);
|
|
|
-
|
|
|
- float roughness = max(surfaceData.roughness, 0.04f); // Prevent NaNs
|
|
|
- float roughness2 = roughness * roughness;
|
|
|
- float roughness4 = roughness2 * roughness2;
|
|
|
-
|
|
|
- float3 specular = calcMicrofacetFresnelShlick(specularColor, LoH) *
|
|
|
- calcMicrofacetDistGGX(roughness4, NoH) *
|
|
|
- calcMicrofacetShadowingSmithGGX(roughness4, NoV, NoL);
|
|
|
-
|
|
|
- // Note: Need to add energy conservation between diffuse and specular terms?
|
|
|
- return diffuse + specular * specLobeEnergy;
|
|
|
- }
|
|
|
+ float3 H = normalize(V + L);
|
|
|
+ float LoH = saturate(dot(L, H));
|
|
|
+ float NoH = saturate(dot(N, H));
|
|
|
+ float NoV = saturate(dot(N, V));
|
|
|
+ float NoL = saturate(dot(N, L));
|
|
|
+
|
|
|
+ float3 diffuseColor = lerp(surfaceData.albedo.rgb, float3(0.0f, 0.0f, 0.0f), 1.0f - surfaceData.metalness);
|
|
|
+
|
|
|
+ // Note: Using a fixed F0 value of 0.04 (plastic) for dielectrics, and using albedo as specular for conductors.
|
|
|
+ // For more customizability allow the user to provide separate albedo/specular colors for both types.
|
|
|
+ float3 specularColor = lerp(float3(0.04f, 0.04f, 0.04f), surfaceData.albedo.rgb, surfaceData.metalness);
|
|
|
|
|
|
- StructuredBuffer<LightData> gLights;
|
|
|
+ float3 diffuse = calcDiffuseLambert(diffuseColor);
|
|
|
|
|
|
- #ifdef USE_COMPUTE_INDICES
|
|
|
- groupshared uint gLightIndices[MAX_LIGHTS];
|
|
|
- #endif
|
|
|
- #ifdef USE_LIGHT_GRID_INDICES
|
|
|
- Buffer<uint> gLightIndices;
|
|
|
- #endif
|
|
|
+ float roughness = max(surfaceData.roughness, 0.04f); // Prevent NaNs
|
|
|
+ float roughness2 = roughness * roughness;
|
|
|
+ float roughness4 = roughness2 * roughness2;
|
|
|
+
|
|
|
+ float3 specular = calcMicrofacetFresnelShlick(specularColor, LoH) *
|
|
|
+ calcMicrofacetDistGGX(roughness4, NoH) *
|
|
|
+ calcMicrofacetShadowingSmithGGX(roughness4, NoV, NoL);
|
|
|
+
|
|
|
+ // Note: Need to add energy conservation between diffuse and specular terms?
|
|
|
+ return diffuse + specular * specLobeEnergy;
|
|
|
+ }
|
|
|
+
|
|
|
+ [internal] StructuredBuffer<LightData> gLights;
|
|
|
+
|
|
|
+ #ifdef USE_COMPUTE_INDICES
|
|
|
+ groupshared uint gLightIndices[MAX_LIGHTS];
|
|
|
+ #endif
|
|
|
+ #ifdef USE_LIGHT_GRID_INDICES
|
|
|
+ [internal] Buffer<uint> gLightIndices;
|
|
|
+ #endif
|
|
|
+
|
|
|
+ float4 getDirectLighting(float3 worldPos, float3 V, float3 R, SurfaceData surfaceData, uint4 lightOffsets)
|
|
|
+ {
|
|
|
+ float3 N = surfaceData.worldNormal.xyz;
|
|
|
+ float roughness2 = max(surfaceData.roughness, 0.08f);
|
|
|
+ roughness2 *= roughness2;
|
|
|
|
|
|
- float4 getDirectLighting(float3 worldPos, float3 V, float3 R, SurfaceData surfaceData, uint4 lightOffsets)
|
|
|
+ float3 outLuminance = 0;
|
|
|
+ float alpha = 0.0f;
|
|
|
+ if(surfaceData.worldNormal.w > 0.0f)
|
|
|
{
|
|
|
- float3 N = surfaceData.worldNormal.xyz;
|
|
|
- float roughness2 = max(surfaceData.roughness, 0.08f);
|
|
|
- roughness2 *= roughness2;
|
|
|
-
|
|
|
- float3 outLuminance = 0;
|
|
|
- float alpha = 0.0f;
|
|
|
- if(surfaceData.worldNormal.w > 0.0f)
|
|
|
+ // Handle directional lights
|
|
|
+ for(uint i = 0; i < lightOffsets.x; ++i)
|
|
|
{
|
|
|
- // Handle directional lights
|
|
|
- for(uint i = 0; i < lightOffsets.x; ++i)
|
|
|
- {
|
|
|
- LightData lightData = gLights[i];
|
|
|
+ LightData lightData = gLights[i];
|
|
|
+
|
|
|
+ float3 L = -lightData.direction;
|
|
|
+ float NoL = saturate(dot(N, L));
|
|
|
+ float specEnergy = 1.0f;
|
|
|
|
|
|
- float3 L = -lightData.direction;
|
|
|
- float NoL = saturate(dot(N, L));
|
|
|
- float specEnergy = 1.0f;
|
|
|
-
|
|
|
- // Distant disk area light. Calculate its contribution analytically by
|
|
|
- // finding the most important (least error) point on the area light and
|
|
|
- // use it as a form of importance sampling.
|
|
|
- if(lightData.srcRadius > 0)
|
|
|
- {
|
|
|
- float diskRadius = sin(lightData.srcRadius);
|
|
|
- float distanceToDisk = cos(lightData.srcRadius);
|
|
|
-
|
|
|
- // Closest point to disk (approximation for distant disks)
|
|
|
- float DoR = dot(L, R);
|
|
|
- float3 S = normalize(R - DoR * L);
|
|
|
- L = DoR < distanceToDisk ? normalize(distanceToDisk * L + S * diskRadius) : R;
|
|
|
- }
|
|
|
+ // Distant disk area light. Calculate its contribution analytically by
|
|
|
+ // finding the most important (least error) point on the area light and
|
|
|
+ // use it as a form of importance sampling.
|
|
|
+ if(lightData.srcRadius > 0)
|
|
|
+ {
|
|
|
+ float diskRadius = sin(lightData.srcRadius);
|
|
|
+ float distanceToDisk = cos(lightData.srcRadius);
|
|
|
|
|
|
- float3 surfaceShading = getSurfaceShading(V, L, specEnergy, surfaceData);
|
|
|
- float illuminance = lightData.luminance * NoL;
|
|
|
- outLuminance += lightData.color * illuminance * surfaceShading;
|
|
|
+ // Closest point to disk (approximation for distant disks)
|
|
|
+ float DoR = dot(L, R);
|
|
|
+ float3 S = normalize(R - DoR * L);
|
|
|
+ L = DoR < distanceToDisk ? normalize(distanceToDisk * L + S * diskRadius) : R;
|
|
|
}
|
|
|
|
|
|
- // Handle radial lights
|
|
|
- for (uint i = lightOffsets.y; i < lightOffsets.z; ++i)
|
|
|
- {
|
|
|
- uint lightIdx = gLightIndices[i];
|
|
|
- LightData lightData = gLights[lightIdx];
|
|
|
-
|
|
|
- float3 toLight = lightData.position - worldPos;
|
|
|
- float distToLightSqrd = dot(toLight, toLight);
|
|
|
- float invDistToLight = rsqrt(distToLightSqrd);
|
|
|
-
|
|
|
- float3 L = toLight * invDistToLight;
|
|
|
- float NoL = dot(N, L);
|
|
|
-
|
|
|
- float specEnergy = 1.0f;
|
|
|
- float illuminance = 0.0f;
|
|
|
+ float3 surfaceShading = getSurfaceShading(V, L, specEnergy, surfaceData);
|
|
|
+ float illuminance = lightData.luminance * NoL;
|
|
|
+ outLuminance += lightData.color * illuminance * surfaceShading;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Handle radial lights
|
|
|
+ for (uint i = lightOffsets.y; i < lightOffsets.z; ++i)
|
|
|
+ {
|
|
|
+ uint lightIdx = gLightIndices[i];
|
|
|
+ LightData lightData = gLights[lightIdx];
|
|
|
+
|
|
|
+ float3 toLight = lightData.position - worldPos;
|
|
|
+ float distToLightSqrd = dot(toLight, toLight);
|
|
|
+ float invDistToLight = rsqrt(distToLightSqrd);
|
|
|
+
|
|
|
+ float3 L = toLight * invDistToLight;
|
|
|
+ float NoL = dot(N, L);
|
|
|
+
|
|
|
+ float specEnergy = 1.0f;
|
|
|
+ float illuminance = 0.0f;
|
|
|
|
|
|
- // Sphere area light. Calculate its contribution analytically by
|
|
|
- // finding the most important (least error) point on the area light and
|
|
|
- // use it as a form of importance sampling.
|
|
|
- if(lightData.srcRadius > 0)
|
|
|
- {
|
|
|
- // Calculate illuminance depending on source size, distance and angle
|
|
|
- illuminance = illuminanceSphereAreaLight(NoL, distToLightSqrd, lightData);
|
|
|
+ // Sphere area light. Calculate its contribution analytically by
|
|
|
+ // finding the most important (least error) point on the area light and
|
|
|
+ // use it as a form of importance sampling.
|
|
|
+ if(lightData.srcRadius > 0)
|
|
|
+ {
|
|
|
+ // Calculate illuminance depending on source size, distance and angle
|
|
|
+ illuminance = illuminanceSphereAreaLight(NoL, distToLightSqrd, lightData);
|
|
|
|
|
|
- // Energy conservation:
|
|
|
- // We are widening the specular distribution by the sphere's subtended angle,
|
|
|
- // so we need to handle the increase in energy. It is not enough just to account
|
|
|
- // for the sphere solid angle, since the energy difference is highly dependent on
|
|
|
- // specular distribution. By accounting for this energy difference we ensure glossy
|
|
|
- // reflections have sharp edges, instead of being too blurry.
|
|
|
- // See http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf for reference
|
|
|
- float sphereAngle = saturate(lightData.srcRadius * invDistToLight);
|
|
|
-
|
|
|
- specEnergy = roughness2 / saturate(roughness2 + 0.5f * sphereAngle);
|
|
|
- specEnergy *= specEnergy;
|
|
|
+ // Energy conservation:
|
|
|
+ // We are widening the specular distribution by the sphere's subtended angle,
|
|
|
+ // so we need to handle the increase in energy. It is not enough just to account
|
|
|
+ // for the sphere solid angle, since the energy difference is highly dependent on
|
|
|
+ // specular distribution. By accounting for this energy difference we ensure glossy
|
|
|
+ // reflections have sharp edges, instead of being too blurry.
|
|
|
+ // See http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf for reference
|
|
|
+ float sphereAngle = saturate(lightData.srcRadius * invDistToLight);
|
|
|
|
|
|
- // Find closest point on sphere to ray
|
|
|
- float3 closestPointOnRay = dot(toLight, R) * R;
|
|
|
- float3 centerToRay = closestPointOnRay - toLight;
|
|
|
- float invDistToRay = rsqrt(dot(centerToRay, centerToRay));
|
|
|
- float3 closestPointOnSphere = toLight + centerToRay * saturate(lightData.srcRadius * invDistToRay);
|
|
|
-
|
|
|
- toLight = closestPointOnSphere;
|
|
|
- L = normalize(toLight);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- NoL = saturate(NoL);
|
|
|
- illuminance = illuminancePointLight(distToLightSqrd, NoL, lightData);
|
|
|
- }
|
|
|
+ specEnergy = roughness2 / saturate(roughness2 + 0.5f * sphereAngle);
|
|
|
+ specEnergy *= specEnergy;
|
|
|
+
|
|
|
+ // Find closest point on sphere to ray
|
|
|
+ float3 closestPointOnRay = dot(toLight, R) * R;
|
|
|
+ float3 centerToRay = closestPointOnRay - toLight;
|
|
|
+ float invDistToRay = rsqrt(dot(centerToRay, centerToRay));
|
|
|
+ float3 closestPointOnSphere = toLight + centerToRay * saturate(lightData.srcRadius * invDistToRay);
|
|
|
|
|
|
- float attenuation = getRadialAttenuation(distToLightSqrd, lightData);
|
|
|
- float3 surfaceShading = getSurfaceShading(V, L, specEnergy, surfaceData);
|
|
|
-
|
|
|
- outLuminance += lightData.color * illuminance * attenuation * surfaceShading;
|
|
|
- }
|
|
|
-
|
|
|
- // Handle spot lights
|
|
|
- for(uint i = lightOffsets.z; i < lightOffsets.w; ++i)
|
|
|
- {
|
|
|
- uint lightIdx = gLightIndices[i];
|
|
|
- LightData lightData = gLights[lightIdx];
|
|
|
+ toLight = closestPointOnSphere;
|
|
|
+ L = normalize(toLight);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ NoL = saturate(NoL);
|
|
|
+ illuminance = illuminancePointLight(distToLightSqrd, NoL, lightData);
|
|
|
+ }
|
|
|
+
|
|
|
+ float attenuation = getRadialAttenuation(distToLightSqrd, lightData);
|
|
|
+ float3 surfaceShading = getSurfaceShading(V, L, specEnergy, surfaceData);
|
|
|
|
|
|
- float3 toLight = lightData.position - worldPos;
|
|
|
- float distToLightSqrd = dot(toLight, toLight);
|
|
|
- float invDistToLight = rsqrt(distToLightSqrd);
|
|
|
+ outLuminance += lightData.color * illuminance * attenuation * surfaceShading;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Handle spot lights
|
|
|
+ for(uint i = lightOffsets.z; i < lightOffsets.w; ++i)
|
|
|
+ {
|
|
|
+ uint lightIdx = gLightIndices[i];
|
|
|
+ LightData lightData = gLights[lightIdx];
|
|
|
+
|
|
|
+ float3 toLight = lightData.position - worldPos;
|
|
|
+ float distToLightSqrd = dot(toLight, toLight);
|
|
|
+ float invDistToLight = rsqrt(distToLightSqrd);
|
|
|
+
|
|
|
+ float3 L = toLight * invDistToLight;
|
|
|
+ float NoL = dot(N, L);
|
|
|
+
|
|
|
+ float specEnergy = 1.0f;
|
|
|
+ float illuminance = 0.0f;
|
|
|
+ float spotAttenuation = 1.0f;
|
|
|
+
|
|
|
+ // Disc area light. Calculate its contribution analytically by
|
|
|
+ // finding the most important (least error) point on the area light and
|
|
|
+ // use it as a form of importance sampling.
|
|
|
+ if(lightData.srcRadius > 0)
|
|
|
+ {
|
|
|
+ // Calculate illuminance depending on source size, distance and angle
|
|
|
+ illuminance = illuminanceDiscAreaLight(NoL, distToLightSqrd, L, lightData);
|
|
|
+
|
|
|
+ // Energy conservation: Similar case as with radial lights
|
|
|
+ float rightDiscAngle = saturate(lightData.srcRadius * invDistToLight);
|
|
|
|
|
|
- float3 L = toLight * invDistToLight;
|
|
|
- float NoL = dot(N, L);
|
|
|
+ // Account for disc orientation somewhat
|
|
|
+ float discAngle = rightDiscAngle * saturate(dot(lightData.direction, -L));
|
|
|
|
|
|
- float specEnergy = 1.0f;
|
|
|
- float illuminance = 0.0f;
|
|
|
- float spotAttenuation = 1.0f;
|
|
|
+ specEnergy = roughness2 / saturate(roughness2 + 0.5f * discAngle);
|
|
|
+ specEnergy *= specEnergy;
|
|
|
+
|
|
|
+ // Find closest point on disc to ray
|
|
|
+ float3 discNormal = -lightData.direction;
|
|
|
+ float distAlongLightDir = max(dot(R, discNormal), 1e-6f);
|
|
|
+ float t = dot(toLight, discNormal) / distAlongLightDir;
|
|
|
+ float3 closestPointOnPlane = R * t; // Relative to shaded world point
|
|
|
|
|
|
- // Disc area light. Calculate its contribution analytically by
|
|
|
- // finding the most important (least error) point on the area light and
|
|
|
- // use it as a form of importance sampling.
|
|
|
- if(lightData.srcRadius > 0)
|
|
|
- {
|
|
|
- // Calculate illuminance depending on source size, distance and angle
|
|
|
- illuminance = illuminanceDiscAreaLight(NoL, distToLightSqrd, L, lightData);
|
|
|
+ float3 centerToRay = closestPointOnPlane - toLight;
|
|
|
+ float invDistToRay = rsqrt(dot(centerToRay, centerToRay));
|
|
|
+ float3 closestPointOnDisc = toLight + centerToRay * saturate(lightData.srcRadius * invDistToRay);
|
|
|
+
|
|
|
+ toLight = closestPointOnDisc;
|
|
|
+ L = normalize(toLight);
|
|
|
|
|
|
- // Energy conservation: Similar case as with radial lights
|
|
|
- float rightDiscAngle = saturate(lightData.srcRadius * invDistToLight);
|
|
|
-
|
|
|
- // Account for disc orientation somewhat
|
|
|
- float discAngle = rightDiscAngle * saturate(dot(lightData.direction, -L));
|
|
|
-
|
|
|
- specEnergy = roughness2 / saturate(roughness2 + 0.5f * discAngle);
|
|
|
- specEnergy *= specEnergy;
|
|
|
+ // Expand spot attenuation by disc radius (not physically based)
|
|
|
+ float3 toSpotEdge = normalize(lightData.shiftedLightPosition - worldPos);
|
|
|
+ spotAttenuation = getSpotAttenuation(toSpotEdge, lightData);
|
|
|
|
|
|
- // Find closest point on disc to ray
|
|
|
- float3 discNormal = -lightData.direction;
|
|
|
- float distAlongLightDir = max(dot(R, discNormal), 1e-6f);
|
|
|
- float t = dot(toLight, discNormal) / distAlongLightDir;
|
|
|
- float3 closestPointOnPlane = R * t; // Relative to shaded world point
|
|
|
-
|
|
|
- float3 centerToRay = closestPointOnPlane - toLight;
|
|
|
- float invDistToRay = rsqrt(dot(centerToRay, centerToRay));
|
|
|
- float3 closestPointOnDisc = toLight + centerToRay * saturate(lightData.srcRadius * invDistToRay);
|
|
|
-
|
|
|
- toLight = closestPointOnDisc;
|
|
|
- L = normalize(toLight);
|
|
|
-
|
|
|
- // Expand spot attenuation by disc radius (not physically based)
|
|
|
- float3 toSpotEdge = normalize(lightData.shiftedLightPosition - worldPos);
|
|
|
- spotAttenuation = getSpotAttenuation(toSpotEdge, lightData);
|
|
|
-
|
|
|
- // TODO - Spot attenuation fades out the specular highlight in a noticeable way
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- NoL = saturate(NoL);
|
|
|
- illuminance = illuminancePointLight(distToLightSqrd, NoL, lightData);
|
|
|
-
|
|
|
- spotAttenuation = getSpotAttenuation(L, lightData);
|
|
|
- }
|
|
|
+ // TODO - Spot attenuation fades out the specular highlight in a noticeable way
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ NoL = saturate(NoL);
|
|
|
+ illuminance = illuminancePointLight(distToLightSqrd, NoL, lightData);
|
|
|
|
|
|
- float radialAttenuation = getRadialAttenuation(distToLightSqrd, lightData);
|
|
|
- float attenuation = spotAttenuation * radialAttenuation;
|
|
|
- float3 surfaceShading = getSurfaceShading(V, L, specEnergy, surfaceData);
|
|
|
-
|
|
|
- outLuminance += lightData.color * illuminance * attenuation * surfaceShading;
|
|
|
- }
|
|
|
+ spotAttenuation = getSpotAttenuation(L, lightData);
|
|
|
+ }
|
|
|
|
|
|
- // Ambient term for in-editor visualization, not used in actual lighting
|
|
|
- outLuminance += surfaceData.albedo.rgb * gAmbientFactor / PI;
|
|
|
- alpha = 1.0f;
|
|
|
+ float radialAttenuation = getRadialAttenuation(distToLightSqrd, lightData);
|
|
|
+ float attenuation = spotAttenuation * radialAttenuation;
|
|
|
+ float3 surfaceShading = getSurfaceShading(V, L, specEnergy, surfaceData);
|
|
|
+
|
|
|
+ outLuminance += lightData.color * illuminance * attenuation * surfaceShading;
|
|
|
}
|
|
|
|
|
|
- return float4(outLuminance, alpha);
|
|
|
+ // Ambient term for in-editor visualization, not used in actual lighting
|
|
|
+ outLuminance += surfaceData.albedo.rgb * gAmbientFactor / PI;
|
|
|
+ alpha = 1.0f;
|
|
|
}
|
|
|
- };
|
|
|
+
|
|
|
+ return float4(outLuminance, alpha);
|
|
|
+ }
|
|
|
};
|
|
|
};
|