Shader in C/C++ Code (HLSL)
이제 HLSL 에서 쉐이더를 어떻게 만들고 컴파일하여 사용하는지를 살펴보자.
struct VOut
{ float4 position : SV_POSITION; }; VOut VShader(float4 position : POSITION) { VOut output; output.position = position; return output; } float4 PShader() : SV_Target { return float4(1.0,1.0,1.0,1.0); } |
HLSL로 작성된 버텍스쉐이더와 픽셀쉐이더이다.
외부 파일로 준비하도록 하자. 그 이유는 OpenGL의 쉐이더 컴파일러는 string 을 입력하여 컴파일 하도록 되어 있다. 그러나 Direct3D의 경우 외부 파일을 컴파일 하도록 되어 있다. 물론 string 메모리를 사용하여 컴파일 할 수 있는 방법은 있으나 복잡하다. 그럴 이유가 없다.
참고적으로 D3DCompiler에 대한 함수는 다음 링크를 잠고하기 바란다.
OpenGL의 예 에서와 마찬가지로 다음과 같은 데이터를 준비한다.
struct VERTEX{FLOAT X, Y, Z;};
VERTEX OurVertices[] = { {0.0f, 0.5f, 0.0f}, {0.45f, -0.5, 0.0f}, {-0.45f, -0.5f, 0.0f} }; |
다음과 같이 버텍스 버퍼를 생성한 후 데이터를 넣도록 하자.
// create the vertex buffer
D3D11_BUFFER_DESC bd; ZeroMemory(&bd, sizeof(bd)); bd.Usage = D3D11_USAGE_DYNAMIC; // write access access by CPU and GPU bd.ByteWidth = sizeof(VERTEX) * 3; // size is the VERTEX struct * 3 bd.BindFlags = D3D11_BIND_VERTEX_BUFFER; // use as a vertex buffer bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; // allow CPU to write in buffer dev->CreateBuffer(&bd, NULL, &pVBuffer); // create the buffer |
우리는 OpenGL에서 버텍스 버퍼의 자료의 구조를 Attribute 라는 것으로 설정해 주었다. 이러한 일을 D3D 에서는 D3D11_BUFFER_DESC 가 수행하게 된다.
즉 buffer descriptor 인 것이다.
위의 코드는 Buffer Desc 를 설정하고 d3d device 에 버텍스 버퍼를 하나 만든 것이다.
이렇게 만든 버퍼에 OurVertices 배열에 있는 정점 정보를 전달하도록 하자.
// copy the vertices into the buffer
D3D11_MAPPED_SUBRESOURCE ms; devcon->Map(pVBuffer, NULL, D3D11_MAP_WRITE_DISCARD, NULL, &ms); // map the buffer memcpy(ms.pData, OurVertices, sizeof(OurVertices)); // copy the data devcon->Unmap(pVBuffer, NULL); // unmap the buffer |
쉐이더의 컴파일
D3DCompileFromFile() 함수는 전달되는 인수도 많고 복잡하다. 그러니 아래와 같이 간단하게 호출할 수 있는 함수를 만들어 사용하도록 하자.
참고적으로 MSDN 에서는 이 함수에 대하여 다음과 같이 말하고 있다.
The D3dcompiler_44.dll or later version of the file contains the D3DCompileFromFile compiler function.
즉 D3DCompileFromFile 을 사용하기 위해서는 D3dcompiler_44.dll 또는 그 이후 버전의 파일이 필요하다.
이 파일은 Windows SDK 의 Direct3D 관런 내용에서 에서 찾아볼수 있을 것이다.
HRESULT CompileShader(const LPCWSTR srcFile, LPCSTR entryPoint, LPCSTR profile, ID3DBlob** blob )
{ // if ( !entryPoint || !profile || !blob ) return E_INVALIDARG; *blob = nullptr; UINT flags = D3DCOMPILE_ENABLE_STRICTNESS; #if defined( DEBUG ) || defined( _DEBUG ) flags |= D3DCOMPILE_DEBUG; #endif const D3D_SHADER_MACRO defines[] = { "EXAMPLE_DEFINE", "1", NULL, NULL }; ID3DBlob* shaderBlob = nullptr; ID3DBlob* errorBlob = nullptr; HRESULT hr = D3DCompileFromFile( srcFile, defines, D3D_COMPILE_STANDARD_FILE_INCLUDE, entryPoint, profile, flags, 0, &shaderBlob, &errorBlob ); if ( FAILED(hr) ) { if ( errorBlob ) { OutputDebugStringA( (char*)errorBlob->GetBufferPointer() ); errorBlob->Release(); } if ( shaderBlob ) shaderBlob->Release(); return hr; } *blob = shaderBlob; return hr; } |
위의 함수를 이용하여 컴파일 쉐이더 코드를 컴파일 해보자.
shader.sfx 파일은 위에서 설명한 쉐이더 프로그램 소스코드 이다.
// load and compile the two shaders
ID3D10Blob *VS, *PS; CompileShader(L"shader.sfx","VShader","vs_4_0",&VS); CompileShader(L"shader.sfx","PShader","ps_4_0",&PS); |
컴파일된 쉐이터 코드들은 아래와 같이하여 device 에 의하여 shader object로 만들 수 있다.
// encapsulate both shaders into shader objects
dev->CreateVertexShader(VS->GetBufferPointer(), VS->GetBufferSize(), NULL, &pVS); dev->CreatePixelShader(PS->GetBufferPointer(), PS->GetBufferSize(), NULL, &pPS); |
생성된 shader object 를 Device Context에서 사용하도록 설정하는 부분이다.
// set the shader objects
devcon->VSSetShader(pVS, 0, 0); devcon->PSSetShader(pPS, 0, 0); |
다음 코드는 버텍스 버퍼의 자료 구조를 쉐이더에게 알려주는 기능을 수행한다. 즉 레이아웃 을 수행하는 것이다. 이러한 것은 GLSL에서도 수행한 적이 있음을 기억하자.
이론적으로는 GLSL과 같으니 자세한 설명은 하지 않겠다.
// create the input layout object
D3D11_INPUT_ELEMENT_DESC ied[] = { {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0} }; dev->CreateInputLayout(ied, 1, VS->GetBufferPointer(), VS->GetBufferSize(), &pLayout); devcon->IASetInputLayout(pLayout); |
이상으로 HLSL 쉐이더를 C++ 코드에서 어떻게 사용하는지를 설명하였다.
댓글 없음:
댓글 쓰기