Computer Graphics/Project
20250422
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) 라는 기법을 알게 되었고,
앞으로 이를 학습하고 제 렌더링 파이프라인에 적용해볼 예정입니다.