6.1 버텍스와 인풋 레이아웃
사용자 정의 버텍스 포멧을 생성하기 위해선 데이터들을 들고있을 구조체를 먼저 생성해야 한다.
다음은 두 개의 다른 버텍스에 대한 구조체이며 하나는 위치와 색상 그리고 하나는 텍스처의 위치,노멀 그리고 피벗 축을 가지고 있다.
struct Vertex1
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
};
struct Vertex2
{
XMFLOAT3 Pos;
XMFLOAT3 Normal;
XMFLOAT2 Tex0;
XMFLOAT2 Tex1;
};
정의한 후 Direct3D에게 정보를 넘겨줘야 한다 그래야 의도와 맞게 출력을 해주기 때문이다. ID3D11InputLayout 형태로 보내진다, 이러한 형태는 D3D11_INPUT_ELEMENT_DESC 구조체형인 배열이다.
typedef struct D3D11_INPUT_ELEMENT_DESC {
LPCSTR SemanticName;
UINT SemanticIndex;
DXGI_FORMAT Format;
UINT InputSlot;
UINT AlignedByteOffset;
D3D11_INPUT_CLASSIFICATION InputSlotClass;
UINT InstanceDataStepRate;
} D3D11_INPUT_ELEMENT_DESC;
1. SemanticName
ID 개념이며, 맵핑하기 위해 나중에 쓰여진다.
2. SemanticIndex
세먼틱에 부착할 인덱스이다, ID 대신 사용할 수가 있다.
3. Format
DXGI_FORMAT 열거형은 버텍스의 특정한 속성을 부여하기 위해 사용된다.
DXGI_FORMAT_R32_FLOAT // 1D 32-bit float scalar
DXGI_FORMAT_R32G32_FLOAT // 2D 32-bit float vector
DXGI_FORMAT_R32G32B32_FLOAT // 3D 32-bit float vector
DXGI_FORMAT_R32G32B32A32_FLOAT // 4D 32-bit float vector
DXGI_FORMAT_R8_UINT // 1D 8-bit unsigned integer scalar
DXGI_FORMAT_R16G16_SINT // 2D 16-bit signed integer vector
DXGI_FORMAT_R32G32B32_UINT // 3D 32-bit unsigned integer vector
DXGI_FORMAT_R8G8B8A8_SINT // 4D 8-bit signed integer vector
DXGI_FORMAT_R8G8B8A8_UINT // 4D 8-bit unsigned integer vector
4. InputSlot
버텍스의 정보를 Direct3D에게 전달하기 위해 16개의 인풋 슬롯을 지원한다 0~15, 위치와 색상을 하나의 인풋 슬롯 또는
각각의 인풋슬롯에 보내줄 수 있으며, Direct3D가 이를 결합한다.
5. AllignedByteOffset
단일 InputSlot에서 버텍스의 구조체 시작점부터 끝지점까지 얼마만큼의 바이트 단위로 멀어지는가에 대해서이다.
struct Vertex2
{
XMFLOAT3 Pos; // 0-byte offset
XMFLOAT3 Normal; // 12-byte offset
XMFLOAT2 Tex0; // 24-byte offset
XMFLOAT2 Tex1; // 32-byte offset
};
6. InputSlotClass
D3D11_INPUT_PER_VERTEX_DATA를 쓰도록 하자, 다른 옵션은 인스턴싱 고급 기술용이다.
7. InstanceDataStepRate
지금으로는 0으로 놔두자, 다른 값들은 인스턴싱 고급 기술용이다.
D3D11_INPUT_ELEMENT_DESC desc1[] =
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12,
D3D11_INPUT_PER_VERTEX_DATA, 0}
};
D3D11_INPUT_ELEMENT_DESC desc2[] =
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
D3D11_INPUT_PER_VERTEX_DATA, 0},
{"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12,
D3D11_INPUT_PER_VERTEX_DATA, 0},
{"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24,
D3D11_INPUT_PER_VERTEX_DATA, 0},
{"TEXCOORD", 1, DXGI_FORMAT_R32G32_FLOAT, 0, 32,
D3D11_INPUT_PER_VERTEX_DATA, 0}
};
Input Layout 설명서가 만들어졌다면, ID3D11InputLayout 인터페이스에 대한 포인터를 얻을 수가 있다.
HRESULT ID3D11Device::CreateInputLayout(
const D3D11_INPUT_ELEMENT_DESC *pInputElementDescs,
UINT NumElements,
const void *pShaderBytecodeWithInputSignature,
SIZE_T BytecodeLength,
ID3D11InputLayout **ppInputLayout);
- pInputElementDescs) 버텍스 구조체의 요소들의 정보가 담긴 D3D11_INPUT_ELEMENT_DESC형 배열
- NumElements) 배열의 원소 개수
- pShaderBytecodeWithInputSignature) 버텍스 셰이더 인풋 시그니처의 셰이더 바이트 코드에 대한 포인터다
- BytecodeLength) 그전에 파라미터로 넘긴 버텍스 셰이더 시그니처의 바이트 크기다
- ppInputLayout) 생성된 인풋 레이아웃에 대한 포인터를 반환한다
아래와 같이 작성할 시 Normal이 매칭이 안되서 오류가 뜬다.
D3D11: ERROR: ID3D11Device::CreateInputLayout: The provided input signature expects to read an element with
SemanticName/Index: 'NORMAL'/0, but the declaration doesn't provide a matching name.
VertexOut VS(float3 Pos : POSITION, float4 Color : COLOR,
float3 Normal : NORMAL) { }
struct Vertex
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
};
수정된 코드
VertexOut VS(int3 Pos : POSITION, float4 Color : COLOR) { }
struct Vertex
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
};
다음은 인풋 레이아웃 생성에 대한 예시다
ID3DX11Effect* mFX;
ID3DX11EffectTechnique* mTech;
ID3D11InputLayout* mInputLayout;
/* ...create the effect... */
mTech = mFX->GetTechniqueByName("Tech");
D3DX11_PASS_DESC passDesc;
mTech->GetPassByIndex(0)->GetDesc(&passDesc);
HR(md3dDevice->CreateInputLayout(vertexDesc, 4, passDesc.
pIAInputSignature, passDesc.IAInputSignatureSize, &mInputLayout));
아직 Direct3D 디바이스에 바인딩이 안되어있으므로 바인딩을 해줘야한다.
ID3D11InputLayout* mInputLayout;
/* ...create the input layout... */
md3dImmediateContext->IASetInputLayout(mInputLayout);
만약에 몇개의 오브젝트들이 한 개의 인풋 레이아웃을 사용하고 다른 오브젝트들이 다른 인풋 레이아웃을 사용하게끔 할려면
md3dImmediateContext->IASetInputLayout(mInputLayout1);
/* ...draw objects using input layout 1... */
md3dImmediateContext->IASetInputLayout(mInputLayout2);
/* ...draw objects using input layout 2... */
디바이스가 바인딩 되어있다면 덮어씌기 이전까진 정보를 유지한다.
6.2 버텍스 버퍼
그래픽카드가 버텍스의 배열에 접근을 할려면 버퍼 안에 등록이 되어있어야 한다. ID3D11Buffer 인터페이스.
버텍스들을 저장하는 버퍼를 버텍스 버퍼라고 한다. Direct3D의 버퍼는 데이터 저장뿐만 아니라 그 데이터의 접근 방식과 렌더링 파이프라인 중 어느 곳에 바인딩이 될 것인가에 대한 정보를 저장한다.
버텍스 버퍼를 생성하기 위해선 다음 과정을 거쳐야한다.
- D3D11_BUFFER_DESC 구조체 변수를 초기화한다 (생성할 버퍼 정보)
- D3D11_SUBRESOURCE_DATA 구조체 변수를 초기화한다 (초기화하고자 하는 버퍼의 데이터)
- ID3D11Device::CreateBuffer 호출해서 버퍼 생성
typedef struct D3D11_BUFFER_DESC {
UINT ByteWidth;
D3D11_USAGE Usage;
UINT BindFlags;
UINT CPUAccessFlags;
UINT MiscFlags;
UINT StructureByteStride;
} D3D11_BUFFER_DESC;
1. ByteWidth
버텍스 버퍼의 크기. (바이트 단위)
2. Usage
D3D11_USAGE 열거형, 버퍼가 어떤 방식으로 사용될건지 명시.
3. BindFlags
버텍스 버퍼는 D3D11_BIND_VERTEX_BUFFER
4. CPUAccessFlags
CPU가 버텍스 버퍼에 어떻게 접근할것인가를 명시, 0 버텍스 생성 이후 CPU의 읽기/쓰기가 불필요할 경우.
쓰기만 필요할 경우 D3D11_CPU_ACCESS_WRITE ,
쓰기 권한이 있어야한다 D3D11_USAGE_DYNAMIC 또는 D3D11_USAGE_STAGING
읽기만 필요할 경우 D3D11_CPU_ACCESS_READ
읽기 권한 D3D11_USAGE_STAGING이 명시 되어있어야 한다.
5. MiscFlags
여러가지의 리소스에 대한 플래그는 필요없으므로 0으로 표시. D3D11_RESOURCE_MISC_FLAG 검색
6. StructureByteStride
구조화된 버퍼(모든 멤버 변수들이 똑같은 크기를 유지하는 버퍼)의 크기를 바이트 단위로. 구조화된 버퍼에만 적용이 가능하고 다른 버퍼들을 0로 초기화 할 수 있다.
typedef struct D3D11_SUBRESOURCE_DATA {
const void *pSysMem;
UINT SysMemPitch;
UINT SysMemSlicePitch;
} D3D11_SUBRESOURCE_DATA;
- pSysMem) 버텍스 버퍼를 초기화할 데이터를 가지고있는 시스템 메모리 배열에 대한 포인터.
- SysMemPitch) 버텍스 버퍼에 사용되지 않음
- SysMemSlicePitch) 버텍스 버퍼에 사용되지 않음
다음은 8개의 버텍스(각기 다른 색상)를 이용하여 큐브를 만드는 코드이다.
// Colors namespace defined in d3dUtil.h.
//
// #define XMGLOBALCONST extern CONST __declspec(selectany)
// 1. extern so there is only one copy of the variable, and not a
// separate private copy in each .obj.
// 2. __declspec(selectany) so that the compiler does not complain
// about multiple definitions in a .cpp file (it can pick anyone
// and discard the rest because they are constant--all the same).
namespace Colors
{
XMGLOBALCONST XMVECTORF32 White = {1.0f, 1.0f, 1.0f, 1.0f};
XMGLOBALCONST XMVECTORF32 Black = {0.0f, 0.0f, 0.0f, 1.0f};
XMGLOBALCONST XMVECTORF32 Red = {1.0f, 0.0f, 0.0f, 1.0f};
XMGLOBALCONST XMVECTORF32 Green = {0.0f, 1.0f, 0.0f, 1.0f};
XMGLOBALCONST XMVECTORF32 Blue = {0.0f, 0.0f, 1.0f, 1.0f};
XMGLOBALCONST XMVECTORF32 Yellow = {1.0f, 1.0f, 0.0f, 1.0f};
XMGLOBALCONST XMVECTORF32 Cyan = {0.0f, 1.0f, 1.0f, 1.0f};
XMGLOBALCONST XMVECTORF32 Magenta = {1.0f, 0.0f, 1.0f, 1.0f};
}
// define raw vertex data
Vertex vertices[] =
{
{ XMFLOAT3(-1.0f, -1.0f, -1.0f), (const float*)&Colors::White },
{ XMFLOAT3(-1.0f, +1.0f, -1.0f), (const float*)&Colors::Black },
{ XMFLOAT3(+1.0f, +1.0f, -1.0f), (const float*)&Colors::Red },
{ XMFLOAT3(+1.0f, -1.0f, -1.0f), (const float*)&Colors::Green },
{ XMFLOAT3(-1.0f, -1.0f, +1.0f), (const float*)&Colors::Blue },
{ XMFLOAT3(-1.0f, +1.0f, +1.0f), (const float*)&Colors::Yellow },
{ XMFLOAT3(+1.0f, +1.0f, +1.0f), (const float*)&Colors::Cyan },
{ XMFLOAT3(+1.0f, -1.0f, +1.0f), (const float*)&Colors::Magenta }
};
D3D11_BUFFER_DESC vbd;
vbd.Usage = D3D11_USAGE_IMMUTABLE;
vbd.ByteWidth = sizeof(Vertex) * 8;
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUAccessFlags = 0;
vbd.MiscFlags = 0;
vbd.StructureByteStride = 0;
D3D11_SUBRESOURCE_DATA vinitData;
vinitData.pSysMem = vertices;
ID3D11Buffer* mVB;
HR(md3dDevice->CreateBuffer(
&vbd, // description of buffer to create
&vinitData, // data to initialize buffer with
& mVB)); // return the created buffer
// where the Vertex type and colors are defined as follows
struct Vertex
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
};
버텍스 버퍼가 생성 되었다면 바인딩을 해줘야한다.
void ID3D11DeviceContext::IASetVertexBuffers(
UINT StartSlot,
UINT NumBuffers,
ID3D11Buffer *const *ppVertexBuffers,
const UINT *pStrides,
const UINT *pOffsets);
- StartSlot) 버텍스 버퍼를 초기화해주는 바인딩 지점 인덱스
- NumBuffers) 바인딩할 버텍스 버퍼의 개수
- ppVertexBuffers) 버텍스 버퍼 배열의 첫번째 원소에 대한 포인터
- pStrides) stride/폭은 버텍스 버퍼 원소의 크기(바이트 단위)임 이에 대한 배열의 첫번째 원소에 대한 포인터
- pOffsets) Offset 배열에서 첫번째 원소에 대한 포인터
만약에 한 개 이상의 버텍스 버퍼를 사용하고 있다면 다음과 같이 코드를 작성해야 한다.
ID3D11Buffer* mVB1; // stores vertices of type Vertex1
ID3D11Buffer* mVB2; // stores vertices of type Vertex2
/*...Create the vertex buffers...*/
UINT stride = sizeof(Vertex1);
UINT offset = 0;
md3dImmediateContext->IASetVertexBuffers(0, 1, &mVB1, &stride, &offset);
/* ...draw objects using vertex buffer 1... */
stride = sizeof(Vertex2);
offset = 0;
md3dImmediateContext->IASetVertexBuffers(0, 1, &mVB2, &stride, &offset);
/* ...draw objects using vertex buffer 2... */
최종적으로 void ID3D11DeviceContext::Draw(UINT VertexCount, UINT StartVertexLocation); 함수로 그려준다.
6.3 꼭지점과 인덱스 버퍼
꼭지점들은 GPU가 접근 해야하므로 인덱스 버퍼에 등록 해야한다.
UINT indices[24] = {
0, 1, 2, // Triangle 0
0, 2, 3, // Triangle 1
0, 3, 4, // Triangle 2
0, 4, 5, // Triangle 3
0, 5, 6, // Triangle 4
0, 6, 7, // Triangle 5
0, 7, 8, // Triangle 6
0, 8, 1 // Triangle 7
};
// Describe the index buffer we are going to create. Observe the
// D3D11_BIND_INDEX_BUFFER bind flag
D3D11_BUFFER_DESC ibd;
ibd.Usage = D3D11_USAGE_IMMUTABLE;
ibd.ByteWidth = sizeof(UINT) * 24;
ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
ibd.CPUAccessFlags = 0;
ibd.MiscFlags = 0;
ibd.StructureByteStride = 0;
// Specify the data to initialize the index buffer.
D3D11_SUBRESOURCE_DATA iinitData;
iinitData.pSysMem = indices;
// Create the index buffer.
ID3D11Buffer* mIB;
HR(md3dDevice->CreateBuffer(&ibd, &iinitData, &mIB));
바인딩 후 파이프라인에 등록하는 함수는
md3dImmediateContext->IASetIndexBuffer(mIB, DXGI_FORMAT_R32_UINT, 0);
그려주는 함수
void ID3D11DeviceContext::DrawIndexed(
UINT IndexCount,
UINT StartIndexLocation,
INT BaseVertexLocation);
- IndexCount) 꼭지점의 개수
- StartIndex) 꼭지점 시작점
- BaseVertexLocation) 각 꼭지점에다 그려주기 전에 추가하는 값
각 박스 꼭지점마다 첫번째의 꼭지점의 위치를 추가 해줘야한다.
firstBoxVertexPos,
firstBoxVertexPos+1,
...,
firstBoxVertexPos+numBoxVertices-1
구, 박스와 기둥을 그리기 위해선
md3dImmediateContext->DrawIndexed(numSphereIndices, 0, 0);
md3dImmediateContext->DrawIndexed(numBoxIndices, firstBoxIndex, firstBoxVertexPos);
md3dImmediateContext->DrawIndexed(numCylIndices, firstCylIndex, firstCylVertexPos);
6.4 버텍스 셰이더
cbuffer cbPerObject
{
float4x4 gWorldViewProj;
};
void VS(float3 iPosL : POSITION,
float4 iColor : COLOR,
out float4 oPosH : SV_POSITION,
out float4 oColor : COLOR)
{
// Transform to homogeneous clip space.
oPosH = mul(float4(iPosL, 1.0f), gWorldViewProj);
// Just pass vertex color into the pixel shader.
oColor = iColor;
}
셰이더는 high level shading language (HLSL)로 작성 되어있다. 확장자는 .fx
POSITION과 COLOR은 들어오는 값
SV_POSITION (SV는 System Value의 약자)와 COLOR은 나가는 값
로컬 좌표를 클립 좌표로 변환
// Transform to homogeneous clip space.
oPosH = mul(float4(iPosL, 1.0f), gWorldViewProj);
// The color that will be fed into the next stage of the pipeline
oColor = iColor;
// Rewrite the previous
cbuffer cbPerObject
{
float4x4 gWorldViewProj;
};
struct VertexIn
{
float3 PosL : POSITION;
float4 Color : COLOR;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float4 Color : COLOR;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout;
// Transform to homogeneous clip space.
vout.PosH = mul(float4(vin.PosL, 1.0f), gWorldViewProj);
// Just pass vertex color into the pixel shader.
vout.Color = vin.Color;
return vout;
}
6.5 상수 버퍼
cbuffer cbPerObject
{
float4x4 gWVP;
};
cbuffer cbPerFrame
{
float3 gLightDirection;
float3 gLightPosition;
float4 gLightColor;
};
cbuffer cbRarely
{
float4 gFogColor;
float gFogStart;
float gFogEnd;
};
이번 예시에는 총 3개의 상수 버퍼를 사용한다.
첫번째는 복수 월드, 뷰와 프로젝션 행렬.
두번째는 씬 라이팅.
세번쨰는 안개.
6.6 픽셀 셰이더 예시
cbuffer cbPerObject
{
float4x4 gWorldViewProj;
};
void VS(float3 iPos : POSITION, float4 iColor : COLOR,
out float4 oPosH : SV_POSITION,
out float4 oColor : COLOR)
{
// Transform to homogeneous clip space.
oPosH = mul(float4(iPos, 1.0f), gWorldViewProj);
// Just pass vertex color into the pixel shader.
oColor = iColor;
}
float4 PS(float4 posH : SV_POSITION, float4 color : COLOR) : SV_Target
{
return pin.Color;
}
단순히 보간된 색상값을 반환한다. 다음과 같이 변경 가능
cbuffer cbPerObject
{
float4x4 gWorldViewProj;
};
struct VertexIn
{
float3 Pos : POSITION;
float4 Color : COLOR;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float4 Color : COLOR;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout;
// Transform to homogeneous clip space.
vout.PosH = mul(float4(vin.Pos, 1.0f), gWorldViewProj);
// Just pass vertex color into the pixel shader.
vout.Color = vin.Color;
return vout;
}
float4 PS(VertexOut pin) : SV_Target
{
return pin.Color;
}
6.7 렌더 상태
Direct3D는 매 프레임마다 돌아가고 있는 상태이다.
- ID3D11RasterizerState) 파이프라인의 레스터라이저를 설정.
- ID3D11BlendState) 블렌딩 작업을 수정할 수 있는 인터페이스다.
- ID3D11DepthStencilState) 깊이와 스텐실 상태를 수정할 수 있는 인터페이스다.
레스터라이저의 설정하는 방법 D3D11_RASTERIZER_DESC 구조체를 사용해서 가능하다.
HRESULT ID3D11Device::CreateRasterizerState(
const D3D11_RASTERIZER_DESC *pRasterizerDesc,
ID3D11RasterizerState **ppRasterizerState);
D3D11_RASTERIZER_DESC 구조체 정의는 아래와 같다.
typedef struct D3D11_RASTERIZER_DESC {
D3D11_FILL_MODE FillMode; // Default: D3D11_FILL_SOLID
D3D11_CULL_MODE CullMode; // Default: D3D11_CULL_BACK
BOOL FrontCounterClockwise; // Default: false
INT DepthBias; // Default: 0
FLOAT DepthBiasClamp; // Default: 0.0f
FLOAT SlopeScaledDepthBias; // Default: 0.0f
BOOL DepthClipEnable; // Default: true
BOOL ScissorEnable; // Default: false
BOOL MultisampleEnable; // Default: false
BOOL AntialiasedLineEnable; // Default: false
} D3D11_RASTERIZER_DESC;
대부분의 기능들은 쓰여지지 않는다, 그 중 가장 많이 사용되는걸 꼽자면.
- FillMode) D3D11_FILL_WIREFRAME 와이어 렌더링일 시, D3D11_FILL_SOLID 기본 렌더링일 시. (디폴트)
- CullMode) D3D11_CULL_NONE 기능을 아예 비활성화 할 시, D3D11_CULL_BACK 세모를 뒤로 그릴 시 (디폴트), D3D11_CULL_FRONT 세모를 앞으로 그릴 시.
- FrontCounterClockwise) 시계 방향으로 렌더링 하고자 하면 true, 그게 아니라면 false.
ID3D11RasterizerState 오브젝트가 생성 되었다면 디바이스로 새로운 블럭을 생성할 수가 있다.
void ID3D11DeviceContext::RSSetState(
ID3D11RasterizerState *pRasterizerState);
다음은 백페이스 컬링을 비활성화 시킨 레스터라이저에 대한 코드다
D3D11_RASTERIZER_DESC rsDesc;
ZeroMemory(&rsDesc, sizeof(D3D11_RASTERIZER_DESC));
rsDesc.FillMode = D3D11_FILL_SOLID;
rsDesc.CullMode = D3D11_CULL_NONE;
rsDesc.FrontCounterClockwise = false;
rsDesc.DepthClipEnable = true;
HR(md3dDevice->CreateRasterizerState(&rsDesc, &mNoCullRS));
각기 다른 방식으로 그리는 두 개의 오브젝트가 있다면 두개의 레스터라이저 스테이트를 생성 해야한다.
// Create render state objects as initialization time.
ID3D11RasterizerState* mWireframeRS;
ID3D11RasterizerState* mSolidRS;
...
// Switch between the render state objects in the draw function.
md3dImmediateContext->RSSetState(mSolidRS);
DrawObject();
md3dImmediateContext->RSSetState(mWireframeRS);
DrawObject();
Direct3D는 절대로 이전에 설정했던 상태를 저장하지 않는다. 따라서 항상 상태를 설정 해야한다.
// Restore default state.
md3dImmediateContext->RSSetState( 0 );
6.8 이펙트
이펙트 프레임워크는 셰이더 프로그램 정리 기능과 렌더링 이펙트를 구현할 때 사용되는 렌더 스테이트를 제공한다.
d3dx11Effect.h 헤더파일에 내장 되어있으며, D3DX11Effects.lib 라이브러리 파일을 링킹 해야한다.
6.8.1 이펙트 파일
확장명은 .fx이며 .cpp, .h 파일과 마찬가지로 단순한 텍스트이다. 추가적으로 셰이더와 상수 버퍼는 하나의 테크닉을 보유하고 있으며, 하나의 테크닉은 하나의 패스를 보유하고 있다.
- 테크닉) 하나 또는 여러 개의 패스를 보유한다. 각 패스별로 도형은 다르게 렌더링된다. 예로 들어서 터레인 렌더링은 여러 개의 패스가 필요한다, 단 패스 한 개마다 도형이 다시 그려져서 비용이 높아진다.
- 패스) 버텍스 셰이더, 도형 셰이더, 테셀레이션 관련 셰이더, 픽셀 셰이더와 렌더 스테이트 컴포넌트들을 보유한다, 이러한 컴포넌트들은 도형을 어떻게 셰이딩 할 것인지를 정한다.
cbuffer cbPerObject
{
float4x4 gWorldViewProj;
};
struct VertexIn
{
float3 Pos : POSITION;
float4 Color : COLOR;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float4 Color : COLOR;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout;
// Transform to homogeneous clip space.
vout.PosH = mul(float4(vin.Pos, 1.0f), gWorldViewProj);
// Just pass vertex color into the pixel shader.
vout.Color = vin.Color;
return vout;
}
float4 PS(VertexOut pin) : SV_Target
{
return pin.Color;
}
technique11 ColorTech
{
pass P0
{
SetVertexShader( CompileShader( vs_5_0, VS() ) );
SetPixelShader( CompileShader( ps_5_0, PS() ) );
}
}
레스터라이저 스테이트 블록 생성하기 위해선
RasterizerState WireframeRS
{
FillMode = Wireframe;
CullMode = Back;
FrontCounterClockwise = false;
// Default values used for any properties we do not set.
};
technique11 ColorTech
{
pass P0
{
SetVertexShader( CompileShader( vs_5_0, VS() ) );
SetPixelShader( CompileShader( ps_5_0, PS() ) );
SetRasterizerState(WireframeRS);
}
}
6.8.2 셰이더 컴파일링
.fx 파일 통해 이펙트를 생성하기 위해선 다음 함수를 호출해야한다.
HRESULT D3DX11CompileFromFile(
LPCTSTR pSrcFile,
CONST D3D10_SHADER_MACRO *pDefines,
LPD3D10INCLUDE pInclude,
LPCSTR pFunctionName,
LPCSTR pProfile,
UINT Flags1,
UINT Flags2,
ID3DX11ThreadPump *pPump,
ID3D10Blob **ppShader,
ID3D10Blob **ppErrorMsgs,
HRESULT *pHResult);
- pFileName: 컴파일 하고자 하는 .fx 파일의 명칭
- pDefines: 사용되지 않는다. NULL
- pInclude: 사용되지 않는다. NULL
- pFunctionName: 셰이더 함수 명칭, 이펙트 프레임워크를 사용한다면 NULL
- pProfile: 셰이더 버전을 명시.
- Flags1: 셰이더 코드가 어떻게 컴파일 되어야하는지 명시. 예) D3D10_SHADER_DEBUG
- Flags2: 고급 이펙트 컴파일 옵션 단 사용되지 않는다.
- pPump: 비동기식으로 셰이더를 컴파일 할 때 사용된다.
- ppShader: 컴파일된 셰이더를 저장하는 ID3D10Blob형 포인터를 반환한다.
- ppErrorMsgs: 컴파일 오류가 발생했다면 오류 문자열을 가지는 ID3D10Blob형 포인터를 반환한다.
- pHResult: 비동기식으로 처리됐을 시 에러 코드를 얻기 위함이다. pPump가 널이면 이것도 널.
셰이더 이펙트가 컴파일 됐다면 이펙트를 다음 함수를 써서 생성이 가능하다.
HRESULT D3DX11CreateEffectFromMemory(
void *pData,
SIZE_T DataLength,
UINT FXFlags,
ID3D11Device *pDevice,
ID3DX11Effect **ppEffect);
- pData: 컴파일된 이펙트 데이터에 대한 포인터
- DataLength: 컴파일된 이펙트 데이터의 크기 (바이트 단위)
- FXFlags: D3DX11CompileFromFile 함수에서 Flags2에 상응되는 플래그
- pDevice: Direct3D 11 디바이스에 대한 포인터
- ppEfect: 생성된 이펙트에 대한 포인터
DWORD shaderFlags = 0;
#if defined(DEBUG) || defined(_DEBUG)
shaderFlags |= D3D10_SHADER_DEBUG;
shaderFlags |= D3D10_SHADER_SKIP_OPTIMIZATION;
#endif
ID3D10Blob* compiledShader = 0;
ID3D10Blob* compilationMsgs = 0;
HRESULT hr = D3DX11CompileFromFile(L"color.fx", 0,
0, 0, "fx_5_0", shaderFlags,
0, 0, &compiledShader, &compilationMsgs, 0);
// compilationMsgs can store errors or warnings.
if(compilationMsgs != 0)
{
MessageBoxA(0, (char*)compilationMsgs->GetBufferPointer(), 0, 0);
ReleaseCOM(compilationMsgs);
}
// Even if there are no compilationMsgs, check to make sure there
// were no other errors.
if(FAILED(hr))
{
DXTrace(__FILE__, (DWORD)__LINE__, hr,
L"D3DX11CompileFromFile", true);
}
ID3DX11Effect* mFX;
HR(D3DX11CreateEffectFromMemory(
compiledShader->GetBufferPointer(),
compiledShader->GetBufferSize(),
0, md3dDevice, &mFX));
// Done with compiled shader.
ReleaseCOM(compiledShader);
Direct3D 리소스를 생성하는건 비용이 크므로 런타임 전에 초기화 해야한다.
6.8.3 C++ 어플레이케이션으로부터 이펙트 인터페이스 구현
이펙트와 통신 해야하므로 상수버퍼는 매 프레임마다 갱신 되야한다.
cbuffer cbPerObject
{
float4x4 gWVP;
float4 gColor;
float gSize;
int gIndex;
bool gOptionOn;
};
ID3DX11Effect 인터페이스 통해 상수 버퍼 안에 들어있는 변수들에 대한 포인터를 획득할 수가 있다.
ID3DX11EffectMatrixVariable* fxWVPVar;
ID3DX11EffectVectorVariable* fxColorVar;
ID3DX11EffectScalarVariable* fxSizeVar;
ID3DX11EffectScalarVariable* fxIndexVar;
ID3DX11EffectScalarVariable* fxOptionOnVar;
fxWVPVar = mFX->GetVariableByName("gWVP")->AsMatrix();
fxColorVar = mFX->GetVariableByName("gColor")->AsVector();
fxSizeVar = mFX->GetVariableByName("gSize")->AsScalar();
fxIndexVar = mFX->GetVariableByName("gIndex")->AsScalar();
fxOptionOnVar = mFX->GetVariableByName("gOptionOn")->AsScalar();
ID3DX11Effect::GetVariableByName 함수는 ID3DX11EffectVariable형에 대한 포인터를 반환한다. 특수 포인터를 예) 행렬, 벡터, 스칼라 획득할려면 그에 상응하는 As- 함수를 사용 해야된다.
변수에 대한 포인터를 모두 가지고 있다면 다음 함수를 써서 C++ 인터페이스를 업데이트 할 수가 있다.
fxWVPVar->SetMatrix((float*)&M ); // assume M is of type XMMATRIX
fxColorVar->SetFloatVector( (float*)&v ); // assume v is of type XMVECTOR
fxSizeVar->SetFloat( 5.0f );
fxIndexVar->SetInt( 77 );
fxOptionOnVar->SetBool( true );
이펙트 변수가 무조건 특수화 안해도된다.
ID3DX11EffectVariable* mfxEyePosVar;
mfxEyePosVar = mFX->GetVariableByName("gEyePosW");
…m
fxEyePosVar->SetRawValue(&mEyePos, 0, sizeof(XMFLOAT3));
상수 버퍼 변수는 이펙트 내에 저장되있는 테크닉 오브젝트에 대한 포인터를 획득하는게 필수다.
ID3DX11EffectTechnique* mTech;
mTech = mFX->GetTechniqueByName("ColorTech"); // 파라미터로 테크닉명칭 넘김
6.8.4 이펙트를 사용해서 그리기
테크닉을 사용해서 그리기 위해 상수 버퍼 내 모든 변수들이 업데이트 되어있어야 한다.
// Set constants
XMMATRIX world = XMLoadFloat4x4(&mWorld);
XMMATRIX view = XMLoadFloat4x4(&mView);
XMMATRIX proj = XMLoadFloat4x4(&mProj);
XMMATRIX worldViewProj = world*view*proj;
mfxWorldViewProj->SetMatrix(reinterpret_cast<float*>(&worldViewProj));
D3DX11_TECHNIQUE_DESC techDesc;
mTech->GetDesc(&techDesc);
for(UINT p = 0; p < techDesc.Passes; ++p)
{
mTech->GetPassByIndex(p)->Apply(0, md3dImmediateContext);
// Draw some geometry.
md3dImmediateContext->DrawIndexed(36, 0, 0);
}
만약에 패스에서 도형이 그려진다면, 패스에 내장되있는 셰이더와 렌더 스테이트에 대한 정보 기반으로 그려질거다.
ID3DX11EffectTechnique::GetPassByIndex 함수는 ID3DX11EffectPass형 인터페이스에 대한 포인터를 반환한다. 해당 인덱스에 상응하는 이펙트. Apply 함수는 GPU 램 즉 VRAM에 내장되있는 상수 버퍼를 업데이트 해준다. ID3DX11EffectPass::Apply 함수는 레거시화 됐다.
만약에 상수 버퍼 내에 변수들의 값을 드로우 콜 중간에 업데이트 하고싶다면 다음과 같이 하면 된다.
for(UINT i = 0; i < techDesc.Passes; ++i)
{
ID3DX11EffectPass* pass = mTech->GetPassByIndex(i);
// Set combined world-view-projection matrix for land geometry.
worldViewProj = landWorld*view*proj;
mfxWorldViewProj->SetMatrix(reinterpret_cast<float*>(&worldViewProj));
pass->Apply(0, md3dImmediateContext);
mLand.draw();
// Set combined world-view-projection matrix for wave geometry.
worldViewProj = wavesWorld*view*proj;
mfxWorldViewProj->SetMatrix(reinterpret_cast<float*>(&worldViewProj));
pass->Apply(0, md3dImmediateContext);
mWaves.draw();
}
6.8.5 빌드 타임 중 이펙트를 컴파일하기
런타임이 아닌 빌드 타임 중에 이펙트를 DirectX SDK 내에 내장되있는 fxc 툴을 사용해서 컴파일 할 수 있다. 그리고 VC++ 프로젝트를 마치 빌드 타임 중 하나의 부분인거처럼 구현할 수가 있다.
- DirectX SDK 경로가 정확하게 설정 되어있는지 확인
- 이펙트 파일을 프로젝트에 추가한다
- 각 이펙트 파일마다 우클릭해서 커스텀 빌드 툴을 추가한다. debug mode: fxc /Fc /Od /Zi /T fx_5_0 /Fo "%(RelativeDir)\%(Filename).fxo" "%(FullPath)" release mode: fxc /T fx_5_0 /Fo "%(RelativeDir)\%(Filename).fxo" "%(FullPath)"
6.8.6 이펙트 프레임워크를 셰이더 생성기로 사용
그림자 이펙트는 퀄리티가 좋아질수록 비용도 비싸진다 따라서 그래픽 카드 사양에 따른 그림자 퀄리티를 제공 해야한다.
그림자 이펙트 파일은 아래와 같이 보여질거다.
// Omit constant buffers, vertex structures, etc...
VertexOut VS(VertexIn vin) {/* Omit implementation details */}
float4 LowQualityPS(VertexOut pin) : SV_Target
{
/* Do work common to all quality levels */
/* Do low quality specific stuff */
/* Do more work common to all quality levels */
}
float4 MediumQualityPS(VertexOut pin) : SV_Target
{
/* Do work common to all quality levels */
/* Do medium quality specific stuff */
/* Do more work common to all quality levels */
}
float4 HighQualityPS(VertexOut pin) : SV_Target
{
/* Do work common to all quality levels */
/* Do high quality specific stuff */
/* Do more work common to all quality levels */
}
technique11 ShadowsLow
{
pass P0
{
SetVertexShader(CompileShader(vs_5_0, VS()));
SetPixelShader(CompileShader(ps_5_0, LowQualityPS()));
}
}
technique11 ShadowsMedium
{
pass P0
{
SetVertexShader(CompileShader(vs_5_0, VS()));
SetPixelShader( CompileShader(ps_5_0, MediumQualityPS()));
}
}
technique11 ShadowsHigh
{
pass P0
{
SetVertexShader(CompileShader(vs_5_0, VS()));
SetPixelShader( CompileShader(ps_5_0, HighQualityPS()));
}
}
하지만 문제점이 존재한다, 매우 비슷한 코드들이 많아 리팩토링이 필요하다.
// Omit constant buffers, vertex structures, etc...
VertexOut VS(VertexIn vin) {/* Omit implementation details */}
#define LowQuality 0
#define MediumQuality 1
#define HighQuality 2
float4 PS(VertexOut pin, uniform int gQuality) : SV_Target
{
/* Do work common to all quality levels */
if(gQuality == LowQuality)
{
/* Do low quality specific stuff */
}
else if(gQuality == MediumQuality)
{
/* Do medium quality specific stuff */
}
else
{
/* Do high quality specific stuff */
}/
* Do more work common to all quality levels */
}
technique11 ShadowsLow
{
pass P0
{
SetVertexShader(CompileShader(vs_5_0, VS()));
SetPixelShader(CompileShader(ps_5_0, PS(LowQuality)));
}
}
technique11 ShadowsMedium
{
pass P0
{
SetVertexShader(CompileShader(vs_5_0, VS()));
SetPixelShader( CompileShader(ps_5_0, PS(MediumQuality)));
}
}
technique11 ShadowsHigh
{
pass P0
{
SetVertexShader(CompileShader(vs_5_0, VS()));
SetPixelShader(CompileShader(ps_5_0, PS(HighQuality)));
}
}
전체 오브젝트가 아닌 일부만 텍스처를 적용하고자 하면 다음 코드를 사용하면 된다.
float4 PS(VertexOut pin, uniform bool gApplyTexture) : SV_Target
{
/* Do common work */
if(gApplyTexture)
{
/* Apply texture */
}/
* Do more common work */
}
technique11 BasicTech
{
pass P0
{
SetVertexShader(CompileShader(vs_5_0, VS()));
SetPixelShader(CompileShader(ps_5_0, PS(false)));
}
}
technique11 TextureTech
{
pass P0
{
SetVertexShader(CompileShader(vs_5_0, VS()));
SetPixelShader(CompileShader(ps_5_0, PS(true)));
}
}
광원은 1~4개가 존재할 수가 있는데 개수가 늘어날수록 비용이 많아진다, 따라서 4개의 광원을 생성 후 그에 맞는걸 선택한다.
VertexOut VS(VertexOut pin, uniform int gLightCount)
{
/* Do common work */
for(int i = 0; i < gLightCount; ++i)
{/
* do lighting work */
}/
* Do more common work */
}
technique11 Light1
{
pass P0
{
SetVertexShader(CompileShader(vs_5_0, VS(1)));
SetPixelShader(CompileShader(ps_5_0, PS()));
}
}
technique11 Light2
{
pass P0
{
SetVertexShader(CompileShader(vs_5_0, VS(2)));
SetPixelShader( CompileShader(ps_5_0, PS()));
}
}
technique11 Light3
{
pass P0
{
SetVertexShader(CompileShader(vs_5_0, VS(3)));
SetPixelShader(CompileShader(ps_5_0, PS()));
}
}
technique11 Light4
{
pass P0
{
SetVertexShader(CompileShader(vs_5_0, VS(4)));
SetPixelShader(CompileShader(ps_5_0, PS()));
}
}
'그래픽스 > DirectX' 카테고리의 다른 글
DirectX 렌더 대상 뷰 (RTV) 생성 (0) | 2023.07.12 |
---|---|
[DX11 물방울책] 챕터 7 - 광원 (0) | 2023.07.11 |
[DX11 물방울책] 챕터 5 - 렌더링 파이프라인 (0) | 2022.07.03 |
DirectX 11 프레임워크 환경 설정하는 방법 (0) | 2022.06.30 |
[DX11 물방울책] 챕터 4 - Direct3D 초기화 (0) | 2022.06.30 |