본문 바로가기

DirectX11 3D/기본 문법

<DirectX11 3D> 78 - PointLighting & HLSL FlowControl

 

필요한 개념


Lighting의 종류
Directional Light(전역 조명) : 우리가 써왔던거(우리가 함수 정의한 ComputeLight()함수가 이역할)

지역 조명들
PointLight : 제일 쉬움, 구 안에 닿는 면적에 빛이 들어가는 형태(위치, 빛의 범위, 빛의 색상, 빛의 강도)
SpotLight : 주인공을 비추는 애(공연에서는), 비출거리(Range), 각도, 색상, 강도
AreaLight : 빛을 어느구역에서 깍어낼꺼냐
CapsuleLight : 광선검만들때 사용

지역 조명은 빛을 어떻게 감쇄시키느냐에 따라 지역 조명의 모양이 결정된다.

모델에서 계산된 색(ComputeLight())을 PointLight로 한번더 계산한다.
그거에 따라 반사되고 외곽선 색이 바뀔 수 있다.
그래서 아래 4개의 색상을 다 가지고 있는다.

struct PointLight
{
    float4 Ambient; // 주변광을 줄거고 얘에 따라 주변광을 줄일수 있도록
    float4 Diffuse; // 주 빛의 색
    float4 Specular; //  반사 색
    float4 Emssive; // 얘에 의해 주변광이 표시될 색
   
    // 포인트 라이팅도 물체의 색에 영향을 주므로 포인트 라이팅도 매터리얼을 가지고 있다.
};

그림 4 - 1



포인트 라이트는 구모양이다.
<그림 4-1참조>

정점들간의 거리로 판단해서 PointLight가 들어갈지,
거리에 따라 얼마만큼의 강도로 들어갈지 결정 가능

1. 거리로 빛을 잘라냄
2. 조명에서 멀어질 수록 강도를 점점 줄여감

 

Light를 관리할 클래스(싱글턴으로 처리)를 만들고
Perframe에서 매 프레임마다 라이트 정보를 밀어줄 것이다.

Light클래스에서 AddPointLight()로 포인트 라이트를 추가할 수 있다.

싱글톤 : 프로그램 전역에서 객체를 n개 이하로 제한하여 생성해서 사용할 때 유용하다.

싱글톤은 관리가 힘듬, 어디에서 값이 바뀌었는지 추적하기 힘듬
생성,소멸은 명확히 해주기 위해 Create(), Delete()함수를 만들어 놓는 것이 좋다.
assert()로 생성되었는지 체크하는것도 좋다.

Window.cpp 프로그램 시작될때 싱글턴을 Create()하고 종료 메세지 들어오고 메세지 루프를 빠져나오고 종료될때 Delete()를 해줌


모닥불이나 캐릭터 비출때 많이함

Ambient가 전체적인 색을 결정함


HLSL FlowControl(HLSL 제어문)

기본값은 Branch(if, else)이며 자동으로 반대의 값을 만들어 실행하고 그 중 조건에 맞는 값을 선택하지만
Flatten은 결과를 합치게 되며 if만 쓸경우 else를 만들지 않음

if 문

아무것도 안쓰면 기본 [branch]가 됨


branch : 아무것도 안쓰고 if만 쓰면 [branch]가 되어서 ShaderCompiler가 자동으로 else구문을 추가한다. 그 후 if, else문을 실행하고(평가가 2개 이루어짐) 둘 중에 값을 하나 선택한다.
flatten : [flatten]을 쓰면 flatten은 합치다라는 의미라서 if만 쓴다면 else구문을 만들지 않고 if만 사용한다. 즉, flatten을 쓸때 else를 쓰면 안된다.(if, else문을 둘다 쓰면 결과를 합쳐버림 if에 a = 10주고, else에 a = 20주면 최종적으로 결과를 합쳐서 a가 10이될지, 20이 될지, 합쳐서 30이 될지 아무도 모름(상황에 따라 달라짐)


C에서는 if문만 있다면 if문만 실행하지만, Shader는 조건해서 분기해서 하나만 실행하는거 보다 전부 실행하는게 빠를때가 있어서 그렇다.(GPU는 빠르게 내려오는게 빠름)

반복문(for, while, do)

아무것도 안쓰면 기본 [loop]가 됨

loop : 참 거짓을 생각해서(평가값), 개당 2개가됨(조건이 10개까지다(10번 반복이다라면), 평가값(10개) * 2 = 20개가 됨)
unroll(x) : unroll을 쓰면 loop가 사라짐, x는 제한할 상수(몇개까지 복사제한), x값만큼만 복사함, flatten처럼 다른 조건을 만들지 않음, 안그러면 loop같이 동작됨,
unroll 사용시 주의할게 예를 들어 x가 256이고, 조건이 10개라면, loop시에는 20개만 복사가 되지만, unroll시에는 x의 개수만큼(256)만큼 복사됨,
그래서 주의해서 사용해야한다. x와 조건값을 동일하게 해주는게 좋다.

* unroll이 비효율적일 때
[unroll(MAX_POINT_LIGHTS)] -> x가 256
for (uint i = 0; i < PointLightCount; i++) -> 조건값 PointLightCount가 10개라면
{
}
loop는 20개인 반면, unroll은 256개가 복사된다. 이런 경우는 비효율적(직접 실행해보니 셰이더 컴파일이 안될정도로 렉걸림)

* unroll이 효율적일 때
[unroll(MAX_POINT_LIGHTS)] -> x가 256
for (uint i = 0; i < MAX_POINT_LIGHTS; i++)
{
}
조건값과 x가 동일하다면 효율적이다.
why? unroll시에는 256개가 복사되고, loop시에는 2배인 512개가 복사된다.


windowkey + R하면 실행창 나옴
여기다 notepad, notepad++이라고 치면 각각의 프로그램 나옴
Notepad++ 유용함(두 파일 코드상의 비교도 가능(플러그인 -> Compare -> Compare)


 

 

 

 

Lighting.h


더보기
#pragma once

#define MAX_POINT_LIGHTS 256
struct PointLight
{
    Color Ambient; // 주변광을 줄거고 얘에 따라 주변광을 줄일수 있도록
    Color Diffuse; // 주 빛의 색
    Color Specular; //  반사 색
    Color Emissive; // 얘에 의해 주변광이 표시될 색

    // 포인트 라이팅도 물체의 색에 영향을 주므로 포인트 라이팅도 매터리얼을 가지고 있다.

    Vector3 Position; // 위치
    float Range; // 범위

    float Intensity; // 강도
    Vector3 Padding;

};


class Lighting
{
public:
	static Lighting* Get();
	static void Create();
	static void Delete();

private:
	Lighting();
	~Lighting();

public:
	UINT PointLightCount() { return pointLightCount; }
	// 포인트라이트 전체를 복사해주고, UINT로는 개수 리턴
	UINT PointLights(OUT PointLight* lights);
	// 추가해주는것
	void AddPointLight(PointLight& light);
	// 하나만 가져올때
	PointLight& GetPointLight(UINT index);

private:
	static Lighting* instance;

private:
	UINT pointLightCount = 0;
	PointLight pointLights[MAX_POINT_LIGHTS];
};

 

 

 

 

 

 

 

Lighting.cpp


더보기
#include "Framework.h"
#include "Lighting.h"

Lighting* Lighting::instance = NULL;

Lighting* Lighting::Get()
{
	assert(instance != NULL);

	return instance;
}

void Lighting::Create()
{
	assert(instance == NULL);

	instance = new Lighting();
}

void Lighting::Delete()
{
	SafeDelete(instance);
}

Lighting::Lighting()
{

}

Lighting::~Lighting()
{

}

UINT Lighting::PointLights(OUT PointLight* lights)
{
	memcpy(lights, pointLights, sizeof(PointLight) * pointLightCount);

	return pointLightCount;
}

void Lighting::AddPointLight(PointLight& light)
{
	pointLights[pointLightCount] = light;
	pointLightCount++;
}

PointLight& Lighting::GetPointLight(UINT index)
{
	return pointLights[index];
}

 

 

 

 

 

 

 

PerFrame.h 추가된 내용


더보기
class PerFrame
{
...
private:
	struct PointLightDesc
	{
		UINT Count = 0;
		float Padding[3];

		PointLight Lights[MAX_POINT_LIGHTS];
	} pointLightDesc;

private:
	ConstantBuffer* pointLightBuffer;
	ID3DX11EffectConstantBuffer* sPointLightBuffer;
}

 

 

 

 

 

 

 

PerFrame.cpp 추가된 내용


더보기
#include "Framework.h"
#include "PerFrame.h"

PerFrame::PerFrame(Shader* shader)
	: shader(shader)
{
	...
	pointLightBuffer = new ConstantBuffer(&pointLightDesc, sizeof(PointLightDesc));
	sPointLightBuffer = shader->AsConstantBuffer("CB_PointLights");
}

PerFrame::~PerFrame()
{
	...
	SafeDelete(pointLightBuffer);
}

void PerFrame::Update()
{
	...
	// Lights에도 복사해주고, count 개수도 저장함
	pointLightDesc.Count = Lighting::Get()->PointLights(pointLightDesc.Lights);
}

void PerFrame::Render()
{	
	...
	pointLightBuffer->Render();
	sPointLightBuffer->SetConstantBuffer(pointLightBuffer->Buffer());
}

 

 

 

00_Lighting.fx 추가된 내용


더보기
...

////////////////////////////////////////////////////////////////////////////

// 한 맵에 256넘어가는것은 초대형 맵을 제외해서는 없다.
#define MAX_POINT_LIGHTS 256
struct PointLight
{
    float4 Ambient; // 주변광을 줄거고 얘에 따라 주변광을 줄일수 있도록
    float4 Diffuse; // 주 빛의 색
    float4 Specular; //  반사 색
    float4 Emissive; // 얘에 의해 주변광이 표시될 색
   
    // 포인트 라이팅도 물체의 색에 영향을 주므로 포인트 라이팅도 매터리얼을 가지고 있다.
    
    float3 Position; // 위치
    float Range; // 범위
    
    float Intensity; // 강도
    float3 Padding;
    
    // Padding을 채우는 이유? 배열로 버퍼의 값을 받기 위해 패딩을 사용, 만약 없으면 데이터가 당겨져 제대로 받을 수 없다.
};

cbuffer CB_PointLights
{
    uint PointLightCount; // 라이트의 개수(들어온 갯수만)
    float3 CB_PointLights_Padding; // 16바이트로 끊기 위해
    
    PointLight PointLights[MAX_POINT_LIGHTS];
};

void ComputePointLight(inout MaterialDesc output, float3 normal, float3 wPosition)
{
    output = MakeMaterial();
    // 값을 누적 시킴
    MaterialDesc result = MakeMaterial();
    
    // 1. 거리로 빛을 잘라냄
    for (uint i = 0; i < PointLightCount; i++)
    {
        // 이렇게 빼면 wPositoin이 Position을 바라보는 것도 되지만, 둘이 뺸 길이이기도 하다.
        // 포인트 라이트 구의 중심 좌표와 정점의 월드 거리를 뺀다.(정점이 구 안에 들어오는지 확인하는작업)
        float3 light = PointLights[i].Position - wPosition;
        // length로 해서 거리로 만든다.
        float dist = length(light);
        
        
        // 정점과 포인트 라이트의 중심점의 차가 조명의 거리보다 크다면 굳이 계산할 필요가 없다.
        [flatten]
        if(dist > PointLights[i].Range)
            continue;
        
        
        light /= dist; // 뱡향벡터 / 크기 -> Normalize(정규화) 하겠다는 의미
        
        
        // 밑에는 기존것과 동일함 -> ComputeLight()
        
        // 이 부분도 지역조명으로 바꿔준다.(GlobalLight -> PointLights[i])
        result.Ambient = PointLights[i].Ambient * Material.Ambient;
        
        // 현재는 light가 조명의 방향이다.(wPosition이 포인트 라이트를 바라보니까)
        float NdotL = dot(light, normalize(normal));
        float3 E = normalize(ViewPosition() - wPosition);

    
    
        [flatten]
        if (NdotL > 0.0f)
        {
            // 조명색이 모델하고 결합이 되어야 하니까 조명 색을 곱해준다.
            result.Diffuse = Material.Diffuse * NdotL * PointLights[i].Diffuse;
        
        

        [flatten]
            if (Material.Specular.a > 0.0f)
            {
                // 여기선 light를 뒤집지 않아도 상관 없음
                float3 R = normalize(reflect(-light, normal));
                float RdotE = saturate(dot(R, E));
                
                float specular = pow(RdotE, Material.Specular.a);
                result.Specular = Material.Specular * specular * PointLights[i].Specular;
            }
        }
        
        [flatten]
        if (Material.Emissive.a > 0.0f)
        {
            float NdotE = dot(E, normalize(normal));
            float emissive = smoothstep(1.0f - Material.Emissive.a, 1.0f, 1.0f - saturate(NdotE));
            result.Emissive = Material.Emissive * emissive * PointLights[i].Emissive;
        }
        
        
        // 조명의 강도를 계산
        // 2. 조명에서 멀어질 수록 강도를 점점 줄여감
        // 길이와 범위를 나눠서(길이에 비례하게 만듬) 0 ~ 1까지로 만들어준 뒤 역수로 만들어준다.
        // 밑 식에서 제곱으로 쓸라고 나눈것이었다.(1.0으로 안나누면, 곱하면 값이 너무 커져서 그랬던 것임(안하면 다시 값을 나눠서 작게 만들어야해서))
        float temp = 1.0f / saturate(dist / PointLights[i].Range);
        // Specular에서 제곱으로 만들시에는 구가 된다.
        // intensity(빛의 세기)는 0 ~ 1까지로 들어온다. 계산해보면 역수가된다. temp때문에 그래서 intensity 값이 커지면 커질수록 강도가 적어짐, 그래서 원래는 값을 뒤집어 줘야 하나(테스트하고 값을 뒤집음)
        // 그래서 값을 뒤집기 위해(1.0f / max(1.0f - PointLights[i].Intensity, 1e-8f)) 해줌
        // 1e-8은 0보다 큰수로 해줌(0이면 완전히 끝나버려서)
        // att(감쇠의 의미)
        float att = temp * temp * (1.0f / max(1.0f - PointLights[i].Intensity, 1e-8f));
        
        
        // result를 계속 누적시킬 것이다.
        // Ambient는 temp한 이유가 att 곱하며 주변광에 diffuse색까지 합쳐져서 너무 밝아짐 그래서 특별한 의미없이 한 것이다.
        // Ambient : 사방에서 들어오는 동일한 강도의 빛(색)
        // att는 구에서 멀어질 수록 값을 떨어트리려고 하는것이다.
        // 그래서 att를 뺴주고 동일한 값을 사용할 수 있게 temp만 곱해줌
        output.Ambient += result.Ambient * temp;
        output.Diffuse += result.Diffuse * att;
        output.Specular += result.Specular * att;
        output.Emissive += result.Emissive * att;
    }

}

 

 

 

 

 

 

 

 

 

 

 

 

78_PointLighting.fx


더보기
#include "00_Global.fx"
#include "00_Light.fx"
#include "00_Render.fx"


float4 PS(MeshOutput input) : SV_Target
{
    Texture(Material.Diffuse, DiffuseMap, input.Uv);
    Texture(Material.Specular, SpecularMap, input.Uv);
    
    MaterialDesc output = MakeMaterial();
    MaterialDesc result = MakeMaterial();
    
    ComputeLight(output, input.Normal, input.wPosition);
    // output을 result에 더함
    AddMaterial(result, output);
    
    ComputePointLight(output, input.Normal, input.wPosition);
    AddMaterial(result, output);
    
    return float4(MaterialToColor(result), 1.0f);
}

technique11 T0
{
    P_VP(P0, VS_Mesh, PS)
    P_VP(P1, VS_Model, PS)
    P_VP(P2, VS_Animation, PS)
}

 

 

 

 

결과