튀김족발
공부 끄적이는 공간
튀김족발
전체 방문자
오늘
어제
  • 분류 전체보기 (33)
    • C++ (10)
    • DirectX (12)
    • Unreal (11)
    • Unity (0)
    • EASTL (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
튀김족발

공부 끄적이는 공간

9. 정반사광
DirectX

9. 정반사광

2022. 12. 13. 21:54

정반사광은 밝은 점광원의 위치가 어디인지 알려주는 단서의 역할을 합니다. 예를들어 단순조명과 주변광이 적용된 시뻘건 구체가 있다면 다음과 같을 것입니다.

단순조명, 주변광 그리고 시뻘건 구체

여기에 하얀 정반사광 효과를 넣어주면 다음과 같아질겁니다.

시뻘건 구체 그리고 정반사광

정반사광은 거울이나 반짝반짝한 금속 표면과 같은 재밀에서의 빛의 반사를 표현하는 거의 모든 경우에 사용됩니다. 또한 태양빛이 물에 반사괴는덧도 같은 다른 재질도 이것으로 표현이 가능합니다. 잘만 사용된다면, 3D장면의 리얼리티 수준을 한층 더 끌어올려 줄 수 있습니다. (레이트레이싱 기술만 봐도 빛 연산을 엄청나게 끌어올린 기술인데 현실과 비슷해진다.)

 

정반사광의 방적식은 다음과 같습니다.

반사광 = 반사색 * (반사된 빛의 색 * ((법선과 하프벡터의 내적) ^ 반사강도) * 감쇄계수 * 스포트라이트의 계수)

 

기본적인 정반사광 효과만을 위해 다음과같이 식을 수정합니다.

반사광 = 반사빛의 색 * (보는 방향과 반사광의 내적) ^ 반사강도

 

이 식에서 반사광 벡터는 빛의 강도의 두배의 크기를 정점의 법선에 곱하고, 여기에 빛의 방향을 빼서 얻어집니다.

반사벡터 = 2 * 빛의강도 * 법선 - 빛의방향

 

식에서 보는 방향은 카메라의 위치에서 정점의 위치를 뺍으로 계산됩니다.

보는방향 = 카메라위치 - 정점위치

 

수정된 조명 쉐이더를 보면서 어떻게 이것이 구현됐는가를 봅시다.

LightShader

LightVertex.hlsl

/////////////
// GLOBALS //
/////////////
cbuffer MatrixBuffer : register(b0)
{
    matrix worldMatrix;
    matrix viewMatrix;
    matrix projectionMatrix;
};

cbuffer CameraBuffer
{
    float3 cameraPosition;
    float padding;
};

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

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
    float3 viewDirection : TEXCOORD1;
};

////////////////////
// Vertext Shader //
////////////////////
PixelInputType LightVertexShader(VertexInputType input)
{
    PixelInputType output;
    
    // 적절한 행렬 계산을 위해 위치 벡터를 4 단위로 변경합니다.
    input.position.w = 1.0f;
    
    // 월드, 뷰 및 투영 행렬에 대한 정점의 위치를 계산합니다.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);

    // 픽셀 쉐이더의 텍스쳐 좌표를 저장합니다.
    output.tex = input.tex;
    
    // 월드 행렬에 대해서만 법선 벡터를 계산합니다
    output.normal = mul(input.normal, (float3x3) worldMatrix);
    
    // 법선 벡터를 정규화 합니다.
    output.normal = normalize(output.normal);
    
    // 세계의 정점 위치를 계산합니다.
    float4 worldPosition = mul(input.position, worldMatrix);
    
    // 카메라의 위치와 세계의 정점 위치를 기준으로 보기 방향을 결정합니다.
    output.viewDirection = cameraPosition.xyz - worldPosition.xyz;
    
    // 뷰 방향 벡터를 표준화합니다.
    output.viewDirection = normalize(output.viewDirection);
    
    return output;
}

하나하나 살펴봅시다.

 

cbuffer CameraBuffer
{
    float3 cameraPosition;
    float padding;
};

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

카메라의 정보를 저장할 새로운 상수 버퍼를 추가합니다. 이 쉐이더에서는 정반사의 계산을 위해 이 정점이 어디서 보여지는지의 정보가 필요하므로 카메라의 위치를 알아야 합니다.

 

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
    float3 viewDirection : TEXCOORD1;
};

PixelInputType 구조체는 정점 쉐이더에서 보는 방향을 계산해야 하기 때문에 이에 대한 변수를 추가하고 정반사광의 계산을 위해 픽셀 쉐이더로 넘겨집니다.

 

////////////////////
// Vertext Shader //
////////////////////
PixelInputType LightVertexShader(VertexInputType input)
{
    PixelInputType output;
    
    // 적절한 행렬 계산을 위해 위치 벡터를 4 단위로 변경합니다.
    input.position.w = 1.0f;
    
    // 월드, 뷰 및 투영 행렬에 대한 정점의 위치를 계산합니다.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);

    // 픽셀 쉐이더의 텍스쳐 좌표를 저장합니다.
    output.tex = input.tex;
    
    // 월드 행렬에 대해서만 법선 벡터를 계산합니다
    output.normal = mul(input.normal, (float3x3) worldMatrix);
    
    // 법선 벡터를 정규화 합니다.
    output.normal = normalize(output.normal);
    
    // 세계의 정점 위치를 계산합니다.
    float4 worldPosition = mul(input.position, worldMatrix);
    
    // 카메라의 위치와 세계의 정점 위치를 기준으로 보기 방향을 결정합니다.
    output.viewDirection = cameraPosition.xyz - worldPosition.xyz;
    
    // 뷰 방향 벡터를 표준화합니다.
    output.viewDirection = normalize(output.viewDirection);
    
    return output;
}

정점 쉐이더에서는 여기서 보는 방향이 계산됩니다.(worldPosition 계산) 정점의 월드상의 위치를 계산하고 카메라의 위치에서 이 위치를 뺌으로 실제 어떤 방향으로 보는지를 알아냅니다. 이 계산의 결과는 정규화되어 픽셀 쉐이더로 보여집니다.

 

LightPixel.hlsl

/////////////
// GLOBALS //
/////////////
Texture2D shaderTexture;
SamplerState sampleType;

cbuffer LightBuffer
{
    float4 ambientColor;
    float4 diffuseColor;
    float3 lightDirection;
    float specularPower;
    float4 specularColor;
};

//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
    float3 viewDirection : TEXCOORD1;
};

////////////////////
// Pixel Shader //
////////////////////
float4 LightPixelShader(PixelInputType input) : SV_TARGET
{
    float4 textureColor;
    float3 lightDir;
    float lightIntensity;
    float4 color;
    float4 specular;
    
    // 이 텍스처 좌표 위치에서 샘플러를 사용하여 텍스처에서 픽셀 색상을 샘플링 합니다.
    textureColor = shaderTexture.Sample(sampleType, input.tex);
    
    // 모든 픽셀의 기본 출력 색상을 주변 광원 값으로 설정합니다.
    color = ambientColor;

    // Specular Color를 초기화한다.
    specular = float4(0.0f, 0.0f, 0.0f, 0.0f);
    
    // 계산을 위해 빛 방향을 반전시킵니다.
    lightDir = -lightDirection;
    
    // 이 픽셀의 빛의 양을 계산합니다.
    lightIntensity = saturate(dot(input.normal, lightDir));
    
    if(lightIntensity > 0.0f)
    {
        // 확산 색과 광 강도의 양에 따라 최종 확산 색을 결정합니다.
        color += (diffuseColor * lightIntensity);
    
        // 최종 빛의 색상을 채웁니다.
        color = saturate(color);
        
        // 빛의 강도, 법선 벡터 및 빛의 방향에 따라 반사 벡터를 계산합니다.
        float3 reflection = normalize(2 * lightIntensity * input.normal - lightDir);

        // 반사 베터, 시선 방향 및 반사 출력을 기준으로 반사 조명의 양을 결정합니다.
        specular = pow(saturate(dot(reflection, input.viewDirection)), specularPower);
    }
    
    // 텍스처 픽셀과 최종 확산 색을 곱하여 최종 픽셀 색상 결과를 얻습니다.
    color = color * textureColor;
    
    // 출력 색상의 마지막 반사 컴포넌트를 추가합니다.
    color = saturate(color + specular);
    
    return color;
}

 

/////////////
// GLOBALS //
/////////////
Texture2D shaderTexture;
SamplerState sampleType;

cbuffer LightBuffer
{
    float4 ambientColor;
    float4 diffuseColor;
    float3 lightDirection;
    float specularPower;
    float4 specularColor;
};

LightBuffer역시 정반사광의 계산을 위해 SpecularColor(반사색)과 SpecularPower(반사강도) 변수가 추가되었습니다.

 

//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
    float3 viewDirection : TEXCOORD1;
};

PixelInputType 구조체도 보고있는 방향에 대한 변수가 추가되었습니다.

 

    if(lightIntensity > 0.0f)
    {
        // 확산 색과 광 강도의 양에 따라 최종 확산 색을 결정합니다.
        color += (diffuseColor * lightIntensity);
    
        // 최종 빛의 색상을 채웁니다.
        color = saturate(color);
        
        // 빛의 강도, 법선 벡터 및 빛의 방향에 따라 반사 벡터를 계산합니다.
        float3 reflection = normalize(2 * lightIntensity * input.normal - lightDir);

LightPixelShader 안에 있는 수식입니다. 픽셀쉐이더에서의 정반사 벡터는 주어진 빛의 강도가 0보다 큰 경우에만 여기서 계산됩니다. 서두에서 설명했던 것과 같은 방정식을 사용합니다.(float3 reflection 계산)

 

        // 반사 베터, 시선 방향 및 반사 출력을 기준으로 반사 조명의 양을 결정합니다.
        specular = pow(saturate(dot(reflection, input.viewDirection)), specularPower);
    }
    
    // 텍스처 픽셀과 최종 확산 색을 곱하여 최종 픽셀 색상 결과를 얻습니다.
    color = color * textureColor;

정반사광의 총량은 반사벡터와 보는 방향을 이용하여 계산됩니다. 광원ㅇ과 카메라의 방향 사이의 각도가 작을수록 더 많은 정반사광이 비쳐들어올 것입니다. 그런 결과는 SpecularPower 값만큼의 제곱으로 나타내게 합니다. SpecularPower의 값이 작을수록 마지막에 빛이 더 들어옵니다.

 

    // 출력 색상의 마지막 반사 컴포넌트를 추가합니다.
    color = saturate(color + specular);
    
    return color;
}

정반사 효과는 마지막에 넣게 되는데, 이 효과는 마지막에 더해져야 하는 최후의 하이라이트 값이며 그렇게 처리되지 않으면 제대로 표시되지 않을 것입니다.

 

여기서부터는 추가 or 수정된것만 설명하겠습니다.

LightShaderClass

LightShaderClass.h

#pragma once

class LightShaderClass : public AlignedAllocationPolicy<16>
{
private:
	struct MatrixBufferType
	{
		XMMATRIX world;
		XMMATRIX view;
		XMMATRIX projection;
	};

	class CameraBufferType
	{
	public:
		XMFLOAT3 cameraPosition;
		float padding;
	};

	struct LightBufferType
	{
		XMFLOAT4 ambientColor;
		XMFLOAT4 diffuseColor;
		XMFLOAT3 lightDirection;
		float specularPower;
		XMFLOAT4 specularColor;
	};

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

	bool Initialize(ID3D11Device* device, HWND hwnd);
	void ShutDown();
	bool Render(ID3D11DeviceContext* deviceContext, int indexCount, XMMATRIX worldMatrix, XMMATRIX viewMatrix, XMMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, XMFLOAT3 lightDirection, XMFLOAT4 ambientColor, XMFLOAT4 diffuseColor, XMFLOAT3 viewDirection, XMFLOAT4 specularColor, float specularPower);

private:
	bool InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFileName, WCHAR* psFileName);
	void ShutdownShader();
	bool OutputShaderErrorMessage(ID3D10Blob* blob, HWND hwnd, WCHAR* shaderFileName);

	bool SetShaderParameters(ID3D11DeviceContext* deviceContext, XMMATRIX worldMatrix, XMMATRIX viewMatrix, XMMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, XMFLOAT3 lightDirection, XMFLOAT4 ambientColor, XMFLOAT4 diffuseColor, XMFLOAT3 viewDirection, XMFLOAT4 specularColor, float specularPower);
	void RenderShader(ID3D11DeviceContext* deviceContext, int indexCount);

private:
	ID3D11VertexShader* _vertexShader = nullptr;
	ID3D11PixelShader* _pixelShader = nullptr;
	ID3D11InputLayout* _layout = nullptr;
	ID3D11SamplerState* _sampleState = nullptr;
	ID3D11Buffer* _matrixBuffer = nullptr;
	ID3D11Buffer* _cameraBuffer = nullptr;
	ID3D11Buffer* _lightBuffer = nullptr;
};

 

private:
	class CameraBufferType
	{
	public:
		XMFLOAT3 cameraPosition;
		float padding;
	};

	struct LightBufferType
	{
		XMFLOAT4 ambientColor;
		XMFLOAT4 diffuseColor;
		XMFLOAT3 lightDirection;
		float specularPower;
		XMFLOAT4 specularColor;
	};
    
public:
	bool Render(ID3D11DeviceContext* deviceContext, int indexCount, XMMATRIX worldMatrix, XMMATRIX viewMatrix, XMMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, XMFLOAT3 lightDirection, XMFLOAT4 ambientColor, XMFLOAT4 diffuseColor, XMFLOAT3 cameraPosition, XMFLOAT4 specularColor, float specularPower);
private:
	bool SetShaderParameters(ID3D11DeviceContext* deviceContext, XMMATRIX worldMatrix, XMMATRIX viewMatrix, XMMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, XMFLOAT3 lightDirection, XMFLOAT4 ambientColor, XMFLOAT4 diffuseColor, XMFLOAT3 cameraPosition, XMFLOAT4 specularColor, float specularPower);
    
private:
	ID3D11VertexShader* _vertexShader = nullptr;
	ID3D11PixelShader* _pixelShader = nullptr;
	ID3D11InputLayout* _layout = nullptr;
	ID3D11SamplerState* _sampleState = nullptr;
	ID3D11Buffer* _matrixBuffer = nullptr;
	ID3D11Buffer* _cameraBuffer = nullptr;
	ID3D11Buffer* _lightBuffer = nullptr;

 

CameraBufferType이 추가됐습니다. 정점 쉐이더의 카메라 상수 버퍼와 마찬가지로 새로운 카메라 버퍼 구조체를 만듭니다. 또한 CreateBuffer 함수가 실패하는 일이 없도록 구조체에 padding을 넣어 16배수가 되도록 합니다.

 

LightBufferType 구조체는 픽셀 쉐이더의 상수 버퍼와 마찬가지로 반사광의 색상과 강도를 저장하도록 바뀌었습니다. 주의해야 할 것은 기존의 16바이트의 배수 크기를 유지하기 위한 padding을 없애고 그 자리에 specularPower를 넣었습니다. 만약 padding을 넣지않고 lightDirection 바로 밑에 specularColor를 넣었다면 쉐이더가 올바르게 동작하지 않았을 것입니다. 구조체의 크기가 16byte의 배수이지만, 각 변수들이 16byte로 정렬되지 않았기 때문입니다.

 

그리고 정점 쉐이더에서 카메라의 위치를 잡는 데 사용할 카메라 상수 버퍼를 더합니다.

 

LightShaderClass.cpp

#include "stdafx.h"
#include "LightShaderClass.h"

LightShaderClass::LightShaderClass()
{
    _vertexShader = 0;
    _pixelShader = 0;
    _layout = 0;
    _sampleState = 0;
    _matrixBuffer = 0;
    _lightBuffer = 0;
}

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

LightShaderClass::~LightShaderClass()
{
}

bool LightShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
    // 정점 및 픽셀 쉐이더를 초기화합니다.
    return InitializeShader(device, hwnd, L"Shader/LightVertex.hlsl", L"Shader/LightPixel.hlsl");
}

void LightShaderClass::ShutDown()
{
    // 버텍스 및 픽셀 쉐이더와 관련된 객체를 종료합니다.
    ShutdownShader();
}

bool LightShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, 
XMMATRIX worldMatrix, XMMATRIX viewMatrix, XMMATRIX projectionMatrix, 
ID3D11ShaderResourceView* texture, XMFLOAT3 lightDirection, XMFLOAT4 ambientColor, XMFLOAT4 diffuseColor, 
XMFLOAT3 cameraPosition, XMFLOAT4 specularColor, float specularPower)
{
    // 렌더링에 사용할 쉐이더 매개 변수를 설정합니다.
    if (!SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, 
    texture, lightDirection, ambientColor, diffuseColor, cameraPosition, specularColor, specularPower))
    {
        return false;
    }

    // 설정된 버퍼를 쉐이더로 렌더링합니다.
    RenderShader(deviceContext, indexCount);

    return true;
}

bool LightShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFileName, WCHAR* psFileName)
{
    HRESULT result;
    ID3D10Blob* errorMessage = nullptr;

    // 버텍스 쉐이더 코드를 컴파일 합니다.
    ID3D10Blob* vertexShaderBuffer = nullptr;
    result = D3DCompileFromFile(vsFileName, NULL, NULL, "LightVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, &vertexShaderBuffer, &errorMessage);
    if (FAILED(result))
    {
        // 셰이더 컴파일 실패시 오류메시지를 출력합니다.
        if (errorMessage)
        {
            OutputShaderErrorMessage(errorMessage, hwnd, vsFileName);
        }
        // 컴파일 오류가 아니라면 셰이더 파일을 찾을 수 없는 경우입니다.
        else
        {
            MessageBox(hwnd, vsFileName, L"Missing Shader File", MB_OK);
        }

        return false;
    }

    // 픽셀 쉐이더 코드를 컴파일합니다.
    ID3D10Blob* pixelShaderBuffer = nullptr;
    result = D3DCompileFromFile(psFileName, NULL, NULL, "LightPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, &pixelShaderBuffer, &errorMessage);
    if (FAILED(result))
    {
        // 셰이더 컴파일 실패시 오류메시지를 출력합니다.
        if (errorMessage)
        {
            OutputShaderErrorMessage(errorMessage, hwnd, psFileName);
        }
        // 컴파일 오류가 아니라면 셰이더 파일을 찾을 수 없는 경우입니다.
        else
        {
            MessageBox(hwnd, psFileName, L"Missing Shader File", MB_OK);
        }

        return false;
    }

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

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

    // 정점 입력 레이아웃 구조체를 설정합니다.
    // 이 설정은 ModelCalss 와 쉐이더의 VertexType 구조와 일치해야 합니다.
    D3D11_INPUT_ELEMENT_DESC polygonLayout[3];
    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;

    // 레이아웃의 요소 수를 가져옵니다.
    UINT numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

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

    // 더 이상 사용되지 않는 정점 쉐이더 버퍼와 픽셀 쉐이더 버퍼를 해제합니다.
    SAFE_RELEASE(vertexShaderBuffer);
    SAFE_RELEASE(pixelShaderBuffer);

    // 텍스처 샘플러 상태 구조체를 생성 및 설정합니다.
    D3D11_SAMPLER_DESC samplerDesc;
    samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
    samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
    samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
    samplerDesc.MipLODBias = 0.0f;
    samplerDesc.MaxAnisotropy = 1;
    samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
    samplerDesc.BorderColor[0] = 0;
    samplerDesc.BorderColor[1] = 0;
    samplerDesc.BorderColor[2] = 0;
    samplerDesc.BorderColor[3] = 0;
    samplerDesc.MinLOD = 0;
    samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;

    // 텍스처 샘플러 상태를 만듭니다.
    result = device->CreateSamplerState(&samplerDesc, &_sampleState);
    if (FAILED(result))
    {
        return false;
    }

    // 정점 쉐이더에 있는 행렬 상수 버퍼의 구조체를 작성합니다.
    D3D11_BUFFER_DESC matrixBufferDesc;
    matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
    matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType);
    matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    matrixBufferDesc.MiscFlags = 0;
    matrixBufferDesc.StructureByteStride = 0;

    // 상수 버퍼 포인터를 만들어 이 클래스에서 정점 쉐이더 상수 버퍼에 접근할 수 있게 합니다.
    result = device->CreateBuffer(&matrixBufferDesc, NULL, &_matrixBuffer);
    if (FAILED(result))
    {
        return false;
    }

    // 정점 쉐이더에 있는 카메라 버퍼의 구조체를 작성합니다.
    D3D11_BUFFER_DESC cameraBufferDesc;
    cameraBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
    cameraBufferDesc.ByteWidth = sizeof(CameraBufferType);
    cameraBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    cameraBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    cameraBufferDesc.MiscFlags = 0;
    cameraBufferDesc.StructureByteStride = 0;

    result = device->CreateBuffer(&cameraBufferDesc, NULL, &_cameraBuffer);
    if(FAILED(result))
    {
        return false;
    }

    // 필셀 쉐이더에 있는 광원 동적 상수 버펑의 설명을 설정합니다.
    // D3D11_BIND_CONSTANT_BUFFER를 사용하면 ByteWidth가 항상 16배수 여야 하며 그렇지 않으면 CreateBuffer가 실패합니다.
    D3D11_BUFFER_DESC lightBufferDesc;
    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;

    // 이 클래스 내에서 정점 쉐이더 상수 버퍼에 액세스 할 수 있도록 상수 버퍼 포인터를 만듭니다.
    result = device->CreateBuffer(&lightBufferDesc, NULL, &_lightBuffer);
    if (FAILED(result))
    {
        return false;
    }

    return true;
}

void LightShaderClass::ShutdownShader()
{
    SAFE_RELEASE(_lightBuffer);
    SAFE_RELEASE(_matrixBuffer);
    SAFE_RELEASE(_sampleState);
    SAFE_RELEASE(_layout);
    SAFE_RELEASE(_pixelShader);
    SAFE_RELEASE(_vertexShader);
}

bool LightShaderClass::OutputShaderErrorMessage(ID3D10Blob* blob, HWND hwnd, WCHAR* shaderFileName)
{
    // 에러 메시지를 출력창에 표시합니다.
    OutputDebugStringA(reinterpret_cast<const char*>(blob->GetBufferPointer()));

    // 에러 메시지를 반환합니다.
    SAFE_RELEASE(blob);

    // 파일 에러가 있음을 팝업 메시지로 알려줍니다.
    MessageBox(hwnd, L"Error Compiling Shader.", shaderFileName, MB_OK);
    return false;
}

bool LightShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, XMMATRIX worldMatrix, XMMATRIX viewMatrix, XMMATRIX projectionMatrix, 
ID3D11ShaderResourceView* texture, XMFLOAT3 lightDirection, XMFLOAT4 ambientColor, XMFLOAT4 diffuseColor, 
XMFLOAT3 cameraPosition, XMFLOAT4 specularColor, float specularPower)
{
    // 행렬을 transpose하여 쉐이더에서 사용할 수 있게 합니다.
    worldMatrix = XMMatrixTranspose(worldMatrix);
    viewMatrix = XMMatrixTranspose(viewMatrix);
    projectionMatrix = XMMatrixTranspose(projectionMatrix);

    // 상수 버퍼의 내용을 쓸 수 있도록 잠급니다.
    D3D11_MAPPED_SUBRESOURCE mappedResource;
    if (FAILED(deviceContext->Map(_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource)))
    {
        return false;
    }

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

    // 상수 버퍼에 행렬을 복사합니다.
    dataPtr->world = worldMatrix;
    dataPtr->view = viewMatrix;
    dataPtr->projection = projectionMatrix;

    // 상수 버퍼의 점금을 풉니다.
    deviceContext->Unmap(_matrixBuffer, 0);

    // 정점 쉐이더에서의 상수 버퍼의 위치를 설정합니다.
    unsigned int bufferNumber = 0;

    // 마지막으로 정점 쉐이더의 상수 버퍼를 바뀐 값으로 바꿉니다.
    deviceContext->VSSetConstantBuffers(bufferNumber, 1, &_matrixBuffer);

    // 쓸 수 있도록 카메라 상수 버퍼를 잠습니다.
    if (FAILED(deviceContext->Map(_cameraBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource)))
    {
        return false;
    }

    // 상수 버퍼의 데이터에 대한 포인터를 가져옵니다.
    CameraBufferType* camBufferPtr = (CameraBufferType*)mappedResource.pData;

    // 카메라 위치를 상수 버퍼에 복사합니다.
    camBufferPtr->cameraPosition = cameraPosition;
    camBufferPtr->padding = 0.0f;

    // 카메라 상수 버퍼를 잠금 해제 합니다.
    deviceContext->Unmap(_cameraBuffer, 0);

    // 버텍스 쉐이더에서 카메라 상수 버퍼의 위치를 설정합니다.
    bufferNumber = 1;

    // 이제 업데이트 된 값으로 버텍스 쉐이더에서 카메라 상수 버퍼를 설정합니다.
    deviceContext->VSSetConstantBuffers(bufferNumber, 1, &_cameraBuffer);

    // 픽셀 쉐이더에서 쉐이더 텍스처 리소스를 설정합니다.
    deviceContext->PSSetShaderResources(0, 1, &texture);

    // light constant buffer를 잠글 수 있도록 기록한다.
    if (FAILED(deviceContext->Map(_lightBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource)))
    {
        return false;
    }

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

    // 조명 변수를 상수 버퍼에 복사합니다.
    dataPtr2->ambientColor = ambientColor;
    dataPtr2->diffuseColor = diffuseColor;
    dataPtr2->lightDirection = lightDirection;
    dataPtr2->specularColor = specularColor;
    dataPtr2->specularPower = specularPower;

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

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

    // 마지막으로 업데이트 된 값으로 픽셀 쉐이더에서 광원 상수 버퍼를 설정합니다.
    deviceContext->PSSetConstantBuffers(bufferNumber, 1, &_lightBuffer);
    return true;
}

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

    // 삼각형을 그릴 정점 쉐이더와 픽셀 쉐이더를 설정합니다.
    deviceContext->VSSetShader(_vertexShader, NULL, 0);
    deviceContext->PSSetShader(_pixelShader, NULL, 0);

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

    // 삼각형을 그립니다
    deviceContext->DrawIndexed(indexCount, 0, 0);
}
bool LightShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, 
XMMATRIX worldMatrix, XMMATRIX viewMatrix, XMMATRIX projectionMatrix, 
ID3D11ShaderResourceView* texture, XMFLOAT3 lightDirection, XMFLOAT4 ambientColor, XMFLOAT4 diffuseColor, 
XMFLOAT3 cameraPosition, XMFLOAT4 specularColor, float specularPower)
{
    // 렌더링에 사용할 쉐이더 매개 변수를 설정합니다.
    if (!SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, 
    texture, lightDirection, ambientColor, diffuseColor, cameraPosition, specularColor, specularPower))
    {
        return false;
    }

    // 설정된 버퍼를 쉐이더로 렌더링합니다.
    RenderShader(deviceContext, indexCount);

    return true;
}

Render함수에 cameraPosition, sepcularColor, specularPower값을 입력으로 받고 이들을 SetShaderParameter함수를 통해 렌더링이 일어날 때 빛쉐이더에서 사용할 수 있게 합니다.

    // 정점 쉐이더에 있는 카메라 버퍼의 구조체를 작성합니다.
    D3D11_BUFFER_DESC cameraBufferDesc;
    cameraBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
    cameraBufferDesc.ByteWidth = sizeof(CameraBufferType);
    cameraBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    cameraBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    cameraBufferDesc.MiscFlags = 0;
    cameraBufferDesc.StructureByteStride = 0;

    result = device->CreateBuffer(&cameraBufferDesc, NULL, &_cameraBuffer);
    if(FAILED(result))
    {
        return false;
    }

카메라 버퍼의 description을 작성하고 버퍼를 생성합니다. 이것은 정점 쉐이더에 카메라의 위치를 정할 수 있게 해 주는 인터페이스를 제공할 것입니다.

 

    // 쓸 수 있도록 카메라 상수 버퍼를 잠습니다.
    if (FAILED(deviceContext->Map(_cameraBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource)))
    {
        return false;
    }

    // 상수 버퍼의 데이터에 대한 포인터를 가져옵니다.
    CameraBufferType* camBufferPtr = (CameraBufferType*)mappedResource.pData;

    // 카메라 위치를 상수 버퍼에 복사합니다.
    camBufferPtr->cameraPosition = cameraPosition;
    camBufferPtr->padding = 0.0f;

    // 카메라 상수 버퍼를 잠금 해제 합니다.
    deviceContext->Unmap(_cameraBuffer, 0);
    
    // 버텍스 쉐이더에서 카메라 상수 버퍼의 위치를 설정합니다.
    bufferNumber = 1;

SetShaderParameters 함수 입니다. 여기서 카메라 버퍼를 잠그고 카메라 위치값을 입력합니다.
상수 버퍼를 세팅할 때 bufferNumber를 0대신 1로 지정한 것에 주의 바랍니다. 카메라 버퍼는 정점 쉐이더에서 두번째 파라미터이기 때문입니다.

 

LightClass

LightClass.h

#pragma once
class LightClass
{
public:
	LightClass();
	LightClass(const LightClass& other);
	~LightClass();

	void SetAmbientColor(float red, float green, float blue, float alpha);
	void SetDiffuseColor(float red, float green, float blue, float alpha);
	void SetDirection(float x, float y, float z);
	void SetSpecularColor(float red, float green, float blue, float alpha);
	void SetSpecularPower(float power);

	XMFLOAT4 GetAmbient();
	XMFLOAT4 GetDiffuseColor();
	XMFLOAT3 GetDirection();
	XMFLOAT4 GetSpecularColor();
	float GetSpecularPower();

private:
	XMFLOAT4 _ambientColor;
	XMFLOAT4 _diffuseColor;
	XMFLOAT3 _direction;
	XMFLOAT4 _specularColor;
	float _specularPower;
};
public:
	XMFLOAT4 GetSpecularColor();
	float GetSpecularPower();
private:
	XMFLOAT4 _specularColor;
	float _specularPower;

정반사 성분에 관한 함수 및 변수가 추가되었습니다.

LightClass.cpp

#include "stdafx.h"
#include "LightClass.h"

LightClass::LightClass()
{
}

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

LightClass::~LightClass()
{
}

void LightClass::SetAmbientColor(float red, float green, float blue, float alpha)
{
    _ambientColor = XMFLOAT4(red, green, blue, alpha);
}

void LightClass::SetDiffuseColor(float red, float green, float blue, float alpha)
{
    _diffuseColor = XMFLOAT4(red, green, blue, alpha);
}

void LightClass::SetDirection(float x, float y, float z)
{
    _direction = XMFLOAT3(x, y, z);
}

void LightClass::SetSpecularColor(float red, float green, float blue, float alpha)
{
    _specularColor = XMFLOAT4(red, green, blue, alpha);
}

void LightClass::SetSpecularPower(float power)
{
    _specularPower = power;
}

XMFLOAT4 LightClass::GetAmbient()
{
    return _ambientColor;
}

XMFLOAT4 LightClass::GetDiffuseColor()
{
    return _diffuseColor;
}

XMFLOAT3 LightClass::GetDirection()
{
    return _direction;
}

XMFLOAT4 LightClass::GetSpecularColor()
{
    return _specularColor;
}

float LightClass::GetSpecularPower()
{
    return _specularPower;
}

캡슐화....

 

GraphicClass

GraphicClass의 헤더는 수정사항이 없기 때문에 넘어갑니다.

GraphicClass.cpp

#include "stdafx.h"
#include "d3dclass.h"
#include "CameraClass.h"
#include "ModelClass.h"
#include "LightShaderClass.h"
#include "LightClass.h"
#include "GraphicsClass.h"


GraphicsClass::GraphicsClass()
{
}


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


GraphicsClass::~GraphicsClass()
{
}


bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
	// Direct3D 객체 생성
	//_direct3D = (D3DClass*) SAFE_ALIGNED_NEW(sizeof(D3DClass), 16);
	_direct3D = new D3DClass();
	if (!_direct3D)
	{
		return false;
	}

	// Direct3D 객체 초기화
	if (!_direct3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR))
	{
		MessageBox(hwnd, L"Could not initialze Direct3D", L"Error", MB_OK);
		return false;
	}

	// _camera 객체 생성
	_camera = new CameraClass();
	if (_camera == nullptr)
	{
		return false;
	}

	// 카메라 포지션 변경
	_camera->SetPosition(0.0f, 0.0f, -5.0f);

	// _model 객체 생성
	_model = new ModelClass();
	if (_model == nullptr)
	{
		return false;
	}

	// _model 객체 초기화
	if (!_model->Initialize(_direct3D->GetDevice(), "Data/Cube.txt", L"Data/seafloor.dds"))
	{
		MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
		return false;
	}

	// _lightShader 생성
	_lightShader = new LightShaderClass();
	if (_lightShader == nullptr)
	{
		return false;
	}

	// _lightShader 객체 초기화
	if (!_lightShader->Initialize(_direct3D->GetDevice(), hwnd))
	{
		MessageBox(hwnd, L"Could not initialize the color shader object.", L"Error", MB_OK);
		return false;
	}

	// _light 객체 생성
	_light = new LightClass();
	if (_light == nullptr)
	{
		return false;
	}

	//_light->SetAmbientColor(0.01f, 0.01f, 0.01f, 1.0f);
	_light->SetAmbientColor(0.15f, 0.15f, 0.15f, 1.0f);
	_light->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f);
	_light->SetDirection(1.0f, 0.0f, 1.0f);
	_light->SetSpecularColor(1.0f, 1.0f, 1.0f, 1.0f);
	_light->SetSpecularPower(32.0f);

	return true;
}


void GraphicsClass::Shutdown()
{
	if (_lightShader)
	{
		_lightShader->ShutDown();
		SAFE_DELETE(_lightShader);
	}

	if (_light)
	{
		SAFE_DELETE(_light);
	}

	if (_model)
	{
		_model->ShutDown();
		SAFE_DELETE(_model);
	}

	// _camera 객체 반환
	SAFE_DELETE(_camera);

	// Direct3D 객체 반환
	if (_direct3D)
	{
		_direct3D->Shutdown();
		SAFE_DELETE(_direct3D);
	}
}


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

	// 각 프레임의 rotation 변수를 업데이트 합니다.
	rotation += (float)XM_PI * 0.005f;
	if (rotation > 360.0f)
	{
		rotation -= 360.0f;
	}

	// 그래픽 랜더링 처리
	return Render(rotation);
}


bool GraphicsClass::Render(float rotation)
{  
	// 씬을 그리기 위해 버퍼를 지웁니다.
	_direct3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

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

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

	// 삼각형이 회전 할 수 있도록 회전 값으로 월드 행렬을 회전합니다.
	worldMatrix = XMMatrixRotationY(rotation);

	// 모델 버텍스와 인덱스 버퍼를 그래픽 파이프 라인에 배치하여 드로잉을 준비합니다.
	_model->Render(_direct3D->GetDeviceContext());

	// 텍스처 쉐이더를 사용하여 모델을 렌더링 합니다.
	if (!_lightShader->Render(_direct3D->GetDeviceContext(), _model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, 
								_model->GetTexture(), _light->GetDirection(), _light->GetAmbient(), _light->GetDiffuseColor(),
								_camera->GetPosition(), _light->GetSpecularColor(), _light->GetSpecularPower()))
	{
		return false;
	}
	
	// 버퍼의 내용을 화면에 출력합니다.
	_direct3D->EndScene();

	return true;
}
	_light->SetAmbientColor(0.15f, 0.15f, 0.15f, 1.0f);
	_light->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f);
	_light->SetDirection(1.0f, 0.0f, 1.0f);
	_light->SetSpecularColor(1.0f, 1.0f, 1.0f, 1.0f);
	_light->SetSpecularPower(32.0f);

반사 색과 반사강도 값을 입력합니다. 반사색을 흰색으로, 그리고 반사 강도를 32로 합니다. 반사강도 값이 낮을수록 반사광 효과가 커집니다.

	// 텍스처 쉐이더를 사용하여 모델을 렌더링 합니다.
	if (!_lightShader->Render(_direct3D->GetDeviceContext(), _model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, 
								_model->GetTexture(), _light->GetDirection(), _light->GetAmbient(), _light->GetDiffuseColor(),
								_camera->GetPosition(), _light->GetSpecularColor(), _light->GetSpecularPower()))
	{
		return false;
	}

LightShaderClass의 render함수가 카메라 위치, 반사색, 반사 광도를 입력으로 받습니다.

결과화면

정반사광이 보인다. 안보이면 기분탓이다.

'DirectX' 카테고리의 다른 글

11. 글꼴 엔진  (0) 2023.03.06
10. 2D 렌더링  (0) 2023.01.31
8. 주변광  (0) 2022.12.06
7. Maya 2011 모델 불러오기  (2) 2022.12.05
6. 3D 모델 렌더링  (0) 2022.11.26
    'DirectX' 카테고리의 다른 글
    • 11. 글꼴 엔진
    • 10. 2D 렌더링
    • 8. 주변광
    • 7. Maya 2011 모델 불러오기
    튀김족발
    튀김족발

    티스토리툴바