You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PCSS阴影实现边缘生硬失真问题排查求助

自研3D渲染器PCSS阴影边缘生硬失真问题排查

我正在自研3D渲染器,在阴影实现阶段遇到PCSS算法的问题:高大物体的阴影边缘出现生硬锐利的失真。怀疑错误出在平均遮挡物深度计算环节,尝试过调整搜索宽度(设为0.2时失真有轻微变化),还发现问题会随lightview矩阵调整而改变,但已确认该矩阵计算逻辑符合预期(仅朝向阴影投射物并考虑阴影接收物)。

问题截图

默认效果:
阴影边缘生硬失真

search width = 0.2时效果:
search width=0.2时的阴影效果

阴影计算Shader代码

#include<random.glsl>

#define DEFAULT_BLOCKER_SEARCH_NUM_SAMPLES 256
#ifndef BLOCKER_SEARCH_NUM_SAMPLES
    #define BLOCKER_SEARCH_NUM_SAMPLES DEFAULT_BLOCKER_SEARCH_NUM_SAMPLES
#endif

#define DEFAULT_PCF_NUM_SAMPLES 256
#ifndef PCF_NUM_SAMPLES
    #define PCF_NUM_SAMPLES DEFAULT_PCF_NUM_SAMPLES
#endif

#define DEFAULT_LIGHT_SIZE 0.01
#ifndef LIGHT_SIZE
    #define LIGHT_SIZE DEFAULT_LIGHT_SIZE
#endif

#define POINTLIGHT_PCF_NUM_SAMPLES 128
#define DIRLIGHT_PCF_NUM_SAMPLES 256


float SampleTexture(sampler2D tex, vec2 coords, float compare)
{
    return step(compare, texture(tex, coords.xy).r);
}

float SmoothPCF(sampler2D shadowMap, float gradientNoise, vec3 projCoords, float radius, int samples) {
    float shadow = 0.0;
    for (int i = 0; i < samples; ++i) {
        vec2 sampleUV = VogelDisk(i, samples, gradientNoise);
        sampleUV = projCoords.xy + sampleUV * radius;
        shadow += SampleTexture(shadowMap, sampleUV, projCoords.z);
    }
    shadow /= samples;
    return 1 - shadow;
}

float CalculateAvgBlockerDepth(sampler2D shadowMap, float gradientNoise, vec3 projCoords, float searchWidth, int samples){
    float avgBlockerDepth = 0.0;
    int numBlockers = 0;

    for (int i = 0; i < samples; ++i) {
        vec2 sampleUV = VogelDisk(i, samples, gradientNoise);
        sampleUV = projCoords.xy + searchWidth * sampleUV;

        float sampleDepth = texture(shadowMap, sampleUV).r;

        if (sampleDepth < projCoords.z) {
            avgBlockerDepth += sampleDepth;
            numBlockers++;
        }
    }

    if (numBlockers == 0)
        return 0;

    return avgBlockerDepth /= numBlockers;
}

float CalculatePenumbra(float avgBlockerDepth, float searchWidth, float zDepth) {
    float penumbra = searchWidth * (zDepth - avgBlockerDepth) / avgBlockerDepth;
    return penumbra;
}

float PCSS(sampler2D shadowMap, vec3 projCoords) {
    float gradientNoise = InterleavedGradientNoise(projCoords.xy);
    float searchWidth = LIGHT_SIZE * clamp(projCoords.z - 0.1, 0.0, 1.0) / projCoords.z * 10.0;
    float avgBlockerDepth = CalculateAvgBlockerDepth(shadowMap, gradientNoise, projCoords, searchWidth, BLOCKER_SEARCH_NUM_SAMPLES);
    if (avgBlockerDepth == 0)
        return 0;
    float penumbra = CalculatePenumbra(avgBlockerDepth, LIGHT_SIZE, projCoords.z);
    return SmoothPCF(shadowMap, gradientNoise, projCoords, penumbra, PCF_NUM_SAMPLES);
}

LightView矩阵计算代码

生成自定义相对光空间矩阵

glm::mat4 dir_light::generate_custom_relative_lightspace_matrix(const glm::vec3& cmin, const glm::vec3& cmax,
    const glm::vec3& rmin, const glm::vec3& rmax, const glm::mat4& model) {
    glm::vec3 center = (cmin + cmax) * 0.5f;
    float radius = glm::length(cmax - cmin) * 0.5f;

    glm::vec3 light_pos = center - direction * radius * 2.0f;
    glm::mat4 light_view = glm::lookAt(light_pos, center, glm::vec3(0, 1, 0));

    auto [cmin_ls, cmax_ls] = utilities::calculate_min_max_light_space(cmin, cmax, light_view, model);
    auto [rmin_ls, rmax_ls] = utilities::calculate_min_max_light_space(rmin, rmax, light_view, model);

    float l = cmin_ls.x, r = cmax_ls.x;
    float b = cmin_ls.y, t = cmax_ls.y;

    float cn = -cmax_ls.z, cf = -cmin_ls.z;
    float rn = -rmax_ls.z, rf = -rmin_ls.z;

    float n = std::min(cn, rn), f = std::max(cf, rf);

    glm::mat4 light_proj = glm::ortho(l, r, b, t, n, f);

    return light_proj * light_view;
}

计算光空间最小最大边界

std::tuple<glm::vec3, glm::vec3> utilities::calculate_min_max_light_space(glm::vec3 min, glm::vec3 max,
    const glm::mat4& light_view, const glm::mat4& model) {
    std::vector<glm::vec3> corners = {
        { min.x, min.y, min.z },
        { min.x, min.y, max.z },
        { min.x, max.y, min.z },
        { min.x, max.y, max.z },
        { max.x, min.y, min.z },
        { max.x, min.y, max.z },
        { max.x, max.y, min.z },
        { max.x, max.y, max.z }
    };

    glm::vec3 min_ls(FLT_MAX), max_ls(-FLT_MAX);
    for (const auto& c : corners) {
        glm::vec3 light_space_pos = glm::vec3(light_view * model * glm::vec4(c, 1.0f));
        min_ls = glm::min(min_ls, light_space_pos);
        max_ls = glm::max(max_ls, light_space_pos);
    }

    return { min_ls, max_ls };
}

已确认矩阵计算逻辑符合预期:仅让光线朝向阴影投射物(cmin, cmax)并考虑阴影接收物(rmin, rmax)。请帮忙排查PCSS实现中的错误,解决阴影边缘失真问题。

内容的提问来源于stack exchange,提问作者FouRed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 21:05:56