본문 바로가기

DX

쉐이딩

쉐이딩

 

조명과 재질의 상호작용을 고려하여 색을 결정하는 과정

 

아래 2가지 중 무엇이 더 빠름?

1. VS 에서 정점 색을 결정한 후 PS 에서 보간하는 방법

 

2.  쉐이딩 알고리즘 자체를 PS 에서 진행하는 방법

 

픽셀 수에 비해 정점의 수가 더 적기 때문에 VS 에서 색을 정하고 PS 에서 보간하는 것이 빠르다.

다만, 요즘은 GPU 성능이 좋아서 PS 에서 쉐이딩 하는 경우가 많다.

 

실습은 2번 방식으로 진행

 

블린 - 퐁 쉐이딩

퐁 쉐이딩의 최적화 버전 : 반사 벡터를 사용하지 않아 연산이 적다.

 

 

실습에서는 지역 조명을 사용하면서 앰비언트와 같은 조명으로 간접광을 표현한다.

 

 

 

조명에도 색을 정해주어 조명에 닿은 오브젝트의 색에 조명의 색을 담을 수 있다.

실제 DX 에서 사용하는 광원의 종류는 3가지로 구분 가능하다.

1. 디렉셔널 라이트 ( 한 가지 방향과 색 등)

2. 포인트 라이트 ( 사방으로 나아가는 빛 )

3. 스포트 라이트 ( 특정 부분만 비추는 빛 )

 

 

Diffuse 재질 (표면이 난반사할 때 보여지는 기본 색.) - 언리얼 유니티에서 Albedo 라고 부르기도 함!

 

법선 벡터빛의 방향에 따라 반사각과 표면이 빛을 받는 세기가 달라진다.

예) 표면에 수직인 빛이 들어올 때 표면은 빛을 가장 많이 흡수하며 반사 또한 강하게 일어난다.

 

 

Specular

물체 표면에 빛이 부딪힐 때, 특정 방향(거울 방향)으로 반사되어서 빛나는 하이라이트

 

Specular 는 표면이 매끄러운 경우를 가정하고 Diffuse 울퉁불퉁한 것을 가정하고 표현되는 재질,조명 효과이다.

 

Specular 는 특정 방향으로 반사 되기 때문에 눈의 방향과 반사 방향의 각도가 크면 Specular 조명 효과가 얕아진다.

 

 

 

Shiningness

빛이 얼마나 집중 될지 특정 부분에 빛이 집중 되는 정도 (세기) 를 의미한다.

 

 

블린 - 퐁 모델

퐁 모델 이 빛의 반사 방향과 뷰 방향을 사용한다면 R, V

블린 퐁 모델은 N ( 법선 ) , H (Harlf) 벡터를 사용한다 여기서 H 는 V 와 L 의 중간 방향을 의미한다.

H 는 R 보다 계산량이 훨씬 적다.

 

 

 

따라서 RV 내적이 아니라 HN 내적으로 조명 효과를 나타내는 것을 의미한다.

 

 

 

실습

 

물체를 회전 시킬 때는 노멀 벡터도 회전 시켜주어야한다.

 

먼저 표면의 노멀 벡터와 인덱스 버퍼 값을 설정해준다.

 

this->indices = {
    0,  1,  2,  0,  2,  3,  // 윗면
    4,5,6,4,6,7,//... // 아랫면
    8,9,10,8,10,11,//... // 앞면
    12,13,14,12,14,15,//... // 뒷면
    16,17,18,16,18,19,//... // 왼쪽
    20,21,22,20,22,23//... // 오른쪽
};

 

결과로 나오는 정육면체 사용 할 정점 정보와 인덱스 버퍼에 넘길 정점 순서를 정해주었다.

 

 

아래 vs 연산 코드를 보면 회전 연산이 있는데 실제로는 더 복잡한 회전들이 많아서 아래와 같이 사용하지 않는다.

그리고 normal 은 벡터이기 때문에 물체와 같이 회전을 해주어야 한다. 따라서 VSOUT 버텍스 버퍼에는 회전된 법선 벡터 값이 들어가게된다.

VSOutput MyVertexShader(const VSInput vsInput) {
    VSOutput vsOutput;

    // 여기서 여러가지 변환 가능
    vsOutput.position =
        RotateAboutX(
            RotateAboutY(vsInput.position * constants.transformation.scale,
                         constants.transformation.rotationY),
            constants.transformation.rotationX) +
        constants.transformation.translation;

    // 주의: 노멀 벡터도 물체와 같이 회전시켜줘야 합니다.
    // 더 편하게 구현할 방법은 없을까요?
    // 노멀 벡터가 제대로 회전됐는 지는 챕터7에서 직접 그려서 확인해보겠습니다.
    vsOutput.normal = RotateAboutX(
        RotateAboutY(vsInput.normal, constants.transformation.rotationY),
        constants.transformation.rotationX);

    return vsOutput;
}

 

 

 

다음으로 해당 정점 정보를 가지고 아래와 같이 //Barycentric 좌표를 이용해 픽셀의 색을 보간해 준다. 

 

 // // //Barycentric 좌표를 활용한 보간 활용 값 
 float w0 = EdgeFunction(v1, v2, point) / area;
 float w1 = EdgeFunction(v2, v0, point) / area;
 float w2 = EdgeFunction(v0, v1, point) / area;

// 여기서부터 Barycentric 을 활용한 픽셀 보간 시작
 if (this->usePerspectiveProjection &&
    this->usePerspectiveCorrectInterpolation) {

    w0 /= z0;
    w1 /= z1;
    w2 /= z2;

    const float wSum = w0 + w1 + w2;

    w0 /= wSum;
    w1 /= wSum;
    w2 /= wSum;
}
 
 const float depth = w0 * z0 + w1 * z1 + w2 * z2;
 // const vec3 color = w0 * c0 + w1 * c1 + w2 * c2;
 // const vec2 uv = w0 * uv0 + w1 * uv1 + w2 * uv2;

 if (depth < depthBuffer[i + width * j]) {
     depthBuffer[i + width * j] = depth;

	
     PSInput psInput;
     psInput.position = w0 * p0 + w1 * p1 + w2 * p2;
     psInput.normal = w0 * n0 + w1 * n1 + w2 * n2;
     // psInput.color = color;
     // psInput.uv = uv;
	
    // 보간 후 최종 값을 PS 함수로 넣어 최종 색 결정
     pixels[i + width * j] = MyPixelShader(psInput);
 }

 

 

이제 블린 퐁 쉐이딩 구현 부 ( PS 함수에서 사용 )

 

vec3 BlinnPhong(vec3 lightStrength, vec3 lightVec, vec3 normal, vec3 toEye,
                Material mat) {

    // Halfway vector 계산
    vec3 halfway = normalize(toEye + lightVec);

    // Halyway vector를 이용해서 specular albedo 계산 . 재질의 spcular (정반사) 색에 (HN 내적*세기)를 곱해준다.
    vec3 specular =
        mat.specular * pow(glm::max(dot(halfway, normal), 0.0f), mat.shininess);

    // ambient, diffuse, specular 합쳐서 계산 ( 빛을 받는 세기(스펙큘러 디퓨즈만)까지 ) 
    return mat.ambient + (mat.diffuse + specular) * lightStrength;
}

vec3 ComputeDirectionalLight(Light L, Material mat, vec3 normal, vec3 toEye) {

    // 계산에 사용하는 lightVector는 directional light가 향하는 방향의 반대
    vec3 lightVec = -L.direction;

    float ndotl = glm::max(dot(lightVec, normal), 0.0f);

    // 표면이 빛을 강하게 받을지 정해준다. ndotl이 빛과 표면의 법선 사잇각의 내적을 나타낸다.
    vec3 lightStrength = L.strength * ndotl;

    // Luna DX12 책에서는 Specular 계산에도
    // Lambert's law가 적용된 lightStrength를 사용합니다.
    return BlinnPhong(lightStrength, lightVec, normal, toEye, mat);
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'DX' 카테고리의 다른 글

Orthogonal  (0) 2025.05.11
조명 기초  (0) 2025.05.07
원근 투영 실습  (0) 2025.04.30
뒷면 제거  (0) 2025.04.30
쉐이더 개념  (0) 2025.04.30