Lighting2

ㅋㅋ·2022년 7월 22일
0

DirectX12강의

목록 보기
23/39

light 클래스

enum class LIGHT_TYPE : uint8
{
	DIRECTIONAL_LIGHT,
	POINT_LIGHT,
	SPOT_LIGHT,
};

빛의 3가지 타입 정의


struct LightColor
{
	Vec4 diffuse;
	Vec4 ambient;
	Vec4 specular;
};

빛의 3 속성을 담는 데이터 정의


struct LightInfo
{
	LightColor color;
	Vec4 position;
	Vec4 direction;
	int32 lightType; // for shader padding
	float range;
	float angle;
	int32 padding;
};

쉐이더에서 계산시 필요한 정보들을 모두 담고있는 데이터

lightType이 uint8이 아니라 int32인 이유는 쉐이더 데이터 패딩 때문

padding 또한 데이터를 16바이트 배수로 만들기 위한 값



struct LightParams
{
	uint32		lightCount;
	Vec3		padding;
	LightInfo	lights[50];
}; 

쉐이더에는 한번에 최대 50개의 빛 정보를 넘겨줌

프레임마다 빛에 대한 정보를 한번 업데이트 해주면

객체들의 transform이나 material처럼 매번 업데이트할 필요가 없기 때문

=>

CBV를 따로 만들어 프레임마다 한번만 업로드하는 버퍼로 이용


class Light : public Component
{
public:
	...

	virtual void FinalUpdate() override;

public:
	const LightInfo& GetLightInfo() { return _lightInfo; }

	...

private:
	LightInfo _lightInfo = {};
};

light 클래스 정의

FinalUpdate 함수 실행 시 LightInfo의 position에 광원의 월드 좌표를 넣어줌


GPU b0 버퍼를 광원 정보를 넘겨줄 때 사용하기 위한

root signature 수정

void RootSignature::CreateRootSignature()
{
	CD3DX12_DESCRIPTOR_RANGE ranges[] =
	{
		CD3DX12_DESCRIPTOR_RANGE(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, CBV_REGISTER_COUNT - 1, 1), // b1~b4
		CD3DX12_DESCRIPTOR_RANGE(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, SRV_REGISTER_COUNT, 0), // t0~t4
	};

	CD3DX12_ROOT_PARAMETER param[2]{};
	param[0].InitAsConstantBufferView(static_cast<uint32>(CBV_REGISTER::b0));
	param[1].InitAsDescriptorTable(_countof(ranges), ranges);

	...
}

InitAsConstantBufferView로 b0는 CBV로 사용할 것이라고 서명


descriptor heap 수정

void TableDescriptorHeap::Init(uint32 count)
{
	_groupCount = count;

	D3D12_DESCRIPTOR_HEAP_DESC desc = {};
	desc.NumDescriptors = count * (REGISTER_COUNT - 1);
	
    ...
    
	_groupSize = _handleSize * (REGISTER_COUNT - 1);
}

b0를 제외한 레지스터 개수로 수정

D3D12_CPU_DESCRIPTOR_HANDLE TableDescriptorHeap::GetCPUHandle(uint8 reg)
{
	...

	D3D12_CPU_DESCRIPTOR_HANDLE handle = _descHeap->GetCPUDescriptorHandleForHeapStart();
	handle.ptr += _currentGroupIndex * _groupSize;
	handle.ptr += (reg - 1) * _handleSize;
	return handle;
}

b0는 table descriptor에서 빠졌기 때문에

테이블에서 1번 레지스터를 찾을 때 b1을 반환할 수 있도록 수정

void TableDescriptorHeap::CommitTable()
{
	...
    
	CMD_LIST->SetGraphicsRootDescriptorTable(1, handle);

	...
}

table index가 1이기 때문에 1로 수정


void Engine::Init(const WindowInfo& info)
{
	...

	CreateConstantBuffer(CBV_REGISTER::b0, sizeof(LightParams), 1);
	CreateConstantBuffer(CBV_REGISTER::b1, sizeof(TransformParams), 256);
	CreateConstantBuffer(CBV_REGISTER::b2, sizeof(MaterialParams), 256);

	...
}

b0의 버퍼는 1개면 된다.


constant buffer

enum class CONSTANT_BUFFER_TYPE : uint8
{
	GLOBAL,
	TRANSFORM,
	MATERIAL,
	END,
};

광원 정보를 넣을 버퍼의 타입을 GLOBAL로 정의

void ConstantBuffer::SetGlobalData(void* buffer, uint32 size)
{
	assert(_elementSize == ((size + 255) & ~255));
	::memcpy(&_mappedBuffer[0], buffer, size);
	CMD_LIST->SetGraphicsRootConstantBufferView(0, GetGpuVirtualAddress(0));
}

CBV 버퍼는 1개만 있기 때문에 항상 0번을 사용하도록 구현


scene 수정

void Scene::Redner()
{
	PushLightData();

	for (auto& gameObject : _gameObjects)
	{
		...

		gameObject->GetCamera()->Render();
	}
}

기존 에서 렌더하던 부분을 scene에서 하도록 수정

void Scene::PushLightData()
{
	...

	for (auto& gameObject : _gameObjects)
	{
		...

		const LightInfo& lightInfo = gameObject->GetLight()->GetLightInfo();

		lightParams.lights[lightParams.lightCount] = lightInfo;
		lightParams.lightCount++;
	}

	CONST_BUFFER(CONSTANT_BUFFER_TYPE::GLOBAL)->SetGlobalData(&lightParams, sizeof(lightParams));
}

light 컴포넌트를 들고있는 오브젝트를 찾고

해당 오브젝트들을 lightParams에 넣어 GPU로 업로드 요청


transform 수정

struct TransformParams
{
	Matrix matWorld;
	Matrix matView;
	Matrix matProjection;
	Matrix matWV;
	Matrix matWVP;
};
void Transform::PushData()
{
	...
    
	transformParams.matWorld = _matWorld;
	transformParams.matView = Camera::S_MatView;
	transformParams.matProjection = Camera::S_MatProjection;
	transformParams.matWV = _matWorld * Camera::S_MatView;
	transformParams.matWVP = _matWorld * Camera::S_MatView * Camera::S_MatProjection;

	...
}

쉐이더에서 사용할만한 행렬들을 모두 전달하도록 수정


쉐이더 파일 수정

쉐이더 파일이 길어짐에 따라 여러개로 분리 후 include 하여 사용

쉐이더 헤더 파일은 pragma를 전처리 할 수 없을 수 있음

=>

#ifndef _DEFAULT_HLSLI_
#define _DEFAULT_HLSLI_

...

#endif

ifndef와 define를 사용하여 헤더 중복 검사


struct LightColor
{
    float4      diffuse;
    float4      ambient;
    float4      specular;
};

struct LightInfo
{
    LightColor  color;
    float4	    position;
    float4	    direction;
    int		    lightType;
    float	    range;
    float	    angle;
    int  	    padding;
};

cbuffer GLOBAL_PARAMS : register(b0)
{
    int         g_lightCount;
    float3      g_lightPadding;
    LightInfo   g_light[50];
}

cbuffer TRANSFORM_PARAMS : register(b1)
{
    row_major matrix g_matWorld;
    row_major matrix g_matView;
    row_major matrix g_matProjection;
    row_major matrix g_matWV;
    row_major matrix g_matWVP;
};

light 관련 데이터들을 정의


constant buffer view data rules

//  2 x 16byte elements
cbuffer IE
{
    float4 Val1;
    float2 Val2;  // starts a new vector
    float2 Val3;
};

//  3 x 16byte elements
cbuffer IE
{
    float2 Val1;
    float4 Val2;  // starts a new vector
    float2 Val3;  // starts a new vector
};

쉐이더에서는 데이터 크기가 16byte를 넘을 수 없으며 이보다 작을 시 packing 하여 처리

packing 시 16byte boundary를 넘을 수 없음

=>

쉐이더 파일에서 사용할 데이터를 설계할 때 이를 고려 해야 함


struct VS_OUT
{
    float4 pos : SV_Position;
    float2 uv : TEXCOORD;
    float3 viewPos : POSITION;
    float3 viewNormal : NORMAL;
};

vertex 쉐이더에서 view 좌표계의 position과 normal 벡터를 반환해주도록 구현

빛 계산 시 position과 normal 벡터 필요

=>

world 좌표계나 view 좌표계 하나를 정하여 계산

=>

view 좌표계 이용 시 카메라의 position이 필요 없게 됨 (카메라 좌표가 원점)


float4 PS_Main(VS_OUT input) : SV_Target
{
    ...

    LightColor totalColor = (LightColor)0.f;

    for (int i = 0; i < g_lightCount; ++i)
    {
         LightColor color = CalculateLightColor(i, input.viewNormal, input.viewPos);
         totalColor.diffuse += color.diffuse;
         totalColor.ambient += color.ambient;
         totalColor.specular += color.specular;
    }

    color.xyz = (totalColor.diffuse.xyz * color.xyz)
        + totalColor.ambient.xyz * color.xyz
        + totalColor.specular.xyz;

     return color;
}

빛 연산은 정해진 공식이 없고 빠르게 계산하면서 진짜처럼 보이기만 하면 됨(?)

specular는 현재 픽셀의 색과 관련이 없이 더해지게 됨


directional light

LightColor CalculateLightColor(int lightIndex, float3 viewNormal, float3 viewPos)
{
    ...

    float diffuseRatio = 0.f;
    float specularRatio = 0.f;
    float distanceRatio = 1.f;

    if (g_light[lightIndex].lightType == 0)
    {
        viewLightDir = normalize(mul(float4(g_light[lightIndex].direction.xyz, 0.f), g_matView).xyz);
        diffuseRatio = saturate(dot(-viewLightDir, viewNormal));
    }
    
    ...

    float3 reflectionDir = normalize(viewLightDir + 2 * (saturate(dot(-viewLightDir, viewNormal)) * viewNormal));
    float3 eyeDir = normalize(viewPos);
    specularRatio = saturate(dot(-eyeDir, reflectionDir));
    specularRatio = pow(specularRatio, 2);

    color.diffuse = g_light[lightIndex].color.diffuse * diffuseRatio * distanceRatio;
    color.ambient = g_light[lightIndex].color.ambient * distanceRatio;
    color.specular = g_light[lightIndex].color.specular * specularRatio * distanceRatio;

    return color;
}

viewLightDir : 빛의 방향 벡터

diffuseRatio :
빛의 방향을 -를 곱하여 반대로 향하게 한 후, (물체에서 나가는 방향으로)

normal 벡터와 내적하여 cosθcos\theta를 구함

saturate 함수로 0과 1사이의 값이 나오도록 clamp

reflectionDir :
diffuseRatio와 동일한 계산을 하여 cosθcos\theta를 구함 = normal 벡터로 투영된 벡터

빛 벡터에 normal 벡터 방향으로 곱한 투영 벡터를 2번 더 함 = 반사된 벡터

이를 normalize하여 방향만 추출

eyeDir :
view 좌표계 이용으로 카메라 좌표가 원점이므로

물체의 position을 normalize하여 원점에서 물체로의 방향 벡터를 구함

specularRatio :
eyeDir 방향 벡터에 - 값을 곱하여 물체에서 나가는 방향으로 만든 후,

반사된 빛의 방향 벡터와 내적하고 이 cosθcos\theta가 0과 1사이의 값이 나오도록 clamp

pow 함수를 사용하는 이유:

specular가 적용되는 면적을 줄이고 빛의 세기를 강하게 하도록 만듬


point Light

LightColor CalculateLightColor(int lightIndex, float3 viewNormal, float3 viewPos)
{
    ...

    else if (g_light[lightIndex].lightType == 1)
    {
        float3 viewLightPos = mul(float4(g_light[lightIndex].position.xyz, 1.f), g_matView).xyz;
        viewLightDir = normalize(viewPos - viewLightPos);
        diffuseRatio = saturate(dot(-viewLightDir, viewNormal));

        float dist = distance(viewPos, viewLightPos);
        if (g_light[lightIndex].range == 0.f)
            distanceRatio = 0.f;
        else
            distanceRatio = saturate(1.f - pow(dist / g_light[lightIndex].range, 2));
    }
    
    ...
}

광원에서 물체로 향하는 방향 벡터를 가지고 diffuse를 계산

거리에 따라 빛이 약해지도록 distanceRatio를 계산

pow 함수를 사용하여 멀어질수록 급격히 세기가 약해지도록 구현


spot light

LightColor CalculateLightColor(int lightIndex, float3 viewNormal, float3 viewPos)
{
		...
        
        
		float3 viewLightPos = mul(float4(g_light[lightIndex].position.xyz, 1.f), g_matView).xyz;
        viewLightDir = normalize(viewPos - viewLightPos);
        diffuseRatio = saturate(dot(-viewLightDir, viewNormal));

        if (g_light[lightIndex].range == 0.f)
            distanceRatio = 0.f;
        else
        {
            float halfAngle = g_light[lightIndex].angle / 2;

            float3 viewLightVec = viewPos - viewLightPos;
            float3 viewCenterLightDir = normalize(mul(float4(g_light[lightIndex].direction.xyz, 0.f), g_matView).xyz);

            float centerDist = dot(viewLightVec, viewCenterLightDir);
            distanceRatio = saturate(1.f - centerDist / g_light[lightIndex].range);

            float lightAngle = acos(dot(normalize(viewLightVec), viewCenterLightDir));

            if (centerDist < 0.f || centerDist > g_light[lightIndex].range)
                distanceRatio = 0.f;
            else if (lightAngle > halfAngle)
                distanceRatio = 0.f;
            else
                distanceRatio = saturate(1.f - pow(centerDist / g_light[lightIndex].range, 2));
        }
        
        ...
        
}

viewLightVec : 광원에서 물체로 가는 벡터

viewCenterLightDir : 광원의 방향 벡터

centerDist :
광원에서 물체로 가는 벡터를 광원 방향으로 투영하여 그 크기를 구함

lightAngle :
광원에서 물체로 가는 방향 벡터와 광원의 방향 벡터를 내적 후 acos 함수로 각도 θ\theta를 구함

=>

거리와 각도에 따라 distanceRatio를 설정


if (FAILED(::D3DCompileFromFile(path.c_str(), nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE
		, name.c_str(), version.c_str(), compileFlag, 0, &blob, &_errBlob)))
{
	::MessageBoxA(nullptr, "Shader Create Failed !", nullptr, MB_OK)
 }

쉐이더 create 가 실패하면 보통 쉐이더 파일의 문법적 오류일 확률이 높음

0개의 댓글