DirectX11 Tutorial 30: 다중 점조명

강좌번역/DirectX 11 2017. 9. 3. 00:54 by 빠재

Tutorial 30: Multiple Point Lights

원문: http://www.rastertek.com/dx11tut30.html

이번 예제에서는 HLSL과 C++를 이용하여 다중 점조명을 구현하는 방법을 설명합니다.

지금까지의 예제에서는 이해하고 디버그하기 용이하다는 이유로 방향성 조명을 사용했습니다. 하지만 점조명(Point lights)은 대부분의 광원 시뮬레이션에서 사용되기 때문에 중요합니다. 빛나는 전구로 묘사되는 거의 모든 것들은 다 점조명입니다. 점조명은 위치와 색상을 가지며, 방향은 가지고 있지 않습니다. 또한 조명의 중심에서 가장 강렬하며 거기서 멀어질수록 약해지기 때문에 구체의 모양이 나오게 됩니다. 이번 예제에서는 빛의 감쇠와 범위가 없는 기본적인 점조명을 만들 것입니다.

이번 예제에서 다룰 두 번째 개념은 다중 광원입니다. 대부분의 장면이 방향성 광원 하나에 여러 개의 점조명을 가지는 경우이기 때문에 점조명이 다중 광원의 역할을 하기에 가장 적합합니다. 예를 들어 공장의 안쪽에 있다고 생각해 봅시다. 창문으로 들어오는 방향을 가진 빛이 있지만 또한 건물 내부를 적절히 밝혀 줄 수백개의 조명이 있을 것입니다. 참고로 HLSL을 사용한 DirectX 11에서는 한 화면에 들어가는 점조명의 개수에 제한이 없기 때문에 모든 것은 여러분 하기에 달려 있습니다.

프레임워크

이번 예제의 코드는 전에 살펴봤던 라이팅 예제에서 이어집니다. 프레임워크는 변경이 없습니다.

Light.vs

////////////////////////////////////////////////////////////////////////////////
// Filename: light.vs
////////////////////////////////////////////////////////////////////////////////

HLSL에서는 define 구문을 사용할 수 있습니다. 이 점조명 예제에서는 셰이더가 얼마나 많은 점조명을 처리할 것인지 미리 정할 것입니다. 정점 셰이더와 픽셀 셰이더에 같은 값으로 선언됩니다.

/////////////
// DEFINES //
/////////////
#define NUM_LIGHTS 4


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

또한 HLSL은 define문으로 설정한 값으로 배열을 만드는 것이 가능합니다. 아래 배열은 4개의 점조명을 위한 4개의 위치를 저장합니다.

cbuffer LightPositionBuffer
{
    float4 lightPosition[NUM_LIGHTS];
};

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

PixelInputType 구조체는 점조명 4개의 최근 위치를 저장하도록 합니다.

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
    float3 lightPos1 : TEXCOORD1;
    float3 lightPos2 : TEXCOORD2;
    float3 lightPos3 : TEXCOORD3;
    float3 lightPos4 : TEXCOORD4;
};


////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType LightVertexShader(VertexInputType input)
{
    PixelInputType output;
    float4 worldPosition;


    // Change the position vector to be 4 units for proper matrix calculations.
    input.position.w = 1.0f;

    // Calculate the position of the vertex against the world, view, and projection matrices.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);
    
    // Store the texture coordinates for the pixel shader.
    output.tex = input.tex;
    
    // Calculate the normal vector against the world matrix only.
    output.normal = mul(input.normal, (float3x3)worldMatrix);
    
    // Normalize the normal vector.
    output.normal = normalize(output.normal);

    // Calculate the position of the vertex in the world.
    worldPosition = mul(input.position, worldMatrix);

정점마다 4개의 조명까지의 상대적인 위치를 계산하고 정규화한 뒤 픽셀 셰이더로 넘어갑니다.

    // Determine the light positions based on the position of the lights and the position of the vertex in the world.
    output.lightPos1.xyz = lightPosition[0].xyz - worldPosition.xyz;
    output.lightPos2.xyz = lightPosition[1].xyz - worldPosition.xyz;
    output.lightPos3.xyz = lightPosition[2].xyz - worldPosition.xyz;
    output.lightPos4.xyz = lightPosition[3].xyz - worldPosition.xyz;

    // Normalize the light position vectors.
    output.lightPos1 = normalize(output.lightPos1);
    output.lightPos2 = normalize(output.lightPos2);
    output.lightPos3 = normalize(output.lightPos3);
    output.lightPos4 = normalize(output.lightPos4);

    return output;
}

Light.ps

////////////////////////////////////////////////////////////////////////////////
// Filename: light.ps
////////////////////////////////////////////////////////////////////////////////


/////////////
// DEFINES //
/////////////
#define NUM_LIGHTS 4


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

아래 배열은 4개의 점조명의 색상을 저장하는 용도입니다.

cbuffer LightColorBuffer
{
    float4 diffuseColor[NUM_LIGHTS];
};


//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
    float3 lightPos1 : TEXCOORD1;
    float3 lightPos2 : TEXCOORD2;
    float3 lightPos3 : TEXCOORD3;
    float3 lightPos4 : TEXCOORD4;
};


////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 LightPixelShader(PixelInputType input) : SV_TARGET
{
    float4 textureColor;
    float lightIntensity1, lightIntensity2, lightIntensity3, lightIntensity4;
    float4 color, color1, color2, color3, color4;

4개의 조명의 강도는 각각의 광원의 위치와 노말 백터를 사용하여 계산됩니다.

    // Calculate the different amounts of light on this pixel based on the positions of the lights.
    lightIntensity1 = saturate(dot(input.normal, input.lightPos1));
    lightIntensity2 = saturate(dot(input.normal, input.lightPos2));
    lightIntensity3 = saturate(dot(input.normal, input.lightPos3));
    lightIntensity4 = saturate(dot(input.normal, input.lightPos4));

각 조명의 색상의 강도는 점조명의 강도와 빛의 색상을 이용하여 계산됩니다.

    // Determine the diffuse color amount of each of the four lights.
    color1 = diffuseColor[0] * lightIntensity1;
    color2 = diffuseColor[1] * lightIntensity2;
    color3 = diffuseColor[2] * lightIntensity3;
    color4 = diffuseColor[3] * lightIntensity4;

    // Sample the texture pixel at this location.
    textureColor = shaderTexture.Sample(SampleType, input.tex);

마지막으로 4개의 조명을 모두 더해 픽셀의 최종 값을 산출해 냅니다. 광원의 색상값들 다 더하고 텍스쳐의 색상을 곱함으로 연산이 완료됩니다.

    // Multiply the texture pixel by the combination of all four light colors to get the final result.
    color = saturate(color1 + color2 + color3 + color4) * textureColor;
    
    return color;
}

Lightshaderclass.h

LightShaderClass는 이전 튜토리얼과 같으며 점조명을 다루는 기능이 추가되었습니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: lightshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _LIGHTSHADERCLASS_H_
#define _LIGHTSHADERCLASS_H_

셰이더에 있던 점조명의 갯수가 여기에도 선언됩니다. 반드시 셰이더의 그것과 값이 일치해야 합니다.

/////////////
// GLOBALS //
/////////////
const int NUM_LIGHTS = 4;


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


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

정점 셰이더와 픽셀 셰이더에 사용할 조명 색상과 위치를 저장할 두 구조체를 선언합니다.

    struct LightColorBufferType
    {
        D3DXVECTOR4 diffuseColor[NUM_LIGHTS];
    };

    struct LightPositionBufferType
    {
        D3DXVECTOR4 lightPosition[NUM_LIGHTS];
    };

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

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

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

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

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

조명의 색상과 위치 배열을 저장하는 버퍼를 선언합니다.

    ID3D11Buffer* m_lightColorBuffer;
    ID3D11Buffer* m_lightPositionBuffer;
};

#endif

Lightshaderclass.cpp

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


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

생성자에서 조명 색상과 위치 버퍼에 대한 포인터를 null로 초기화합니다.

    m_lightColorBuffer = 0;
    m_lightPositionBuffer = 0;
}


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


LightShaderClass::~LightShaderClass()
{
}


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

점조명이 구현된 light.vslight.ps HLSL 셰이더 파일을 로드합니다.

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

    return true;
}


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

    return;
}

Render 함수는 점조명의 디퓨즈 색상과 위치에 대한 배열을 추가 인자로 받습니다. 두 배열은 렌더링이 일어나기 전에 셰이더로 전달될 것입니다.

bool LightShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, 
                  D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR4 diffuseColor[],
                  D3DXVECTOR4 lightPosition[])
{
    bool result;


    // Set the shader parameters that it will use for rendering.
    result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture, diffuseColor, lightPosition);
    if(!result)
    {
        return false;
    }

    // Now render the prepared buffers with the shader.
    RenderShader(deviceContext, indexCount);

    return true;
}


bool LightShaderClass::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 lightColorBufferDesc;
    D3D11_BUFFER_DESC lightPositionBufferDesc;


    // 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, "LightVertexShader", "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, "LightPixelShader", "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 vertex 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.
    // 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;

    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 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_sampleState);
    if(FAILED(result))
    {
        return false;
    }

    // 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 matrix 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 dynamic constant buffer that is in the pixel shader.
    lightColorBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
    lightColorBufferDesc.ByteWidth = sizeof(LightColorBufferType);
    lightColorBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    lightColorBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    lightColorBufferDesc.MiscFlags = 0;
    lightColorBufferDesc.StructureByteStride = 0;

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

조명 위치 배열 버퍼를 생성합니다. 참고로 버퍼 크기가 16바이트의 배수이어야 하기 때문에 위치 벡터로는 4차원 벡터를 사용했습니다.

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

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

    return true;
}


void LightShaderClass::ShutdownShader()
{

ShutdownShader 함수에서 새로 만든 조명 버퍼들을 해제합니다.

    // Release the light constant buffers.
    if(m_lightColorBuffer)
    {
        m_lightColorBuffer->Release();
        m_lightColorBuffer = 0;
    }
    if(m_lightPositionBuffer)
    {
        m_lightPositionBuffer->Release();
        m_lightPositionBuffer = 0;
    }

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

    // Release the sampler state.
    if(m_sampleState)
    {
        m_sampleState->Release();
        m_sampleState = 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 LightShaderClass::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 LightShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, 
                       D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture,
                       D3DXVECTOR4 diffuseColor[], D3DXVECTOR4 lightPosition[])
{
    HRESULT result;
    D3D11_MAPPED_SUBRESOURCE mappedResource;
    MatrixBufferType* dataPtr;
    LightPositionBufferType* dataPtr2;
    LightColorBufferType* dataPtr3;
    unsigned int bufferNumber;


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

    // Lock the matrix 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 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, &m_matrixBuffer);

정점 셰이더에 조명의 위치 배열 버퍼를 설정합니다.

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

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

    // Copy the light position variables into the constant buffer.
    dataPtr2->lightPosition[0] = lightPosition[0];
    dataPtr2->lightPosition[1] = lightPosition[1];
    dataPtr2->lightPosition[2] = lightPosition[2];
    dataPtr2->lightPosition[3] = lightPosition[3];

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

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

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

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

픽셀 셰이더에 조명 색상 배열 버퍼를 설정합니다.

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

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

    // Copy the light color variables into the constant buffer.
    dataPtr3->diffuseColor[0] = diffuseColor[0];
    dataPtr3->diffuseColor[1] = diffuseColor[1];
    dataPtr3->diffuseColor[2] = diffuseColor[2];
    dataPtr3->diffuseColor[3] = diffuseColor[3];

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

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

    // Finally set the constant buffer in the pixel shader with the updated values.
    deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_lightColorBuffer);

    return true;
}


void LightShaderClass::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, &m_sampleState);

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

    return;
}

Lightclass.h

LightClass는 방향이 있는 조명 대신 점조명을 사용하도록 바꾸었습니다. 이 예제에서 필요한 조명의 위치와 색상을 이용하기 위한 변수들과 보조 함수들이 추가됐습니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: lightclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _LIGHTCLASS_H_
#define _LIGHTCLASS_H_


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


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

    void SetDiffuseColor(float, float, float, float);
    void SetPosition(float, float, float);

    D3DXVECTOR4 GetDiffuseColor();
    D3DXVECTOR4 GetPosition();

private:
    D3DXVECTOR4 m_diffuseColor;
    D3DXVECTOR4 m_position;
};

#endif

Lightclass.cpp

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


LightClass::LightClass()
{
}


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


LightClass::~LightClass()
{
}


void LightClass::SetDiffuseColor(float red, float green, float blue, float alpha)
{
    m_diffuseColor = D3DXVECTOR4(red, green, blue, alpha);
    return;
}


void LightClass::SetPosition(float x, float y, float z)
{
    m_position = D3DXVECTOR4(x, y, z, 1.0f);
    return;
}


D3DXVECTOR4 LightClass::GetDiffuseColor()
{
    return m_diffuseColor;
}


D3DXVECTOR4 LightClass::GetPosition()
{
    return m_position;
}

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 "lightshaderclass.h"
#include "lightclass.h"


////////////////////////////////////////////////////////////////////////////////
// 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;
    LightShaderClass* m_LightShader;

이번 예제에서는 점조명과 다중 조명을 모두 구현하기 위해 4개의 점조명을 생성합니다.

    LightClass *m_Light1, *m_Light2, *m_Light3, *m_Light4;
};

#endif

Graphicsclass.cpp

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


GraphicsClass::GraphicsClass()
{
    m_D3D = 0;
    m_Camera = 0;
    m_Model = 0;
    m_LightShader = 0;

생성자에서 4개의 조명 모두 null로 초기화합니다.

    m_Light1 = 0;
    m_Light2 = 0;
    m_Light3 = 0;
    m_Light4 = 0;
}


GraphicsClass::GraphicsClass(const GraphicsClass& 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/stone01.dds", "../Engine/data/plane01.txt");
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
        return false;
    }

    // Create the light shader object.
    m_LightShader = new LightShaderClass;
    if(!m_LightShader)
    {
        return false;
    }

    // Initialize the light shader object.
    result = m_LightShader->Initialize(m_D3D->GetDevice(), hwnd);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK);
        return false;
    }

첫번째 조명은 왼쪽 위에 빨간색으로 놓겠습니다.

    // Create the first light object.
    m_Light1 = new LightClass;
    if(!m_Light1)
    {
        return false;
    }

    // Initialize the first light object.
    m_Light1->SetDiffuseColor(1.0f, 0.0f, 0.0f, 1.0f);
    m_Light1->SetPosition(-3.0f, 1.0f, 3.0f);

두번째 조명은 오른쪽 위에 녹색으로 놓겠습니다.

    // Create the second light object.
    m_Light2 = new LightClass;
    if(!m_Light2)
    {
        return false;
    }

    // Initialize the second light object.
    m_Light2->SetDiffuseColor(0.0f, 1.0f, 0.0f, 1.0f);
    m_Light2->SetPosition(3.0f, 1.0f, 3.0f);
````

세번째 조명은 왼쪽 아래에 파란색으로 놓겠습니다.

```c++
    // Create the third light object.
    m_Light3 = new LightClass;
    if(!m_Light3)
    {
        return false;
    }

    // Initialize the third light object.
    m_Light3->SetDiffuseColor(0.0f, 0.0f, 1.0f, 1.0f);
    m_Light3->SetPosition(-3.0f, 1.0f, -3.0f);

네번째 조명은 오른쪽 아래에 흰색으로 놓겠습니다.

    // Create the fourth light object.
    m_Light4 = new LightClass;
    if(!m_Light4)
    {
        return false;
    }

    // Initialize the fourth light object.
    m_Light4->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f);
    m_Light4->SetPosition(3.0f, 1.0f, -3.0f);

    return true;
}


void GraphicsClass::Shutdown()
{

Shutdown 함수에서 모든 점조명들을 해제합니다.

    // Release the light objects.
    if(m_Light1)
    {
        delete m_Light1;
        m_Light1 = 0;
    }

    if(m_Light2)
    {
        delete m_Light2;
        m_Light2 = 0;
    }

    if(m_Light3)
    {
        delete m_Light3;
        m_Light3 = 0;
    }

    if(m_Light4)
    {
        delete m_Light4;
        m_Light4 = 0;
    }

    // Release the light shader object.
    if(m_LightShader)
    {
        m_LightShader->Shutdown();
        delete m_LightShader;
        m_LightShader = 0;
    }

    // Release the model object.
    if(m_Model)
    {
        m_Model->Shutdown();
        delete m_Model;
        m_Model = 0;
    }

    // Release the camera object.
    if(m_Camera)
    {
        delete m_Camera;
        m_Camera = 0;
    }

    // Release the Direct3D object.
    if(m_D3D)
    {
        m_D3D->Shutdown();
        delete m_D3D;
        m_D3D = 0;
    }

    return;
}


bool GraphicsClass::Frame()
{
    bool result;


    // Set the position of the camera.
    m_Camera->SetPosition(0.0f, 2.0f, -12.0f);

    // Render the scene.
    result = Render();
    if(!result)
    {
        return false;
    }

    return true;
}


bool GraphicsClass::Render()
{
    D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix;
    D3DXVECTOR4 diffuseColor[4];
    D3DXVECTOR4 lightPosition[4];
    bool result;

네 개의 점조명으로부터 두 배열(색상 및 위치)을 설정합니다. 이렇게 하면 배열 2개만 보내면 되므로 8개의 인자를 일일이 보내는 것보다 쉽습니다.
(역자주: 성능상으로 도움이 됩니다)

    // Create the diffuse color array from the four light colors.
    diffuseColor[0] = m_Light1->GetDiffuseColor();
    diffuseColor[1] = m_Light2->GetDiffuseColor();
    diffuseColor[2] = m_Light3->GetDiffuseColor();
    diffuseColor[3] = m_Light4->GetDiffuseColor();

    // Create the light position array from the four light positions.
    lightPosition[0] = m_Light1->GetPosition();
    lightPosition[1] = m_Light2->GetPosition();
    lightPosition[2] = m_Light3->GetPosition();
    lightPosition[3] = m_Light4->GetPosition();

    // Clear the buffers to begin the scene.
    m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

    // Generate the view matrix based on the camera's position.
    m_Camera->Render();

    // Get the world, view, and projection matrices from the camera and d3d objects.
    m_D3D->GetWorldMatrix(worldMatrix);
    m_Camera->GetViewMatrix(viewMatrix);
    m_D3D->GetProjectionMatrix(projectionMatrix);

파이프라인에 평면 모델도 넣습니다.

    // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing.
    m_Model->Render(m_D3D->GetDeviceContext());

조명 셰이더와 4개의 점조명을 이용하여 평면을 그립니다.

    // Render the model using the light shader and the light arrays.
    result = m_LightShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, 
                       m_Model->GetTexture(), diffuseColor, lightPosition);
    if(!result)
    {
        return false;
    }

    // Present the rendered scene to the screen.
    m_D3D->EndScene();

    return true;
}

정리하며

이제 여러분은 점조명과 여러 개의 광원을 이용한 라이팅의 기본적인 개념을 이해하셨으리라 생각합니다.

연습 문제

  1. 프로그램을 컴파일하여 실행해 보십시오. 4개의 점조명으로 비춰지는 평면이 나타날 것입니다.
  2. 빨강, 초록, 파랑색의 조명을 모두 같은 위치에 놓아 보십시오. 최종 색이 하얀색으로 나타날 것입니다.
  3. 조명의 색상과 위치를 바꾸어서 다른 효과를 주어 보십시오.
  4. 다섯 번째 점조명을 배치해 보십시오.
  5. 하나의 점조명만을 사용하여 나타나는 효과를 확인해 보십시오.

소스 코드

Visual Studio 2008 프로젝트: dx11tut30.zip

소스파일만: dx11src30.zip

실행파일만: dx11exe30.zip

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

DirectX11 Tutorial 32: 유리, 얼음  (0) 2017.09.23
DirectX11 Tutorial 31: 3차원 음향  (0) 2017.09.08
DirectX11 Tutorial 29: 물  (2) 2017.09.01
DirectX11 Tutorial 28: 페이드 효과  (2) 2015.09.06
DirectX11 Tutorial 27: 반사  (0) 2015.06.21
Nav