셰이더 개체 포팅

중요 API

OpenGL ES 2.0에서 간단한 렌더러를 포팅할 경우, 첫 번째 단계는 Direct3D 11에서 동등한 꼭짓점 및 조각 셰이더 개체를 설정하고 기본 프로그램이 컴파일된 후 셰이더 개체와 통신할 수 있게 하는 것입니다.

참고 항목

Direct3D 프로젝트를 새로 작성했나요? 그렇지 않은 경우 UWP(유니버설 Windows 플랫폼)에 대한 새 DirectX 11 프로젝트 생성의 지침을 따릅니다. 이 연습에서는 화면에 그리기 위해 DXGI 및 Direct3D 리소스를 생성했으며 템플릿에 제공된 것으로 가정합니다.

OpenGL ES 2.0과 마찬가지로 Direct3D의 컴파일된 셰이더는 그리기 컨텍스트와 연결되어야 합니다. 그러나 Direct3D에는 셰이더 프로그램 개체의 개념이 없습니다. 대신 ID3D11DeviceContext에 셰이더를 직접 할당해야 합니다. 이 단계는 셰이더 개체를 만들고 바인딩하기 위한 OpenGL ES 2.0 프로세스를 따르고 Direct3D에서 해당 API 동작을 제공합니다.

지침

1단계: 셰이더 컴파일

이 간단한 OpenGL ES 2.0 샘플에서 셰이더는 텍스트 파일로써 저장되고 런타임 컴파일용 문자열 데이터로써 로드됩니다.

OpenGL ES 2.0: 셰이더 컴파일

GLuint __cdecl CompileShader (GLenum shaderType, const char *shaderSrcStr)
// shaderType can be GL_VERTEX_SHADER or GL_FRAGMENT_SHADER. Returns 0 if compilation fails.
{
  GLuint shaderHandle;
  GLint compiledShaderHandle;
   
  // Create an empty shader object.
  shaderHandle = glCreateShader(shaderType);

  if (shaderHandle == 0)
  return 0;

  // Load the GLSL shader source as a string value. You could obtain it from
  // from reading a text file or hardcoded.
  glShaderSource(shaderHandle, 1, &shaderSrcStr, NULL);
   
  // Compile the shader.
  glCompileShader(shaderHandle);

  // Check the compile status
  glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compiledShaderHandle);

  if (!compiledShaderHandle) // error in compilation occurred
  {
    // Handle any errors here.
              
    glDeleteShader(shaderHandle);
    return 0;
  }

  return shaderHandle;

}

Direct3D에서 셰이더는 런타임 동안 컴파일되지 않습니다. 프로그램의 나머지 부분을 컴파일하는 경우 항상 CSO 파일로 컴파일됩니다. Microsoft Visual Studio를 사용하여 앱을 컴파일할 경우, HLSL 파일이 앱에서 로드해야 하는 CSO(.cso) 파일로 컴파일됩니다. 패키지할 때 앱에 이러한 CSO 파일을 포함해야 합니다!

참고 다음 예제에서는 자동 키워드 및 람다 구문을 사용하여 셰이더 로드 및 컴파일을 비동기적으로 수행합니다. ReadDataAsync()는 CSO 파일에서 바이트 데이터 배열(fileData)로 읽는 템플릿에 구현된 메서드입니다.

 

Direct3D 11: 셰이더 컴파일

auto loadVSTask = DX::ReadDataAsync(m_projectDir + "SimpleVertexShader.cso");
auto loadPSTask = DX::ReadDataAsync(m_projectDir + "SimplePixelShader.cso");

auto createVSTask = loadVSTask.then([this](Platform::Array<byte>^ fileData) {

m_d3dDevice->CreateVertexShader(
  fileData->Data,
  fileData->Length,
  nullptr,
  &m_vertexShader);

auto createPSTask = loadPSTask.then([this](Platform::Array<byte>^ fileData) {
  m_d3dDevice->CreatePixelShader(
    fileData->Data,
    fileData->Length,
    nullptr,
    &m_pixelShader;
};

2단계: 꼭짓점 및 조각(픽셀) 셰이더 생성 및 로드

OpenGL ES 2.0에는 CPU에서 실행되는 기본 프로그램과 GPU에서 실행되는 셰이더 간의 인터페이스 역할을 하는 셰이더 '프로그램'이라는 개념이 있습니다. 셰이더는 컴파일되거나 컴파일된 원본에서 로드되며, 이는 GPU에서 실행할 수 있는 프로그램과 연결됩니다.

OpenGL ES 2.0: 셰이딩 프로그램에 꼭짓점 및 조각 셰이더 로드

GLuint __cdecl LoadShaderProgram (const char *vertShaderSrcStr, const char *fragShaderSrcStr)
{
  GLuint programObject, vertexShaderHandle, fragmentShaderHandle;
  GLint linkStatusCode;

  // Load the vertex shader and compile it to an internal executable format.
  vertexShaderHandle = CompileShader(GL_VERTEX_SHADER, vertShaderSrcStr);
  if (vertexShaderHandle == 0)
  {
    glDeleteShader(vertexShaderHandle);
    return 0;
  }

   // Load the fragment/pixel shader and compile it to an internal executable format.
  fragmentShaderHandle = CompileShader(GL_FRAGMENT_SHADER, fragShaderSrcStr);
  if (fragmentShaderHandle == 0)
  {
    glDeleteShader(fragmentShaderHandle);
    return 0;
  }

  // Create the program object proper.
  programObject = glCreateProgram();
   
  if (programObject == 0)    return 0;

  // Attach the compiled shaders
  glAttachShader(programObject, vertexShaderHandle);
  glAttachShader(programObject, fragmentShaderHandle);

  // Compile the shaders into binary executables in memory and link them to the program object..
  glLinkProgram(programObject);

  // Check the project object link status and determine if the program is available.
  glGetProgramiv(programObject, GL_LINK_STATUS, &linkStatusCode);

  if (!linkStatusCode) // if link status <> 0
  {
    // Linking failed; delete the program object and return a failure code (0).

    glDeleteProgram (programObject);
    return 0;
  }

  // Deallocate the unused shader resources. The actual executables are part of the program object.
  glDeleteShader(vertexShaderHandle);
  glDeleteShader(fragmentShaderHandle);

  return programObject;
}

// ...

glUseProgram(renderer->programObject);

Direct3D에는 셰이더 프로그램 개체의 개념이 없습니다. 대신 ID3D11Device 인터페이스(예: ID3D11Device::CreateVertexShader 또는 ID3D11Device::CreatePixelShader)의 셰이더 생성 메서드 중 하나가 호출될 때 셰이더가 생성됩니다. 현재 그리기 컨텍스트에 대한 셰이더를 설정하려면 꼭짓점 셰이더의 경우 ID3D11DeviceContext::VSSetShader와 같은 집합 셰이더 메서드를 사용하여 해당 ID3D11DeviceContext에 제공하거나 조각 셰이더에 대한 ID3D11DeviceContext::PSSetShader를 제공합니다.

Direct3D 11: 그래픽 디바이스 그리기 컨텍스트에 대한 셰이더를 설정합니다.

m_d3dContext->VSSetShader(
  m_vertexShader.Get(),
  nullptr,
  0);

m_d3dContext->PSSetShader(
  m_pixelShader.Get(),
  nullptr,
  0);

3단계: 셰이더에 제공할 데이터 정의

OpenGL ES 2.0 예제에서는 셰이더 파이프라인에 대해 선언할 유니폼이 하나 있습니다.

  • u_mvpMatrix: 큐브에 대한 모델 좌표를 가져오고 스캔 변환을 위한 2D 투영 좌표로 이 좌표를 변환하는 최종 model-view-projection 변환 행렬을 나타내는 float의 4x4 배열

꼭짓점 데이터에 대한 두 가지 특성 값은 다음과 같습니다.

  • a_position: 꼭짓점의 모델 좌표에 대한 소수점 4-float 벡터
  • a_color: 꼭짓점과 연결된 RGBA 색상 값에 대한 4-float 벡터

GL ES 2.0 열기: 유니폼 및 특성에 대한 GLSL 정의

uniform mat4 u_mvpMatrix;
attribute vec4 a_position;
attribute vec4 a_color;

이 경우 해당 기본 프로그램 변수가 렌더러 개체의 필드로 정의됩니다. (방법: 간단한 OpenGL ES 2.0 렌더러를 Direct3D 11로 포팅의 헤더를 참조합니다.) 이 작업을 마치면 메인 프로그램이 셰이더 파이프라인에 대해 이러한 값을 제공할 메모리의 위치를 지정해야 하며, 이는 일반적으로 그리기 호출 직전에 수행합니다.

OpenGL ES 2.0: 유니폼 및 특성 데이터의 위치 표시


// Inform the shader of the attribute locations
loc = glGetAttribLocation(renderer->programObject, "a_position");
glVertexAttribPointer(loc, 3, GL_FLOAT, GL_FALSE, 
    sizeof(Vertex), 0);
glEnableVertexAttribArray(loc);

loc = glGetAttribLocation(renderer->programObject, "a_color");
glVertexAttribPointer(loc, 4, GL_FLOAT, GL_FALSE, 
    sizeof(Vertex), (GLvoid*) (sizeof(float) * 3));
glEnableVertexAttribArray(loc);


// Inform the shader program of the uniform location
renderer->mvpLoc = glGetUniformLocation(renderer->programObject, "u_mvpMatrix");

Direct3D에는 동일한 의미에서 '특성' 또는 '균일'의 개념이 없습니다(또는 적어도 이 구문을 공유하지 않음). 대신 Direct3D 하위 리소스로 표현되는 상수 버퍼(기본 프로그램과 셰이더 프로그램 간에 공유되는 리소스)가 있습니다. 꼭짓점 위치 및 색과 같은 하위 리소스 중 일부는 HLSL 의미 체계로 설명됩니다. OpenGL ES 2.0 개념과 관련된 상수 버퍼 및 HLSL 의미 체계에 대한 자세한 내용은 포트 프레임 버퍼 개체, 유니폼 및 특성을 참조하세요.

이 프로세스를 Direct3D로 이동할 때 균일을 Direct3D 상수 버퍼(cbuffer)로 변환하고 레지스터 HLSL 의미 체계를 사용하여 조회를 위한 레지스터를 할당합니다. 두 꼭짓점 특성은 셰이더 파이프라인 단계에 대한 입력 요소로 처리되며 셰이더를 알리는 HLSL 의미 체계(POSITION 및 COLOR0)도 할당됩니다. 픽셀 셰이더는 GPU에서 생성하는 시스템 값임을 나타내는 SV_ 접두사가 있는 SV_POSITION을 가져옵니다. (이 경우 스캔 변환 중에 생성된 픽셀 위치입니다.) 꼭짓점 버퍼를 정의하는 데 사용되고(꼭짓점 버퍼 및 데이터 포팅 참조) 후자의 데이터는 파이프라인의 이전 단계(이 경우 꼭짓점 셰이더)의 결과로 생성되기 때문에 VertexShaderInput 및 PixelShaderInput은 상수 버퍼로 선언되지 않습니다.

Direct3D: 상수 버퍼 및 꼭짓점 데이터에 대한 HLSL 정의

cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
  matrix mvp;
};

// Per-vertex data used as input to the vertex shader.
struct VertexShaderInput
{
  float4 pos : POSITION;
  float4 color : COLOR0;
};

// Per-vertex color data passed through the pixel shader.
struct PixelShaderInput
{
  float4 pos : SV_POSITION;
  float3 color : COLOR0;
};

상수 버퍼로 포팅하고 HLSL 의미 체계를 적용하는 방법에 대한 자세한 내용은 포트 프레임 버퍼 개체, 유니폼, 특성을 참조하세요.

상수 또는 꼭짓점 버퍼를 사용하여 셰이더 파이프라인에 전달된 데이터의 레이아웃 구조는 다음과 같습니다.

Direct3D 11: 상수 및 꼭짓점 버퍼 레이아웃 선언

// Constant buffer used to send MVP matrices to the vertex shader.
struct ModelViewProjectionConstantBuffer
{
  DirectX::XMFLOAT4X4 modelViewProjection;
};

// Used to send per-vertex data to the vertex shader.
struct VertexPositionColor
{
  DirectX::XMFLOAT4 pos;
  DirectX::XMFLOAT4 color;
};

상수 버퍼 요소에 대한 DirectXMath XM* 유형을 사용합니다. 이 유형은 셰이더 파이프라인에 전송될 때 콘텐츠에 대한 적절한 압축 및 맞춤을 제공하기 때문입니다. 표준 Windows 플랫폼 float 형식 및 배열을 사용하는 경우, 직접 압축 및 정렬을 수행해야 합니다.

상수 버퍼를 바인딩하기 위해 레이아웃 설명을 CD3D11_BUFFER_DESC 구조로 만들고 이 구조를 ID3DDevice::CreateBuffer에 전달합니다. 그런 다음 렌더링 메서드에서 그리기 전에 상수 버퍼를 ID3D11DeviceContext::UpdateSubresource에 전달합니다.

Direct3D 11: 상수 버퍼 바인딩

CD3D11_BUFFER_DESC constantBufferDesc(sizeof(ModelViewProjectionConstantBuffer), D3D11_BIND_CONSTANT_BUFFER);

m_d3dDevice->CreateBuffer(
  &constantBufferDesc,
  nullptr,
  &m_constantBuffer);

// ...

// Only update shader resources that have changed since the last frame.
m_d3dContext->UpdateSubresource(
  m_constantBuffer.Get(),
  0,
  NULL,
  &m_constantBufferData,
  0,
  0);

꼭짓점 버퍼도 비슷하게 생성하고 업데이트되며, 다음 단계에서는 꼭짓점 버퍼 및 데이터 포팅에 대해 설명합니다.

다음 단계

꼭짓점 버퍼 및 데이터 포팅

방법 - 간단한 OpenGL ES 2.0 렌더러를 Direct3D 11로 포팅

꼭짓점 버퍼 및 데이터 포팅

GLSL 포팅

화면에 그리기