Skip to content

Commit 4a98acd

Browse files
committed
Implement SSS transmittance for punctual and directional lights
1 parent c96f23f commit 4a98acd

5 files changed

Lines changed: 132 additions & 38 deletions

File tree

Assets/ScriptableRenderLoop/HDRenderPipeline/HDRenderPipeline.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ void Resize(Camera camera)
408408
}
409409
}
410410

411-
public void PushGlobalParams(HDCamera hdCamera, ScriptableRenderContext renderContext)
411+
public void PushGlobalParams(HDCamera hdCamera, ScriptableRenderContext renderContext, SubsurfaceScatteringParameters sssParameters)
412412
{
413413
if (m_SkyManager.IsSkyValid())
414414
{
@@ -420,6 +420,10 @@ public void PushGlobalParams(HDCamera hdCamera, ScriptableRenderContext renderCo
420420
Shader.SetGlobalInt("_EnvLightSkyEnabled", 0);
421421
}
422422

423+
// Broadcast SSS parameters to all shaders.
424+
Shader.SetGlobalInt("_TransmittanceFlags", sssParameters.transmittanceFlags);
425+
Shader.SetGlobalVectorArray("_HalfRcpVariancesAndLerpWeights", sssParameters.halfRcpVariancesAndLerpWeights);
426+
423427
var cmd = new CommandBuffer {name = "Push Global Parameters"};
424428

425429
cmd.SetGlobalVector("_ScreenSize", hdCamera.screenSize);
@@ -530,7 +534,7 @@ public override void Render(ScriptableRenderContext renderContext, Camera[] came
530534
}
531535
}
532536

533-
PushGlobalParams(hdCamera, renderContext);
537+
PushGlobalParams(hdCamera, renderContext, m_Owner.sssParameters);
534538

535539
// Caution: We require sun light here as some sky use the sun light to render, mean UpdateSkyEnvironment
536540
// must be call after BuildGPULightLists.

Assets/ScriptableRenderLoop/HDRenderPipeline/Lighting/TilePass/TilePass.hlsl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ struct LightLoopContext
9494
// Shadow sampling function
9595
// ----------------------------------------------------------------------------
9696

97-
float GetPunctualShadowAttenuation(LightLoopContext lightLoopContext, uint lightType, float3 positionWS, int index, float3 L, float2 unPositionSS)
97+
float GetPunctualShadowAttenuation(LightLoopContext lightLoopContext, uint lightType, float3 positionWS, int index, float3 L, float2 unPositionSS, float bias)
9898
{
9999
int faceIndex = 0;
100100
if (lightType == GPULIGHTTYPE_POINT)
@@ -107,8 +107,8 @@ float GetPunctualShadowAttenuation(LightLoopContext lightLoopContext, uint light
107107
// Note: scale and bias of shadow atlas are included in ShadowTransform but could be apply here.
108108
float4 positionTXS = mul(float4(positionWS, 1.0), shadowData.worldToShadow);
109109
positionTXS.xyz /= positionTXS.w;
110-
// positionTXS.z -= shadowData.bias; // Apply a linear bias
111-
positionTXS.z -= 0.001;
110+
// positionTXS.z -= shadowData.bias;
111+
positionTXS.z -= (bias + 0.001); // Apply a linear bias
112112

113113
#if UNITY_REVERSED_Z
114114
positionTXS.z = 1.0 - positionTXS.z;
@@ -145,7 +145,7 @@ int GetSplitSphereIndexForDirshadows(float3 positionWS, float4 dirShadowSplitSph
145145
return int(4.0 - dot(weights, float4(4.0, 3.0, 2.0, 1.0)));
146146
}
147147

148-
float GetDirectionalShadowAttenuation(LightLoopContext lightLoopContext, float3 positionWS, int index, float3 L, float2 unPositionSS)
148+
float GetDirectionalShadowAttenuation(LightLoopContext lightLoopContext, float3 positionWS, int index, float3 L, float2 unPositionSS, float bias)
149149
{
150150
// Note Index is 0 for now, but else we need to provide the correct index in _DirShadowSplitSpheres and _ShadowDatas
151151
int shadowSplitIndex = GetSplitSphereIndexForDirshadows(positionWS, _DirShadowSplitSpheres);
@@ -157,8 +157,8 @@ float GetDirectionalShadowAttenuation(LightLoopContext lightLoopContext, float3
157157
// Note: scale and bias of shadow atlas are included in ShadowTransform but could be apply here.
158158
float4 positionTXS = mul(float4(positionWS, 1.0), shadowData.worldToShadow);
159159
positionTXS.xyz /= positionTXS.w;
160-
// positionTXS.z -= shadowData.bias; // Apply a linear bias
161-
positionTXS.z -= 0.003;
160+
// positionTXS.z -= shadowData.bias;
161+
positionTXS.z -= (bias + 0.003); // Apply a linear bias
162162

163163
#if UNITY_REVERSED_Z
164164
positionTXS.z = 1.0 - positionTXS.z;

Assets/ScriptableRenderLoop/HDRenderPipeline/Material/Lit/Lit.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,11 @@ public struct BSDFData
9898
// fold into fresnel0
9999

100100
// SSS
101-
public float subsurfaceRadius;
102-
public float thickness;
103-
public int subsurfaceProfile;
101+
public float subsurfaceRadius;
102+
public float thickness;
103+
public int subsurfaceProfile;
104+
public bool enableTransmittance;
105+
public Vector3 transmittance;
104106

105107
// Clearcoat
106108
public Vector3 coatNormalWS;

Assets/ScriptableRenderLoop/HDRenderPipeline/Material/Lit/Lit.cs.hlsl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,10 @@
5151
#define DEBUGVIEW_LIT_BSDFDATA_SUBSURFACE_RADIUS (1042)
5252
#define DEBUGVIEW_LIT_BSDFDATA_THICKNESS (1043)
5353
#define DEBUGVIEW_LIT_BSDFDATA_SUBSURFACE_PROFILE (1044)
54-
#define DEBUGVIEW_LIT_BSDFDATA_COAT_NORMAL_WS (1045)
55-
#define DEBUGVIEW_LIT_BSDFDATA_COAT_ROUGHNESS (1046)
54+
#define DEBUGVIEW_LIT_BSDFDATA_ENABLE_TRANSMITTANCE (1045)
55+
#define DEBUGVIEW_LIT_BSDFDATA_TRANSMITTANCE (1046)
56+
#define DEBUGVIEW_LIT_BSDFDATA_COAT_NORMAL_WS (1047)
57+
#define DEBUGVIEW_LIT_BSDFDATA_COAT_ROUGHNESS (1048)
5658

5759
//
5860
// UnityEngine.Experimental.Rendering.HDPipeline.Lit.GBufferMaterial: static fields
@@ -100,6 +102,8 @@ struct BSDFData
100102
float subsurfaceRadius;
101103
float thickness;
102104
int subsurfaceProfile;
105+
bool enableTransmittance;
106+
float3 transmittance;
103107
float3 coatNormalWS;
104108
float coatRoughness;
105109
};

Assets/ScriptableRenderLoop/HDRenderPipeline/Material/Lit/Lit.hlsl

Lines changed: 109 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ SAMPLER2D(sampler_LtcData);
5151
#define LTC_DISNEY_DIFFUSE_MATRIX_INDEX 1 // RGBA
5252
#define LTC_MULTI_GGX_FRESNEL_DISNEY_DIFFUSE_INDEX 2 // RGB, A unused
5353

54+
// SSS parameters
55+
#define N_PROFILES 8
56+
uint _TransmittanceFlags; // One bit per profile; 1 = enabled
57+
float4 _HalfRcpVariancesAndLerpWeights[N_PROFILES][2]; // 2x Gaussians per color channel, A is the the associated interpolation weight
58+
5459
//-----------------------------------------------------------------------------
5560
// Helper functions/variable specific to this material
5661
//-----------------------------------------------------------------------------
@@ -107,6 +112,24 @@ void ApplyDebugToBSDFData(inout BSDFData bsdfData)
107112

108113
#endif
109114
}
115+
116+
// Evaluates transmittance for a linear combination of two normalized 2D Gaussians.
117+
// Computes results for each color channel separately.
118+
// Ref: Real-Time Realistic Skin Translucency (2010), equation 9 (modified).
119+
float3 ComputeTransmittance(float3 halfRcpVariance1, float lerpWeight1,
120+
float3 halfRcpVariance2, float lerpWeight2,
121+
float thickness, float radiusScale)
122+
{
123+
// To be consistent with SSS, we modify the thickness instead of scaling the radius of the profile.
124+
thickness /= radiusScale;
125+
126+
float t2 = thickness * thickness;
127+
128+
// TODO: 6 exponentials is kind of expensive... Should we use a LUT instead?
129+
// lerp(exp(-t2 * halfRcpVariance1), exp(-t2 * halfRcpVariance2), lerpWeight2)
130+
return exp(-t2 * halfRcpVariance1) * lerpWeight1 + exp(-t2 * halfRcpVariance2) * lerpWeight2;
131+
}
132+
110133
//-----------------------------------------------------------------------------
111134
// conversion function for forward
112135
//-----------------------------------------------------------------------------
@@ -138,9 +161,18 @@ BSDFData ConvertSurfaceDataToBSDFData(SurfaceData surfaceData)
138161
{
139162
bsdfData.diffuseColor = surfaceData.baseColor;
140163
bsdfData.fresnel0 = 0.028; // TODO take from subsurfaceProfile
141-
bsdfData.subsurfaceRadius = surfaceData.subsurfaceRadius;
142-
bsdfData.thickness = surfaceData.thickness;
164+
bsdfData.subsurfaceRadius = surfaceData.subsurfaceRadius * 0.01;
165+
bsdfData.thickness = surfaceData.thickness * 0.01;
143166
bsdfData.subsurfaceProfile = surfaceData.subsurfaceProfile;
167+
bsdfData.enableTransmittance = (1 << bsdfData.subsurfaceProfile) & _TransmittanceFlags;
168+
if (bsdfData.enableTransmittance)
169+
{
170+
bsdfData.transmittance = ComputeTransmittance(_HalfRcpVariancesAndLerpWeights[bsdfData.subsurfaceProfile][0].xyz,
171+
_HalfRcpVariancesAndLerpWeights[bsdfData.subsurfaceProfile][0].w,
172+
_HalfRcpVariancesAndLerpWeights[bsdfData.subsurfaceProfile][1].xyz,
173+
_HalfRcpVariancesAndLerpWeights[bsdfData.subsurfaceProfile][1].w,
174+
bsdfData.thickness, bsdfData.subsurfaceRadius);
175+
}
144176
}
145177
else if (bsdfData.materialId == MATERIALID_LIT_CLEAR_COAT)
146178
{
@@ -311,9 +343,18 @@ void DecodeFromGBuffer(
311343
{
312344
bsdfData.diffuseColor = baseColor;
313345
bsdfData.fresnel0 = 0.028; // TODO take from subsurfaceProfile
314-
bsdfData.subsurfaceRadius = inGBuffer2.r;
315-
bsdfData.thickness = inGBuffer2.g;
346+
bsdfData.subsurfaceRadius = inGBuffer2.r * 0.01;
347+
bsdfData.thickness = inGBuffer2.g * 0.01;
316348
bsdfData.subsurfaceProfile = inGBuffer2.a * 8.0;
349+
bsdfData.enableTransmittance = (1 << bsdfData.subsurfaceProfile) & _TransmittanceFlags;
350+
if (bsdfData.enableTransmittance)
351+
{
352+
bsdfData.transmittance = ComputeTransmittance(_HalfRcpVariancesAndLerpWeights[bsdfData.subsurfaceProfile][0].xyz,
353+
_HalfRcpVariancesAndLerpWeights[bsdfData.subsurfaceProfile][0].w,
354+
_HalfRcpVariancesAndLerpWeights[bsdfData.subsurfaceProfile][1].xyz,
355+
_HalfRcpVariancesAndLerpWeights[bsdfData.subsurfaceProfile][1].w,
356+
bsdfData.thickness, bsdfData.subsurfaceRadius);
357+
}
317358
}
318359
else if (bsdfData.materialId == MATERIALID_LIT_CLEAR_COAT)
319360
{
@@ -659,15 +700,15 @@ void EvaluateBSDF_Directional( LightLoopContext lightLoopContext,
659700
float3 L = -lightData.forward; // Lights are pointing backward in Unity
660701
float illuminance = saturate(dot(bsdfData.normalWS, L));
661702

662-
diffuseLighting = float3(0.0, 0.0, 0.0);
663-
specularLighting = float3(0.0, 0.0, 0.0);
664-
float3 cookieColor = float3(1.0, 1.0, 1.0);
703+
diffuseLighting = float3(0.0, 0.0, 0.0);
704+
specularLighting = float3(0.0, 0.0, 0.0);
705+
float4 cookie = float4(1.0, 1.0, 1.0, 1.0);
665706

666707
[branch] if (lightData.shadowIndex >= 0 && illuminance > 0.0)
667708
{
668-
float shadowAttenuation = GetDirectionalShadowAttenuation(lightLoopContext, positionWS, lightData.shadowIndex, L, posInput.unPositionSS);
709+
float shadow = GetDirectionalShadowAttenuation(lightLoopContext, positionWS, lightData.shadowIndex, L, posInput.unPositionSS, 0);
669710

670-
illuminance *= shadowAttenuation;
711+
illuminance *= shadow;
671712
}
672713

673714
[branch] if (lightData.cookieIndex >= 0 && illuminance > 0.0)
@@ -685,20 +726,41 @@ void EvaluateBSDF_Directional( LightLoopContext lightLoopContext,
685726
coord = coord * 0.5 + 0.5;
686727

687728
// Tile the texture if the 'repeat' wrap mode is enabled.
688-
if (lightData.tileCookie)
729+
if (lightData.tileCookie)
689730
coord = frac(coord);
690731

691-
float4 cookie = SampleCookie2D(lightLoopContext, coord, lightData.cookieIndex);
732+
cookie = SampleCookie2D(lightLoopContext, coord, lightData.cookieIndex);
692733

693-
cookieColor = cookie.rgb;
694734
illuminance *= cookie.a;
695735
}
696736

697737
[branch] if (illuminance > 0.0)
698738
{
699739
BSDF(V, L, positionWS, preLightData, bsdfData, diffuseLighting, specularLighting);
700-
diffuseLighting *= (cookieColor * lightData.color) * (illuminance * lightData.diffuseScale);
701-
specularLighting *= (cookieColor * lightData.color) * (illuminance * lightData.specularScale);
740+
diffuseLighting *= (cookie.rgb * lightData.color) * (illuminance * lightData.diffuseScale);
741+
specularLighting *= (cookie.rgb * lightData.color) * (illuminance * lightData.specularScale);
742+
}
743+
744+
[branch] if (bsdfData.enableTransmittance)
745+
{
746+
// Reverse the normal.
747+
illuminance = saturate(dot(-bsdfData.normalWS, L));
748+
749+
[branch] if (lightData.shadowIndex >= 0 && illuminance > 0.0)
750+
{
751+
float shadow = GetDirectionalShadowAttenuation(lightLoopContext, positionWS, lightData.shadowIndex, L, posInput.unPositionSS, bsdfData.thickness);
752+
illuminance *= shadow;
753+
}
754+
755+
illuminance *= cookie.a;
756+
757+
// The difference between the Disney Diffuse and the Lambertian BRDF for transmittance is negligible.
758+
float3 backLight = (cookie.rgb * lightData.color) * (illuminance * lightData.diffuseScale * Lambert());
759+
// TODO: multiplication by 'diffuseColor' and 'transmittance' is the same for each light.
760+
float3 transmittedLight = backLight * bsdfData.diffuseColor * bsdfData.transmittance;
761+
762+
// We use diffuse lighting for accumulation since it is going to be blurred during the SSS pass.
763+
diffuseLighting += transmittedLight;
702764
}
703765
}
704766

@@ -726,9 +788,9 @@ void EvaluateBSDF_Punctual( LightLoopContext lightLoopContext,
726788
attenuation *= GetAngleAttenuation(L, -lightData.forward, lightData.angleScale, lightData.angleOffset);
727789
float illuminance = saturate(dot(bsdfData.normalWS, L)) * attenuation;
728790

729-
diffuseLighting = float3(0.0, 0.0, 0.0);
730-
specularLighting = float3(0.0, 0.0, 0.0);
731-
float3 cookieColor = float3(1.0, 1.0, 1.0);
791+
diffuseLighting = float3(0.0, 0.0, 0.0);
792+
specularLighting = float3(0.0, 0.0, 0.0);
793+
float4 cookie = float4(1.0, 1.0, 1.0, 1.0);
732794

733795
// TODO: measure impact of having all these dynamic branch here and the gain (or not) of testing illuminace > 0
734796

@@ -742,10 +804,10 @@ void EvaluateBSDF_Punctual( LightLoopContext lightLoopContext,
742804
[branch] if (lightData.shadowIndex >= 0 && illuminance > 0.0)
743805
{
744806
float3 offset = float3(0.0, 0.0, 0.0); // GetShadowPosOffset(nDotL, normal);
745-
float shadowAttenuation = GetPunctualShadowAttenuation(lightLoopContext, lightData.lightType, positionWS + offset, lightData.shadowIndex, L, posInput.unPositionSS);
746-
shadowAttenuation = lerp(1.0, shadowAttenuation, lightData.shadowDimmer);
807+
float shadow = GetPunctualShadowAttenuation(lightLoopContext, lightData.lightType, positionWS + offset, lightData.shadowIndex, L, posInput.unPositionSS, 0);
808+
shadow = lerp(1.0, shadow, lightData.shadowDimmer);
747809

748-
illuminance *= shadowAttenuation;
810+
illuminance *= shadow;
749811
}
750812

751813
[branch] if (lightData.cookieIndex >= 0 && illuminance > 0.0)
@@ -756,8 +818,6 @@ void EvaluateBSDF_Punctual( LightLoopContext lightLoopContext,
756818
// We perform the negation because lights are oriented backwards (-Z).
757819
float3 coord = mul(-L, transpose(lightToWorld));
758820

759-
float4 cookie;
760-
761821
[branch] if (lightData.lightType == GPULIGHTTYPE_SPOT)
762822
{
763823
// Perform the perspective projection of the hemisphere onto the disk.
@@ -777,16 +837,40 @@ void EvaluateBSDF_Punctual( LightLoopContext lightLoopContext,
777837
cookie = SampleCookieCube(lightLoopContext, coord, lightData.cookieIndex);
778838
}
779839

780-
cookieColor = cookie.rgb;
781840
illuminance *= cookie.a;
782841
}
783842

784843
[branch] if (illuminance > 0.0)
785844
{
786845
BSDF(V, L, positionWS, preLightData, bsdfData, diffuseLighting, specularLighting);
787846

788-
diffuseLighting *= (cookieColor * lightData.color) * (illuminance * lightData.diffuseScale);
789-
specularLighting *= (cookieColor * lightData.color) * (illuminance * lightData.specularScale);
847+
diffuseLighting *= (cookie.rgb * lightData.color) * (illuminance * lightData.diffuseScale);
848+
specularLighting *= (cookie.rgb * lightData.color) * (illuminance * lightData.specularScale);
849+
}
850+
851+
[branch] if (bsdfData.enableTransmittance)
852+
{
853+
// Reverse the normal.
854+
illuminance = saturate(dot(-bsdfData.normalWS, L)) * attenuation;
855+
856+
[branch] if (lightData.shadowIndex >= 0 && illuminance > 0.0)
857+
{
858+
float3 offset = float3(0.0, 0.0, 0.0); // GetShadowPosOffset(nDotL, normal);
859+
float shadow = GetPunctualShadowAttenuation(lightLoopContext, lightData.lightType, positionWS + offset, lightData.shadowIndex, L, posInput.unPositionSS, bsdfData.thickness);
860+
shadow = lerp(1.0, shadow, lightData.shadowDimmer);
861+
862+
illuminance *= shadow;
863+
}
864+
865+
illuminance *= cookie.a;
866+
867+
// The difference between the Disney Diffuse and the Lambertian BRDF for transmittance is negligible.
868+
float3 backLight = (cookie.rgb * lightData.color) * (illuminance * lightData.diffuseScale * Lambert());
869+
// TODO: multiplication by 'diffuseColor' and 'transmittance' is the same for each light.
870+
float3 transmittedLight = backLight * bsdfData.diffuseColor * bsdfData.transmittance;
871+
872+
// We use diffuse lighting for accumulation since it is going to be blurred during the SSS pass.
873+
diffuseLighting += transmittedLight;
790874
}
791875
}
792876

0 commit comments

Comments
 (0)