본문 바로가기

그래픽스

[DX12] Lighting 코드 #2

VS_OUT VS_Main(VS_IN input)
{
    VS_OUT output = (VS_OUT)0;

    output.pos = mul(float4(input.pos, 1.f), g_matWVP);
    output.uv = input.uv;

    output.viewPos = mul(float4(input.pos, 1.f), g_matWV).xyz;
    output.viewNormal = normalize(mul(float4(input.normal, 0.f), g_matWV).xyz);

    return output;
}

 

쉐이더 코드에서 input.pos(local좌표)를 g_matWV와 곱해 View 좌표계 기준으로 변환한 것을 viewPos, 

input.normal을 View좌표계 기준 normal 벡터로 변환한 것을 viewNormal에 저장한다. 

 

float4 PS_Main(VS_OUT input) : SV_Target
{
    //float4 color = g_tex_0.Sample(g_sam_0, input.uv);
    float4 color = float4(1.f, 1.f, 1.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;
}

 

VS_Main에서 정점 단위 계산을 해주고 래스터라이즈 단계에서 보간을 하고 PS_Main에서 픽셀 단위 계산을 한다.

color.xyz부분을 보면 diffuse와 ambient는 기존 color에 곱해서 계산하지만 specular은 반짝임을 표현하기 때문에 기존 color에 곱하진 않고 있다. (정해진 공식은 없다) 

 

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

 

그리고 라이팅을 계산하는데 lighting type(directional, point, spot)에 따라 diffuse, specular ratio를 계산해서 색상에 곱해준다. directional light 일 경우 -viewLightDir과 viewNormal을 내적하고 saturate함수로 0~1 값으로 만든다.(음수일시 0)

 

    float3 reflectionDir = normalize(viewLightDir + 2 * (saturate(dot(-viewLightDir, viewNormal)) * viewNormal));
    float3 eyeDir = normalize(viewPos);
    specularRatio = saturate(dot(-eyeDir, reflectionDir));
    //pow(제곱)로 specular 범위가 너무 넓을 경우 범위를 좁히는 용도
    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;

 

specular을 계산하기 위해 reflectionDir을 구하는 법은 -viewLightDir과 viewNormal을 내적해서 saturate한 스칼라 값을 viewNormal 벡터와 곱하고 그 값을 두번 viewLightDir에 더하는 것이다. 그리고 eyeDir은 카메라로부터 빛이 부딪힌 점까지 벡터인데 View좌표계에서 카메라의 좌표가 0,0,0 이므로 viewPos를 정규화하면 카메라로부터 viewPos까지 벡터를 나타낸다. specularRation는 -eyeDir에 reflectionDir을 내적한 값을 saturate하면 된다. 

 

 else if (g_light[lightIndex].lightType == 1)
    {
        // Point Light
        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));
    }

 

PointLight일 경우 광원에서 모든 방향으로 빛이 뻗어나가기 때문에 viewLightDir을 현재 위치 - 광원 위치로 계산한다.

그리고 거리에 따라 어두워지도록 distanceRatio를 설정하는데 1 - (현재 거리 / 최대 범위) 식을 이용한다. 

 

 // Spot Light
        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));
        }

 

SpotLight는 viewLightDir, DiffuseRatio 를 구하는 법은 동일하다. ViewCenterLightDir은 Spot Light가 비추고 있는 중간 방향을 표현한다. distanceRatio는 pointlight과 같은 방법으로 구하고 lightAngle은 viewLightVec과 viewCenterLightDir을 내적한 값을 acos(아크코사인)해서 현재 각도를 구한다. 그래서 최대 거리와 시야각을 벗어났는지 체크한다. 

'그래픽스' 카테고리의 다른 글

[DX12] Quaternion #1  (0) 2022.07.27
[DX12] Skybox  (0) 2022.07.26
[DX12] Lighting 코드 #1  (0) 2022.07.25
[DX12] Camera  (0) 2022.07.24
[DX12] Comoponent  (0) 2022.07.24