본문 바로가기
DirectX/[Inflearn_rookiss] Part2: DirectX12

16. Lighting 구현

by 헛둘이 2023. 1. 26.

Light 클래스 구현

 

- LightColor

-> diffuse, ambient, specular 세 요소를 가진 구조체

 

- LightInfo

-> LightColor, 위치, 방향, 라이트 타입, 범위, 각도를 가진 구조체

 

- LightParams

-> 라이트의 개수, 패딩, LightInfo의 배열을 가진 구조체

-> 이 구조체는 오브젝트가 렌더링되기 전에 씬의 오브젝트를 다 긁어와서

-> 라이트를 다 LightInfo의 배열의 요소로 집어넣은 후 한번에 쉐이더에 전달됨

 

그러므로 쉐이더 코드에서 이 LightParams를 받아주게 되는데,

그러면 그 코드에서도 같은 형식을 맞춰줘야 함

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;
    int g_lightPadding;
    LightInfo g_light[50];
}

- b0 레지스터는 전역에서 사용할 수 있게 테이블에서 빼 주는 선행작업이 필요함

 

padding 멤버는 뭐지?

- hlsl에서는 데이터 한 슬롯당 16바이트로 구성되어 있기 때문에 어떤 바이트가 16바이트 바운더리를 넘어가면 안됨

- 쉐이더에서 이 값에 접근할 때 16바이트 단위로 접근하기 때문에 그에 맞게 패딩을 맞춰줘야 한다

struct Sample
{
    float2 f_1;
    float4 f_2;
    float2 f_3;
}

- 위와 같은 코드에서 float2는 8바이트를 차지하는데,

- 8바이트와 다음 멤버인 float4(16바이트)가 한 슬롯에 놓일 수 없으므로 

- 이 그림에선 float4가 아래 슬롯으로 내려가게 되면서 Sample 구조체는 총 3슬롯을 차지하게 된다.

- 그렇기 때문에 이 데이터 크기을 맞춰주기 위해 padding을 사용하는 것

 


쉐이더에서 컬러값 계산

- 빛에 대한 계산은 픽셀 쉐이더에서 진행하게 된다.

float4 PS_Main(VS_OUT input) : SV_Target
{
    // 버텍스쉐이더에서 처리가 끝난 후 이 픽셀쉐이더로 넘어오게 된다.
    //float4 color = g_tex_0.Sample(g_sam_0, input.uv);
    
    float4 color = float4(1.f, 0.f, 0.f, 1.f);
    
    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;
}

- for문을 돌면서 lightCount만큼 계산하게 된다.

- lightCount는 빛 컴포넌트의 개수를 뜻함

 

void Scene::PushLightData()
{
	LightParams lightParams = {};

	for (auto& gameObject : _gameObjects)
	{
		if (nullptr == gameObject->GetLight())
			continue;

		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 배열에 채워넣고,

- 그 카운트를 세서 같이 쉐이더로 보내준다.

 

LightColor CalculateLightColor(int lightIndex, float3 viewNormal, float3 viewPos)
{
    LightColor color = (LightColor) 0.f;
    
    float3 viewLightDir = (float3) 0.f;
    
    float diffuseRatio = 0.f;
    float specularRatio = 0.f;
    float distanceRatio = 1.f;
    
    if (g_light[lightIndex].lightType == 0)
    {
        // Directional Light
        // 뷰행렬과 라이트의 direction을 곱해줘서 뷰좌표계로 변경한 후 노멀라이즈
        // 뷰 좌표계에서의 방향을 구해주는 것 = 빛의 방향을 그냥 뷰좌표계로 옮겨오는것
        
        viewLightDir = normalize(mul(float4(g_light[lightIndex].direction.xyz, 0.f), g_matView).xyz);
        diffuseRatio = saturate(dot(-viewLightDir, viewNormal)); // 빛의 -방향과 노멀벡터를 내적함으로써 costheta가 나온다.
        // saturate를 쓰면 dot의 결과값이 0에서 1사이에 있게끔 해준다.
        // 빛이 아래서 올경우 음수가 나오면 0이 된다.

    }
    
    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);
        // 픽셀 좌표에서 광원 중심의 좌표를 빼줌으로써 광원에서 픽셀을 향하는 벡터를 구한다.
       
        // 그 방향 벡터와 노멀벡터를 내적해서 cos값을 구한다
        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));    
        // 멀어질수록 희미해지는 효과
        // pow를 제곱해주는 이유는 아래와 같음
    }
    
    else
    {
        
        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.f;
            
            // 광원으로부터 픽셀을 가리키는 벡터
            float3 viewLightVec = viewPos - viewLightPos;
            //빛이 나가는 방향을 뷰좌표계기준으로 변경해주는 것
            float3 viewCenterLightDir = normalize(mul(float4(g_light[lightIndex].direction.xyz, 0.f), g_matView).xyz);
            
            // 광원의 중심으로 나가는 벡터와 픽셀을 내적해서 cos값으로 범위 체크
            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));
        }
    }
                                                                   // 거리                             방향
    float3 reflectionDir = normalize(viewLightDir + 2 * (saturate(dot(-viewLightDir, viewNormal)) * viewNormal));
    float3 eyeDir = normalize(viewPos); // 뷰포트기준 좌표기 떄문에 좌표 자체가 카메라에서 물체에 대한 offset 그러므로 normalize하면 카메라에서 물체를 가리키는 벡터가 나온다.
    
    // reflection vector와 eye vector를 내적해서 결과물로 cos값을 구할 수 있다.
    specularRatio = saturate(dot(-eyeDir, reflectionDir));
    specularRatio = pow(specularRatio, 5);
    // 옵션인데, pow는 제곱해주는 함수임
    // 코사인그래프가 파동모양인데 파동모양보단 더 가파르게 하고 싶어서
    // 제곱을 안해주면 넓은 범위에 하얗게 보이게 됨
    // 비주얼적으로 좀더 효과를 얻기 위해서
    
    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;
}

- 쉐이더에서는 위와 같이 라이트의 종류에 따라 식을 계산해서 컬러값을 반환하게 된다.

 

'DirectX > [Inflearn_rookiss] Part2: DirectX12' 카테고리의 다른 글

18. SkyBox  (0) 2023.01.27
17. Normal Mapping  (0) 2023.01.27
15. Lighting 기본 개념  (0) 2023.01.25
14. Resources & 3D Cube 생성  (0) 2023.01.25
13. Translation 정리 & Camera 클래스 추가  (0) 2023.01.24

댓글