DirectX11 Tutorial 37: 인스턴싱

강좌번역/DirectX 11 2018. 8. 15. 12:55 by 빠재

원문: Tutorial 37: Instancing

그래픽스 엔진은 종종 위치나 크기, 색상 등의 것들은 다르지만 모양은 똑같은 물체를 아주 많이 그려야 하는 기능이 필요할 때가 있습니다. 그렇게 수백 수천의 다른 모델을 그리는 좋은 실례로는 파티클이나 폴리지(풀 같은 것들), 나무 엔진들이 있습니다. 이런 시스템은 대개 비효율적이거나 그래픽 카드에 많은 데이터를 보내게 되기 쉽습니다.

인스턴싱은 모델 정보를 가지고 있는 하나의 정점 버퍼와 인스턴스 버퍼라는 각 복사본의 변경점들만 담은 두 번째 버퍼를 사용하여 위의 문제를 해결하는 DirectX 11의 기능입니다. 정점 버퍼는 그래픽 카드에 계속 캐싱되어 있고 인스턴스 버퍼의 내용대로 바뀌어 그려지게 됩니다.

이번 예제는 5번 예제인 [텍스쳐]()를 가져다가 인스턴싱을 사용하여 네 개의 삼각형을 서로 다른 위치에 그려 이 기능이 기본적으로 어떻게 동작하는지 알려드리도록 하겠습니다. 인스턴싱의 구현을 위해 ModelClass, TextureShaderClass 그리고 텍스쳐 HLSL 프로그램들이 변경될 것입니다. 우선 ModelClass를 수정하는 것으로 인스턴싱의 구현을 시작하겠습니다.

Modelclass.h

////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "textureclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: ModelClass
////////////////////////////////////////////////////////////////////////////////
class ModelClass
{
private:
    struct VertexType
    {
        D3DXVECTOR3 position;
        D3DXVECTOR2 texture;
    };

인스턴스의 정보를 담아둘 구조체를 선언합니다. 이 예제에서는 삼각형 인스턴스의 위치를 바꿀 것이기 때문에 위치 벡터를 저장할 것입니다. 하지만 여러분이 원하는 바에 따라 색상이나 크기, 회전 등등 어느것이든지 바꾸게 할 수 있습니다. 인스턴스마다 여러 종류를 바꾸게 하는 것도 가능합니다.

    struct InstanceType
    {
        D3DXVECTOR3 position;
    };

public:
    ModelClass();
    ModelClass(const ModelClass&);
    ~ModelClass();

    bool Initialize(ID3D11Device*, WCHAR*);
    void Shutdown();
    void Render(ID3D11DeviceContext*);

정점과 인스턴스의 개수를 각각 가져오는 함수를 만듭니다. 인스턴스 개수를 가져오는 함수가 인덱스 개수를 가져오는 기능을 대체하므로 기존의 인덱스 가져오는 도우미 함수는 제거합니다.

    int GetVertexCount();
    int GetInstanceCount();
    ID3D11ShaderResourceView* GetTexture();

private:
    bool InitializeBuffers(ID3D11Device*);
    void ShutdownBuffers();
    void RenderBuffers(ID3D11DeviceContext*);

    bool LoadTexture(ID3D11Device*, WCHAR*);
    void ReleaseTexture();

private:
    ID3D11Buffer* m_vertexBuffer;

이번 ModelClass에는 인덱스 버퍼 대신 인스턴스 버퍼가 있습니다. DirectX의 모든 버퍼는 원소 타입을 가리지 않으므로 여전히 ID3D11Buffer 타입입니다.

    ID3D11Buffer* m_instanceBuffer;
    int m_vertexCount;

인덱스 개수는 인스턴스 개수로 대체됩니다.

    int m_instanceCount;
    TextureClass* m_Texture;
};

#endif

Modelclass.cpp

////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "modelclass.h"


ModelClass::ModelClass()
{
    m_vertexBuffer = 0;

인스턴스 버퍼의 포인터를 null로 초기화합니다.

    m_instanceBuffer = 0;
    m_Texture = 0;
}


ModelClass::ModelClass(const ModelClass& other)
{
}


ModelClass::~ModelClass()
{
}


bool ModelClass::Initialize(ID3D11Device* device, WCHAR* textureFilename)
{
    bool result;


    // Initialize the vertex and instance buffers.
    result = InitializeBuffers(device);
    if(!result)
    {
        return false;
    }

    // Load the texture for this model.
    result = LoadTexture(device, textureFilename);
    if(!result)
    {
        return false;
    }

    return true;
}


void ModelClass::Shutdown()
{
    // Release the model texture.
    ReleaseTexture();

    // Shutdown the vertex and instance buffers.
    ShutdownBuffers();

    return;
}


void ModelClass::Render(ID3D11DeviceContext* deviceContext)
{
    // Put the vertex and instance buffers on the graphics pipeline to prepare them for drawing.
    RenderBuffers(deviceContext);

    return;
}

정점의 개수와 인스턴스의 개수를 알려주는 도우미 함수입니다.

int ModelClass::GetVertexCount()
{
    return m_vertexCount;
}


int ModelClass::GetInstanceCount()
{
    return m_instanceCount;
}


ID3D11ShaderResourceView* ModelClass::GetTexture()
{
    return m_Texture->GetTexture();
}


bool ModelClass::InitializeBuffers(ID3D11Device* device)
{
    VertexType* vertices;
    InstanceType* instances;
    D3D11_BUFFER_DESC vertexBufferDesc, instanceBufferDesc;
    D3D11_SUBRESOURCE_DATA vertexData, instanceData;
    HRESULT result;

우선 삼각형을 담아둘 정점 버퍼는 이전과 동일하게 설정합니다. 하지만 이번에는 인덱스 버퍼를 만들지 않습니다.

    // 정점 배열의 길이입니다.
    m_vertexCount = 3;

    // 정점 배열을 생성합니다.
    vertices = new VertexType[m_vertexCount];
    if(!vertices)
    {
        return false;
    }

    // 정점 배열에 데이터를 넣습니다.
    vertices[0].position = D3DXVECTOR3(-1.0f, -1.0f, 0.0f);  // Bottom left.
    vertices[0].texture = D3DXVECTOR2(0.0f, 1.0f);

    vertices[1].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f);  // Top middle.
    vertices[1].texture = D3DXVECTOR2(0.5f, 0.0f);

    vertices[2].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f);  // Bottom right.
    vertices[2].texture = D3DXVECTOR2(1.0f, 1.0f);

    // 정적 정점 버퍼의 디스크립션입니다.
    vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
    vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vertexBufferDesc.CPUAccessFlags = 0;
    vertexBufferDesc.MiscFlags = 0;
    vertexBufferDesc.StructureByteStride = 0;

    // 서브리소스 구조체에 정점 데이터의 포인터를 넣어줍니다.
    vertexData.pSysMem = vertices;
    vertexData.SysMemPitch = 0;
    vertexData.SysMemSlicePitch = 0;

    // 정점 버퍼를 생성합니다.
    result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
    if(FAILED(result))
    {
        return false;
    }

    // 정점 버퍼가 생성되었으므로 정점 배열은 할당 해제합니다.
    delete [] vertices;
    vertices = 0;

이제 이번에 추가한 인스턴스 버퍼를 생성합니다. 우선 렌더링되어야 할 삼각형 인스턴스의 개수를 설정합니다. 이번 예제에서는 4개의 삼각형을 화면에 그릴 것이기 때문에 4로 설정합니다.

    // 인스턴스 수를 설정합니다.
    m_instanceCount = 4;

이 인스턴스 숫자를 가지고 임시로 사용할 인스턴스 배열을 생성합니다. 참고로 배열의 타입은 ModelClass 헤더 파일에 있던 InstanceType 구조체입니다.

    // 인스턴스 배열을 생성합니다.
    instances = new InstanceType[m_instanceCount];
    if(!instances)
    {
        return false;
    }

이제 각 삼각형들의 위치를 다르게 지정해 줍니다. 삼각형별로 4개의 다른 x, y, z 위치를 설정했습니다. 원한다면 이 지점에서 색상이나 크기, 다른 텍스쳐 좌표 등등을 바꿀 수도 있습니다. 여러분이 원하는 어떤 방향으로든지 인스턴스를 수정할 수 있습니다. 눈으로 인스턴스가 동작하는지 확인을 쉽게 하기 위하여 이 예제에서는 위치를 바꿉니다.

    // 인스턴스 배열에 데이터를 넣습니다.
    instances[0].position = D3DXVECTOR3(-1.5f, -1.5f, 5.0f);
    instances[1].position = D3DXVECTOR3(-1.5f,  1.5f, 5.0f);
    instances[2].position = D3DXVECTOR3( 1.5f, -1.5f, 5.0f);
    instances[3].position = D3DXVECTOR3( 1.5f,  1.5f, 5.0f);

인스턴스 버퍼 디스크립션은 정점 버퍼 디스크립션과 동일하게 설정합니다.

    // 인스턴스 버퍼의 디스크립션입니다.
    instanceBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    instanceBufferDesc.ByteWidth = sizeof(InstanceType) * m_instanceCount;
    instanceBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    instanceBufferDesc.CPUAccessFlags = 0;
    instanceBufferDesc.MiscFlags = 0;
    instanceBufferDesc.StructureByteStride = 0;

정점 버퍼 때와 동일하게 인스턴스 배열의 포인터를 가지고 인스턴스 버퍼를 생성합니다. 생성하고 나면 임시로 생성했던 인스턴스 배열의 데이터는 이미 버퍼로 복사가 되었으므로 임시 배열의 할당을 해제합니다.

    // 서브리소스 구조체에 인스턴스 데이터의 포인터를 설정합니다.
    instanceData.pSysMem = instances;
    instanceData.SysMemPitch = 0;
    instanceData.SysMemSlicePitch = 0;

    // 인스턴스 버퍼를 생성합니다.
    result = device->CreateBuffer(&instanceBufferDesc, &instanceData, &m_instanceBuffer);
    if(FAILED(result))
    {
        return false;
    }

    // 인스턴스 버퍼가 생성되었으므로 인스턴스 배열의 할당을 해제합니다.
    delete [] instances;
    instances = 0;

    return true;
}


void ModelClass::ShutdownBuffers()
{

ShutdownBuffers함수에서 인스턴스 버퍼를 해제합니다.

    // 인스턴스 버퍼를 해제합니다.
    if(m_instanceBuffer)
    {
        m_instanceBuffer->Release();
        m_instanceBuffer = 0;
    }

    // 정점 버퍼를 해제합니다.
    if(m_vertexBuffer)
    {
        m_vertexBuffer->Release();
        m_vertexBuffer = 0;
    }

    return;
}


void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext)
{
    unsigned int strides[2];
    unsigned int offsets[2];
    ID3D11Buffer* bufferPointers[2];

인스턴스 버퍼는 단지 다른 정보를 담고 있는 두 번째 정점 버퍼이기 때문에 정점 버퍼가 세팅되는 시점에 같이 세팅됩니다. 따라서 이번에는 IASetVertexBuffers함수를 호출할 때 스트라이드와 오프셋, 버퍼를 하나만 보내는 것이 아니라 배열로 만들어 보냅니다.

우선 VertexTypeInstanceType의 크기를 스트라이드로 정해줍니다.

    // 버퍼의 스트라이드를 설정합니다.
    strides[0] = sizeof(VertexType); 
    strides[1] = sizeof(InstanceType); 

정점과 인스턴스 버퍼의 오프셋을 설정합니다.

    // 버퍼의 오프셋을 설정합니다.
    offsets[0] = 0;
    offsets[1] = 0;

그런 다음 정점 버퍼와 인스턴스 버퍼의 포인터를 담고 있는 배열을 하나 만듭니다.

    // 정점과 인스턴스 버퍼를 가리키는 포인터 배열을 설정합니다.
    bufferPointers[0] = m_vertexBuffer; 
    bufferPointers[1] = m_instanceBuffer;

마지막으로 정점 버퍼와 인스턴스 버퍼를 한번에 디바이스 컨텍스트에 설정합니다.

    // 정점 버퍼를 입력 어셈블러에서 활성화하여 렌더링되게 합니다.
    deviceContext->IASetVertexBuffers(0, 2, bufferPointers, strides, offsets);

    // 정점 버퍼의 내용이 그려질 도형의 타입을 정합니다. 이번에는 삼각형입니다.
    deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    return;
}


bool ModelClass::LoadTexture(ID3D11Device* device, WCHAR* filename)
{
    bool result;


    // Create the texture object.
    m_Texture = new TextureClass;
    if(!m_Texture)
    {
        return false;
    }

    // Initialize the texture object.
    result = m_Texture->Initialize(device, filename);
    if(!result)
    {
        return false;
    }

    return true;
}


void ModelClass::ReleaseTexture()
{
    // Release the texture object.
    if(m_Texture)
    {
        m_Texture->Shutdown();
        delete m_Texture;
        m_Texture = 0;
    }

    return;
}

Textureshaderclass.h

TextureShaderClass 역시 셰이더에 인스턴싱을 사용할 수 있게끔 수정되었습니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: textureshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TEXTURESHADERCLASS_H_
#define _TEXTURESHADERCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;


////////////////////////////////////////////////////////////////////////////////
// Class name: TextureShaderClass
////////////////////////////////////////////////////////////////////////////////
class TextureShaderClass
{
private:
    struct MatrixBufferType
    {
        D3DXMATRIX world;
        D3DXMATRIX view;
        D3DXMATRIX projection;
    };

public:
    TextureShaderClass();
    TextureShaderClass(const TextureShaderClass&);
    ~TextureShaderClass();

    bool Initialize(ID3D11Device*, HWND);
    void Shutdown();
    bool Render(ID3D11DeviceContext*, int, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*);

private:
    bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*);
    void ShutdownShader();
    void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);

    bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*);
    void RenderShader(ID3D11DeviceContext*, int, int);

private:
    ID3D11VertexShader* m_vertexShader;
    ID3D11PixelShader* m_pixelShader;
    ID3D11InputLayout* m_layout;
    ID3D11Buffer* m_matrixBuffer;
    ID3D11SamplerState* m_sampleState;
};

#endif

Textureshaderclass.cpp

////////////////////////////////////////////////////////////////////////////////
// Filename: textureshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "textureshaderclass.h"


TextureShaderClass::TextureShaderClass()
{
    m_vertexShader = 0;
    m_pixelShader = 0;
    m_layout = 0;
    m_matrixBuffer = 0;
    m_sampleState = 0;
}


TextureShaderClass::TextureShaderClass(const TextureShaderClass& other)
{
}


TextureShaderClass::~TextureShaderClass()
{
}


bool TextureShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
    bool result;


    // 정점 및 픽셀 셰이더를 초기화합니다.
    result = InitializeShader(device, hwnd, L"../Engine/texture.vs", L"../Engine/texture.ps");
    if(!result)
    {
        return false;
    }

    return true;
}


void TextureShaderClass::Shutdown()
{
    // 정점 및 픽셀 셰이더를 다른 객체과 함께 해제합니다.
    ShutdownShader();

    return;
}

Render 함수는 정점의 개수 그리고 인스턴스의 개수를 인덱스 개수 대신 입력으로 받습니다.

bool TextureShaderClass::Render(ID3D11DeviceContext* deviceContext, int vertexCount, int instanceCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, 
                D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture)
{
    bool result;


    // 렌더링에 사용할 셰이더 파라미터들을 설정합니다.
    result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture);
    if(!result)
    {
        return false;
    }

    // 셰이더로 준비된 버퍼를 그립니다.
    RenderShader(deviceContext, vertexCount, instanceCount);

    return true;
}


bool TextureShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename)
{
    HRESULT result;
    ID3D10Blob* errorMessage;
    ID3D10Blob* vertexShaderBuffer;
    ID3D10Blob* pixelShaderBuffer;
    D3D11_INPUT_ELEMENT_DESC polygonLayout[3];
    unsigned int numElements;
    D3D11_BUFFER_DESC matrixBufferDesc;
    D3D11_SAMPLER_DESC samplerDesc;


    // 함수에서 사용하는 포인터들을 null로 초기화합니다.
    errorMessage = 0;
    vertexShaderBuffer = 0;
    pixelShaderBuffer = 0;

    // 정점 셰이더를 컴파일합니다.
    result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "TextureVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, 
                       &vertexShaderBuffer, &errorMessage, NULL);
    if(FAILED(result))
    {
        // If the shader failed to compile it should have writen something to the error message.
        if(errorMessage)
        {
            OutputShaderErrorMessage(errorMessage, hwnd, vsFilename);
        }
        // If there was nothing in the error message then it simply could not find the shader file itself.
        else
        {
            MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK);
        }

        return false;
    }

    // 픽셀 셰이더를 컴파일합니다.
    result = D3DX11CompileFromFile(psFilename, NULL, NULL, "TexturePixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, 
                       &pixelShaderBuffer, &errorMessage, NULL);
    if(FAILED(result))
    {
        // If the shader failed to compile it should have writen something to the error message.
        if(errorMessage)
        {
            OutputShaderErrorMessage(errorMessage, hwnd, psFilename);
        }
        // If there was  nothing in the error message then it simply could not find the file itself.
        else
        {
            MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK);
        }

        return false;
    }

    // 버퍼에서 정점 셰이더를 생성합니다.
    result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader);
    if(FAILED(result))
    {
        return false;
    }

    // 버퍼에서 픽셀 셰이더를 생성합니다.
    result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader);
    if(FAILED(result))
    {
        return false;
    }

    // 정점 입력 레이아웃 디스크립션입니다.
    // 셰이더와 ModelClass의 VertexType과 일치해야 합니다.
    polygonLayout[0].SemanticName = "POSITION";
    polygonLayout[0].SemanticIndex = 0;
    polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
    polygonLayout[0].InputSlot = 0;
    polygonLayout[0].AlignedByteOffset = 0;
    polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
    polygonLayout[0].InstanceDataStepRate = 0;

    polygonLayout[1].SemanticName = "TEXCOORD";
    polygonLayout[1].SemanticIndex = 0;
    polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT;
    polygonLayout[1].InputSlot = 0;
    polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
    polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
    polygonLayout[1].InstanceDataStepRate = 0;

인스턴싱을 위해 레이아웃에 세 번째 요소를 추가합니다. 4개의 인스턴싱된 삼각형을 다른 3D 위치에 그릴 예정이기 때문에 셰이더에 추가적으로 x, y, z로 이루어진 벡터 정보를 보내야 합니다. 따라서 그 포맷은 DXGI_FORMAT_R32G32B32_FLOAT가 됩니다. SemanticName은 범용 시맨틱이기 때문에 TEXCOORD가 됩니다. 참고로 이미 위에서 텍스쳐 좌표에 사용하는 TEXCOORD가 0번 슬롯을 쓰고 있기 때문에 이번 레이아웃의 SemanticIndex은 1로 해야 합니다.

인스턴싱과 관련되어 InputSlotClassD3D11_INPUT_PER_INSTANCE_DATA로 하여 이 데이터가 인스턴싱 데이터라는 것을 표시합니다. 그 다음으로 InstanceDataStepRate를 사용하게 되는데 이 값을 1로 하여 인스턴스 데이터 당 하나의 인스턴스를 그리게 합니다. 또한 이것이 인스턴스 버퍼의 첫번째 유닛이고 정점 버퍼와 인스턴스 데이터를 서로 정렬할 필요가 없기 때문에 AlignedByteOffset을 0으로 했습니다.

    polygonLayout[2].SemanticName = "TEXCOORD";
    polygonLayout[2].SemanticIndex = 1;
    polygonLayout[2].Format = DXGI_FORMAT_R32G32B32_FLOAT;
    polygonLayout[2].InputSlot = 1;
    polygonLayout[2].AlignedByteOffset = 0;
    polygonLayout[2].InputSlotClass = D3D11_INPUT_PER_INSTANCE_DATA;
    polygonLayout[2].InstanceDataStepRate = 1;

    // 레이아웃의 원소 개수를 구합니다.
    numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

    // 정점 입력 레이아웃을 생성합니다.
    result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), 
                       &m_layout);
    if(FAILED(result))
    {
        return false;
    }

    // 더 이상 사용되지 않는 정점 셰이더 버퍼와 픽셀 셰이더 버퍼를 해제합니다.
    vertexShaderBuffer->Release();
    vertexShaderBuffer = 0;

    pixelShaderBuffer->Release();
    pixelShaderBuffer = 0;

    // 정점 셰이더에 있는 동적 행렬 상수 버퍼의 디스크립션입니다.
    matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
    matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType);
    matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    matrixBufferDesc.MiscFlags = 0;
    matrixBufferDesc.StructureByteStride = 0;

    // 이 클래스에서 정점 셰이더 상수 버퍼에 접근할 수 있도록 상수 버퍼 포인터를 생성합니다.
    result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer);
    if(FAILED(result))
    {
        return false;
    }

    // 텍스쳐 샘플러 상태 디스크립션입니다.
    samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
    samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
    samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
    samplerDesc.MipLODBias = 0.0f;
    samplerDesc.MaxAnisotropy = 1;
    samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
    samplerDesc.BorderColor[0] = 0;
    samplerDesc.BorderColor[1] = 0;
    samplerDesc.BorderColor[2] = 0;
    samplerDesc.BorderColor[3] = 0;
    samplerDesc.MinLOD = 0;
    samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;

    // 텍스쳐 샘플러 상태를 생성합니다.
    result = device->CreateSamplerState(&samplerDesc, &m_sampleState);
    if(FAILED(result))
    {
        return false;
    }

    return true;
}


void TextureShaderClass::ShutdownShader()
{
    // Release the sampler state.
    if(m_sampleState)
    {
        m_sampleState->Release();
        m_sampleState = 0;
    }

    // Release the matrix constant buffer.
    if(m_matrixBuffer)
    {
        m_matrixBuffer->Release();
        m_matrixBuffer = 0;
    }

    // Release the layout.
    if(m_layout)
    {
        m_layout->Release();
        m_layout = 0;
    }

    // Release the pixel shader.
    if(m_pixelShader)
    {
        m_pixelShader->Release();
        m_pixelShader = 0;
    }

    // Release the vertex shader.
    if(m_vertexShader)
    {
        m_vertexShader->Release();
        m_vertexShader = 0;
    }

    return;
}


void TextureShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
{
    char* compileErrors;
    unsigned long bufferSize, i;
    ofstream fout;


    // Get a pointer to the error message text buffer.
    compileErrors = (char*)(errorMessage->GetBufferPointer());

    // Get the length of the message.
    bufferSize = errorMessage->GetBufferSize();

    // Open a file to write the error message to.
    fout.open("shader-error.txt");

    // Write out the error message.
    for(i=0; i<bufferSize; i++)
    {
        fout << compileErrors[i];
    }

    // Close the file.
    fout.close();

    // Release the error message.
    errorMessage->Release();
    errorMessage = 0;

    // Pop a message up on the screen to notify the user to check the text file for compile errors.
    MessageBox(hwnd, L"Error compiling shader.  Check shader-error.txt for message.", shaderFilename, MB_OK);

    return;
}


bool TextureShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, 
                         D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture)
{
    HRESULT result;
    D3D11_MAPPED_SUBRESOURCE mappedResource;
    MatrixBufferType* dataPtr;
    unsigned int bufferNumber;


    // Transpose the matrices to prepare them for the shader.
    D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
    D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
    D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);

    // Lock the constant buffer so it can be written to.
    result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
    if(FAILED(result))
    {
        return false;
    }

    // Get a pointer to the data in the constant buffer.
    dataPtr = (MatrixBufferType*)mappedResource.pData;

    // Copy the matrices into the constant buffer.
    dataPtr->world = worldMatrix;
    dataPtr->view = viewMatrix;
    dataPtr->projection = projectionMatrix;

    // Unlock the constant buffer.
    deviceContext->Unmap(m_matrixBuffer, 0);

    // Set the position of the constant buffer in the vertex shader.
    bufferNumber = 0;

    // Now set the constant buffer in the vertex shader with the updated values.
    deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);

    // Set shader texture resource in the pixel shader.
    deviceContext->PSSetShaderResources(0, 1, &texture);

    return true;
}

RenderShader 함수는 두 가지가 달라졌습니다. 첫 번째로 평소 인덱스 개수를 받았던 것과는 달리 정점과 인스턴스의 개수를 입력으로 받습니다. 두 번째로는 DrawIndexed함수가 아닌 DrawInstanced 함수를 이용하여 삼각형을들을 그립니다.

void TextureShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int vertexCount, int instanceCount)
{
    // 정점 입력 레이아웃을 설정합니다.
    deviceContext->IASetInputLayout(m_layout);

    // 삼각형을 그리는 데 사용할 정점 및 픽셀 셰이더를 설정합니다.
    deviceContext->VSSetShader(m_vertexShader, NULL, 0);
    deviceContext->PSSetShader(m_pixelShader, NULL, 0);

    // 픽셀 셰이더의 샘플러 상태를 설정합니다.
    deviceContext->PSSetSamplers(0, 1, &m_sampleState);

    // 삼각형을 그립니다.
    deviceContext->DrawInstanced(vertexCount, instanceCount, 0, 0);

    return;
}

Texture.vs

정점 셰이더는 인스턴싱을 사용하도록 수정하였습니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: texture.vs
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
cbuffer MatrixBuffer
{
    matrix worldMatrix;
    matrix viewMatrix;
    matrix projectionMatrix;
};


//////////////
// TYPEDEFS //
//////////////

VertexInputType 구조체는 인스턴스의 위치 데이터를 받는 세 번째 멤버가 생겼습니다.

struct VertexInputType
{
    float4 position : POSITION;
    float2 tex : TEXCOORD0;
    float3 instancePosition : TEXCOORD1;
};

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
};


////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType TextureVertexShader(VertexInputType input)
{
    PixelInputType output;
    

    // 행렬 연산을 위해 위치 벡터의 원소를 4개로 바꿉니다.
    input.position.w = 1.0f;

인스턴스 위치 정보를 사용하여 각 삼각형이 그려질 위치를 수정합니다.

    // 이 인스턴스의 정보를 이용하여 정점의 위치를 갱신합니다.
    input.position.x += input.instancePosition.x;
    input.position.y += input.instancePosition.y;
    input.position.z += input.instancePosition.z;

    // 정점의 위치를 월드, 뷰, 투영 행렬에 대하여 각각 계산합니다.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);
    
    // 픽셀 셰이더에 보낼 텍스쳐 좌표를 저장합니다.
    output.tex = input.tex;
    
    return output;
}

Texture.ps

픽셀 셰이더는 이 예제에서 바뀌지 않습니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: texture.ps
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
Texture2D shaderTexture;
SamplerState SampleType;


//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
};


////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 TexturePixelShader(PixelInputType input) : SV_TARGET
{
    float4 textureColor;


    // 현재 좌표의 픽셀 색상을 샘플러를 이용하여 추출합니다.
    textureColor = shaderTexture.Sample(SampleType, input.tex);

    return textureColor;
}

Graphicsclass.h

GraphicsClass의 헤더 파일도 바뀌지 않았습니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"
#include "textureshaderclass.h"


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
    GraphicsClass();
    GraphicsClass(const GraphicsClass&);
    ~GraphicsClass();

    bool Initialize(int, int, HWND);
    void Shutdown();
    bool Frame();

private:
    bool Render();

private:
    D3DClass* m_D3D;
    CameraClass* m_Camera;
    ModelClass* m_Model;
    TextureShaderClass* m_TextureShader;
};

#endif

Graphicsclass.cpp

////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"


GraphicsClass::GraphicsClass()
{
    m_D3D = 0;
    m_Camera = 0;
    m_Model = 0;
    m_TextureShader = 0;
}


GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}


GraphicsClass::~GraphicsClass()
{
}


bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
    bool result;


    // Direct3D 객체를 생성합니다.
    m_D3D = new D3DClass;
    if(!m_D3D)
    {
        return false;
    }

    // Direct3D 객체를 초기화합니다.
    result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
        return false;
    }

    // 카메라를 생성합니다.
    m_Camera = new CameraClass;
    if(!m_Camera)
    {
        return false;
    }

    // 카메라의 처음 위치입니다.
    m_Camera->SetPosition(0.0f, 0.0f, -10.0f);
    
    // 모델을 생성합니다.
    m_Model = new ModelClass;
    if(!m_Model)
    {
        return false;
    }

    // 모델을 초기화합니다.
    result = m_Model->Initialize(m_D3D->GetDevice(), L"../Engine/data/seafloor.dds");
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
        return false;
    }

    // 텍스쳐 셰이더 객체를 생성합니다.
    m_TextureShader = new TextureShaderClass;
    if(!m_TextureShader)
    {
        return false;
    }

    // 텍스쳐 셰이더 객체를 초기화합니다.
    result = m_TextureShader->Initialize(m_D3D->GetDevice(), hwnd);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the texture shader object.", L"Error", MB_OK);
        return false;
    }

    return true;
}


void GraphicsClass::Shutdown()
{
    // 텍스쳐 셰이더 객체를 해제합니다.
    if(m_TextureShader)
    {
        m_TextureShader->Shutdown();
        delete m_TextureShader;
        m_TextureShader = 0;
    }

    // 모델을 해제합니다.
    if(m_Model)
    {
        m_Model->Shutdown();
        delete m_Model;
        m_Model = 0;
    }

    // 카메라를 해제합니다.
    if(m_Camera)
    {
        delete m_Camera;
        m_Camera = 0;
    }

    // D3D 객체를 해제합니다.
    if(m_D3D)
    {
        m_D3D->Shutdown();
        delete m_D3D;
        m_D3D = 0;
    }

    return;
}


bool GraphicsClass::Frame()
{
    bool result;


    // 그래픽스 화면을 그립니다.
    result = Render();
    if(!result)
    {
        return false;
    }

    return true;
}


bool GraphicsClass::Render()
{
    D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix;
    bool result;


    // 장면을 시작하기 위하여 버퍼를 초기화합니다.
    m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

    // 카메라의 위치에서 뷰 행렬을 생성합니다.
    m_Camera->Render();

    // 카메라와 d3d 객체에서 월드, 뷰, 투영 행렬을 가져옵니다.
    m_Camera->GetViewMatrix(viewMatrix);
    m_D3D->GetWorldMatrix(worldMatrix);
    m_D3D->GetProjectionMatrix(projectionMatrix);

    // 정점과 인덱스 버퍼를 그래픽스 파이프라인에 넣어 그릴 준비를 합니다.
    m_Model->Render(m_D3D->GetDeviceContext());

Render 함수에서 셰이더는 모델 객체의 정점과 인스턴스 개수를 요구합니다.

    // 텍스쳐 셰이더를 이용하여 모델을 그립니다.
    result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_Model->GetVertexCount(), m_Model->GetInstanceCount(), worldMatrix, viewMatrix, 
                     projectionMatrix, m_Model->GetTexture());
    if(!result)
    {
        return false;
    }

    // 그려진 장면을 화면에 표시합니다.
    m_D3D->EndScene();

    return true;
}

마치면서

인스턴싱을 사용하면 하나의 모델로도 개별 속성을 가진 여러 인스턴스로 그릴 수 있습니다.

연습 문제

  1. 프로그램을 다시 컴파일하여 실행해 보십시오. 하나의 삼각형 모델을 이용하여 그린 네 개의 삼각형이 보일 것입니다.
  2. 위치 정보를 수정하여 삼각형이 다른 위치에 가도록 해 보십시오.
  3. 다섯 번째 삼각형을 추가해 보십시오.
  4. 각 인스턴스마다 색상이 달라지도록 수정해 보십시오.

소스 코드

소스 코드 및 데이터: dx11src37.zip

실행파일만: dx11exe37.zip

Nav