DirectX11 Tutorial 28: 페이드 효과

강좌번역/DirectX 11 2015. 9. 6. 21:02 by 빠재

원문: http://rastertek.com/dx11tut28.html (이 번역에 나오는 모든 이미지와 소스코드의 출처는 원문입니다.)

이번 튜토리얼에서는 페이드 이펙트를 만드는 내용을 다룰 것입니다. 이 튜토리얼은 이전 튜토리얼에서 이어지는 것이며 DirectX11 및 HLSL과 C++을 사용합니다.

프로그램의 품질을 높이는 방법 중 효과적인 것 하나는 화면 간의 전환이 있을 때 재빠른 페이드 이펙트를 주는 것입니다. 로딩 화면을 보여줬다가 불쑥 다음 장면을 보여주던 전통적인 방법은 개선되어야 마땅합니다. 화면 페이딩 효과를 만드는 가장 간단한 방법 중 하나는 렌더 투 텍스쳐 기능을 이용하여 픽셀들의 색상을 점점 바꾸는 것입니다. 그 외에도 두 장면을 그려서 미끄러지듯 지나가게 한다던지 다른 녹아드는듯한 방법 등 다양하게 확장할 수 있습니다.

이 튜토리얼에서는 렌더 투 텍스쳐 기능을 이용하여 페이드 효과를 만들 것입니다. 화면이 100% 가려질 때 다음 화면을 렌더링하는 것으로 바꾸어 렌더 투 텍스쳐를 끌 것입니다.

프레임워크

이번 프레임워크에는 페이드 인 효과를 처리하는 FadeShaderClass라는 클래스가 들어갑니다. 그리고 BitmapClass를 다시 사용하여 FadeShaderClassRenderTextureClass와 같이 동작할 수 있도록 하였습니다. 페이드 인 연출 시간을 다루기 위한 TimerClass도 사용됩니다.

Fade.vs

페이드 HLSL 셰이더는 기존 텍스쳐 셰이더를 조금 바꾼 것입니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: fade.vs
////////////////////////////////////////////////////////////////////////////////


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


//////////////
// TYPEDEFS //
//////////////
struct VertexInputType
{
    float4 position : POSITION;
    float2 tex : TEXCOORD0;
};

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


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

    // 행렬 연산을 하기 위해서는 벡터가 4개의 성분으로 되어 있어야 합니다.
    input.position.w = 1.0f;

    // 월드, 뷰, 프로젝션 행렬을 이용하여 대해 정점의 위치를 계산합니다.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);
    
    // 픽셀 셰이더에서 사용할 텍스쳐의 좌표를 저장합니다.
    output.tex = input.tex;
    
    return output;
}

Fade.ps

////////////////////////////////////////////////////////////////////////////////
// Filename: fade.ps
////////////////////////////////////////////////////////////////////////////////


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

FadeBuffer이라는 버퍼를 추가하여 fadeAmount로 텍스쳐의 색상 강도를 조절할 수 있도록 합니다. 이 값은 매 프레임마다 증가하며 이미지를 밝아지게 하므로 페이드 인 효과가 만들어집니다. 값의 범위는 0.0(0%)에서 1.0(100%)까지입니다. padding 변수는 버퍼의 크기가 16의 배수가 되도록 잡아놓은 것입니다.

cbuffer FadeBuffer
{
    float fadeAmount;
    float3 padding;
};


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


////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 FadePixelShader(PixelInputType input) : SV_TARGET
{
    float4 color;


    // 텍스쳐의 현재 위치 색상을 샘플링합니다.
    color = shaderTexture.Sample(SampleType, input.tex);

fadeAmount를 사용하여 출력되는 모든 픽셀의 밝기가 특정한 값이 되도록 합니다.

    // 색상의 밝기를 현재 페이드 퍼센트에 맞게 줄입니다.
    color = color * fadeAmount;

    return color;
}

Fadeshaderclass.h

FadeShaderClassTextureShaderClass가 텍스쳐 페이드 효과를 지원하도록 수정한 것입니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: fadeshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _FADESHADERCLASS_H_
#define _FADESHADERCLASS_H_


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


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

픽셀 셰이더의 페이드 상수 버퍼에 해당하는 구조체입니다.

    struct FadeBufferType
    {
        float fadeAmount;
        D3DXVECTOR3 padding;
    };

public:
    FadeShaderClass();
    FadeShaderClass(const FadeShaderClass&amp;);
    ~FadeShaderClass();

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

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

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

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

m_fadeBuffer은 텍스쳐의 모든 픽셀들의 밝기를 조절하는 데 사용됩니다.

    ID3D11Buffer* m_fadeBuffer;
};

#endif

Fadeshaderclass.cpp

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


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

새로 만든 버퍼를 생성자에서 null로 초기화합니다.

    m_fadeBuffer = 0;
}


FadeShaderClass::FadeShaderClass(const FadeShaderClass&amp; other)
{
}


FadeShaderClass::~FadeShaderClass()
{
}


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

fade.vsfade.ps HLSL 셰이더 코드를 로드합니다.

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

    return true;
}


void FadeShaderClass::Shutdown()
{
    // Shutdown the vertex and pixel shaders as well as the related objects.
    ShutdownShader();

    return;
}

Render 함수는 faceAmount라는 인자를 받습니다. 이 인자는 텍스쳐가 그려야 할 밝기의 퍼센트 값을 가집니다. 텍스쳐에 그려지기 전에 이 값이 셰이더에 지정됩니다.

bool FadeShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix,
                 D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, float fadeAmount)
{
    bool result;


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

    // 셰이더를 이용하여 준비된 버퍼에 그려냅니다.
    RenderShader(deviceContext, indexCount);

    return true;
}


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


    // Initialize the pointers this function will use to null.
    errorMessage = 0;
    vertexShaderBuffer = 0;
    pixelShaderBuffer = 0;

정점 셰이더를 불러옵니다.

    // Compile the vertex shader code.
    result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "FadeVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, 
                       &amp;vertexShaderBuffer, &amp;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;
    }

픽셀 셰이더를 불러옵니다.

    // Compile the pixel shader code.
    result = D3DX11CompileFromFile(psFilename, NULL, NULL, "FadePixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, 
                       &amp;pixelShaderBuffer, &amp;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;
    }

    // Create the vertex shader from the buffer.
    result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, 
                        &amp;m_vertexShader);
    if(FAILED(result))
    {
        return false;
    }

    // Create the vertex shader from the buffer.
    result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, 
                       &amp;m_pixelShader);
    if(FAILED(result))
    {
        return false;
    }

    // Create the vertex input layout description.
    // This setup needs to match the VertexType stucture in the ModelClass and in the shader.
    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;

    // Get a count of the elements in the layout.
    numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

    // Create the vertex input layout.
    result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), 
                       vertexShaderBuffer->GetBufferSize(), &amp;m_layout);
    if(FAILED(result))
    {
        return false;
    }

    // Release the vertex shader buffer and pixel shader buffer since they are no longer needed.
    vertexShaderBuffer->Release();
    vertexShaderBuffer = 0;

    pixelShaderBuffer->Release();
    pixelShaderBuffer = 0;

    // Setup the description of the matrix dynamic constant buffer that is in the vertex shader.
    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;

    // Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class.
    result = device->CreateBuffer(&amp;matrixBufferDesc, NULL, &amp;m_matrixBuffer);
    if(FAILED(result))
    {
        return false;
    }

    // Create a texture sampler state description.
    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;

    // Create the texture sampler state.
    result = device->CreateSamplerState(&amp;samplerDesc, &amp;m_sampleState);
    if(FAILED(result))
    {
        return false;
    }

픽셀 셰이더 안의 fadeAmount 값을 저장할 버퍼를 생성합니다.

    // Setup the description of the fade dynamic constant buffer that is in the vertex shader.
    fadeBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
    fadeBufferDesc.ByteWidth = sizeof(FadeBufferType);
    fadeBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    fadeBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    fadeBufferDesc.MiscFlags = 0;
    fadeBufferDesc.StructureByteStride = 0;

    // Create the constant buffer pointer so we can access the pixel shader constant buffer from within this class.
    result = device->CreateBuffer(&amp;fadeBufferDesc, NULL, &amp;m_fadeBuffer);
    if(FAILED(result))
    {
        return false;
    }

    return true;
}


void FadeShaderClass::ShutdownShader()
{

ShutdownShader 함수에서 페이드 버퍼를 해제합니다.

    // 상수 버퍼를 해제합니다.
    if(m_fadeBuffer)
    {
        m_fadeBuffer->Release();
        m_fadeBuffer = 0;
    }

    // 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 FadeShaderClass::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 FadeShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix,
                      D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, float fadeAmount)
{
    HRESULT result;
    D3D11_MAPPED_SUBRESOURCE mappedResource;
    MatrixBufferType* dataPtr;
    unsigned int bufferNumber;
    FadeBufferType* dataPtr2;


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

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

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

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

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

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

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

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

여기서 픽셀 셰이더에 fadeAmount 값을 설정합니다. 우선 페이드 버퍼를 잠근 다음 갱신할 fadeAmount 값을 복사해 넣습니다. 그런 뒤에 페이드 버퍼의 잠금을 풀고 PSSetConstantBuffers 함수를 통하여 픽셀 셰이더에 적용합니다.

    // 상수 버퍼를 잠궈 내용을 쓸 수 있도록 합니다.
    result = deviceContext->Map(m_fadeBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &amp;mappedResource);
    if(FAILED(result))
    {
        return false;
    }

    // 페이드 상수 버퍼의 데이터를 가리키는 포인터를 얻습니다.
    dataPtr2 = (FadeBufferType*)mappedResource.pData;

    // fadeAmount의 값을 버퍼로 복사합니다.
    dataPtr2->fadeAmount = fadeAmount;
    dataPtr2->padding = D3DXVECTOR3(0.0f, 0.0f, 0.0f);

    // 상수 버퍼의 잠금을 해제합니다.
    deviceContext->Unmap(m_fadeBuffer, 0);

    // 픽셀 셰이더에서의 상수 버퍼의 위치를 설정합니다.
    bufferNumber = 0;

    // 픽셀 셰이더의 페이드 상수 버퍼를 새로운 값으로 갱신합니다.
    deviceContext->PSSetConstantBuffers(bufferNumber, 1, &amp;m_fadeBuffer);

    return true;
}


void FadeShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount)
{
    // Set the vertex input layout.
    deviceContext->IASetInputLayout(m_layout);

    // Set the vertex and pixel shaders that will be used to render this triangle.
    deviceContext->VSSetShader(m_vertexShader, NULL, 0);
    deviceContext->PSSetShader(m_pixelShader, NULL, 0);

    // Set the sampler state in the pixel shader.
    deviceContext->PSSetSamplers(0, 1, &amp;m_sampleState);

    // Render the triangle.
    deviceContext->DrawIndexed(indexCount, 0, 0);

    return;
}

Bitmapclass.h

BitmapClass는 이전의 구현에서 아무런 텍스쳐의 정보도 가지지 않도록 수정되었습니다. 이전에 BitmapClass가 실제 텍스쳐를 제공했던 것 대신 지금은 FadeShaderClassRenderTextureClass에 실제 텍스쳐를 제공합니다. 하지만 렌더 투 텍스쳐를 비트맵처럼 2D 화면에 그리는 기능이 필요하기 때문에 이 수정된 BitmapClass에서 버퍼를 준비하고 페이드 셰이더가 호출되도록 할 것입니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: bitmapclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _BITMAPCLASS_H_
#define _BITMAPCLASS_H_


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


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

public:
    BitmapClass();
    BitmapClass(const BitmapClass&amp;);
    ~BitmapClass();

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

    int GetIndexCount();

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

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

private:
    ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
    int m_vertexCount, m_indexCount;
    int m_screenWidth, m_screenHeight;
    int m_bitmapWidth, m_bitmapHeight;
    int m_previousPosX, m_previousPosY;
};

#endif

Bitmapclass.cpp

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


BitmapClass::BitmapClass()
{
    m_vertexBuffer = 0;
    m_indexBuffer = 0;
}


BitmapClass::BitmapClass(const BitmapClass&amp; other)
{
}


BitmapClass::~BitmapClass()
{
}


bool BitmapClass::Initialize(ID3D11Device* device, int screenWidth, int screenHeight, int bitmapWidth, int bitmapHeight)
{
    bool result;


    // Store the screen size.
    m_screenWidth = screenWidth;
    m_screenHeight = screenHeight;

    // Store the size in pixels that this bitmap should be rendered at.
    m_bitmapWidth = bitmapWidth;
    m_bitmapHeight = bitmapHeight;

    // Initialize the previous rendering position to -1.
    m_previousPosX = -1;
    m_previousPosY = -1;

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

    return true;
}


void BitmapClass::Shutdown()
{
    // Shutdown the vertex and index buffers.
    ShutdownBuffers();

    return;
}


bool BitmapClass::Render(ID3D11DeviceContext* deviceContext, int positionX, int positionY)
{
    bool result;


    // Re-build the dynamic vertex buffer for rendering to possibly a different location on the screen.
    result = UpdateBuffers(deviceContext, positionX, positionY);
    if(!result)
    {
        return false;
    }

    // Put the vertex and index buffers on the graphics pipeline to prepare them for drawing.
    RenderBuffers(deviceContext);

    return true;
}


int BitmapClass::GetIndexCount()
{
    return m_indexCount;
}


bool BitmapClass::InitializeBuffers(ID3D11Device* device)
{
    VertexType* vertices;
    unsigned long* indices;
    D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
    D3D11_SUBRESOURCE_DATA vertexData, indexData;
    HRESULT result;
    int i;


    // Set the number of vertices in the vertex array.
    m_vertexCount = 6;

    // Set the number of indices in the index array.
    m_indexCount = m_vertexCount;

    // Create the vertex array.
    vertices = new VertexType[m_vertexCount];
    if(!vertices)
    {
        return false;
    }

    // Create the index array.
    indices = new unsigned long[m_indexCount];
    if(!indices)
    {
        return false;
    }

    // Initialize vertex array to zeros at first.
    memset(vertices, 0, (sizeof(VertexType) * m_vertexCount));

    // Load the index array with data.
    for(i=0; i<m_indexCount; i++)
    {
        indices[i] = i;
    }

    // Set up the description of the static vertex buffer.
    vertexBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
    vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
    vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vertexBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    vertexBufferDesc.MiscFlags = 0;
    vertexBufferDesc.StructureByteStride = 0;

    // Give the subresource structure a pointer to the vertex data.
    vertexData.pSysMem = vertices;
    vertexData.SysMemPitch = 0;
    vertexData.SysMemSlicePitch = 0;

    // Now create the vertex buffer.
    result = device->CreateBuffer(&amp;vertexBufferDesc, &amp;vertexData, &amp;m_vertexBuffer);
    if(FAILED(result))
    {
        return false;
    }

    // Set up the description of the static index buffer.
    indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount;
    indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    indexBufferDesc.CPUAccessFlags = 0;
    indexBufferDesc.MiscFlags = 0;
    indexBufferDesc.StructureByteStride = 0;

    // Give the subresource structure a pointer to the index data.
    indexData.pSysMem = indices;
    indexData.SysMemPitch = 0;
    indexData.SysMemSlicePitch = 0;

    // Create the index buffer.
    result = device->CreateBuffer(&amp;indexBufferDesc, &amp;indexData, &amp;m_indexBuffer);
    if(FAILED(result))
    {
        return false;
    }

    // Release the arrays now that the vertex and index buffers have been created and loaded.
    delete [] vertices;
    vertices = 0;

    delete [] indices;
    indices = 0;

    return true;
}


void BitmapClass::ShutdownBuffers()
{
    // Release the index buffer.
    if(m_indexBuffer)
    {
        m_indexBuffer->Release();
        m_indexBuffer = 0;
    }

    // Release the vertex buffer.
    if(m_vertexBuffer)
    {
        m_vertexBuffer->Release();
        m_vertexBuffer = 0;
    }

    return;
}


bool BitmapClass::UpdateBuffers(ID3D11DeviceContext* deviceContext, int positionX, int positionY)
{
    float left, right, top, bottom;
    VertexType* vertices;
    D3D11_MAPPED_SUBRESOURCE mappedResource;
    VertexType* verticesPtr;
    HRESULT result;


    // If the position we are rendering this bitmap to has not changed then don't update the vertex buffer since it
    // currently has the correct parameters.
    if((positionX == m_previousPosX) &amp;&amp; (positionY == m_previousPosY))
    {
        return true;
    }
    
    // If it has changed then update the position it is being rendered to.
    m_previousPosX = positionX;
    m_previousPosY = positionY;

    // Calculate the screen coordinates of the left side of the bitmap.
    left = (float)((m_screenWidth / 2) * -1) + (float)positionX;

    // Calculate the screen coordinates of the right side of the bitmap.
    right = left + (float)m_bitmapWidth;

    // Calculate the screen coordinates of the top of the bitmap.
    top = (float)(m_screenHeight / 2) - (float)positionY;

    // Calculate the screen coordinates of the bottom of the bitmap.
    bottom = top - (float)m_bitmapHeight;

    // Create the vertex array.
    vertices = new VertexType[m_vertexCount];
    if(!vertices)
    {
        return false;
    }

    // Load the vertex array with data.
    // First triangle.
    vertices[0].position = D3DXVECTOR3(left, top, 0.0f);  // Top left.
    vertices[0].texture = D3DXVECTOR2(0.0f, 0.0f);

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

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

    // Second triangle.
    vertices[3].position = D3DXVECTOR3(left, top, 0.0f);  // Top left.
    vertices[3].texture = D3DXVECTOR2(0.0f, 0.0f);

    vertices[4].position = D3DXVECTOR3(right, top, 0.0f);  // Top right.
    vertices[4].texture = D3DXVECTOR2(1.0f, 0.0f);

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

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

    // Get a pointer to the data in the vertex buffer.
    verticesPtr = (VertexType*)mappedResource.pData;

    // Copy the data into the vertex buffer.
    memcpy(verticesPtr, (void*)vertices, (sizeof(VertexType) * m_vertexCount));

    // Unlock the vertex buffer.
    deviceContext->Unmap(m_vertexBuffer, 0);

    // Release the vertex array as it is no longer needed.
    delete [] vertices;
    vertices = 0;

    return true;
}


void BitmapClass::RenderBuffers(ID3D11DeviceContext* deviceContext)
{
    unsigned int stride;
    unsigned int offset;


    // Set vertex buffer stride and offset.
    stride = sizeof(VertexType); 
    offset = 0;
    
    // Set the vertex buffer to active in the input assembler so it can be rendered.
    deviceContext->IASetVertexBuffers(0, 1, &amp;m_vertexBuffer, &amp;stride, &amp;offset);

    // Set the index buffer to active in the input assembler so it can be rendered.
    deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);

    // Set the type of primitive that should be rendered from this vertex buffer, in this case triangles.
    deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    return;
}

Graphicsclass.h

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


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


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

FadeShaderClass의 헤더 파일을 추가합니다.

#include "fadeshaderclass.h"


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

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

private:
    bool RenderToTexture(float);
    bool RenderFadingScene();
    bool RenderNormalScene(float);

private:
    D3DClass* m_D3D;
    CameraClass* m_Camera;
    ModelClass* m_Model;
    TextureShaderClass* m_TextureShader;
    RenderTextureClass* m_RenderTexture;
    BitmapClass* m_Bitmap;

페이드 인 효과에 대한 정보를 담는 변수를 추가합니다.

    float m_fadeInTime, m_accumulatedTime, m_fadePercentage;
    bool m_fadeDone;

FadeShaderClass 객체를 선언합니다.

    FadeShaderClass* m_FadeShader;
};

#endif

Graphicsclass.cpp

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


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

FadeShaderClass 객체를 null로 초기화합니다.


    m_FadeShader = 0;
}


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


GraphicsClass::~GraphicsClass()
{
}


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

        
    // Create the Direct3D object.
    m_D3D = new D3DClass;
    if(!m_D3D)
    {
        return false;
    }

    // Initialize the Direct3D object.
    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;
    }

    // Create the camera object.
    m_Camera = new CameraClass;
    if(!m_Camera)
    {
        return false;
    }

    // Create the model object.
    m_Model = new ModelClass;
    if(!m_Model)
    {
        return false;
    }

    // Initialize the model object.
    result = m_Model->Initialize(m_D3D->GetDevice(), L"../Engine/data/seafloor.dds", "../Engine/data/cube.txt");
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
        return false;
    }

    // Create the texture shader object.
    m_TextureShader = new TextureShaderClass;
    if(!m_TextureShader)
    {
        return false;
    }

    // Initialize the texture shader object.
    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;
    }

렌더 투 텍스쳐 객체를 생성 및 초기화합니다.

    // 렌더 투 텍스쳐 객체를 생성합니다.
    m_RenderTexture = new RenderTextureClass;
    if(!m_RenderTexture)
    {
        return false;
    }

    // 렌더 투 텍스쳐 객체를 초기화합니다.
    result = m_RenderTexture->Initialize(m_D3D->GetDevice(), screenWidth, screenHeight);
    if(!result)
    {
        return false;
    }

비트맵 객체를 생성 및 초기화합니다. 화면 전체에 그릴 것이므로 크기는 화면의 크기만큼 설정합니다.

    // 비트맵 객체를 생성합니다.
    m_Bitmap = new BitmapClass;
    if(!m_Bitmap)
    {
        return false;
    }

    // 비트맵 객체를 초기화합니다.
    result = m_Bitmap->Initialize(m_D3D->GetDevice(), screenWidth, screenHeight, screenWidth, screenHeight);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the bitmap object.", L"Error", MB_OK);
        return false;
    }

페이드 변수들을 설정합니다.

    // 페이드 인 시간을 3000ms로 설정합니다.
    m_fadeInTime = 3000.0f;

    // 누적 시간을 0ms로 초기화합니다.
    m_accumulatedTime = 0;

    // 페이드 진행률을 0%로 하여 화면이 검게 나오도록 합니다.
    m_fadePercentage = 0;

    // 페이드 이펙트가 끝나지 않은 것으로 표시합니다.
    m_fadeDone = false;

페이드 셰이더 객체를 생성 및 초기화합니다.

    // 페이드 셰이더 객체를 생성합니다.
    m_FadeShader = new FadeShaderClass;
    if(!m_FadeShader)
    {
        return false;
    }

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

    return true;
}


void GraphicsClass::Shutdown()
{

Shutdown 함수에서 FadeShaderClass 객체를 해제합니다.

    // 페이드 셰이더 객체를 해제합니다.
    if(m_FadeShader)
    {
        m_FadeShader->Shutdown();
        delete m_FadeShader;
        m_FadeShader = 0;
    }

    // 비트맵 객체를 해제합니다.
    if(m_Bitmap)
    {
        m_Bitmap->Shutdown();
        delete m_Bitmap;
        m_Bitmap = 0;
    }

    // 렌더 투 텍스쳐 객체를 해제합니다.
    if(m_RenderTexture)
    {
        m_RenderTexture->Shutdown();
        delete m_RenderTexture;
        m_RenderTexture = 0;
    }

    // 텍스쳐 셰이더 객체를 해제합니다.
    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;
}

Frame함수는 페이드 인 효과의 진행을 관장합니다. 매 프레임마다 프레임 간 시간이 함수의 인자로 전달되고 텍스쳐가 어느 만큼 보여야 할 지 계산됩니다. 누적된 프레임 시간이 페이드 인 시간을 넘게 되면 m_fadeDone 변수를 true로 바꾸어 더 이상 페이드 효과가 나지 않고 일반 장면을 그리도록 합니다.

bool GraphicsClass::Frame(float frameTime)
{
    if(!m_fadeDone)
    {
        // 누적 시간을 추가합니다.
        m_accumulatedTime += frameTime;

        // 시간이 지나면서 페이드 효과의 진행률이 증가하도록 합니다.
        if(m_accumulatedTime < m_fadeInTime)
        {
            // 누적된 시간을 이용하여 어느정도까지 진행되었는지 계산합니다.
            m_fadePercentage = m_accumulatedTime / m_fadeInTime;
        }
        else
        {
            // 페이드 인 시간이 지난 경우 페이드 효과를 끄고 일반 장면을 그리도록 합니다.
            m_fadeDone = true;

            // 진행률을 100%로 합니다.
            m_fadePercentage = 1.0f;
        }
    }

    // 카메라의 위치를 설정합니다.
    m_Camera->SetPosition(0.0f, 0.0f, -10.0f);

    return true;
}

Render 함수는 육면체의 회전뿐만 아니라 렌더 투 텍스쳐와 페이드 인 효과의 전환을 담당하며 페이드 효과가 끝나면 일반 장면을 그리는 기능을 담당합니다.

bool GraphicsClass::Render()
{
    bool result;
    static float rotation = 0.0f;


    // 매 프레임마다 회전량을 갱신합니다.
    rotation += (float)D3DX_PI * 0.005f;
    if(rotation > 360.0f)
    {
        rotation -= 360.0f;
    }

    if(m_fadeDone)
    {
        // 페이드 효과가 끝나면 백버퍼에 일반 장면을 그립니다.
        RenderNormalScene(rotation);
    }
    else
    {
        // 페이드 효과가 끝나지 않았다면 렌더 투 텍스쳐로 그려낸 뒤 그 텍스쳐에 페이드 효과를 줍니다.
        result = RenderToTexture(rotation);
        if(!result)
        {
            return false;
        }
    
        result = RenderFadingScene();
        if(!result)
        {
            return false;
        }
    }

    return true;
}

RenderToTexture 함수는 회전하는 육면체를 백버퍼가 아닌 텍스쳐에 그리도록 합니다.

bool GraphicsClass::RenderToTexture(float rotation)
{
    D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix;
    bool result;


    // 렌더 테겟을 렌더 투 텍스쳐가 되게 합니다.
    m_RenderTexture->SetRenderTarget(m_D3D->GetDeviceContext(), m_D3D->GetDepthStencilView());

    // 스텐실 뷰의 내용을 초기화합니다.
    m_RenderTexture->ClearRenderTarget(m_D3D->GetDeviceContext(), m_D3D->GetDepthStencilView(), 0.0f, 0.0f, 0.0f, 1.0f);

    // 카메라의 위치를 이용하여 뷰 행렬을 생성합니다.
    m_Camera->Render();

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

    // 월드 행렬에 회전을 곱합니다.
    D3DXMatrixRotationY(&amp;worldMatrix, rotation);

    // 렌더링이 일어나기 전에 모델의 정점 및 인덱스 버퍼를 파이프라인에 넣습니다.
    m_Model->Render(m_D3D->GetDeviceContext());

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

    // 렌더 타겟을 다시 원래의 백버퍼로 돌려놓습니다.
    m_D3D->SetBackBufferRenderTarget();

    return true;
}

RenderFadingScene 함수는 렌더 투 텍스쳐로 뽑힌 육면체 이미지를 FadeShaderClass를 통하여 마치 2D 비트맵처럼 화면에 그립니다. FadeShaderClass 객체는 m_fadePercentage의 값을 이용하여 매 프레임마다 어느 정도의 밝기가 들어가야 하는지 알아냅니다.

bool GraphicsClass::RenderFadingScene()
{
    D3DXMATRIX worldMatrix, viewMatrix, orthoMatrix;
    bool result;


    // 화면을 그리기 위하여 내용을 초기화합니다.
    m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

    // 카메라의 위치를 이용하여 뷰 행렬을 생성합니다.
    m_Camera->Render();

    // 카메라와 d3d 객체에서 월드, 뷰, 직교 행렬을 구합니다.
    m_D3D->GetWorldMatrix(worldMatrix);
    m_Camera->GetViewMatrix(viewMatrix);
    m_D3D->GetOrthoMatrix(orthoMatrix);

    // 2D 렌더링이므로 Z버퍼를 끕니다.
    m_D3D->TurnZBufferOff();

    // 렌더링이 일어나기 전에 비트맵의 정점 및 인덱스 버퍼를 파이프라인에 넣습니다.
    result = m_Bitmap->Render(m_D3D->GetDeviceContext(), 0, 0);
    if(!result)
    {
        return false;
    }

    // 페이드 셰이더를 이용하여 비트맵을 그립니다.
    result = m_FadeShader->Render(m_D3D->GetDeviceContext(), m_Bitmap->GetIndexCount(), worldMatrix, viewMatrix, orthoMatrix, 
                      m_RenderTexture->GetShaderResourceView(), m_fadePercentage);
    if(!result)
    {
        return false;
    }

    // 2D 렌더링이 끝나면 Z버퍼를 다시 켜줍니다.
    m_D3D->TurnZBufferOn();

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

    return true;
}

페이드 효과가 끝났다면 이 함수를 호출하여 렌더 투 텍스쳐를 통하지 않고 평소대로 큰 부하 없이 장면을 그릴 수 있도록 합니다.

bool GraphicsClass::RenderNormalScene(float rotation)
{
    D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix;
    bool result;


    // 장면의 내용을 초기화합니다.
    m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

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

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

    // 월드 행렬에 회전을 곱합니다.
    D3DXMatrixRotationY(&amp;worldMatrix, rotation);

    // 렌더링이 일어나기 전에 모델의 정점 및 인덱스 버퍼를 파이프라인에 넣습니다.
    m_Model->Render(m_D3D->GetDeviceContext());

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

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

    return true;
}

마치면서

렌더 투 텍스쳐를 이용하여 3D 장면에서 페이드 인 및 페이드 아웃 효과를 낼 수 있습니다. 여기에 이전 화면과 새로운 화면을 동시에 그리는 식의 더욱 멋진 전환 효과로 확장할 수 있습니다.

연습문제

  1. 프로그램을 다시 컴파일하고 실행해 보십시오. 육면체가 5초간 페이드 인 된 다음 평소대로 그려질 것입니다. esc키로 종료합니다.

  2. 페이드 인 시간을 바꾸어 보십시오.

  3. 백버퍼의 클리어 색상을 바꾸어 렌더 투 텍스쳐에서 평소대로 렌더링이 전환되는 시점이 언제인지 확인해 보십시오.

  4. 코드를 고쳐 페이드 인이 끝난 다음 페이드 아웃이 되도록 해 보십시오.

소스코드

Visual Studio 2008 프로젝트: dx11tut28.zip

소스코드만: dx11src28.zip

실행파일만: dx11exe28.zip

'강좌번역 > DirectX 11' 카테고리의 다른 글

DirectX11 Tutorial 30: 다중 점조명  (0) 2017.09.03
DirectX11 Tutorial 29: 물  (2) 2017.09.01
DirectX11 Tutorial 27: 반사  (0) 2015.06.21
DirectX11 Tutorial 26: 투명도  (1) 2015.06.14
DirectX11 Tutorial 25: 텍스쳐 이동  (0) 2015.06.07
Nav