모든 일반적인 사물은 기울어질수록 심하게 반사가 된다.
즉, 시선 벡터(카메라 벡터)와 노말 벡터가 90도에 가까워질수록 반사가 심해진다.
금속 재질은 비금속 재질보다 확연하게 반사율이 다르다.
이런 반사 공식을 Fresnel(프레넬)이라고 부른다.
역광이 있다거나, 털이나 반투명의 재질이라거나 하면 프레넬 현상의 정도가 달라진다.
사진에서는 이것을 림 라이트(Rim Light)라 부른다. (같은 개념은 아니지만, 림 라이트를 프레넬로 많이 만든다)
림 라이트 게임 사용 예시
이전에 배운 램버트 공식을 간단히 정의하면,
Normal Vector와 Light Direction의 각도가 같으면(0도) 밝고, 90도가 되면 어두워져 검은 색이 된다.

램버트 공식에서 Light Direction을 View Direction으로 치환하면,
마치 카메라가 조명인 것처럼, 카메라를 바라보는 면이 밝아지고 카메라와 각도가 벌어질수록 어두워진다.
('언제나' 어두운 부분은 존재하지 않는다고 생각해도 될듯)

one minus 노드를 이용해 결과를 뒤집으면
보는 각도를 바꿔도 언제나 외곽이 밝아보이면서 림 라이트처럼 보이기 시작한다.

두께를 얇게 만들어주기 위해 3제곱을 해준다.

림 라이트의 두께와 색깔을 커스텀할 수 있게 프로퍼티로 만들고 컬러를 곱해줬다.
거기에 이전 단원에서 만들었던 램버트 라이트를 더해줬다.
Fresnel 기능은 자주 쓰이므로, Fresnel Effect라는 이름으로 노드가 만들어져 있다.
Troubleshooting
책에서처럼 고급스럽게 빛나지를 않아 예제 씬을 Import해서 비교했는데, Global Volume의 Tone Mapping이 ACES로 돼있었다. 나도 ACES로 바꿔주니 해결됨.

여기 Emission 맵이 있는 가로등이 있다. Intensity를 올리면 Bloom 효과와 함께 발광할 것이다.
하지만 저사양의 모바일 게임이거나 레트로한 느낌을 주기 위해 HDR이나 Bloom을 사용하지 않고 이런 효과를 내야 한다고 생각해보자.

Fresnel Effect 노드를 이용하면 색상을 굳이 HDR로 하지 않아도 빛나는 것처럼 보이는 이펙트를 만들 수 있다.
이 방법은 모델링을 이용해서 빛나는 효과를 만드는 것이기 때문에, 모양대로 빛나게 만드는 것이 가능하다.

조명이 깜빡이는 효과를 주기 위해, 0에서 1 사이를 오가는 값을 곱해주었다.
하지만 그냥 곱해주면 sine의 음수 부분 때문에 이상한 색이 나온다.
해결책
*0.5+0.5) : 부드럽게 지속적으로 반짝이는 곡선이 나온다Time에 계수를 곱해주면 반짝이는 속도를 조절할 수 있다.
애니메이션이 잘 보이지 않으면?
씬 뷰 상단의 스택에 반짝이는 효과가 붙어있는 아이콘 (Toggle skybox, fog...) 에서 Always Refresh를 체크한다.

반짝임 효과에서 one minus를 빼주면 보호막, 비눗방울, 홀로그램같은 느낌을 줄 수 있다.
(위에서의 깜빡임 효과를 적용하면 깜빡이는 홀로그램도 만들 수 있다)

World Position을 UV 대용으로 사용하면 어떤 물체라도 World 좌표계의 기준에 맞게 텍스쳐를 입힐 수 있다.
Time을 Negate로 음수로 만들고, 이를 Combine Y에 연결한 후 Add하여 시간에 따라 y축도 흘러간다.
Swizzle 노드?
Vector4에서 필요한 좌표만, 순서까지 마음대로 바꾸어 출력하는 노드
Triplaner?
xy, zy, xz 방면으로 각 면의 방향에 맞게 UV를 만들어 입혀주는 기술. 노드로도 만들어져 있다.
만들어져 있는 것을 사용하는 것은 응용의 폭에 한계가 있고, 혹시나 낭비되는 영역이 있더라도 최적화를 할 수 없다.
각 노드를 선택한 뒤 F1이나 우클릭 > Open Documentation으로 도움말을 볼 수 있다.
Checkerboard 노드를 이용하지 않고 줄무늬를 구현해보자.

World Position과 Fraction 노드를 이용해 줄무늬를 만들 수 있다. Power을 빼면 검은 부분이 사라진다.
이때 Graph Settings에서 Blending Mode를 Alpha로 바꿔주어야 검은색이 투명해지지 않는다.
Fraction 노드?
들어온 숫자의 소수점 부분만 리턴한다. 1.1은 0.1을, 1.9는 0.9를 리턴한다.

Step 노드를 추가하면, 딱 끊어져 보이는 줄무늬를 만들 수 있다.
Step 노드?
in에 들어온 값이 Edge에 쓴 수보다 크거나 같으면 1을 반환하고, 작으면 0을 반환한다.
Simple Lit = Diffuse Lambert + Ambient Color + Specular
가장 유명하고 전통적인 스페큘러 표현 방식은 퐁 반사(Phong Reflection)이다.
이 공식의 기본 원리는 '내가 보는 방향으로부터 반사된 방향에 조명이 있으면 그 부분의 하이라이트가 가장 높다'이다.
스페큘러는 거울 반사를 의미한다.
하이라이트는 '강한 조명을' 거울반사한 것을 의미한다.
과거 컴퓨터의 연산 능력이 높지 않았을 땐 광원에 의한 스페큘러만 적당히 계산해주었다.
퐁 공식 : 노말 방향을 기준으로 조명 벡터를 반사하는 반사 벡터 R과 시선 벡터 V의 내적으로 스페큘러 표현 (반사벡터 계산이 옛날 기준으로 무거움)
블린-퐁 공식 : 시선 벡터와 조명 벡터의 중간 값인 하프벡터 H를 구하고, 이를 노말 벡터 N과 내적

Phong Specular 구현
View Direction과 View Vector의 차이?
View Direction : 길이가 1로 Normalize되어 있는지
View Vector : 길이가 Normalize되어 있지 않은, 순수하게 카메라와 버텍스와의 거리를 가지고 있는 노드

Lambert Light와 더해주면 정반사임이 확연히 드러난다.

Blinn-Phong Specular 구현


Fresnel을 One Minus로 뒤집고, Power로 작게 만든 후, 컬러를 조절할 수 있게 컬러를 곱하면 스페큘러처럼 보인다.
스페큘러는 특정 방향을 바라볼 때 생기는 하이라이트이므로 크게 다를게 없기 때문.
하지만 Fresnel의 특성상 내가 바라보는 방향만이 언제나 빛난다. (카메라 위에 붙어 있는 조명 느낌)
이걸 스페큘러로 써도 아무 문제 없다.
빛의 영향이 없는 뒷면에서도 보이기 때문에 어두운 곳에서도 음영과 질감을 좀 더 잘 보이게 할 수 있다.
이 공식은 림 라이트 연산에서 나오는 부산물로 만든 결과물이므로 연산량도 절약할 수 있다.
Normal map을 적용하고 Gloss로 마스킹도 해주면 가짜 스페큘러 완성.
(이쯤 오면 Add가 너무 많아지는데, Sub graph로 묶어 4Add, 5Add 이런 식으로 사용하면 정리하기 좋다)
ShaderGraph에서는 그림자가 그려지는 Shadow Pass를 자동으로 생성한다.
HLSL 코드로 셰이더를 작성하면 Shadow Pass와 Depth Pass까지 모두 작성해야 한다.
하지만 우리가 만든 Shader Graph를 적용한 오브젝트 위에 공을 가져다 놓으면 그림자가 드리워지지 않는다.
'그림자를 드리우는 연산'은 했는데, '내가 그림자를 받는 연산'은 해주지 않았기 때문이다.
이것을 연산하려면, 그림자 연산을 받아와서 Lambert 연산과 Specular 연산에 곱해줘 조명 계산을 없애줘야 한다.

void CustomLight_File_float(out float3 Direction, out float3 Color)
{
#ifdef SHADERGRAPH_PREVIEW
Direction = float3(1,1,1);
Color = float3(1,1,1);
#else
Light light = GetMainLight();
Direction = light.direction;
Color = light.color;
#endif
}
CustomLight.hlsl 파일을 만들고 sub graph로 만들었던 Lambert에 가서 Cumstom Function의 Precision, Type, Name 등을 바꿔주면 String이 아닌 File 형식으로 CustomLight.hlsl을 적용시킬 수 있다.
//커스텀라이트
void CustomLight_File_float(out float3 Direction, out float3 Color)
{
#ifdef SHADERGRAPH_PREVIEW
Direction = float3(1, 1, 1);
Color = float3(1, 1, 1);
#else
Light light = GetMainLight();
Direction = light.direction;
Color = light.color;
#endif
}
//커스텀라이트 셰도우
void CustomLight_Shadow_float(float3 worldPos, out float ShadowAtten)
{
#ifdef SHADERGRAPH_PREVIEW
ShadowAtten = 1.0f;
#else
//shadow Coord 만들기
#if defined(_MAIN_LIGHT_SHADOWS_SCREEN) && !defined(_SURFACE_TYPE_TRANSPARENT)
half4 clipPos = TransformWorldToHClip(worldPos);
half4 shadowCoord = ComputeScreenPos(clipPos);
#else
half4 shadowCoord = TransformWorldToShadowCoord(worldPos);
#endif
Light light = GetMainLight();
//메인라이트가 없거나 받는 셰도우 오프 옵션이 되어 있을때는 그림자를 없앤다
#if !defined(_MAIN_LIGHT_SHADOWS) || defined(_RECEIVE_SHADOWS_OFF)
ShadowAtten = 1.0f;
#endif
//ShadowAtten 받아와서 만들기
#if SHADOWS_SCREEN
ShadowAtten = SampleScreenSpaceShadowmap(shadowCoord);
#else
ShadowSamplingData shadowSamplingData = GetMainLightShadowSamplingData();
half shadowStrength = GetMainLightShadowStrength();
ShadowAtten = SampleShadowmap(shadowCoord, TEXTURE2D_ARGS(_MainLightShadowmapTexture,
sampler_MainLightShadowmapTexture),
shadowSamplingData, shadowStrength, false);
#endif
#endif
}

CustomLight.hlsl에 CustomLight_Shadow 함수를 추가한 뒤 테스트해보면 두 가지 문제가 있다.
이유는 Keyword로 Multi Compile을 제대로 설정해줘야 하기 때문이다.
그림자는 유니티 엔진 내부에서 제어하고 있다.
그래서 그림자 관련 셰이더를 만든다면, 유니티에서 제어되고 있는 그림자 옵션과 동일하게 만들어야 사용할 수 있다.
Keyword
키워드는 Property의 일종이다. 경우에 따라 다른 셰이더 옵션을 활성시키는 기능이다.
키워드는 플랫폼별, 머테리얼별, 조건별로 변화하는 셰이더를 만들어야 할 때 유리하다.
if문은 실시간으로 필요에 따라 작동하지만,
키워드는 미리 지정한 선택대로 셰이더를 자동으로 분리해서 갖고 있다는 점이 다르다.
즉, 이 작업이 많아지면 셰이더의 개수가 제곱으로 늘어난다.
Keyword의 Node Settings
_로 변환된다
그림자는 직접광, 즉 Diffuse와 Specular에만 영향을 끼치는 것이므로
Diffuse + Specular에 Shadow를 곱해주면 된다.
Troubleshooting
책과 유니티 버전이 달라서인지 혼란이 조금 있었다. 책에서는Keyword로 Multi Compile을 제대로 설정해줘야 하기 때문이다.라는 언급이 있었는데, 확인해보니 Keyword를 추가해주지 않아도 Soft Shadow가 설정됐다. Soft Shadow 옵션을 켜니 뭔가 달라지긴 하는데, 이상하게도 바닥에 드리워진 그림자와 돼지에게 드리워진 그림자 둘 다 "Soft"하진 않아 보인다. 일단 그림자 드리워졌으니 넘어가기.
아직 남은 것들