오브젝트의 월드 변환을 통한 노말 변환합니다.
방향성 빛으로 작업합니다.
디퓨즈와 스펙큘러를 계산합니다.
metallic workflow을 사용합니다.
유니티의 PBS 알고리즘을 활용합니다.
렌더링과 관련된 네번째 튜토리얼이며, 조명 계산에 대해 살펴보고자 한다.
- 기본 조명 연산 결과
우리의 눈은 방사선을 감지할 수 있어서 사물을 볼 수 있다. 우리는 가시 광선으로 알고있는 전자기 스펙트럼의 일부를 볼 수 있지만, 나머지 스펙트럼은 우리에게 보이지 않습니다.
빛이 물체와 충돌하면 그 빛의 일부가 반사되고, 그 반사된 빛이 눈과 충돌하면 그것을 통해 우리는 대상을 볼 수 있다. 물체의 표면에 따라 반사광의 방향이 달라지는데 이 방향을 알 수 있도록 하는 것이 노말 벡터다.
'My First Lighting Shader'라는 쉐이더를 만든 후, 이 쉐이더를 사용하여 Material을 생성하고 씬의 오브젝트들에 Material에 할당합니다. 여러 결과를 볼 수 있도록 각 객체들의 행렬을 변환시켜줍니다.
Shader "Custom/My First Lighting Shader" { … }
- 3D 모델들
유니티의 3D 모델들은 정점 노말벡터를 포함합니다. 이를 fragment shader에 전달할 수 있다.
struct VertexData { float4 position : POSITION; float3 normal : NORMAL; // 정점으로 부터 노말을 받음 float2 uv : TEXCOORD0; }; struct Interpolators { float4 position : SV_POSITION; float2 uv : TEXCOORD0; float3 normal : TEXCOORD1; // 월드 변환된 노말을 리턴 }; Interpolators MyVertexProgram (VertexData v) { Interpolators i; i.uv = TRANSFORM_TEX(v.uv, _MainTex); i.position = mul(UNITY_MATRIX_MVP, v.position); i.normal = v.normal; return i; }
이를 통해 노말 벡터의 시각화를 할 수 있다.
float4 MyFragmentProgram (Interpolators i) : SV_TARGET { return float4(i.normal * 0.5 + 0.5, 1); // 정규화된 노말 벡터를 0 ~ 1의 값으로 바꿔 색으로 표현할 수 있도록 함 }
- 노말 벡터의 시각화된 색상
큐브의 각 면에 해당되는 4개의 정점은 같은 방향의 노말 벡터를 가지고 있어 평평해 보이지만, 구의 정점은 모두 다른 방향을 가리키고 있어 보간된 색을 볼 수 있습니다.
Dynamic Batching (동적 일괄 처리)
큐브의 색이 다 같을 것으로 예상했지만 다른 이유는 유니티의 동적 일괄 처리 때문에 다르게 보이게 됩니다. 유니티는 렌더링 호출을 줄이기 위해 정점 수가 적은 메쉬를 동적으로 변환합니다.
메쉬는 로컬 공간에서 월드 공간으로 변환하는데 그로 인해 노말 벡터의 방향이 변하기 때문에 색이 바껴 보인다. 설정을 통해 이를 해제할 수 있다.
- 동적 일괄 처리 설정
정적 배치 작업도 수행 가능한데, 정적 지오메트리에 대해 다르게 작동하지만 월드 공간 변환도 포함된다.
- 동적 일괄 처리 해제된 씬
Normals in World Space (월드 공간의 노말)
일괄 처리되는 객체가 아니라면 객체는 로컬 공간에 존재합니다. 따라서 노말도 로컬 공간에 존재하기 때문에 월드 공간으로 변환해야
합니다.
유니티에서는 unity_ObjectToWorld를 이용해 노말을 월드 공간으로 변환할 수 있습니다. 또한 월드 변환 행렬을 곱해줄 때 노말 벡터는 방향 벡터임으로 w 값에 0을 대입해줍니다.
Interpolators MyVertexProgram (VertexData v) { Interpolators i; i.position = mul(UNITY_MATRIX_MVP, v.position); i.normal = mul(unity_ObjectToWorld, float4(v.normal, 0)); i.uv = TRANSFORM_TEX(v.uv, _MainTex); return i; }
3x3 행렬로도 곱할 수 있다.
i.normal = mul((float3x3)unity_ObjectToWorld, v.normal);
- 정규화된 노말
벡터를 정규화했지만 각 축마다 크기의 값이 같지 않으면 예상했던 결과로 보이지 않습니다. 왜나하면 평면이 한 축으로 늘어나게 되면 그 노말 벡터도 같은 방식으로 늘어나지 않기 때문입니다.
-x축의 크기를 늘리고, 꼭지점과 법선을 ½씩 늘렸을 때.
-x축의 크기를 늘리고, 꼭지점과 법선을 2배씩 늘렸을 때.
행렬의 변환은 기본적으로 크기, 회전, 이동이며, T = SRP라고 표현할 수 있습니다.
노말 벡터는 방향 벡터이기 때문에 위치와 상관없기 때문에, S와 R만 곱한 값을 사용해도 되고 따라서 3x3 행렬로 곱할 수 있다.
스케일 값을 반전시키면서 회전 값은 유지하려면 행렬을
−
1
−
1
N
=
S
1
R
1
S
2
R
2
N=S1-1R1S2-1R2
로 바꿔야 합니다.
유니티는 월드에서 로컬로 변환하는 역행렬도 지원합니다. 그 역행렬은
−
1
−
1
−
1
−
1
−
1
O
=
R
2
S
2
R
1
S
1 입니다.
O-1=R2-1S2-1R1-1S1-1
이 역행렬은 크기나 회전, 하나의 역행렬을 나타내는 것이 아니라 전체 변환 행렬의 역행렬을 제공하기 때문에 원치 않는 결과를 피할 수 있습니다. 이를 통해
T
(
O
−
1
)
=
N
(O-1)T=N
- 원하던 월드 공간상의 노말 벡터의 색상
UnityCG에서는 이를 위해 UnityObjectToWorldNormal를 제공 합니다. 이를 이용하면 다양한 언어에서 사용할 수 있습니다.
Interpolators MyVertexProgram (VertexData v) { Interpolators i; i.position = mul(UNITY_MATRIX_MVP, v.position); i.normal = UnityObjectToWorldNormal(v.normal); i.uv = TRANSFORM_TEX(v.uv, _MainTex); return i; }
정점 이외에 값들은 보간된 노말벡터를 가지게 되는데, 이 노말 벡터들의 길이는 짧습니다. 그래서 우린 픽셀 쉐이더에서 다시 노말 벡터를 정규화 해야 됩니다.
float4 MyFragmentProgram (Interpolators i) : SV_TARGET { i.normal = normalize(i.normal); return float4(i.normal * 0.5 + 0.5, 1); }
- 재정규화한 노말 벡터
이를 통해 결과는 좋아지지만 그 효과는 미비하기 때문에 모바일 기기에서는 하지 않는 것이 좋은 방법일 수 있습니다.
- 재정규화를 안했을 때 볼 수 있는 최악의 경우
우린 빛을 반사하는 물체를 볼 수 있습니다. 이런 빛의 반사는 여러가지가 있는데 우선 난반사광을 보겠습니다.
반사된 빛은 표면에서 반사되는 것이 아니라 빛이 표면을 통과했다가 튕겨져 나오는 빛이며, 그 빛은 몇번 분열하게 됩니다. 표면에서 반사되는 빛의 양은 광선이 표면에 부딪치는 각도에 따라 달라집니다. 0도를 기준으로 90도 까지 감소하며 그 이상의 값은 90도와 같습니다. 확산된 빛의 양은 빛의 방향과 표면 정상 사이의 각도의 코사인에 정비례합니다. 이것은 램버트의 코사인 법칙으로 알려져 있다.
- 위쪽에서 오는 빛을 받은 장면
디퓨즈 값을 계산할 때 내적을 사용하게 되는데 내적의 값은 - 1 ~ 1 이므로 음수 값은 제외시켜줘야 합니다. 이를 max 함수를 이용해 할 수 있습니다.
return max(0, dot(float3(0, 1, 0), i.normal));
max 함수를 이용해 값을 0 ~ 1 사이의 값으로 고정시킬 수 있습니다.
return saturate(dot(float3(0, 1, 0), i.normal));
UnityStandardBRDF 파일에는 DomPlamped 함수가 있는데 이를 이용해 같은 결과를 얻을 수 있습니다.
#include "UnityCG.cginc" #include "UnityStandardBRDF.cginc" … float4 MyFragmentProgram (Interpolators i) : SV_TARGET { i.normal = normalize(i.normal); return DotClamped(float3(0, 1, 0), i.normal); }
// #include "UnityCG.cginc" #include "UnityStandardBRDF.cginc"
-UnityStandardBRDF. 파일의 계층 구조
이제 고정된 빛을 사용하는 것이 아니라 씬에 있는 빛을 사용해보려 합니다. 그 빛은 방향성 빛으로 어디서든 같은 방향으로 빛이 들어오는 것으로 되어 있습니다. 현실과 다르지만 태양은 멀리 떨어져 있기 때문에 그와 근사한 결과를 볼 수 있습니다.
- 씬의 기본 빛
float3 lightDir = _WorldSpaceLightPos0.xyz; return DotClamped(lightDir, i.normal);
조명 모드는 장면을 어떻게 렌더링 할 것인가에 따라 다르지만 우린 기본값을 사용하겠습니다.
- 렌더링 모드 선택 옵션
우리는 ForwardBase 패스를 사용하려 합니다. 이 패스는 렌더링 경로를 통해 무언가를 렌더링 할 때 사용되는 첫번째 패스입니다. 또한 우리에게 장면의 주요 방향 조명에 접근할 수 있게 해줍니다.
Pass { Tags { "LightMode" = "ForwardBase" } CGPROGRAM … ENDCG }
빛의 색상이 항상 흰색일 수는 없습니다. 그래서 각 광원은 고유의 색을 가질 수 있으며 이
색상은 _LightColor0 변수를 통해 확인할 수 있습니다.
이 변수는 빛의 색상에 강도를 곱한 값이며, RGBA를 다 제공하지만 RGB만 있어도 됩니다.
float4 MyFragmentProgram (Interpolators i) : SV_TARGET { i.normal = normalize(i.normal); float3 lightDir = _WorldSpaceLightPos0.xyz; float3 lightColor = _LightColor0.rgb; float3 diffuse = lightColor * DotClamped(lightDir, i.normal); return float4(diffuse, 1); }
- 사용자 정의 광원 색상이 적용된 장면
재질의 확산 반사율의 색을 Albedo라고 합니다. 따라서 RGB 각 채널들이 어느정도 반사하는지를 나타내고, 나머지 채널들은 흡수됩니다.
float4 MyFragmentProgram (Interpolators i) : SV_TARGET { i.normal = normalize(i.normal); float3 lightDir = _WorldSpaceLightPos0.xyz; float3 lightColor = _LightColor0.rgb; float3 albedo = tex2D(_MainTex, i.uv).rgb * _Tint.rgb; float3 diffuse = albedo * lightColor * DotClamped(lightDir, i.normal); return float4(diffuse, 1); }
메인 텍스쳐 프로퍼티를 Albedo로 변경하겠습니다.
Properties { _Tint ("Tint", Color) = (1, 1, 1, 1) _MainTex ("Albedo", 2D) = "white" {} }
- Albedo 및 선형 공간에서 광원을 확산 시킵니다.
확산광 이외에도 정반사광도 있습니다. 정반사광은 빛이 확산되는 게 아니라 광원이 표면을 치고 있는 각도로 반사된 광원을 통해 나타냅니다. 이 현상은 거울의 반사와 같은 원리입니다.
확산광과 다르게 정반사광은 눈 또는 카메라의 위치가 중요합니다. 광원 방향 벡터와 노말 벡터로 얻은 반사 벡터와 바라보는 방향을 연산해야 되기 때문입니다.
그래서 표면에서부터 관측자까지의 방향을 알아야합니다. 이를 위해 표면과 카메라의 월드 변환된 위치가 필요합니다.
object-to-world matrix,를 정점의 위치와 곱해 픽셀 쉐이더에 보냅니다.
struct Interpolators { float4 position : SV_POSITION; float2 uv : TEXCOORD0; float3 normal : TEXCOORD1; float3 worldPos : TEXCOORD2; }; Interpolators MyVertexProgram (VertexData v) { Interpolators i; i.position = mul(UNITY_MATRIX_MVP, v.position); i.worldPos = mul(unity_ObjectToWorld, v.position); i.normal = UnityObjectToWorldNormal(v.normal); i.uv = TRANSFORM_TEX(v.uv, _MainTex); return i; }
카메라의 위치는 _WorldSpaceCameraPos를 통해 접근할 수 있습니다. 이 값을 표면 위치에서 카메라 위치를 뺀 후 정규화합니다.
float4 MyFragmentProgram (Interpolators i) : SV_TARGET { i.normal = normalize(i.normal); float3 lightDir = _WorldSpaceLightPos0.xyz; float3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos); float3 lightColor = _LightColor0.rgb; float3 albedo = tex2D(_MainTex, i.uv).rgb * _Tint.rgb; float3 diffuse = albedo * lightColor * DotClamped(lightDir, i.normal); return float4(diffuse, 1); }
Reflecting Light
반사광은 들어오는 광선의 방향과 표면의 노말 벡터를 기반으로 구합니다. 이때 노말 벡터나 광원의 방향의 부호를 반대로 해줘야 합니다.
float3 reflectionDir = reflect(-lightDir, i.normal); return float4(reflectionDir * 0.5 + 0.5, 1);
- 정반사광의 방향
표면이 완전히 매끄럽다면 표면의 각과 딱 맞는 정점만 반사광을 볼 수 있습니다. 하지만 사물의 표면은 완벽하게 매끄럽지 않기 떄문에 표면의 노말 벡터는 생각한 것과는 많이 다를 수 있습니다.
그래서 우린 시야 방향과 반사 방향이 정확히 일치하지 않더라도 일부 정반사광을 볼 수 있습니다. 정반사광이 유효한 정점에서 멀어질수록 정반사광이 약해지는데 이는 내적을 이용해 얼마나 많은 빛이 도달하는지 파악할 수 있습니다.
return DotClamped(viewDir, reflectionDir);
- 정반사광 반사
이 효과로 생성되는 Hightlight의 크기는 재질의 거칠기에 따라 다릅니다.
매끄러운 재질은 빛을 더 잘 받으므로 Light가 작게 나옵니다.
우리는 이 효과를 material속성으로 만들어 Smoothness를 제어할 수 있습니다.
일반적으로 0과 1 사이값으로 정의되므로 슬라이더로 만듭시다.
Properties { _Tint ("Tint", Color) = (1, 1, 1, 1) _MainTex ("Texture", 2D) = "white" {} _Smoothness ("Smoothness", Range(0, 1)) = 0.5 } … float _Smoothness;
내적을 높여서 Hightlight 부분을 좁힙니다.
우리는 Smoothness값을 사용하지만, 우리가 원하는 효과를 얻기 위해서는 1보다 훨씬 큰 값을
대입해야하므로, 100을 곱해줍니다.
return pow( DotClamped(viewDir, reflectionDir), _Smoothness * 100 );
Pretty smooth.
현재 Blinn반사 모델에 따라서 반사광을 계산하고 있습니다.
그러나 가장 자주 사용되는 모델은 Blinn_Phong입니다.
Blinn_Phong은 빛의 vector와 방향의 vector 사이의 중간의 vector를 사용합니다.
Normal vector와 Half vector 사이의 내적이 반사 정도를 결정합니다.
// float3 reflectionDir = reflect(-lightDir, i.normal); float3 halfVector = normalize(lightDir + viewDir); return pow( DotClamped(halfVector, i.normal), _Smoothness * 100 );
Blinn-Phong specular.
이러한 방법은 더 큰 Hightlight를 생성하지만
그러나 높은 Smoothness 값을 사용하면, HightLight는 여전히 작게 생성됩니다.
이 두가지의 방법은 비슷하지만, 결과는 Phong보다는 조금 더 현실적으로 보여집니다.
한가지 큰 한계는 뒤에서 조명되는 Object에 대해서 유효하지 않은 HightLight를 생성할 수 있다는 것입니다.
Incorrect specular, with smoothness 0.01.
이러한 artifacts는 낮은 Smoothness 값을 사용할 때 눈에 띄게 됩니다.
이 artifacts는 그림자를 사용하거나 Light_Angle을 조정하여 반사를 Fade_Out하여 숨길수도 있습니다.
유니티의 legacy_Sahder도 이 문제를 가지고 있으므로, 곧 다른 조명방법으로 넘어갈 것입니다.
물론 정반사의 색은 광원의 색과 일치하므로 고려해봅시다
float3 halfVector = normalize(lightDir + viewDir); float3 specular = lightColor * pow( DotClamped(halfVector, i.normal), _Smoothness * 100 ); return float4(specular, 1);
그러나 이것이 전부는 아닙니다. 반사 색상 또한 재료에 따라 다릅니다.
이는 albedo와 다릅니다. 금속은 albedo가 거의 없으며 반사성이 강해 종종 반사된 색을 띄는 경향이 있습니다.
반대로 비금속은 뚜렷한 albedo를 갖는 반면에, 반사성이 약해 색채가 거의 없습니다.
우리는 albedo처럼 color의 투영을 정의하기 위해 texture와 color를 추가할 수 있습니다.
그러나 다른 텍스쳐로 번거롭게 하지말고 그냥 color값을 사용합니다.
Properties { _Tint ("Tint", Color) = (1, 1, 1, 1) _MainTex ("Albedo", 2D) = "white" {} _SpecularTint ("Specular", Color) = (0.5, 0.5, 0.5) _Smoothness ("Smoothness", Range(0, 1)) = 0.1 } … float4 _SpecularTint; float _Smoothness; … float4 MyFragmentProgram (Interpolators i) : SV_TARGET { … float3 halfVector = normalize(lightDir + viewDir); float3 specular = _SpecularTint.rgb * lightColor * pow( DotClamped(halfVector, i.normal), _Smoothness * 100 ); return float4(specular, 1); }
color의 속성을 사용하여 벙반사의 색상 및 강도를 제어할 수 있습니다.
Tinted specular reflection.
정반사 및 난반사는 조명 퍼즐의 두 부분입니다.
우리는 사진을 더 완벽하게 만들기 위해서 함께 추가할 수 있습니다.
return float4(diffuse + specular, 1);
Diffuse plus specular, in gamma and linear space.
난반사와 정반사를 함께 추가할 때 문제가 있습니다.
이 결과가 광원보다 밝을 수 있습니다.
완전히 흰색을 반사하는 것과 낮은 Smoothness를 함께 사용하면 이 문제가 나타납니다.
White specular, 0.1 smoothness. Too bright.
이처럼 빛이 표면에 부딪힐 때 빛의 일부가 반사 광선처럼 튀어 오릅니다.
나머지는 표면을 관통하거나 확산빛으로 되돌아 오거나 흡수합니다.
그러나 현재 우리는 이것을 고려하지 않고, 대신에 빛을 완전히 반사시켜 확산시킵니다.
그래서 우리는 빛의 에너지를 두 배로 늘릴 수 있습니다.
우리는 물질의 확산과 반사 부분의 합이 1을 초과하지 않도록 해야합니다.
초과 할 시 Light효과가 사라지기 때문입니다.
합계가 1보다 작으면, 빛의 일부가 흡수된다는 의미입니다.
일정한 반사성 color을 사용함에 따라, 반사율 color에 1을 곱하여 반사율의 color값을 간단히
정돈할 수 있습니다.
그러나 이 작업을 수동으로 하기에는 불편합니다. 특히 특정 albedo 색조를 사용 할 땐 더더욱 그러므로
쉐이더에서 이를 해결해봅니다.
float3 albedo = tex2D(_MainTex, i.uv).rgb * _Tint.rgb; albedo *= 1 - _SpecularTint.rgb;
No longer too bright.
diffuse 와 specular는 이제 연결이 되므로, 반사가 강하면 확산 부분이 희미해집니다.
검은 색의 반사 color는 반사가 없고, 이때 albedo는 최대의 강도로 보입니다
하얀 거울같은 색조는 완벽한 거울같이 만들어 내기 때문에 albedo는 완전히 제거됩니다.
에너지 보존.
이 방법은 반사 color가 회색 음영의 color일 때 크게 작동합니다.
그러나 다른 color을 사용할 때 이상한 결과를 만들어 냅니다. 예를 들어, 빨간색의 반사 color는
확산 부분의 빨간색 diffuse만 줄입니다. 이 결과 albedo는 'sync color'로 착색됩니다.
Monochome energy conservation.
예상대로 우니티에는 에너지 보존을 담당하는 유틸리티 기능을 가지고 있습니다.
EnergyConservationBetweenDiffuseAndSpecular이며 UnityStandardUtils에 정의되어 있습니다.
#include "UnityStandardBRDF.cginc" #include "UnityStandardUtils.cginc"
Include file hierarchy, starting at UnityStandardUtils.
이 함수는 albedo 및 반사 color를 입력하면 조정 도니 albedo를 출력합니다.
또한 one-minus-reflectivity(첫번째 반사율)이라고 하는 세번째 출력 매개변수도 있습니다.
이 출력값은 반사 강도를 뺀 것입니다. 그래서 우리가 albedo를 곱하는 요인입니다.
이것은 다른 조명 계산에도 반사가 필요하기 때문에 출력해 줍니다.
float3 albedo = tex2D(_MainTex, i.uv).rgb * _Tint.rgb; // albedo *= 1 -// max(_SpecularTint.r, max(_SpecularTint.g, _SpecularTint.b)); float oneMinusReflectivity; albedo = EnergyConservationBetweenDiffuseAndSpecular( albedo, _SpecularTint.rgb, oneMinusReflectivity );
기본적으로 두 가지 종류의 재료가 있습니다. 금속이 있고 비금속이 있습니다.
후자는 또한 유전물질로 알려져있고, 현재 강한 반사성 color를 사용하여 금속을 만들 수 있습니다.
그리고 약한 단색color를 사용하여 유전물질을 만들 수 있습니다. 이것이 specular workflow 입니다.
우리가 금속과 비금속을 toggle할 수 있다면 더 간단할 것입니다.
금속은 albedo가 없으므로 대신에 color_data를 반사 color로 사용할 수 있습니다.
어쨌거나 비금속에서는 color의 반사가 없으므로, 별도의 반사 color가 필요하지 않습니다.
이를 Metallic Workflow라고 합니다. 이 방법으로 진행합시다.
우리는 반사 color를 대체하기 위해서 다른 슬라이더 속성을 금속 toggle로 사용할 수 있습니다.
일반적으로 금속 또는 비금속으로 0 또는 1 사이로 설정해야 합니다.
이 사이의 값은 금속과 비금속의 성분이 혼합된 정도를 나타냅니다.
Properties { _Tint ("Tint", Color) = (1, 1, 1, 1) _MainTex ("Albedo", 2D) = "white" {} // _SpecularTint ("Specular", Color) = (0.5, 0.5, 0.5) _Metallic ("Metallic", Range(0, 1)) = 0 _Smoothness ("Smoothness", Range(0, 1)) = 0.1 } … // float4 _SpecularTint; float _Metallic; float _Smoothness;
Metallic slider.
이제 우리는 albedo와 금속의 속성으로부터 반사의 color를 끌어낼 수 있습니다.
그러면 albedo에 1을 곱하여 금속 값을 간단히 곱할 수 있습니다.
float3 specularTint = albedo * _Metallic; float oneMinusReflectivity = 1 - _Metallic; // albedo = EnergyConservationBetweenDiffuseAndSpecular(// albedo, _SpecularTint.rgb, oneMinusReflectivity// ); albedo *= oneMinusReflectivity; float3 diffuse = albedo * lightColor * DotClamped(lightDir, i.normal); float3 halfVector = normalize(lightDir + viewDir); float3 specular = specularTint * lightColor * pow( DotClamped(halfVector, i.normal), _Smoothness * 100 );
그러나 이는 지나치게 단순화 된 것입니다.
순수한 유전체라도 여전히 반사를 조금 가지고 있습니다.
따라서 specular의 강도와 반사 값은 금속 슬라이더의 값과 정확하게 일치하지 않습니다.
그리고 이는 color공간에 영향을 받습니다. 다행스럽게도, UnityStandardUtils에는
DiffuseAndSpecularFromMetallic 함수가 있습니다.
float3 specularTint; // = albedo * _Metallic; float oneMinusReflectivity; // = 1 - _Metallic;// albedo *= oneMinusReflectivity; albedo = DiffuseAndSpecularFromMetallic( albedo, _Metallic, specularTint, oneMinusReflectivity );
Metallic workflow.
하나의 세부사항은 금속 슬라이더 자체가 감마 공간에 있어야 한다는 것입니다. 그러나 단일 값은 선형
공간에 랜더링할 때 유니티에 의해 자동으로 감마가 보정이 되지 않습니다.
감마 속성을 사용하여 유니티에 금속성 슬라이더에 감마 보정을 적용해야 한다고 알릴 수 있습니다.
[Gamma] _Metallic ("Metallic", Range(0, 1)) = 0
불행하게도, 지금까지 정반사는 비금속성에 대해 모호해졌습니다.
이 옵션을 향상시키기 위해서는 조명을 계산하는 더 좋은 방법이 필요합니다.
Blinn_Phong은 오랫동안 게임 산업에 사용되었지만, 요즘에는 physically-based shading(PBS)의
쉐이딩 방법이 좋습니다. PBS는 더 현실적이고, 예측할 수 있기 때문입니다.
이상적으로, 게임 엔진과 모델링 도구는 모두 동일한 shading 알고리즘을 사용합니다.
따라서 여러 콘텐츠를 훨씬 쉽게 제작할 수 있습니다.
업계헤서는 표준 PBS 구현에 수렴하고 있습니다.
유니티의 표준 Shader는 PBS접근 방식을 사용합니다.
유니티에는 실제로 여러 구현법이 있습니다.
대상 플랫폼, 하드웨어 및 API수준에 따라 사용할 대상을 결정합니다.
이 알고리즘은 UnityPBSLighting에 정의 된 UNITY_BRDF_PBS 매크로를 통해 액세스 할 수 있습니다.
BRDF는 양방향 반사율 분포 함수를 나타냅니다.
// #include "UnityStandardBRDF.cginc"// #include "UnityStandardUtils.cginc" #include "UnityPBSLighting.cginc"
Partial include file hierarchy, starting at UnityPBSLighting.
이 함수는 꽤 많은 수학적 내용을 포함하므로 자세한 것은 다루지 않겠습니다.
Blinn_Phong과는 다른 방식으로 난반사 및 정반사를 계산합니다.
그 외에도 Fresnel반사가 있습니다.
이는 여러 각도에서 객체를 볼때 얻는 반사를 추가합니다.
일단 우리가 환경을 포함시켜준다면 분명해 질 것입니다.
유니티가 최상의 BRDF함수를 선택하도록 하기 위해서 최소한 쉐이더레벨3.0을 목표로 해야합니다.
우리는 pragma문을 사용하여 이 작업을 수행합니다.
CGPROGRAM #pragma target 3.0 #pragma vertex MyVertexProgram #pragma fragment MyFragmentProgram
유니티의 BRDF 함수는 알파 성분이 항상 1로 설정된 RGBA색상을 반환합니다.
따라서 우리는 직접적으로 우리의 fragment 프로그램이 이 결과를 반환하도록 할 수 있습니다.
// float3 diffuse =// albedo * lightColor * DotClamped(lightDir, i.normal);// float3 halfVector = normalize(lightDir + viewDir);// float3 specular = specularTint * lightColor * pow(// DotClamped(halfVector, i.normal),// _Smoothness * 100// ); return UNITY_BRDF_PBS();
물론 우리가 인수로 호출해야합니다.
각 함수에는 8개의 매개변수가 있으며 처음 두가지는 물질의 확산 및 반사 color입니다.
return UNITY_BRDF_PBS( albedo, specularTint );
다음 두 인자는 반사율과 거칠기를 추론합니다.
이 매개변수는 -1 형식이어야 하며 이 형식이 최적화입니다.
우리는 이미 DiffuseAndSpecularFromMetallic에서 MinusReflectivity를 얻었습니다.
Smoothness는 거칠기의 반대이므로 직접 사용할 수 있습니다.
return UNITY_BRDF_PBS( albedo, specularTint, oneMinusReflectivity, _Smoothness );
물론 표면 법선과 view방향도 필요합니다.
return UNITY_BRDF_PBS( albedo, specularTint, oneMinusReflectivity, _Smoothness, i.normal, viewDir );
마지막 두 인수는 직접 및 간접조명이어야 합니다.
UnityLightingCommon은 유니티 shader가 빛의 데이터를 전달하는데 사용하는 간단한
unitylight구조를 정의합니다. light의 color, vector 및 확산 용어인 ndotl 값을 포함합니다.
이 구조는 우리의 편의를 위한 것이며, 컴파일 된 코드에는 영향을 주지 않습니다
우리는 이러한 모든 정보를 가지고 있으므로, 우리가 해야할 일은 이를 구조에 넣고
일곱번째 변수에 전달하는 것입니다.
UnityLight light; light.color = lightColor; light.dir = lightDir; light.ndotl = DotClamped(i.normal, lightDir); return UNITY_BRDF_PBS( albedo, specularTint, oneMinusReflectivity, _Smoothness, i.normal, viewDir, light );
마지막으로, 간접적인 빛에 대한 것 입니다.
UnityLndingCommon에서도 정의 된 UnityIndirect 구조를 사용해야합니다.
여기에서는 확산광과 반사광이라는 두 가지의 color가 있습니다.
확산color은 주변광을 나타내며 반사color는 환경 반사를 나타냅니다.
간접조명은 후에 다루므로, 간단히 이 색상을 검정색으로 설정합니다.
float4 MyFragmentProgram (Interpolators i) : SV_TARGET { i.normal = normalize(i.normal); float3 lightDir = _WorldSpaceLightPos0.xyz; float3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos); float3 lightColor = _LightColor0.rgb; float3 albedo = tex2D(_MainTex, i.uv).rgb * _Tint.rgb; float3 specularTint; float oneMinusReflectivity; albedo = DiffuseAndSpecularFromMetallic( albedo, _Metallic, specularTint, oneMinusReflectivity ); UnityLight light; light.color = lightColor; light.dir = lightDir; light.ndotl = DotClamped(i.normal, lightDir); UnityIndirect indirectLight; indirectLight.diffuse = 0; indirectLight.specular = 0; return UNITY_BRDF_PBS( albedo, specularTint, oneMinusReflectivity, _Smoothness, i.normal, viewDir, light, indirectLight ); }
Nonmetal and metal, in gamma and linear space.