11 – 03 ~ 04 Half-Lambert(하프 램버트) 연산 및 완성 (Unity Shader)
Lambert 라이트는 꽤 가벼운 조명 공식이지만, cos 그래프 연산의 특성상 밝다가 너무 갑자기 검게 음영이 떨어지는 단점이 있습니다.
그래서 이 단점을 해결하고자 만든 것이 Half-Lambert (하프 램버트) 공식입니다.
물리적으로는 옳지 않지만, 매우 가벼우며 보기 좋은 장점이 있습니다.
저번에 글에서 사용한 코드에서 시작하겠습니다.
Shader "Custom/CustomLight" { Properties { _MainTex ("Albedo (RGB)", 2D) = "white" {} _BumpMap("Normal Map", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf _MyLight noambient #pragma target 3.0 sampler2D _MainTex; sampler2D _BumpMap; struct Input { float2 uv_MainTex; float2 uv_BumpMap; }; void surf (Input IN, inout SurfaceOutput o) { fixed4 c = tex2D (_MainTex, IN.uv_MainTex); float3 d = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap)); o.Normal = d; o.Albedo = c.rgb; o.Alpha = c.a; } float4 Lighting_MyLight(SurfaceOutput s, float3 lightDir, float atten) { float ndotl = saturate(dot(s.Normal, lightDir)); return ndotl; } ENDCG } FallBack "Diffuse" }
직접 확인해보기 위해서 아래 에셋을 사용했습니다. (무료)
https://assetstore.unity.com/packages/3d/characters/humanoids/barbarian-warrior-75519 <- TestAsset
위의 이미지는 saturate() 함수로 인하여 0 이하의 값은 전부 0 이 되었습니다.
saturate() 를 다시 제거해서 다시 -1 ~ 1까지의 범위를 출력하도록 만들어 줍니다.
변경 전
그리고 라이팅의 결과물에 Half- Lamber ( 하프 람버트 ) 공식인 * 0.5 + 0.5 를 적용해줍니다.
Half- Lamber ( 하프 람버트 공식) 적용 후
* 0.5 + 0.5 의 식은 -1 에서부터 1까지의 숫자를 0 에서 부터 1까지의 범위로 만들어 줍니다.
아래의 푸른 cos 이 붉은 라인으로 끌어 올라오게 됩니다.
이로 인하여 매우 부드러운 결과 값이 나오게 됩니다.
실제 사용할 때에는 빛의 영향을 좀 줄이고자 제곱을 해서 사용합니다.
Shader "Custom/CustomLight" { Properties { _MainTex ("Albedo (RGB)", 2D) = "white" {} _BumpMap("Normal Map", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf _MyLight noambient #pragma target 3.0 sampler2D _MainTex; sampler2D _BumpMap; struct Input { float2 uv_MainTex; float2 uv_BumpMap; }; void surf (Input IN, inout SurfaceOutput o) { fixed4 c = tex2D (_MainTex, IN.uv_MainTex); float3 d = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap)); o.Normal = d; o.Albedo = c.rgb; o.Alpha = c.a; } float4 Lighting_MyLight(SurfaceOutput s, float3 lightDir, float atten) { float ndotl = dot(s.Normal, lightDir) * 0.5 + 0.5; return pow(ndotl, 3); // pow는 제곱 } ENDCG } FallBack "Diffuse" }
pow 제곱 적용 후
Lambert 라이트 완성하기
위의 상황은 빛 방향에 따른 밝기만 구현되었을 뿐, 조명의 색상과 강고 / 빛 감쇄 Albedo 텍스쳐 는 전혀 적용되지 않았습니다.
아래에서 나머지 내용을 추가하겠습니다.
pow와 dot 계산식을 하나로 병합합니다.
제 텍스쳐와 조명색상, 감쇠를 연산해서 조명을 완성해줍니다.
전체 코드
Shader "Custom/CustomLight" { Properties { _MainTex ("Albedo (RGB)", 2D) = "white" {} _BumpMap("Normal Map", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf _MyLight noambient #pragma target 3.0 sampler2D _MainTex; sampler2D _BumpMap; struct Input { float2 uv_MainTex; float2 uv_BumpMap; }; void surf (Input IN, inout SurfaceOutput o) { fixed4 c = tex2D (_MainTex, IN.uv_MainTex); float3 d = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap)); o.Normal = d; o.Albedo = c.rgb; o.Alpha = c.a; } float4 Lighting_MyLight(SurfaceOutput s, float3 lightDir, float atten) { float ndotl = pow ( dot(s.Normal, lightDir) * 0.5 + 0.5 , 3 ); float4 final; final.rgb = ndotl * s.Albedo * _LightColor0.rgb * atten; final.a = s.Alpha; return final; } ENDCG } FallBack "Diffuse" }
비어있던 MainTex도 넣어줍니다.
s.Albedo
Albedo 로 입력받은 텍스쳐 입니다.
SurfaceOutput이 s 로 들어왔으니 s.Albedo 가 됩니다.
여기서 들어온 Albedo 텍스쳐가 Lambert 라이트와 연산이 되어서 Diffuse (확산) 됩니다.
_LightColor0.rgb
이전에 연산했던 Ndotl 연산은 조명과 노멀의 각도를 표현한 것 뿐입니다.
조명의 색상이나 강도는 이 내장 변수를 이용해서 조명의 색상이나 강도를 가져옵니다.
이 변수를 곱한 후 조명의 생상과 강도를 변화시키면 그대로 반영됩니다.
atten
빛의 감쇠 현상을 시뮬레이트합니다.
빛의 감쇠가 일어나는 경우는 아래와 같습니다. (atten를 계산하지 않으면 다음 일이 일어나지 않습니다.)
1. self shadow가 생기지 않습니다.
자신의 그림자를 자신이 받지 않습니다.
2. receive shadow가 동작하지 않습니다.
다른 물체가 그림자를 드리우지 못합니다.
3. 조명의 감쇠 현상이 일어나지 않습니다.
Directional 라이트에서는 확인이 불가능하고 point 라이트에서 확인이 가능합니다.
point 라이트가 멀어질수록 어두워지는데 이것이 atten가 하는 일입니다.
atten 미적용
atten 적용
// noambient 제거
Shader "Custom/CustomLight" { Properties { _MainTex ("Albedo (RGB)", 2D) = "white" {} _BumpMap("Normal Map", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf _MyLight //noambient #pragma target 3.0 sampler2D _MainTex; sampler2D _BumpMap; struct Input { float2 uv_MainTex; float2 uv_BumpMap; }; void surf (Input IN, inout SurfaceOutput o) { fixed4 c = tex2D (_MainTex, IN.uv_MainTex); float3 d = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap)); o.Normal = d; o.Albedo = c.rgb; o.Alpha = c.a; } float4 Lighting_MyLight(SurfaceOutput s, float3 lightDir, float atten) { float ndotl = pow ( dot(s.Normal, lightDir) * 0.5 + 0.5 , 3 ); float4 final; final.rgb = ndotl * s.Albedo * _LightColor0.rgb * atten; final.a = s.Alpha; return final; } ENDCG } FallBack "Diffuse" }
이렇게 적용하면 얼굴이 밝아지면서 atten으로 생기는 얼굴의 그림자가 두드러지게 보이는 단점이 생길 것 입니다.
정답은 없고 atten를 제거해도 좋고 pow 함수를 이용하여 적절하게 숨기는 것도 하나의 방법입니다.
이제 완전한 Lambert 가 완성되었습니다.
이것은 유니티4까지 사용되었던 Bump Diffuse와 완전히 같은 쉐이더이며
유니티5에서도 Legacy Shaders / Bumped Diffuse와도 완전히 같은 쉐이더입니다.