- 정종필님이 집필한 유니티 쉐이더 스타트업 책에 참고해야할 만한 사항을 정리함.
감마콜렉션
쉐이더 작성
- 쉐이더 랩 : 하드웨어와 호환성이 좋지만 기능이 부족해 고급효과 어려움.
다른 쉐이더 문법과 호환되지 않아 다른 쉐이더를 배울때 사용하지 않으므로 학습하지 않으며 그리고 엔진에서도 지원 중단 수순.
- 서피스 쉐이더 : 쉐이더랩 스크립트 + CG 쉐이더 코드 같이 사용.
버텍스 쉐이더의 복잡부분(좌표행렬변환)은 자동처리가 되며 초보자가 쉐이더 학습에 사용하기 좋지만 마찬가지로 고급 기법 구현 힘들다.
프레그먼트 쉐이더를 만질수 없기 때문(?!)
- 버텍스 & 프레그먼트 쉐이더 : 쉐이더랩 + CG 쉐이더 쓰지만 자동으로 처리하는 부분(좌표행렬변환)이 없어 어렵고 고급 기법을 사용가능.
완전 수동으로 제어
- HLSL(High Level Shader Language)를 사용한는 쉐이더? : 유니티에서 URP부터 지원하는 언어로 이것으로 만드는 쉐이더를 좀 더 찾아볼 생각.
- GLSL(OPEN GL Shader Language) : ??
UV, Time
- 유니티는 왼쪽아래 부터 0,0 UV좌표, 언리얼은 왼쪽위에 부터 0,0
- 아래 time은 다 float4
- _Time.x(1/20속도) , y(본래), z(2배), w(3배)
- _SinTime, _CosTime은 sin, cos그래프를 적용한 타임, xyzw는 각 t/8, t/4, t/2, t로 대응
- unity_DeltaTime : delta time, xyzw는 dt. 1/dt, smothDt, 1/smothDt에 대응
- UV를 증가시키게 한다면 이미지는 왼쪽, 아래로 흐르게된다.
atten 빛감쇠가 계산하는 항목
- self shadow
- receive shadow
- 조명의 세기에 따른 감쇠. 포인트 라이트, 스폿라이트
Property
- _VariableName ("display_name", 타입)= 디폴트값
- Sampler(2D, 3D, Rect, Cube) "값"{option} 으로 값에는 white, gray, blue
이렇게 들어가기도 하며 BumpMap은 bump라고 넣기도 하며 {}로 비워줄수 도 있다.
- 위 샘플러는 변수로 Sampler2D, Sampler3D, SamplerCUBE 이런 변수타입으로 SubShader에서 변수로 받는다.
- 쉐이더에서 _BumpMap으로 프로퍼티 작성시 Normal Map을 넣지않으면 경고를 띄움. 그렇게 유니티에서 만들어져 있는 방어 코드.
- Color, Vector 디폴트값 (x,y,z,w)
- Float, Range(min,max), int
Normal Map
- o.Normal = UnpackNormal(tex2D(_BumbMap, IN.uv_BumpMap));
버텍스 쉐이더
- Surface shader는 프레그먼트 쉐이더로 버텍스 쉐이더를 내부적으로 연산한다.
- vertex:함수명 으로 버텍스 쉐이더를 사용할 수 있으며 void vert(inout appdata_full) 로 사용가능
- appdata_base, appdata_tan, appdata_full로 사용할 있으며 full의 경우 가져오는 데이터가 더 많아 부하가 좀 더 생기긴한다.
- texcood 에서 x,y만 사용하고 나머지 2개는 편하게 이용하면됨.
- 참고로 버텍스 쉐이더를 사용하여 좌표를 움직이는것보다 직접 오브젝트의 위치 변환이 더 가볍다.
커스텀 라이팅
- half4 Lighting(라이팅이름) (SurfaceOutput s, half3 lightDir, half3 viewDir(생량가능) half atten)
- half4 Lighting(라이팅이름)_PrePass(SurfaceOutput s, half4 light)은 디퍼드 라이팅 패스로 사용됨
surf
- void surf(Input IN, 램버트, 블린퐁은 SurfaceOutput o
또는 Standard 라이팅의 경우 SurfaceOutPutStandard 또는 SurfaceOutPutStandardSpecular 사용)
- Input 구조체는 엔진에서 받아오는 값으로 worldPos나 UV와 같은것
- SurfaceOutputCustom 처럼 임의로 만들어서 여기에다 추가로 원하는 데이터를 넣어서 사용하여 전달해도 된다.
- Input 에 아무것도 넣지않으면 오류가 생김. float4 color:COLOR(보간된 버텍스 컬러) 로 입력을 하나라도 넣어주면 됨.
frac()
- frac(숫자) 함수는 소숫점만 반환해준다. 이걸이용하여 _Time.y랑 같이쓰면 선형으로 증가하다
다시 0부터 시작하는 톱니모양의 함수가 되며, 주기성을 가지도록 만들수 있는 방법으로 sin 함수와 같이 알아두면 좋다.
Emission
- 스스로 빛을 내는 값으로 surf에서 Emission을 세팅할 경우 커스텀 Light를 사용시
return하는 값에 더해지는 값으로 별도로 Light함수에서 처리할 필요가 없다.
cull
- 디폴트로 cull back으로 뒷면을 그려주지 않는다. 랜더링 속도도 개선되는데 이것을 backface culling이라 한다.
- 뒤쪽을 그려준다고 해서 normal이 뒤집히지는 않는다.
- cull 을 한 번 지정하면 다른 Pass에도 영향을 미치니 원상태로 복구하는 것도 신경을 써주자'
반사
- 반사는 빛을 반사하는것으로 Emission에 넣어줘야한다.
- IN.worldRefl, IN.worldNormal을 쓸 때 normalMap이 사용될때 INTERNAL_DATA를 쓰는 이유는
노멀맵은 픽셀데이터로 픽셀로부터 데이터를 받아와야하므로 INTERNAL_DATA의 추가 키워드를 적어야한다. 그렇지 않으면 오류
LOD
- 쉐이더에서 LOD는 프로젝트에서 설정(Shader.(global)MaximumLOD)하고 변경하는 LOD 값에 따라
이 쉐이더가 동작할지 말지를 값으로 나타내며, LOD 값 마다 SubShader를 여러개 만들어 사양에 따라 적용가능.
- 내장쉐이더는 아래와 같다.
VertexLit 유형 셰이더 = 100
데칼, 반사 VertexLit = 150
디퓨즈 = 200
디퓨즈 디테일, 반사 범프 언릿, 반사 범프 VertexLit = 250
범프, 스페큘러 = 300
범프 스페큘러 = 400
패럴랙스 = 500
패럴랙스 스페큘러 = 600
그림자
- fullfowardshadows : 모든 조명에서 그림자가 생김. 포인트, 스폿라이트도 생성하게 하려면 사용하며 이것은 자신에게
드리워지는 receive 그림자가 생기는것을 의미
- noshadow : receive 그림자 하지 않음.
- noforwardadd : 포워드 렌더링 추가 패스를 비활성화하여 디렉셔날 라이트 하나만 지원한다.
다른 모든 라이트는 버텍스당 계산이 됨.
반투명
- 풀의 그림자가 네모네모하게 나오면 Fallback으로 "Legacy Shaders/Transparent/VertexLit"으로
Transparent 계열의 쉐이더를 넣어 그림자가 보이지 않게 하면 된다. 반투명은 원래 그림자가 안보이는게 맞다.
Legacy Shaders/Transparent/Cutout/VertexLit"로 그림자 노출도 가능하긴함.
- Deferred 랜더링으로는 반투명을 처리할 수 없지는 않고 어려우며 할수는 있음.
- 반투명 구현을 위해
- 알파 페이드는 Tags에 "RenderType"="Transparent" "Queue"="Transparent",
pragma로 aplha:fade (alpha, alpha:auto로 지정시 일반조명은 fade로 지정되며, 물리조명은 premul로 지정됨
- 알파테스트는 Tags에 "RenderType"="TransparentCutout" "Queue"="AlphaTest",
pragma로 aplhatest:_CutOff(변수명으로 프로퍼티에서 받아옴)
- 알파테스트에서는 _Color 프로퍼티를 참고하여 그림자의 색을 받아온다.
그리고 "Legacy Shaders/Transparent/Cutout/VertexLit"으로 그림자를 만들수 있다.
- 불투명 -> 알파테스팅 -> 알파블랜딩 순서로 그림
- keepalpha : 유니티 5.0부터 서피스 쉐이더에 기본적으로 모든 불투명은 알파에 1.0을 입력하게 되었는데
그걸 막아주며 알파블렌딩때 사용하며 주로 이펙트 만들때 배경이나, 물체가 불투명일 경우 알파가 1.0으로 강제하면
블렌딩에 의해 겹치게 되면 특이하게 하얗게 변하는 예상외의 상황이 발생할 수 있기 때문이다.
그리고 포그나 다른기능과 충돌이 있을수 있어서 유지?가 필요할 수 있다.
- 파티클 이펙트의 쉐이더는 보통 빛, 그림자, 기타 부수적인 연산이 없는 순수한 프래그먼트 쉐이더로 작성되어
연산을 줄이기 때문에 SufaceShader는 이펙트에서 사용을 보통 하지 않는다.
써야한다면 noforwardadd nolightmap noambient novertexligths noshadow 같이 최대한 줄여주는 코드를 써보자.
프라넬 효과
- 재질(금속, 비금속 안에서도 모두 다름) 마다 각도에 따른 프라넬 반사율이 차이가나며
BRDF 를 이용해 Standard SurfaceShader에 구현되어있음
- BRDF : "Bidirectional Reflectance Distribution Function" 의 머리글자.
우리 말로는 "양방향 반사율 분포 함수"로 PBR의 물리 반사를 위해 사용 어느 개발자 벨로그
- 법선과 90도에 가까워질수록 100% 반사로 수렴함.
- 프레넬 공식으로 dot하여 saturate를 할 경우 2side(cull off) 할 경우 빛을 안받는 90도 넘어가는 안쪽이 밝게 표시된다.
이때는 abs를 사용하자.
외곽선
- 2Pass로 cull front로 뒷면을 그린후 노멀방향으로 확장하는 방법 단점
2번 그림, 매쉬끼리 침범시 찌꺼기처럼보임, 하드엣지는 선이 끝어지고, Plane으로 끝나면(앞뒷면 경계가 바뀌는 선) 외곽선이 생기지 않음.
- 면을 뒤집는 이유는 cull back 시 앞면을 그리는 영역은 앞면으로
뒷면보다 우선하게 보이게 된다. 그러므로 뒷면이 더 확장된 영역만 외곽선으로 보임.
- 프라넬로 외곽선을 만들면 단점
viewDir이 평면하고 평행일경우 조금 이상한 모양으로 표시된다.
- 위 단점으로 기타 버퍼를 이용한 후처리나, 소벨 마스크, 라플라스 필터를 이용하여 외곽선 검출하는데 사용.
깔끔한 투명처리
- 처음 그리는 pass는 zwrite on하여 z버퍼에 기록만하고 보이지 않게 처리 ColorMask 0 를 zwrite on 아래에 명시
- 두번째 pass는 zwrite off 하여 z버퍼에 기록하지 않게 하며 그릴때 처음 패스의 z버퍼의 데이터를 참조하기에
그 z버퍼에는 보이는 면의 z버퍼만 남아있으므로 앞면만 보이게 되는 원리
MapCap(Material Capture)
- Ramp texture 와 비슷하게 IBL(Image Based Lighting) 한종류
- 바라보는 방향으로 빛의 텍스처의 형태되로 씌우는 방법
- MapCap의 UV는 보통 버텍스 단계에서 연산됨. Normal Map을 쓰면 픽셀 단계에서 연산하게되므로 추가 연산이 들어간다.
- o.Emission = IN.worldNormal을 입력시 노멀에 따른 색이 표시되며 이걸 행렬변환으로
UNITY_MATRIX_V로 View 좌표계로 변환하면 UV가 완성됨.
- 쉽게 구체(스피어)로 생각해서 카메라로 오브젝트가 어느 방향에서 바라보든 월드 좌표-1,0의 위치에서의 Normal의 값은 변하지 않는다.
-1,0은 왼쪽을 바라보는 노멀을 나타낼수 있으며 그것이 UV가 된다면 그 UV의 텍스처는 빛이 비춰질때 -1,0이 어떻게 보여줘야하는지
텍스처로 이미지화 하면 되는것이다.
- 다만 view로 변환된 Normaldmf *0.5+0.5하여 UV의 0 ~ 1 범위를 맞춰줘야한다.
굴절
- GrabPass{ 변수명(대표적으로 _GrabTexture) } 으로 sampler2D로 화면을 캡쳐한 텍스처 샘플러를 받아올 수 있다.
- UV를 위해 IN.screenPos를 받아오며 IN.screenPos.rgb / IN.screenPos.a(카메라로부터의 거리) 로
정확히 오브젝트가 스크린에 보여지는 부분을 그대로 랜더링 가능하다. 거기다 노이즈를 넣으면 되며 결과적으로 o.Emission에 입력
트라이 플래너(Triplanar)
- HeightMap을 가지고 처리못하는 높이가 동적으로 변경되는 오브젝트에 랜더링을 하기위해서 사용하는 기법
- 컨셉은 worldPos를 가지고 각 front, top, side의 UV를 만들어 놓고, 그걸 각 면에 맞는 Texture를 만들어내서
Normal의 방향에 따라 어느 UV를 사용할지 정하여 lerp를 하여 Albedo에 넣으면 구현가능
기타
- 필사의 랜더맨이라는 프로그램이 최초, 랜더 몽키도 랜더맨에서 유래
- In-House : 내부의. 자체제작을 의미하는 IT용어
- 깊이 값을 가져오려면 카메라에 깊이 버퍼를 만들어야 하기 때문에 카메라 컴포넌트에 랜더 패스를
Deferred로 설정해줘야 하며 sampler2D로 _CameraDepthTexture; 로 가져올 수 있다.
- 랜더큐를 상대적으로 지정하고 싶다면? : "Queue"="Geometry+1"