3D SDF 多光源 阴影 的不同尝试

一、选择对角色光照贡献最大的一盏灯作为主灯,用于主基调的亮光面和阴影面,其他灯光对亮光面和被光面进行补光(增亮)

如果对角色模型,在所有光源里面进行遍历,找到对角色光照贡献最强的一盏光作为主光,那么当角色移动,或者有一盏灯亮度逐渐增大时,取代之前的主光,容易产生阴影闪烁的问题

例如:

主灯会硬切

如果当前主灯 A 强度是 10,副灯 B 慢慢变到 10.1,材质里如果直接选最大值,主灯会瞬间从 A 切到 B。

那么现在问题就是如何平和过渡这个主光的切换

二、根据每盏灯对角色脸部光照贡献程度,取一个带权重的方向作为主光,且只有一个主光没有副光

做法就是每个方向都进行带权加到一个方向向量上,比如说:我这里离角色近,且灯光亮度高,那么我就对最后的主方向给的贡献就更大,这样带权累加过后进行归一化处理

这样的好处:

我就不需要再考虑什么主光源之间的切换问题,不会有单体上的主光源的概念,只会灯光根据对角色脸部的光照贡献对最终方向进行加权

  1. 多个方向会生成不存在的阴影形状

比如左前方一盏灯、右前方一盏灯。

如果你把两个方向加权平均,得到的是"正前方光"。但真实卡渲里这不一定等价,因为 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;
相关推荐
人工智能培训3 小时前
用知识图谱重构搜索引擎
大数据·人工智能·3d·重构·知识图谱·agent
FII工业富联科技服务4 小时前
AI+3D世界模型:重构园区安防的“可感知、可推演、可进化”
大数据·人工智能·3d·ai·制造
HyperAI超神经1 天前
深度估计准确率冲上0.9,Meta提出VLM³,论证视觉模型天生会学3D,以Qwen3-VL-4B为基础实现多任务的统一建模
人工智能·3d·大模型·多模态·空间推理·3d感知·3d理解
ZK_H1 天前
3D NAND Flash手册阅读指南
3d
百度搜知知学社1 天前
ScreenCraft壁纸进阶玩法:4K超清与3D视差动态效果全解析
3d·动态效果·壁纸·4k超清·3d视差·screencraft
插件开发1 天前
矢量路径运算如何选GPU技术?——适用算法对比及OpenGL/Direct3D/CUDA选型指南
算法·3d
CG_MAGIC1 天前
3ds Max粒子系统:雪与雨特效制作
3d·blender·材质·效果图·渲云渲染
李二。1 天前
鸿蒙原生ArkTS-鸿蒙6.0新特性-3D卡片翻转画廊
3d·华为·harmonyos
大江东去浪淘尽千古风流人物1 天前
【VGGT】统一3D重建:单网络同时预测相机位姿、深度图、点云与3D轨迹的前馈Transformer架构深度解析
网络·数码相机·3d·transformer·slam·3d重建·cvpr2025