2018년 1월 16일 화요일

Vertex , 정점 정보의 확장

Vertex , 정점 정보의 확장

그동안의 설명에 있어서 우리는 VERTEX 정보에 대하여 3차원의 위치 정보만 사용하였다.

struct VERTEX{float x,y,z;};

이제 이를 확장하여 색상을 추가해 보자.
OpenGL 과 Direct3D 에서 같이 사용하도록 특정한 API에 종속되지 않는 형식으로 작성하도록 하는것이 좋다.

만약 D3DX 의 구조체를 사용한다면 아래와 같을 것이지만 이렇게 된다면 OpenGL에서는 사용하기 어렵기 때문에 이러한 방식은 사용하지 않을 것이다.
물론 매크로를 사용하여 선별적으로 사용할 수 있으나 매크로를 남발 하는 것보다 직관적으로 작성하는 것이 가장 합리적이다.

struct VERTEX{float x,y,z; D3DXCOLOR color;};

새로 작성된 VERTEX는 다음과 같다.
struct VERTEX{float x, y, z; float r,g,b,a;}

이제 다음과 같이 vertices 정보를 입력한다.
앞의 3개는 정점 좌표이고 뒤의 4개는 해당 정점의 색상을 rgba 로 입력한 것이다.
VERTEX vertices[] =
{
{0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f},   // red
{0.45f, -0.5, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f},  // green
{-0.45f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f} // blue
};


쉐이더 소스를 이제 다음과 같이 변경한다.

OpenGL GLSL
const GLchar* vertexSource = R"glsl(
#version 150 core
in vec3 position;
in vec4 color;
out vec4 out_color;
void main()
{
gl_Position = vec4(position.x,position.y, position.z, 1.0);
out_color = color;
}
)glsl";

const GLchar* fragmentSource = R"glsl(
#version 150 core
in vec4 out_color;
out vec4 pixel_color;
void main()
{
pixel_color = out_color;
}
)glsl";



Direct3D HLSL
struct VOut
{
   float4 position : SV_POSITION;
   float4 color : COLOR;
};

VOut VShader(float4 position : POSITION, float4 color : COLOR)
{
   VOut output;

   output.position = position;
   output.color = color;

   return output;
}


float4 PShader(float4 position : POSITION, float4 color : COLOR) : SV_Target
{
   return color;
}


버텍스 버퍼 데이터의 레이아웃이 변경 되었기 때문에 이를 다시 기술해 주어야 한다.

OpenGL

// Specify the layout of the vertex data
GLint posAttrib = glGetAttribLocation(shaderProgram, "position");
GLint colorAttrib = glGetAttribLocation(shaderProgram, "color");

glEnableVertexAttribArray(posAttrib);
glVertexAttribPointer(posAttrib, 3, GL_FLOAT, GL_FALSE, sizeof(VERTEX), (void *)0);

glEnableVertexAttribArray(colorAttrib);
glVertexAttribPointer(colorAttrib, 4, GL_FLOAT, GL_FALSE, sizeof(VERTEX), (void *)12);



Direct3D

D3D11_INPUT_ELEMENT_DESC ied[] =
{
  {"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}
};

dev->CreateInputLayout(ied, 2, VS->GetBufferPointer(), VS->GetBufferSize(), &pLayout);
devcon->IASetInputLayout(pLayout);


다음 그림은 OpenGL에서의 데이터 연결도를 직관적으로 그려본 것이다.


이러한 변경을 수행하여 실행하면 다음과 같은 출력이 만들어 질 것이다.

이는 OpenGL 과 Direct3D 모두 같은 모습이 될 것이다.




위의 그림에서 color 에 대한 연결은 정확한 답은 아니다. 버텍스 쉐이더는 각 정점당 호출되게 되어 있지만 픽셀쉐이더(프레그먼트 쉐이더)는 정점당 호출되는 것이 아니라 정점들에 의하여 만들어지는 삼각형을 구성하는 모든 픽셀(점) 들에 대하여 호출되어 진다. 사실 엄청 많이 발생하겠지만 병렬로 처리되는 것이니 걱정하지 말기 바란다.

OpenGL(Direct3D) 은 면을 레스터라이징화 할때 내부의 픽셀들은 정점의 색상을 보간하여 계산한다.

이를 Color Interpolation이라 하는데 이에 대한 내용은 구글링에 맡기기로 하겠다.

즉 위와 같은 그림이 나온다는 것은 픽셀쉐이더가 입력된 정점수만큼 호출되는 것이 아니라는 것을 보여주고 있기도 하다. 생성된 모든 점에 대해 호출 된다는 것이다.

Shader in C/C++ Code (HLSL)

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++ 코드에서 어떻게 사용하는지를 설명하였다.

Vertex , 정점 정보의 확장

Vertex , 정점 정보의 확장 그동안의 설명에 있어서 우리는 VERTEX 정보에 대하여 3 차원의 위치 정보만 사용하였다. struct VERTEX {float x,y,z;}; 이제 이를 확장하여 색상을...