DirectX11 Tutorial 41: 다중 조명 그림자 매핑

강좌번역/DirectX 11 2019. 5. 4. 07:59 by 빠재

원문: Tutorial 41: Multiple Light Shadow Mapping

이번 예제에서는 그림자 매핑을 이용하여 여러 조명의 경우를 구현하는 방법을 알아보겠습니다. 이 예제는 C++와 HLSL을 이용하여 DirectX로 구현되었습니다. 이번 코드는 이전 그림자 매핑 예제에서 계속됩니다. 또한 지난 다중 조명 예제도 읽어보시지 않았다면 한번 살펴보시기 바랍니다. 이 예제는 이미 다중 조명을 사용하는 방법을 알고 계신 분들을 위한 것입니다.

다중 조명과 그림자 매핑을 같이 사용하는 건 기본 그림자 매핑 기법에서 쉽게 확장 가능합니다. 조명 하나의 관점에서 깊이 맵 하나만들 그리는 대신 모든 조명마다 깊이 맵을 그리면 되는 것입니다. 그리고 깊이 맵(그림자 맵)을 각 조명의 위치와 디퓨즈 색상과 함께 셰이더로 보냅니다. 다음으로 각 조명에 동일하게 단일 조명에 하던 것과 같은 그림자 과정을 거치고 모든 결과를 더하여 최종 픽셀값을 구합니다.

실제로 동작하는 것을 보기 위해 두 개의 조명이 있는 장면을 구성할 것입니다. 아래 장면에는 왼쪽 뒤에 위치한 파란색 조명과 오른쪽 뒤에 위치한 녹색 조명이 있습니다. 그래서 구체를 보면 오른쪽은 녹색으로 비치고 왼쪽에는 파란색 빛이 나는 걸 볼 수 있습니다. 구체 중간에 보이는 시안색은 파란색과 녹색을 합친 결과입니다.

여기서 파란색 빛의 관점에서의 깊이맵과 녹색 빛의 관점에서의 깊이맵을 렌더링하면 어디에 그림자가 위치할지에 대한 모든 정보를 알 수 있습니다. 이 두 깊이맵과 두 조명의 색상을 셰이더로 보내 각 빛에 대해 그림자맵 알고리즘을 돌립니다. 그리고 나면 두 조명에 대한 두 개의 색상 결과가 나오는데, 이 두 색을 더해 최종 결과를 얻어냅니다.

녹색 빛이 닿지 않는 곳에는 파란색 빛만 영향을 미치기 때문에 녹색 빛이 파란색 그림자를 드리우는 것이 보입니다. 같은 이유로 파란색 빛은 녹색 그림자를 드리웁니다. 장면 중간에 그림자가 겹치는 부분에는 어느 조명도 표면을 비추지 않기 때문에 배경색으로 표현됩니다.

셰이더 프로그램부터 예제 코드를 살펴보겠습니다.

Shadow.vs

////////////////////////////////////////////////////////////////////////////////
// Filename: shadow.vs
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////

두번째 조명을 위한 퍼스펙티브 및 뷰 행렬을 추가합니다.

cbuffer MatrixBuffer
{
    matrix worldMatrix;
    matrix viewMatrix;
    matrix projectionMatrix;
    matrix lightViewMatrix;
    matrix lightProjectionMatrix;
    matrix lightViewMatrix2;
    matrix lightProjectionMatrix2;
};


//////////////////////
// CONSTANT BUFFERS //
//////////////////////

두번째 조명의 위치 벡터도 추가합니다.

cbuffer LightBuffer2
{
    float3 lightPosition;
    float padding1;
    float3 lightPosition2;
    float padding2;
};


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

PixelInputType에 두번째 조명의 위치와 바라보는 방향을 추가합니다.

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
    float4 lightViewPosition : TEXCOORD1;
    float3 lightPos : TEXCOORD2;
    float4 lightViewPosition2 : TEXCOORD3;
    float3 lightPos2 : TEXCOORD4;
};


////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType ShadowVertexShader(VertexInputType input)
{
    PixelInputType output;
    float4 worldPosition;
    
    
    // 행렬 연산을 위해 위치 벡터의 성분을 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.lightViewPosition = mul(input.position, worldMatrix);
    output.lightViewPosition = mul(output.lightViewPosition, lightViewMatrix);
    output.lightViewPosition = mul(output.lightViewPosition, lightProjectionMatrix);

두번째 조명에서 보는 정점의 위치를 계산합니다.

    // 두번째 조명의 위치에 따른 정점의 위치를 계산합니다.
    output.lightViewPosition2 = mul(input.position, worldMatrix);
    output.lightViewPosition2 = mul(output.lightViewPosition2, lightViewMatrix2);
    output.lightViewPosition2 = mul(output.lightViewPosition2, lightProjectionMatrix2);

    // 픽셀 셰이더에 보낼 텍스쳐 좌표를 저장합니다.
    output.tex = input.tex;
    
    // 월드 행렬에 대해서만 노멀 벡터를 계산합니다.
    output.normal = mul(input.normal, (float3x3)worldMatrix);
    
    // 노멀 벡터를 정규화합니다.
    output.normal = normalize(output.normal);

    // 월드에서의 정점의 위치를 계산합니다.
    worldPosition = mul(input.position, worldMatrix);

    // 조명의 위치에 상대적인 정점의 위치를 계산합니다.
    output.lightPos = lightPosition.xyz - worldPosition.xyz;

    // 조명 위치 벡터를 정규화합니다.
    output.lightPos = normalize(output.lightPos);

두번째 조명의 위치를 계산합니다.

    // 두번째 조명의 위치에 상대적인 정점의 위치를 계산합니다.
    output.lightPos2 = lightPosition2.xyz - worldPosition.xyz;

    // 두번째 조명 위치 벡터를 정규화합니다.
    output.lightPos2 = normalize(output.lightPos2);

    return output;
}

Shadow.ps

////////////////////////////////////////////////////////////////////////////////
// Filename: shadow.ps
////////////////////////////////////////////////////////////////////////////////


//////////////
// TEXTURES //
//////////////
Texture2D shaderTexture : register(t0);
Texture2D depthMapTexture : register(t1);

두번째 조명에 쓰일 두번째 깊이맵을 추가합니다.

Texture2D depthMapTexture2 : register(t2);


///////////////////
// SAMPLE STATES //
///////////////////
SamplerState SampleTypeClamp : register(s0);
SamplerState SampleTypeWrap  : register(s1);


//////////////////////
// CONSTANT BUFFERS //
//////////////////////

두번째 조명의 디퓨즈 색상을 추가합니다.

cbuffer LightBuffer
{
    float4 ambientColor;
    float4 diffuseColor;
    float4 diffuseColor2;
};


//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
    float4 lightViewPosition : TEXCOORD1;
    float3 lightPos : TEXCOORD2;
    float4 lightViewPosition2 : TEXCOORD3;
    float3 lightPos2 : TEXCOORD4;
};


////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 ShadowPixelShader(PixelInputType input) : SV_TARGET
{
    float bias;
    float4 color;
    float2 projectTexCoord;
    float depthValue;
    float lightDepthValue;
    float lightIntensity;
    float4 textureColor;


    // 부동소수점 오차 문제를 고치기 위한 바이어스값입니다.
    bias = 0.001f;

    // 모든 픽셀의 기본 색상을 배경색으로 지정합니다.
    color = ambientColor;

첫번째 조명과 그 그림자를 평소와 같이 계산하되 마지막 조명 색상을 아직 처리하지 않고 둡니다.

    // 투영된 텍스쳐 좌표를 계산합니다.
    projectTexCoord.x =  input.lightViewPosition.x / input.lightViewPosition.w / 2.0f + 0.5f;
    projectTexCoord.y = -input.lightViewPosition.y / input.lightViewPosition.w / 2.0f + 0.5f;

    // 투영된 좌표가 0에서 1사이에 있는지 확인합니다. 범위 안에 있다면 현재 픽셀은 빛의 시야 내에 있는 것입니다.
    if((saturate(projectTexCoord.x) == projectTexCoord.x) && (saturate(projectTexCoord.y) == projectTexCoord.y))
    {
        // 투영된 텍스쳐 좌표에서 그림자 맵의 깊이값을 샘플링합니다.
        depthValue = depthMapTexture.Sample(SampleTypeClamp, projectTexCoord).r;

        // 빛의 깊이를 계산합니다.
        lightDepthValue = input.lightViewPosition.z / input.lightViewPosition.w;

        // lightDepthValue에서 바이어스값을 뺍니다.
        lightDepthValue = lightDepthValue - bias;

        // 그림자맵에 있는 깊이값과 조명의 깊이값을 비교하여 현재 픽셀이 그림자인지 아닌지를 확인합니다.
        // 만약 빛이 물체 앞에 있다면 픽셀에 빛을 주고, 그렇지 않다면 그림자로 처리합니다.
        if(lightDepthValue < depthValue)
        {
            // 현재 픽셀에 비추는 빛의 양을 계산합니다.
            lightIntensity = saturate(dot(input.normal, input.lightPos));
            if(lightIntensity > 0.0f)
            {
                // 현재 디퓨즈 색상과 빛의 강도를 이용하여 마지막 디퓨즈 색상을 계산합니다.
                color += (diffuseColor * lightIntensity);
            }
        }
    }

똑같은 과정을 두번째 조명의 그림자맵과 조명값을들 이용하여 반복합니다. 또한 색상값을 따로 계산하는 것이 아니라 첫번째 조명에서 나온 결과에 더하는 식으로 계산합니다.

    // 두번째 조명
    projectTexCoord.x =  input.lightViewPosition2.x / input.lightViewPosition2.w / 2.0f + 0.5f;
    projectTexCoord.y = -input.lightViewPosition2.y / input.lightViewPosition2.w / 2.0f + 0.5f;

    if((saturate(projectTexCoord.x) == projectTexCoord.x) && (saturate(projectTexCoord.y) == projectTexCoord.y))
    {
        depthValue = depthMapTexture2.Sample(SampleTypeClamp, projectTexCoord).r;

        lightDepthValue = input.lightViewPosition2.z / input.lightViewPosition2.w;
        lightDepthValue = lightDepthValue - bias;

        if(lightDepthValue < depthValue)
        {
            lightIntensity = saturate(dot(input.normal, input.lightPos2));
            if(lightIntensity > 0.0f)
            {
                color += (diffuseColor2 * lightIntensity);
            }
        }
    }

색상을 다 계산했으므로 saturate를 합니다.

    // 최종 조명 색상을 잘라냅니다.
    color = saturate(color);

    // 현재 텍스쳐 좌표에서 픽셀 색상을 샘플링합니다.
    textureColor = shaderTexture.Sample(SampleTypeWrap, input.tex);

    // 텍스쳐 색상과 조명 색상을 혼합합니다.
    color = color * textureColor;

    return color;
}

Shadowshaderclass.h

////////////////////////////////////////////////////////////////////////////////
// Filename: shadowshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _SHADOWSHADERCLASS_H_
#define _SHADOWSHADERCLASS_H_


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


////////////////////////////////////////////////////////////////////////////////
// Class name: ShadowShaderClass
////////////////////////////////////////////////////////////////////////////////
class ShadowShaderClass
{
private:

MatrixBufferType에 두번째 조명의 뷰 및 투영행렬을 추가합니다.

    struct MatrixBufferType
    {
        D3DXMATRIX world;
        D3DXMATRIX view;
        D3DXMATRIX projection;
        D3DXMATRIX lightView;
        D3DXMATRIX lightProjection;
        D3DXMATRIX lightView2;
        D3DXMATRIX lightProjection2;
    };

LightBufferType에는 두번째 조명의 색상을 추가합니다.

    struct LightBufferType
    {
        D3DXVECTOR4 ambientColor;
        D3DXVECTOR4 diffuseColor;
        D3DXVECTOR4 diffuseColor2;
    };

LightBufferType2에는 두번째 조명의 위치벡터가 추가됩니다.

    struct LightBufferType2
    {
        D3DXVECTOR3 lightPosition;
        float padding1;
        D3DXVECTOR3 lightPosition2;
        float padding2;
    };

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

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

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

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

private:
    ID3D11VertexShader* m_vertexShader;
    ID3D11PixelShader* m_pixelShader;
    ID3D11InputLayout* m_layout;
    ID3D11SamplerState* m_sampleStateWrap;
    ID3D11SamplerState* m_sampleStateClamp;
    ID3D11Buffer* m_matrixBuffer;
    ID3D11Buffer* m_lightBuffer;
    ID3D11Buffer* m_lightBuffer2;
};

#endif

Shadowshaderclass.cpp

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


ShadowShaderClass::ShadowShaderClass()
{
    m_vertexShader = 0;
    m_pixelShader = 0;
    m_layout = 0;
    m_sampleStateWrap = 0;
    m_sampleStateClamp = 0;
    m_matrixBuffer = 0;
    m_lightBuffer = 0;
    m_lightBuffer2 = 0;
}


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


ShadowShaderClass::~ShadowShaderClass()
{
}


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


    // Initialize the vertex and pixel shaders.
    result = InitializeShader(device, hwnd, L"../Engine/shadow.vs", L"../Engine/shadow.ps");
    if(!result)
    {
        return false;
    }

    return true;
}


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

    return;
}

Render함수는 두번째 조명의 뷰 행렬, 투영 행렬, 깊이맵 텍스쳐, 위치, 색상 인자가 추가됩니다. 이 값들은 SetShaderParameters함수의 인자로 전달됩니다.

bool ShadowShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, 
                   D3DXMATRIX projectionMatrix, D3DXMATRIX lightViewMatrix, D3DXMATRIX lightProjectionMatrix, 
                   ID3D11ShaderResourceView* texture, ID3D11ShaderResourceView* depthMapTexture, D3DXVECTOR3 lightPosition, 
                   D3DXVECTOR4 ambientColor, D3DXVECTOR4 diffuseColor, D3DXMATRIX lightViewMatrix2, D3DXMATRIX lightProjectionMatrix2, 
                   ID3D11ShaderResourceView* depthMapTexture2, D3DXVECTOR3 lightPosition2, D3DXVECTOR4 diffuseColor2)
{
    bool result;


    // 렌더링에 사용할 셰이더 인자들을 설정합니다.
    result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, lightViewMatrix, lightProjectionMatrix, texture, 
                     depthMapTexture, lightPosition, ambientColor, diffuseColor, lightViewMatrix2, lightProjectionMatrix2, depthMapTexture2,
                     lightPosition2, diffuseColor2);
    if(!result)
    {
        return false;
    }

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

    return true;
}


bool ShadowShaderClass::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_SAMPLER_DESC samplerDesc;
    D3D11_BUFFER_DESC matrixBufferDesc;
    D3D11_BUFFER_DESC lightBufferDesc;
    D3D11_BUFFER_DESC lightBufferDesc2;


    // 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, "ShadowVertexShader", "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;
    }

    // Compile the pixel shader code.
    result = D3DX11CompileFromFile(psFilename, NULL, NULL, "ShadowPixelShader", "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;
    }

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

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

    // Create the vertex input layout description.
    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;

    polygonLayout[2].SemanticName = "NORMAL";
    polygonLayout[2].SemanticIndex = 0;
    polygonLayout[2].Format = DXGI_FORMAT_R32G32B32_FLOAT;
    polygonLayout[2].InputSlot = 0;
    polygonLayout[2].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
    polygonLayout[2].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
    polygonLayout[2].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(), 
                       &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;

    // Create a wrap 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(&samplerDesc, &m_sampleStateWrap);
    if(FAILED(result))
    {
        return false;
    }

    // Create a clamp texture sampler state description.
    samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
    samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
    samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;

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

    // Setup the description of the dynamic matrix 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(&matrixBufferDesc, NULL, &m_matrixBuffer);
    if(FAILED(result))
    {
        return false;
    }

    // Setup the description of the light dynamic constant buffer that is in the pixel shader.
    lightBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
    lightBufferDesc.ByteWidth = sizeof(LightBufferType);
    lightBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    lightBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    lightBufferDesc.MiscFlags = 0;
    lightBufferDesc.StructureByteStride = 0;

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

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

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

    return true;
}


void ShadowShaderClass::ShutdownShader()
{
    // Release the light constant buffers.
    if(m_lightBuffer)
    {
        m_lightBuffer->Release();
        m_lightBuffer = 0;
    }

    if(m_lightBuffer2)
    {
        m_lightBuffer2->Release();
        m_lightBuffer2 = 0;
    }

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

    // Release the sampler states.
    if(m_sampleStateWrap)
    {
        m_sampleStateWrap->Release();
        m_sampleStateWrap = 0;
    }

    if(m_sampleStateClamp)
    {
        m_sampleStateClamp->Release();
        m_sampleStateClamp = 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 ShadowShaderClass::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 ShadowShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, 
                        D3DXMATRIX projectionMatrix, D3DXMATRIX lightViewMatrix, D3DXMATRIX lightProjectionMatrix, 
                        ID3D11ShaderResourceView* texture, ID3D11ShaderResourceView* depthMapTexture, D3DXVECTOR3 lightPosition,
                        D3DXVECTOR4 ambientColor, D3DXVECTOR4 diffuseColor, D3DXMATRIX lightViewMatrix2, 
                        D3DXMATRIX lightProjectionMatrix2, ID3D11ShaderResourceView* depthMapTexture2, D3DXVECTOR3 lightPosition2, 
                        D3DXVECTOR4 diffuseColor2)
{
    HRESULT result;
    D3D11_MAPPED_SUBRESOURCE mappedResource;
    unsigned int bufferNumber;
    MatrixBufferType* dataPtr;
    LightBufferType* dataPtr2;
    LightBufferType2* dataPtr3;


    // 셰이더에 사용할 행렬들을 전치합니다.
    D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
    D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
    D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);
    D3DXMatrixTranspose(&lightViewMatrix, &lightViewMatrix);
    D3DXMatrixTranspose(&lightProjectionMatrix, &lightProjectionMatrix);
    D3DXMatrixTranspose(&lightViewMatrix2, &lightViewMatrix2);
    D3DXMatrixTranspose(&lightProjectionMatrix2, &lightProjectionMatrix2);

    // 상수 버퍼에 쓰기 위하여 락을 겁니다.
    result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
    if(FAILED(result))
    {
        return false;
    }

    // 상수 버퍼의 데이터 포인터를 가져옵니다.
    dataPtr = (MatrixBufferType*)mappedResource.pData;

    // 행렬들을 상수 버퍼에 복사합니다.
    dataPtr->world = worldMatrix;
    dataPtr->view = viewMatrix;
    dataPtr->projection = projectionMatrix;
    dataPtr->lightView = lightViewMatrix;
    dataPtr->lightProjection = lightProjectionMatrix;
    dataPtr->lightView2 = lightViewMatrix2;
    dataPtr->lightProjection2 = lightProjectionMatrix2;

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

    // 정점 셰이더에서의 상수 버퍼의 위치입니다.
    bufferNumber = 0;

    // 정점 셰이더의 상수 버퍼를 갱신된 값으로 설정합니다.
    deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);

    // 픽셀 셰이더의 텍스쳐 리소스를 설정합니다.
    deviceContext->PSSetShaderResources(0, 1, &texture);
    deviceContext->PSSetShaderResources(1, 1, &depthMapTexture);
    deviceContext->PSSetShaderResources(2, 1, &depthMapTexture2);

    // 조명 상수 버퍼에 쓰기 위하여 락을 겁니다.
    result = deviceContext->Map(m_lightBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
    if(FAILED(result))
    {
        return false;
    }

    // 상수 버퍼의 데이터 포인터를 가져옵니다.
    dataPtr2 = (LightBufferType*)mappedResource.pData;

    // 조명값들을 상수 버퍼에 복사합니다.
    dataPtr2->ambientColor = ambientColor;
    dataPtr2->diffuseColor = diffuseColor;
    dataPtr2->diffuseColor2 = diffuseColor2;

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

    // 픽셀 셰이더에서의 조명 상수 버퍼의 위치입니다.
    bufferNumber = 0;

    // 픽셀 셰이더에 조명 상수 버퍼를 갱신된 값으로 설정합니다.
    deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_lightBuffer);

    // 두번째 조명 상수 버퍼에 쓰기 위하여 락을 겁니다.
    result = deviceContext->Map(m_lightBuffer2, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
    if(FAILED(result))
    {
        return false;
    }

    // 상수 버퍼의 데이터 포인터를 얻어옵니다.
    dataPtr3 = (LightBufferType2*)mappedResource.pData;

    // 조명값들을 상수 버퍼에 복사합니다.
    dataPtr3->lightPosition = lightPosition;
    dataPtr3->lightPosition2 = lightPosition2;
    dataPtr3->padding1 = 0.0f;
    dataPtr3->padding2 = 0.0f;

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

    // 정점 셰이더에서의 조명 상수 버퍼의 위치입니다.
    bufferNumber = 1;

    // 마지막으로 셰이더에 조명 상수 버퍼를 갱신된 값으로 설정합니다.
    deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_lightBuffer2);

    return true;
}


void ShadowShaderClass::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 states in the pixel shader.
    deviceContext->PSSetSamplers(0, 1, &m_sampleStateClamp);
    deviceContext->PSSetSamplers(1, 1, &m_sampleStateWrap);

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

    return;
}

Graphicsclass.h

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


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"
#include "lightclass.h"
#include "rendertextureclass.h"
#include "depthshaderclass.h"
#include "shadowshaderclass.h"


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 100.0f;
const float SCREEN_NEAR = 1.0f;
const int SHADOWMAP_WIDTH = 1024;
const int SHADOWMAP_HEIGHT = 1024;


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

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

private:
    bool RenderSceneToTexture();

두번째 조명의 시점에서 장면을 두번째 깊이맵 텍스쳐에 렌더링하는 함수를 추가합니다.

    bool RenderSceneToTexture2();
    bool Render();

private:
    D3DClass* m_D3D;
    CameraClass* m_Camera;
    ModelClass *m_CubeModel, *m_GroundModel, *m_SphereModel;
    LightClass* m_Light;
    RenderTextureClass* m_RenderTexture;
    DepthShaderClass* m_DepthShader;
    ShadowShaderClass* m_ShadowShader;

두번째 조명과 두번째 렌더 투 텍스쳐가 될 변수를 추가합니다.

    LightClass* m_Light2;
    RenderTextureClass* m_RenderTexture2;
};

#endif

Graphicsclass.cpp

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


GraphicsClass::GraphicsClass()
{
    m_D3D = 0;
    m_Camera = 0;
    m_CubeModel = 0;
    m_GroundModel = 0;
    m_SphereModel = 0;
    m_Light = 0;
    m_RenderTexture = 0;
    m_DepthShader = 0;  
    m_ShadowShader = 0;

두번째 조명과 렌더 투 텍스쳐를 생성자에서 null로 초기화합니다.

    m_Light2 = 0;
    m_RenderTexture2 = 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_CubeModel = new ModelClass;
    if(!m_CubeModel)
    {
        return false;
    }

    // 큐브 모델 객체를 초기화합니다.
    result = m_CubeModel->Initialize(m_D3D->GetDevice(), "../Engine/data/cube.txt", L"../Engine/data/wall01.dds");
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the cube model object.", L"Error", MB_OK);
        return false;
    }

    // 큐브 모델의 최초 위치입니다.
    m_CubeModel->SetPosition(-2.0f, 2.0f, 0.0f);

    // 공 모델 객체를 생성합니다.
    m_SphereModel = new ModelClass;
    if(!m_SphereModel)
    {
        return false;
    }

    // 공 모델 객체를 초기화합니다.
    result = m_SphereModel->Initialize(m_D3D->GetDevice(), "../Engine/data/sphere.txt", L"../Engine/data/ice.dds");
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the sphere model object.", L"Error", MB_OK);
        return false;
    }

    // 공 모델의 최초 위치입니다.
    m_SphereModel->SetPosition(2.0f, 2.0f, 0.0f);

    // 바닥 모델 객체를 생성합니다.
    m_GroundModel = new ModelClass;
    if(!m_GroundModel)
    {
        return false;
    }

    // 바닥 모델 객체를 초기화합니다.
    result = m_GroundModel->Initialize(m_D3D->GetDevice(), "../Engine/data/plane01.txt", L"../Engine/data/metal001.dds");
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the ground model object.", L"Error", MB_OK);
        return false;
    }

    // 바닥 모델의 최초 위치입니다.
    m_GroundModel->SetPosition(0.0f, 1.0f, 0.0f);

    // 조명 객체를 생성합니다.
    m_Light = new LightClass;
    if(!m_Light)
    {
        return false;
    }

    // 조명 객체를 초기화합니다.
    m_Light->SetAmbientColor(0.15f, 0.15f, 0.15f, 1.0f);
    m_Light->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f);
    m_Light->SetLookAt(0.0f, 0.0f, 0.0f);
    m_Light->GenerateProjectionMatrix(SCREEN_DEPTH, SCREEN_NEAR);

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

    // 렌더 투 텍스쳐 객체를 초기화합니다.
    result = m_RenderTexture->Initialize(m_D3D->GetDevice(), SHADOWMAP_WIDTH, SHADOWMAP_HEIGHT, SCREEN_DEPTH, SCREEN_NEAR);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the render to texture object.", L"Error", MB_OK);
        return false;
    }

    // 깊이 셰이더 객체를 생성합니다.
    m_DepthShader = new DepthShaderClass;
    if(!m_DepthShader)
    {
        return false;
    }

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

    // 그림자 셰이더 객체를 생성합니다.
    m_ShadowShader = new ShadowShaderClass;
    if(!m_ShadowShader)
    {
        return false;
    }

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

두번째 조명 객체와 렌더 투 텍스쳐 객체를 설정합니다.

    // 두번째 조명 객체를 생성합니다.
    m_Light2 = new LightClass;
    if(!m_Light2)
    {
        return false;
    }

    // 두번째 조명 객체를 초기화합니다.
    m_Light2->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f);
    m_Light2->SetLookAt(0.0f, 0.0f, 0.0f);
    m_Light2->GenerateProjectionMatrix(SCREEN_DEPTH, SCREEN_NEAR);

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

    // 두번째 렌더 투 텍스쳐 객체를 초기화합니다.
    result = m_RenderTexture2->Initialize(m_D3D->GetDevice(), SHADOWMAP_WIDTH, SHADOWMAP_HEIGHT, SCREEN_DEPTH, SCREEN_NEAR);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the second render to texture object.", L"Error", MB_OK);
        return false;
    }

    return true;
}


void GraphicsClass::Shutdown()
{

Shutdown 함수에서 두번째 조명과 렌더 투 텍스쳐 객체를 해제합니다.

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

    // 두번째 조명 객체를 해제합니다.
    if(m_Light2)
    {
        delete m_Light2;
        m_Light2 = 0;
    }

    // 그림자 셰이더 객체를 해제합니다.
    if(m_ShadowShader)
    {
        m_ShadowShader->Shutdown();
        delete m_ShadowShader;
        m_ShadowShader = 0;
    }

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

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

    // 조명 객체를 해제합니다.
    if(m_Light)
    {
        delete m_Light;
        m_Light = 0;
    }

    // 바닥 모델 객체를 해제합니다.
    if(m_GroundModel)
    {
        m_GroundModel->Shutdown();
        delete m_GroundModel;
        m_GroundModel = 0;
    }

    // 공 모델 객체를 해제합니다.
    if(m_SphereModel)
    {
        m_SphereModel->Shutdown();
        delete m_SphereModel;
        m_SphereModel = 0;
    }

    // 큐브 모델 객체를 해제합니다.
    if(m_CubeModel)
    {
        m_CubeModel->Shutdown();
        delete m_CubeModel;
        m_CubeModel = 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(float posX, float posY, float posZ, float rotX, float rotY, float rotZ)
{
    bool result;


    // 카메라의 위치를 잡습니다.
    m_Camera->SetPosition(posX, posY, posZ);
    m_Camera->SetRotation(rotX, rotY, rotZ);

첫번째 조명을 오른쪽 뒤에 배치합니다. 두번째 조명은 왼쪽 뒤에 놓습니다.

    // 첫번째 조명의 위치입니다.
    m_Light->SetPosition(5.0f, 8.0f, -5.0f);

    // 두번째 조명의 위치입니다.
    m_Light2->SetPosition(-5.0f, 8.0f, -5.0f);

    // 그래픽스 장면을 렌더링합니다.
    result = Render();
    if(!result)
    {
        return false;
    }

    return true;
}


bool GraphicsClass::RenderSceneToTexture()
{
    D3DXMATRIX worldMatrix, lightViewMatrix, lightProjectionMatrix, translateMatrix;
    float posX, posY, posZ;
    bool result;


    // Set the render target to be the render to texture.
    m_RenderTexture->SetRenderTarget(m_D3D->GetDeviceContext());

    // Clear the render to texture.
    m_RenderTexture->ClearRenderTarget(m_D3D->GetDeviceContext(), 0.0f, 0.0f, 0.0f, 1.0f);

    // Generate the light view matrix based on the light's position.
    m_Light->GenerateViewMatrix();

    // Get the world matrix from the d3d object.
    m_D3D->GetWorldMatrix(worldMatrix);

    // Get the view and orthographic matrices from the light object.
    m_Light->GetViewMatrix(lightViewMatrix);
    m_Light->GetProjectionMatrix(lightProjectionMatrix);

    // Setup the translation matrix for the cube model.
    m_CubeModel->GetPosition(posX, posY, posZ);
    D3DXMatrixTranslation(&worldMatrix, posX, posY, posZ);

    // Render the cube model with the depth shader.
    m_CubeModel->Render(m_D3D->GetDeviceContext());
    result = m_DepthShader->Render(m_D3D->GetDeviceContext(), m_CubeModel->GetIndexCount(), worldMatrix, lightViewMatrix, lightProjectionMatrix);
    if(!result)
    {
        return false;
    }

    // Reset the world matrix.
    m_D3D->GetWorldMatrix(worldMatrix);

    // Setup the translation matrix for the sphere model.
    m_SphereModel->GetPosition(posX, posY, posZ);
    D3DXMatrixTranslation(&worldMatrix, posX, posY, posZ);

    // Render the sphere model with the depth shader.
    m_SphereModel->Render(m_D3D->GetDeviceContext());
    result = m_DepthShader->Render(m_D3D->GetDeviceContext(), m_SphereModel->GetIndexCount(), worldMatrix, lightViewMatrix, lightProjectionMatrix);
    if(!result)
    {
        return false;
    }

    // Reset the world matrix.
    m_D3D->GetWorldMatrix(worldMatrix);

    // Setup the translation matrix for the ground model.
    m_GroundModel->GetPosition(posX, posY, posZ);
    D3DXMatrixTranslation(&worldMatrix, posX, posY, posZ);

    // Render the ground model with the depth shader.
    m_GroundModel->Render(m_D3D->GetDeviceContext());
    result = m_DepthShader->Render(m_D3D->GetDeviceContext(), m_GroundModel->GetIndexCount(), worldMatrix, lightViewMatrix, lightProjectionMatrix);
    if(!result)
    {
        return false;
    }

    // Reset the render target back to the original back buffer and not the render to texture anymore.
    m_D3D->SetBackBufferRenderTarget();

    // Reset the viewport back to the original.
    m_D3D->ResetViewport();

    return true;
}

두번째 조명의 시점에서 두번째 렌더 투 텍스쳐에 장면을 그리는 함수를 추가합니다.

bool GraphicsClass::RenderSceneToTexture2()
{
    D3DXMATRIX worldMatrix, lightViewMatrix, lightProjectionMatrix, translateMatrix;
    float posX, posY, posZ;
    bool result;


    // 렌더 타겟이 렌더 투 텍스쳐가 되게 합니다.
    m_RenderTexture2->SetRenderTarget(m_D3D->GetDeviceContext());

    // 렌더 투 텍스쳐의 내용을 지웁니다.
    m_RenderTexture2->ClearRenderTarget(m_D3D->GetDeviceContext(), 0.0f, 0.0f, 0.0f, 1.0f);

    // 조명의 위치에서 조명 뷰 행렬을 생성합니다.
    m_Light2->GenerateViewMatrix();

    // d3d 객체에서 월드 행렬을 가져옵니다.
    m_D3D->GetWorldMatrix(worldMatrix);

    // 조명 객체의 뷰 및 직교 행렬을 가져옵니다.
    m_Light2->GetViewMatrix(lightViewMatrix);
    m_Light2->GetProjectionMatrix(lightProjectionMatrix);

    // 큐브 모델의 이동 행렬을 설정합니다.
    m_CubeModel->GetPosition(posX, posY, posZ);
    D3DXMatrixTranslation(&worldMatrix, posX, posY, posZ);

    // 큐브 모델을 깊이 셰이더를 이용하여 그립니다.
    m_CubeModel->Render(m_D3D->GetDeviceContext());
    result = m_DepthShader->Render(m_D3D->GetDeviceContext(), m_CubeModel->GetIndexCount(), worldMatrix, lightViewMatrix, lightProjectionMatrix);
    if(!result)
    {
        return false;
    }

    // 월드 행렬을 리셋합니다.
    m_D3D->GetWorldMatrix(worldMatrix);

    // 공 모델의 이동 행렬을 설정합니다.
    m_SphereModel->GetPosition(posX, posY, posZ);
    D3DXMatrixTranslation(&worldMatrix, posX, posY, posZ);

    // 공 모델을 깊이 셰이더를 이용하여 그립니다.
    m_SphereModel->Render(m_D3D->GetDeviceContext());
    result = m_DepthShader->Render(m_D3D->GetDeviceContext(), m_SphereModel->GetIndexCount(), worldMatrix, lightViewMatrix, lightProjectionMatrix);
    if(!result)
    {
        return false;
    }

    // 월드 행렬을 리셋합니다.
    m_D3D->GetWorldMatrix(worldMatrix);

    // 바닥 모델의 이동 행렬을 설정합니다.
    m_GroundModel->GetPosition(posX, posY, posZ);
    D3DXMatrixTranslation(&worldMatrix, posX, posY, posZ);

    // 바닥 모델을 깊이 셰이더를 이용하여 그립니다.
    m_GroundModel->Render(m_D3D->GetDeviceContext());
    result = m_DepthShader->Render(m_D3D->GetDeviceContext(), m_GroundModel->GetIndexCount(), worldMatrix, lightViewMatrix, lightProjectionMatrix);
    if(!result)
    {
        return false;
    }

    // 렌더 타겟을 렌더 투 텍스쳐에서 기존 백버퍼로 돌립니다.
    m_D3D->SetBackBufferRenderTarget();

    // 뷰포트를 원래대로 돌립니다.
    m_D3D->ResetViewport();

    return true;
}


bool GraphicsClass::Render()
{
    D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix, translateMatrix;
    D3DXMATRIX lightViewMatrix, lightProjectionMatrix;
    D3DXMATRIX lightViewMatrix2, lightProjectionMatrix2;
    bool result;
    float posX, posY, posZ;


    // 우선 장면을 텍스쳐에 그립니다.
    result = RenderSceneToTexture();
    if(!result)
    {
        return false;
    }

두번째 조명에서의 장면을 두번째 렌더 투 텍스쳐에 그립니다.

    // 두번째 조명의 시점에서 장면을 렌더 투 텍스쳐에 다시 그립니다.
    result = RenderSceneToTexture2();
    if(!result)
    {
        return false;
    }

    // 장면 렌더링을 시작하기 위해 버퍼를 지웁니다.
    m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

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

    // 조명의 위치에서 조명 뷰 행렬을 생성합니다.
    m_Light->GenerateViewMatrix();

두번째 조명의 뷰 행렬을 생성합니다.

    // 두번째 조명도 똑같이 생성합니다.
    m_Light2->GenerateViewMatrix();

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

    // 조명 객체에서 조명의 뷰 및 투영 행렬을 가져옵니다.
    m_Light->GetViewMatrix(lightViewMatrix);
    m_Light->GetProjectionMatrix(lightProjectionMatrix);

두번째 조명에서 뷰 및 투영 행렬을 가져옵니다.

    // 두번째 조명도 똑같이 가져옵니다.
    m_Light2->GetViewMatrix(lightViewMatrix2);
    m_Light2->GetProjectionMatrix(lightProjectionMatrix2);

두번째 조명 인자들을 그림자 셰이더에 전달하고 나머지 렌더링 과정들을 처리합니다.

    // 큐브 모델의 이동 행렬을 설정합니다.
    m_CubeModel->GetPosition(posX, posY, posZ);
    D3DXMatrixTranslation(&worldMatrix, posX, posY, posZ);
    
    // 큐브 모델의 정점 및 인덱스 버퍼를 그래픽스 파이프라인에 넣어 렌더링되도록 합니다.
    m_CubeModel->Render(m_D3D->GetDeviceContext());

    // 그림자 셰이더를 이용하여 모델을 그립니다.
    result = m_ShadowShader->Render(m_D3D->GetDeviceContext(), m_CubeModel->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, lightViewMatrix, 
                    lightProjectionMatrix, m_CubeModel->GetTexture(), m_RenderTexture->GetShaderResourceView(), m_Light->GetPosition(),
                    m_Light->GetAmbientColor(), m_Light->GetDiffuseColor(), lightViewMatrix2, lightProjectionMatrix2, 
                    m_RenderTexture2->GetShaderResourceView(), m_Light2->GetPosition(), m_Light2->GetDiffuseColor());
    if(!result)
    {
        return false;
    }

    // 월드 행렬을 리셋합니다.
    m_D3D->GetWorldMatrix(worldMatrix);

    // 공 모델의 이동 행렬을 설정합니다.
    m_SphereModel->GetPosition(posX, posY, posZ);
    D3DXMatrixTranslation(&worldMatrix, posX, posY, posZ);

    // 공 모델의 정점 및 인덱스 버퍼를 그래픽스 파이프라인에 넣어 렌더링되도록 합니다.
    m_SphereModel->Render(m_D3D->GetDeviceContext());
    result = m_ShadowShader->Render(m_D3D->GetDeviceContext(), m_SphereModel->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, lightViewMatrix, 
                    lightProjectionMatrix, m_SphereModel->GetTexture(), m_RenderTexture->GetShaderResourceView(), m_Light->GetPosition(), 
                    m_Light->GetAmbientColor(), m_Light->GetDiffuseColor(), lightViewMatrix2, lightProjectionMatrix2, 
                    m_RenderTexture2->GetShaderResourceView(), m_Light2->GetPosition(), m_Light2->GetDiffuseColor());
    if(!result)
    {
        return false;
    }

    // 월드 행렬을 리셋합니다.
    m_D3D->GetWorldMatrix(worldMatrix);

    // 바닥 모델의 이동 행렬을 설정합니다.
    m_GroundModel->GetPosition(posX, posY, posZ);
    D3DXMatrixTranslation(&worldMatrix, posX, posY, posZ);

    // 바닥 모델을 그림자 셰이더를 이용하여 그립니다.
    m_GroundModel->Render(m_D3D->GetDeviceContext());
    result = m_ShadowShader->Render(m_D3D->GetDeviceContext(), m_GroundModel->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, lightViewMatrix,
                    lightProjectionMatrix, m_GroundModel->GetTexture(), m_RenderTexture->GetShaderResourceView(), m_Light->GetPosition(), 
                    m_Light->GetAmbientColor(), m_Light->GetDiffuseColor(), lightViewMatrix2, lightProjectionMatrix2, 
                    m_RenderTexture2->GetShaderResourceView(), m_Light2->GetPosition(), m_Light2->GetDiffuseColor());
    if(!result)
    {
        return false;
    }

    // 렌더링된 장면을 화면에 표시합니다.
    m_D3D->EndScene();

    return true;
}

마치면서

이제 여러 개의 조명이 모두 그림자를 드리우는 장면을 만들 수 있습니다.

연습문제

  1. 프로그램을 컴파일하고 실행해 보십시오. 방향키와 A, Z, PgUp, PgDn 키로 장면을 둘러볼 수 있습니다.
  2. 조명의 색상과 위치를 수정하여 어떻게 장면에 영향을 미치는지 확인해 보십시오.
  3. 조명을 3개가 되게 코드를 수정해 보십시오. 셰이더와 GraphicsClass의 코드 흐름이 반복문을 이용하여 개별 조명마다 처리될 수 있게 해 보십시오. 배열을 사용하여 조명을 설정하고, 렌더 투 텍스쳐에 그리게끔 하여 for문을 활용할 수 있게 하십시오.

소스코드

Source Code and Data Files: dx11src41.zip

Executable: dx11exe41.zip


Nav