@@ -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