Rendering 5 Multiple Lights
객체 당 여러 광원을 렌더링합니다.
다양한 조명 유형을 지원합니다.
light cookies를 사용합니다.
정점 조명을 계산합니다.
구형 고조파를 포함합니다.
이것은 렌더링에 관한 튜토리얼 시리즈의 다섯 번째 부분입니다. 이전 부는 single directional light을 도입했습니다. 이제 여러 조명에 대한 지원을 추가 할 것입니다.
이 튜토리얼은 Unity 5.4.0b21을 사용하여 작성되었습니다.
흥미로운 조명과 함께 지루한 흰색 영역.
여러 라이트에 대한 지원을 쉐이더에 너무 추가하면 패스를 더 추가해야합니다. 이 패스는 거의 동일한 코드를 포함하게됩니다. 코드 중복을 방지하기 위해 셰이더 코드를 포함 파일로 이동합니다.
Unity에는 셰이더 포함 파일을 만드는 메뉴 옵션이 없습니다. 따라서 운영 체제의 파일 브라우저를 통해 수동으로 프로젝트의 자산 폴더로 이동해야합니다. 조명 쉐이더와 같은 폴더에 My Lighting.cginc 일반 텍스트 파일을 만듭니다 . 셰이더를 복사하고 이름을 변경 한 다음 내용을 지워서 그렇게 할 수 있습니다.
첫 번째 포함 파일.
조명 쉐이더의 모든 코드를 #pragma 명령문 바로 아래에서부터이 파일 바로 아래까지이 파일에 복사합니다 ENDCG. 이 코드는 더 이상 셰이더 패스 내부에 있지 않으므로 더 이상 들여 쓰지 않습니다.
#include "UnityPBSLighting.cginc" … float4 MyFragmentProgram (Interpolators i) : SV_TARGET { … }
이제 이 파일을 쉐이더에 포함시켜 이전에 사용하던 코드를 대체 할 수 있습니다. 동일한 폴더에 있기 때문에 직접 참조 할 수 있습니다.
CGPROGRAM #pragma target 3.0 #pragma vertex MyVertexProgram #pragma fragment MyFragmentProgram #include "My Lighting.cginc" ENDCG
이미 알고 있듯이, 포함 파일 자체는 다른 포함 파일을 포함 할 수 있습니다. 동일한 파일을 포함하는 파일을 포함 시키면 코드 중복이 발생합니다. 이렇게하면 코드 재정의에 대한 컴파일러 오류가 발생합니다.
이러한 재 정의를 방지하기 위해 정의 검사로 포함 파일을 보호하는 것이 일반적입니다. 이것은 특정 정의가 작성되었는지 여부를 확인하기위한 사전 프로세서 검사입니다. 정의는 단순히 include 파일의 이름과 일치하는 고유 식별자입니다. 아무것도 아닐지라도 정의 할 수 있습니다. 이 경우 식별자를 사용합니다.
MY_LIGHTING_INCLUDED.
#define MY_LIGHTING_INCLUDED#include "UnityPBSLighting.cginc" …
이제 include 파일의 전체 내용을 if-pre-processor 블록에 넣을 수 있습니다.
MY_LIGHTING_INCLUDED
#if !defined(MY_LIGHTING_INCLUDED)#define MY_LIGHTING_INCLUDED #include "UnityPBSLighting.cginc" … #endif
일반적으로 이 include-file guard 안의 코드는 들여 쓰여지지 않습니다.
우리의 두 번째 빛은 다시 한 번 directional light이 될 것입니다. 주 조명을 복제하고 색상과 회전을 변경하여 서로 구분할 수 있도록하십시오. 또한 intensity 슬라이더를 0.8로 줄이십시오. Unity는 자동으로 주광을 결정하기 위해 intensity 를 사용합니다.
Two directional lights.
두 개의 directional light가 있지만 시각적 인 차이는 없습니다. 우리는 한 번에 오직 하나만 활성화시킴으로써 그들의 빛을 독립적으로 볼 수 있습니다. 그러나 둘 다 활성화되어 있으면 주광 만 효과가 있습니다.
둘 중 하나만 가능합니다.
셰이더는 하나의 빛만 계산하기 때문에 하나의 빛만 볼 수 있습니다. 전방베이스 패스는 주 지향성 라이트 용입니다. 추가 조명을 렌더링하려면 추가 패스가 필요합니다.
셰이더 패스 코드를 복제하고 새 모드의 라이트 모드를 ForwardAdd로 설정하십시오 . Unity는이 패스를 사용하여 추가 조명을 렌더링합니다.
SubShader { Pass { Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma target 3.0 #pragma vertex MyVertexProgram #pragma fragment MyFragmentProgram #include "My Lighting.cginc" ENDCG } Pass { Tags { "LightMode" = "ForwardAdd" } CGPROGRAM #pragma target 3.0 #pragma vertex MyVertexProgram #pragma fragment MyFragmentProgram #include "My Lighting.cginc" ENDCG } }
이제 우리는 주광 대신 보조 빛을 봅니다. Unity는 둘 다 렌더링하지만 추가 패스는 기본 패스의 결과를 덮어 씁니다. 이것은 잘못된 것입니다. 추가된 패스는 결과를 기본 패스에 추가하고 그것을 대체하지 않아야합니다. GPU에 추가 패스의 블렌드 모드를 변경하여이를 수행하도록 지시 할 수 있습니다.
새롭고 오래된 픽셀 데이터가 결합되는 방법은 두 가지 요소에 의해 정의됩니다. 새롭고 오래된 데이터에 이러한 요소가 곱해지고 최종 결과가 추가됩니다. 기본 모드는 블렌딩이 아닙니다 . 이러한 패스의 결과는 이전에 프레임 버퍼에 있었던 모든 것을 대체했습니다. 프레임 버퍼에 추가하려면 블렌드 모드 를 사용하도록 지시해야 합니다. 이것을 additive blending이라고합니다.
Tags { "LightMode" = "ForwardAdd" } Blend One One CGPROGRAM
두 빛이 함께 추가되었습니다.
객체가 처음 렌더링 될 때 GPU는 이미 해당 픽셀에 렌더링 된 다른 모든 객체 앞에 조각이 있는지 확인합니다. 이 거리 정보는 GPU의 깊이 버퍼 (Z 버퍼라고도 함)에 저장됩니다. 따라서 각 픽셀에는 색상과 깊이가 모두 있습니다. 이 깊이는 픽셀 당 가장 가까운 표면까지의 거리를 나타냅니다.
fragment 앞에 렌더링 할 내용이 없다면 현재 카메라에 가장 가까운 표면입니다. GPU는 계속 진행되어 fragment 프로그램을 실행합니다. 픽셀의 색상을 덮어 쓰고 새로운 깊이를 기록합니다.
fragment 가 이미 존재하는 것보다 더 멀리 끝나면 그 앞에 뭔가가 있습니다. 이 경우, 우리는 그것을 볼 수없고 전혀 렌더링되지 않을 것입니다.
이 과정은 이미 존재하는 것에 추가하는 것을 제외하고는 2 차 조명에 대해 반복됩니다. 다시 한번 말하지만 fragment 프로그램은 우리가 렌더링하고있는 것의 앞에 아무것도없는 경우에만 실행됩니다. 그렇다면, 우리는 이전의 패스와 동일한 깊이로 끝납니다. 왜냐하면 그것은 같은 오브젝트를위한 것이기 때문입니다. 그래서 우리는 똑같은 깊이 값을 기록하게됩니다.
깊이 버퍼에 두 번 쓰는 것은 필요하지 않으므로 사용하지 마십시오. 셰이더 문을 사용하여 수행됩니다.
Blend One One ZWrite Off
진행 상황을 더 잘 이해하기 위해 game view의 오른쪽 상단에있는 Stats panel을 사용하도록 설정할 수 있습니다. saved by batching뿐만 아니라 amount of batches을 살펴보십시오. 이들은 draw calls을 나타냅니다. 메인 라이트 만 활성 상태로하십시오.
Five batches, seven total.
여섯 개의 물체를 가지고 있기 때문에 6 개의 배치를 기대할 수 있습니다. 그러나 동적 배치를 사용하면 세 개의 모든 큐브가 단일 배치로 결합됩니다. 따라서 네 개의 배치를 기대할 수 있습니다. 두 개는 저장됩니다. 그러나 당신은 다섯 배치를 가질 수 있습니다.
extra batch는 dynamic shadows로 인해 발생합니다. Edit / Project Settings / Quality 통해 quality settings에서 그림자를 완전히 비활성화하여 제거하십시오 . 현재 editor에서 사용중인 quality settings을 조정했는지 확인하십시오.
No more shadows, four batches.
예를 들어 game view를 클릭하여 통계 업데이트를 트리거해야 할 수도 있습니다. 그 후에 draw calls은 4 개가되어야하며, 2 개는 일괄 처리에 의해 저장됩니다. 그런 다음 secondary light을 활성화하십시오.
Two lights, twelve batches.
각 객체는 이제 두 번 렌더링되기 때문에 여섯 개가 아닌 일괄 처리가 끝납니다. 이것은 예상된 결과입니다. 예상하지 못한 것은 동적 일괄 처리가 더 이상 작동하지 않는다는 것입니다. 유감스럽게도 Unity의 동적 일괄 처리는 최대 한 개의 directional light의 영향을받는 오브젝트에만 작동합니다. second light을 활성화하면 이 최적화를 불가능하게 만들었습니다.
장면 렌더링 방법에 대한 더 나은 결과를 얻으려면 frame debugger를 사용할 수 있습니다. Window / Frame Debugger를 통해 엽니다 .
Frame debugger window.
활성화되면 프레임 디버거를 사용하여 개별 draw call을 단계별로 수행 할 수 있습니다. 창 자체에는 각 draw call의 세부 정보가 표시됩니다. game view는 선택된 draw call을 포함하여 렌더링 된 것을 보여줍니다.
카메라에 가까운 불투명 한 물체를 먼저 그리는 것이 바람직합니다. 깊이 방향 버퍼로 인해 숨겨진 조각이 건너 뛸 수 있기 때문에이 전면에서 후면 그리기 순서는 효율적입니다. 대신에 앞뒤로 그릴 경우 더 먼 물체의 픽셀을 계속 덮어 씁니다. 이는 오버 드로 (overdraw)로 알려져 있으며 가능한 한 많이 피해야합니다.
유니티는 오브젝트를 앞뒤로 정렬하지만 그리기 순서를 결정하는 것은 오브젝트 만이 아닙니다. GPU 상태를 변경하는 것도 비용이 많이 소요되므로 최소화해야합니다. 이것은 유사한 객체를 함께 렌더링하여 수행됩니다. 예를 들어, Unity는 구체와 큐브를 그룹으로 렌더링하는 것을 선호합니다. 그 이유는 메시와 메시를 자주 전환 할 필요가 없기 때문입니다. 마찬가지로 Unity는 동일한 자료를 사용하는 객체를 그룹화하는 것을 선호합니다.
Directional lights 만이 유일한 유형의 빛이 아닙니다. GameObject / Light / Point Light 를 통해 point light을 추가합시다 .
A point light.
잘 살펴 보려면 directional lights을 모두 비활성화하십시오. 그런 다음 point light을 조금 움직여보십시오.
아래에서 위로 빛을 이동하십시오.
빛은 아주 이상하게 행동합니다. 프레임 디버거를 사용하면 객체가 먼저 단색 검정색으로 렌더링 된 다음 다시 이상한 빛으로 렌더링됩니다.
첫 번째 패스는 기본 패스입니다. active directional light이 없더라도 항상 렌더링됩니다. 그래서 우리는 검은 실루엣으로 끝납니다.
두 번째 패스는 다시 additive 패스입니다. 이번에는 directional light 대신 point light을 사용합니다. 그러나 우리의 코드는 여전히 directional light을 지니고 있습니다. 이 문제를 해결해야합니다.
우리의 빛은 점점 복잡해지기 때문에 그것을 만드는 코드를 별도의 함수로 옮기십시오. 이 함수를 함수 바로 위에 배치하십시오
UnityLight CreateLight (Interpolators i) { UnityLight light; light.dir = _WorldSpaceLightPos0.xyz; light.color = _LightColor0.rgb; light.ndotl = DotClamped(i.normal, light.dir); return light;}
이제 우리는 단순화 할 수 있습니다.
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, CreateLight(i), indirectLight ); }
The _WorldSpaceLightPos0 변수는 현재 빛의 위치를 포함합니다. 그러나 directional light의 경우 실제로 빛쪽으로 향하는 방향을 유지합니다. point light을 사용 했으므로 변수에는 실제로 그 이름에서 알 수있는 데이터가 포함되어 있습니다. 그래서 우리는 빛의 방향을 스스로 계산해야합니다. 이것은 프래그먼트의 월드 위치를 빼고 결과를 정규화하여 수행됩니다.
light.dir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos);
위치에서 방향을 유도합니다.
directional light의 경우 방향을 알면 충분합니다. 무한히 멀리 있다고 가정합니다. 그러나 point light은 명확한 위치를 가지고 있습니다. 이것은 물체의 표면까지의 거리가 효과가 있음을 의미합니다. 빛이 멀리 떨어지면 어두워집니다. 이것은 빛의 감쇠로 알려져 있습니다.
direction light의 경우, 감쇠는 천천히 변화하여 일정한 것으로 간주 할 수 있습니다. 그래서 우리는 그것에 신경 쓰지 않습니다. 그러나 point light의 감쇠는 어떻게 생겼습니까?
하나의 광자가 방출되는 지점을 상상해보십시오. 이 광자는 모든 방향으로 이동합니다. 시간이 지남에 따라 광자는 지점에서 더 멀리 이동합니다. 그들 모두가 같은 속도로 움직이기 때문에, 광자는 구의 중심으로 점을 가진 구의 표면으로 작용합니다. 이 구체의 반지름은 광자가 계속 움직일 때 증가합니다. 구가 커질수록 표면도 커집니다. 그러나이 표면은 항상 같은 양의 광자를 포함합니다. 따라서 광자의 밀도는 감소합니다. 이것은 관찰 된 빛의 밝기를 결정합니다.
Spherical attenuation.
4π
4πr2
r
2
의 감쇠 계수를 유도합니다. 여기서 d
. 광자 밀도를 결정하기 위해서, 우리는 그것으로 나눌 수 있습니다. 우리는 상수 4
4π를
π
1d2
무시할 수 있습니다. 우리는 그것이 빛의 강도에 포함되어 있다고 가정 할 수 있습니다. 이는 1
d
d
2
는 빛의 거리입니다.
UnityLight CreateLight (Interpolators i) { UnityLight light; light.dir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos); float3 lightVec = _WorldSpaceLightPos0.xyz - i.worldPos; float attenuation = 1 / (dot(lightVec, lightVec)); light.color = _LightColor0.rgb * attenuation; light.ndotl = DotClamped(i.normal, light.dir); return light; }
Too bright up close.
이렇게하면 빛 가까이에서 매우 밝은 결과를 얻을 수 있습니다. 이것은 거리가 0에 가까워지면 감쇠 계수가 무한대에 도달하기 때문에 발생합니다. 빛의 세기는 0의 최대 거리에있는 것을 확인하기 위해, 상기 감쇠 식을 변경 합니다.
No longer too bright.
실생활에서, 광자는 무언가를 치기까지 계속 움직입니다. 이것은 빛의 범위가 너무 약해져 더 이상 볼 수 없더라도 잠재적으로 무한하다는 것을 의미합니다. 그러나 우리는 볼 수없는 조명을 렌더링하는 데 시간을 낭비하고 싶지 않습니다. 그래서 우리는 어떤 시점에서 렌더링을 멈춰야 할 것입니다.
Point lights와 spotlights는 범위가 있습니다. 이 범위 안에있는 물체는이 불빛으로 draw call을하게됩니다. 다른 모든 객체는 그렇지 않습니다. 기본 범위는 10입니다.이 범위가 작을수록 더 적은 draw call 을받는 개체가 줄어들어 프레임 속도가 높아집니다. 우리의 빛의 범위를 1로 설정하고 주변을 이동하십시오.
범위가 1 인 조명.
범위와 범위를 벗어난 개체가 갑자기 켜지거나 꺼질 때 명확하게 표시됩니다. 이것은 빛이 우리가 선택한 범위를 넘어서도 여전히 보일 수 있기 때문에 발생합니다. 이를 해결하기 위해서는 감쇠와 범위가 동기화되어 있는지 확인해야합니다.
현실적으로 빛에는 최대 범위가 없습니다. 그래서 우리가 설정 한 범위는 예술적 자유입니다. 우리의 목표는 물체가 범위를 벗어날 때 갑작스런 전이가 없는지 확인하는 것입니다. 이를 위해서는 감쇠 계수가 최대 범위에서 0에 도달해야합니다.
Unity는 밝은 공간 위치로 변환하여 point light의 감쇠를 결정합니다. 이것은 가벼운 물체의 로컬 공간에서 감쇄로 스케일링 된 점입니다. 이 공간에서 point light은 원점에 위치합니다. 멀리 떨어져있는 유닛이 하나 밖에없는 것은 범위를 벗어났습니다. 따라서 원점으로부터의 제곱 거리는 스케일 된 감쇠 계수를 정의합니다.
Unity는 한 단계 더 나아가 제곱 된 거리를 사용하여 폴 오프 텍스처를 샘플링합니다. 이는 감쇄가 조금 일찍 0으로 떨어지도록하기 위해 수행됩니다. 이 단계가 없으면 물체가 범위를 벗어나 이동할 때 빛이 계속 터지게됩니다.
이 기법의 코드는 AutoLight 포함 파일에 있습니다. 그것을 직접 쓰는 대신 사용하십시오..
AutoLight include file hierarchy.
#include "AutoLight.cginc"#include "UnityPBSLighting.cginc"
이제 UNITY_LIGHT_ATTENUATION 매크로에 액세스 할 수 있습니다 . 이 매크로는 코드를 삽입하여 올바른 감쇠 계수를 계산합니다. 세 가지 매개 변수가 있습니다. 첫 번째는 감쇠가 포함될 변수의 이름입니다. 우리는 그것을 사용할 attenuation 것입니다. 두 번째 매개 변수는 그림자와 관련이 있습니다. 아직 지원하지 않기 때문에 0을 사용하십시오. 세 번째 매개 변수는 world position입니다.
매크로는 현재 범위의 변수를 정의합니다. 그래서 우리는 더 이상 선언해서는 안됩니다.
UnityLight CreateLight (Interpolators i) { UnityLight light; light.dir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos); // float3 lightVec = _WorldSpaceLightPos0.xyz - i.worldPos;// float attenuation = 1 / (dot(lightVec, lightVec)); UNITY_LIGHT_ATTENUATION(attenuation, 0, i.worldPos); light.color = _LightColor0.rgb * attenuation; light.ndotl = DotClamped(i.normal, light.dir); return light; }
이 매크로를 사용하면 감쇠가 더 이상 작동하지 않는 것처럼 보입니다. 그것은 빛의 유형별로 하나씩 여러 버전이 있기 때문입니다. 기본적으로 directional light은 감쇠가 전혀 없습니다.
올바른 매크로는 point light을 처리하는 것으로 알려진 경우에만 정의됩니다. 이를 나타 내기 위해서는 #define POINT 를 포함시켜야 합니다. 추가 조명에서 point lights만을 다루므로 My Lighting을 포함하기 전에 정의하십시오 .
Pass { Tags { "LightMode" = "ForwardAdd" } Blend One One ZWrite Off CGPROGRAM #pragma target 3.0 #pragma vertex MyVertexProgram #pragma fragment MyFragmentProgram #define POINT #include "My Lighting.cginc" ENDCG }
Attenuation with range 10.
point light을 끄고 두 방향 등을 다시 활성화하십시오.
Incorrect vs. correct directional lights.
우리는 빛의 방향을 위치로 해석합니다. 그리고 부가적으로 패스에 의해 렌더링 된 2 차 directional light은 완전히 마치 point light처럼 취급됩니다. 이를 해결하기 위해 다양한 광원 유형에 대한 셰이더 변형을 만들어야합니다.
inspector의 셰이더를 확인하십시오. Compile and show code 버튼 아래의 드롭 다운 메뉴 에는 현재 얼마나 많은 shader variants이 있는지 알려주는 섹션이 있습니다. Show를 클릭하여 그들에 대한 개요를 얻을 버튼을 누릅니다.
Currently two variants.
// Total snippets: 2 // ----------------------------------------- // Snippet #0 platforms ffffffff: Just one shader variant. // ----------------------------------------- // Snippet #1 platforms ffffffff: Just one shader variant.
열린 파일은 우리에게 하나의 shader variant을 가진 두 개의 미리보기가 있음을 알려줍니다. 이것들은 우리의 기본 및 추가 패스입니다.
우리는 additive pass를위한 두 개의 shader variants을 만들고 싶습니다. 하나는 directional lights을위한 것이고 다른 하나는 point lights을위한 것입니다. 다중 컴파일 pragma 문을 패스에 추가하여이 작업을 수행합니다. 이 문은 키워드 목록을 정의합니다. Unity는 우리에게 여러 shader variants을 만들어 각각의 키워드 중 하나를 정의합니다.
각 variant은 별도의 셰이더입니다. 그들은 개별적으로 집계됩니다. 키워드의 유일한 차이점은 정의 된 키워드입니다.
이 경우 우리는 DIRECTIONAL, POINT가 필요하고 , 더 이상 POINT를 정의해서는 안됩니다 .
Pass { Tags { "LightMode" = "ForwardAdd" } Blend One One ZWrite Off CGPROGRAM #pragma target 3.0 #pragma multi_compile DIRECTIONAL POINT #pragma vertex MyVertexProgram #pragma fragment MyFragmentProgram // #define POINT #include "My Lighting.cginc" ENDCG }
셰이더 변형 개요를 다시 overview하십시오. 이번에는 두 번째 발췌 문장에 두 개의 variants이 포함될 것입니다.
// Total snippets: 2 // ----------------------------------------- // Snippet #0 platforms ffffffff: Just one shader variant. // ----------------------------------------- // Snippet #1 platforms ffffffff: DIRECTIONAL POINT 2 keyword variants used in scene: DIRECTIONAL POINT
우리는 단지 같은 존재의 키워드를 확인할 수 있습니다 AutoLight는 POINT를 위해 하지 않습니다 . 우리의 경우, 만약 POINT 가 정의되면, 우리는 스스로 빛의 방향을 계산해야합니다. 그렇지 않으면 우리는 directional light과 _WorldSpaceLightPos0 .의 방향을 갖습니다
UnityLight CreateLight (Interpolators i) { UnityLight light; #if defined(POINT) light.dir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos); #else light.dir = _WorldSpaceLightPos0.xyz; #endif float3 lightVec = _WorldSpaceLightPos0.xyz - i.worldPos; UNITY_LIGHT_ATTENUATION(attenuation, 0, i.worldPos); light.color = _LightColor0.rgb * attenuation; light.ndotl = DotClamped(i.normal, light.dir); return light; }
이것은 두 개의 additive pass variants에 적용됩니다. 그것은 POINT.를 정의하지 않기 때문에 기본 패스에서도 작동합니다
Unity는 현재 라이트 및 shader variant 키워드를 기반으로 사용할 variant을 결정합니다. directional light을 렌더링 할 때 DIRECTIONAl 변형을 사용합니다 . point light을 렌더링 할 때 POINT 변형을 사용합니다 . 일치하는 항목이 없으면 목록에서 첫 번째 변형을 선택합니다.
3 개의 빛을 렌더링합니다.
directional 그리고 point light 외에도 Unity는 spotlight 를 지원합니다. 스포트라이트는 점 광원과 같고 원추형의 범위로 제한되지만 모든 방향으로 빛나는 광원입니다.
A spotlight.
spotlight 를 지원하기 위해서는 다중 컴파일 구문의 키워드 목록에 SPOT을 추가해야합니다.
#pragma multi_compile DIRECTIONAL POINT SPOT
우리의 가산 쉐이더는 이제 3가지 의 변형들이 있습니다.
// Snippet #1 platforms ffffffff: DIRECTIONAL POINT SPOT 3 keyword variants used in scene: DIRECTIONAL POINT SPOT
스포트 라이트는 점등과 마찬가지로 위치가 있어서 POINT 나 SPOT이 정의되면, 우리는 빛의 방향을 계산해야 합니다.
#if defined(POINT) || defined(SPOT) light.dir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos); #else light.dir = _WorldSpaceLightPos0.xyz; #endif
Spotlight with 60° angle.
이제 스포트라이트 작동하기에 충분하다. 원뿔 모양을 처리하는 다른 UNITY_LIGHT_ATTENUATION 매크로로 끝을 맺읍시다.
감쇠 방식은 점 광원의 감쇠 방식부터 시작합니다. 밝은 공간으로 변환 한 다음 감쇠 계수를 계산합니다.
그런 다음 원점 뒤에있는 모든 점에 대해 감쇠를 0으로 설정하십시오. 그것은 스포트라이트 앞에있는 모든 것에 빛을 제한합니다.
그런 다음 UV 공간의 X 및 Y 좌표. 이 질감은 빛을 가리기 위해 사용됩니다. 텍스처는 가장자리가 흐려진 단순한 원입니다. 이것은 가벼운 실린더를 생산합니다.
이것을 원뿔로 바꾸기 위해, 빛으로의 변환은 사실 원근법 변환이며 homogeneous 좌표를 사용합니다.
UNITY_LIGHT_ATTENUATION 은 스포트라이트에게 무엇입니까?
기본 스포트라이트 마스크 텍스처는 흐릿한 원입니다. 그러나 가장자리가 0으로 떨어지는 동안은 어떠한 정사각형 텍스쳐라도 사용할 수 있습니다.
이러한 텍스처들을 스포트라이트 쿠키라고합니다. 이 이름은 빛에서 그림자를 추가하는 영화, 극장 또는 사진을 나타내는 색상에서 파생되었습니다.
쿠키의 알파 채널은 빛을 가리기 위해 사용됩니다. 다른 채널은 중요하지 않습니다. 예제 텍스처는 4 개의 채널 모두가 같은 값으로 설정되어 있습니다.
Spotlight cookie.
텍스처를 가져올 때에 Texture Type 을 쿠키로 선택할 수 있습니다. 그런 다음 스포트라이트 상자에 조명 유형을 설정해야합니다. 유니티가 세팅 대부분을 알아서 해줄 것입니다.
Imported texture.
그리고 이제 텍스쳐를 스포트라이트의 쿠키로 사용할 수 있습니다.
Using a spotlight cookie.
Directional lights 도 쿠키를 가질 수 있습니다. 이 쿠키들은 타일화 되어있어서 가장자리가 0으로 떨어질 필요가 없습니다. 대신에 그들은 자연스럽게 연결되어야 합니다.
A directional cookie.
directional 조명을위한 쿠키는 크기가 있습니다. 이렇게하면 시각적 크기가 결정되며 타일의 크기에 영향을줍니다. 기본값은 10이지만 작은 장면에서는 1과 같이 훨씬 작은 눈금이 필요합니다.
Main directional light with cookie.
쿠기가 있는 주 지향광
쿠키가 있는 directional 조명은 또한 반드시 조명 공간 변환이 필요합니다. UNITY_LIGHT_ATTENUATION 매크로가 있습니다.
따라서 Unity는 쿠키가 없는 directional 조명과 다른 조명 유형을 처리합니다. 따라서 DIRECTIONAL_COOKIE 키워드를 사용함으로써 항상 additive 패스에 의해 렌더링됩니다.
#pragma multi_compile DIRECTIONAL DIRECTIONAL_COOKIE POINT SPOT
Directional light with cookie.
UNITY_LIGHT_ATTENUATION 은 이 경우에 무엇을 합니까?
포인트 라이트 또한 쿠키를 가질 수 있습니다. 이 경우 빛은 모든 방향으로 진행되므로 쿠키는 구체 주위를 둘러 싸야합니다. 이것은 큐브 맵을 사용하여 수행됩니다.
다양한 텍스처 포맷을 사용하여 라이트 맵을 만들 수 있으며 Unity는 쿠키 텍스처를 큐브 맵으로 변환할 것입니다.
Unity가 이미지 해석 방법을 알 수 있도록 매핑을 지정해야합니다.
가장 좋은 방법은 직접 큐브 맵을 만드는 것입니다. 이 경우 자동 맵핑 모드로 충분할 수 있습니다.
Point light cookie cube map.
점광 쿠키는 다른 세팅이 없습니다.
Point light with cookie.
이 시점에서 DIRECTIONAL_COOKIE 키워드를 다중 컴파일 문에 추가해야합니다.
꽤 긴 목록이되고 있습니다. 왜냐하면 이것은 일반적인 목록이기 때문이고 대신 사용할 수있는 약식 pragma 선언문이 있습니다.
#pragma multi_compile_fwdadd// #pragma multi_compile DIRECTIONAL DIRECTIONAL_COOKIE POINT SPOT
이것이 우리가 필요로하는 것 인지 확인할 수 있습니다.
// Snippet #1 platforms ffffffff: DIRECTIONAL DIRECTIONAL_COOKIE POINT POINT_COOKIE SPOT 5 keyword variants used in scene: POINT DIRECTIONAL SPOT POINT_COOKIE DIRECTIONAL_COOKIE
그리고 쿠키를 사용하여 포인트 라이트의 방향을 계산하는 것을 잊지 마십시오.
#if defined(POINT) || defined(POINT_COOKIE) || defined(SPOT) light.dir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos); #else light.dir = _WorldSpaceLightPos0.xyz; #endif
Point light with a cookie.
보이는 모든 객체들은 항상 기본 패스로 렌더링됩니다. 이 패스는 지향광을 처리합니다.
모든 추가된 라이트는 추가 패스에 또 다른 추가 패스를 추가합니다. 따라서 많은 조명이 많은 드로 콜을 유발할 것입니다.
해당 범위에 많은 객체가있는 많은 조명은 그리기 호출이 많이 발생합니다.
예를 들어, 4 개의 점 광원과 6 개의 물체가 있는 장면을 찍습니다. 모든 물체는 네 개의 모든 등 범위에 있습니다. 이를 위해서는 객체 당 5 회의 draw call이 필요합니다.
하나는 베이스 패스에, 4 개의 추가적인 패스를 더한 것입니다.
총 30 회의 draw call입니다.
draw call을 증가시키지 않고 단일 지향성 라이트를 추가 할 수 있습니다.
Four point lights, six objects, 30 draw calls.
draw call 수를 유지하기위해서 객체 당 사용되는 최대 점광 수를 Quality Settings 에서 제한합니다.
빛은 단편마다 계산 될 때 픽셀 라이트라고합니다.
품질 수준이 높을수록 픽셀 조명을 많이 받을 수 있게 합니다. 최고 품질 수준의 기본값은 4입니다.
From 0 to 4 lights per object.
어떤 광원이 렌더될 것인가는 각 오브젝트에 따라 다릅니다. Unity 는 라이트를 상대적인 강도와 거리에 의해서 중요도로 분류합니다.
최소한의 영향을 미칠 것으로 예상되는 조명들은 먼저 버려집니다.
사실, 몇 가지 일이 더 일어나지만, 나중에 다시 살펴 보겠습니다.
왜냐하면 다른 오브젝트들은 다른 광원에 의해서 영향을 받아 일치하지 않는 조명을 얻게됩니다.
움직이면 더 나빠지고, 이 것은 갑작스러운 조명 변화를 초래할 수 있습니다.
이 문제는 심각합니다. 왜냐하면 광원들이 완전히 꺼질 수도 있기 때문입니다. 운 좋게도, 광원을 켜고 끄는 것보다 더 값싸게 렌더링하는 다른 방법이 있습니다. 정점당 렌더링이라는.
정점당 광원 렌더링은 정점 프로그램에서 계산을 수행하는 것을 의미합니다. 그 다음 결과 색상이 보간되어 fragment 프로그램에 전달됩니다.
비용이 낮고, 유니티의 베이스 패스에 그런 조명들이 있습니다.
정점당 렌더링이 일어나면, 유니티는 VERTEXLIGHT_ON 라는 베이스 패스 쉐이더 변환자를 찾습니다.
정점 조명은 오직 점광만 지원되어 지향광은 적용될 수 없습니다.
정점 조명을 사용하려면 우리는 다중 패스 선언문을 베이스 패스에 추가해야 합니다. VERTEXLIGHT_ON 이라는 키워드 하나만 있으면 됩니다.
다른 옵션은 그런 키워드가 없습니다. 보여주기위해서 우리는
버텍스 라이트를 사용하기 위해베이스 패스에 다중 컴파일 문이 있습니다. VERTEXLIGHT_ON이라는 단일 키워드 만 있으면됩니다. 다른 옵션은 전혀 키워드가 전혀 없습니다.
Pass { Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma target 3.0 #pragma multi_compile _ VERTEXLIGHT_ON #pragma vertex MyVertexProgram #pragma fragment MyFragmentProgram #include "My Lighting.cginc" ENDCG }
정점 조명의 색상을 fragment 프로그램에 전달 하기 위해서는 Interpolators 구조체를 추가해야합니다. 물론 VERTEXLIGHT_ON 키워드가 정의되어야 합니다.
struct Interpolators { float4 position : SV_POSITION; float2 uv : TEXCOORD0; float3 normal : TEXCOORD1; float3 worldPos : TEXCOORD2; #if defined(VERTEXLIGHT_ON) float3 vertexLightColor : TEXCOORD3; #endif };
색상 계산을 위해 함수를 분리합시다. 읽어오고 그리고 보간자에게 씀으로 inout 매개변수가 됩니다.
void ComputeVertexLightColor (inout Interpolators i) {}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); ComputeVertexLightColor(i); return i; }
이제 우리는 우리의 첫 번째 점광색을 통과시킬 것 입니다. 라이트가 존재할 때만 작업이 가능한데, 그렇지 않으면 아무것도 아니게됩니다.
UnityShaderVariables 는 정점 조명 색상의 배열을 정의합니다. 이들은 RGBA 로 구성되어 있습니다.
void ComputeVertexLightColor (inout Interpolators i) { #if defined(VERTEXLIGHT_ON) i.vertexLightColor = unity_LightColor[0].rgb; #endif }
fragment 프로그램에서 우리가 연산할 모든 라이트에 색상을 추가해야합니다. 정점은 오직 간접광으로 취급됩니다.
간접광 정보 생성을 이 함수로 가지고 오시길 바랍니다. 여기서는 정점색을 간접광 디퓨즈 컴포넌트로 할당합니다.
UnityIndirect CreateIndirectLight (Interpolators i) { UnityIndirect indirectLight; indirectLight.diffuse = 0; indirectLight.specular = 0; #if defined(VERTEXLIGHT_ON) indirectLight.diffuse = i.vertexLightColor; #endif return indirectLight;}float4 MyFragmentProgram (Interpolators i) : SV_TARGET { i.normal = normalize(i.normal); float3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos); float3 albedo = tex2D(_MainTex, i.uv).rgb * _Tint.rgb; float3 specularTint; float oneMinusReflectivity; albedo = DiffuseAndSpecularFromMetallic( albedo, _Metallic, specularTint, oneMinusReflectivity ); // UnityIndirect indirectLight;// indirectLight.diffuse = 0;// indirectLight.specular = 0; return UNITY_BRDF_PBS( albedo, specularTint, oneMinusReflectivity, _Smoothness, i.normal, viewDir, CreateLight(i), CreateIndirectLight(i) ); }
픽셀 조명의 수를 0으로 만들면 모든 오브젝트는 단일광의 색으로 실루엣만 렌더됩니다.
Color of the first vertex light, per object.
물체 당 첫 번째 정점광색상
Unity는 최대 4개의 정점광을 지원합니다. 이 정점들의 위치는 float4 변수에 저장됩니다.
unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0 로 구성되어 있고 UnityShaederVariables 로 정의됩니다.
이 세 변수들의 첫 번째 컴포넌트는 첫 번째 정점광의 위치를 포함합니다.
void ComputeVertexLightColor (inout Interpolators i) { #if defined(VERTEXLIGHT_ON) float3 lightPos = float3( unity_4LightPosX0.x, unity_4LightPosY0.x, unity_4LightPosZ0.x ); i.vertexLightColor = unity_LightColor[0].rgb; #endif }
그리고, 빛 벡터, 빛 방향과 법선 벡터를 계산합니다. 여기서는 UNITY_LIGHT_ATTENUATION 을 사용할 수 없으며,
을 사용하여 결과 색상에 다가갑니다.
11+d2
1
+
1
d
2
void ComputeVertexLightColor (VertexData v, inout Interpolators i) { #if defined(VERTEXLIGHT_ON) i.vertexLightColor = unity_LightColor[0]; float3 lightPos = float3( unity_4LightPosX0.x, unity_4LightPosY0.x, unity_4LightPosZ0.x ); float3 lightVec = lightPos - i.worldPos; float3 lightDir = normalize(lightVec); float ndotl = DotClamped(i.normal, lightDir); float attenuation = 1 / (1 + dot(lightDir, lightDir)); i.vertexLightColor = unity_LightColor[0].rgb * ndotl * attenuation; #endif }
이건 그냥 diffuse 이고, 우리가 specular를 이용하여 연산 할 수도 있지만 이는 큰 삼각형을 보간 하므로 결과가 매우 별로입니다.
사실, UnityShaderVariables 는 다른 변수인 unity_4LightAtten0 변수를 제공합니다. 이는 픽셀광원의 감쇄를 추측하는데 도움을 주며 이를 사용ㅎ
11+d2a
이 됩니다.
1
+
1
d
2
a
float attenuation = 1 / (1 + dot(lightDir, lightDir) * unity_4LightAtten0.x);
One vertex light per object.
To include all four vertex lights that Unity supports, we have to perform the same vertex-light computations four times, and add the results together. Instead of writing all the code ourselves, we can use the Shade4PointLights function, which is defined in UnityCG. We have to feed it the position vectors, light colors, attenuation factors, plus the vertex position and normal.
Unity가 지원하는 4개 정점 광을 포함하기 위해서, 4회의 같은 정점광 연산을 수행해야합니다. 그리고 같이 묶어 결과를 만듭니다.
직접 코드를 쓰기보다는, 우리는 UnityCG 에 정의된 Shader4PointLights 함수를 이용합니다.
우리는 위치 벡터, 광원 색상, 감쇄 요소 그리고 추가적으로 정점 위치, 노멀 들을 제공해야합니다.
void ComputeVertexLightColor (VertexData v, inout Interpolators i) { #if defined(VERTEXLIGHT_ON) i.vertexLightColor = Shade4PointLights( unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0, unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb, unity_4LightAtten0, i.worldPos, i.normal ); #endif }
Four vertex lights.
이제 4개의 정점광까지 받을 수 있게 되었고, 만약에 물체가 더 많이 받으려하면, Unity는 정점광원과 픽셀광원 을 하나의 광원으로 포함하여 이런 과정들을 숨기려고 할 것입니다.
이 광원은 정점 광원과 픽셀 광원 의 강도를 다르게 하여 2번 들어갑니다.
Switching between vertex and pixel lights.
기본적으로 Unity 는 조명을 픽셀 라이트로 둡니다. Render Mode 를 재설정 함으로 바꿀 수 있습니다.
중요한 조명은 제한이 없는 픽셀 라이트로 항상 설정합니다. 중요하지 않은 조명들은 픽셀라이트로 렌더되지 않습니다.
Light render mode.
우리가 모든 조명과 모든 조명을 다 사용할 때 조명을 다른 방법으로 렌더링하기 위해서 약간 뒤로 갈 수 있습니다.
구형 고조파를 사용할 수 있습니다. 이것은 세 가지 조명 유형 모두에 지원됩니다.
구형 고조파의 개념은 단일 기능으로 모든 들어오는 빛을 한개의 점에서 한개의 함수로 표현할 수 있다는 것입니다. 이 함수는 구의 표면에 정의됩니다.
일반적으로이 함수는 구 좌표로 설명됩니다. 그러나 3D 좌표도 사용할 수 있습니다. 그것은 우리가 법선 벡터를 이용하여 라이트를 샘플링 할 수 있게 해줍니다.
그러한 함수를 생성하려면 모든 방향으로 빛의 강도를 모든 방향으로 샘플링 한 다음, 이를 단일 연속 함수로 전환하는 방법을 찾아야합니다.
완벽하려면 모든 물체 표면의 모든 점에 대해이 작업을 수행해야합니다. 이것은 물론 불가능합니다. 우리는 근사치로 충분해야합니다.
먼저 시점내의 객체의 로컬 원점부터 함수를 정의합니다. 이것은 물체의 표면을 따라 많이 변하지 않는 조명 조건에 적합합니다.
이는 작은 물체와 물체가 약하거나 멀리 떨어져있는 경우에도 마찬가지입니다. 다행히도 픽셀 또는 정점 조명 상태에 적합하지 않은 광원의 경우가 일반적입니다.
둘째, 함수 자체를 근사화 해야합니다. 연속 함수를 다른 주파수의 다른 연속 함수로 분해 할 수 있습니다.
이들은 밴드로 알려져 있습니다. 임의의 함수의 경우,이를 수행하기 위해 무한대의 밴드가 필요할 수 있습니다.
간단한 예는 사인 곡선을 작성하는 것입니다. 기본적인 사인파로 시작하십시오.
Sine wave, sin2πx
두 번째 밴드를 위해 2배 늘어난 주파수와 1/2배 증폭된 사인파를 사용합니다.
sin2πx
Double frequency, half amplitude, sin4πx/2
함께 합칠 때, 두 밴드들은 더 복잡해집니다.
sin4πx2
Two bands, sin2πx+sin4πx/2
.
계속 이렇게 밴드를 합칠 수 있고, 주파수를 2배 올리고 1/2배 증폭해야한다.
sin2πx+sin4πx2
Third and fourth bands.
계속 복잡해진다.
Four sine wave bands,
4
sin
2
π
i
x
∑
i
2
∑i=14sin2πixi2
i
=
1
.
이 예에서는 패턴이 고정된 일반 사인파를 사용했습니다. 사인파를 사용하여 임의 기능을 설명하려면 완벽한 매치를 얻을 때까지 각 밴드의 주파수, 진폭 및 오프셋을 조정해야 합니다.
밴드를 적게 사용할수록 근사치의 정확도가 떨어집니다. 이 기술은 소리와 이미지 데이터와 같은 많은 것들을 압축하는데 사용됩니다만.
이 경우에는 주파수가 가장 낮은 대역이 기능의 큰 특징과 일치합니다. 우리는 확실히 그것들을 유지하고 싶습니다.
그래서 우리는 더 높은 주파수를 가진 밴드들을 버릴 것입니다. 이것은 우리가 우리의 조명 기능의 세부 사항을 잃는다는 것을 의미합니다.
만약 조명이 빠르게 바뀌지 않는다면 괜찮습니다, 그래서 우리는 다시 한번 확산광을 시키는 것에만 집중해야만 합니다.
조명의 가장 간단한 근사치는 균일한 색상이. 조명은 모든 방향에서 동일합니다. 이것이 첫번째 밴드입니다.
0
Y
0
Y00
.으로 표시하겠습니다. 이 기능은 단일 하위 기능으로 정의되며, 이는 일정한 값에 불과합니다.
두번째 대역은 선형 방향성 조명을 도입합니다. 각 축에 대해 대부분의 빛이 어디에서 오는지 설명합니다.
이에 따라
−
1
0
1
Y
1
, Y
1
, Y
1
Y1-1
Y10
Y11
으로 식별되는 세가지 함수로 분할됩니다. 각 기능에는 일반 좌표에 상수를 곱한 값이 포함됩니다.
세번째 대역은 더 복잡해집니다. 이 기능은
−
2
Y
2
… Y
2
Y2-2
Y22
2
의 다섯가지 기능으로 구성됩니다. 이러한 기능은 2차 방정식으로, 일반적인 2개의 좌표 제품이 포함되어 있습니다.
계속할 수는 있지만 Unity에서는 처음 3개의 대역만 사용합니다. 여기 테이블에 있습니다. 모든 조건에 다음을 곱하십시오. by
-2
-1
0
1
2 1 /
0
1
2
√
√
1
1
12π
2
√
π
−
y
3
z
√
3
−
x
√
√
3
-y3
z3
-x3
x
y
√
15
−
y
z
15
(
2
)
√
5
−
x
z
15
(
2
2
)
√
15
xy15
-yz15
3
z
−
1
2
-xz15
x
−
y
2
(3z2-1)52
(x2-y2)152
이 기능은 분할된 단일 기능이므로 하위 기능을 식별할 수 있습니다. 최종 결과는 9개의 용어를 합친 것이구, 9개 항의 각 항을 추가 요인으로 변조하여 서로 다른 조명 조건이 생성됩니다.
a
+
b
y
+
c
z
+
d
x
+
e
x
y
+
f
y
z
+
g
z
2
+
h
x
z
+
i
(
x
2
−
y
2
)
a+by+cz+dx+exy+fyz+gz2+hxz+i(x2-y2)
,
0
Y
2
Y20
일반 좌표를 시각화하여 항이 나타내는 방향을 파악할 수 있습니다. 예를 들어, 흰색과 빨간 색의 양의 좌표를 빨간 색으로 칠하는 방법이 있습니다.
float t = i.normal.x; return t > 0 ? t : float4(1, 0, 0, 1) * -t;
그런 다음 i.normal.x및 i.normal.x*i.normal.y등을 사용하여 각 용어를 시각화할 수 있습니다.
1,
y, z, x,
xy, yz, zz, xz, xx - yy.
구형 고조파로 근사치를 구하는 모든 빛은 27개의 숫자에 포함되어야 합니다. 다행히, Unity는 이 작업을 매우 빠르게 수행할 수 있습니다.
기본 패스는 UnityShaderVariables에 정의된 7개의 float4변수 집합을 통해 이러한 기능에 액세스 할 수 있습니다.
UnityCG에는 일반적인 데이터 고조파 및 구형을 기반으로 계산하는 이 검출기는 네번째 구성 요소가 1로 설정된 float4 매개 변수를 사용합니다..
근접한 최종 근사치를 확인하려면 fragment 프로그램에서 ShadeSH9 를 직접 반환하십시오.
float3 shColor = ShadeSH9(float4(i.normal, 1)); return float4(shColor, 1); return UNITY_BRDF_PBS( albedo, specularTint, oneMinusReflectivity, _Smoothness, i.normal, viewDir, CreateLight(i), CreateIndirectLight(i) );
이제 모든 불을 끄십시오.
Ambient color.
환경 색상
쨘! 우리의 물체들은 더 이상 검은 색이 아닙니다. 그들은 환경 색상을 가지고 있습니다.
Unity는 구형 고조파를 사용하여 객체에 장면의 환경 색상을 추가합니다.
이제 여러개의 조명을 활성화합니다. 픽셀 및 정점 조명이 모두 사용될 만큼 충분히 있는지 확인합니다.
나머지는 구형 고조파에 추가됩니다. 다시, Unity는 이 과정을 혼합하기 위해 조명을 분할합니다
Lights via spherical harmonics.
Just like with vertex lights, we'll add the spherical harmonics light data to the diffuse indirect light. Also, let's make sure that it never contributes any negative light. It's an approximation, after all.
정점 조명과 마찬가지로 구형 고조파 빛 데이터를 확산된 간접 조명에 추가할 것입니다. 또한, 그것이 어떠한 부정적인 빛에도 기여하지 않도록 합시다. 근사치니깐요.
UnityIndirect CreateIndirectLight (Interpolators i) { UnityIndirect indirectLight; indirectLight.diffuse = 0; indirectLight.specular = 0; #if defined(VERTEXLIGHT_ON) indirectLight.diffuse = i.vertexLightColor; #endif indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1))); return indirectLight; } float4 MyFragmentProgram (Interpolators i) : SV_TARGET { … // float3 shColor = ShadeSH9(float4(i.normal, 1));// return float4(shColor, 1); return UNITY_BRDF_PBS( albedo, specularTint, oneMinusReflectivity, _Smoothness, i.normal, viewDir, CreateLight(i), CreateIndirectLight(i) ); }
하지만 우리는 이것을 베이스 패스에서만 해야 합니다. 구형 고조파는 정점 조명과 독립적이므로 동일한 키워드에 의존할 수 없습니다. 대신 FOWARD_BASE_PASS가 정의되어 있는지 확인합니다.
#if defined(FORWARD_BASE_PASS) indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1))); #endif
이를 통해 구형 고조파가 다시 제거되는데, 이는 FOWARD_BASE_PASS 가 어디에도 정의 되어 있지 않기 때문입니다. 픽셀 조명 카운트를 0으로 설정한 경우 정점 조명만 표시됩니다.
Only four vertex lights.
조명을 포함하기 전에 베이스 패스에 FORWARD_BASE_PASS 를 정의합니다. 이제 우리의 코드는 우리가 언제 베이스 패스를 할 것인지를 알게되었습니다.
Pass { Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma target 3.0 #pragma multi_compile _ VERTEXLIGHT_ON #pragma vertex MyVertexProgram #pragma fragment MyFragmentProgram #define FORWARD_BASE_PASS #include "My Lighting.cginc" ENDCG }
Vertex lights and spherical harmonics.
정점광과 구형 고조파
우리의 셰이더는 마침내 정점 조명과 구형 고조파 모두를 포함합니다. 픽셀 조명 수가 0보다 크면 세가지 조명 방법이 모두 결합된 것을 볼 수 있습니다.
With an additional four pixel lights.
구형 고조파가 단색의 주변 색상을 포함하는 경우, 환경적인 스카이프에서도 작동할 수 있을까요? 네! 유니티는 구형 고조파가 적용된 스카이박스 로 추측할 것입니다.
이 작업을 시도해 보려면 모든 조명을 끄고 환경 조명에 대한 기본 스카이 박스를 선택합니다. 새로운 장면에서는 기본적인 박스를 사용하지만, 이전 튜토리얼에서 제거했습니다.
Default skybox, without directional light.
이제 Unity기능을 사용하면 배경에 스카이박스가 렌더링 됩니다. 이것은 주 방향 등을 바탕으로, 자동적으로 생성되었습니다. 활동적인 빛이 없기 때문에, 그것은 마치 태양이 지평선에 있는 것처럼 작용합니다.
여러분은 이 물체들이 스카이박스의 색 중 일부에 영향을 받고 있는 것을 알 수 있고, 이것은 약간의 미묘한 음영을 만들어 냅니다. 이 모든 과정은 구형 고조파를 통해 이루어집니다.
주 방향 등을 켜십시오. 이것은 스카이박스를 변화시킵니다. 구형 고조파는 스카이박스보다는 조금 늦게 변한다는 것을 알 수 있을 것입니다.
그것은 Unity가 스카이박스를 계산하기 위해서는 시간이 좀 필요하기 때문입니다. 이것은 그것이 갑자기 변할 때만 매우 눈에 띄게됩니다.
Skybox with main light, with and without spherical harmonics.
물체가 갑자기 훨씬 더 밝아졌습니다! 환경광에 매우 강하게 작용합니다. 절차 스카이박스는 완벽한 맑은 날을 의미합니다.
이러한 조건에서, 완전히 하얀 표면은 정말로 밝게 보일 것이고
이 효과는 감마 공간에서 렌더링 할 때 가장 강력합니다. 실제 생활에서 완벽하게 하얀 표면은 많지 않습니다. 보통 훨씬 더 어둡죠.