1 분 소요

Rim Lighting

Image

캐릭터나 ‘오브젝트’의 가장자리에 ‘밝은 윤곽선’을 넣어주는 조명 기법

  • 빛이 뒤/측면 에 올때의 피사체의 외곽선을
    은은하게 강조
    (‘역광’이라고도 표현)

구현 1

기본적으로 Pixel 쉐이더에서 진행한다

#include "Common.hlsli" // 쉐이더에서도 include 사용 가능

Texture2D g_texture0 : register(t0);
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;
};

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);
    }

    // Rim Lighting
    // OpenGL SuperBible 7th Edition, Ch13. Rendering Techniques
    
    // '테두리'의 위치 확인 필요
    // 화면 방향 벡터 기준으로 normal이 90도에 가까운 녀석들 (dot)
    // - (1 - dot) 를 통해 테두리에 가까운 녀석들을 판별
    // - 해당 부분을 pow 하여 '더 날카롭게' 테두리에 있는 녀석들만 선정 가능하다
    // - 이후 rim color와 strength를 적용
    color += ((rimColor * pow(1 - dot(toEye, input.normalWorld),rimPower)) * rimStrength);
    
    // Smoothstep
    // https://thebookofshaders.com/glossary/?search=smoothstep

    return useTexture ? float4(color, 1.0) * g_texture0.Sample(g_sampler, input.texcoord) : float4(color, 1.0);
}
  • toEye : 화면을 향하는 벡터
  • input.normalWorld : 현재 normal 벡터

  • 테두리 구하는 법?
    화면을 향하는 벡터와 ‘normal’을 내적(dot)하여
    그 값이 0에 가까우면 ‘직교’하므로
    0에 가까울수록 ‘테두리’에 가깝다고 판정 가능
  • 이후 해당 값에 1- 를 적용하여
    테두리에 ‘가까운’ 녀석들을 골라냄

  • Pow를 통하여 ‘테두리’에 더 가까운 부분만을 적용할수도 있음
    (앞쪽에 abs를 넣어줄수도 있음)

  • 마지막으로 color와 strength를 적용

사실 unreal 쪽의 fresnel 효과 구현과 매우 유사하여
쉽게 구현을 할 수 있었다

결과

Image

  • 색이 잘 적용되는 모습

Image

  • pow를 높이면 테두리에 더 가까운 녀석들만 빛나게 된다

SmoothStep 적용해보기

float rim = 1.0 - dot(toEye, input.normalWorld);

if(useSmoothstep)
    rim = smoothstep(0.0, 1.0, rim);

color += ((rimColor * pow(rim, rimPower)) * rimStrength);

rim 값은 dot의 결과이기에
0~1 사이의 값
smoothstep 을 통하여 그 사이 값을 좀 더 ‘부드럽게’ 보간할 수 있음

  • smoothstep을 켜지 않는 것

Image

  • smoothstep을 킨 것

Image

여담으로 중앙의 하얀색 부분은
specular 이다

댓글남기기