오늘은 Sky Box에 대해 코드를 보며 학습해보자.

우선 예제코드를 실행해보면 풍경이 담긴 Sky Box가 적용되어 있는 모습을 볼 수 있다.

 

전에 Sky Box를 구현할 때는 커다란 구를 만들어두고 여기에 텍스처를 붙이고 우리가 안에서 그 텍스처를 보는 방식으로 

구현했었다. 이때 안쪽을 볼 수 있게 하기 위해 기존에 컬링으로 후면을 제거해줬는데 이를 없애고 전면을 없애는 방식으로 바꿔주어야 했고 카메라가 움직임에 따라 따라가게 해야했다. 그리고 스카이박스는 깊이를 1에 가깝게 만들어서 물체가

그려질 수 있고 Sky Box가 제일 끝에 위치하도록 만들어 줬다.

 

지금이 예제에는 구가 아닌 큐브를 통해 6면에 텍스처를 붙여주는 것으로 Sky Box를 구현해주고 있다. 이를 위해 상하좌우 풍경에 따라 다른 텍스처를 매핑해줘야한다. 이렇게 해주기 위해 3D Look up Vector를 사용하여 매핑해준다. 이 벡터를 활용하는 방법은 중심에서 가리키는 벡터에 따라 그려주는 연산을 해준다.

 

우선 쉐이더 코드를 살펴보자. 텍스처를 받아오는 변수로 TextureCube를 통해 받아오고 있으며 이를 PS단계에서 Sampler 연산을 해줄 때 pin.PosL가 Look up Vector이다. 

TextureCube gCubeMap;

float4 PS(VertexOut pin) : SV_Target
{
	return gCubeMap.Sample(samTriLinearSam, pin.PosL);
}

 

그리고 이 pin값은 VS단계에서 위치를 xyww로 밀어주고 있다. 이는 깊이값이 정확하게 1이 되도록 해주는 것이다. 이를 통해 SkyBox가 제일 뒤에 있어서 물체가 그려질 수 있다. 그리고 원점을 기준으로하는 로컬좌표를 넘겨주는 것으로 로컬좌표를 통해 각 정점의 위치로의 벡터도 나중에 만들어 줄 수 있게 한다.

VertexOut VS(VertexIn vin)
{
	VertexOut vout;

	// Set z = w so that z/w = 1 (i.e., skydome always on far plane).
	vout.PosH = mul(float4(vin.PosL, 1.0f), gWorldViewProj).xyww;

	// Use local vertex position as cubemap lookup vector.
	vout.PosL = vin.PosL;

	return vout;
}

 

그리고 밑의 코드를 보면 컬링을 꺼주고 원래 깊이가 1인 오브젝트도 그려줄 수 있도록 옵션 설정을 해주고 있다.

RasterizerState NoCull
{
	CullMode = None;
};

DepthStencilState LessEqualDSS
{
	// Make sure the depth function is LESS_EQUAL and not just LESS.  
	// Otherwise, the normalized depth values at z = 1 (NDC) will 
	// fail the depth test if the depth buffer was cleared to 1.
	DepthFunc = LESS_EQUAL;
};

 

이렇게 해주면 큐브 매핑을 통한 기본적인 Sky Box를 구현할 수 있다.

 

다음으로 다이나믹 큐브맵에 대해 알아보자. 다이나믹 큐브맵을 주변 환경을 반사하는 모습을 표현할 때 자주 활용된다.

우선 예제코드를 실행해보면 가운데 구에 주변 환경이 반사되어 보이는 것을 볼 수 있다.

 

이것을 구현하는 것은 스텐실을 활용해서 실시간으로 여섯면을 만들어줘야한다. Dynamic Cube라고 했지만 구 모양을 그려주고 있는데 이는 큐브모양을 look up vector를 통해 오려붙여서 구를 만들어 줄 수 가 있다. 구에 반사되는 모습은 카메라를 원점에 위치시키고 반사 공식을 통해 구한 각 6면의 장면을 텍스처로 만든 다음 이를 적용 시켜 주면 된다.

 

코드를 살펴보면 동적으로 큐브맵 텍스처를 만들기 위해 각 장면을 화면이 아닌 텍스처에 먼저 그려주기 위한 변수가 있다. 그리고 원래 DrawScene이 원래 Scene을 그리지만 당장 화면에 그려주는 것이 아니라 텍스처로 그려줄 수 있다는 것을 볼 수 있다. 

이때 구의 각면에 적용할 DepthStencilView도 따로 적용해주고 있다. 이는 구에 적용하는 해상도에 맞춰서 적용해줘야하기 때문이다.

//각 장면을 텍스처로 저장하기 위한 변수
ComPtr<ID3D11RenderTargetView> _dynamicCubeMapRTV[6];

//6면의 카메라
Camera _cubeMapCamera[6];


void DynamicCubeMapDemo::DrawScene()
{
	ID3D11RenderTargetView* renderTargets[1];

	// 각 면을 텍스처로 저장
	_deviceContext->RSSetViewports(1, &_cubeMapViewport);
	for (int i = 0; i < 6; ++i)
	{
		// Clear cube map face and depth buffer.
		_deviceContext->ClearRenderTargetView(_dynamicCubeMapRTV[i].Get(), reinterpret_cast<const float*>(&Colors::Silver));
		_deviceContext->ClearDepthStencilView(_dynamicCubeMapDSV.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

		// Bind cube map face as render target.
		renderTargets[0] = _dynamicCubeMapRTV[i].Get();
		_deviceContext->OMSetRenderTargets(1, renderTargets, _dynamicCubeMapDSV.Get());

		// Draw the scene with the exception of the center sphere to this cube map face.
		DrawScene(_cubeMapCamera[i], false);
	}

	// 화면에 그려줄 수 있도록 렌더타켓 초기화
	_deviceContext->RSSetViewports(1, &_viewport);
	renderTargets[0] = _renderTargetView.Get();
	_deviceContext->OMSetRenderTargets(1, renderTargets, _depthStencilView.Get());

	// Have hardware generate lower mipmap levels of cube map.
	_deviceContext->GenerateMips(_dynamicCubeMapSRV.Get());

	// 구를 포함한 전체 그려주기
	_deviceContext->ClearRenderTargetView(_renderTargetView.Get(), reinterpret_cast<const float*>(&Colors::Silver));
	_deviceContext->ClearDepthStencilView(_depthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

	DrawScene(_camera, true);

	HR(_swapChain->Present(0, 0));
}

 

 

여기서 핵심은 UV매핑이 아닌 Look up Vector를 활용하여 텍스처를 그려줄 수 있고 이를 통해 환경이나 거울 같은 오브젝트를 그려줄 때도 사용할 수 있다는 것을 알 수 있다.

+ Recent posts