본문 바로가기
Computer Graphics/ShaderLab, HLSL

[ShaderLab] NPR - 2Pass, Fresnel

by 진현개발일기 2024. 3. 4.

■ PR, NPR

앞서 공부했던 Standard, Lambert, Blinn-Phong의 공통점은 실사와 비슷하게 표현하려고 하는 것이다. 이와 같은 렌더링 방식을 PR(Photo Realistic)이라고 한다. 이와 반대로, 전혀 사실적이지 않은 렌더링 시도가 있는데 대표적으로 셀 쉐이더 (Cell Shader or Toon Shader)가 있고 이러한 방식을 NPR(Non-Photo Realistic)이라고 한다.

 

■ 외곽선 만드는 방법

대표적으로 아래와 같이 두 가지가 있다. 

(1) 2Pass를 이용한 외곽선 제작

(2) Fresnel을 이용한 외곽선 제작

 

구분 특징 장점 단점
2Pass 이용 먼저 Pass란 '한 번 그리는 것'을 뜻한다.그러므로 2Pass는 두 번 그린다는 뜻이다.

(1)
 완전히 검게 그린다
(2) 크기를 키워서 그린다.
   : 정확히 말하자면 그냥 스케일을 키우는 것이 아니라 '노멀 방향으로 확장'을 이야기 하는 것이다.)

(3) 노멀을 뒤집는다.
  : 3ds Max에서 면을 뒤집는다는 행위는 'Normal을 뒤집는다'라고 표현하지만, 엔진에선 보통 노멀을 뒤집지 않고 '앞면을 날리고 뒷면만 그리는 Front Face Culling'을 쓰기 때문에 보통 노멀을 뒤집지 않는다.
(1) 균일한 외곽선을 얻는다.

(2) 구현하기 간단하다.
(1) 두 번 그리는 것이므로 무겁다.

(2) 면을 뒤집은 것뿐 온전한 오브젝트이기 때문에, 메쉬끼리 침범하거나 찌꺼기가 보인다.

(3) 각진 면(Hard Edge)에선 선이 끊어져 보인다.

(4) Plane으로 끝난 오브젝에선 외곽선이 생기지 않는다.
Fresnel 이용 Rim라이트를 만들었던 결과물을 단순히 뒤집어주거나, if문을 이용해 외곽라인을 검출해 외곽선을 칠해준다. (1) 폴리건의 밀도와 각도에 따라 선의 굵기가 다양하게 나타나므로 의도적으로 선의 강약을 세팅할 수 있다.

(2) 결과물의 품질도 꽤 좋은 편이다.
각 픽셀의 노멀방향과 시선방향의 차이로 계산되는 외곽선이므로 완전한 평면을 가진 오브젝트에선 이상한 모양으로 보이게 된다.

 

■ 1Pass vs 2Pass

1Pass일때 Stat상태
2Pass일때 성능 상태

 

위에서 볼 수 있듯이 두 번 그리기 때문에 쉐이더가 무거워지는 것을 볼 수 있다.

■ 2Pass: Vertex Shader

앞서 살펴봤던 2Pass의 특징을 되새김한다면, '노멀 방향으로 크기를 확장'해줘야한다. 이는 버텍스의 좌표를 여러 좌표계에 맞춰 변환해주는 단계인 버텍스 쉐이더에서 조절해줘야한다.

 

※ 먼저 알아야할 것은 Untiy의 Surface Shader의 버텍스 쉐이더는 '필요가 없으면' 안써도 알아서 자동 변환을 해준다. 우리가 필요할 때 필요한 것만 가져다 쓰면 되기 때문에 매우 편리한 구조인 것이다.

 

아래는 코드 작성 부분이다.

 

위와 같이 vertex:vert를 작성해줘 vertex shader는 vert라는 함수에서 일어나는 것을 명시해준다.

파라미터는 appdata_full 구조체를 받고있다. 이는 UnityCG.cginc에 내포된 데이터로 공식 문서를 확인하면 아래와 같은 종류가 있는 것을 확인할 수 있다. (링크)

 

그리고 궁금해서 UnityCG.cginc 파일을 찾아봤는데 아래와 같이 구조체의 구조를 확인할 수 있었다.

해당 변수들의 데이터 타입 설명은 위 (링크) 에서 확인해봤다.

 

다시 본론으로 돌아와서 버텍스 쉐이더 함수를 조금 건드려봤다.

 

2Pass를 통해 두 번 그림으로써 버텍스들의 위치 변환을 직접 확인해봤다.

 

버텍스 좌표를 직접 변환하는 것을 눈으로 확인해봤으니 원래 목표였던 아웃라인 그리기를 시행하기 위해 노멀방향으로 크기를 키워봤다.

위와 같이 단순히 키우기만 한다면 아래와 같은 결과물을 얻게 된다.

이렇게 되는 이유는 기본적으로 모든 벡터는 길이가 1로 되어있는데 각 버텍스는 노멀 방향으로 1유닛(1m)를 이동한 것이다. 그러면 1cm만큼의 이동효과를 줄려면 * 0.01을 해주며 되는 것이다.

 

그냥 넓히기만하면 재미가 없으니 동적으로 크기가 움직이는 것을 구현해봤다.

 

■ 외곽선 작업 with 2Pass

이제 실제로 외곽선을 작업해보도록 하겠다. 아래와 같이 1st Pass를 작성해줬다.

1st Pass

노말 방향으로 확장된 물체를 그리도록 하였고 NoLight라는 커스텀 라이팅 구조를 작성했다. 색깔은 검은색으로 고정해줬고 cull front 키워드를 통해 앞면을 컬링하도록 하였다.

2nd Pass

 

결과물

결과물이 조금 이상하다. 입이 안쪽으로 들어가 있는데 이러한 이유는 1st Pass만 cull front를 하고 2nd Pass는 쉐이더 디폴트값인 cull back을 해줘야하는 것이다. 아래는 참고용으로 작성했다

 

[Cull 종류]

(1) cull front : 프론트 페이스 컬링. 앞면을 날리고 뒷면을 보여준다.

(2) cull back : 백페이스 컬링. 뒷면을 날리고 앞면을 보여준다.

https://yjhdevelopdiary.tistory.com/161

 

[Math] 외적, 백페이스 컬링, 로드리게스 회전

■ 외적 내적 같은 경우 서로 같은 위치에 있는 요소끼리 연산을 하지만, 외적은 서로 다른 위치에 존재하는 요소만 사용한다. 예로 외적의 계산은 아래와 같다. [공식] 아래와 같이 벡터u와 벡터

yjhdevelopdiary.tistory.com

 

(3) cull off : 양면을 모두 나타나게 하는 방식. 2-side로 불린다.

 

 

다시 본론으로 돌아와서 2nd Pass 이전에 cull back 키워드를 추가해주면

 

 

의도한 바와 같이 아웃라인이 생기는 것을 확인할 수 있었다.

 

마지막으로 아웃라인의 속성 (크기, 색깔)을 프로퍼티화 해봤다.

 

■ 끊어지는 음영 처리

2nd Pass의 하프램버트 구조를 적용한 커스텀 라이팅에서 끊어지도록 설정해봤다. 하프램버트를 사용한 이유는 계산이 좀 더 명확해지기 때문이다.

 

이를 세 단계로 나누면

아래와 같이 세 개의 레이어로 나뉜 음영을 확인할 수 있었다.

 

또 다른 방법으론 ceil이라는 올림 함수를 이용하는 것이다. 이를 이용하면 0~1 범위였던 ndotl을 0~5범위로 정수형태로 늘린 뒤 5로 나누면 1, 0.8, 0.6, 0.4, 0.2, 0 과 같이 층을 여러개 만들어줄 수가 있다. 5의 값을 조절하면 레이어의 개수를 조절할 수 있겠다라는 생각이 들었다.

 

이제 이러한 음영에다 1st Pass인 아웃라인을 적용한 뒤 Normal 맵과 MainTexture의 Albedo를 적용해봤다.

 

▼ 최종 결과물

 

■ Fresnel

프레넬로 외곽선을 표현하는 방법이다.

외곽선을 그릴 예정이니 위에 작성한 코드 중 1st Pass를 제거하고 두번째 pass만 남겨뒀다.

 

대신에 아래와 같이 커스텀 라이팅 구조에 rim light를 추가해줬다.

이와 같은 외곽선 효과를 볼 수 있다.

여기서 의문점은 왜 rim을 0이 아닌 -1 음수값을 주는 것인가? 이다. 음수값의 영향은 좋지 않은 것으로 기억하는데

이는 noambient를 제거하고 0을 대입해보면 그 이유를 알 수 있었다.

noambient를 제거하고 rim의 하한선을 0으로 설정한다면 아래와 같이 아웃라인이 희미해지는 구간이 생겨버린다.

 

이를 -1로 변경한다면 방지할 수 있게 된다.

 

 

728x90