Computer Graphics/Vulkan

Vulkan Game Engine - 18. 그림자 퀄리티 개선, PCF

surkim 2025. 2. 17. 12:58

그림자 퀄리티 개선은 사실 opengl 공부할 때 배웠던 방식 그대로 적용했다.

디테일이 중요하니 사진도 크게

 

자세~~~~히 보면 우둘투둘하게 계단식으로 그림자가 보이는데 이게 모델이 커지면 커질수록 더 심해진다.

그림자 개선을 위한 PCF라는 방법이 있는데

그냥 간단히 말해 shadow map에서 그림자를 따올 때 주면 픽셀까지 같이 샘플링해서 평균치 내는 것이다.

 

적용 후 사진은

별차이 없는거 같아 보이지만 약간 스무스~ 해졌다.

 

어려운건 없었고 신기한거와 신경 쓸게 하나씩 있었는데

신기한거는

float PCFShadow(sampler2DShadow shadowMap, vec3 shadowCoord, float currentDepth) {
    float shadow = 0.0;
    vec2 texelSize = 1.0 / textureSize(shadowMap, 0);

    for (int x = -1; x <= 1; ++x) {
        for (int y = -1; y <= 1; ++y) {
            vec2 offset = vec2(x, y) * texelSize;
            shadow += texture(shadowMap, vec3(shadowCoord.xy + offset, currentDepth));
        }
    }

    return shadow / 9.0;
}

이거 코드보면 texture 받아오는 방식이 특이한데

받아올때 sampler2Dshadow으로 받았기 때문에

texture로 받아올 때 두번째 인자가 비교 인자로 들어가서 이 인자가 그림자가 될건지 자동으로 판단해준댄다.

굳이 이 깊이가 shadowmap에서 그림자가 될 깊이인가 따져서 1인지 0인지 판단해서 계산해줄 필요가 없이 함수하나면 충분했다.

 

신경써야 했던 부분은

shadow cubemap 부분인데 여기서는 샘플링을 벡터로 하니깐 벡터 주변의 방향을 샘플링해야해서

Rodrigues 회전공식을 이용해 샘플링할 벡터를 구해줬다.

vec3 rotatedVectors[8];


vec3 getRotationAxis(vec3 direction) {
    vec3 worldUp = vec3(0.0, 1.0, 0.0);
    
    if (abs(dot(direction, worldUp)) > 0.99) {
        return normalize(vec3(1.0, 0.0, 0.0));
    }

    return normalize(cross(direction, worldUp));
}

vec3 rotateVector(vec3 v, vec3 axis, float angle) {
    float cosTheta = cos(angle);
    float sinTheta = sin(angle);
    
    return v * cosTheta + cross(axis, v) * sinTheta + axis * dot(axis, v) * (1.0 - cosTheta);
}


void generateRotatedVectors(vec3 direction) {
    vec3 rotationAxis = getRotationAxis(direction);
    float angleStep = radians(0.02);
    for (int i = 0; i < 8; i++) {
        float angle = angleStep * float(i);
        rotatedVectors[i] = rotateVector(direction, rotationAxis, angle);
    }
}

float PCFShadowCube(samplerCube shadowMap, vec3 fragToLight, float currentDepth) {
    float shadow = 0.0;
    generateRotatedVectors(fragToLight);

    for (int i = 0; i < 8; i++) {
        float closestDepth = texture(shadowMap, rotatedVectors[i]).r;
        shadow += (currentDepth - 0.005 > closestDepth) ? 0.0 : 1.0;
    }

    return shadow / 8.0;
}