From ae8383c14bfa43713977c9d1c579af23368b1839 Mon Sep 17 00:00:00 2001 From: Antoine Lelievre Date: Tue, 30 Oct 2018 13:29:10 +0100 Subject: [PATCH 1/2] Added area light code for simple lights --- .../Lighting/LightLoop/SimpleLightLoop.hlsl | 61 +++++++-- .../Runtime/Material/Lit/SimpleLit.hlsl | 116 ++++++++++++++++++ 2 files changed, 168 insertions(+), 9 deletions(-) diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/SimpleLightLoop.hlsl b/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/SimpleLightLoop.hlsl index db6fe8130a9..71f64f8e6f7 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/SimpleLightLoop.hlsl +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/SimpleLightLoop.hlsl @@ -93,17 +93,62 @@ void SimpleLightLoop( float3 V, PositionInputs posInput, PreLightData preLightDa } } } +//Note: We don't enable area lights yet because there are some issues with punctual light attenuation intensity for simple area lights +#if 0 // We don't evaluate area lights in simple lit mode + if (featureFlags & LIGHTFEATUREFLAGS_AREA) + { + uint lightCount, lightStart; + + #ifdef LIGHTLOOP_TILE_PASS + GetCountAndStart(posInput, LIGHTCATEGORY_AREA, lightStart, lightCount); + #else + lightCount = _AreaLightCount; + lightStart = _PunctualLightCount; + #endif + + // COMPILER BEHAVIOR WARNING! + // If rectangle lights are before line lights, the compiler will duplicate light matrices in VGPR because they are used differently between the two types of lights. + // By keeping line lights first we avoid this behavior and save substantial register pressure. + // TODO: This is based on the current Lit.shader and can be different for any other way of implementing area lights, how to be generic and ensure performance ? + + if (lightCount > 0) + { + i = 0; + + uint last = lightCount - 1; + LightData lightData = FetchLight(lightStart, i); + + while (i <= last && lightData.lightType == GPULIGHTTYPE_LINE) + { + lightData.lightType = GPULIGHTTYPE_LINE; // Enforce constant propagation - // Define macro for a better understanding of the loop - // TODO: this code is now much harder to understand... -#define EVALUATE_BSDF_ENV_SKY(envLightData, TYPE, type) \ - IndirectLighting lighting = EvaluateBSDF_Env(context, V, posInput, preLightData, envLightData, bsdfData, envLightData.influenceShapeType, MERGE_NAME(GPUIMAGEBASEDLIGHTINGTYPE_, TYPE), MERGE_NAME(type, HierarchyWeight)); \ - AccumulateIndirectLighting(lighting, aggregateLighting); + if (IsMatchingLightLayer(lightData.lightLayers, builtinData.renderingLayers)) + { + DirectLighting lighting = SimpleEvaluateBSDF_Area(context, V, posInput, preLightData, lightData, bsdfData, builtinData); + AccumulateDirectLighting(lighting, aggregateLighting); + } + + lightData = FetchLight(lightStart, min(++i, last)); + } + + while (i <= last) // GPULIGHTTYPE_RECTANGLE + { + lightData.lightType = GPULIGHTTYPE_RECTANGLE; // Enforce constant propagation + + if (IsMatchingLightLayer(lightData.lightLayers, builtinData.renderingLayers)) + { + DirectLighting lighting = SimpleEvaluateBSDF_Area(context, V, posInput, preLightData, lightData, bsdfData, builtinData); + AccumulateDirectLighting(lighting, aggregateLighting); + } + + lightData = FetchLight(lightStart, min(++i, last)); + } + } + } -// Environment cubemap test lightlayers, sky don't test it -#define EVALUATE_BSDF_ENV(envLightData, TYPE, type) if (IsMatchingLightLayer(envLightData.lightLayers, builtinData.renderingLayers)) { EVALUATE_BSDF_ENV_SKY(envLightData, TYPE, type) } +#endif // Area light disabled #if HDRP_ENABLE_ENV_LIGHT // First loop iteration @@ -162,8 +207,6 @@ void SimpleLightLoop( float3 V, PositionInputs posInput, PreLightData preLightDa } } #endif // HDRP_ENABLE_ENV_LIGHT -#undef EVALUATE_BSDF_ENV -#undef EVALUATE_BSDF_ENV_SKY // Also Apply indiret diffuse (GI) // PostEvaluateBSDF will perform any operation wanted by the material and sum everything into diffuseLighting and specularLighting diff --git a/com.unity.render-pipelines.high-definition/Runtime/Material/Lit/SimpleLit.hlsl b/com.unity.render-pipelines.high-definition/Runtime/Material/Lit/SimpleLit.hlsl index 14284d35020..163c84cdd16 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Material/Lit/SimpleLit.hlsl +++ b/com.unity.render-pipelines.high-definition/Runtime/Material/Lit/SimpleLit.hlsl @@ -402,6 +402,122 @@ DirectLighting SimpleEvaluateBSDF_Punctual(LightLoopContext lightLoopContext, return lighting; } +//----------------------------------------------------------------------------- +// EvaluateBSDF_Area +// ---------------------------------------------------------------------------- + +// Area light axis aligned sdf +float AALineSDF_Sq(float3 p, float3 right) +{ + float3 d = max(abs(p) - right, 0.0); + return dot(d, d); +} + +float AARectSDF_Sq(float3 p, float3 size) +{ + float3 f = max(0, abs(p) - size); + return dot(f, f); +} + +float AASDF_Sq(float3 p, LightData lightData) +{ + switch (lightData.lightType) + { + case GPULIGHTTYPE_LINE: + return AALineSDF_Sq(p, float3(lightData.size.x * 0.5, 0, 0)); + case GPULIGHTTYPE_RECTANGLE: + return AARectSDF_Sq(p, float3(lightData.size.x * 0.5, lightData.size.y * 0.5, 0.0)); + default: + return 1e20; + } +} + +// Get the closest point on the light from a given direction +float3 ConstructLightPosition(float3 positionLS, float3 normalLS, LightData lightData, out float lightDistance) +{ + lightDistance = sqrt(AASDF_Sq(positionLS, lightData)); + float3 p = positionLS + normalLS * lightDistance; + float distSq = AASDF_Sq(p, lightData); + float2 delta = float2(0.01, 0); + + // light direction from sdf normal approximation + float3 lightDirection = normalize(float3( + distSq - AASDF_Sq(p - delta.xyy, lightData), + distSq - AASDF_Sq(p - delta.yxy, lightData), + distSq - AASDF_Sq(p - delta.yyx, lightData) + )); + + return p - lightDirection * sqrt(distSq); +} + +// limit the attenation distance at 5cm to avoid super-bright spots on particles +#define AREA_LIGHT_ATTENUATION_THRESHOLD 0.05 + +float SimpleSmoothPunctualLightAttenuation(float4 distances, float rangeAttenuationScale, float rangeAttenuationBias) +{ + float distSq = distances.y; + float distRcp = distances.z; + + float attenuation = min(distRcp, 1.0 / AREA_LIGHT_ATTENUATION_THRESHOLD); + attenuation *= DistanceWindowing(distSq, rangeAttenuationScale, rangeAttenuationBias); + + return Sq(attenuation); +} + +float SimpleEvalutePunctualLightAttenuation(LightData lightData, float lightDistance) +{ + float dist = lightDistance; + float distSq = Sq(dist); + float distRcp = rcp(dist); + float scale = lightData.rangeAttenuationScale; + float4 distances = float4(dist, distSq, distRcp, 1.0); + + if (lightData.lightType == GPULIGHTTYPE_RECTANGLE) + scale = rcp(lightData.range * lightData.range); // For area lights rangeAttenuation is not computed so we do it here + + return SimpleSmoothPunctualLightAttenuation(distances, scale, 1.0); +} + +// Note: Specular is not supported for simple lit area lights +DirectLighting SimpleEvaluateBSDF_Area(LightLoopContext lightLoopContext, + float3 V, PositionInputs posInput, + PreLightData preLightData, LightData lightData, + BSDFData bsdfData, BuiltinData builtinData) +{ + DirectLighting lighting; + ZERO_INITIALIZE(DirectLighting, lighting); + + float3x3 lightToWorld = float3x3(lightData.right, lightData.up, lightData.forward); + + float3 positionLS = mul(lightToWorld, posInput.positionWS - lightData.positionRWS); + float3 normalLS = mul(lightToWorld, bsdfData.normalWS); + + float lightDistance; + float3 lightPosition = ConstructLightPosition(positionLS, normalLS, lightData, lightDistance); + float3 lightToSample = positionLS - lightPosition; + + float intensity = SimpleEvalutePunctualLightAttenuation(lightData, lightDistance); + + if (intensity == 0.0) + return lighting; + + lightData.diffuseDimmer *= intensity; + + lighting.diffuse = Lambert() * lightData.diffuseDimmer; + + // Smooth the rectangle hard cut + if (lightData.lightType == GPULIGHTTYPE_RECTANGLE) + { + lighting.diffuse *= saturate(positionLS.z); + } + + float NdotL = saturate(dot(normalLS, normalize(-lightToSample))); // no wrap lighting because it causes light leaking + + lighting.diffuse *= NdotL * lightData.color; + + return lighting; +} + //----------------------------------------------------------------------------- // EvaluateBSDF_Env // ---------------------------------------------------------------------------- From d89021b79462d24b7d31df0366da6576959d87b7 Mon Sep 17 00:00:00 2001 From: Antoine Lelievre Date: Wed, 31 Oct 2018 11:46:09 +0100 Subject: [PATCH 2/2] Add comments to simple area light evaluation --- .../Runtime/Material/Lit/SimpleLit.hlsl | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/com.unity.render-pipelines.high-definition/Runtime/Material/Lit/SimpleLit.hlsl b/com.unity.render-pipelines.high-definition/Runtime/Material/Lit/SimpleLit.hlsl index 163c84cdd16..21ff295f62e 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Material/Lit/SimpleLit.hlsl +++ b/com.unity.render-pipelines.high-definition/Runtime/Material/Lit/SimpleLit.hlsl @@ -406,7 +406,7 @@ DirectLighting SimpleEvaluateBSDF_Punctual(LightLoopContext lightLoopContext, // EvaluateBSDF_Area // ---------------------------------------------------------------------------- -// Area light axis aligned sdf +// Area light squared axis aligned SDF float AALineSDF_Sq(float3 p, float3 right) { float3 d = max(abs(p) - right, 0.0); @@ -435,24 +435,33 @@ float AASDF_Sq(float3 p, LightData lightData) // Get the closest point on the light from a given direction float3 ConstructLightPosition(float3 positionLS, float3 normalLS, LightData lightData, out float lightDistance) { + // Normally to find the closest point from an SDF to a ray we would have to raymarch + // in the direction of the normal and stops when we hit the light or when the distance + // of the last step is smallter than our current. But this is expansive so instead + // we only evaluate one point on the normal ray at the initial light distance + lightDistance = sqrt(AASDF_Sq(positionLS, lightData)); float3 p = positionLS + normalLS * lightDistance; + + // Then we reconstruct the light normal from the SDF, it normally takes 6 sample of the SDF + // to have a correct result but it's too expansive so we approximate this with only 4 float distSq = AASDF_Sq(p, lightData); float2 delta = float2(0.01, 0); - - // light direction from sdf normal approximation float3 lightDirection = normalize(float3( distSq - AASDF_Sq(p - delta.xyy, lightData), distSq - AASDF_Sq(p - delta.yxy, lightData), distSq - AASDF_Sq(p - delta.yyx, lightData) )); + // Finally create the point on the light using the direction of the light return p - lightDirection * sqrt(distSq); } // limit the attenation distance at 5cm to avoid super-bright spots on particles #define AREA_LIGHT_ATTENUATION_THRESHOLD 0.05 +// Simple version of PunctualLightAttenuation, without AngleAttenuation and with a reduced max intensity to avoid +// Super bright spots when an object crosses the area light float SimpleSmoothPunctualLightAttenuation(float4 distances, float rangeAttenuationScale, float rangeAttenuationBias) { float distSq = distances.y; @@ -473,12 +482,14 @@ float SimpleEvalutePunctualLightAttenuation(LightData lightData, float lightDist float4 distances = float4(dist, distSq, distRcp, 1.0); if (lightData.lightType == GPULIGHTTYPE_RECTANGLE) - scale = rcp(lightData.range * lightData.range); // For area lights rangeAttenuation is not computed so we do it here + scale = rcp(lightData.range * lightData.range); // For rectangle lights rangeAttenuation is not computed so we do it here return SimpleSmoothPunctualLightAttenuation(distances, scale, 1.0); } // Note: Specular is not supported for simple lit area lights +// Simplified area lighting model based of axis aligned SDF, since they are cheap to evaluate +// and give the distance to the light so we can compute the attenuation DirectLighting SimpleEvaluateBSDF_Area(LightLoopContext lightLoopContext, float3 V, PositionInputs posInput, PreLightData preLightData, LightData lightData, @@ -489,13 +500,17 @@ DirectLighting SimpleEvaluateBSDF_Area(LightLoopContext lightLoopContext, float3x3 lightToWorld = float3x3(lightData.right, lightData.up, lightData.forward); + // Transform the position and normal (for light position computing) into light space so we can use axis aligned SDFs float3 positionLS = mul(lightToWorld, posInput.positionWS - lightData.positionRWS); float3 normalLS = mul(lightToWorld, bsdfData.normalWS); + // We find the point on the light that is the closest to the normal ray, so when we do the N dot L + // it automaticallly matches the shape of the area light float lightDistance; float3 lightPosition = ConstructLightPosition(positionLS, normalLS, lightData, lightDistance); float3 lightToSample = positionLS - lightPosition; + // Compute a punctual attenuation from the light distance float intensity = SimpleEvalutePunctualLightAttenuation(lightData, lightDistance); if (intensity == 0.0) @@ -505,7 +520,7 @@ DirectLighting SimpleEvaluateBSDF_Area(LightLoopContext lightLoopContext, lighting.diffuse = Lambert() * lightData.diffuseDimmer; - // Smooth the rectangle hard cut + // Smooth the rectangle hard cut and remove the -z part of the rectangle light if (lightData.lightType == GPULIGHTTYPE_RECTANGLE) { lighting.diffuse *= saturate(positionLS.z);