surkim 2025. 4. 22. 10:56

광선이 맞은 지점에서의 조명계산을 적용했습니다.

 

rgen

for (int i = 0; i < sampleCount; ++i) {
    ...
    vec3 H = importanceSampleGGX(Xi, worldNormal, roughness);
    vec3 L = normalize(reflect(-viewDir, H));
    ...
    float pdf = ggxPdf(worldNormal, H, viewDir, roughness);
    
    vec3 F = fresnelSchlick(dot(H, viewDir), F0);
    float D = distributionGGX(worldNormal, H, roughness);
    float G = geometrySmith(worldNormal, viewDir, L, roughness);

    // BRDF: Cook-Torrance 식
    vec3 brdf = (D * G * F) / (4 * NoV * NoL +  1e-5);

    // 경로 감쇄 계수: importance sampling 보정 포함
    vec3 beta = brdf * NoL / pdf;
    
    payload.beta = beta;
    traceRayEXT(..., L, ..., missIndex = 0);
    
    accumulatedColor += payload.color;
}

 

  • GGX Importance Sampling으로 각 방향 L을 선택
  • 선택된 방향이 실제 반사를 일으킬 확률(pdf)에 따라 보정계수 1/pdf를 곱함
  • 각 반사 경로에 대해, BRDF * cosθ / pdf 형태로 조명 기여를 누적

rchit

traceRayEXT(..., L, ..., missIndex = 1); // 섀도우 확인

if (!isShadowed) {
    vec3 H = normalize(V + L);
    vec3 F = fresnelSchlick(dot(H, V), F0);
    float D = distributionGGX(N, H, roughness);
    float G = geometrySmith(N, V, L, roughness);

    vec3 brdf = (D * G * F) / (4 * N⋅V * N⋅L + ε);
    vec3 diffuse = (1 - F) * albedo / π;
    vec3 radiance = light.intensity * light.color * attenuation * N⋅L;

    // 최종 직접 조명 기여
    Li += ambient + (diffuse + specular) * radiance * payload.beta;
}
else {
    Li += ambient * payload.beta;
}

 

 

  • 조명 방향과 히트된 지점간의 visibility 확인 후 조명 계산
  • Cook-Torrance 기반 BRDF를 그대로 사용
  • payload.beta는 상위 경로에서 누적된 감쇠 계수이므로,

 

 

rchit에서 남은 바운스(payload.bounce > 1)가 존재하는 경우
rgen에서 계산한 payload.beta를 그대로 계승하여
다음 반사 방향으로 새로운 GGX importance sampling을 진행

 

즉, 1개 샘플에 대해 들어오는 간접광은

여기서 반사 경로가 0 -> k로 진행된다면,

 

 

 

이 계산은 간접광만을 위한 계산이므로 나중에 lightpass에서 이 결과 이미지를 가져와서 계산됩니다.

앞서 설명한 RT Reflection 경로에서 계산된 조도는 전부 간접 조명(indirect lighting)에 해당하며,
이는 별도의 저장 버퍼(rtOutput)에 기록된 뒤, Light Pass에서 불러와 통합 조명 계산에 활용됩니다.

vec3 F0_view = mix(vec3(0.04), albedo, metallic);
vec3 F_view  = fresnelSchlick(max(dot(N, V), 0.0), F0_view);

vec3 direct = vec3(0.0);
vec3 indirect = vec3(0.0);

if (renderOptions.rtMode == 1) {
    vec3 rtCol = imageLoad(rtOutput, ivec2(gl_FragCoord.xy)).rgb;
    // 조명 분리: 프레넬 계수 기반
    direct = diffuseSum + specularSum * (1 - F_view); // 화면 공간 직접광
    indirect = rtCol * F_view; // RT reflection 간접광
} else {
    direct = diffuseSum + specularSum;
}
finalColor = ambient + direct + indirect;
finalColor += emissive;
outColor = vec4(clamp(finalColor, 0.0, 1.0), 1.0);

 

결국 이번 구현은 path tracing의 일부 개념과 RT reflection의 실용적 측면이 섞인
그 중간 어딘가에 위치한 하이브리드 구조가 되었습니다.

 

사용자가 bounce와 sample 수를 조절할 수 있도록 한 만큼,
최악의 설정값에서도 어느 정도 안정적인 품질을 유지할 수 있도록
노이즈 제거 기법을 도입할 계획입니다.

SVGF(Spatiotemporal Variance-Guided Filtering) 라는 기법을 알게 되었고,
앞으로 이를 학습하고 제 렌더링 파이프라인에 적용해볼 예정입니다.