Fresnel Effect
Fresnel(프레넬) 효과
-
물체의 ‘표면’을 볼 때, ‘시선’과 표면의 법선(Normal) 사이의 각도에 따라
반사율이 달라지는 현상
(그러한 계산을 ‘프레넬 방정식’에 따라 하기에 프레넬 효과라 부르기도 함) -
물이나 금속 같은 재질의 경우
똑바로 보면 ‘물’ 안이 투과되어 보이지만
(시야각과 수직에 가까움)
사선으로 갈수록 수면 위의 여러 요소들이 반사되어 비춰진다
(시야각과 수평에 가까움) -
시야각에 의존하기에 Rim Lighting 이나 Outline glow 등의 효과처럼 사용할 수 있음
예제
pixel shader에서 다룬다
#include "Common.hlsli" // 쉐이더에서도 include 사용 가능
Texture2D g_texture0 : register(t0);
TextureCube g_diffuseCube : register(t1);
TextureCube g_specularCube : register(t2);
SamplerState g_sampler : register(s0);
cbuffer BasicPixelConstantBuffer : register(b0)
{
float3 eyeWorld;
bool useTexture;
Material material;
Light light[MAX_LIGHTS];
float3 rimColor;
float rimPower;
float rimStrength;
bool useSmoothstep;
};
// Schlick approximation: Eq. 9.17 in "Real-Time Rendering 4th Ed."
// fresnelR0는 물질의 고유 성질
// Water : (0.02, 0.02, 0.02)
// Glass : (0.08, 0.08, 0.08)
// Plastic : (0.05, 0.05, 0.05)
// Gold: (1.0, 0.71, 0.29)
// Silver: (0.95, 0.93, 0.88)
// Copper: (0.95, 0.64, 0.54)
float3 SchlickFresnel(float3 fresnelR0, float3 normal, float3 toEye)
{
// 참고 자료들
// THE SCHLICK FRESNEL APPROXIMATION by Zander Majercik, NVIDIA
// http://psgraphics.blogspot.com/2020/03/fresnel-equations-schlick-approximation.html
float normalDotView = saturate(dot(normal, toEye));
float f0 = 1.0f - normalDotView; // 90도이면 f0 = 1, 0도이면 f0 = 0
// 1.0 보다 작은 값은 여러 번 곱하면 더 작은 값이 됩니다.
// 0도 -> f0 = 0 -> fresnelR0 반환
// 90도 -> f0 = 1.0 -> float3(1.0) 반환
// 0도에 가까운 가장자리는 Specular 색상, 90도에 가까운 안쪽은 고유 색상(fresnelR0)
return fresnelR0 + (1.0f - fresnelR0) * pow(f0, 5.0);
}
float4 main(PixelShaderInput input) : SV_TARGET
{
float3 toEye = normalize(eyeWorld - input.posWorld);
float3 color = float3(0.0, 0.0, 0.0);
int i = 0;
// https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-for
// https://forum.unity.com/threads/what-are-unroll-and-loop-when-to-use-them.1283096/
[unroll] // warning X3557: loop only executes for 1 iteration(s), forcing loop to unroll
for (i = 0; i < NUM_DIR_LIGHTS; ++i)
{
color += ComputeDirectionalLight(light[i], material, input.normalWorld, toEye);
}
[unroll]
for (i = NUM_DIR_LIGHTS; i < NUM_DIR_LIGHTS + NUM_POINT_LIGHTS; ++i)
{
color += ComputePointLight(light[i], material, input.posWorld, input.normalWorld, toEye);
}
[unroll]
for (i = NUM_DIR_LIGHTS + NUM_POINT_LIGHTS; i < NUM_DIR_LIGHTS + NUM_POINT_LIGHTS + NUM_SPOT_LIGHTS; ++i)
{
color += ComputeSpotLight(light[i], material, input.posWorld, input.normalWorld, toEye);
}
// 쉽게 이해할 수 있는 간단한 구현입니다.
// IBL과 다른 쉐이딩 기법(예: 퐁 쉐이딩)을 같이 사용할 수도 있습니다.
float4 diffuse = g_diffuseCube.Sample(g_sampler, input.normalWorld);
float4 specular = g_specularCube.Sample(g_sampler, reflect(-toEye, input.normalWorld));
diffuse *= float4(material.diffuse, 1.0);
specular *= pow((specular.r + specular.g + specular.b)/3.0, material.shininess);
specular *= float4(material.specular, 1.0);
// 참고: https://www.shadertoy.com/view/lscBW4
float3 f = SchlickFresnel(material.fresnelR0, input.normalWorld, toEye);
specular.xyz *= f;
if (useTexture) {
diffuse *= g_texture0.Sample(g_sampler, input.texcoord);
// Specular texture를 별도로 사용할 수도 있습니다.
}
return diffuse + specular;
//return useTexture ? float4(color, 1.0) * g_texture0.Sample(g_sampler, input.texcoord) : float4(color, 1.0);
}
핵심인 부분은 다음과 같다
// Schlick approximation: Eq. 9.17 in "Real-Time Rendering 4th Ed."
// fresnelR0는 물질의 고유 성질
// Water : (0.02, 0.02, 0.02)
// Glass : (0.08, 0.08, 0.08)
// Plastic : (0.05, 0.05, 0.05)
// Gold: (1.0, 0.71, 0.29)
// Silver: (0.95, 0.93, 0.88)
// Copper: (0.95, 0.64, 0.54)
float3 SchlickFresnel(float3 fresnelR0, float3 normal, float3 toEye)
{
// 참고 자료들
// THE SCHLICK FRESNEL APPROXIMATION by Zander Majercik, NVIDIA
// http://psgraphics.blogspot.com/2020/03/fresnel-equations-schlick-approximation.html
float normalDotView = saturate(dot(normal, toEye));
float f0 = 1.0f - normalDotView; // 90도이면 f0 = 1, 0도이면 f0 = 0
// 1.0 보다 작은 값은 여러 번 곱하면 더 작은 값이 됩니다.
// 0도 -> f0 = 0 -> fresnelR0 반환
// 90도 -> f0 = 1.0 -> float3(1.0) 반환
// 0도에 가까운 가장자리는 Specular 색상, 90도에 가까운 안쪽은 고유 색상(fresnelR0)
return fresnelR0 + (1.0f - fresnelR0) * pow(f0, 5.0);
}
...
float3 f = SchlickFresnel(material.fresnelR0, input.normalWorld, toEye);
specular.xyz *= f;
-
- fresnelR0?
- 물질 고유의 ‘특성’ 같은 것
표현하고 싶은 고유의 float3 값을 입력하여 사용
- fresnelR0?
-
normal과 시야 벡터를 내적한 후,
saturate로 0~1 사이로 보간 -
이후 1에 빼주어
‘시야각’에 ‘수평’에 가까운 ‘테두리’ 부분을 1
직각에 가까운 정면 부분을 0으로 잡아 f0에 저장
(이전에 Rim Lighting 할때도 사용한 방식) -
f0을 pow하여 값을 테두리에 더 가깝게 한 후,
직교에 가깝다면(f0 : 0) fresnelR0의 값을,
아니라면 float3(1.0)의 값을 반환 -
그렇게 반환한 값을 specular 값에 곱해준다
(테두리의 빛은 반사광이므로?) -
IBL 에서 Specular는 반사방향이 R로 샘플링을 하는 편
여기서는 표면 법선인 Normal과 시점 V를 이용
RdotN과 NdotV가 SchlickFresnel에선 결과가 같다하여
여기서는 NdotV를 사용 -
결과적으로 ‘테두리’가 아닌 값은 fresnelR0의 수치에 영향을 받음
-
- SchlickFresnel?
- 일반 Fresnel 보다 더 빠르게 사용할 수 있는 ‘근사’식
(실시간 렌더링에선 보통 이걸로 Fresnel을 구현하여 사용)
- SchlickFresnel?
일반 버전
fresnelR0의 색을 붉게 조정한 버전
식의 의미?
pow(5.0)을 한 이유?
그래프를 보듯
값이 ‘빠르게’ 내려와 완만하게 값을 유지하게 된다
(우린 Schlick 사용 중)
실제로 정확하게 구현하려면
저 아래쪽처럼 내려가는 과정이 필요
우리는 ‘근사식’을 사용하기에 빠르지만 살짝은
정교하지 못한 결과를 얻는것과 같다
댓글남기기