쉐이더란
쉐이더 언어
쉐이더 언어도 여러가지가 존재한다. 대표적으로는 아래의 3가지가 있다.
•
Cg(C for graphics): NVIDIA에서 개발한 상위 레벨 쉐이딩 언어. OpenGL/DirectX 모두 호환된다.
•
HLSL(High Level Shading Language): DirectX에서 지원하는 쉐이더 언어.
•
GLSL(OpenGL Shading Language): OpenGL에서 지원하는 쉐이더 언어.
세 언어 모두 C 기반이기 때문에 문법이 유사하다. 특히 Cg와 HLSL은 거의 똑같다.
셰이더 생성 및 파이프라인 세팅 함수
레스터라이제이션에서의 셰이더 종류
정점 셰이더 (Vertex Shader)
입력 조립기가 넘겨준 정점 자료의 정점들을 한번에 하나씩 처리한다. 새로운 정점을 생성하거나 기존 정점을 삭제하지 못하고 모든 정점은 독릭접으로 처리된다. 정점 셰이더의 한 실행은 다른 실행의 정보에 접근하지 못한다.
버텍스 셰이더는 버텍스별로 실행되며, 주어진 오브젝트를 변형하고 블렌드 셰이프, GPU 스키닝 등을 사용하여 버텍스별 애니메이션을 수행하는 데 적합합니다.
테셀레이션(Tessellation) 단계
덮개 셰이더 (Hull Shader)
각 기본도형마다 실행되는 것으로, 기본 도형에 대한 일단의 테셀레이션 계수들을 결정한다. 이 계수들은 이후 과정에서 해당 기본도형을 얼마나 세밀하게 분할해야 하는지 파악하는데 쓰인다. 바람직한 출력 제어 패치 구성이 각 제어점마다 실행되기 위해, 이후 Domain 셰이더 단계에서 기본도형을 실제로 분할하는데 사용할 제어점 (Control Point)들을 만들어낸다.
테셀레이더 (Tessellator)
현재 기본도형 종류에 적합한 Sampling Pattern을 결정한다. 테셀레이션 계수들과 자기 자신의 구성을 이용해서 현재 기본도형의 정점들 중 기본도형을 더 작은 조각으로 분할하기 위한 표본으로 사용할 정점들을 결정한다. 그 정점들로부터 산출한 무게중심 좌표들을 Domain 셰이더에 넘긴다.
영역 셰이더 (Domain Shader)
무게중심 좌표 (Barycentric Coordinates)들과 덮개 셰이더가 생성한 제어점들을 입력으로 받아서 새 정점들을 생성한다. 현재 기본도형에 대해 생성된 제어점들 전체와 텍스처, 절차적 알고리즘 등을 이용해서, 테셀레이션된 각 점마다 무게중심 '위치'들을 출력 기하구조(Geometry)로 변환해서 다음 단계로 넘겨준다.
기하 셰이더 (Gemotry Shader)
완성된 형태의 기본도형들을 처리하거나 생성한다. 새로운 자료 요소를 추가하거나 제거할 수 있다. 또 처리된 기하구조를 파이프라인 바깥의 버퍼 자원으로 보낼 수 있는 분기점이기도 하다. 스트림 출력 단계가 이 버퍼 출력을 담당한다. 이 단계가 끝나면 기하구조가 래스터화되어서 단편 수준에서 처리된다.
픽셀 셰이더 (Pixel Shader)
픽셀 셰이더는 해당 픽셀 좌표에 해당하는 다른 어태치먼트를 포함하여 출력의 각 픽셀마다 실행됩니다.
파이프라인에 연결된 각 렌더 타겟을 위한 색상 값을 출력한다. 여러 텍스처에서 Sampling하거나 주어진 단편의 특성 값들로 계산을 수행한다.
자 설명은 이정도까지 하고 우리 엔진에서 셰이더연산을 셰이더 객체로 감싸서 진행 해줄것이다.
위에서 설명한 각 셰이더 단계들을 클래스 안에 넣어둔다. 또한 리소스로 셰이더 객체를 등록하여
특정 셰이더 단계를 여러 객체에서 나누어서 사용해도 메모리 적으로 이득을 볼 수 있다.
#pragma once
#include "yaResource.h"
#include "yaGraphicDevice_DX11.h"
namespace ya::graphics
{
class Shader : public Resource
{
public:
Shader();
virtual ~Shader();
virtual HRESULT Load(const std::wstring& path) override;
bool Create(const eShaderStage stage, const std::wstring& fileName);
bool CreateVertexShader(const std::wstring& fileName);
bool CreatePixelShader(const std::wstring& fileName);
void Bind();
Microsoft::WRL::ComPtr<ID3DBlob> GetVSBlob() { return mVSBlob; }
private:
Microsoft::WRL::ComPtr<ID3DBlob> mVSBlob;
Microsoft::WRL::ComPtr<ID3DBlob> mHSBlob;
Microsoft::WRL::ComPtr<ID3DBlob> mDSBlob;
Microsoft::WRL::ComPtr<ID3DBlob> mGSBlob;
Microsoft::WRL::ComPtr<ID3DBlob> mPSBlob;
Microsoft::WRL::ComPtr<ID3D11VertexShader> mVS;
Microsoft::WRL::ComPtr<ID3D11HullShader> mHS;
Microsoft::WRL::ComPtr<ID3D11DomainShader> mDS;
Microsoft::WRL::ComPtr<ID3D11GeometryShader> mGS;
Microsoft::WRL::ComPtr<ID3D11PixelShader> mPS;
};
}
C++
복사
기존에 그래픽 디바이스 초기화에서 진행해주는 각 생성단계와 이미지를 그려줄때 셰이더 설정을 파이프라인에 묶어주는 단계(void Bind())를 각각 함수로 구성하였다.
#include "yaShader.h"
namespace ya::graphics
{
Shader::Shader()
: Resource(enums::eResourceType::Shader)
{
}
Shader::~Shader()
{
}
HRESULT Shader::Load(const std::wstring& path)
{
int fineNameBeginOffset = path.rfind(L"\\") + 1;
int fineNameEndOffset = path.length() - fineNameBeginOffset;
const std::wstring fileName(path.substr(fineNameBeginOffset, fineNameEndOffset));
if (!Create(eShaderStage::VS, fileName))
return S_FALSE;
if (!Create(eShaderStage::PS, fileName))
return S_FALSE;
return S_OK;
}
bool Shader::Create(const eShaderStage stage, const std::wstring& fileName)
{
if (stage == eShaderStage::VS)
CreateVertexShader(fileName);
if (stage == eShaderStage::PS)
CreatePixelShader(fileName);
return true;
}
bool Shader::CreateVertexShader(const std::wstring& fileName)
{
if (!GetDevice()->CreateVertexShader(fileName, mVSBlob.GetAddressOf(), mVS.GetAddressOf()))
return false;
return true;
}
bool Shader::CreatePixelShader(const std::wstring& fileName)
{
if (!GetDevice()->CreatePixelShader(fileName, mPSBlob.GetAddressOf(), mPS.GetAddressOf()))
return false;
return true;
}
void Shader::Bind()
{
if (mVS)
GetDevice()->BindVS(mVS.Get());
if (mPS)
GetDevice()->BindPS(mPS.Get());
}
}
C++
복사
그리고 앞으로는 물체를 그려줄때 셰이더 리소스를 로딩해와서 세팅해준 후 그려주면 된다.
void GraphicDevice_DX11::Draw()
{
FLOAT backgroundColor[4] = { 0.2f, 0.2f, 0.2f, 1.0f };
mContext->ClearRenderTargetView(mRenderTargetView.Get(), backgroundColor);
mContext->ClearDepthStencilView(mDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.f, 0);
D3D11_VIEWPORT viewPort =
{
0, 0, application.GetWidth(), application.GetHeight(),
0.0f, 1.0f
};
mContext->RSSetViewports(1, &viewPort);
mContext->OMSetRenderTargets(1, mRenderTargetView.GetAddressOf(), mDepthStencilView.Get());
BindConstantBuffer(eShaderStage::VS, eCBType::Transform, renderer::constantBuffer);
mContext->IASetInputLayout(renderer::inputLayouts);
mContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY::D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
UINT vertexSize = sizeof(renderer::Vertex);
UINT offset = 0;
mContext->IASetVertexBuffers(0, 1, &renderer::vertexBuffer, &vertexSize, &offset);
mContext->IASetIndexBuffer(renderer::indexBuffer, DXGI_FORMAT_R32_UINT, 0);
graphics::Shader* triangle = Resources::Find<graphics::Shader>(L"TriangleShader");
triangle->Bind();
mContext->Draw(3, 0);
mSwapChain->Present(1, 0);
}
C++
복사