Computer Graphics/Vulkan

Vulkan - 12. 다중 광원 적용, 광원 타입 적용

surkim 2025. 1. 15. 12:59

deferred shading을 먼저해서 사실 다중 광원은 별 거 없었다.

 

lighting pass fragment shader code

#version 450

layout(input_attachment_index = 0, binding = 0) uniform subpassInput positionAttachment;
layout(input_attachment_index = 1, binding = 1) uniform subpassInput normalAttachment;
layout(input_attachment_index = 2, binding = 2) uniform subpassInput albedoAttachment;
layout(input_attachment_index = 3, binding = 3) uniform subpassInput pbrAttachment;

struct Light {
    vec3 position;
    vec3 direction;
    vec3 color;
    float intensity;
    float innerCutoff;
    float outerCutoff;
    uint type;
};

layout(binding = 4) uniform LightingInfo {
    Light lights[16];
    vec3 cameraPos;
    uint numLights;
    float ambientStrength;
};

layout(location = 0) in vec2 fragTexCoord;
layout(location = 0) out vec4 outColor;

// Fresnel-Schlick Approximation
vec3 fresnelSchlick(float cosTheta, vec3 F0) {
    return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}

// Normal Distribution Function (NDF)
float distributionGGX(vec3 N, vec3 H, float roughness) {
    float a = roughness * roughness;
    float a2 = a * a;
    float NdotH = max(dot(N, H), 0.0);
    float NdotH2 = NdotH * NdotH;
    float denominator = (NdotH2 * (a2 - 1.0) + 1.0);
    return a2 / (3.14159265359 * denominator * denominator);
}

// Geometry Function
float geometrySchlickGGX(float NdotV, float roughness) {
    float k = (roughness + 1.0) * (roughness + 1.0) / 8.0;
    return NdotV / (NdotV * (1.0 - k) + k);
}

float geometrySmith(vec3 N, vec3 V, vec3 L, float roughness) {
    float NdotV = max(dot(N, V), 0.0);
    float NdotL = max(dot(N, L), 0.0);
    return geometrySchlickGGX(NdotV, roughness) * geometrySchlickGGX(NdotL, roughness);
}

void main() {
    vec3 fragPosition = subpassLoad(positionAttachment).rgb;
    vec3 fragNormal = normalize(subpassLoad(normalAttachment).rgb);
    vec3 albedo = subpassLoad(albedoAttachment).rgb;

    vec4 pbr = subpassLoad(pbrAttachment);
    float roughness = pbr.r;
    float metallic = pbr.g;
    float ao = pbr.b;

    vec3 N = fragNormal;
    vec3 V = normalize(cameraPos - fragPosition);

    vec3 finalColor = vec3(0.0);

    vec3 ambient = ambientStrength * albedo * ao;
    finalColor += ambient;

    for (uint i = 0; i < numLights; ++i) {
        vec3 L;
        float attenuation = 1.0;

        if (lights[i].type == 0) { // Point Light
            L = normalize(lights[i].position - fragPosition);
            float distance = length(lights[i].position - fragPosition);
            float constant = 1.0;
            float linear = 0.09;
            float quadratic = 0.032;
            attenuation = 1.0 / (constant + linear * distance + quadratic * (distance * distance));
        }
        else if (lights[i].type == 1) { // Spot Light
            L = normalize(lights[i].position - fragPosition);
            float distance = length(lights[i].position - fragPosition);
            float constant = 1.0;
            float linear = 0.09;
            float quadratic = 0.032;
            attenuation = 1.0 / (constant + linear * distance + quadratic * (distance * distance));

            float theta = dot(L, normalize(-lights[i].direction));
            float epsilon = max(lights[i].innerCutoff - lights[i].outerCutoff, 0.001);
            attenuation *= clamp((theta - lights[i].outerCutoff) / epsilon, 0.0, 1.0);
        }
        else { // Directional Light
            L = normalize(-lights[i].direction);
            attenuation = 1.0;
        }

        vec3 H = normalize(V + L);
        vec3 F0 = mix(vec3(0.04), albedo, metallic);
        vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);

        float NDF = distributionGGX(N, H, roughness);
        float G = geometrySmith(N, V, L, roughness);

        vec3 numerator = NDF * G * F;
        float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.001;
        vec3 specular = numerator / denominator;

        vec3 kS = F;
        vec3 kD = vec3(1.0) - kS;
        kD *= 1.0 - metallic;

        float NdotL = max(dot(N, L), 0.0);
        vec3 diffuse = kD * albedo / 3.14159265359;
        vec3 radiance = lights[i].color * lights[i].intensity * NdotL * attenuation;

        finalColor += (diffuse + specular) * radiance;
    }
    outColor = vec4(clamp(finalColor, 0.0, 1.0), 1.0);
}

 

pbr은 워낙 잘 알려져있어

코드는 gpt에게 물어보면 잘 알려준다.

 

point light 하나만 적용되는 코드에서

for 문을 돌면서 타입을 봐서 색을 누적시키는 형식으로 변경

 

shader에 맞춰 구조체 변경해주고

uniform buffer와 descriptorset변경 해주다가

알게 된 사실인데

 

shader에 배열 넣는게 별 거 아니라

그냥 더 큰 구조체 넣어주는 형식이더라

 

그래서 사실상 바뀌는 게 거의 없었다.

 

결과물

스포트 라이트
랜덤 포지션, 랜덤 색상의 다중 광원

 

다중광원의 light object의 색을 광원색에 맞추면 참 이쁠 거 같은데

지금은 전부 default material이라 귀찮아서 안해줬다.

 

요즘 느끼는 게 엄청 힘들게 해준거는 당연한 부분이고

쉽게 쉽게 추가할 수 있는 건 이쁘게 보이는 거 같다.

 

이제 진짜 그림자 해보자