커스텀쉐이더에서 리플렉션 프로브 사용하기 (Using Reflection Probes in Custom Shader)
Unity3D/Graphics 2015. 9. 30. 15:41게임 그래픽에서 반사의 표현은 매우 중요한 시각적 요소 중 하나입니다. 주변 환경의 반사를 표현함으로써 금속 재질을 처리할 수 있기 때문입니다. 판타지물의 투구라든가 SiFi물의 로보트 등 금속 재질에서의 반사 표현 여부는 그래픽 퀄리티에 큰 영향을 미칩니다.
유니티5에서 라이팅 관련 기능이 강화되면서 리플렉션 프로브(Reflection Probe) 기능이 추가되었습니다. 이름에서 유추할 수 있듯이, 라이트 프로브(Light Probe)처럼 특정 지점들의 리플렉션을 미리 연산해서 프로브에 저장해놓는 것입니다.
라이트 프로브 및 라이트맵(Lightmap)이 정적(static)인 오브젝트와 Baked 또는 mixed로 설정 된 라이트만 반영하는 것과 마찬가지로, 리플렉션 프로브 역시 정적인 오브젝트들만 반영한다는 등의 제약점들이 있긴 합니다. 하지만 부분 부분 마다의 반사를 제대로 반영할 수 있다는 점은 크나큰 매력이 될 것입니다. 자세한 사용법은 메뉴얼 및 블로그 글을 참고하세요. (PHYSICALLY BASED SHADING IN UNITY 5: A PRIMER, 리플렉션 프로브, 리플렉션 프로브 개요, 리플렉션 프로브 사용하기, 물리 기반 쉐이딩으로 작업하기) 메뉴얼의 글은 조만간 한글로 업데이트 될 예정입니다.
사실, 반사의 표현은 유니티5에서부터만 쓰게된 것은 아닙니다. 전통적으로 큐브맵(Cubemap)을 사용하여 쉐이더에서 반사를 표현해왔습니다. 유니티에 내장(Built-in)된 기존 쉐이더(Legacy Shader)에서도 이러한 큐브맵 반사들을 지원해왔습니다. (메뉴얼 : 반사 쉐이더 Family)
하지만 기존의 쉐이더들은 특정 큐브맵을 명시적으로 지정해줘야만 하고 위치마다 다른 환경을 반영하는 리플렉션 프로브를 적용하지는 않습니다. 때문에, 내장 쉐이더에서 리플렉션 프로브를 사용하기 위해서 스탠다드 쉐이더(Standard Shadder)를 이용하여야합니다. 하지만, 저가의 보급형 모바일 기기에서는 스탠다드 쉐이더를 사용하기에는 성능의 부담이 존재합니다.
따라서, 모바일에서 리플렉션 프로브를 사용하기 위해서는 내장되어있는 기존 쉐이더나 스탠다드 쉐이더 대신, 쉐이더를 직접 작성한 커스텀 쉐이더를 사용하여야 합니다. 사실, 리플렉션 프로브도 근본적인 형태는 큐브맵입니다. 큐브맵을 미리 프로브별로 미리 구워놓고 가지고 있는 것이지요. 오브젝트의 위치에 따라서 적절한 큐브맵을 리플렉션 프로브로부터 가져오게 되는 것입니다.
여차 저차 서론이 길어졌습니다만 결론만 말씀드리면 큐브맵을 인스펙터 창에서 지정해서 받아오는 대신 유니티에서 사전 정의해놓은 uniform sampler를 사용하여 큐브맵을 읽어들이면 된다는 것입니니다.
유니티가 리플렉션 프로브로 들어오는 큐브맵을 unity_SpecCube0로 사전 정의해놓았습니다. 이를 사전 정의된 UNITY_SAMPLE_TEXCUBE()로 읽어들입니다. 사실, UNITY_SAMPLE_TEXCUBE()는 texCUBE()를 디파인 한 것 뿐이지만 모바일 외 다른 플랫폼에서도 정상 작동을 하기 위해서는 UNITY_SAMPLE_TEXCUBE()로 사용하는 것이 좋습니다. 어찌되었든 리플렉션 프로브의 큐브맵으로부터 읽어들인 값에다가 리플렉션 프로브의 HDR 계수인 unity_SpecCube0_HDR의 r속성을 곱해주어서 반사 이미지의 최종 결과물을 얻습니다. 서피스 쉐이더를 작성하는 경우에는 surface 아웃풋의 Emission으로 이 값을 설정해두면 됩니다.
o.Emission = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, rv).rgb * unity_SpecCube0_HDR.r;
이름이 unity_SpecCube가 아니라 뒤에 0가 붙었다는 것을 보면 unity_SpecCube1도 존재한다는 것을 유추하실 수 있을 것입니다. unity_SpecCube0_HDR도 마찬가지로 unity_SpecCube1_HDR이 존재합니다. 이를 설명하기 위해서 오브젝트의 Mesh Renderer 인스펙터를 살펴보겠습니다. Reflection Probes 드롭다운을 내려보면 항목 중 Blend Probes라는 항목이 있는 것을 확인 가능합니다. 말 그대로 리플렉션 프로브를 블렌딩 해준다는 의미입니다. 오브젝트가 각기 다른 리플렉션 프로브 영역으로 이동 할 시 리플렉션 이미지가 갑자기 틱 하고 바뀌는 것을 방지하기 위하여 블렌딩을 해주는 것입니다.
그러기 위해서는 쉐이더에서 큐브맵 두 개를 읽어야한다는 의미가 됩니다. unity_SpecCube1을 unity_SpecCube0와 같이 읽어들인 다음 unity_SpecCube0_BoxMin.w값으로 보간해주면 됩니다. 원래는 이런 블렌딩 기능을 UNITY_SPECCUBE_BLENDING 디파인으로 묶어놓아야 합니다. 하지만 모바일 타겟에서는 UNITY_SPECCUBE_BLENDING 디파인이 꺼져있기 때문에 해당 디파인에 종속적이지는 않게 두었습니다. 사실, 모바일에서 성능을 생각한다면 블렌딩 기능은 사용하지 않는 것이 좋습니다. 다만, 금속 느낌이 강한 재질이여서 이미지가 툭툭 바뀌는 모습이 크게 거슬린다면 블렌딩 연산을 사용할 수 밖에 없을 것입니다.
fixed3 blendTarget = UNITY_SAMPLE_TEXCUBE(unity_SpecCube1, rv).rgb * unity_SpecCube1_HDR.r;
o.Emission = lerp(blendTarget, o.Emission, unity_SpecCube0_BoxMin.w);
그러고 나면 리플렉션 프로브가 자연스럽게 반영되는 모습을 확인 할 수 있습니다. 다음 영상은 리플렉션 프로브를 사용하는 커스텀 쉐이더를 적용한 드론의 모습을 보여주고 있습니다.
모두 유료 에셋을 사용한 관계로 프로젝트를 공유해드리지 못하는 점 양해 바랍니다. 다만 제작한 쉐이더의 전체 코드는 다음과 같습니다.
Shader "Example/WorldRefl Normalmap Refl" {
Properties{
_MainTex("Texture", 2D) = "white" {}
_BumpMap("Bumpmap", 2D) = "bump" {}
_Metalic("Metalic", Range(0,1)) = 0.5
_MetalicMap("Metalicmap", 2D) = "white" {}
}
SubShader{
Tags{ "RenderType" = "Opaque" }
CGPROGRAM
#pragma surface surf FakeMetal noshadow nolightmap
struct Input {
float2 uv_MainTex;
float2 uv_BumpMap;
float3 worldRefl;
INTERNAL_DATA
};
struct SurfaceOutputCustom
{
fixed3 Albedo; // diffuse color
fixed3 Normal; // tangent space normal, if written
fixed Alpha; // alpha for transparencies
fixed3 Emission;
fixed3 Metalic;
};
sampler2D _MainTex;
sampler2D _BumpMap;
sampler2D _MetalicMap;
fixed _Metalic;
half4 LightingFakeMetal(SurfaceOutputCustom s, half3 lightDir, half3 viewDir, half atten) {
half NdotL = saturate(dot(s.Normal, lightDir));
half diff = NdotL * 0.5 + 0.5; // Half-Lambert
half4 c;
c.rgb = s.Albedo *_LightColor0.rgb * (diff * atten);
c.a = s.Alpha;
c.rgb *= 1 - s.Metalic;
return c;
}
void surf(Input IN, inout SurfaceOutputCustom o) {
o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
float3 rv = WorldReflectionVector(IN, o.Normal).xyz;
o.Emission = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, rv).rgb * unity_SpecCube0_HDR.r;
//#if UNITY_SPECCUBE_BLENDING
fixed3 blendTarget = UNITY_SAMPLE_TEXCUBE(unity_SpecCube1, rv).rgb * unity_SpecCube1_HDR.r;
o.Emission = lerp(blendTarget, o.Emission, unity_SpecCube0_BoxMin.w);
//#endif
o.Metalic = tex2D(_MetalicMap, IN.uv_MainTex).rgb * _Metalic;
o.Emission.rgb *= o.Metalic.rgb;
}
ENDCG
}
Fallback "Diffuse"
}
이는 메탈 느낌에 초점을 맞춘 유사 PBS라고 볼 수 있겠습니다. 물론 PBS는 전혀 아니지만 모바일에서 저렴한 비용으로 PBS의 느낌적인 느낌을 내기에는 적당하지 않나 싶습니다 :)
'Unity3D > Graphics' 카테고리의 다른 글
유니티에서 ETC2 텍스쳐 사용 시 애매한 사항들 (Ambiguous things by using ETC2 on Unity (4) | 2016.01.06 |
---|---|
PBS 느낌을 흉내내기 위한 Metallic MatCap 쉐이더 (1) | 2016.01.03 |
유니티 5.3 WebGL 업데이트 (UNITY 5.3 WEBGL UPDATES) (0) | 2015.12.09 |
쉐이더에서 한번에 샘플링 할 수 있는 텍스쳐 갯수는? (How many textures do you sample in Shader?) (0) | 2015.10.08 |
유니티5의 라이트맵 빌드 시간이 향상됩니다. (라이트맵 분산 빌드 & 점진적 빌드 소개) (0) | 2015.09.23 |
Unity WebGL 로드맵(Roadmap) (한글) (0) | 2015.09.01 |
몹의 다양화(variation) 작업을 위한 파츠 컬러 쉐이더(Parts Color Shader) (2) | 2015.08.24 |
유니티 5.1부터는 Unity Analytics가 기본적으로 통합되었습니다. (0) | 2015.06.12 |