struct VtxDUV1
{
D3DXVECTOR3 p;
DWORD d;
FLOAT u, v;
VtxDUV1() {}
VtxDUV1(FLOAT X, FLOAT Y, FLOAT Z,
FLOAT U, FLOAT V, DWORD D = 0xFFFFFFFF)
: p(X, Y, Z), u(U), v(V), d(D) {}
enum { FVF = (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1) };
};
struct VtxIdx
{
WORD a, b, c;
VtxIdx() : a(0), b(1), c(2) {}
VtxIdx(WORD A, WORD B, WORD C) : a(A), b(B), c(C) {}
};
LPD3DXEFFECT m_pEft; // ID3DXEffect Instance
LPDIRECT3DVERTEXDECLARATION9 m_pFVF; // Vertex Declarator
D3DXMATRIX m_mtWld; // World Matrix
VtxDUV1 m_pVtx[24]; // Vertex Buffer
VtxIdx m_pIdx[12];
LPDIRECT3DTEXTURE9 m_pTex;
정점 데이터(VtxDUV1)은 포지션(XYZ), 색상(DIFFUSE), 텍스처 좌표(TEX1)를 가지고 있다.
그리고 인덱스 버퍼를 위한 데이터(VtxIdx) 이번에 Effect를 사용하기 위한 새로 추가된 객체 m_pEft(LPD3DXEFFECT)
정육면체를 구성하기 위해 정점 버퍼 24개(m_pVtx[24]), 인덱스 버퍼 12개(m_pldx[12]) 로 구성 되어 있다.
*shader.fx*
float4x4 m_mtWVP; // World * View * Projection Matrix
texture m_TxDif;
sampler smpDif : register(s0) = sampler_state
{
texture = (m_TxDif);
MinFilter = LINEAR;
MagFilter = LINEAR;
MipFilter = NONE;
AddressU = WRAP;
AddressV = WRAP;
};
struct SVsOut
{
float4 Pos : POSITION;
float4 Dff : COLOR0;
float2 Tex : TEXCOORD0;
};
// Vertex Shader Processing
SVsOut VtxPrc( float3 Pos : POSITION, float4 Dif : COLOR0, float2 Tex : TEXCOORD0)
{
SVsOut Out = (SVsOut)0;
Out.Pos = mul( float4(Pos, 1), m_mtWVP);
Out.Dff = Dif;
Out.Tex = Tex;
return Out;
}
// Pixel Shader Processing
float4 PxlPrc(SVsOut In) : COLOR
{
float4 Out= 0;
Out = max(In.Dff, tex2D(smpDif, In.Tex) );
return Out;
}
technique Tech0
{
pass P0
{
VertexShader = compile vs_3_0 VtxPrc();
PixelShader = compile ps_3_0 PxlPrc();
}
}
기본적인 문법은 정점과 픽셀 처리의 함수 작성은 여태 작성한 방법과 같고, 새롭게 추가 된것은 "technique"와, "pass"가 추가 되었고 이 안에서 컴파일을 지정하고 있음을 볼 수 있다.
------ ex ------
technique Tech0
{
pass P0
{
VertexShader =
compile vs_3_0 VtxPrc();
PixelShader =
compile ps_3_0 PxlPrc();
}
pass P1
...
}
technique Tech1
{
...
"technique"은 정점과 픽셀 처리에 대한 단위이고, "pass"는 정점 처리와 픽셀 처리의 main 함수와 랜더링 상태 설정을 작성한다.
하나의 테크틱은 여러 패스를 가질 수 있고 전체 셰이더 코드는 여러 개의 테크닉을 가질 수 있다.
여러 개의 패스와 테크닉의 지원은 패스에서 여러 개의 셰이더 함수를 조합해서 하나의 패스를 만들고 이를 테크닉은 여러 패스를 설정 할 수 있어서 다양한 연출을 실행 프로그램에서가 아니라 셰이더 언어를 작성하는 곳에서 결정을 할 수 있게 된다. 또한 반복적인 내용을 분리해서 모듈의 응집력 또한 높일 수 있다.
*create()*
HRESULT hr = 0;
LPD3DXBUFFER pErr = NULL;
hr = D3DXCreateEffectFromFile(m_pdev
, "data/shader.fx"
, NULL
, NULL
, dwFlags
, NULL
, &m_pEft
, &pErr);
DWORD dFVF = VtxDUV1::FVF;
D3DVERTEXELEMENT9 vertex_decl[MAX_FVF_DECL_SIZE] = { 0 };
D3DXDeclaratorFromFVF(dFVF, vertex_decl);
if (FAILED(hr = m_pdev->CreateVertexDeclaration(vertex_decl, &m_pFVF)))
return -1;
D3DXCreateTextureFromFile(m_pdev, "texture/dx5_logo.bmp", &m_pTex);
float fSize = 30;
m_pVtx[0] = VtxDUV1(-1.f, -1.f, -1.f, 0.f, 1.f, 0XFFFF0000);
m_pVtx[1] = VtxDUV1(-1.f, 1.f, -1.f, 0.f, 0.f, 0XFF00FF00);
m_pVtx[2] = VtxDUV1(1.f, 1.f, -1.f, 1.f, 0.f, 0XFF0000FF);
m_pVtx[3] = VtxDUV1(1.f, -1.f, -1.f, 1.f, 1.f, 0XFFFF00FF);
// the back face vertex data
m_pVtx[4] = VtxDUV1(-1.f, -1.f, 1.f, 0.f, 1.f, 0XFFFFFF00);
m_pVtx[5] = VtxDUV1(1.f, -1.f, 1.f, 0.f, 0.f, 0XFF00FFFF);
m_pVtx[6] = VtxDUV1(1.f, 1.f, 1.f, 1.f, 0.f, 0XFFFF00FF);
m_pVtx[7] = VtxDUV1(-1.f, 1.f, 1.f, 1.f, 1.f, 0XFFFFFFFF);
// the top face vertex data
m_pVtx[8] = VtxDUV1(-1.f, 1.f, -1.f, 0.f, 1.f, 0XFFF0FFF0);
m_pVtx[9] = VtxDUV1(-1.f, 1.f, 1.f, 0.f, 0.f, 0XFF0F0FFF);
m_pVtx[10] = VtxDUV1(1.f, 1.f, 1.f, 1.f, 0.f, 0XFFFFF00F);
m_pVtx[11] = VtxDUV1(1.f, 1.f, -1.f, 1.f, 1.f, 0XFFFFFFFF);
// the bottom face vertex data
m_pVtx[12] = VtxDUV1(-1.f, -1.f, -1.f, 0.f, 1.f, 0XFFFF0FF0);
m_pVtx[13] = VtxDUV1(1.f, -1.f, -1.f, 0.f, 0.f, 0XFFF0F0FF);
m_pVtx[14] = VtxDUV1(1.f, -1.f, 1.f, 1.f, 0.f, 0XFF0FFF0F);
m_pVtx[15] = VtxDUV1(-1.f, -1.f, 1.f, 1.f, 1.f, 0XFFFFFFFF);
// the left face vertex data
m_pVtx[16] = VtxDUV1(-1.f, -1.f, 1.f, 0.f, 1.f, 0XFFF0FFF0);
m_pVtx[17] = VtxDUV1(-1.f, 1.f, 1.f, 0.f, 0.f, 0XFF0FF0FF);
m_pVtx[18] = VtxDUV1(-1.f, 1.f, -1.f, 1.f, 0.f, 0XFFFF0F0F);
m_pVtx[19] = VtxDUV1(-1.f, -1.f, -1.f, 1.f, 1.f, 0XFFFFFFFF);
// the right face vertex data
m_pVtx[20] = VtxDUV1(1.f, -1.f, -1.f, 0.f, 1.f, 0XFFFFF00F);
m_pVtx[21] = VtxDUV1(1.f, 1.f, -1.f, 0.f, 0.f, 0XFF0FFFF0);
m_pVtx[22] = VtxDUV1(1.f, 1.f, 1.f, 1.f, 0.f, 0XFFF00FFF);
m_pVtx[23] = VtxDUV1(1.f, -1.f, 1.f, 1.f, 1.f, 0XFFFFFFFF);
for (int i = 0; i<24; ++i)
m_pVtx[i].p *= fSize;
// the front face index data
m_pIdx[0] = VtxIdx(0, 1, 2);
m_pIdx[1] = VtxIdx(0, 2, 3);
// the back face index data
m_pIdx[2] = VtxIdx(4, 5, 6);
m_pIdx[3] = VtxIdx(4, 6, 7);
// the top face index data
m_pIdx[4] = VtxIdx(8, 9, 10);
m_pIdx[5] = VtxIdx(8, 10, 11);
// the bottom face index data
m_pIdx[6] = VtxIdx(12, 13, 14);
m_pIdx[7] = VtxIdx(12, 14, 15);
// the left face index data
m_pIdx[8] = VtxIdx(16, 17, 18);
m_pIdx[9] = VtxIdx(16, 18, 19);
// the right face index data
m_pIdx[10] = VtxIdx(20, 21, 22);
m_pIdx[11] = VtxIdx(20, 22, 23);
D3DXCreateEffect...() 함수로 이렇게 작성된 셰이더를 컴파일 하며 컴파일과 동시에 ID3DXEffect 객체를 생성한다.
이전까지 HLSL 셰이더와의 차이점은 컴파일과 동시에 이펙트 객체를 생성하고, 상수 테이블을 만들지 않고 있다. 나머지 에러에 대한 처리는 이전과 동일하다.
ID3DXEffect 객체는 내부에 상수를 설정할 수 있도록 구성되어 있고 인터페이스는 상수 테이블에서 사용한 함수들을 그대로 사용할 수 있고 Technique, Pass, 텍스처에 대한 인터페이스가 추가 되어있다.
*Render()*
HRESULT hr = -1;
D3DXMATRIX mtWld = m_mtWld;
D3DXMATRIX mtViw;
D3DXMATRIX mtPrj;
D3DXMATRIX mtWVP;
m_pdev->SetRenderState(D3DRS_LIGHTING, FALSE);
m_pdev->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
m_pdev->GetTransform(D3DTS_VIEW, &mtViw);
m_pdev->GetTransform(D3DTS_PROJECTION, &mtPrj);
mtWVP = mtWld * mtViw * mtPrj;
// Setup Constant
hr = m_pEft->SetMatrix("m_mtWVP", &mtWVP);
hr = m_pEft->SetTexture("m_TxDif", m_pTex);
// Rendering
hr = m_pdev->SetVertexDeclaration(m_pFVF);
hr = m_pEft->SetTechnique("Tech0");
UINT nPass = 0;
hr = m_pEft->Begin(&nPass, 0);
for (UINT n = 0; n < nPass; ++n)
{
hr = m_pEft->BeginPass(n);
hr = m_pdev->DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST
, 0
, 24
, 12
, m_pIdx
, D3DFMT_INDEX16
, m_pVtx
, sizeof(VtxDUV1));
m_pEft->EndPass();
}
m_pEft->End();
// 정점, 픽셀 Shader 해제
hr = m_pdev->SetVertexDeclaration(NULL);
hr = m_pdev->SetVertexShader(NULL);
hr = m_pdev->SetPixelShader(NULL);
1. ID3DXEffect로 장면을 연출하는 과정은 SetVector(), SetMatrix(), SetInt()등의 함수로 상수를 설정하고, SetTechnique() 함수로 Technique을 지정한다.
2. 다음으로 Begin() 함수를 이용해서 Pass 개수를 확인한다.
(Pass의 개수가 필요 없다면 &nPass 부분에 NULL 값을 넣어 주면 된다.)
3. Begin() 함수는 End() 함수와 반드시 짝을 이루어야 하고, Begin() / End() 함수 사이에 for문 또는 직접 인덱스를 사용해서 Pass() 함수 ( 또는 BeginPass() / EndPass() ) 아래에 Draw...() 함수를 호출하여 그린다.
Pass(), BeginPass() 함수는 Pass안에 구성된 정점 셰이더와 픽셀 셰이더 객체 사용을 지정하는 것과 같다.
( Pass() -> 2003버전 || BeginPass() / EndPass() -> 2003 이후 버전.)
4. End() 함수를 호출하면 정점 셰이더, 픽셀 셰이더 사용이 끝나야 되는데 간혹 그래픽 카드에서 처리되지 않을 경우를 대비하여 명시적으로 SetVertexShader(NULL), SetPixelShader(NULL) 처럼 셰이더 사용을 해제 해준다.