DirectX11 Tutorial 29: 물

강좌번역/DirectX 11 2017. 9. 1. 02:13 by 빠재

Tutorial 29: Water

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

이번 예제에서는 HLSL과 C++를 이용하여 DirectX 11에서 물을 구현하는 방법을 다룹니다. 코드는 이전 예제의 코드에 기초합니다.

물의 구현은 현재 여러 가지가 나와 있고, 각각 장단점이 존재합니다. 여기에 구현할 물은 반사와 굴절이 되는 물입니다. 이것이 가장 비주얼적으로 좋은 구현이지만 물결의 높이가 너무 높으면 경계 문제가 발생합니다. 따라서 이 구현은 강물이나 연못, 수영장과 같이 물결이 잔잔한 물을 표현하기에 가장 적합합니다. 물론 비교적 잔잔하다는 가정 하에 호수나 바다에도 이것을 적용할 수 있습니다.

우선 굴절 및 반사가 되는 물을 보여주기 위한 간단한 장면을 만들 것입니다. 이 씬에는 평평한 지면 위에 물을 담아두기 위한 대리석 욕조가 있습니다. 또한 물에 물체가 반사되는 것을 표현하기 위해 돌 기둥을 하나 둘 것입니다. 만든 씬은 아래 그림과 같습니다:

그리고 나서 두 개의 삼각형으로 만든 사각형 물을 만듭니다. 그리고 욕조 안에 들어갈 수 있도록 배치합니다. 이 물에 사용할 텍스쳐는 따로 만드는 대신 물에 반사된 장면 자체를 사용할 것입니다. 반사 텍스쳐를 만들기 위해서는 이전 반사 예제에서 사용했던 반사 기술을 사용하여 텍스쳐에 반사 장면을 그릴 것입니다. 물의 높이와 카메라의 위치, 각도를 이용하여 물 위에 있는 모든 것들이 반사된 씬이 물에 그려집니다:

반사가 되는 물이 있으니 이제 굴절을 추가할 수 있습니다. 굴절은 기본적으로 반사의 반대 역할입니다. 반사가 물 위에 있는 것들을 그리는 것이었다면, 굴절은 물 아래에 있는 것을 그립니다. 반사 기능을 조금만 수정하면 굴절 기능을 구현할 수 있기 때문에 반사 때와 같은 방법으로 굴절된 씬은 텍스쳐에 렌더링하고 그것을 물 평면에 사용합니다. 기억해야 할 것은 굴절된 물 안쪽에는 욕조만 보여야 하기 때문에 돌기둥이나 땅바닥이 렌더링되어서는 안됩니다. 아래 그림에는 굴절 텍스쳐가 노란색으로 강조되어 있습니다. 굴절 씬이 어떻게 보여야 하는지 명확히 보여드리기 위하여 의도적으로 욕조는 그리지 않았습니다:

이제 물 평면에 할당할 반사 텍스쳐와 굴절 텍스쳐 모두 가지고 있기 때문에 두 텍스쳐를 선형 보간으로 혼합하여 두 효과가 동시에 적용된 물을 만들 수 있습니다:

구현한 굴절 및 반사 물을 좀 더 발전시키기 위해 잔잔한 물결을 표현할 노말맵을 추가합니다. 이전 범프맵 예제에서 물체의 굴곡을 표현하기 위한 노말맵의 역할을 다루었었습니다. 이를 응용하여 여기서는 물결을 표현하기 위해 노말맵을 사용합니다. 이전 예제에서는 빛의 방향과 범프 노멀의 값을 이용하여 한 픽셀의 라이팅을 계산했습니다. 하지만 물에서는 이 노말값을 이용하여 텍스쳐의 샘플링 위치를 흩뜨려서 실제 물결이 물 아래 있는 물체를 왜곡시키는 효과를 낼 것입니다. 또한 Y축 방향으로 텍스쳐를 흐르게 하여 흐르는 듯한 물을 재현할 것입니다.

참고로 제가 이 예제에서 사용하는 노말맵을 만들기 위해서 포토샵과 Nvidia의 텍스쳐 도구를 사용했습니다(Nvidia의 웹사이트에서 구할 수 있습니다). 우선 포토샵에서 새로운 이미지를 생성합니다(저는 256x256 크기로 했습니다). 그리고 나서 검정과 흰색만으로 필터->렌더->구름 도구를 사용하여 펄린 노이즈를 스타일의 구름 이미지를 만듭니다. 마지막으로 필터->NVIDIA 도구->노말맵 필터 메뉴를 선택하고 스케일을 10 정도로 줍니다. 이렇게 만들어진 이미지를 DirectX 텍스쳐 툴을 이용하여 DDS 파일로 뽑아냅니다:

마지막으로 알아야 할 것은 일부 그래픽 엔진은 굴절이나 반사 텍스쳐를 15-30프레임 간격으로 캡쳐하는데, 이렇게 하여 매 프레임마다 비싼 렌더 투 텍스쳐를 하지 않도록 합니다.

프레임워크

프레임워크에는 RefractionShaderClassWaterShaderClass가 추가됩니다.

Water.vs

////////////////////////////////////////////////////////////////////////////////
// Filename: water.vs
////////////////////////////////////////////////////////////////////////////////


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

반사 예제와 마찬가지로 물 셰이더 역시 반사 행렬을 가집니다.

cbuffer ReflectionBuffer
{
    matrix reflectionMatrix;
};


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

PixelInputType에는 반사 텍스쳐와 굴절 텍스쳐를 위한 두 개의 텍스쳐 좌표가 추가되어 있습니다.

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float4 reflectionPosition : TEXCOORD1;
    float4 refractionPosition : TEXCOORD2;
};


////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType WaterVertexShader(VertexInputType input)
{
    PixelInputType output;
    matrix reflectProjectWorld;
    matrix viewProjectWorld;
    
    
    // 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;

반사 예제에서 했던 것과 같이 반사용 월드 프로젝션 행렬을 만들고 반사 좌표를 계산합니다.

    // Create the reflection projection world matrix.
    reflectProjectWorld = mul(reflectionMatrix, projectionMatrix);
    reflectProjectWorld = mul(worldMatrix, reflectProjectWorld);

    // Calculate the input position against the reflectProjectWorld matrix.
    output.reflectionPosition = mul(input.position, reflectProjectWorld);

굴절 좌표 역시 반사 대신 뷰 행렬을 사용하는 것만 빼고는 반사 좌표와 같은 방법으로 계산됩니다.

    // Create the view projection world matrix for refraction.
    viewProjectWorld = mul(viewMatrix, projectionMatrix);
    viewProjectWorld = mul(worldMatrix, viewProjectWorld);
   
    // Calculate the input position against the viewProjectWorld matrix.
    output.refractionPosition = mul(input.position, viewProjectWorld);
    
    return output;
}

Water.ps

////////////////////////////////////////////////////////////////////////////////
// Filename: water.ps
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
SamplerState SampleType;

물 셰이더에는 세 개의 텍스쳐를 사용합니다. 첫번째는 반사 텍스쳐이고 그 다음으로 굴절, 마지막으로 물결을 표현하기 위한 노말맵 텍스쳐가 필요합니다.

Texture2D reflectionTexture;
Texture2D refractionTexture;
Texture2D normalTexture;

버퍼에 있는 waterTranslation 변수는 매 프레임마다 텍스쳐 샘플링 위치를 바꾸어 물이 흐르는 것처럼 보이게 하는 데 사용됩니다. reflectRefractScale 변수는 노말맵에 사용되어 물결의 크기를 조절하는 데 사용됩니다. 어떤 노말맵은 값이 급격하게 바뀌기도 하고 그렇지 않기도 합니다. 이런 정도를 조절할 수 있는 변수를 두는 것이 꽤 유용합니다.

cbuffer WaterBuffer
{
    float waterTranslation;
    float reflectRefractScale;
    float2 padding;
};


//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float4 reflectionPosition : TEXCOORD1;
    float4 refractionPosition : TEXCOORD2;
};


////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 WaterPixelShader(PixelInputType input) : SV_TARGET
{
    float2 reflectTexCoord;
    float2 refractTexCoord;
    float4 normalMap;
    float3 normal;
    float4 reflectionColor;
    float4 refractionColor;
    float4 color;

이동 셰이더 예제에서와 같이 매 프레임마다 업데이트되는 위치 변수를 사용하여 물 노말맵이 Y축으로 흐르는 효과를 냅니다.

    // Move the position the water normal is sampled from to simulate moving water. 
    input.tex.y += waterTranslation;

굴절과 반사에 사용하는 좌표값들을 -1과 +1 사이에 들어가게 합니다.

    // Calculate the projected reflection texture coordinates.
    reflectTexCoord.x = input.reflectionPosition.x / input.reflectionPosition.w / 2.0f + 0.5f;
    reflectTexCoord.y = -input.reflectionPosition.y / input.reflectionPosition.w / 2.0f + 0.5f;
    
    // Calculate the projected refraction texture coordinates.
    refractTexCoord.x = input.refractionPosition.x / input.refractionPosition.w / 2.0f + 0.5f;
    refractTexCoord.y = -input.refractionPosition.y / input.refractionPosition.w / 2.0f + 0.5f;

노말값을 노말맵에서 샘플링하고 값이 -1과 +1 사이데 들어가게 합니다.

    // Sample the normal from the normal map texture.
    normalMap = normalTexture.Sample(SampleType, input.tex);

    // Expand the range of the normal from (0,1) to (-1,+1).
    normal = (normalMap.xyz * 2.0f) - 1.0f;

반사와 굴절 좌표를 노말값에 따라 흩뜨립니다. 실제 물결이 빛을 왜곡하는 것과 같이 노말 값이 -1에서 +1사이로 변형시켜 물결치는 효과를 만들어 냅니다. 노말 값에 reflectRefractScale을 곱하여 좀 더 자연스럽고 잔잔하게 보이게 합니다.

    // Re-position the texture coordinate sampling position by the normal map value to simulate the rippling wave effect.
    reflectTexCoord = reflectTexCoord + (normal.xy * reflectRefractScale);
    refractTexCoord = refractTexCoord + (normal.xy * reflectRefractScale);

그리고 나서 계산된 샘플링 좌표를 이용하여 픽셀값을 뽑아냅니다.

    // Sample the texture pixels from the textures using the updated texture coordinates.
    reflectionColor = reflectionTexture.Sample(SampleType, reflectTexCoord);
    refractionColor = refractionTexture.Sample(SampleType, refractTexCoord);

마지막으로 선형보간으로 두 픽셀값들을 혼합합니다.

    // Combine the reflection and refraction results for the final color.
    color = lerp(reflectionColor, refractionColor, 0.6f);
    
    return color;
}

Watershaderclass.h

WaterShaderClasswater.vswater.ps 셰이더를 사용하여 물을 렌더링합니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: watershaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _WATERSHADERCLASS_H_
#define _WATERSHADERCLASS_H_


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


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

정점 및 픽셀 셰이더에서 사용하는 반사와 물 상수 버퍼를 위한 두 구조체를 선언합니다.

    struct ReflectionBufferType
    {
        D3DXMATRIX reflection;
    };

    struct WaterBufferType
    {
        float waterTranslation;
        float reflectRefractScale;
        D3DXVECTOR2 padding;
    };

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

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

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

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

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

셰이더에서 사용하는 반사 및 물을 위한 상수 버퍼를 선언합니다.

    ID3D11Buffer* m_reflectionBuffer;
    ID3D11Buffer* m_waterBuffer;
};

#endif

Watershaderclass.cpp

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


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

새로 추가된 포인터를 초기화합니다.

    m_reflectionBuffer = 0;
    m_waterBuffer = 0;
}


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


WaterShaderClass::~WaterShaderClass()
{
}


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

새로 추가된 water.vs, water.ps HLSL 셰이더 파일을 불러옵니다.

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

    return true;
}


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

    return;
}

Render 함수는 물을 렌더링하기 전에 셰이더에 세팅할 물 인자들을 받습니다. 이 인자들에는 반사 행렬, 반사 텍스쳐, 굴절 텍스쳐, 노말맵, 물의 이동 위치, 물결 스케일입니다.

bool WaterShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, 
                  D3DXMATRIX projectionMatrix, D3DXMATRIX reflectionMatrix, 
                  ID3D11ShaderResourceView* reflectionTexture, ID3D11ShaderResourceView* refractionTexture,
                  ID3D11ShaderResourceView* normalTexture, float waterTranslation, float reflectRefractScale)
{
    bool result;


    // Set the shader parameters that it will use for rendering.
    result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, reflectionMatrix, reflectionTexture, 
                     refractionTexture, normalTexture, waterTranslation, reflectRefractScale);
    if(!result)
    {
        return false;
    }

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

    return true;
}


bool WaterShaderClass::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_SAMPLER_DESC samplerDesc;
    D3D11_BUFFER_DESC matrixBufferDesc;
    D3D11_BUFFER_DESC reflectionBufferDesc;
    D3D11_BUFFER_DESC waterBufferDesc;


    // 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, "WaterVertexShader", "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, "WaterPixelShader", "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;

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

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

물 상수 버퍼를 설정합니다.

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

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

    return true;
}


void WaterShaderClass::ShutdownShader()
{

ShutdownShader함수에서 물과 반사 상수 버퍼를 해제합니다.

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

    // Release the reflection constant buffer.
    if(m_reflectionBuffer)
    {
        m_reflectionBuffer->Release();
        m_reflectionBuffer = 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 WaterShaderClass::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;
}

SetShaderParameters 함수는 물 모델을 그리기 전에 셰이더 인자들을 설정합니다.

bool WaterShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, 
                       D3DXMATRIX projectionMatrix, D3DXMATRIX reflectionMatrix, 
                       ID3D11ShaderResourceView* reflectionTexture, 
                       ID3D11ShaderResourceView* refractionTexture, ID3D11ShaderResourceView* normalTexture,
                       float waterTranslation, float reflectRefractScale)
{
    HRESULT result;
    D3D11_MAPPED_SUBRESOURCE mappedResource;
    unsigned int bufferNumber;
    MatrixBufferType* dataPtr;
    ReflectionBufferType* dataPtr2;
    WaterBufferType* dataPtr3;

4개의 입력 행렬을 전치시킵니다.

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

정점 셰이더에 행렬 버퍼를 설정합니다.

    // 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 reflection constant buffer so it can be written to.
    result = deviceContext->Map(m_reflectionBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
    if(FAILED(result))
    {
        return false;
    }

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

    // Copy the reflection matrix into the constant buffer.
    dataPtr2->reflection = reflectionMatrix;

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

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

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

픽셀 셰이더에 반사, 굴절, 노말맵 텍스쳐를 설정합니다.

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

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

    // Set the normal map texture resource in the pixel shader.
    deviceContext->PSSetShaderResources(2, 1, &normalTexture);

픽셀 셰이더에 물 버퍼를 설정합니다.

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

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

    // Copy the water data into the constant buffer.
    dataPtr3->waterTranslation = waterTranslation;
    dataPtr3->reflectRefractScale = reflectRefractScale;
    dataPtr3->padding = D3DXVECTOR2(0.0f, 0.0f);

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

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

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

    return true;
}

RenderShader 함수는 물 셰이더를 이용하여 모델을 그립니다.

void WaterShaderClass::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 triangles.
    deviceContext->DrawIndexed(indexCount, 0, 0);

    return;
}

Refraction.vs

굴절 셰이더는 라이트 셰이더에 클리핑 평면이 추가된 형태입니다. 굴절이 정상적으로 씬을 렌더링하지만 물 아래 있는 것들만 그려야 하기 때문에 클리핑 평면을 사용하여 해결합니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: refraction.vs
////////////////////////////////////////////////////////////////////////////////


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

라이트 셰이더에 클리핑 평면 버퍼를 추가하여 굴절 셰이더가 되게 합니다.

cbuffer ClipPlaneBuffer
{
    float4 clipPlane;
};


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

PixelInputTypeclip변수를 추가하여 굴절 셰이더에 필요한 클리핑 거리를 설정할 수 있게 합니다.

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
    float clip : SV_ClipDistance0;
};


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

    // 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);

여기에 클리핑 평면을 설정합니다.

    // Set the clipping plane.
    output.clip = dot(mul(input.position, worldMatrix), clipPlane);

    return output;
}

Refraction.ps

////////////////////////////////////////////////////////////////////////////////
// Filename: refraction.ps
////////////////////////////////////////////////////////////////////////////////


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

cbuffer LightBuffer
{
    float4 ambientColor;
    float4 diffuseColor;
    float3 lightDirection;
};


//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
    float clip : SV_ClipDistance0;
};


////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 RefractionPixelShader(PixelInputType input) : SV_TARGET
{
    float4 textureColor;
    float3 lightDir;
    float lightIntensity;
    float4 color;
    

    // Sample the texture pixel at this location.
    textureColor = shaderTexture.Sample(SampleType, input.tex);
    
    // Set the default output color to the ambient light value for all pixels.
    color = ambientColor;

    // Invert the light direction for calculations.
    lightDir = -lightDirection;

    // Calculate the amount of light on this pixel.
    lightIntensity = saturate(dot(input.normal, lightDir));

    if(lightIntensity > 0.0f)
    {
        // Determine the final diffuse color based on the diffuse color and the amount of light intensity.
        color += (diffuseColor * lightIntensity);
    }

    // Saturate the final light color.
    color = saturate(color);

    // Multiply the texture pixel and the input color to get the final result.
    color = color * textureColor;
    
    return color;
}

Refractionshaderclass.h

RefractionShaderClassLightShaderClass에 클리핑 평면 버퍼가 추가된 형태입니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: refractionshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _REFRACTIONSHADERCLASS_H_
#define _REFRACTIONSHADERCLASS_H_


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


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

    struct LightBufferType
    {
        D3DXVECTOR4 ambientColor;
        D3DXVECTOR4 diffuseColor;
        D3DXVECTOR3 lightDirection;
        float padding;
    };

여기에 클리핑 평면 구조체를 추가합니다.

    struct ClipPlaneBufferType
    {
        D3DXVECTOR4 clipPlane;
    };

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

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

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

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

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

클리핑 평면 버퍼를 선언합니다.

    ID3D11Buffer* m_clipPlaneBuffer;
};

#endif

Refractionshaderclass.cpp

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


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

클리핑 평변 버퍼를 생성자에서 null로 초기화합니다.

    m_clipPlaneBuffer = 0;
}


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


RefractionShaderClass::~RefractionShaderClass()
{
}


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

굴절 HLSL 파일을 불러옵니다.

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

    return true;
}


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

    return;
}

Render함수는 클리핑 평면 인자가 추가됩니다.

bool RefractionShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, 
                   D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, 
                   D3DXVECTOR3 lightDirection, D3DXVECTOR4 ambientColor, D3DXVECTOR4 diffuseColor, 
                   D3DXVECTOR4 clipPlane)
{
    bool result;


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

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

    return true;
}


bool RefractionShaderClass::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 clipPlaneBufferDesc;


    // 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, "RefractionVertexShader", "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, "RefractionPixelShader", "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 light dynamic constant buffer that is in the pixel shader.
    // Note that ByteWidth always needs to be a multiple of 16 if using D3D11_BIND_CONSTANT_BUFFER or CreateBuffer will fail.
    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 vertex shader constant buffer from within this class.
    result = device->CreateBuffer(&lightBufferDesc, NULL, &m_lightBuffer);
    if(FAILED(result))
    {
        return false;
    }

클리핑 평면 상수 버퍼를 설정하여 정점 셰이더에서 클리핑 평면을 조작할 수 있게 합니다.

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

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

    return true;
}


void RefractionShaderClass::ShutdownShader()
{

ShutdownShader 함수에서 클리핑 평면 버퍼를 해제합니다.

    // Release the clip plane constant buffer.
    if(m_clipPlaneBuffer)
    {
        m_clipPlaneBuffer->Release();
        m_clipPlaneBuffer = 0;
    }

    // Release the light constant buffer.
    if(m_lightBuffer)
    {
        m_lightBuffer->Release();
        m_lightBuffer = 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 RefractionShaderClass::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 RefractionShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix,
                        D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture,
                        D3DXVECTOR3 lightDirection, D3DXVECTOR4 ambientColor, D3DXVECTOR4 diffuseColor,
                        D3DXVECTOR4 clipPlane)
{
    HRESULT result;
    D3D11_MAPPED_SUBRESOURCE mappedResource;
    MatrixBufferType* dataPtr;
    LightBufferType* dataPtr2;
    unsigned int bufferNumber;
    ClipPlaneBufferType* dataPtr3;


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

    // 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;

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

    // 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 constant buffer so it can be written to.
    result = deviceContext->Map(m_lightBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
    if(FAILED(result))
    {
        return false;
    }

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

    // Copy the lighting variables into the constant buffer.
    dataPtr2->ambientColor = ambientColor;
    dataPtr2->diffuseColor = diffuseColor;
    dataPtr2->lightDirection = lightDirection;

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

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

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

정점 셰이더에 클리핑 평면을 설정합니다.

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

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

    // Copy the clip plane into the clip plane constant buffer.
    dataPtr3->clipPlane = clipPlane;

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

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

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

    return true;
}


void RefractionShaderClass::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;
}

Graphicsclass.h

GraphicsClass에서 3D 씬과 물을 설정하고 렌더링합니다.

////////////////////////////////////////////////////////////////////////////////
// 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 "lightclass.h"
#include "rendertextureclass.h"
#include "lightshaderclass.h"
#include "refractionshaderclass.h"
#include "watershaderclass.h"


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

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

private:
    bool RenderRefractionToTexture();
    bool RenderReflectionToTexture();
    bool RenderScene();

private:
    D3DClass* m_D3D;
    CameraClass* m_Camera;

이 예제에서는 바닥, 벽, 욕조 그리고 물 이렇게 네 종류의 모델이 필요합니다.

    ModelClass *m_GroundModel, *m_WallModel, *m_BathModel, *m_WaterModel;
    LightClass* m_Light;

또한 반사와 굴절 텍스쳐를 위한 두 개의 렌더 투 텍스쳐가 필요합니다.

    RenderTextureClass *m_RefractionTexture, *m_ReflectionTexture;

빛 셰이더, 반사 셰이더, 물 셰이더가 사용됩니다.

    LightShaderClass* m_LightShader;
    RefractionShaderClass* m_RefractionShader;
    WaterShaderClass* m_WaterShader;

또한 물의 위치와 높이를 저장할 변수 두 개를 선언합니다.

    float m_waterHeight, m_waterTranslation;
};

#endif

Graphicsclass.cpp

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

이 클래스에서 사용하는 모든 포인터는 생성자에서 null로 초기화합니다.

GraphicsClass::GraphicsClass()
{
    m_D3D = 0;
    m_Camera = 0;
    m_GroundModel = 0;
    m_WallModel = 0;
    m_BathModel = 0;
    m_WaterModel = 0;
    m_Light = 0;
    m_RefractionTexture = 0;
    m_ReflectionTexture = 0;
    m_LightShader = 0;
    m_RefractionShader = 0;
    m_WaterShader = 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 ground model object.
    m_GroundModel = new ModelClass;
    if(!m_GroundModel)
    {
        return false;
    }

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

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

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

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

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

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

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

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

    // Initialize the light object.
    m_Light->SetAmbientColor(0.15f, 0.15f, 0.15f, 1.0f);
    m_Light->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f);
    m_Light->SetDirection(0.0f, -1.0f, 0.5f);

반사와 굴절에 사용할 두 렌더 투 텍스쳐를 설정합니다.

    // Create the refraction render to texture object.
    m_RefractionTexture = new RenderTextureClass;
    if(!m_RefractionTexture)
    {
        return false;
    }

    // Initialize the refraction render to texture object.
    result = m_RefractionTexture->Initialize(m_D3D->GetDevice(), screenWidth, screenHeight);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the refraction render to texture object.", L"Error", MB_OK);
        return false;
    }

    // Create the reflection render to texture object.
    m_ReflectionTexture = new RenderTextureClass;
    if(!m_ReflectionTexture)
    {
        return false;
    }

    // Initialize the reflection render to texture object.
    result = m_ReflectionTexture->Initialize(m_D3D->GetDevice(), screenWidth, screenHeight);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the reflection render to texture 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 refraction shader object.
    m_RefractionShader = new RefractionShaderClass;
    if(!m_RefractionShader)
    {
        return false;
    }

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

    // Create the water shader object.
    m_WaterShader = new WaterShaderClass;
    if(!m_WaterShader)
    {
        return false;
    }

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

물 평면의 높이와 이동 값을 설정합니다.

    // Set the height of the water.
    m_waterHeight = 2.75f;

    // Initialize the position of the water.
    m_waterTranslation = 0.0f;

    return true;
}


void GraphicsClass::Shutdown()
{
    // Release the water shader object.
    if(m_WaterShader)
    {
        m_WaterShader->Shutdown();
        delete m_WaterShader;
        m_WaterShader = 0;
    }

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

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

    // Release the reflection render to texture object.
    if(m_ReflectionTexture)
    {
        m_ReflectionTexture->Shutdown();
        delete m_ReflectionTexture;
        m_ReflectionTexture = 0;
    }

    // Release the refraction render to texture object.
    if(m_RefractionTexture)
    {
        m_RefractionTexture->Shutdown();
        delete m_RefractionTexture;
        m_RefractionTexture = 0;
    }

    // Release the light object.
    if(m_Light)
    {
        delete m_Light;
        m_Light = 0;
    }

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

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

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

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

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

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

    return;
}


bool GraphicsClass::Frame()
{

매 프레임마다 물의 위치를 갱신하여 물의 흐름을 표현합니다.

    // Update the position of the water to simulate motion.
    m_waterTranslation += 0.001f;
    if(m_waterTranslation > 1.0f)
    {
        m_waterTranslation -= 1.0f;
    }

    // Set the position and rotation of the camera.
    m_Camera->SetPosition(-10.0f, 6.0f, -10.0f);
    m_Camera->SetRotation(0.0f, 45.0f, 0.0f);

    return true;
}

먼저 텍스쳐에 굴절된 씬을 그립니다. 그리고 나서 반사된 씬을 텍스쳐에 그립니다. 마지막으로 전체 씬을 렌더링하고 반사와 굴절 텍스쳐를 이용하여 물 효과를 만듭니다. 렌더 투 텍스쳐로 인한 비용을 절약하고 싶다면 매 프레임마다 캡쳐하는 대신 15-30프레임마다 캡쳐하는 방법이 있다는 것을 기억해 주시기 바랍니다.

bool GraphicsClass::Render()
{
    bool result;


    // Render the refraction of the scene to a texture.
    result = RenderRefractionToTexture();
    if(!result)
    {
        return false;
    }

    // Render the reflection of the scene to a texture.
    result = RenderReflectionToTexture();
    if(!result)
    {
        return false;
    }

    // Render the scene as normal to the back buffer.
    result = RenderScene();
    if(!result)
    {
        return false;
    }

    return true;
}

RenderRefractionToTexture 함수는 굴절된 씬을 렌더 투 텍스쳐로 그립니다. 굴절된 씬에는 물 아래 있는 욕조만 보여야 하기 때문에 욕조 모델만 그리도록 선택합니다.

bool GraphicsClass::RenderRefractionToTexture()
{
    D3DXVECTOR4 clipPlane;
    D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix;
    bool result;

예상했던 것처럼 클리핑 평면을 사용하여 물 위에 있는 모든 것들을 잘라내고 그 아래 있는 것만 그려지도록 합니다. 하지만 여기서 클리핑 평면이 물보다 살짝 위로 가도록 한다는 걸 주목할 수 있습니다. 이렇게 하는 이유는 이 기술이 경계 부분에 노말맵으로 변형시킨 샘플링 위치가 검게 보이는 문제가 있어 더 많은 색상 데이터가 모이게끔 하기 위한 것입니다(클리핑 평면을 살짝 위로 들음으로써). 이 프로그램을 실행할 때 클리핑 평면의 높이를 물 높이와 맞춰 보면 그 결함이 자주 일어남을 볼 수 있습니다.

    // Setup a clipping plane based on the height of the water to clip everything above it.
    clipPlane = D3DXVECTOR4(0.0f, -1.0f, 0.0f, m_waterHeight + 0.1f);

    // Set the render target to be the refraction render to texture.
    m_RefractionTexture->SetRenderTarget(m_D3D->GetDeviceContext(), m_D3D->GetDepthStencilView());

    // Clear the refraction render to texture.
    m_RefractionTexture->ClearRenderTarget(m_D3D->GetDeviceContext(), m_D3D->GetDepthStencilView(), 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);

    // Translate to where the bath model will be rendered.
    D3DXMatrixTranslation(&worldMatrix, 0.0f, 2.0f, 0.0f);

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

    // Render the bath model using the light shader.
    result = m_RefractionShader->Render(m_D3D->GetDeviceContext(), m_BathModel->GetIndexCount(), worldMatrix, viewMatrix,
                        projectionMatrix, m_BathModel->GetTexture(), m_Light->GetDirection(), 
                        m_Light->GetAmbientColor(), m_Light->GetDiffuseColor(), clipPlane);
    if(!result)
    {
        return false;
    }

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

    return true;
}

RenderReflectionToTexture 함수는 반사된 장면을 렌더 투 텍스쳐에 그립니다. 물 위에 있는 물체만 그리기 때문에 물에 반사가 되는 것처럼 보이는 벽 모델만 그리도록 합니다.

bool GraphicsClass::RenderReflectionToTexture()
{
    D3DXMATRIX reflectionViewMatrix, worldMatrix, projectionMatrix;
    bool result;


    // Set the render target to be the reflection render to texture.
    m_ReflectionTexture->SetRenderTarget(m_D3D->GetDeviceContext(), m_D3D->GetDepthStencilView());

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

물의 높이를 이용하여 반사 행렬을 설정합니다.

    // Use the camera to render the reflection and create a reflection view matrix.
    m_Camera->RenderReflection(m_waterHeight);

    // Get the camera reflection view matrix instead of the normal view matrix.
    reflectionViewMatrix = m_Camera->GetReflectionViewMatrix();

    // Get the world and projection matrices from the d3d object.
    m_D3D->GetWorldMatrix(worldMatrix);
    m_D3D->GetProjectionMatrix(projectionMatrix);

    // Translate to where the wall model will be rendered.
    D3DXMatrixTranslation(&worldMatrix, 0.0f, 6.0f, 8.0f);

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

    // Render the wall model using the light shader and the reflection view matrix.
    result = m_LightShader->Render(m_D3D->GetDeviceContext(), m_WallModel->GetIndexCount(), worldMatrix, reflectionViewMatrix, 
                       projectionMatrix, m_WallModel->GetTexture(), m_Light->GetDirection(), 
                       m_Light->GetAmbientColor(), m_Light->GetDiffuseColor());
    if(!result)
    {
        return false;
    }

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

    return true;
}

RenderScene 함수는 화면을 백버퍼에 기록합니다. 그리고 렌더링된 반사 및 굴절 텍스쳐를 사용하여 물을 그립니다.

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


    // 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);

바닥 모델을 먼저 그립니다.

    // Translate to where the ground model will be rendered.
    D3DXMatrixTranslation(&worldMatrix, 0.0f, 1.0f, 0.0f);

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

    // Render the ground model using the light shader.
    result = m_LightShader->Render(m_D3D->GetDeviceContext(), m_GroundModel->GetIndexCount(), worldMatrix, viewMatrix, 
                       projectionMatrix, m_GroundModel->GetTexture(), m_Light->GetDirection(), 
                       m_Light->GetAmbientColor(), m_Light->GetDiffuseColor());
    if(!result)
    {
        return false;
    }

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

그 다음으로 벽을 그립니다.

    // Translate to where the wall model will be rendered.
    D3DXMatrixTranslation(&worldMatrix, 0.0f, 6.0f, 8.0f);

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

    // Render the wall model using the light shader.
    result = m_LightShader->Render(m_D3D->GetDeviceContext(), m_WallModel->GetIndexCount(), worldMatrix, viewMatrix, 
                       projectionMatrix, m_WallModel->GetTexture(), m_Light->GetDirection(), 
                       m_Light->GetAmbientColor(), m_Light->GetDiffuseColor());
    if(!result)
    {
        return false;
    }

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

욕조 모델을 그립니다.

    // Translate to where the bath model will be rendered.
    D3DXMatrixTranslation(&worldMatrix, 0.0f, 2.0f, 0.0f);

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

    // Render the bath model using the light shader.
    result = m_LightShader->Render(m_D3D->GetDeviceContext(), m_BathModel->GetIndexCount(), worldMatrix, viewMatrix, 
                       projectionMatrix, m_BathModel->GetTexture(), m_Light->GetDirection(), 
                       m_Light->GetAmbientColor(), m_Light->GetDiffuseColor());
    if(!result)
    {
        return false;
    }
    
    // Reset the world matrix.
    m_D3D->GetWorldMatrix(worldMatrix);

마지막으로 반사 행렬과 렌더 투 텍스쳐, 노말맵, 이동값, reflectRefractScale값을 사용하여 물 모델을 그립니다.

    // Get the camera reflection view matrix.
    reflectionMatrix = m_Camera->GetReflectionViewMatrix();

    // Translate to where the water model will be rendered.
    D3DXMatrixTranslation(&worldMatrix, 0.0f, m_waterHeight, 0.0f); 

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

    // Render the water model using the water shader.
    result = m_WaterShader->Render(m_D3D->GetDeviceContext(), m_WaterModel->GetIndexCount(), worldMatrix, viewMatrix, 
                       projectionMatrix, reflectionMatrix, m_ReflectionTexture->GetShaderResourceView(),
                       m_RefractionTexture->GetShaderResourceView(), m_WaterModel->GetTexture(), 
                       m_waterTranslation, 0.01f);
    if(!result)
    {
        return false;
    }

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

    return true;
}

정리하며

반사 및 굴절되는 물에 이동하는 노말맵을 추가하여 매우 사실적인 물 효과를 낼 수 있습니다. 그리고 다른 효과를 낼 때에도 다양하게 확장될 수 있습니다(유리, 얼음 등등).

연습 문제

  1. 프로그램을 컴파일하여 실행해 보십시오. 반사와 굴절 그리고 움직이는 물을 볼 수 있습니다.
  2. color = lerp(reflectionColor, refractionColor, 0.6f)에서 0.6f 값을 조절하여 반사와 굴절이 어떻게 보이는지 확인해 보십시오.
  3. reflectRefractScale 값을 수정해 보십시오(m_WaterShader->Render 함수의 마지막 인자)
  4. 여러분만의 노말맵을 만들어 다른 물 효과를 내 보십시오.
  5. RenderRefractionToTexture() 함수에서 클리핑 평면의 높이를 물의 높이와 동일하게 해 보십시오.

소스코드

Visual Studio 2008 프로젝트: dx11tut29.zip

소스파일만: dx11src29.zip

실행파일만: dx11exe29.zip

Nav