一、选择对角色光照贡献最大的一盏灯作为主灯,用于主基调的亮光面和阴影面,其他灯光对亮光面和被光面进行补光(增亮)
如果对角色模型,在所有光源里面进行遍历,找到对角色光照贡献最强的一盏光作为主光,那么当角色移动,或者有一盏灯亮度逐渐增大时,取代之前的主光,容易产生阴影闪烁的问题
例如:
主灯会硬切
如果当前主灯 A 强度是 10,副灯 B 慢慢变到 10.1,材质里如果直接选最大值,主灯会瞬间从 A 切到 B。
那么现在问题就是如何平和过渡这个主光的切换
二、根据每盏灯对角色脸部光照贡献程度,取一个带权重的方向作为主光,且只有一个主光没有副光
做法就是每个方向都进行带权加到一个方向向量上,比如说:我这里离角色近,且灯光亮度高,那么我就对最后的主方向给的贡献就更大,这样带权累加过后进行归一化处理
这样的好处:
我就不需要再考虑什么主光源之间的切换问题,不会有单体上的主光源的概念,只会灯光根据对角色脸部的光照贡献对最终方向进行加权
- 多个方向会生成不存在的阴影形状
比如左前方一盏灯、右前方一盏灯。
如果你把两个方向加权平均,得到的是"正前方光"。但真实卡渲里这不一定等价,因为 SDF 图集里"正前方阴影"是单独画出来的美术结果,不是左右两张图数学平均出来的。
三、我选择一个灯光作为主光并一直保持不变(例如选一盏方向光)
做法就是选一个灯光作为主光,不管其他灯光对角色脸部亮度的贡献是否大于选择的这盏主光,都不会代替人为选择的这个主光,其他的灯光对脸部进行补光
好处:不会有不正常的阴影或者主光发生变化产生的阴影突变的问题
坏处:加入我突然进屋了,那么按照道理这个方向光对角色的贡献已经失效了,我应该选一个新主光,那么这个时候,假如选择了新主光,那么阴影很容易造成阴影突变的问题,这其实本质又是主光发生突变导致的阴影突变的问题
四、我根据光照对脸部的光照贡献选最强光为主光,其余光如果能够照到主光的阴影区,则增亮阴影区,若光照贡献相同,则脸部两盏灯照的是一样亮的
特别注意一点,就是照亮阴影不能只照亮一部分,那样会特别脏,如果其余光照亮了主光阴影的全部范围,这样才对阴影提亮进行贡献,否则不贡献
如果只是这样去限制,会产生一个情况,就是我现在有一个主光和多个副光,我任一一个副光对脸部的光照着色不超过主光,但是我可能多个副光对主光阴影的贡献会超过主光这个情况,那我该如何去限制?
还有一个问题,我们刚才只讨论了标题上面是根据一盏灯在角色左前方,一盏灯在右前方,互相照亮对方的阴影,提亮彼此的阴影的情况
我们还有一个情况没有考虑到,那就是两盏灯同侧(同为角色向前左前方,同为角色向前右前方)但是角度不同,我就不应该存在完全照亮对方阴影的可能,所以这个时候我尝试用smooth union平滑融合角度比较小的阴影,使得当同一侧的A主光被同一侧的B副光替代为新主光的时候,有一个很平滑的阴影融合效果
性能优化方面,我希望对脸部光照贡献最强的和第二强的才采样SDF贴图,其余直接根据光照贡献对脸部进行增亮即可,不用采样
还有一个就是当对脸部光照贡献小于某个值的时候,我将剩下的逻辑直接跳过continue不进行下一步更复杂的计算
代码如下:
// M_qita_SDF_3D Custom HLSL
// Key light controls main 3D SDF shadow.
// Top1/Top2 relation drives stable transition.
// Same-side close Top2 smooth-unions with key shadow.
// Opposite-side close Top2 fades key shadow to lit.
// Other fills only brighten, they do not reshape the main shadow.
// Exposure is allowed to output values above 1.0.
#ifndef GM_BASEPASS_FORWARD_LIGHT_ACCESS
#define GM_BASEPASS_FORWARD_LIGHT_ACCESS 0
#endif
#define FACE_FORWARD_AXIS float3(0.0, 1.0, 0.0)
#define FACE_RIGHT_AXIS float3(1.0, 0.0, 0.0)
#define FACE_UP_AXIS float3(0.0, 0.0, 1.0)
#define FACE_CENTER_OFFSET_LOCAL float3(0.0, 0.0, 145.0)
#define SDF_ATLAS_SIZE 9.0
#define SDF_ATLAS_MAX_CELL 8.0
#define SDF_CELL_UV_PADDING 0.008
#define SDF_EDGE_WIDTH 0.08
#define SDF_SHADOW_MIN 0.22
#define SDF_LIGHT_POWER 1.0
#define SDF_NO_SHADOW_VALUE 1.0
#define LOCAL_LIGHT_VISUAL_REFERENCE_INTENSITY 80.0
#define DIRECTIONAL_LIGHT_VISUAL_REFERENCE_INTENSITY 1.0
#define QITA_DIRECTIONAL_VISUAL_INTENSITY_FIX 3.14159265
#define SDF_LOCAL_VISUAL_RESPONSE_SCALE 0.005
#define SDF_LOCAL_KEY_WEIGHT 1.0
#define SDF_DIRECTIONAL_KEY_WEIGHT 1.0
#define SDF_FILL_SHADOW_STRENGTH 1.0
#define SDF_FILL_RATIO_MAX 1.0
#define SDF_FILL_VISUAL_SCALE 1.0
#define SDF_FILL_FACE_GATE_START -0.20
#define SDF_FILL_FACE_GATE_END 0.30
#define SDF_FILL_SHADOW_SIDE_START 0.15
#define SDF_FILL_SHADOW_SIDE_END 0.80
#define SDF_SAME_SIDE_RAW_START -0.08
#define SDF_SAME_SIDE_RAW_END 0.08
#define SDF_SAME_SIDE_CONTRIB_START 0.70
#define SDF_SAME_SIDE_CONTRIB_END 0.95
#define SDF_SAME_SIDE_SMOOTH_UNION_K 0.30
#define SDF_CROSS_SIDE_FADE_START 0.70
#define SDF_CROSS_SIDE_FADE_END 1.0
#define SDF_VISUAL_COLOR_SCALE 0.18
#define SDF_VISUAL_COLOR_MAX 0.65
#define SDF_VISUAL_LIT_START 0.35
#define SDF_VISUAL_LIT_END 1.0
#define SDF_EXPOSURE_SCALE 0.035
#define SDF_EXPOSURE_LIT_START 0.62
#define SDF_EXPOSURE_LIT_END 0.98
#define SDF_EXPOSURE_LIT_POWER 1.40
#define SDF_VERTICAL_BLEND_START 0.04
#define SDF_VERTICAL_BLEND_END 0.30
#define SDF_FRONT_BACK_BLEND_WIDTH 0.10
#define SDF_LR_BLEND_WIDTH 0.04
#define SDF_LR_SIGN_FLIP 1.0
#define QITA_SAFE_NORM3(V) ((V) * rsqrt(max(dot((V), (V)), 0.0001)))
#define QITA_SAMPLE_SDF3D(UV_VALUE) Texture2DSample(SDF3D, SDF3DSampler, UV_VALUE).r
#define QITA_TO_SDF_MASK(SIGNED_SDF_VALUE) pow(saturate(smoothstep(-SDF_EDGE_WIDTH, SDF_EDGE_WIDTH, SIGNED_SDF_VALUE)), SDF_LIGHT_POWER)
#define QITA_SMOOTH_MAX_SIGNED(A_VALUE, B_VALUE, K_VALUE, OUT_VALUE) \
{ \
float _SmoothA = (A_VALUE); \
float _SmoothB = (B_VALUE); \
float _SmoothK = max((K_VALUE), 0.0001); \
float _SmoothH = saturate(0.5 + 0.5 * (_SmoothA - _SmoothB) / _SmoothK); \
OUT_VALUE = clamp(lerp(_SmoothB, _SmoothA, _SmoothH) + _SmoothK * _SmoothH * (1.0 - _SmoothH), -1.0, 1.0); \
}
float4x4 LocalToWorld = DFHackToFloat(GetPrimitiveData(Parameters).LocalToWorld);
float3 ObjectTranslatedWorld = GetObjectTranslatedWorldPosition(Parameters);
float3 FaceForwardWS = QITA_SAFE_NORM3(mul(float4(FACE_FORWARD_AXIS, 0.0), LocalToWorld).xyz);
float3 FaceRightWS = QITA_SAFE_NORM3(mul(float4(FACE_RIGHT_AXIS, 0.0), LocalToWorld).xyz);
float3 FaceUpWS = QITA_SAFE_NORM3(mul(float4(FACE_UP_AXIS, 0.0), LocalToWorld).xyz);
float3 FaceCenterWS = ObjectTranslatedWorld + mul(float4(FACE_CENTER_OFFSET_LOCAL, 0.0), LocalToWorld).xyz;
float2 TexCoord = UV0;
float3 BaseColor = Texture2DSample(ColorTexture, ColorTextureSampler, UV0).rgb;
float KeyScore = 0.0;
float3 KeyLightDir = FaceForwardWS;
float KeyVisualEnergy = 0.0;
float KeyIsDirectional = 0.0;
uint KeyLocalLightIndex = 0;
float SecondScore = 0.0;
float3 SecondLightDir = FaceForwardWS;
float SecondVisualEnergy = 0.0;
float SecondIsDirectional = 0.0;
uint SecondLocalLightIndex = 0;
float KeyMask = 0.0;
float OriginalKeyMask = 0.0;
float FusedKeySignedSDF = SDF_NO_SHADOW_VALUE;
float FillShadowAccum = 0.0;
float CrossSideFadeAccum = 0.0;
float PixelVisualEnergy = 0.0;
#define QITA_PUSH_TOP2(CAND_SCORE, CAND_DIR, CAND_VISUAL, CAND_IS_DIRECTIONAL, CAND_INDEX) \
{ \
float _CandidateScore = (CAND_SCORE); \
if (_CandidateScore > KeyScore) \
{ \
SecondScore = KeyScore; \
SecondLightDir = KeyLightDir; \
SecondVisualEnergy = KeyVisualEnergy; \
SecondIsDirectional = KeyIsDirectional; \
SecondLocalLightIndex = KeyLocalLightIndex; \
KeyScore = _CandidateScore; \
KeyLightDir = (CAND_DIR); \
KeyVisualEnergy = (CAND_VISUAL); \
KeyIsDirectional = (CAND_IS_DIRECTIONAL); \
KeyLocalLightIndex = (CAND_INDEX); \
} \
else if (_CandidateScore > SecondScore) \
{ \
SecondScore = _CandidateScore; \
SecondLightDir = (CAND_DIR); \
SecondVisualEnergy = (CAND_VISUAL); \
SecondIsDirectional = (CAND_IS_DIRECTIONAL); \
SecondLocalLightIndex = (CAND_INDEX); \
} \
}
#define QITA_SAMPLE_ATLAS_BY_FACE_DOTS(DOT_FORWARD_VALUE, SIDE_SIGN_VALUE, DOT_UP_VALUE, OUT_SIGNED_SDF) \
{ \
float _MirrorWeight = smoothstep(-SDF_LR_BLEND_WIDTH, SDF_LR_BLEND_WIDTH, (SIDE_SIGN_VALUE) * SDF_LR_SIGN_FLIP); \
float2 _RawCellUV = saturate(TexCoord); \
float _CellU_A = lerp(SDF_CELL_UV_PADDING, 1.0 - SDF_CELL_UV_PADDING, _RawCellUV.x); \
float _CellU_B = lerp(SDF_CELL_UV_PADDING, 1.0 - SDF_CELL_UV_PADDING, 1.0 - _RawCellUV.x); \
float _CellV = lerp(SDF_CELL_UV_PADDING, 1.0 - SDF_CELL_UV_PADDING, _RawCellUV.y); \
float2 _CellUV_A = float2(_CellU_A, _CellV); \
float2 _CellUV_B = float2(_CellU_B, _CellV); \
float2 _Angle01 = 1.0 - acos(clamp(float2(DOT_FORWARD_VALUE, DOT_UP_VALUE), -1.0, 1.0)) * 0.31830988618; \
float2 _Grid = min(floor(_Angle01 * SDF_ATLAS_MAX_CELL), float2(SDF_ATLAS_MAX_CELL, SDF_ATLAS_MAX_CELL)); \
float2 _Frac = frac(_Angle01 * SDF_ATLAS_MAX_CELL); \
float2 _G00 = _Grid; \
float2 _G10 = min(_Grid + float2(1.0, 0.0), float2(SDF_ATLAS_MAX_CELL, SDF_ATLAS_MAX_CELL)); \
float2 _G01 = min(_Grid + float2(0.0, 1.0), float2(SDF_ATLAS_MAX_CELL, SDF_ATLAS_MAX_CELL)); \
float2 _G11 = min(_Grid + float2(1.0, 1.0), float2(SDF_ATLAS_MAX_CELL, SDF_ATLAS_MAX_CELL)); \
float _S00A = QITA_SAMPLE_SDF3D((_G00 + _CellUV_A) / SDF_ATLAS_SIZE); \
float _S10A = QITA_SAMPLE_SDF3D((_G10 + _CellUV_A) / SDF_ATLAS_SIZE); \
float _S01A = QITA_SAMPLE_SDF3D((_G01 + _CellUV_A) / SDF_ATLAS_SIZE); \
float _S11A = QITA_SAMPLE_SDF3D((_G11 + _CellUV_A) / SDF_ATLAS_SIZE); \
float _SampleA = lerp(lerp(_S00A, _S10A, _Frac.x), lerp(_S01A, _S11A, _Frac.x), _Frac.y); \
float _S00B = QITA_SAMPLE_SDF3D((_G00 + _CellUV_B) / SDF_ATLAS_SIZE); \
float _S10B = QITA_SAMPLE_SDF3D((_G10 + _CellUV_B) / SDF_ATLAS_SIZE); \
float _S01B = QITA_SAMPLE_SDF3D((_G01 + _CellUV_B) / SDF_ATLAS_SIZE); \
float _S11B = QITA_SAMPLE_SDF3D((_G11 + _CellUV_B) / SDF_ATLAS_SIZE); \
float _SampleB = lerp(lerp(_S00B, _S10B, _Frac.x), lerp(_S01B, _S11B, _Frac.x), _Frac.y); \
float _SDFSample = lerp(_SampleA, _SampleB, _MirrorWeight); \
OUT_SIGNED_SDF = clamp((0.5 - _SDFSample) * 2.0, -1.0, 1.0); \
}
#define QITA_SAMPLE_3D_FACE_SDF_SIGNED(LIGHT_DIR_VALUE, OUT_SIGNED_SDF) \
{ \
float3 _LightDir = QITA_SAFE_NORM3(LIGHT_DIR_VALUE); \
float _UpRaw = dot(FaceUpWS, _LightDir); \
float3 _Planar = _LightDir - FaceUpWS * _UpRaw; \
float _HorizontalLen = length(_Planar); \
float3 _PlanarDir = _Planar * rsqrt(max(dot(_Planar, _Planar), 0.0001)); \
float _NoHorizontal = 1.0 - step(0.0001, _HorizontalLen); \
_PlanarDir = lerp(_PlanarDir, FaceForwardWS, _NoHorizontal); \
float _DotForward = dot(FaceForwardWS, _PlanarDir); \
float _SideSign = dot(FaceRightWS, _PlanarDir); \
float _DotUp = -_UpRaw; \
float _NormalSigned = SDF_NO_SHADOW_VALUE; \
QITA_SAMPLE_ATLAS_BY_FACE_DOTS(_DotForward, _SideSign, _DotUp, _NormalSigned); \
float _FrontSign = _DotForward >= 0.0 ? 1.0 : -1.0; \
float _BeforeDotForward = abs(_DotForward); \
float _BeforeSideSign = _SideSign * _FrontSign; \
float _AfterDotForward = -_BeforeDotForward; \
float _AfterSideSign = -_BeforeSideSign; \
float _Before90Signed = SDF_NO_SHADOW_VALUE; \
float _After90Signed = SDF_NO_SHADOW_VALUE; \
QITA_SAMPLE_ATLAS_BY_FACE_DOTS(_BeforeDotForward, _BeforeSideSign, _DotUp, _Before90Signed); \
QITA_SAMPLE_ATLAS_BY_FACE_DOTS(_AfterDotForward, _AfterSideSign, _DotUp, _After90Signed); \
float _Forward3D = dot(FaceForwardWS, _LightDir); \
float _Before90Weight = smoothstep(-SDF_FRONT_BACK_BLEND_WIDTH, SDF_FRONT_BACK_BLEND_WIDTH, _Forward3D); \
float _DualSigned = lerp(_After90Signed, _Before90Signed, _Before90Weight); \
float _VerticalBlend = 1.0 - smoothstep(SDF_VERTICAL_BLEND_START, SDF_VERTICAL_BLEND_END, _HorizontalLen); \
OUT_SIGNED_SDF = lerp(_NormalSigned, _DualSigned, _VerticalBlend); \
}
#if GM_BASEPASS_FORWARD_LIGHT_ACCESS && FEATURE_LEVEL >= FEATURE_LEVEL_SM5
const uint EyeIndex = GM_GetBasePassForwardEyeIndex(Parameters);
const uint GridIndex = GM_GetForwardLightGridIndex(Parameters);
const FCulledLightsGridHeader Header = GetCulledLightsGridHeader(GridIndex);
const uint NumLights = min(Header.NumLights, GetMaxLightsPerCell());
const FDirectionalLightData DirectionalLightData = GetDirectionalLightData();
float3 DirectionalLightDirStored = FaceForwardWS;
float DirectionalVisualEnergyStored = 0.0;
float DirectionalScoreStored = 0.0;
if (DirectionalLightData.HasDirectionalLight != 0)
{
DirectionalLightDirStored = QITA_SAFE_NORM3(DirectionalLightData.DirectionalLightDirection);
float DirectionalRawIntensity = dot(DirectionalLightData.DirectionalLightColor, float3(0.299, 0.587, 0.114));
DirectionalVisualEnergyStored = max((DirectionalRawIntensity * QITA_DIRECTIONAL_VISUAL_INTENSITY_FIX) / max(DIRECTIONAL_LIGHT_VISUAL_REFERENCE_INTENSITY, 0.001), 0.0);
DirectionalScoreStored = DirectionalVisualEnergyStored * SDF_DIRECTIONAL_KEY_WEIGHT;
QITA_PUSH_TOP2(DirectionalScoreStored, DirectionalLightDirStored, DirectionalVisualEnergyStored, 1.0, 0);
}
LOOP
for (uint LightIndex = 0; LightIndex < NumLights; ++LightIndex)
{
const FLocalLightData LocalLight = GetLocalLightDataFromGrid(Header.DataStartIndex + LightIndex, EyeIndex);
float3 LightPos = UnpackLightTranslatedWorldPosition(LocalLight);
float3 FaceToLight = LightPos - FaceCenterWS;
float FaceDistSqr = dot(FaceToLight, FaceToLight);
float3 LightDir = QITA_SAFE_NORM3(FaceToLight);
float InvRadius = UnpackLightInvRadius(LocalLight);
float Dist01Sqr = FaceDistSqr * InvRadius * InvRadius;
float Fade = saturate(1.0 - Dist01Sqr * Dist01Sqr);
float Atten = Fade * Fade * Fade;
float3 LightColor = UnpackLightColor(LocalLight);
float RawIntensity = dot(LightColor, float3(0.299, 0.587, 0.114));
float VisualEnergy = Atten * max(RawIntensity / max(LOCAL_LIGHT_VISUAL_REFERENCE_INTENSITY, 0.001), 0.0) * SDF_LOCAL_VISUAL_RESPONSE_SCALE;
float LocalScore = VisualEnergy * SDF_LOCAL_KEY_WEIGHT;
QITA_PUSH_TOP2(LocalScore, LightDir, VisualEnergy, 0.0, LightIndex);
}
if (KeyScore > 0.000001)
{
float KeySignedSDF = SDF_NO_SHADOW_VALUE;
QITA_SAMPLE_3D_FACE_SDF_SIGNED(KeyLightDir, KeySignedSDF);
OriginalKeyMask = QITA_TO_SDF_MASK(KeySignedSDF);
FusedKeySignedSDF = KeySignedSDF;
if (SecondScore > 0.000001)
{
float3 SecondDirN = QITA_SAFE_NORM3(SecondLightDir);
float3 KeyDirN = QITA_SAFE_NORM3(KeyLightDir);
float SecondRatio = min(SecondScore / max(KeyScore, 0.0001), SDF_FILL_RATIO_MAX);
float SameSideRaw = dot(FaceRightWS, SecondDirN) * dot(FaceRightWS, KeyDirN);
float SameSideGate = smoothstep(SDF_SAME_SIDE_RAW_START, SDF_SAME_SIDE_RAW_END, SameSideRaw);
float CrossSideGate = 1.0 - SameSideGate;
float SameSideContribGate = smoothstep(SDF_SAME_SIDE_CONTRIB_START, SDF_SAME_SIDE_CONTRIB_END, SecondRatio);
float SameSideFuseWeight = saturate(SameSideGate * SameSideContribGate);
if (SameSideFuseWeight > 0.0001)
{
float SecondSignedSDF = SDF_NO_SHADOW_VALUE;
QITA_SAMPLE_3D_FACE_SDF_SIGNED(SecondDirN, SecondSignedSDF);
float WeightedSecondSignedSDF = lerp(-SDF_NO_SHADOW_VALUE, SecondSignedSDF, SameSideFuseWeight);
QITA_SMOOTH_MAX_SIGNED(FusedKeySignedSDF, WeightedSecondSignedSDF, SDF_SAME_SIDE_SMOOTH_UNION_K, FusedKeySignedSDF);
}
float CrossFade = CrossSideGate * smoothstep(SDF_CROSS_SIDE_FADE_START, SDF_CROSS_SIDE_FADE_END, SecondRatio);
CrossSideFadeAccum = max(CrossSideFadeAccum, CrossFade);
float SecondFaceGate = smoothstep(SDF_FILL_FACE_GATE_START, SDF_FILL_FACE_GATE_END, dot(FaceForwardWS, SecondDirN));
float SecondShadowSide = 1.0 - smoothstep(SDF_FILL_SHADOW_SIDE_START, SDF_FILL_SHADOW_SIDE_END, dot(SecondDirN, KeyDirN));
float SecondGlobalMask = saturate(SecondFaceGate * SecondShadowSide * CrossSideGate);
FillShadowAccum += SecondGlobalMask * SecondRatio * SDF_FILL_SHADOW_STRENGTH;
float SecondVisualMask = saturate(OriginalKeyMask * SecondFaceGate + (1.0 - OriginalKeyMask) * max(SecondGlobalMask, SameSideFuseWeight * SecondFaceGate + CrossFade));
PixelVisualEnergy += SecondVisualEnergy * SecondVisualMask * SDF_FILL_VISUAL_SCALE;
}
if (DirectionalLightData.HasDirectionalLight != 0 && KeyIsDirectional < 0.5 && SecondIsDirectional < 0.5)
{
float3 FillDirN = QITA_SAFE_NORM3(DirectionalLightDirStored);
float3 KeyDirN = QITA_SAFE_NORM3(KeyLightDir);
float FillRatio = min(DirectionalScoreStored / max(KeyScore, 0.0001), SDF_FILL_RATIO_MAX);
float SameSideRaw = dot(FaceRightWS, FillDirN) * dot(FaceRightWS, KeyDirN);
float SameSideGate = smoothstep(SDF_SAME_SIDE_RAW_START, SDF_SAME_SIDE_RAW_END, SameSideRaw);
float CrossSideGate = 1.0 - SameSideGate;
float FillFaceGate = smoothstep(SDF_FILL_FACE_GATE_START, SDF_FILL_FACE_GATE_END, dot(FaceForwardWS, FillDirN));
float FillShadowSide = 1.0 - smoothstep(SDF_FILL_SHADOW_SIDE_START, SDF_FILL_SHADOW_SIDE_END, dot(FillDirN, KeyDirN));
float FillGlobalMask = saturate(FillFaceGate * FillShadowSide * CrossSideGate);
FillShadowAccum += FillGlobalMask * FillRatio * SDF_FILL_SHADOW_STRENGTH;
float FillVisualMask = saturate(OriginalKeyMask * FillFaceGate + (1.0 - OriginalKeyMask) * FillGlobalMask);
PixelVisualEnergy += DirectionalVisualEnergyStored * FillVisualMask * SDF_FILL_VISUAL_SCALE;
}
LOOP
for (uint LightIndex = 0; LightIndex < NumLights; ++LightIndex)
{
bool UsedAsKey = (KeyIsDirectional < 0.5 && LightIndex == KeyLocalLightIndex);
bool UsedAsSecond = (SecondScore > 0.000001 && SecondIsDirectional < 0.5 && LightIndex == SecondLocalLightIndex);
if (!UsedAsKey && !UsedAsSecond)
{
const FLocalLightData LocalLight = GetLocalLightDataFromGrid(Header.DataStartIndex + LightIndex, EyeIndex);
float3 LightPos = UnpackLightTranslatedWorldPosition(LocalLight);
float3 FaceToLight = LightPos - FaceCenterWS;
float FaceDistSqr = dot(FaceToLight, FaceToLight);
float3 LightDir = QITA_SAFE_NORM3(FaceToLight);
float InvRadius = UnpackLightInvRadius(LocalLight);
float Dist01Sqr = FaceDistSqr * InvRadius * InvRadius;
float Fade = saturate(1.0 - Dist01Sqr * Dist01Sqr);
float Atten = Fade * Fade * Fade;
float3 LightColor = UnpackLightColor(LocalLight);
float RawIntensity = dot(LightColor, float3(0.299, 0.587, 0.114));
float VisualEnergy = Atten * max(RawIntensity / max(LOCAL_LIGHT_VISUAL_REFERENCE_INTENSITY, 0.001), 0.0) * SDF_LOCAL_VISUAL_RESPONSE_SCALE;
float LocalScore = VisualEnergy * SDF_LOCAL_KEY_WEIGHT;
float3 FillDirN = QITA_SAFE_NORM3(LightDir);
float3 KeyDirN = QITA_SAFE_NORM3(KeyLightDir);
float FillRatio = min(LocalScore / max(KeyScore, 0.0001), SDF_FILL_RATIO_MAX);
float SameSideRaw = dot(FaceRightWS, FillDirN) * dot(FaceRightWS, KeyDirN);
float SameSideGate = smoothstep(SDF_SAME_SIDE_RAW_START, SDF_SAME_SIDE_RAW_END, SameSideRaw);
float CrossSideGate = 1.0 - SameSideGate;
float FillFaceGate = smoothstep(SDF_FILL_FACE_GATE_START, SDF_FILL_FACE_GATE_END, dot(FaceForwardWS, FillDirN));
float FillShadowSide = 1.0 - smoothstep(SDF_FILL_SHADOW_SIDE_START, SDF_FILL_SHADOW_SIDE_END, dot(FillDirN, KeyDirN));
float FillGlobalMask = saturate(FillFaceGate * FillShadowSide * CrossSideGate);
FillShadowAccum += FillGlobalMask * FillRatio * SDF_FILL_SHADOW_STRENGTH;
float FillVisualMask = saturate(OriginalKeyMask * FillFaceGate + (1.0 - OriginalKeyMask) * FillGlobalMask);
PixelVisualEnergy += VisualEnergy * FillVisualMask * SDF_FILL_VISUAL_SCALE;
}
}
KeyMask = QITA_TO_SDF_MASK(FusedKeySignedSDF);
KeyMask = lerp(KeyMask, 1.0, saturate(CrossSideFadeAccum));
PixelVisualEnergy += KeyVisualEnergy * KeyMask;
}
#endif
float FillShadowMask = saturate(FillShadowAccum);
float SDFMask = saturate(KeyMask + (1.0 - KeyMask) * FillShadowMask);
float FinalMask = lerp(SDF_SHADOW_MIN, 1.0, SDFMask);
float VisualLitMask = smoothstep(SDF_VISUAL_LIT_START, SDF_VISUAL_LIT_END, SDFMask);
float VisualBoost = min(max(PixelVisualEnergy * SDF_VISUAL_COLOR_SCALE, 0.0), SDF_VISUAL_COLOR_MAX);
FinalMask = lerp(FinalMask, 1.0, VisualBoost * VisualLitMask);
float3 FinalTint = lerp(ShadowColor, NormalColor, FinalMask);
float3 ResultColor = FinalTint * BaseColor;
float ExposureMask = smoothstep(SDF_EXPOSURE_LIT_START, SDF_EXPOSURE_LIT_END, SDFMask);
ExposureMask = pow(saturate(ExposureMask), SDF_EXPOSURE_LIT_POWER);
float ExposureBoost = max(PixelVisualEnergy * SDF_EXPOSURE_SCALE, 0.0);
ResultColor += (NormalColor * BaseColor) * ExposureBoost * ExposureMask;
#undef QITA_PUSH_TOP2
#undef QITA_SMOOTH_MAX_SIGNED
#undef QITA_TO_SDF_MASK
#undef QITA_SAMPLE_3D_FACE_SDF_SIGNED
#undef QITA_SAMPLE_ATLAS_BY_FACE_DOTS
#undef QITA_SAMPLE_SDF3D
#undef QITA_SAFE_NORM3
#undef FACE_FORWARD_AXIS
#undef FACE_RIGHT_AXIS
#undef FACE_UP_AXIS
#undef FACE_CENTER_OFFSET_LOCAL
#undef SDF_ATLAS_SIZE
#undef SDF_ATLAS_MAX_CELL
#undef SDF_CELL_UV_PADDING
#undef SDF_EDGE_WIDTH
#undef SDF_SHADOW_MIN
#undef SDF_LIGHT_POWER
#undef SDF_NO_SHADOW_VALUE
#undef LOCAL_LIGHT_VISUAL_REFERENCE_INTENSITY
#undef DIRECTIONAL_LIGHT_VISUAL_REFERENCE_INTENSITY
#undef QITA_DIRECTIONAL_VISUAL_INTENSITY_FIX
#undef SDF_LOCAL_VISUAL_RESPONSE_SCALE
#undef SDF_LOCAL_KEY_WEIGHT
#undef SDF_DIRECTIONAL_KEY_WEIGHT
#undef SDF_FILL_SHADOW_STRENGTH
#undef SDF_FILL_RATIO_MAX
#undef SDF_FILL_VISUAL_SCALE
#undef SDF_FILL_FACE_GATE_START
#undef SDF_FILL_FACE_GATE_END
#undef SDF_FILL_SHADOW_SIDE_START
#undef SDF_FILL_SHADOW_SIDE_END
#undef SDF_SAME_SIDE_RAW_START
#undef SDF_SAME_SIDE_RAW_END
#undef SDF_SAME_SIDE_CONTRIB_START
#undef SDF_SAME_SIDE_CONTRIB_END
#undef SDF_SAME_SIDE_SMOOTH_UNION_K
#undef SDF_CROSS_SIDE_FADE_START
#undef SDF_CROSS_SIDE_FADE_END
#undef SDF_VISUAL_COLOR_SCALE
#undef SDF_VISUAL_COLOR_MAX
#undef SDF_VISUAL_LIT_START
#undef SDF_VISUAL_LIT_END
#undef SDF_EXPOSURE_SCALE
#undef SDF_EXPOSURE_LIT_START
#undef SDF_EXPOSURE_LIT_END
#undef SDF_EXPOSURE_LIT_POWER
#undef SDF_VERTICAL_BLEND_START
#undef SDF_VERTICAL_BLEND_END
#undef SDF_FRONT_BACK_BLEND_WIDTH
#undef SDF_LR_BLEND_WIDTH
#undef SDF_LR_SIGN_FLIP
return ResultColor;