2 분 소요

Height Map?

Image

이전 강의에서 적용한 노멀 매핑을 사용하면
울퉁불퉁한 질감을 가진 텍스쳐를 구현할 수 있다

그런데 텍스쳐는 ‘다양한 메시‘에 붙일 수 있어야 함
ex) 울퉁불퉁한 메시, 평평한 메시

그렇기에 위처럼
깔끔한 ‘구체’이지만 표현되는 질감은 ‘패인’듯한 인상을 줄 수 있음

그렇기에 Height Map을 통하여
‘질감’ 에 대한 표현을 별도로 관리하고
하나의 텍스쳐로 더 많고 자연스러운 표현을 연출

  • 텍스쳐와 메시는 각각 표현의 역할이 분리
    • 메시 (Geometry) : 공간적, 실질적 형태, 비용 높음
    • 텍스쳐 (Texture) : 색상, 질감의 세부 디테일, 비용 낮지만 한계가 존재
  • 효율적인 표현을 위해 등장한 것이
    노멀 맵 / 높이 맵!

  • 노멀 맵
    실제 메시의 형태를 바꾸지 않고
    조명 연산을 변화시키는 트릭
    • 다만 ‘실제로 매끈’해야 하는 메시에
      울퉁불퉁한 노멀맵을 붙이면
      울퉁불퉁한 반사는 진행되지만
      윤곽은 매끈한 '이상한 상태'가 됨
      -> ‘높이 맵’의 등장 이유
  • 높이 맵
    픽셀의 깊이를 표현하려는 다양한 시도들
    • Displacement Mapping : Vertex를 이동시켜 Geometry에 변형을 줌(다만 고비용)
    • Parallex / Occusion Mapping : 픽셀 쉐이더 단계에서 텍스쳐 좌표를 깊이값으로 왜곡 (카메라 관점에서 입체감만 흉내, 저비용)

그렇기에 PBR과 같은 사실적 표현 방식은 양 쪽을 복합적으로 고려

  • HeightMap을 통해 픽셀의 깊이 표현
  • NormalMap을 통해 디테일한 조명 표현

우리가 해보려는 것은 Vertex Shader를 통해
변형을 하는 Displacement Mapping을 사용해보려 함

Height Map Texture

Image

하얄수록 ‘높아져야 하는’ 강도를 표시
어두운 부분은 ‘안쪽으로 들어가는’ 부분을 표시

  • 이러한 효과에 강도를 줌으로서
    불규칙해보이는 패턴 등을 표현할 때 효과적

3D 렌더링 툴에서도
Height Map을 만들어 주는 기능이 존재

AO(Ambient Occlusion, 환경 폐색)

Image

이전 TIL 에서도 간략하게 이야기 한 내용이지만

  • 복잡한 3D 물체를 그릴 때,
    조명 계산이 쉽지 않은 부분이 존재
    (빛이 닿지 않는)
    이러한 부분에 대한 ‘그림자 연산’을 진행해 두는 것

  • 레이트레이싱이 아닌 Rasterize 기반의 렌더링에서
    이러한 부분을 판단하기 힘듦

  • 그렇기에 어둡게 나오는 것이 자연스러운
    구석진 부분 등에 ‘표시’를 해둔 것

Image

AO Texture

파여있는 부분에 대하여 미리 Texture Map에 미리 표기하고
이를 Pixel Shader에서 이용하여 조명 효과를 효율적으로 표현

예제 - Height Map을 적용해보기

Image

그림과 같은 효과를 적용하려면
미리 말하였듯
정점의 위치를 변화시킴으로서 그 효과를 극대화 시킬 수 있음

VertexShader.hlsl

PixelShaderInput main(VertexShaderInput input)
{
    PixelShaderInput output;
    
    // Normal 벡터 먼저 변환 (Height Mapping)
    float4 normal = float4(input.normalModel, 0.0f);
    output.normalWorld = mul(normal, invTranspose).xyz;
    output.normalWorld = normalize(output.normalWorld);
    
    // Tangent 벡터는 modelWorld로 변환
    float4 tangentWorld = float4(input.tangentModel, 0.0f);
    tangentWorld = mul(tangentWorld, modelWorld);

    float4 pos = float4(input.posModel, 1.0f);
    pos = mul(pos, modelWorld);
    
    if (useHeightMap)
    {
        // VertexShader에서는 SampleLevel 사용
        float3 heightMap = g_heightTexture.SampleLevel(g_sampler, input.texcoord, 0.0).xyz;
        heightMap = 2 * heightMap - 1.0;
        
        pos.xyz += output.normalWorld * heightMap * heightScale;
    }

    output.posWorld = pos.xyz; // 월드 위치 따로 저장

    pos = mul(pos, view);
    pos = mul(pos, projection);

    output.posProj = pos;
    output.texcoord = input.texcoord;
    output.tangentWorld = tangentWorld.xyz;

    output.color = float3(0.0f, 0.0f, 0.0f);

    return output;
}

  • HeightMap의 요소들 역시
    0~1로 되어있는 ‘강도’에 대한 수치 이기에
    이들을 2* -1 해줌으로서 [0,1] -> [-1,1] 로 범위 변환

  • 그리고 이들은 각각의 표면 방향(Normal) 으로
    그 수치만큼 나아가야 하므로
    normalWorld에 곱해준다

  • 커스텀 여지를 heightScale을 통해 전달
    (Constance Buffer)

예제 정답

    if (useHeightMap)
    {
        // VertexShader에서는 SampleLevel 사용
        float heightMap = g_heightTexture.SampleLevel(g_sampler, input.texcoord, 0.0).r;
        heightMap = 2 * heightMap - 1.0;
        
        pos.xyz += output.normalWorld * heightMap * heightScale;
    }
  • HeightMap은 보통 R만 사용하며 상황에 따라 g,b 채널에 다른 값을 부여

  • 또한 이러한 Height Map의 효과는 Vertex가 어느정도 존재해야 볼 수 있다는 점을 유념
    (바닥 같은 경우는 단순한 Square이기에, Vertex 변형의 의미가 거의 없음)
    • 그렇기에 Height Map의 효과를 보려면 Vertex가 일정 수준은 있어야 함
    • 다만 이것은 ‘해상도’에 따라 결정되기에
      너무 높은 수준의 해상도를 가진 텍스쳐를 위하여
      Vertex를 늘리는 것은 성능 소모가 과해질 수 있으니 유의할 것!!
  • Height Map 대신 Displacement 라는 용어를 사용하기도 함
    (아까 위에도 말했듯 Vertex를 변환시키므로)

  • 뭔가 앨리어싱이 발생하는 것 같은데?
    • 이전에도 보았듯, 거리에 따른 LOD 레벨을 조정!
      (다만 이건 Pixel Shader의 영역임)

댓글남기기