Home 5. 셰이더, 렌더링순서
Post
Cancel

5. 셰이더, 렌더링순서

서론

작성하다가

Untitled

이 사단이 났습니다.

사유 :

Untitled (하드디스크가 유니티때문에 죽음)

쉐이더

오늘은 간단하게 갑니다. 죽을 것 같아요.

CPU와 GPU

간단하게 갑시다.

CPU. 강력한 한명의 능동 작업자 CPU. 강력한 한명의 능동 작업자

GPU. 멍청한 ‘단체’ GPU. 멍청한 ‘단체’

렌더파이프라인

Untitled

  1. IA. Input assembler에서 입력된 데이터를 어떻게 해석할지를 정합니다.
  2. 버텍스쉐이더에서 모델링 공간에서, 투영공간까지 변환하며 버텍스에 처리해야할 추가작업을 합니다.
    1. (테셀레이터 사용시)Hull shader에서 컨트롤포인트(제어점)를 받아서 테셀레이션 팩터(분할 계수)를 설정하여
    2. (테셀레이터 사용시)테셀레이터에서 분할을 진행하고
    3. (테셀레이터 사용시)도메인쉐이더에서 버텍스쉐이더의 역할을 진행합니다 (테셀레이터를 사용할때)
  3. 지오메트리 쉐이더에서 버텍스를 추가하여 기하구조를 생성할 수 있습니다
  4. 이후 래스터라이징(점찍기)을 거쳐 점으로 변경한 다음
  5. 픽셀쉐이더에서 각 점마다의 색상결정을 진행합니다

즉, 순서는 큰 것(오브젝트,메쉬)부터 해서 다 정리한 다음 가장 작은 단위(픽셀)로 처리하게 됩니다.

체에 다 걸러진 남은것만 하나하나 처리하는게 가장 빠르니까요.

하나하나의 연산이 큰것은 CPU가,

병렬적으로 묶어서 작은 연산을 한번에 할 수 있는것은 GPU가 담당하는게 좋겠죠?

CPU

CPU에서는 오브젝트 단위에서 처리될 수 있는 것들을 처리합니다.

정리

메시(mesh) → 절두체 컬링(Frustum Culling) → 오클루전 컬링(Occlusion Culling)→ GPU

절두체 컬링(회색)과 오클루전 컬링(갈색). 최종적으로 갈색 오브젝트만 남는다

절두체 컬링(회색)과 오클루전 컬링(갈색). 최종적으로 갈색 오브젝트만 남는다

GPU

GPU에선 이제 정점데이터를 받아

정점단위 처리를 우선 진행하고, 래스터화를 진행하며, 픽셀단위 처리를 하게 됩니다.

정리

정점 버퍼(Vertex) →

[버텍스 단위] 좌표 변환, 후면 컬링(BackFace Culling)삼각형 클리핑

래스터화

[픽셀 단위] 깊이 버퍼 테스트(Z-Test) → 스텐실 테스트 → 알파블렌딩 →

후 처리(/posts Processing) → 노출

쉐이더란?

그런데 항상 프로그래밍을 하다보면

어? 이부분은 부품 갈아끼우듯 사용하면 안되나??? 하는 부분들이 있습니다.

마치 전동드릴에 팁을 갈아끼우면서

육각볼트도 조으고, 십자볼트도 조으고, 나무구멍도 뚫고 그러는거죠.

그걸 위해 지정한 부분들이 있습니다.

‘나는 잘 모르겠고 어떻게 처리할지는 너가 정해준대로 할게’

Dependency Injection이라고도 생각할 수 있습니다.

VertexShader

버텍스에 관한 쉐이더입니다.

정점을 이동하거나, 마음대로 처리하고 최종적으로 ‘투영 좌표계 기준으로’ 다시 뱉습니다.

최적화를 하는 버텍스라이트의 경우, 픽셀보다는 버텍스가 더 적을테니

(모 게임처럼 고무오리에 100만개의 버텍스가 있는게 아니라면요) (모 게임처럼 고무오리에 100만개의 버텍스가 있는게 아니라면요)

조금 덜 디테일한, 빠른 작업을 위해 처리하기도 합니다.

아 이거 참 예제를 짜드리고싶은데 너무 바빠요…

PixelShader

최종적으로 화면에 출력되는 픽셀마다 연산이 들어갑니다.

그래서 만약 픽셀쉐이더 연산이 엄청 큰 쉐이더가 들어간 머테리얼이 적용된 물체가

카메라 근처로 온다면 프레임이 떨어지는 것을 구경할 수 있기도 합니다.

래스터라이저

픽셀화를 보통 Rasterization. 래스터화라고 합니다. 래스터가 무엇일까요?

래스터화에서 나오는 래스터 그래픽스는 비트맵 그래픽스라고 볼 수 있습니다.

개별적으로 제어되는 점에 의해 영상이나 이미지를 표시하는 방식이며, 수학에서 선과 모양을 표현하기 위한 방법입니다.

여기에는 이제 픽셀, 텍셀, 프래그먼트등이 속할 수 있는데

예전에 대충 설명했으니 (3강) 넘기겠습니다..

저희는 모니터의 화면을 구성하는 화소, 즉 픽셀을 기준으로 다루겠습니다.

래스터화에서, 물리적인 픽셀과 픽셀사이의 중간에 위치한 수학적 점을 생각해봅시다.

스크린크기가 홀수라면 0,0에 점을 찍을때는 width / 2, height / 2 로 하면 되지만, 스크린 크기가 짝수라면, 중심 비트맵 점을 정확히 표현할 수 없다는 단점이 있습니다.

둘 다 그리거나, 둘 다 안그리거나. 정할 수 없는 것이죠

이를 위해, 표현하는 방법을 정의합니다. 이를 Rasterization Rules 라고 합니다.

여러 예시를 들어봅시다. 아참, 이것은 화면 좌표계가 아닌 래스터화 규칙입니다.

래스터라이징 룰

  • 좌상단 - 왼쪽 상단 채우기 규칙(Top-left Rule)
    • Direct X
    • OpenGL
    • (windows) GDI

간단하게 설명한다면, 어느방향으로 검사를 진행하냐고 볼 수 있습니다.

좌상단 채우기 규칙은 간단히 말해 수학적 점의 위치에서 좌측과 상단의 변에 대응되는지로만 검사를 진행합니다. 우하단 규칙은 반대로 하단과 우측의 변에 대응되는지에 대한 검사만 진행합니다.

Untitled%205.png

이는 연결된 두 삼각형의 경우, 자석의 N극과 S극처럼

한 삼각형의 이웃한 변이 왼쪽이라면 다른 삼각형에게서 그 변은 오른쪽이기 때문입니다.

따라서 중복되거나, 빈공간이 생기지 않고 꼼꼼하게 채울 수 있게 됩니다.

이를 수학적 점과 픽셀의 관계에 대해 사용할때는 다음과 같습니다.

Untitled%206.png

수학적 점에서 1*1 사이즈의 Z 패턴 사각형¹ 을 그린 후 Top-Left filling convention을 적용하는 것입니다. 사이즈만 작을 뿐 동일하니까요.

¹ 아마 이것은 Top-Left filling convention 때문에 Z패턴을 사용하는 것 같습니다. 만약 Top-Right filling Convention이 절대적이였다면 저 위의 큰 네모와 같은 삼각형으로 구성된 사각형이겠죠?

렌더독

Untitled

Z buffer (깊이 버퍼, Depth)

안타깝게도 화면의 2차원공간으로만 사용하게 된다면 앞과 뒤의 개념이 없어지게됩니다.

그렇게되면 뒤에있는 오브젝트가 그려진다던지,

반만그려진다던지 하는 여러가지 문제가 발생합니다.

이걸 보통 뎁스(Depth)문제라고 하는데, 쉽게보자면 아래와 같이 개판이 되는 겁니다.

자, 익숙한 유니티 샘플신에서의 그냥 일방적 렌더링을 한번 봅시다.

개판이 나버린 뎁스(Depth) 개판이 나버린 뎁스(Depth)

땅 밑에있는 나무골자가 드러나고, 내부 파이프가 앞에그려지고, 심지어 파란색 무언가는 저 선반에 가려 보이지도 않습니다(다시 그려졌을지도 모르죠)

이번에 설명하고자 하는것은 이것입니다.

일단 결과론적으로

여기서 이제 화면에 어떻게 그릴 것인지 깊이를 설정하게되면,

유니티에서의 화면좌표값 (ScreenPosition.w) 유니티에서의 화면좌표값 (ScreenPosition.w)

이렇게 (지금은 시각화된) 깊이정보를 그리게 되고, 최종적으로 Comparision 하여 그리게 됩니다.

깊이 테스트가 된 결과물 깊이 테스트가 된 결과물

짜잔

이번에 진행할 내용은 전체적으로 위 내용에 대한 것입니다.

하나하나 차근차근 진행해 볼까요?

깊이 버퍼(Depth Buffer)


하하

깊이 테스트란 무엇인가!

간단하게는 거리값을 각 픽셀마다 테스트하여, 지금 그리고있는 메시의 해당 화면좌표에 저장된 값과 비교하여 갱신하거나, 패스하는 방법입니다.

유니티 깊이 텍스처 유니티 깊이 텍스처

[깊이버퍼(Depth)](/posts/5-Depth-a-k-a-Z-Buffer-410e22eca2134669bbc3c94076a4b513?pvs=21)

유니티에서의 화면좌표값 (ScreenPosition.w) 유니티에서의 화면좌표값 (ScreenPosition.w)

이렇게 (지금은 시각화된) 깊이정보를 그리게 되고, 최종적으로 Comparision 하여 그리게 됩니다.

이제 이부분을 조금 자세하게 설명하려 합니다.

렌더 루프에서 결국 메쉬→삼각형→삼각형내부의점→픽셀색상→화면위치에 그리기

에서 화면위치에 이미 그려진 점이 있다면? 이라는 개념으로 접근합니다.

초기화 : 화면크기만큼의 버퍼를 생성

  1. 렌더루프 : 현재 그리고 있는 화면의 픽셀의 저장된 거리값과 비교
    1. 만약 비교값이 참이라면
      1. 버퍼에 거리값을 저장
      2. 그리기
    2. 만약 비교값이 거짓이라면
      1. 그리지 않고 넘기기(Continue;)

여기서 왜 설명을 ‘비교값이 ~라면’ 의 꼴로 두었는지를 봐야합니다.

이 비교(comparision) 라는 것은 변경될 수 있습니다.

사용하여 변경할 수 있습니다.

일반적인경우

  • Less
    • 다이렉트X
    • OpenGL
    • (교주님의 CK - 소프트렌더러(2021))
  • LessEqual
    • 유니티

을 사용합니다.

즉 더 가까운것만 그리는 것이죠.

그리고 이런 뎁스를 저장해둔 정보를 텍스처로써 저장해 사용할 수 있습니다.

활용


텍스처로 저장하여 사용할 수 있다 하였는데 어떤걸 사용할 수 있을까요?

과거 하는 게임들에서 어 이부분은 이렇게 구현하면 되지않을까? 하는 부분들을 찾아서 구현하는게 취미였었어서…(최근엔 졸작때문에 아무것도 못함)

그 중 뎁스를 사용한 것들을 일부 소개할려합니다.

첫번째로, Scene Depth에서 screenPosition.w의 일부를 제거한 값을 빼,

접점영역을 구할 수 있습니다.

3D게임에서 영역을 표시하거나,

바다의 거품, 물의 끝쪽의 깊이색상을 낮추는 곳에 사용할 수 있습니다.

뎁스를 사용한 물체의 접(intersection) 표시 쉐이더 뎁스를 사용한 물체의 접(intersection) 표시 쉐이더

뎁스를 사용한 물 쉐이더 뎁스를 사용한 물 쉐이더

두번째로, 아까 소개한 Depth비교를 변경하여, 가려진 오브젝트를 다른 머테리얼로 렌더링할 수 있죠.

Untitled

깊이테스트를 Less(가까움!)이 아닌 Greater(멀어!) 일 경우,

다른 머테리얼을 사용하서 그려줍니다. 저는 실험적으로 만들어둔 디더 머테리얼을 넣었습니다.

아래의 큐브를 봐주세요 (그냥 unlit 넣을걸 그랬나봅니다)

디더특징 : 축소하면 안보임 디더특징 : 축소하면 안보임

개발 초기 더미 모델링 + 실루엣 하늘섬 개발 초기 더미 모델링 + 실루엣

그러면 이렇게 가려진부분만 한번 더 그려서, 다른 방식으로 보여질 수 있게 나오게 됩니다.

첫번째 뎁스 예제의 경우 거리에 초점을 맞춘 케이스고,

두번째 뎁스 예제의 경우 그려진다, 그려지지않는다 라는 비교에 초점을 맞춘 케이스입니다.

그런데..

Z fighting

문제가 발생합니다.

Untitled

두개의 렌더대상이 카메라와 아주 유사한 거리를 가질때 발생하는, 어떤 것이 더 멀리 떨어져있는지 정확히 구분할 수 없을 때 무작위로 그려지는 현상을 말합니다.

보통 부동소숫점 정밀도와 고정소숫점 반올림 오류로 발생하죠.

현실에서도 간혹 발생하는 문제죠.. 현실에서도 간혹 발생하는 문제죠..

깊이 버퍼의 선형화(Linearize Depth)


깊이버퍼를 선형화 하지 않으면 생기는 문제부터 말씀을 드리는게 인지상정.

선형화 하지 않을 경우, 아무래도 정밀도를 손해보기때문에

부드러운 처리가 되기 힘든 경향이 있습니다.

Untitled

Untitled

Z-fighting 의 경우도 마찬가지입니다.

Untitled

선형과 Raw값을 비교해보면 쉽게 알 수 있죠.

그렇기 때문에 엔진에서는 선형화를 해야합니다.

Near/Far

Untitled

예전 선형 포그에서 시작과 끝이 존재하였듯(저는 설명하지 않았지만요)

카메라도 그리기 시작하는 면과 더이상 그리지 않는 면이 존재합니다.

깊이 선형화 방법

1. 처음부터 넣을 때 선형값을 사용한다.

가장 편한방법은 처음부터 선형값을 사용하는 것입니다.

실제 무게중심좌표를 구하는 타이밍에서 사용하였던 그 거리값을 가져와서 사용하는 방법입니다.

Untitled

유니티에서 ScreenPosition.w를 받는 느낌이죠.

2. 어려운길을 걷는다.

이걸 이제 나온값을 되돌릴려고한다면… $[-1,1]$의 영역에 대해서,

프로젝션 행렬 P 를 참조하여

이걸..또? 이걸..또?

이걸 또.........? 이걸 또………?

를 처리해주면 됩니다.

소감

살려주세요…

졸작이 하고싶어요…

This post is licensed under CC BY 4.0 by the author.
Contents