3 분 소요

Environment Mapping

Image

  • 주어지는 ‘환경’ 요소의 색을 반사하여
    물체에 코팅하는 매핑 방식

  • 환경 요소(큐브맵 같은 ‘스카이 박스’ 등)를 이용하는 기법

  • 반사 벡터를 통해 그 값을 가져온다

구현 법?

Image

  • 시점과 상관없이 ‘표면’에 수직인 방향으로부터
    ‘큐브맵’에 ‘색’을 가져오는 것

  • 시각 벡터를 기반으로 반사시킨
    ‘반사 벡터’에 부딪힌 ‘큐브맵’의 색 가져오기
    (v = i - 2 * n * dot(i,n))
    (v : 반사 벡터, i : 입사벡터, n : 법선 벡터)

구현 시 주의사항

  • ‘시각 벡터’에 영향을 받기에
    관찰자 시점에 따라 구체에 보이는 모습이 달라져야 한다

  • Model Rotation과 Model Translation에 영향을 받지 않음
    (회전, 이동)

  • 반사 벡터를 잘 구현할 것!
    (이게 적용되냐 아니냐의 시각적 효과 차이가 큰 편)

내 구현 - Environment Mapping 구현 1

example.cpp - Render()
{
... 위쪽에서 큐브맵 렌더링

// 버텍스/인덱스 버퍼 설정
for (const auto &mesh : m_meshes) {
    m_context->VSSetConstantBuffers(
        0, 1, mesh->vertexConstantBuffer.GetAddressOf());

    // TODO: 물체 렌더링할 때 큐브맵도 같이 사용
    ID3D11ShaderResourceView *resViews[2] = {
        mesh->textureResourceView.Get(),
        m_cubeMapping.cubemapResourceView.Get()};
    m_context->PSSetShaderResources(0, 2, resViews);

    m_context->PSSetConstantBuffers(
        0, 1, mesh->pixelConstantBuffer.GetAddressOf());

    m_context->IASetInputLayout(m_basicInputLayout.Get());
    m_context->IASetVertexBuffers(0, 1, mesh->vertexBuffer.GetAddressOf(),
                                  &stride, &offset);
    m_context->IASetIndexBuffer(mesh->indexBuffer.Get(),
                                DXGI_FORMAT_R32_UINT, 0);
    m_context->IASetPrimitiveTopology(
        D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    m_context->DrawIndexed(mesh->m_indexCount, 0, 0);
}
}

큐브맵 에서 사용하는 SRV를 PixelShader에 전송

그리고 Pixel Shader에서 해당 큐브맵을 이용하여 매핑을 해준다

// PixelShader.hlsl

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

Texture2D g_texture0 : register(t0);
// TODO:
TextureCube g_textureCube1 : register(t1);

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

    // Normal에 해당하는 큐브맵 좌표값 사용
    color = g_textureCube1.Sample(g_sampler, input.normalWorld.xyz).xyz;
    
    // reflect(광선이 들어오는 방향, 노멀 벡터)
    // https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-reflect
    
    return useTexture ? float4(color, 1.0) * g_texture0.Sample(g_sampler, input.texcoord) : float4(color, 1.0);
}

  • color = g_textureCube1.Sample(g_sampler, input.normalWorld.xyz).xyz; 부분을 통하여
    값을 ‘대입’해준다
    (color += 해주니 광원에 영향을 너무 많이 받아 하얗게 나온다)

여기까지 구현하면 사진처럼 나오게 된다

Image

내 구현 - Environment Mapping 구현 2 (반사 벡터 적용)

이제 위의 공식을 이용하여 반사벡터 부분을 구현하자

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

Texture2D g_texture0 : register(t0);
// TODO:
TextureCube g_textureCube1 : register(t1);

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

    // Normal에 해당하는 큐브맵 좌표값 사용
    //color = g_textureCube1.Sample(g_sampler, input.normalWorld.xyz).xyz;
    
    // reflect(광선이 들어오는 방향, 노멀 벡터)
    // https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-reflect
    color = g_textureCube1.Sample(g_sampler, reflect(-toEye, input.normalWorld)).xyz;
    
    return useTexture ? float4(color, 1.0) * g_texture0.Sample(g_sampler, input.texcoord) : float4(color, 1.0);
}

  • reflect 라는 쉐이더 내부에서 제공하는 함수가 이미 존재!
    (해당 함수 내부에서 주어진 공식을 내온다)
    이걸 가져다 사용하면 된다!

  • color = g_textureCube1.Sample(g_sampler, reflect(-toEye, input.normalWorld)).xyz;
    해당 부분에서 ‘입사 벡터’는
    눈 쪽에서 ‘현재 위치’를 향해 바라보는 것이므로
    (input.posWorld - eyeWorld)
    마침 toEye가 딱 -를 곱한 것이니
    -를 곱하여 이용한다

구현 결과

Image

  • 일단 예제 부분에서도 color를 바로 return 하는 방식을 취하는 편

TMI 모델을 바꾼다면…?

Image

  • 마치 젤다가 ‘동상’처럼 보인다

  • 반사와 같은 요소를 잘 조절하면 ‘재질’을 표현할 수 있는 것을 잊지 말자!

댓글남기기