일반적으로 Deferred 시스템에서 오브젝트의 위치값은 따로 저장하지 않고 깊이값을 이용해 계산해내는 방식을 사용한다. 추가적인 RenderTarget 사용을 없앨 수 있기 때문이다. 깊이값으로 위치값을 계산하는데는 여러 방법이 있는데 그 중 몇가지만 소개하고자한다.
1. Projection-Inverse 행렬 사용
가장 간단한 방법은 깊이 버퍼에 저장되는 값을 계산하는 과정을 거꾸로 적용하는 것이다. 깊이 버퍼에는 위치에 View-Projection 행렬을 적용한 값을 저장하고, 위치를 계산할 때는 깊이 버퍼에 저장된 값에 Projection 행렬의 역행렬을 적용하는 것이다. Projection 행렬의 역행렬을 적용하면 View-공간 위치를 얻을 수 있고 View-Projection행렬의 역행렬을 적용하면 World-공간 위치를 얻을 수 있다.
코드를 보면...
* 깊이 저장
VSOUTPUT VS(VSINPUT input)
{
...
output.position = mul(input.position, matWVP);
output.depth = output.position.z/output.position.w;
...
}
PSOUTPUT PS(VSOUTPUT input)
{
...
output.depth = input.depth;
...
}
* 위치 계산
float4 PS (VSOUTPUT input)
{
float depth = tex2D(DepthBuffer, input.screencoord);
float4 PosInProj = {input.screencoord.x, input.screencoord.y, depth , 1};
float4 PosInView = mul(PosInProj , matProjInverse);
PosInView /= PosInView .w;
...
}
가장 간단한 방식이긴 하지만 행렬 연산이기 때문에 비용이 크다.
2. 카메라에서 원평면으로의 광선
또 다른 방법은 정점 쉐이더에서 카메라에서 원평면까지의 벡터를 계산하고 픽셀 쉐이더에서 이 벡터와 깊이값을 사용해 위치를 계산하는 방법이다. 깊이 버퍼에는 Linear-깊이 값을 저장하고, 카메라-To-원평면 벡터에 이 깊이 값을 곱하면 위치값을 구할 수 있다.
우선, 이 방법은 스크린-공간 사각형을 렌더링할 때 사용할 수 있다. 정점 쉐이더에서 카메라-To-정점 벡터를 구해서 픽셀 쉐이더에 넘겨주고, 픽셀 쉐이더에서는 이 벡터들이 선형 보간되어 해당 픽셀을 지나는 카메라-To-원평면 벡터(위 그림의 파란 백터)가 된다. 그럼 구하고자 하는 위치(위 그림의 빨간점)는 이 벡터위에 있게 된다.
이제 이 벡터의 길이만 조절해주면 카메라-To-위치 벡터(위 그림의 녹색 벡터)가 된다. 이 때 사용하는 것이 깊이-버퍼에 저장된 값이다. 깊이-버퍼에는 해당 위치의 Linear-깊이 값이 저장된다. 위 그림에서 보면 Depth/Far_Distance 값이 저장된다. 카메라-To-원평면 벡터에 이 값을 곱하기만 하면 카메라-To-위치 벡터가 된다. 정점 쉐이더에서 카메라-To-정점 벡터가 View-공간에서 계산되었다면 카메라-To-위치 벡터가 그대로 View-공간 위치가 된다. 카메라-To-정점 벡터가 World-공간에서 계산되었다면 카메라의 World-공간 위치에 카메라-To-위치 벡터를 더하면 World-공간 위치를 구할 수 있다. View-공간 위치를 구하는 것이 비용상 이득이다.
코드를 보면,
* 깊이 저장
VSOUTPUT VS(VSINPUT input)
{
...
output.depth = PosInView.z / g_FarDistance;
...
}
PSOUTPUT PS(VSOUTPUT input)
{
...
output.depth = input.depth;
...
}
* 위치 계산
VSOUTPUT VS(VSINPUT input)
{
...
//View-공간 카메라-To-정점 벡터
//= View-공간 위치 - View-공간 카메라 위치
//= View-공간 위치 - float3(0,0,0) = View-공간 위치
ouput.ray = PosInView;
...
}
PSOUTPUT PS(VSOUTPUT input)
{
...
//스크린-공간 사각형을 사용하기 때문에 기하의 텍스쳐 좌표가 곧 스크린 좌표가 된다.
float depth = tex2D(DepthBuffer, input.texcoord);
float3 PosInView = input.ray * depth;
...
}
이 방법은 곱셈 한번으로 위치를 구할 수 있기 때문에 첫번째 방법에 비해 비용이 훨씬 싸다. 그리고 깊이 버퍼에 Linear-깊이를 저장하기 때문에 Linear-깊이가 필요한 다른 Post-이펙트에서 깊이 버퍼 값을 그대로 사용할 수 있다. 단점은 스크린-공간 사각형을 렌더링할 때만 사용할 수 있다는 것이다.
3. 카메라에서 기하(geometry)까지의 광선
Deferred 시스템에서는 일반적으로 Point Light 나 Spot Light 를 렌더링할 때는 Light 범위에 맞는 기하(구, 원뿔등) 를 사용하기 때문에 두번째 방법을 그대로 사용할 수 없다. 이 방법은 스크린-공간 사각형을 포함하여 어떤 기하 형태든 사용할 수 있도록 두번째 방식을 약간 변형한 것이다. Ray를 이용하는 것은 동일하지만 깊이-버퍼에 저장하는 값이 다르다. 깊이 버퍼에 Linear-깊이가 아닌 카메라까지의 거리를 저장한다.
위치를 계산할 때는 우선 정점 쉐이더에서 받은 벡터를 정규화한다. 방향이 있으므로 크기만 있으면 카메라-To-위치 벡터를 구할 수 있다. 크기는 깊이-버퍼에 저장되어 있으므로 정규화된 벡터에 깊이-버퍼에 저장된 값을 곱하기만 하면 된다. 여기서 주의할 점은 정점 쉐이더에서 벡터를 정규화하는 것은 의미가 없다는 것이다. 정점 쉐이더에서 정규화를 하더라도 선형 보간되어 픽셀 쉐이더로 넘어가면 정규화되지 않은 벡터가 되기 때문이다. 따라서 꼭 픽셀 쉐이더에서 정규화해주어야 한다.
코드를 보면,
* 깊이 저장
VSOUTPUT VS(VSINPUT input)
{
...
output.depth = length(PosInView);
...
}
PSOUTPUT PS(VSOUTPUT input)
{
...
output.depth = input.depth;
...
}
* 위치 계산
VSOUTPUT VS(VSINPUT input)
{
...
//View-공간 카메라-To-정점 벡터
//= View-공간 위치 - View-공간 카메라 위치
//= View-공간 위치 - float3(0,0,0) = View-공간 위치
ouput.ray = PosInView;
...
}
PSOUTPUT PS(VSOUTPUT input)
{
...
float depth = tex2D(DepthBuffer, input.screencoord);
float3 PosInView = normalize(input.ray) * depth;
...
}
4. 픽셀 쉐이더에서 카메라-To-원평면 광선 계산
2, 3번째 방법의 단점은 해당 픽셀 외에 다른 픽셀의 위치를 계산할 수 없다는 것이다. SSAO같은 Post-이펙트는 다른 픽셀에서의 위치값이 필요하기 때문에 또 다른 방식이 필요하다. 2, 3번째 방식은 정점 쉐이더에서 Ray를 계산해서 픽셀 쉐이더에 넘겨주는 방식이기 때문에 다른 픽셀에 대한 Ray를 얻을 수 없다. 정점 쉐이더가 아닌 픽셀 쉐이더에서 Ray를 계산할 수 있으면 이런 제한이 사라진다.
픽셀 쉐이더에서 Ray를 계산하는 것은 간단하다. 원평면의 네 꼭지점을 스크린 좌표로 보간하기만 하면 된다. 원평면의 top-right 점과 left-bottom 점을 보간하면 되는데, View-공간 Ray를 계산한다면 실제로 픽셀 쉐이더에서 필요한 값은 top-right 점의 View-공간 위치 하나이다. left-bottom 점의 View-공간 위치는 top-right 점의 부호만 바꾸면 되기 때문이다.
깊이-버퍼에 저장되는 값은 두번째 방법과 동일하다. 두번재 방법과 마찬가지로 카메라-To-원평면 Ray를 사용하기 때문이다.
코드를 보면,
* 위치 계산
PSOUTPUT PS(VSOUTPUT input)
{
...
float3 ray = lerp(-g_FarTopRight, g_FarTopRight, input.screencoord);
float depth = tex2D(DepthBuffer, input.screencoord);
float3 PosInView = ray * depth;
...
}
이 방식의 또 다른 장점은 두번째 방법과 마찬가지로 어떤 기하 모양을 사용하든 적용할 수 있다는 것이다. 또한 두번째 방법과 비교했을 때 normalize() 연산을 하는 대신에 lerp() 연산을 하기 때문에 추가 비용도 없다.
Reference:
http://mynameismjp.wordpress.com/2009/03/10/reconstructing-position-from-depth/
'3D Programming' 카테고리의 다른 글
wxWidget 어플 실행시 Cannot initialize OLE 에러 (0) | 2011.08.19 |
---|---|
Tangent 공간 <=> Object 공간 변환 (0) | 2011.03.01 |
Material Editor - Node별 Preview (0) | 2011.01.29 |
wxWidget 프로젝트 설정 (0) | 2010.12.30 |