- 리얼 타임으로 주변을 반사하는 것은 다른 연산들보다 상당히 무겁다.
- 실시간 반사를 위해 많이 사용하는 방법은 Cubemap이라는 환경 텍스쳐를 이용해서 주변 이미지를 텍스쳐로 만든 후 이를 오브젝트에 적용하는 것이다.
- Cubemap은 일종의 텍스쳐이며, 텍스쳐 한 장으로 사방향을 모두 표현하는 특수한 텍스쳐이기 때문에 사용 방법이 약간 다르다.
☑️ Cubemap 적용 코드
_Cube("Cubemap", Cube) = "" {}
- 2D가 아닌 Cube로 받는다.samplerCUBE _Cube;
- Properties에서 입력받은 텍스쳐를 SamplerCUBE 샘플러로 받는다.float3 worldRefl;
- UV가 아닌 반사벡터를 받는다. Cubemap은 주변을 둘러싸고 있는 3차원 공간이기 때문에 float3이며, 이것을 UV로 사용한다.float4 re = texCUBE(_Cube, IN.worldRefl);
- Cubemap 텍스쳐를 연산하는 texCUBE 함수로 텍스쳐의 컬러를 만든다.o.Emission = re.rgb;
- 반사 이미지 자체는 빛의 영향을 받지 않기 때문에 Albedo가 아닌 Emission에 넣는다.❗ 반사하는 것에 빛을 비춘다고 빛에 영향을 받아 밝아지거나 어두워지는 것이 아닌, '밝거나 어두운 주변 이미지를 반사'하는 것 뿐이다.
o.Albedo
에 들어간 흰색과, o.Emission
에 반사 이미지가 더해져 최종 결과가 1이 한참 넘어가 하얗게 떠보이는 결과물이 나왔다.
반사가 100%인 이미지를 만들기 위해 o.Albedo
를 0으로 코드를 수정한다.
(ex-반사가 100%인 물체: 거울이나 금속은 물체의 자체 색상 즉, Albedo가 보이지 않는다.)
void surf (Input IN, inout SurfaceOutput o)
{
fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
float4 re = texCUBE(_Cube, IN.worldRefl);
o.Albedo = 0;
o.Emission = re.rgb;
o.Alpha = c.a;
}
Shader "Custom/CubeMapShader"
{
Properties
{
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_BumpMap("NormalMap", 2D) = "bump"{}
_Cube("Cubemap", Cube) = "" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
CGPROGRAM
#pragma surface surf Lambert noambient
sampler2D _MainTex;
sampler2D _BumpMap;
samplerCUBE _Cube;
struct Input
{
float2 uv_MainTex;
float2 uv_BumpMap;
float3 worldRefl;
};
void surf (Input IN, inout SurfaceOutput o)
{
fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
float4 re = texCUBE(_Cube, IN.worldRefl);
o.Albedo = 0;
o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
o.Emission = re.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
worldRefl
와 UnpackNormal 함수를 거쳐 탄젠트 노멀이 된 데이터o.Normal
을 surf함수에서 동시에 사용해서 발생했다.☑️ 픽셀 월드 노멀로 변화
- 버텍스로부터 받는 반사 벡터는
worldRefl
가 존재해야 한다.INTERNAL_DATA
키워드를 추가한다.struct Input { float2 uv_MainTex; float2 uv_BumpMap; float3 worldRefl; INTERNAL_DATA //세미콜론을 붙이지 않는다! };
- 노멀 연산을 위로 올리고,
WorldReflectionVector
함수로 NormalMap이 적용된 월드 좌표계의 픽셀 노멀을 뽑아 큐브맵의 UV로 사용한다.void surf (Input IN, inout SurfaceOutput o) { o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap)); fixed4 c = tex2D (_MainTex, IN.uv_MainTex); float4 re = texCUBE(_Cube, WorldReflectionVector(IN, o.Normal)); o.Albedo = 0; o.Emission = re.rgb; o.Alpha = c.a; }
- NormalMap이 잘 적용되었다!
Shader "Custom/CubeMapShader"
{
Properties
{
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_BumpMap("NormalMap", 2D) = "bump"{}
_Cube("Cubemap", Cube) = "" {}
_ReflPow("Reflection Power", Range(0, 1)) = 0.5
}
SubShader
{
Tags { "RenderType"="Opaque" }
CGPROGRAM
#pragma surface surf Lambert noambient
sampler2D _MainTex;
sampler2D _BumpMap;
samplerCUBE _Cube;
float _ReflPow;
struct Input
{
float2 uv_MainTex;
float2 uv_BumpMap;
float3 worldRefl;
INTERNAL_DATA
};
void surf (Input IN, inout SurfaceOutput o)
{
o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
float4 re = texCUBE(_Cube, WorldReflectionVector(IN, o.Normal));
o.Albedo = c.rgb * (1 - _ReflPow); //이게 맞는지 모르겠움ㅠ 어려워잉
o.Emission = re.rgb * _ReflPow;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
➕ 여러가지 노멀 상태의 비교
NormalMap이 들어간 픽셀 월드 노멀을 구할 때도 위와 동일한 방법이다.
- UnpackNormal의 결과물 - 전부 파랑색인 탄젠트 좌표계의 노멀
- 버텍스 월드 노멀의 결과물, 버텍스 사이 보간(Interpolation)이 보임
- NormalMap이 적용된 월드좌표계의 픽셀 노멀의 결과물
m.r
만 사용해서 반사의 영역을 조절했다. 나머지 GBA는 어딘가 응용 가능할 것이다.