본문 바로가기
셰이더 (Shader)/셰이더 프로그래밍 입문 - Pope Kim (완)

[HLSL] 챕터8 - 환경매핑

by Minkyu Lee 2023. 3. 20.

환경매핑 (environment mapping) 셰이더 제작하기

 

// 환경매핑이란?

주위환경이 거울처럼 표면에 반사되는 것을 재현하는 기법이다.

표면이 입사광 전혀 흡수하지 않고 반사하면 거울이 된다.

끝없는 빛의 반사 구하는 것 어렵다.

따라서 이를 속임수를 써서 표현하는 것이다.

 

주위환경 미리 텍스쳐안에 저장해놓는다.

이 텍스쳐를 입힌다.

 

// 주위환경을 360도 전부 텍스쳐에 담는 방법은 무엇인가?

여러방법이 있을 수 있다.

그 중에서 상하, 좌우, 전후 6개의 텍스쳐를 만들어 물체 주위를 감싸는 방법이 있다.

마치 스카이박스 원리와 같다.

 

// 이 텍스쳐를 어떻게 사용하는가?

반사한 빛이 표면에 정반사 되어 눈에 들어온다.

이 방향성을 뒤집는 것이다.

아래와 그림과 같이 된다.

이를 이용해 부딪힌 텍셀의 값을 가져온다.

 

반사벡터는 다른 물체에서 반사되어 들어오는 빛을 뒤집는다.

카메라벡터는 거울표면에서 정반사된 벡터를 뒤집는다.

reflect 함수를 사용하면 쉽게 구할 수 있다.

 

// 입방체맵 6개의 텍스쳐 중 한개에 부딪히는 것을 어떻게 찾아낼 것인가?

내장함수를 사용하면 된다.

 

// 입방체 이미지 제작법

.dds 파일로 제작한다. 플러그인 사용해서 포토샵에서 제작 가능하다.

그 외 다른 방법도 있다.

 

정점셰이더

float4x4 gWorldMatrix;
float4x4 gWorldViewProjectionMatrix;
float4 gWorldLightPosition;
float4 gWorldCameraPosition;

struct VS_INPUT
{
   float4 mPosition : POSITION;
   float3 mNormal : NORMAL;
   float3 mTangent : TANGENT;
   float3 mBinormal : BINORMAL;
   float2 mUV : TEXCOORD0;
};

struct VS_OUTPUT
{
   float4 mPosition : POSITION;
   float2 mUV : TEXCOORD0;
   float3 mLightDir : TEXCOORD1;
   float3 mViewDir : TEXCOORD2;
   
   float3 T : TEXCOORD3;
   float3 B : TEXCOORD4;
   float3 N : TEXCOORD5;
};

VS_OUTPUT vs_main(VS_INPUT Input)
{
   VS_OUTPUT Output;
   
   // base
   Output.mPosition = mul(Input.mPosition, gWorldViewProjectionMatrix);
   Output.mUV = Input.mUV;
   
   // world pos vector
   float4 worldPosition = mul(Input.mPosition, gWorldMatrix);
   float3 lightDir = worldPosition.xyz - gWorldLightPosition.xyz;
   Output.mLightDir = normalize(lightDir);
   float3 viewDir = normalize(worldPosition.xyz - gWorldCameraPosition.xyz);
   Output.mViewDir = viewDir;
   
   // object pos vector to world pos vector
   float3 worldNormal = mul(Input.mNormal, (float3x3)gWorldMatrix);
   Output.N = normalize(worldNormal);
   
   float3 worldTangent = mul(Input.mTangent, (float3x3)gWorldMatrix);
   Output.T = normalize(worldTangent);
   
   float3 worldBinormal = mul(Input.mBinormal, (float3x3)gWorldMatrix);
   Output.B = normalize(worldBinormal);
   
   return Output;
}

이전 챕터와 달라진 내용이 없다.

 

픽셀셰이더

sampler2D DiffuseSampler;
sampler2D SpecularSampler;
sampler2D NormalSampler;
samplerCUBE EnvironmentSampler;

float3 gLightColor;

struct PS_INPUT
{
   float2 mUV : TEXCOORD0;
   float3 mLightDir : TEXCOORD1;
   float3 mViewDir : TEXCOORD2;
   
   float3 T : TEXCOORD3;
   float3 B : TEXCOORD4;
   float3 N : TEXCOORD5;
};

float4 ps_main(PS_INPUT Input) : COLOR
{
   // World Normal
   float3 tangentNormal = tex2D(NormalSampler, Input.mUV).xyz; // don't use alpha.
   tangentNormal = normalize(tangentNormal *2 -1);
   tangentNormal = float3(0,0,1);
   
   float3x3 TBN = float3x3(normalize(Input.T), normalize(Input.B), normalize(Input.N));
   TBN = transpose(TBN);
   float3 worldNormal = mul(TBN, tangentNormal); //float3x3 is rows major matrix.
   
   // Lighting
   float4 albedo = tex2D(DiffuseSampler, Input.mUV);
   float3 lightDir = normalize(Input.mLightDir);
   float3 diffuse = saturate(dot(-lightDir, worldNormal));
   diffuse = gLightColor * albedo.rgb * diffuse;
   
   float3 viewDir = normalize(Input.mViewDir);
   float3 specular = 0;
   if(diffuse.x > 0)
   {
      float3 reflection = reflect(lightDir, worldNormal); // important argument order.
      specular = saturate(dot(reflection, -viewDir));
      specular = pow(specular, 20.0);
      
      float4 specularIntensity = tex2D(SpecularSampler, Input.mUV);
      specular *= specularIntensity.rgb * gLightColor ;
   }
   float3 viewReflect = reflect(viewDir, worldNormal);
   float3 environment = texCUBE(EnvironmentSampler, viewReflect).rgb;
   
   float3 ambient = float3(0.1, 0.1, 0.1) * albedo;
   
   return float4(ambient + diffuse + specular + environment * 0.5f, 1.0);
}

새로 추가한 것은 입방체 텍스쳐 밖에 없다.

입방체 텍스쳐는 samplerCUBE를 이용한다.

 

카메라 벡터를 if문 밖으로 뺀다.

카메라 벡터의 반사벡터를 구한다.

환경맵을 샘플링한다.

환경맵을 구성하는 텍스쳐 6개 중 어떤 것을 읽어와야하는지 처리해주는 함수를 사용한다.

texCUBE 함수이다.

 

첫번째 인자로 입방체 텍스쳐 샘플러를 받는다.

두번째 인자로 반사벡터를 받는다.

단, 반사벡터는 월드공간에 있어야한다.

이전 챕터에서 월드공간에서 조명계산한 이유가 바로 여기에 있다.

 

결과를 보면,

반사되는 것은 보이지만 울퉁불퉁해서 잘보이지 않는다.

울퉁불퉁하지 않게 노말을 수정한다.

노말이 0,0,1을 가리키면 평평해진다.

 

// 환경매핑 결과를 기존 조명결과에 더하는 방법은?

마음대로 하면 된다.

필자는 그냥 더하고 환경매핑 강도를 절반으로 줄였다.

또는 텍스쳐를 이용해 픽셀마다 환경맵 양을 조절하는 것도 좋은 방법이다.

 

  • 환경매핑과 비슷한 기타 고급기법

// 구면조화 조명기법

입방체 텍스쳐 이용한 주변광 처리 기법이다.

 

게임속 장면을, 미리 입방체 텍스처로 만든다.

32x32 정도로 텍스쳐 크기를 줄여버린다.

배경 색이 모두 혼합된 매우 흐릿한 텍스쳐가 된다.

난반사광과 비슷해지는 효과가 생긴다.

texCUBE함수를 이용해 주변광으로 사용한다.

 

texCUBE함수 사용이 마땅치 않는 경우

위 입방체 결과를 계수로 바꾼다.

셰이더에서 수학공식을 통해 재구성한다.

이 방법을 구면조화 조명기법이라고 한다. (spherical harmonics)

 

// 렌더타깃

실시간 입방체맵 만드는 기법도 있다.

실시간으로 게임 장면을 6방향 텍스쳐 위에 그린다.

이를 입방체맵으로 사용한다.

이렇게 실시간으로 렌더링 결과 받는 텍스쳐를 렌더타깃이라고 한다. (render target)

챕터 10에서 배울 것이다.

 

댓글