|
@@ -8,37 +8,61 @@
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
+#if ENABLE_CAPSULE_LIGHTS
|
|
|
+
|
|
|
#include <Atom/Features/PBR/Lights/LightTypesCommon.azsli>
|
|
|
-#include <Atom/Features/PBR/Lights/PointLight.azsli>
|
|
|
|
|
|
-#if ENABLE_CAPSULE_LIGHTS
|
|
|
+#ifndef CapsuleLightUtil
|
|
|
+#define CapsuleLightUtil CapsuleLightUtil_PBR
|
|
|
+#endif
|
|
|
|
|
|
-void ApplyCapsuleLight(CapsuleLight light, Surface surface, inout LightingData lightingData)
|
|
|
+class CapsuleLightUtil_PBR
|
|
|
{
|
|
|
- float lightLength = light.m_length;
|
|
|
- float3 startPoint = light.m_startPoint;
|
|
|
- float3 startToEnd = light.m_direction * lightLength;
|
|
|
- float3 endPoint = startPoint + startToEnd;
|
|
|
-
|
|
|
- // Do simple distance check to line for falloff and tight culling.
|
|
|
- float3 surfaceToStart = startPoint - surface.position;
|
|
|
- float closestPointDistance = dot(-surfaceToStart, light.m_direction);
|
|
|
- closestPointDistance = clamp(closestPointDistance, 0.0f, lightLength);
|
|
|
+ real m_lightLength;
|
|
|
+ real3 m_startPoint;
|
|
|
+ real3 m_startToEnd;
|
|
|
+ real3 m_endPoint;
|
|
|
+ real3 m_surfaceToStart;
|
|
|
+ real m_closestPointDistance;
|
|
|
+ real3 m_dirToClosestPoint;
|
|
|
+ real m_d2;
|
|
|
+ real m_falloff;
|
|
|
+
|
|
|
+ static CapsuleLightUtil_PBR Init(CapsuleLight light, Surface surface)
|
|
|
+ {
|
|
|
+ CapsuleLightUtil_PBR result;
|
|
|
+
|
|
|
+ result.m_lightLength = light.m_length;
|
|
|
+ result.m_startPoint = light.m_startPoint;
|
|
|
+ result.m_startToEnd = light.m_direction * result.m_lightLength;
|
|
|
+ result.m_endPoint = result.m_startPoint + result.m_startToEnd;
|
|
|
+
|
|
|
+ // Do simple distance check to line for m_falloff and tight culling.
|
|
|
+ result.m_surfaceToStart = result.m_startPoint - surface.position;
|
|
|
+ result.m_closestPointDistance = dot(-result.m_surfaceToStart, light.m_direction);
|
|
|
+ result.m_closestPointDistance = clamp(result.m_closestPointDistance, 0.0f, result.m_lightLength);
|
|
|
+
|
|
|
+ result.m_dirToClosestPoint = result.m_surfaceToStart + result.m_closestPointDistance * light.m_direction;
|
|
|
+ result.m_d2 = dot(result.m_dirToClosestPoint, result.m_dirToClosestPoint);
|
|
|
+ result.m_falloff = result.m_d2 * light.m_invAttenuationRadiusSquared;
|
|
|
+ return result;
|
|
|
+ }
|
|
|
|
|
|
- float3 dirToClosestPoint = surfaceToStart + closestPointDistance * light.m_direction;
|
|
|
- float d2 = dot(dirToClosestPoint, dirToClosestPoint);
|
|
|
- float falloff = d2 * light.m_invAttenuationRadiusSquared;
|
|
|
+ real GetFalloff()
|
|
|
+ {
|
|
|
+ return m_falloff;
|
|
|
+ }
|
|
|
|
|
|
- if (falloff < 1.0)
|
|
|
+ void Apply(CapsuleLight light, Surface surface, real litRatio, inout LightingData lightingData)
|
|
|
{
|
|
|
// Smoothly adjusts the light intensity so it reaches 0 at light.m_attenuationRadius distance
|
|
|
- float radiusAttenuation = 1.0 - (falloff * falloff);
|
|
|
+ float radiusAttenuation = 1.0 - (m_falloff * m_falloff);
|
|
|
radiusAttenuation = radiusAttenuation * radiusAttenuation;
|
|
|
|
|
|
// Check if the capsule intersects the plane formed by the surface normal. If so, the capsule should be
|
|
|
// truncated to just the portion above the plane.
|
|
|
- float startProjection = dot(surface.GetDiffuseNormal(), surfaceToStart);
|
|
|
- float lengthProjection = dot(surface.GetDiffuseNormal(), startToEnd);
|
|
|
+ float startProjection = dot(surface.GetDiffuseNormal(), m_surfaceToStart);
|
|
|
+ float lengthProjection = dot(surface.GetDiffuseNormal(), m_startToEnd);
|
|
|
float startOverLength = startProjection / lengthProjection;
|
|
|
float ratioVisible = 1.0;
|
|
|
|
|
@@ -53,36 +77,37 @@ void ApplyCapsuleLight(CapsuleLight light, Surface surface, inout LightingData l
|
|
|
if (startProjection < 0.0f)
|
|
|
{
|
|
|
// start point is behind the surface, so move it
|
|
|
- startPoint = startPoint + lightLength * startOverLength * light.m_direction;
|
|
|
+ m_startPoint = m_startPoint + m_lightLength * startOverLength * light.m_direction;
|
|
|
ratioVisible = 1.0 - startOverLength;
|
|
|
- lightLength *= ratioVisible;
|
|
|
- surfaceToStart = startPoint - surface.position;
|
|
|
+ m_lightLength *= ratioVisible;
|
|
|
+ m_surfaceToStart = m_startPoint - surface.position;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
// end point is behind the surface so adjust the capsule length
|
|
|
- lightLength *= startOverLength;
|
|
|
+ m_lightLength *= startOverLength;
|
|
|
ratioVisible = startOverLength;
|
|
|
- endPoint = light.m_startPoint + light.m_direction * lightLength;
|
|
|
+ m_endPoint = light.m_startPoint + light.m_direction * m_lightLength;
|
|
|
}
|
|
|
- startToEnd = light.m_direction * lightLength;
|
|
|
+ m_startToEnd = light.m_direction * m_lightLength;
|
|
|
}
|
|
|
|
|
|
// Calculate the distances to start and end
|
|
|
- float distanceToStart = length(surfaceToStart);
|
|
|
- float3 surfaceToEnd = endPoint - surface.position;
|
|
|
+ float distanceToStart = length(m_surfaceToStart);
|
|
|
+ float3 surfaceToEnd = m_endPoint - surface.position;
|
|
|
float distanceToEnd = length(surfaceToEnd);
|
|
|
|
|
|
// Integration of lambert reflectance of a line segment to get diffuse intensity
|
|
|
// See https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
|
|
|
- float NdotStart = dot(surface.GetDiffuseNormal(), surfaceToStart) / distanceToStart;
|
|
|
+ float NdotStart = dot(surface.GetDiffuseNormal(), m_surfaceToStart) / distanceToStart;
|
|
|
float NdotEnd = dot(surface.GetDiffuseNormal(), surfaceToEnd) / distanceToEnd;
|
|
|
- float intensity = (NdotStart + NdotEnd) / (distanceToStart * distanceToEnd + dot(surfaceToStart, surfaceToEnd));
|
|
|
+ float intensity = (NdotStart + NdotEnd) / (distanceToStart * distanceToEnd + dot(m_surfaceToStart, surfaceToEnd));
|
|
|
intensity *= INV_PI; // normalize for lambert reflectance.
|
|
|
|
|
|
float3 lightIntensity = (intensity * radiusAttenuation * ratioVisible) * light.m_rgbIntensityCandelas;
|
|
|
- lightingData.diffuseLighting += max(0.0, surface.albedo * lightIntensity);
|
|
|
+ lightingData.diffuseLighting += max(0.0, surface.albedo * lightIntensity) * litRatio;
|
|
|
|
|
|
+#if ENABLE_TRANSMISSION
|
|
|
// Transmission contribution
|
|
|
// We cannot compute the actual transmission distance so we want to:
|
|
|
// - If transmission mode is thick object -> use transmission thickness parameter instead
|
|
@@ -92,8 +117,7 @@ void ApplyCapsuleLight(CapsuleLight light, Surface surface, inout LightingData l
|
|
|
// If the transmissionDistance is ignored then the attenuation distance (only used on thin objects) does not have any influence
|
|
|
const float attenuationDistance = 0.0f;
|
|
|
|
|
|
-#if ENABLE_TRANSMISSION
|
|
|
- lightingData.translucentBackLighting += GetBackLighting(surface, lightingData, lightIntensity, normalize(dirToClosestPoint), transmissionDistance, attenuationDistance);
|
|
|
+ lightingData.translucentBackLighting += GetBackLighting(surface, lightingData, lightIntensity, normalize(m_dirToClosestPoint), transmissionDistance, attenuationDistance) * litRatio;
|
|
|
#endif
|
|
|
|
|
|
// Calculate specular lighting for each view
|
|
@@ -105,11 +129,11 @@ void ApplyCapsuleLight(CapsuleLight light, Surface surface, inout LightingData l
|
|
|
|
|
|
// Find closest point on light to reflection vector.
|
|
|
// See https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
|
|
|
- float reflDotStartToEnd = dot(reflectionDir, startToEnd);
|
|
|
- float closestT = (dot(reflectionDir, surfaceToStart) * reflDotStartToEnd - dot(surfaceToStart, startToEnd)) / (dot(startToEnd, startToEnd) - reflDotStartToEnd * reflDotStartToEnd);
|
|
|
+ float reflDotm_startToEnd = dot(reflectionDir, m_startToEnd);
|
|
|
+ float closestT = (dot(reflectionDir, m_surfaceToStart) * reflDotm_startToEnd - dot(m_surfaceToStart, m_startToEnd)) / (dot(m_startToEnd, m_startToEnd) - reflDotm_startToEnd * reflDotm_startToEnd);
|
|
|
closestT = saturate(closestT);
|
|
|
|
|
|
- float3 closestIntersectionPoint = startPoint + closestT * startToEnd;
|
|
|
+ float3 closestIntersectionPoint = m_startPoint + closestT * m_startToEnd;
|
|
|
float3 posToLight = closestIntersectionPoint - surface.position;
|
|
|
|
|
|
// Calculate the offset from the nearest point on the reflection vector to the nearest point on the capsule light
|
|
@@ -118,11 +142,11 @@ void ApplyCapsuleLight(CapsuleLight light, Surface surface, inout LightingData l
|
|
|
|
|
|
// Adjust the direction to light based on the capsule radius
|
|
|
posToLight -= lightReflectionOffset * saturate(light.m_radius / length(lightReflectionOffset));
|
|
|
- d2 = dot(posToLight, posToLight);
|
|
|
- d2 = max(light.m_radius * light.m_radius, d2);
|
|
|
+ m_d2 = dot(posToLight, posToLight);
|
|
|
+ m_d2 = max(light.m_radius * light.m_radius, m_d2);
|
|
|
|
|
|
// Adjust the intensity of the light based on the capsule radius to conserve energy
|
|
|
- float sphereIntensityNormalization = GetIntensityAdjustedByRadiusAndRoughness(surface.roughnessA, light.m_radius, d2);
|
|
|
+ float sphereIntensityNormalization = GetIntensityAdjustedByRadiusAndRoughness(surface.roughnessA, light.m_radius, m_d2);
|
|
|
|
|
|
// Capsule specular is done just like spherical specular, we just move the position of the sphere along the capsule depending
|
|
|
// on the point being shaded. However this means that the intensity needs to be reduced by the ratio of the capsule surface
|
|
@@ -130,160 +154,64 @@ void ApplyCapsuleLight(CapsuleLight light, Surface surface, inout LightingData l
|
|
|
float sphereToCapsuleAreaRatio = 2.0 * light.m_radius / max(2.0 * light.m_radius + light.m_length, EPSILON);
|
|
|
|
|
|
// Specular contribution
|
|
|
- float3 viewLightIntensity = sphereToCapsuleAreaRatio * radiusAttenuation / d2;
|
|
|
+ float3 viewLightIntensity = sphereToCapsuleAreaRatio * radiusAttenuation / m_d2;
|
|
|
viewLightIntensity *= light.m_rgbIntensityCandelas;
|
|
|
- lightingData.specularLighting[viewIndex] += sphereIntensityNormalization * GetSpecularLighting(surface, lightingData, viewLightIntensity, normalize(posToLight), viewIndex);
|
|
|
+ lightingData.specularLighting[viewIndex] += sphereIntensityNormalization * GetSpecularLighting(surface, lightingData, viewLightIntensity, normalize(posToLight), viewIndex) * litRatio;
|
|
|
}
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-// Get a uniformly distributed point on the surface of a capsule from the provided uniformly distributed 2d point. Uses
|
|
|
-// capsRatio to determine if the point should be on the caps or cylinder to ensure an even distribution.
|
|
|
-void SampleCapsule(float2 randomPoint, CapsuleLight light, float capToCylinderAreaRatio, float3x3 localToWorld, out float3 outPosition, out float3 outDirection)
|
|
|
-{
|
|
|
- float3 startToEnd = light.m_direction * light.m_length;
|
|
|
- float3 endPoint = light.m_startPoint + startToEnd;
|
|
|
- if (randomPoint.x < capToCylinderAreaRatio)
|
|
|
+ static void ApplySampled(CapsuleLight light, Surface surface, inout LightingData lightingData, const uint sampleCount = 1024)
|
|
|
{
|
|
|
- // Sample on cap
|
|
|
- float2 spherePoint = randomPoint;
|
|
|
- spherePoint.x /= capToCylinderAreaRatio; // normalize to 0.0 -> 1.0
|
|
|
-
|
|
|
- float3 pointDirection = SampleSphere(spherePoint);
|
|
|
- if (dot(pointDirection, light.m_direction) < 0.0)
|
|
|
+ float capArea = 4.0 * PI * light.m_radius * light.m_radius;
|
|
|
+ float cylinderArea = 2.0 * PI * light.m_radius * light.m_length;
|
|
|
+ float capToCylinderAreaRatio = capArea / (capArea + cylinderArea);
|
|
|
+
|
|
|
+ // Construct local to world matrix for transforming sample points.
|
|
|
+ float3x3 localToWorld;
|
|
|
+ localToWorld[0] = light.m_direction;
|
|
|
+ if (abs(light.m_direction.z) > 0.9)
|
|
|
{
|
|
|
- // start point cap
|
|
|
- outPosition = light.m_startPoint + pointDirection * light.m_radius;
|
|
|
+ // If vector is close to aligned with z axis choose y as up.
|
|
|
+ localToWorld[2] = normalize(cross(light.m_direction, float3(0.0, 1.0, 0.0)));
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- // end point cap
|
|
|
- outPosition = endPoint + pointDirection * light.m_radius;
|
|
|
+ localToWorld[2] = normalize(cross(light.m_direction, float3(0.0, 0.0, 1.0)));
|
|
|
}
|
|
|
- outDirection = pointDirection;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // Sample on cylinder
|
|
|
- float angle = randomPoint.y * PI * 2.0;
|
|
|
- outDirection.x = 0.0;
|
|
|
- outDirection.y = sin(angle);
|
|
|
- outDirection.z = cos(angle);
|
|
|
- outDirection = mul(outDirection, localToWorld);
|
|
|
-
|
|
|
- float positionAlongCylinder = (randomPoint.x - capToCylinderAreaRatio) / (1.0 - capToCylinderAreaRatio); // normalize to 0.0 -> 1.0
|
|
|
- positionAlongCylinder *= light.m_length;
|
|
|
-
|
|
|
- float3 positionInCylinder = light.m_startPoint + light.m_direction * positionAlongCylinder;
|
|
|
- outPosition = positionInCylinder + outDirection * light.m_radius;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-void ValidateCapsuleLight(CapsuleLight light, Surface surface, inout LightingData lightingData)
|
|
|
-{
|
|
|
- const uint sampleCount = 1024;
|
|
|
-
|
|
|
- float capArea = 4.0 * PI * light.m_radius * light.m_radius;
|
|
|
- float cylinderArea = 2.0 * PI * light.m_radius * light.m_length;
|
|
|
- float capToCylinderAreaRatio = capArea / (capArea + cylinderArea);
|
|
|
-
|
|
|
- // Construct local to world matrix for transforming sample points.
|
|
|
- float3x3 localToWorld;
|
|
|
- localToWorld[0] = light.m_direction;
|
|
|
- if (abs(light.m_direction.z) > 0.9)
|
|
|
- {
|
|
|
- // If vector is close to aligned with z axis choose y as up.
|
|
|
- localToWorld[2] = normalize(cross(light.m_direction, float3(0.0, 1.0, 0.0)));
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- localToWorld[2] = normalize(cross(light.m_direction, float3(0.0, 0.0, 1.0)));
|
|
|
- }
|
|
|
- localToWorld[1]= cross(localToWorld[0], localToWorld[2]);
|
|
|
-
|
|
|
- real3 diffuseAcc = float3(0.0, 0.0, 0.0);
|
|
|
- real3 translucentAcc = float3(0.0, 0.0, 0.0);
|
|
|
- real3 specularAcc[MAX_SHADING_VIEWS];
|
|
|
-
|
|
|
- [unroll]
|
|
|
- for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
|
|
|
- specularAcc[viewIndex] = float3(0.0, 0.0, 0.0);
|
|
|
-
|
|
|
- for (uint i = 0; i < sampleCount; ++i)
|
|
|
- {
|
|
|
- float3 position;
|
|
|
- float3 direction;
|
|
|
- float2 randomPoint = GetHammersleyPoint(i, sampleCount);
|
|
|
- SampleCapsule(randomPoint, light, capToCylinderAreaRatio, localToWorld, position, direction);
|
|
|
- AddSampleContribution(surface, lightingData, position, direction, 0.0, diffuseAcc, specularAcc, translucentAcc);
|
|
|
- }
|
|
|
+ localToWorld[1]= cross(localToWorld[0], localToWorld[2]);
|
|
|
|
|
|
- // Lighting value is in Candela, convert to Lumen for total light output of the light
|
|
|
- float3 intensityLumens = light.m_rgbIntensityCandelas * 4.0 * PI;
|
|
|
+ real3 diffuseAcc = float3(0.0, 0.0, 0.0);
|
|
|
+ real3 translucentAcc = float3(0.0, 0.0, 0.0);
|
|
|
+ real3 specularAcc[MAX_SHADING_VIEWS];
|
|
|
|
|
|
- // Each of the N samples will contribute intensity / N lumens. However it will radiate in
|
|
|
- // equal directions across the hemisphere, so we need to account for that
|
|
|
- float3 intensity = intensityLumens * INV_PI;
|
|
|
-
|
|
|
- lightingData.diffuseLighting += (diffuseAcc / float(sampleCount)) * intensity;
|
|
|
-
|
|
|
- [unroll]
|
|
|
- for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
|
|
|
- lightingData.specularLighting[viewIndex] += (specularAcc[viewIndex] / float(sampleCount)) * intensity;
|
|
|
-
|
|
|
-#if ENABLE_TRANSMISSION
|
|
|
- lightingData.translucentBackLighting += (translucentAcc / float(sampleCount)) * intensity;
|
|
|
-#endif
|
|
|
-}
|
|
|
+ [unroll]
|
|
|
+ for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
|
|
|
+ specularAcc[viewIndex] = float3(0.0, 0.0, 0.0);
|
|
|
|
|
|
-void ApplyCapsuleLightInternal(uint lightIndex, Surface surface, inout LightingData lightingData)
|
|
|
-{
|
|
|
- if (o_enableCapsuleLights)
|
|
|
- {
|
|
|
- CapsuleLight light = ViewSrg::m_capsuleLights[lightIndex];
|
|
|
- if(!IsSameLightChannel(lightingData.lightingChannels, light.m_lightingChannelMask))
|
|
|
+ for (uint i = 0; i < sampleCount; ++i)
|
|
|
{
|
|
|
- return;
|
|
|
+ float3 position;
|
|
|
+ float3 direction;
|
|
|
+ float2 randomPoint = GetHammersleyPoint(i, sampleCount);
|
|
|
+ SampleCapsule(randomPoint, light, capToCylinderAreaRatio, localToWorld, position, direction);
|
|
|
+ AddSampleContribution(surface, lightingData, position, direction, 0.0, diffuseAcc, specularAcc, translucentAcc);
|
|
|
}
|
|
|
|
|
|
-#if ENABLE_AREA_LIGHT_VALIDATION
|
|
|
- if (o_area_light_validation)
|
|
|
- {
|
|
|
- ValidateCapsuleLight(light, surface, lightingData);
|
|
|
- }
|
|
|
- else
|
|
|
-#endif // ENABLE_AREA_LIGHT_VALIDATION
|
|
|
- {
|
|
|
- ApplyCapsuleLight(light, surface, lightingData);
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-void ApplyCapsuleLights(Surface surface, inout LightingData lightingData)
|
|
|
-{
|
|
|
-#if ENABLE_LIGHT_CULLING
|
|
|
- lightingData.tileIterator.LoadAdvance();
|
|
|
-
|
|
|
- while(!lightingData.tileIterator.IsDone())
|
|
|
- {
|
|
|
- uint currLightIndex = lightingData.tileIterator.GetValue();
|
|
|
- lightingData.tileIterator.LoadAdvance();
|
|
|
+ // Lighting value is in Candela, convert to Lumen for total light output of the light
|
|
|
+ float3 intensityLumens = light.m_rgbIntensityCandelas * 4.0 * PI;
|
|
|
+ // Each of the N samples will contribute intensity / N lumens. However it will radiate in
|
|
|
+ // equal directions across the hemisphere, so we need to account for that
|
|
|
+ float3 intensity = intensityLumens * INV_PI;
|
|
|
+
|
|
|
+ lightingData.diffuseLighting += (diffuseAcc / float(sampleCount)) * intensity;
|
|
|
+ [unroll]
|
|
|
+ for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
|
|
|
+ lightingData.specularLighting[viewIndex] += (specularAcc[viewIndex] / float(sampleCount)) * intensity;
|
|
|
|
|
|
- ApplyCapsuleLightInternal(currLightIndex, surface, lightingData);
|
|
|
- }
|
|
|
-#else
|
|
|
- for(uint lightIndex = 0; lightIndex < ViewSrg::m_capsuleLightCount; lightIndex++)
|
|
|
- {
|
|
|
- ApplyCapsuleLightInternal(lightIndex, surface, lightingData);
|
|
|
- }
|
|
|
+#if ENABLE_TRANSMISSION
|
|
|
+ lightingData.translucentBackLighting += (translucentAcc / float(sampleCount)) * intensity;
|
|
|
#endif
|
|
|
-}
|
|
|
-
|
|
|
-#else // ENABLE_CAPSULE_LIGHTS
|
|
|
-
|
|
|
-void ApplyCapsuleLights(Surface surface, inout LightingData lightingData)
|
|
|
-{
|
|
|
- //Not Enabled
|
|
|
-}
|
|
|
+ }
|
|
|
+};
|
|
|
|
|
|
-#endif // ENABLE_CAPSULE_LIGHTS
|
|
|
+#endif // ENABLE_CAPSULE_LIGHTS
|