이번에 다룰 내용은 꿈과 희망의 화면좌표 기준으로 2D Sprite 그리기 이다.

3D API ( OpenGL/ DirectX( Direct3D ) ) 를 이용해 2D 게임을 만들고 싶은 인간이라면

한번 들여다 봐도 손해보진 않을것이다.

이론적인 부분은 앞서 [ 3D Programming ] 코너에서

- http://blog.naver.com/kzh8055/140050063556

소개해 뒀으니 참고할 인간은 참고 하길 바란다. 사실 별 내용도 없지만서도.

하여간 바로 소스 코드로 들어간다.

//----------------------------------------------------------------------------
// 화면 좌표기준으로 2D Sprite 를 그린다
//----------------------------------------------------------------------------
// 아래의 모듈을 별도의 함수로 만드는 것이 편리할것이다.
// 뭐 가령 DrawSprite( Texture객체 포인터, 위치 좌표 ... ) 식으로 말이다
//----------------------------------------------------------------------------
float Sprite2DPosX = 0.0f; // 화면에서의 Sprite X좌표
float Sprite2DPosY = 0.0f; // 화면에서의 Sprite Y좌표
float Sprite2DWidth = 200.0f; // Sprite 의 폭( 임시 )
float Sprite2DHeight = 200.0f; // Sprite 의 높이( 임시 )

//--------------------------------------------------------
// 현재 View Port 의 가로, 세로 길이를 얻어온다
//--------------------------------------------------------
float ViewPortWidth = 0.0f;
float ViewPortHeight = 0.0f;

D3DVIEWPORT9 vp;

g_pd3dDevice->GetViewport( &vp ); //현재 View Port 정보를 얻어온다

ViewPortWidth = vp.Width;
ViewPortHeight = vp.Height;

//--------------------------------------------------------
// 화면 좌표를 투영 좌표계로 변환
//--------------------------------------------------------
float PosStartX = ( ( 2.0 * Sprite2DPosX ) / ViewPortWidth ) - 1.0f;
float PosStartY = ( ( -2.0 * Sprite2DPosY ) / ViewPortHeight ) + 1.0f;
float PosEndX = ( ( 2.0 * ( Sprite2DPosX + Sprite2DWidth ) ) / ViewPortWidth ) - 1.0f;
float PosEndY = ( ( -2.0 * ( Sprite2DPosY + Sprite2DHeight ) ) / ViewPortHeight ) + 1.0f;

//-----------------------------
// 기하 변환 행렬들을 설정
//-----------------------------
D3DXMATRIXA16 matWorld; // 월드 변환
D3DXMATRIXA16 matView; // 뷰 변환
D3DXMATRIXA16 matOrtho; // 투영 변환( 직교 )

D3DXMatrixIdentity( &matWorld );
D3DXMatrixIdentity( &matOrtho );
D3DXMatrixIdentity( &matView );

g_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );
g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );

//--------------------------------------------------------------
// 직교 투영 변환 행렬
//--------------------------------------------------------------
// 카메라 좌표계의 시야 절두체를
// ( -1.0f, -1.0f, -1.0f ) ~ ( 1.0f, 1.0f, 0.0f )
// 범위를 갖는 길이 2.0f인 정육 면체로 만든다.
//--------------------------------------------------------------
D3DXMatrixOrthoOffCenterLH( &matOrtho, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 0.0f );
g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matOrtho );


//----------------------------------
// 정점 정의( 투영 좌표계 기준 )
//----------------------------------
MYVERTEX Vertices[] =
{
{ PosStartX, PosStartY, 0.0f, 0, 0 },
{ PosEndX, PosStartY, 0.0f, 1, 0 },
{ PosStartX, PosEndY, 0.0f, 0, 1 },
{ PosEndX, PosEndY, 0.0f, 1, 1 },
};

//----------------------------------------------------------------

// * Sprite를 그리기전에 Z Buffer를 꺼둔다.

//----------------------------------------------------------------

// 아니면 Sprite 의 Z 좌표를 다른 3D 객체 보다 앞쪽에 그리는

// 것도 나쁘지 않을것이다.

//-----------------------------------------------------------------

g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, false );
g_pd3dDevice->SetTexture( 0, g_pTexBillboard[2] ); // Sprite로 그릴 Texture 를 설정한다
//----------------------------------------
// Rendering Sprite!!!
//----------------------------------------
g_pd3dDevice->DrawPrimitiveUP( D3DPT_TRIANGLESTRIP, 2, Vertices, sizeof(MYVERTEX));

그럼 이번에는 Quaternion에 의한 회전을 해보도록 하겠습니다.

Euler의 각도로는 그냥 해당하는 축에 대해서 각도를 늘려주면 되지만

Quaternion은 어떻게 해야지 회전이 되는걸까요?

그럼 DirectX에 있는 Quaternion함수를 사용해보겠습니다.

먼저 앞에서 사용했던 소스에 이번에 비행기를 또 추가합니다.

위치는 0,0,0인 원점에 놓이게 합니다.

이정도는 알아서 해주세요. 소스 없습니다.

그럼 아래와 같은 상태가 될것입니다.

그럼 현재 상태를 90도 회전하려면 어떻게 해야할까요?

일단 뭐든지 회전을 하려면 기준축을 정해야하는것은 당연하기때문에

여러가지 방법을 알아보도록 하겠습니다.

1.Rotation axis, Rotation angle을 정의하여 회전하는 방법

Rotation axis = 0,1,0 이어야 하죠? 이유는 당연하구요.

angle = D3DX_PI/2(90도)

이렇게 해서 회전된 quaternion을 이용하여 matrix로 변환하여

회전시키는 방법.

소스)

Frame_Move에 아래소스를 추가
//static int qut_cnt=0;
D3DXQUATERNION rot_q; //The variable will be used to transformation
D3DXVECTOR3 rot_axis(0.0f,1.0f,0.0f); //Rotation Axis
D3DXQuaternionRotationAxis(&rot_q,&rot_axis,D3DX_PI/2);
D3DXMatrixRotationQuaternion(&g_Obj[17].m_mWorld,&rot_q);
g_Obj[17].m_mWorld *= 0.1f;
g_Obj[17].m_mWorld._44 = 1.0f;

이렇게 하면

위와 같이 90도 회전한 상태가 된것을 보실 수 있을것입니다.

그런데 회전방향이 이상하네요. 음... 이것도 왼손으로 90도 돌아간 상태로 나오는군요 역시...

이넘의 왼손좌표계...

아무튼 이상은 없습니다.

그럼 이것을 또한 Animation해보지요.

소스)

FrameMove에 추가한 소스를 아래와 같이 수정

static int qut_cnt=0;
D3DXQUATERNION rot_q; //The variable will be used to transformation
D3DXVECTOR3 rot_axis(0.0f,1.0f,0.0f); //Rotation Axis
D3DXQuaternionRotationAxis(&rot_q,&rot_axis,D3DX_PI/200*qut_cnt++);
D3DXMatrixRotationQuaternion(&g_Obj[17].m_mWorld,&rot_q);
g_Obj[17].m_mWorld *= 0.1f;
g_Obj[17].m_mWorld._44 = 1.0f;

이렇게 수정하면 count가 계속 올라가면서 Quaternion을 생성하고 또한 이를 Matrix로 변환해서 회전을 하도록 Animation하게

할 수 있습니다.

소스는 shadowmap(tutorial-61)-piccoro77.cpp입니다.

아무튼 이렇게 회전하는 방법이 있을테구요.

다른방법은로는

2.그냥 RPY를 이용하는 방법

static int qut_cnt=0;
D3DXQUATERNION rot_q; //The variable will be used to transformation
D3DXQuaternionRotationYawPitchRoll(&rot_q,D3DX_PI/200*qut_cnt++,0.0f,0.0f);
D3DXMatrixRotationQuaternion(&g_Obj[17].m_mWorld,&rot_q);
g_Obj[17].m_mWorld *= 0.1f;
g_Obj[17].m_mWorld._44 = 1.0f;

참고로 여기서는 Yaw가 Y축, Pitch가 X축, Roll가 X축이랍니다.

Euler각도는 각도의 순서를 잘 정의해줘야 사용할 수 있으니 주의가 필요하겠죠?

Soft-Edged Shadows

by Anirudh.s Shastry

 

Introduction

 

원래 동적 그림자 기법은 한정된 방식으로만 가능했다. 그러나 강력한 프로그래밍 가능한 그래픽 하드웨어의 발전과 함께 동적 그림자 기법은 조명 매핑(light mapping) 과 같은 정적 기법이나 투영 그림자(projected shadow) 와 같은 약간 동적인 기법들을 거의 완벽히 대체하게 되었다. 동적 그림자 기법으로 일반적으로 사용되는 것은 그림자 부피(shadow volume)와 그림자 매핑(shadow mapping)이다.

 

A Closer look

 

그림자 부피는 닫힌 부피를 생성하기 위해서 조명 방향으로 기하도형을 밀어낸 것(extrusion)을 요구하는 기하도형 기반 기법이다. 그리고 나서 ray casting 을 통해서 씬 내의 그림자가 드리운 부분이 결정될 수 있다(일반적으로 스텐실 버퍼가 ray-casting 을 위해서 사용된다). 이 기법은 픽셀 단위로 정확하며 aliasing 문제를 발생시키지 않지만, 다른 기법들과 마찬가지로 자신만의 단점을 가지고 있다. 이 기법의 두 가지 큰 문제는 그것이 기하도형에 매우 의존적이며 fill-rate 에 민감하다는 것이다. 이 때문에 그림자 매핑이 천천히 대중화되어 가고 있다.

 

한편 그림자 매핑은 뷰의 조명 위치로부터의 씬 깊이를 렌더링하고 이 깊이 정보를 사용하여 씬 내의 어떠한 위치에 그림자가 드리워져야 하는지를 결정하는 것을 포함하는 이미지 공간 기법이다. 이 기법이 몇 가지 이점을 가지고 있기는 하지만, 그것은 aliasing 인공물과 z-fighting 을 피할 수 없다. 그러나 이에 대한 해결책이 존재하고 장점이 단점보다는 크기 때문에 이것이 이 기사를 위해서 내가 선택하는 기법이 될 것이다.

 

Soft shadows

 

딱딱한 그림자는 씬의 사실감을 떨어뜨린다. 그러므로 우리는 씬의 가시 품질을 증가시키기 위해서 부드러운 그림자를 써서 속일 필요가 있다. 매우 열광적인 많은 PHD 학생들이 부드러운 그림자를구현하기 위한 기법을 설명하는 문서를 가지고 왔다. 그러나 이 기법들 중 대부분은 복잡한 씬을 고려했을 때 실시간에 구현하기 어려웠다. 이들 기법들 중 약간의 제약을 극복할 수 있는 하드웨어를 가지게 될 때까지 우리에게는 더 현실적인 기법이 필요할 것이다.

 

이 기사에서 나는 그림자 맵을 사용해 부드러운 모서리를 가진 그림자를 생성하기 위한 이미지 공간 기법을 제시한다. 이 기법은 완벽한 부드러운 그림자를 생성하지는 않는다(no umbar-penumbra). But it not only solves the aliasing problems of shadow mapping, it improves the visual quality by achieving aesthetically pleasing soft edged shadows.

 

So how does it work?

 

먼저 우리는 뷰에서의 조명 위치로부터의 씬 깊이를 부동 소수점 버퍼로 렌더링함으로써 일반적인 그림자 맵을 생성한다. 그리고 나서 그림자를 사용해 씬을 렌더링하는 대신에 그림자가 있는 영역을 스크린 크기의 버퍼로 렌더링한다. 이제 bloom 필터를 사용해 블러링(blur)하고 그것을 스크린 공간에 있는 씬에다가 다시 투영한다. 쉬워 보이는가?

 

이 기사에서 우리는 집중 조명(spot light)만을 다룬다. 그러나 이 기사는 점 조명(point light)를 다루기 위해서도 쉽게 확장될 수 있다.

 

여기에 그 단계가 나와 있다 :

 

    - 부동 소수점 버퍼에 씬의 깊이를 씀으로써 일반적인 그림자 맵을 생성한다.

    - 깊이 비교 후에 씬 내의 그림자 영역을 고정 소수점 텍스처에 조명 없이 넣는다.

    - 위의 버퍼를 bloom 필터를 사용해 블러링한다( 이 기사에서는

      sperable Gaussian 필터를 사용하지만, 다른 필터를 사용해도 상관없다).

    - 스크린 공간에서 씬에 블러링된 버퍼를 투영하여 전체 조명을 따라 존재하는

      cool soft-edged 그림자를 획득한다.

 

Step1 : Rendering the Shadow map

 

먼저 우리는 씬 깊이를 저장할 수 있는 텍스처를 생성할 필요가 있다. 우리는 이를 렌더 타겟으로 사용할 것이기 때문에, 우리는 텍스처 서피스 데이터를 저장할 서피스도 생성할 필요가 있을 것이다. 그 텍스처는 반드시 부동 소수점이어야 한다. 왜냐하면 깊이 값의 범위가 넓기 때문이다. R32F 포맷은 만족스런 정밀도를 가지고 있으며, 그래서 우리는 그것을 사용할 것이다. 여기에 텍스처를 생성하는 데 사용된 코드가 있다.

 

// 그림자 맵 생성
if( FAILED( g_pd3dDevice->CreateTexture( SHADOW_MAP_SIZE,

                                         SHADOW_MAP_SIZE, 1,
                                         D3DUSAGE_RENDERTARGET, D3DFMT_R32F,
                                         D3DPOOL_DEFAULT, &g_pShadowMap,
                                         NULL ) ) )
{
    MessageBox( g_hWnd, "Unable to create shadow map!",
               "Error", MB_OK | MB_ICONERROR );
    return E_FAIL;
}

// 텍스처의 서피스 획득
g_pShadowMap->GetSurfaceLevel( 0, &g_pShadowSurf );

 

이제 그림자 맵을 생성하기 위해서 우리는 씬의 깊이를 그림자 맵에 렌더링할 필요가 있다. 이를 위해서 조명의 world-view-projection 행렬을 사용해 씬을 렌더링해야만 한다. 아래에 그 행렬을 생성하는 방법이 나와 있다.

 

// 정규 뷰 행렬
D3DXMatrixLookAtLH( &matView, &vLightPos, &vLightAim, &g_vUp );


// 조명을 위한 투영 행렬

D3DXMatrixPerspectiveFovLH( &matProj, D3DXToRadian(30.0f), 1.0f, 1.0f, 1024.0f );


// 위의 2 행렬을 월드 행렬과 곱하여 원하는 행렬 획득
matLightViewProj = matWorld * matView * matProj;

 

아래에 씬 깊이를 렌더링하기 위한 정점 및 픽셀 쉐이더가 나와 있다.

 

// 그림자 생성 정점 쉐이더


struct VSOUTPUT_SHADOW
{
   float4 vPosition    : POSITION;
   float  fDepth       : TEXCOORD0;
};

VSOUTPUT_SHADOW VS_Shadow( float4 inPosition : POSITION )
{
   // 출력 구조체
   VSOUTPUT_SHADOW OUT = (VSOUTPUT_SHADOW)0;
   // 변환된 위치를 출력
   OUT.vPosition = mul( inPosition, g_matLightViewProj );
   // 씬 깊이를 출력
   OUT.fDepth = OUT.vPosition.z;
   return OUT;
}

 

여기에서 우리는 조명의 위치에 world-view-projection 행렬(g_matLightViewProj)을 곱했으며, 변환된 위치의 z 값을 깊이로 사용했다. 픽셀 쉐이더에서 우리는 색상값으로 그 깊이를 출력한다.

 

float4  PS_Shadow( VSOUTPUT_SHADOW IN ) : COLOR0
{
   // 씬 깊이를 출력한다

   return float4( IN.fDepth, IN.fDepth, IN.fDepth, 1.0f );
}

 

자 봐라! 우리는 그림자 맵을 가지게 되었다. 아래에는 그림자의 색상으로 코딩된 버전이 나와 있다. 어두운 파란색은 깊이 값이 낮음을 의미하고, 밝은 파란색은 깊이 값이 높음을 의미한다.

 


 
Setp 2 : Rendering the Shadowed scene into a buffer
 
다음으로 씬에서 우리는 그림자가 있는 영역을 오프스크린 버퍼로 렌더링할 필요가 있다. 그럼으로써 우리는 그것을 블러링하고 그것을 다시 씬으로 투영할 수 있다. 그렇게 하기 위해서 먼저 씬 내에서 그림자가 있는 영역을 스크린 크기의 고정 소수점 텍스처에 렌더링한다.
 

// 스크린 크기 버퍼 맵을 생성
if( FAILED( g_pd3dDevice->CreateTexture( SCREEN_WIDTH, SCREEN_HEIGHT, 1,
            D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT,

            &g_pScreenMap, NULL ) ) )
{
   MessageBox( g_hWnd, "Unable to create screen map!",
               "Error", MB_OK | MB_ICONERROR );
   return E_FAIL;
}


// 텍스처의 서피스를 획득
g_pScreenMap->GetSurfaceLevel( 0, & g_pScreenSurf );

projective 텍스처 좌표를 얻기 위해서 우리는 "texture" 행렬을 필요로 한다. 이는 투영 공간으로부터의 위치를 텍스처 공간으로 매핑할 것이다.
 
// 텍스처 행렬 생성
float fTexOffs = 0.5 + (0.5 / (float)SHADOW_MAP_SIZE);
D3DXMATRIX matTexAdj( 0.5f,     0.0f,     0.0f, 0.0f,
                      0.0f,     -0.5f,    0.0f, 0.0f,
                      0.0f,     0.0f,     1.0f, 0.0f,
                      fTexOffs, fTexOffs, 0.0f, 1.0f );

matTexture = matLightViewProj * matTexAdj;
 
우리는 깊이 비교를 통해 일반적인 그림자 요소를 획득했다. 그러나 완벽하게 조명이 비춰지는 씬을 출력하는 대신에, 우리는 단지 그림자 요소만을 출력한다. 여기에 그 작업을 하기 위한 정점 및 픽셀 쉐이더가 있다.
// 그림자 매핑 정점 쉐이더 
struct VSOUTPUT_UNLIT 
{ 
 float4 vPosition : POSITION; 
 float4 vTexCoord : TEXCOORD0; 
 float fDepth : TEXCOORD1; 
};
 VSOUTPUT_UNLIT VS_Unlit( float4 inPosition : POSITION ) 
{ 
 // 출력 구조체 
 VSOUTPUT_UNLIT OUT = (VSOUTPUT_UNLIT)0; 
 // 변환된 정점 출력 
 OUT.vPosition = mul( inPosition, g_matWorldViewProj ); 
 // 투영 텍스처 좌표 출력 
 OUT.vTexCoord = mul( inPosition, g_matTexture ); 
 // 씬 깊이 출력 
 OUT.fDepth = mul( inPosition, g_matLightViewProj ).z;
 return OUT; 
} 
우리는 percentage closer filtering(PCF) 를 사용해 들쑥날쑥한 모서리들을 부드럽게 한다. PCF 를 "수행"하기 위해서는 중심 텍셀을 둘러 싸는 8 개의 주변 텍셀을 샘플링하고(여기에서 우리는 3x3 PCF 커널을 사용한다), 모든 깊이 비교를 이용한다.
 
// 그림자 매핑 픽셀 쉐이더
float4  PS_Unlit( VSOUTPUT_UNLIT IN ) : COLOR0
{
   // 3x3 PCF 커널을 위해서 9 개의 좌표를 생성
   float4 vTexCoords[9];
   // 텍셀 크기
   float fTexelSize = 1.0f / 1024.0f;

   
// 지정된 깊이 맵 크기를 위한 텍스처 좌표를 생성
   // 4 3 5
   // 1 0 2
   // 7 6 8

   vTexCoords[0] = IN.vTexCoord;
   vTexCoords[1] = IN.vTexCoord + float4( -fTexelSize, 0.0f, 0.0f, 0.0f );
   vTexCoords[2] = IN.vTexCoord + float4(  fTexelSize, 0.0f, 0.0f, 0.0f );
   vTexCoords[3] = IN.vTexCoord + float4( 0.0f, -fTexelSize, 0.0f, 0.0f );
   vTexCoords[6] = IN.vTexCoord + float4( 0.0f,  fTexelSize, 0.0f, 0.0f );
   vTexCoords[4] = IN.vTexCoord + float4( -fTexelSize, -fTexelSize, 0.0f, 0.0f );
   vTexCoords[5] = IN.vTexCoord + float4(  fTexelSize, -fTexelSize, 0.0f, 0.0f );
   vTexCoords[7] = IN.vTexCoord + float4( -fTexelSize,  fTexelSize, 0.0f, 0.0f );
   vTexCoords[8] = IN.vTexCoord + float4(  fTexelSize,  fTexelSize, 0.0f, 0.0f );

   // 테스트하는 픽셀이 그림자 영역에 있는지를 검사하면서 샘플링
   float fShadowTerms[9];
   float fShadowTerm = 0.0f;
   forint i = 0; i < 9; i++ )
   {
      float A = tex2Dproj( ShadowSampler, vTexCoords[i] ).r;
      float B = (IN.fDepth ?0.1f);

      // 그림자 영역의 텍셀
      fShadowTerms[i] = A < B ? 0.0f : 1.0f;
      fShadowTerm     += fShadowTerms[i];
   }
   // 평균 획득
   fShadowTerm /= 9.0f;
   return fShadowTerm;
}
 
이 스크린 버퍼는 썩 괜찮다! 이제 이것을 블러링하고 다시 스크린 공간의 씬에 투영할 필요가 있다.
 

 
Step 3 : Blurring the screen buffer
 
우리는 seperable gaussian 필터를 사용해서 스크린 버퍼를 블러링하지만, Poisson 필터를 사용할 수도 있다. 현재의 렌더 타겟은 해당 서피스에 연결된 A8R8G8B8 텍스처이다. 우리는 2 개의 렌더 타겟을 필요로 하는데, 하나는 수직 경로를 위한 것이고 다른 하나는 수평 경로를 위한 것이다.
 
// 블러 맵 생성
for( int i = 0; i < 2; i++ )
{
   if( FAILED( g_pd3dDevice->CreateTexture( SCREEN_WIDTH, SCREEN_HEIGHT, 1,
                                            D3DUSAGE_RENDERTARGET,
                                            D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT,
                                            &g_pBlurMap[i], NULL ) ) )
   {
      MessageBox( g_hWnd, "Unable to create blur map!",
                  "Error", MB_OK | MB_ICONERROR );
      return E_FAIL;
   }
  // 텍스처 서피스 획득
   g_pBlurMap[i]->GetSurfaceLevel( 0, & g_pBlurSurf[i] );
}
 
우리는 15 개의 Gaussian 오프셋 및 관련 가중치를 다음 함수들을 사용해 생성했다.
 
float GetGaussianDistribution( float x, float y, float rho )
{
   float g = 1.0f / sqrt( 2.0f * 3.141592654f * rho * rho );
   return g * exp( -(x * x + y * y) / (2 * rho * rho) );
}
 
void GetGaussianOffsets( bool bHorizontal, D3DXVECTOR2 vViewportTexelSize,
                         D3DXVECTOR2* vSampleOffsets, float* fSampleWeights )
{
   // 중심텍셀 오프셋과 가중치 획득
   fSampleWeights[0] = 1.0f * GetGaussianDistribution( 0, 0, 2.0f );
   vSampleOffsets[0] = D3DXVECTOR2( 0.0f, 0.0f );
   // 남은 요소들의 오프셋 및 가중치 획득
   if( bHorizontal )
   {
      for( int i = 1; i < 15; i += 2 )
      {
         vSampleOffsets[i + 0] = D3DXVECTOR2( i * vViewportTexelSize.x, 0.0f );
         vSampleOffsets[i + 1] = D3DXVECTOR2( -i * vViewportTexelSize.x, 0.0f );
         fSampleWeights[i + 0] = 2.0f * GetGaussianDistribution( float(i + 0), 0.0f, 3.0f );
         fSampleWeights[i + 1] = 2.0f * GetGaussianDistribution( float(i + 1), 0.0f, 3.0f );
      }
   }
   else 
   {
      for( int i = 1; i < 15; i += 2 )
      {
         vSampleOffsets[i + 0] = D3DXVECTOR2( 0.0f, i * vViewportTexelSize.y );
         vSampleOffsets[i + 1] = D3DXVECTOR2( 0.0f, -i * vViewportTexelSize.y );
         fSampleWeights[i + 0] = 2.0f * GetGaussianDistribution( 0.0f, float(i + 0), 3.0f );
         fSampleWeights[i + 1] = 2.0f * GetGaussianDistribution( 0.0f, float(i + 1), 3.0f );
      }
   }
}
 
스크린 버퍼를 블러링하기 위해서 블러 맵을 렌더 타겟으로 설정하고, 스크린 크기의 쿼드를 다음 정점 및 픽셀 쉐이더를 사용해 렌더링한다.
 

// Gaussian 필터 정점 쉐이더
struct VSOUTPUT_BLUR
{
   float4 vPosition    : POSITION;
   float2 vTexCoord    : TEXCOORD0;
};

 

VSOUTPUT_BLUR VS_Blur( float4 inPosition : POSITION, float2 inTexCoord : TEXCOORD0 )
{
   // 출력 구조체
   VSOUTPUT_BLUR OUT = (VSOUTPUT_BLUR)0;
   // Output the position
   OUT.vPosition = inPosition;
   // Output the texture coordinates
   OUT.vTexCoord = inTexCoord;
   return OUT;
}


// 수평 블러 픽셀 쉐이더
float4 PS_BlurH( VSOUTPUT_BLUR IN ): COLOR0
{
   // Accumulated color
   float4 vAccum = float4( 0.0f, 0.0f, 0.0f, 0.0f );
   // Sample the taps (g_vSampleOffsets holds the texel offsets
   // and g_fSampleWeights holds the texel weights)
   for(int i = 0; i < 15; i++ )
   {
      vAccum += tex2D( ScreenSampler, IN.vTexCoord + g_vSampleOffsets[i] ) * g_fSampleWeights[i];
   }
   return vAccum;
}


// 수직 블러 픽셀 쉐이더
float4 PS_BlurV( VSOUTPUT_BLUR IN ): COLOR0
{
   // Accumulated color
   float4 vAccum = float4( 0.0f, 0.0f, 0.0f, 0.0f );
   // Sample the taps (g_vSampleOffsets holds the texel offsets and
   // g_fSampleWeights holds the texel weights)
   for( int i = 0; i < 15; i++ )
   {
      vAccum += tex2D( BlurHSampler, IN.vTexCoord + g_vSampleOffsets[i] ) * g_fSampleWeights[i];
   }
   return vAccum;
}

이제 블러 맵이 준비되었다. 그림자의 블러 양을 늘리려면 텍셀 샘플링 거리를 증가시키면 된다. 마지막 단계는 블러링된 맵을 스크린 공간의 씬에 다시 투영하는 것이다.
 
 

첫 번째 Gaussian 패스 이후

두 번째 Gaussian 패스 이후
 
Step 4 : Rendering the shadowed scene
 
블러 맵을 씬에 투영하기 위해서 우리는 씬을 보통과 같이 렌더링한다. 그러나 블러 맵을 스크린 공간 좌표를 사용해 투영한다. 우리는 약간의 하드 코딩된 math 를 사용하는 clip 공간 위치를 사용해 스크린 공간 좌표를 생성한다. 아래에 그림자를 따라 픽셀당 조명을 사용하여 씬을 렌더링하는 정점 및 픽셀 쉐이더가 나와 있다.
 
struct VSOUTPUT_SCENE
{
   float4 vPosition      : POSITION;
   float2 vTexCoord      : TEXCOORD0;
   float4 vProjCoord     : TEXCOORD1;
   float4 vScreenCoord   : TEXCOORD2;
   float3 vNormal        : TEXCOORD3;
   float3 vLightVec      : TEXCOORD4;
   float3 vEyeVec        : TEXCOORD5;
};

// 씬 정점 쉐이더
VSOUTPUT_SCENE VS_Scene( float4 inPosition : POSITION, float3 inNormal : NORMAL,
                         float2 inTexCoord : TEXCOORD0 )
{
   VSOUTPUT_SCENE OUT = (VSOUTPUT_SCENE)0;
   // 변환된 위치 출력
   OUT.vPosition = mul( inPosition, g_matWorldViewProj );
   // 텍스처 좌표 출력
   OUT.vTexCoord = inTexCoord;
   // 투영 텍스처 좌표 출력
   // (spot 텍스처를 씬에 투영하기 위해 이를 사용)
   OUT.vProjCoord = mul( inPosition, g_matTexture );
   // 스크린 공간 텍스처 좌표 출력
   OUT.vScreenCoord.x = ( OUT.vPosition.x * 0.5 + OUT.vPosition.w * 0.5 );
   OUT.vScreenCoord.y = ( OUT.vPosition.w * 0.5 - OUT.vPosition.y * 0.5 );
   OUT.vScreenCoord.z = OUT.vPosition.w;
   OUT.vScreenCoord.w = OUT.vPosition.w;
   // 월드 공간 정점 위치 획득
   float4 vWorldPos = mul( inPosition, g_matWorld );
   // 월드 공간 법선 출력
   OUT.vNormal = mul( inNormal, g_matWorldIT );
   // 조명 벡터를 탄젠트 공간으로 이동시킴
   OUT.vLightVec = g_vLightPos.xyz - vWorldPos.xyz;
   // 관찰자 위치(eye) 벡터를 탄젠트 공간으로 이동시킴
   OUT.vEyeVec = g_vEyePos.xyz - vWorldPos.xyz;
   return OUT;
}
 
우리는 조명으로부터 sopt 텍스처를 투영하기 위해서 부가적인 spot 항을 추가했다. 이것은 집중 조명 효과를 시뮬레이션하기 위한 목적 뿐만 아니라, 그림자 맵 외부의 씬의 바깥쪽 영역을 잘라내기 위한 목적도 있다. 이 spot 맵은 표준 투영 텍스처링을 사용해서 투영된다.
 

float4 PS_Scene( VSOUTPUT_SCENE IN ) : COLOR0
{
   // 법선, 광원, 눈 벡터 정규화
   IN.vNormal   = normalize( IN.vNormal );
   IN.vLightVec = normalize( IN.vLightVec );
   IN.vEyeVec   = normalize( IN.vEyeVec );


   // 색상 및 법선 맵 샘플링
   float4 vColor  = tex2D( ColorSampler, IN.vTexCoord );
   // 주변, 분산, 반사 조명 항 계산
   float ambient  = 0.0f;
   float diffuse  = max( dot( IN.vNormal, IN.vLightVec ), 0 );
   float specular = pow(max(dot( 2 * dot( IN.vNormal, IN.vLightVec ) * IN.vNormal
                                 - IN.vLightVec, IN.vEyeVec ), 0 ), 8 );
   if( diffuse == 0 ) specular = 0;
   // 그림자 항 획득
   float fShadowTerm = tex2Dproj( BlurVSampler, IN.vScreenCoord );
   // spot 항 획득
   float fSpotTerm = tex2Dproj( SpotSampler, IN.vProjCoord );
   // 최종 색상 계산
   return (ambient * vColor) +
          (diffuse * vColor * g_vLightColor * fShadowTerm * fSpotTerm) +
          (specular * vColor * g_vLightColor.a * fShadowTerm * fSpotTerm);
}

바로 이것이다! 우리는 매우 괜찮아 보이는 부드러운 모서리 그림자를 가지게 되었다. 이 기법의 이점은 완벽하게 그림자 맵의 단점인 모서리 aliasing 인공물을 제거한다는 것이다.  다른 이점은 적은 메모리 오버헤드를 가지고 다중 조명을 위한 부드러운 그림자를 생성할 수 있다는 것이다. 다중 조명을 다룰 때 당신이 할 일은 조명당 그림자 맵을 하나씩 만들어 주는 것 뿐이다. 반면에 스크린이나 블러 버퍼는 모든 조명에 의해 공유될 수 있다. 마지막으로 이 기법은 그림자 맵 및 그림자 부피 모두에 적용될 수 있다. 그래서 그림자 기법에 상관없이 당신은 이 기법을 사용해 부드러운 모서리 그림자를 생성할 수 있다. 단점이 하나 있다면 이 기법은 Gaussian 필터를 사용하기 위해서 fill-rate 에 상당히 민감하다는 것이다. 이것은 작은 블러 버퍼를 사용하거나 가시 품질을 약간 희생함으로써 최소화될 수 있다.
 
다음은 여기에서 언급한 접근들 사이의 비교이다. 3x3 percent closer  필터링 및 일반 그림자 매핑이다.
 

 
내 기사를 읽어주어서 감사를 표한다. 당신이 좋아했으면 하는 바램이다. 만약 의구심, 질문, 덧글이 있다면anidex@yahoo.com 으로 편하게 메일 보내기 바란다. 소스코드는http://downloads.gamedev.net/features/hardcore/softedgeshadow/SoftShadows.zip 에 있다.
 
Reference
 
- Hardware Shadow Mapping. Cass Everitt, Ashu Rege and Cem Cebenoyan
- Hardware-accelerated Renderng of Antialiased Shadows with Shadow Map. Stefan Brabec and Hans-Peter Seidel.
 

Discuss this article in the forums


Date this article was posted to GameDev.net: 1/18/2005 
(Note that this date does not necessarily correspond to the date the article was written)

 

See Also:


Hardcore Game Programming 
Shadows 

 

© 1999-2006 Gamedev.net. All rights reserved. Terms of Use Privacy Policy 
Comments? Questions? Feedback? Click here!

깊이 버퍼 쉐도우 맵이라는거 정말 좋은 그림자 기법입니다.

첫째 구현하기 간단하고.. 둘째 간단함에도 불구 하고 자기그림자까지 표현하는 정교함까지..

그런데 장점이 좋은 만큼 단점도 눈에 확 들어오는데요.

 

1. Z Fighting 에 의한 Moire 발생.

2. 넓은 영역을 커버 할때 나타나는 엄청 거슬리는 깍뚜기(알리아싱)들.

 

그래서 여러방법이 나오는데

PSM (Perspective Shadow map), LisPSM(Light Space Perspective Shadow map), TSM(Trapezoidal Shadow Maps).. 등등등..

그래서 검색을 해보면 별 해괴 망측한 수식들과 그림들이 튀어나옵니다.

뭐 이런 그림들이지요. 저는 당최 모르겠습니다.

대학교에서 4년이나 물리학을 배웠는데도 도데체가 뭔소린지.. 뷰프러스텀 짜부려놓고 저게 무슨 논문이라고.. 지들은 알아보는건지..

남들은 잘정리된 논문이라고 하던데.. 난 머리가 나쁜것인지..

 

그래서 그냥 제가 만들기로 했더랬지요.

먼저 아주 전통적인 깊이버퍼 그림자를 구현 해보면..

이렇게 나옵니다.. 180m X 180m를 그림자 한장으로 덮었더니 이꼴이 났는데요.

그림자의 해상도는 자그마치 2048X2048 짜립니다.

예.. 멀리 있는건 볼만 하지만 앞에 있는 그림자는 정말 못봐주겠더랩니다.

2048이라는 해상도는 다 어따가 팽개친건지..

다음 그림이 어따가 팽개쳤는지 알려줍니다.

개략적으로 그린것이긴 하지만 대충 봐도 낭비가 엄청 심하다는것을 알수 있구요.

2048이면 뭐합니까 앞에서는 40~200 픽셀 정도 밖에 못쓰는데..
..

..

..
그럼 다음 그림을 보면..

오오.. 앞에 그림과 거의 같은 위치에서 보는 것이지만 틀려도 너무 틀립니다.

물론 해상도는 되려 작습니다. 1024X1024를 쓰고 있습니다.

어떻게 이런일이 가능해 버린걸까요?

깊이버퍼를 보면 다음과 같습니다.

역시 대충 그린 그림입니다. 하지만 뭔가 틀리지요?

네. 카메라와 가까운곳은 크게 그려주고 먼곳은 작게 그려주는것. 그게 포인트 입니다.

낭비되는 범위는 비슷하지만 그려지는 화질이 틀린것이지요.

PSM의 공통된 의도는 바로 그겁니다. 가까운곳에는 텍스처의 픽셀을 많이 할당하고 먼곳은 적게 할당한다.

간단하지요?

그런데 그 수식 구할라고 하면 아주 박터집니다. 도데체 뭔말인지도 모르겠고 수식은 뭔 기호가 그렇게 많은지..

물론 위에 제가 직접 사용한 방식은 PSM의 의도만을 가지고 순수하게 제가 그냥 코딩한 것이라서 내세우긴 좀 뭐합니다만....

 

저와 마찬가지로 그냥 PSM의 맛보기라도 하고싶다라는 분들이 있을꺼라 생각하고..

또 제가 알고 있는 이 방법이 의외로 다른 분들에게 도움이 될지도 모른다는 생각에 씁니다.

 

[시작.]

먼저 전통적인 깊이 버퍼 그림자의 순서입니다.

1. 깊이텍스처를 준비한다.

2. 렌더 타겟을 깊이텍스처로 전환

3. 광원의 위치로 View행렬을 세팅(단. Up Direction은 카메라가 보는 방향)

4. 광원의 FOV대로 Projection행렬 세팅

5. 깊이값을 렌더링

6. 렌더 타겟을 다시 주버퍼로 전환

7.  카메라의 위치로 View행렬 세팅

8. 카메라의 FOV대로 Projection 행렬 세팅

9. 3과4에서 쓰인 행렬식을 토대로 깊이 버퍼를 가져와 비교하여 그림자 채색.

- 2로 다시 반복.

...

이건데요.

제가 사용한 방법은 위의 전통적인 방법과 순서는 전혀 안틀립니다.

아니.. 아예 똑같습니다.

하지만 코드상에서 틀린부분은 바로 4번(광원의 FOV대로 Projection행렬 세팅) 인데요.

일단 똑깉이 구해놓고 행렬하나를 더 만들어서 곱한후에 세팅합니다.

그 행렬이 뭔고 하면 그림을 위에 보시는 것처럼 짜부러 뜨리는 행렬입니다. (아래는 넓게 위쪽은 좁게)

 

어떻게?

바로 우리가 알고 있는 카메라 프로젝션 행렬을 곱해주는 겁니다. 근데 그냥 곱하면 안되구요.

..

..

프로젝션 행렬을 통하고 나면 좌우 위아래는 -1~1 로 고정되구요 앞뒤는 0~1로 고정됩니다.

원래 프로젝션 행렬은 앞은 크게 뒤는 작게 그릴수 있게 해주는 것입니다.

-Z 축으로 왜곡

 

그런데 우리는 위는 좁게 아래는 크게 만들어줘야 합니다.

-Y 축으로 왜곡

감이 오시지요?

우리가 가장 많이 쓰는 프로젝션 행렬은 Dx에서 다음과 같이 정의 됩니다.

2*zn/w  0          0                   0
0           2*zn/h  0                   0
0           0          zf/(zf-zn)       1
0           0          zn*zf/(zn-zf)  0

w는 뷰 프러스텀의 폭

h는 뷰 프러스텀의 높이

zn은 근단면의 거리

zf는 원단면의 거리입니다.

앞에서 말한것과 같이 Z축 왜곡이니까 Y축 왜곡으로 바꿔볼잡시면

2*zn/w  0                     0            0
0           zf/(zf-zn)        0             1
0           0                    2*zn/h     0
0           zn*zf/(zn-zf)   0             0

이렇게 됩니다.

이걸 4번에서 구한 프로젝션 행렬에 곱해준다음 적용하는 겁니다.

간단하죠?

하다못해 셰이더 코드는 100% 똑같습니다.

 

w는 원래 같으면 다른 값을 쓰겠지만 1 이하가 됩니다. 이미 프로젝션 행렬을 통한 다음엔 최대 좌우 폭은 -1~1 2가 되기때문이지요.

w와 h는 근단면의 폭과 높이이기 때문에 2보다는 작아지니까요.

그리고 zn과 zf는 모두 0~1 사이값을 갖게 됩니다. zf는 1보다 더 커져도 큰 무리는 없지만 zn은 정말 세밀하게 조절 해야됩니다.

zn값 하나 때문에 퀄리티가 오르락 내리락 합니다. 너무 작으면 깍뚜기가 나오고 너무크면 가까운곳은 그림자가 사라집니다.

테스트를 거쳐야 나오는 값이라 뭐라 포스팅 할수가 없네요.

 

이 방법도 단점은 있습니다.

바로 라이트의 방향과 카메라의 방향의 각에 따라 퀄리티가 들쭉 날쭉 합니다.

이는 좀더 그림자맵을 그릴때 방향을 잘 잡아야 하는 문제니까 금방 잡힐꺼 같습니다.

 

최종적으로 모든게 적용된 스크린샷을 올려 볼께욤.










갑자기 만든거 치고는 괜찮아보여 혼자 만족중입니다. 하하핫!

/////////////////////////////////////////////////////////////////////////////////
float4x4 mLightSpaceWvp;
float4x4 mWvp;
float4x4 mObjWorld;


float4 LightAmbient = {0.1, 0.1, 0.1, 1};
float4 LightDiffuse = {1, 1, 1, 1};
float4 LightDirInv;
float4 LightPos;

/////////////////////////////////////////////////////////////////////////////////
#include "../common/afx_texture_macro.fxh"

TEXTURE_2D( txrDepthMap, sampDepthMap, Clamp, NONE, LINEAR, LINEAR)
TEXTURE_2D( txrTerrain, sampTerrain, Wrap, LINEAR, LINEAR, LINEAR)

/////////////////////////////////////////////////////////////////////////////////
// Render Depth from Light
struct vso_RenderDepthMap
{
float4 HPosition : POSITION;
float4 LightSpacePos : TEXCOORD0;
};

vso_RenderDepthMap VS_RenderDepthMap(float3 pos : POSITION)
{
vso_RenderDepthMap o = (vso_RenderDepthMap)0;

float4 Pos = {pos, 1};
o.HPosition = mul(Pos, mLightSpaceWvp); // 라이트위치로부터의 깊이값을 저장하기 위해.
// 실제 필요한 건 (깊이맵을 그리기위한)z 값.
o.LightSpacePos = o.HPosition / o.HPosition.w; // 프로젝션을 거치면서 값이 w로 정규화되지 않는 경우를 위해 표준회
return o;
}
float4 PS_RenderDepthMap(float4 LightSpacePos : TEXCOORD0 ) : COLOR0
{
//LightSpacePos /= LightSpacePos.w; // 픽셀셰이더에서 정규화하면 좀더 세밀하겠지만 별 차이없음.
return (float4)LightSpacePos.z; // 깊이맵 그리기. 캐릭터가 이동할때마다 재계산되어 깊이맵도 변함.
// 모든 오브젝트에 그림자가 생김.
}


/////////////////////////////////////////////////////////////////////////////////
// Render Terrain
struct vso_RenderTerrain
{
float4 HPosition : POSITION;
float2 t : TEXCOORD0;
float4 LightSpacePos: TEXCOORD1;
};
vso_RenderTerrain VS_RenderTerrain(float3 pos:POSITION, float2 t:TEXCOORD0)
{
vso_RenderTerrain o = (vso_RenderTerrain)0;

float4 Pos = float4(pos,1);
o.HPosition = mul( Pos, mWvp);
o.t = t;

float4 PosLWvp = mul( float4(pos,1), mLightSpaceWvp);

PosLWvp /= PosLWvp.w; // for range ( -1 ~ 1 )
o.LightSpacePos.x = PosLWvp.x*0.5 + 0.5;
o.LightSpacePos.y = -PosLWvp.y*0.5 + 0.5;
o.LightSpacePos.z = PosLWvp.z;
o.LightSpacePos.w = PosLWvp.w;

return o;
}
float4 PS_RenderTerrain(float2 t:TEXCOORD0, float4 LightSpacePos:TEXCOORD1) : COLOR0
{
float4 addc = (float4)0.1;
float4 col1 = tex2D(sampTerrain, t)+addc;
float4 col2;
// 깊이값 비교.
// 0.03 는 오차보정용.
if( LightSpacePos.z-0.03 > tex2D(sampDepthMap, LightSpacePos.xy).z) col2 = (float4)0.3;
else col2 = (float4)1.0;

return col1*col2;
}
// ///////////////////////////////////////////////////////////////////////////////
struct vso_Object
{
float4 HPosition : POSITION;
float4 Normal : TEXCOORD0;
};
vso_Object VS_RenderObj( float3 pos : POSITION, float3 Normal:NORMAL )
{
vso_Object o = (vso_Object)0;
o.HPosition = mul(float4(pos, 1), mWvp );
o.Normal = float4( mul(Normal,(float3x3)mObjWorld), 0);
return o;
}

float4 PS_RenderObj(float4 Normal : TEXCOORD0) : COLOR0
{
float4 o = (float4)0;
float4 N = normalize(Normal);
float4 L = normalize(LightDirInv);
float ldn = max( dot(N, L), 0 );
o = LightAmbient + LightDiffuse * ldn ;
return o;
}
/////////////////////////////////////////////////////////////////////////////////
technique DepthShadow
{
// Render Shadow to Texture
pass P0
{
VertexShader = compile vs_2_0 VS_RenderDepthMap();
PixelShader = compile ps_2_0 PS_RenderDepthMap();
}
pass P1
{
VertexShader = compile vs_2_0 VS_RenderTerrain();
PixelShader = compile ps_2_0 PS_RenderTerrain();
}
pass P2
{
VertexShader = compile vs_2_0 VS_RenderObj();
PixelShader = compile ps_2_0 PS_RenderObj();
}
}

원근투영은 물체의 정점이 시점으로 수렴되지만, 직교투영은 그렇지 않죠. 

따라서 직교투영에서 피킹에 사용할 반직선은 노멀라이즈한 시선방향벡터를 투영평면상의 마우스 위치만큼 옮기면 간단하게 구할 수 있습니다. 

OpenGL의 경우 직교투영행렬의 [0,0], [1,1] 은 다음과 같이 생겨먹었습니다. {D3D도 마찬가지겠군요.} 

OrthoMatrix[0,0] = 2/(Right-Left) 
OrthoMatrix[1,1] = 2/(Top-Bottom) 

투영평면에서 뷰볼륨에 정규화된 마우스의 위치는 다음과 같이 구할 수 있죠. 

aX = (마우스 X * 2.0 / 화면 너비)-1.0 
aY = -(마우스 Y * 2.0 / 화면 높이)-1.0 

정규화되지 않은 마우스 위치는 OrthoMatrix[0,0], OrthoMatrix[1,1]을 aX,aY에 각각 곱해주시면 되겠죠?? 

bX = aX * OrthoMatrix[0,0] 
bY = aY * OrthoMatrix[1,1] 

이렇게 얻어진 bX와 bY를 이용해 반직선의 시점과 종점을 만들어 보죠. 

시점 = (bX, bY, 0) 
종점 = (bX, bY, -1) {D3D에서는 (bX, bY, 1)이 되겠죠??} 

이 정점을 뷰변환행렬의 역행렬로 변환시키면 세계공간에서 피킹에 사용할 반직선을 얻을 수 있습니다. 

뷰좌표계의 U, V, N 벡터를 알고 있다면... U벡터를 bX만큼 스케일, V벡터를 bY만큼 스케일 해서 얻어진 좌표에 카메라 시점벡터를 더한 좌표를 원점으로, 다시 여기에 N벡터를 뺀 좌표를 종점으로 하는 반직선을 만들어 쓰시면 되죠. {D3D에서는 N벡터를 더해야... ^^} 

더 간단하게 해결하시려면 피킹하려는 물체와 시점의 거리를 아주 길게 만드세요. 그러면 직교투영에서의 실제 반직선과 비슷하게 맞아들어가기 때문에 그럭저럭 사용할 수는 있습니다. 물론 현재 직교투영으로 보여지는 이미지와 같은 크기로 보여지도록 반직선을 계산할 원근투영의 Fov를 잘 설정하셔야 하지만요~ ^^; 

Fov = arctan( OrthoMatrix[0,0] / 카메라 거리 ) 

제가 만들어 쓰는 맵툴도 직교투영과 원근투영을 바꿀 수 있지만 어차피 내부에서만 쓸 거라... 맵작업 하시는 분들 께 직교투영할 때는 카메라를 멀리 빼서 사용하라고 압력을 넣고 있습죠~~ ^^;; (음... 이 글을 보면 안되는데~~ 흐흐~~) 

 

Resource Management Best Practices

By Chuck Walbourn, Software Design Engineer

Microsoft Game Technology Group

June 2005

Introduction

Managed textures (also known as "automatic texture management") have been available in DirectX since version 6, with several revisions and enhancements made in subsequent releases. As of the Direct3D 9 API, the automatic resource management includes support for textures, vertex buffers, and index buffers all with a consistent shared interface. By using the Direct3D resource manager, applications can greatly simplify the handling of lost-device situations and can rely on the system to handle a reasonable amount of over-commitment of video memory resources.

Developers sometimes have difficulties using managed resources, in part due to the abstract nature of the system. While many common scenarios for resources are a good fit for managed resources, some cases are more performant using unmanaged resources. This article will discuss best practices for dealing with resources generally, how managed and unmanaged resources behave, and provide some detail on how resources are typically handled by the runtime and drivers.

Contents:

Video Memory

In order for the video system to make use of a resource, it must be located in memory accessible to the GPU. Local video memory is the most performant for the GPU, and certain resources (such as render targets and depth/stencil buffers) must be located in local video memory. With the advent of AGP, the GPU can also access a portion of the system memory directly. This memory area, known as the AGP aperture, is referred to as 'non-local video memory' and is not available for other purposes. Non-local video memory can be read from and written to by the CPU, which typically has no high-performance access to local video memory, and is thus ideal for use as a shared memory resource. A key thing to remember about AGP memory is that it, like local video memory, is invalidated in lost-device situations and persistent assets located there must be restored.


Some integrated video solutions make use of a Unified Memory Architecture where main memory is addressable by all components of the systems. Direct3D supports UMA without requiring any change to the application, utilizing the same hints as for local video memory configurations. For such systems, resources are always located in system memory and the driver is responsible for ensuring resources work much like they do in a more traditional architecture while taking advantage of UMA's properties and any specific behavior of the hardware implementation.


Managed Resources

The majority of your resources should be created as managed resources in POOL_MANAGED. All your resources will be created in system memory, and then copied as needed into video memory. Lost-device situations will be handled automatically from the system memory copy. Since not all managed resources are required to fit into video memory all at once, you can over commit memory where a smaller video memory working set of resources is all that is required to render in any given frame. Note that it is likely that the majority of this backing-store system memory will be paged out to disk over time, which is why the Reset operation can be slow due to the need to page this data in to restore the lost video memory.

The runtime keeps a timestamp for the last time a resource is used, and when a video memory allocation fails for loading a needed managed resource, it will release resources based on this timestamp in a LRU fashion. Usage of the SetPriority API takes precedence over the timestamp, so more commonly used resources should be set to a higher priority value. Direct3D 9.0 has limited information about the video memory managed by the driver, so the runtime may have to evict several resources in order create a large enough region for the allocation to succeed. Proper priorities can help eliminate situations where something gets evicted and then is required again shortly there-after. The application can also use the EvictManagedResources API call to force all the managed resources to be removed. Again, this can be a time-consuming operation to reload all the resources required for the next frame, but is very useful for level transitions where the working set changes significantly and removing video memory fragmentation.

A frame count is also kept to allow the runtime to detect if the resource it just choose to evict was used early the current frame, which implies a 'thrashing' situation where more resources are in use in a single frame than will fit into video memory. This triggers the replacement policy to switch to a MRU fashion rather than LRU for the remainder of the frame as this tends to perform slightly better under such conditions. Such 'thrashing' behavior will significantly impact the rendering performance. Note that the notion of current frame is tied to EndScene, so any application making use of managed resources needs to make regular calls to this method.

Developers looking to find more information about how managed resources are behaving in their application can make use of the RESOURCEMANAGER event query via the IDirect3DQuery9 interface. This only works when using the debug runtimes so this information cannot be depended upon by the application, but it provides deep detail on the resources managed by the runtime.

While understanding how the resource manager works can help when tuning and debugging your applications, it is important to not tie your application too tightly to the implementation details of the current runtime or drivers. Revisions of the driver or changes in hardware can significantly change the behavior, and future versions of Direct3D will have significantly improved and sophisticated resource management.

Driver Managed Resources

Direct3D drivers are free to implement the 'driver managed textures' capability, indicated by D3DCAPS2_CANMANAGERESOURCE, which allows the driver to handle the resource management instead of the runtime. For the (rare) driver that implements this feature, the exact behavior of the driver's resource manager can vary widely and you should contact the driver vendor for details on how this works for their implementation. Alternatively, you can ensure that the runtime manager is always used instead by specifying D3DCREATE_DISABLE_DRIVER_MANAGEMENT when creating the device.

Default Resources

While managed resources are simple, efficient, and easy-to-use there are times when using video memory directly is preferred or even required. Such resources are created in the POOL_DEFAULT category. Making use of such resources does cause additional complications for your application. Code is required to cope with the lost-device situation for all the POOL_DEFAULT resources, and performance considerations must be taken into account when copying data into them. Failure to specify USAGE_WRITEONLY or making a render target lockable can also impose serious performance penalties.

Calling Lock on a POOL_DEFAULT resource is more likely to cause the GPU to stall than working with a POOL_MANAGED resource unless using certain hint flags. Depending on the location of the resource the pointer returned could be to a temporary system memory buffer or it can be a pointer directly into AGP memory. If it is a temporary system memory buffer, data will need to be transferred to the video memory after the Unlock call. If the video resource is not write-only, data will have to be transferred into the temporary buffer during the Lock. If it is an AGP memory area, temporary copies are avoided but the cache behavior required can result in slow performance.

Care should be taken to write a full cache line of data into any pointer to AGP aperture memory to avoid the penalty of write-combing which induces a read/write cycle, and sequential access of the memory area is preferred. If your application needs to make random access to data during creation and you do not wish to make use of a managed resource for the buffer, you should work with a system memory copy instead. Once the data has been created, you can then stream the result into the locked resource memory to avoid paying a high penalty for the cache write-combing operation.

The LOCK_NOOVERWRITE flag can be used to append data in an efficient manner for some resources, but ideally multiple Lock and Unlock calls to the same resource can be avoided. Making proper use of the various Lock flags is important to optimal performance, as is using a cache-friendly data access pattern when filling locked memory.

Using Both Managed and Default Resources

Mixing allocations of managed and POOL_DEFAULT resources can cause video memory fragmentation and confuse the runtime's view of the video memory available for managed resources. Ideally, you should create all POOL_DEFAULT resources before making use of POOL_MANAGED resources or make use of the EvictManagedResources call before allocating unmanaged resources. Remember that all allocations made from POOL_DEFAULT that reside in video memory tie up memory for the life that resource that is unavailable for use by the resource manager or for any other purpose.

Note that unlike previous versions of Direct3D, the version 9 runtime will automatically evict some managed resources before giving up on a failed unmanaged resource allocation for a lack of video memory, but this can potentially create additional fragmentation and even force a resource into a sub-optimal location (a static texture in non local video memory for example). Again, it is best to allocate all required unmanaged resources up-front and before using any managed ones.

Dynamic Default Resources

Data that is generated and updated at a high frequency has no need for the backing-store since all the information will be re-created when restoring the device. Such data is typically best created in POOL_DEFAULT specifying the USAGE_DYNAMIC hint so the driver can make optimization decisions when placing the resource knowing it will be updated often. This typically means putting the resource into non-local video memory, and thus is usually much slower for the GPU to access than local video memory. For UMA architectures, the driver might choose a particular placement for dynamic resources to optimize for CPU write access.

This usage is typical for software skinning solutions and CPU-based particle systems filling out vertex/index buffers, and the LOCK_DISCARD flag will ensure that stalls are not created in cases where the resource is still in use from the previous frame. Using a managed resource in this case would update a system memory buffer, which would then be copied to video memory, and then used for only a frame or two before being replaced. For systems with non-local video memory, the extra copy is eliminated by proper use of this dynamic pattern.

Standard textures cannot be locked, and can only be updated via UpdateSurface or UpdateTexture. Some systems support dynamic textures, which can be locked and use the LOCK_DISCARD pattern, but a capabilities bit (D3DCAPS2_DYNAMICTEXTURES) must be checked before making use of such resources. For highly dynamic (video or procedural) textures, your application could create matching POOL_DEFAULT and POOL_SYSTEMMEM resources and handle video-memory update via the UpdateTexture API. For high frequency partial updates, the UpdateTexture paradigm is likely the better choice.

As useful as dynamic resources can be, be careful when designing systems that rely heavily on dynamic submission. Static resources should be placed into POOL_MANAGED to ensure both good utilization of local video memory, and to make more efficient use of limited bus and main memory bandwidth. For resources that are 'semi-static', you may find that the cost of an occasional upload to local video memory is much less than the constant bus traffic generated by making them dynamic.

System Memory Resources

Resources can also be created in POOL_SYSTEMMEM. While they cannot be used by the graphics pipeline, they can be used as sources for updating POOL_DEFAULT resources via UpdateSurface and UpdateTexture. Their locking behavior is simple, although stalls might occur if they are in use by one of the previously mentioned methods.

Though they reside in system memory, POOL_SYSTEMMEM resources are limited to the same formats and capabilities (such as maximum size) supported by the device driver. The POOL_SCRATCH resource type is another form of system memory resource that can utilize all formats and capabilities supported by the runtime, but cannot be accessed by the device. Scratch resources are intended primarily for use by content tools.


General Recommendations

Getting the technical implementation details of resource management correct will go a long way to achieving your performance goals in your application. Planning how the resources are presented to Direct3D and the architectural design around getting the data loaded in a timely fashion is a more complicated task. We recommend a number of best practices when making these decisions for your application:

  • Pre-process all your resources. Relying on expensive load-time conversion and optimization for your resources is convenient during development, but puts a high performance burden on your users. Pre-processed resources are faster to load, faster to use, and gives you the option of doing sophisticated off-line work.
  • Avoid creating many resources per frame. The driver interactions required can serialize the CPU and GPU, and the operations involved are heavy-weight as they often require kernel transitions. Spread out creation over several frames or reuse resources without creating/releasing them. Ideally, you should wait several frames before locking or releasing resources recently used to render.
  • Be sure to unbind all resource channels (i.e. stream sources, texture stages, and current indices) at the end of the frame. This will ensure that dangling references to resources are removed before they cause the resource manager to keep resources resident that are actually no longer in use.
  • For textures, use compressed formats (e.g. DXTn) with mip-maps and consider making use of a texture atlas. These greatly reduce bandwidth requirements and can reduce the overall size of the resources making them more efficient.
  • For geometry, make use of indexed geometry as this helps compress vertex buffer resources and modern video hardware is heavily optimized around reuse of vertices. By making use of programmable vertex shaders, you can compress the vertex information and expand it during the vertex processing. Again, this helps reduce bandwidth requirements and makes vertex buffer resources more efficient.
  • Be careful about over-optimizing your resource management. Future revisions of drivers, hardware, and the operating system can potentially cause compatibility problems if the application is tuned too heavily to a particularly combination. Since most applications are CPU bound, expensive CPU-based management generally causes more performance issues than they solve.
  • [출처] DirectX Video memory|작성자 예스빌



http://hldec.net/77

깊이맵과 노말맵을 이용한 외곽선과

단순한 명함처리를 설정하여 만든 카툰 랜더링 예제

근데 LPF가 머지? ㅡ,.ㅡa

http://hldec.net/78

 

아직 제대로 읽어보지 않았지만 이미지만 봤을시에 상당히

매력적인듯하다.

원 작성자: maitte (구 kgda)

작성일 : 2004/7/6 10:00

--------------------------------------

3차원 그래픽을 표현하는데 있어서, 많은 분들이 헷갈리는 것들이 바로 쉐이딩이라는 것입니다.

3차원 그래픽에서의 쉐이딩은 그림자(쉐도우)와는 명확하게 구별됩니다.

쉐이딩은 빛에 의해서 빛이 어떻게 비추어지느냐를 다룬다면, 그림자는 빛이 비추는 방향으로부터 빛을 차단하는 장애물이 있는지 없는지를 다룹니다.  쉐이딩이 아날로그적 특성을 가진다면, 그림자는 바이너리적 특성을 가집니다.

그림에서 나온것처럼 쉐이딩은 빛의 방향에 따라서 크게 세가지로 나누어집니다.

A 영역은 빛이 비추지 못하는 영역이고요.

B 영역은 빛이 비추어지지만, 빛의 각도에 따라서 빛에 대항 영향이 연속적으로 바뀌는 영역입니다.

C 영역은 빛이 곧바로 비춰져서 빛의 색이 그대로 투영되는 영역입니다.

이것을 각각, A : Ambient, B : Diffuse, C : Specular 로 나눕니다.

재질을 설정할때..

Ambient의 값은 A, B, C 모두에 미칩니다.

Diffuse의 값은 B, C 영역에 미칩니다.

Specular의 값은 C 영역에 미칩니다.

Emissive의 값은 A, B, C 모두에 미치지만, 조명과는 관계가 없습니다.

Shininess의 값은 C 영역의 크기와 밝기를 결정합니다.  이 값이 크면 C 영역은 줄어들면서 밝기가 증가하지만, 작으면 C 영역은 늘어나면서 밝기가 감소합니다.  전자의 경우에는 반사도가 큰 메탈 재질 등, 후자의 경우는 반사도가 작은 종이, 나무 재질 등에 사용합니다.

다소간에 도움이 되었으면 하네요.

[출처] 조명에 의한 쉐이딩 정리(Ambient, Diffuse, Specular, Emissive)|작성자 재준


+ Recent posts