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 |
댓글