반응형



http://blog.naver.com/sorkelf/40157348191



이번엔 환경맵(큐브맵, Cube Map)을 알아보자


환경맵이란 모델 표면의 배경등이 비추는 모습을 재현한 것으로,필요한 소재들이나 텍스처들을 가리킨다. 

(흔히들 말하는 잘 닦은 금속이 비추는 모습) 같은거 그런거







 

① 눈 →모델 표면 반사→배경의 어딘가


우리들의 시선으로부터 한 물건의 어떤 지점에 광선을 쏜다고 생각하자

그러면 광선은 면에 입사하여 다시 반사한다.


반사한 후의 광선은 분명 어딘가 먼곳으로 가다가 반드시 어딘가에 충돌한다.

그 부딪힌곳에 적어도 무언가의 색이 반드시 있다.


이와는 반대로 그 부딪힌 곳에서부터 물건의 반사지점까지 광선을 쏜다고하자

그러면 레이는 표면에 반사해서 분명 우리들 눈에 다시 들어 온다.

이러한 원리로 인해 우리들은 표면의 색을 알 수 있다.

반사한 색을 보고 있음

 

그럼 이 부분을 셰이더에서 생각해보자


버텍스 셰이더에는 로컬 공간의 버텍스 좌표가 인수로써 들어온다.

이것에 월드 변환행렬을 곱하면 월드 공간으로 이동한다.


지금 카메의 월드 위치가 있다고 하고 그 위치로부터 월드 공간에 있는 버텍스에 대해서 레이를 쏜다.

이 레이의 방향이 [ 시선 벡터 ] 이다.


버텍스에는 이미 또 다른 [ 법선 (Normal) ] 이라는 정보도 들어 있다.

법선은 표면을 가리키는 것이는 것이므로 

우리는 시선벡터와 법선 벡터만 알면 반사벡터는 간단히 구할 수 있다.

그 다음엔 반사한 곳에 있는 배경색으로부터 색을 얻어오면 끝이다.


물론 셰이더 내에는 월드에 있는  어떤 모델의 정보를 알 수 없다

셰이더 내에서 얻을 수 있는 것은 색정보와 버텍스 컬러 그리고 입혀진 텍스처 만이기 때문이다.


여기서 생각할 수 있는 것이 [ 3D 공간에 세계는 커다란 입방체! ]다 라는 것이다.

그렇게 하면 배경의 색정보는 6면의 텍스처로 모두 표현될 수 있다



 

셰이더내에서 입방체라고 생각하면 그 모델(배경) 에 대한 정보를 셰이더내에 전달할 필요는없다

이 후에는 시선이 닿을 것이라는 커다란 텍스처의 UV를 직접 지정하여 그 색을 출력하면

모델의 표면에 주위의 색이 반사한 것 처럼 보일 것이다


이 입방체의 내측에 붙어있는 텍스처를 [ 환경 맵 ] 이라고 한다



② IDirect3DCubeTexture + reflect함수 사용하면 끝



또한 위 텍스처는 큐브맵이라고도 하며 이러한 6면의 텍스처를 IDirect3DTexture9를 6개 사용해서 표현할 수도 있지만 

DirectX에서는 큐브맵 전용 IDirect3DCubeTexture라는 인터페이스가 준비되어 있으며 이것을 사용하면 

간단히 큐브 환경맵을 만들수 있다


큐브맵은 보통 D3DXCreateCubeTextureFromFile 함수를 사용하여 불러온다


 HRESULT D3DXCreateCubeTextureFromFile(

    LPDIRECT3DDEVICE9            pDevice,
    LPCTSTR                             pSrcFile,
    LPDIRECT3DCUBETEXTURE9 *ppCubeTexture
);

pDevice는 렌더링 디바이스
pSrcFile 에는 큐브 맵이 들어간 파일명 (단 dds 포맷)。
ppCubeTexture 에 큐브맵 오브젝트의 포인터를 전달한다。


이 함수에서 사용할 수 있는 파일은 dds 포맷 파일이며 DirectX Texture Tool로 다른 파일들로부터 만들수도 있다.



이렇게 해서 만든 큐브맵 텍스처를 셰이더에 전달한다.


 // 환경 매핑 버텍스 셰이더

const char *vertexShaderStr =
float4x4 view : register(c0);          // 보통 WVP로 한번에 보냄
float4x4 proj : register(c4);
float4x4 world : register(c8);
float3 cameraPosW : register(c12); // 카메라 월드 위치

struct VS_IN {
    float3 pos : POSITION;
    float3 normal: NORMAL;
};

struct VS_OUT {
   float4 pos : POSITION;
   float3 normalW: TEXCOORD0; // 월드 좌표상의 법선
   float3 viewVecW: TEXCOORD1; // 월드 좌표상의 시선벡터
};

VS_OUT main( VS_IN In ) {
  VS_OUT Out;
  Out.pos = mul( float4(In.pos, 1.0f), world );
  Out.viewVecW = Out.pos.xyz - cameraPosW;
  Out.pos = mul( Out.pos, view );
  Out.pos = mul( Out.pos, proj );

  Out.normalW = mul( float4(In.normal, 0.0f), world );

  return Out;
}


// 환경 매핑용 픽셀 셰이더
const char *pixelShaderStr =
textureCUBE cubeTex;
samplerCUBE cubeTexSampler =
sampler_state {
  Texture = <cubeTex>;
  MinFilter = LINEAR;
  MagFilter = LINEAR;
  MipFilter = LINEAR;
};

struct VS_OUT {
  float3 normalW: TEXCOORD0;
  float3 viewVecW: TEXCOORD1;
};

float4 main( VS_OUT In ) : COLOR {
  float3 vReflect = reflect( In.viewVecW, In.normalW );
  return texCUBE(cubeTexSampler, vReflect);
}


대부분이 변수나 샘플러등의 선언이지 실제 셰이더 코드는 얼마 없다


특히 픽셀 셰이더는 달랑 2행 뿐이다.

이게 가능한 것은 셰이더 자체가 큐브맵 텍스처로부터 샘플링을 하는것을 지원해주기 때문이다


우선 큐브맵은 textureCUBE라는 텍스처 형으로 얻어진다

그리고 이 텍스처 전용 샘플러로 samplerCUBE를 지원한다


texCUBE 함수를 통해 samplerCUBE샘플러에 대해서 반사 벡터를 전달하면 

그 벡터에 맞는 큐브맵의 색을 얻어 반사벡터와 입방체의 교점을 구하고

그것을 UV로 변환하고 뭐하고뭐 하고 하는 여러가지 계산을 전부 생략 가능하다


그러면 반사 벡터는 어떻게 구하는가?


3D게임 프로그래밍 & 컴퓨터 그래픽을 위한 수학 1판 160쪽에 있는 (2판이 없어 제길..)




R = 2(N·L)N-L 을 사용해도 되지만

셰이더에서는 reflect라는 함수를 지원하며 (내부가 저렇게 되있긴 하지만..)

입사벡터와 면의 법선벡터로부터 시선벡터에 진행방향인 반사벡터를 구해준다


시선벡터는 카메라의 위치와 카메라가 보고 있는 점을 알수 있으면 구할수 있다

(월드좌표로 변한된 버텍스 위치 - 카메라의 월드 좌표상의 위치)


이것을 TEXCOORD1로 하여 법선벡터와 함께 픽셀 셰이더에 보낸다



③ 애플리케이션 프로그램


애플리케이션에서는 IDirect3DDevice9::SetTexture 함수로 큐브맵 텍스처를 전달하고

버텍스 셰이더와 픽셀 셰이더를 각각 렌더링 디바이스에 설정하고 렌더링 하면 된다


 // 카메라 갱신

D3DXVECTOR3 cameraPos(350.0f * cos(a*0.5f), 450.0f * sin(0.2f*a), 350.0f * sin(a*0.5f));

g_pD3DDev->SetTexture(0, cubeTex); // 큐브맵 세팅

g_pD3DDev->SetVertexShader(vertexShader);
g_pD3DDev->SetPixelShader(pixelShader);

g_pD3DDev->SetVertexShaderConstantF(0, (float*)&view, 4);   //보통 SetMatrix로 한번에 보냄
g_pD3DDev->SetVertexShaderConstantF(4, (float*)&proj, 4);
g_pD3DDev->SetVertexShaderConstantF(8, (float*)&world, 4);
g_pD3DDev->SetVertexShaderConstantF(12, (float*)&cameraPos, 4);

cube->DrawSubset(0);


어려울지도 모르지만 봐왔듯이 환경 매핑은 큐브맵만 있으면 의외로 간단하다

반응형

+ Recent posts