물리기반렌더링( Physically based rendering, PBR, Physically based shading, PBS, 이하 PBR, PBS)이라는 키워드가 떠오른 것은 꽤 오래 되었고 이미 PC나 콘솔에서는 널리 적용되었습니다. 최근 출시한 메탈기어솔리드5는 그 정점을 보여주고 있습니다.
이러한 관점을 기준으로 느낌을 내는 쉐이더를 만져보보았습니다. 그 결과물인 PBR 대응 머티리얼 텍스쳐들을 그대로 사용하는 가벼운 쉐이더를 소개해드릴까 합니다. MatCap 쉐이더에 Metallic factor를 적용해서 PBS같은 느낌을 내는 방식입니다. 다음 스크린샷 이미지에서 렌더링 되고 있는 케릭터와 발판은 PBR 대응으로 만들어진 텍스쳐들을 그대로 사용하고 있으며 실시간 라이팅이 아닌 MatCap 텍스쳐 기반으로 처리되어있습니다. 데모를 구글 플레이스토어에 올려놓았으므로 기기에 설치하여 확인해보실 수 있습니다.
우선, MatCap 쉐이더를 기반으로 하고 있으므로 MatCap 쉐이더에 대해 간략하게 설명을 드리도록 하겠습니다. MatCap 쉐이더는 모바일에서 유용하게 사용할 수 있는 이미지 기반 라이팅 중 하나입니다. MatCap은 Material Capture의 약자인데, Material Capture는 현실 세계의 라이팅을 수집해서 캡쳐하기 위한 구체를 의미합니다. 보통 CG 영상이나 이미지에서 실사 렌더링을 위한 라이팅 참고 자료로 사용하기 위한 용도로 만들어집니다.
PBS에서의 특징중 하나는 금속과 비금속을 수치로 표현하는 것입니다. 빛이 사물에 닿으면 일부는 흡수되었다가 방출되고 나머지는 바로 반사되 튕겨나갑니다. 이 중 흡수되었다가 방출출되는 빛이 일반적으로 말하는 디퓨즈 영역이고 바로 튕겨나가는 빛이 일반적으로 말하는 스페큘라 영역이 되는 것입니다.
금속은 빛이 닿으면 이러한 디퓨즈 영역이 없이 완전 반사가 일어납니다. 즉, 표면이 금속에 가까울수록 반사의 비중이 높고 금속이 아닌 비전도체에 가까울 수록 디퓨즈의 비중이 높아집니다.
또한, AO맵 역시 반영해주어야합니다. AO는 빛이 차폐되는 정도를 나타내주는 것이므로 라이팅 연산 최종 단계에서 곱해주는 것으로 간단히 처리 해주면 됩니다. Emissive는 발광하는 부분이므로 라이팅 연산 최종 단계에서 더해주는 것으로 간단히 처리 해주면 됩니다. 최종적인 파라미터들은 다음과 같습니다.
쉐이더 코드는 다음과 같습니다. 데모에 쓰인 리소스는 에셋스토어에서 유료로 판매되고 있는 리소스이므로 프로젝트를 공유드리지 못하는 점 양해 바랍니다.
Shader "MatCap/Bumped/Textured Metalic" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_AOMap ("AO (RGB)", 2D) = "white" {}
_AOFactor ("AO Factor" , Range(0.0,2.0)) =1.0
_BumpMap ("Normal Map", 2D) = "bump" {}
_MetalicMap ("Metallic (RGB)", 2D) = "white" {}
_MetalicMultiply ("Metallic Multiply" , Range(0.0,10.0)) =1.0
_EmissiveTex ("Emissive (RGB)", 2D) = "black" {}
_EmissiveMultiply ("Emissive Multiply" , Range(0.0,10.0)) =1.0
_MatCapDiffuse ("MatCap Diffuse (RGB)", 2D) = "white" {}
_MatCapReflect ("MatCap Reflect (RGB)", 2D) = "white" {}
_MatCapReflectMultiply ("MatCap Reflect Multiply" , Range(0.0,10.0)) =1.0
}
Subshader {
Tags { "RenderType"="Opaque" }
Pass {
Tags { "LightMode" = "Always" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"
struct v2f {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float3 c0 : TEXCOORD1;
float3 c1 : TEXCOORD2;
};
uniform float4 _MainTex_ST;
uniform float4 _BumpMap_ST;
v2f vert (appdata_tan v) {
v2f o;
o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);
float3 n = normalize(v.normal).xyz;
float3 t = normalize(v.tangent).xyz;
float3 b = normalize(cross( n, t ) * v.tangent.w);
float3x3 rotation = float3x3( t, b, n );
o.c0 = mul(rotation, normalize(UNITY_MATRIX_IT_MV[0].xyz));
o.c1 = mul(rotation, normalize(UNITY_MATRIX_IT_MV[1].xyz));
return o;
}
uniform sampler2D _MainTex;
uniform sampler2D _BumpMap;
uniform sampler2D _AOMap;
uniform sampler2D _MetalicMap;
uniform sampler2D _MatCapDiffuse;
uniform sampler2D _MatCapReflect;
uniform sampler2D _EmissiveTex;
uniform float _MatCapReflectMultiply;
uniform float _AOFactor;
uniform float _EmissiveMultiply;
uniform float _MetalicMultiply;
fixed4 frag (v2f i) : COLOR {
fixed4 tex = tex2D(_MainTex, i.uv.xy);
fixed4 ao = tex2D(_AOMap, i.uv.xy);
fixed4 metalic = tex2D(_MetalicMap, i.uv.xy);
fixed4 si = tex2D(_EmissiveTex, i.uv.xy);
fixed3 normals = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
half2 capCoord = half2(dot(i.c0, normals), dot(i.c1, normals));
fixed4 diff = tex2D(_MatCapDiffuse, capCoord*0.5+0.5) * tex;
fixed4 refl = tex2D(_MatCapReflect, capCoord*0.5+0.5);
refl.a = 1;
refl *= _MatCapReflectMultiply;
fixed4 ret = lerp( diff, refl, saturate(float4(metalic.rgb * _MetalicMultiply,1)));
return ret * lerp( 1, ao, _AOFactor) + si * _EmissiveMultiply;
}
ENDCG
}
}
Fallback "VertexLit"
}
간단한 쉐이더다보니 많은 한계점을 가지고있습니다. 보시다시피 이는 정확한 PBS는 아닙니다. 많은 부분들이 생략되어 있고 라이팅 재질 느낌은 전적으로 MatCap 텍스쳐에 의지하고 있습니다. 때문에, MatCap 텍스쳐를 어떤 것으로 사용하느냐에 따라 품질이 크게 좌우됩니다. 또한, 씬 별로 라이팅이 다르다면 씬 별로 MatCap 텍스쳐를 만들어 줘야 합니다. MatCap 텍스쳐를 어떻게 만드냐에 따라 결과가 달라질 것입니다.
MatCap 방식은 한 쪽에서 바라보는 라이팅만 표현이 가능하기 때문에 카메라 각도가 고정되어 있는 경우에만 유효하다는 제약이 있습니다.그러나, 대부분의 모바일 게임들은 탑 뷰(Top View), 쿼터 뷰(Quarter View), 백 뷰(Back View) 등 카메라의 방향이 고정된 채로 게임이 진행되는 경우가 대부분이므로 모바일서는 큰 제약 사항이 되지는 않을 것입니다.
덧글
cagetu 2010/12/29 11:05 # 삭제 답글
오즈라엘 2010/12/29 11:13 # 수정 삭제
냥냥양 2010/12/29 23:41 # 삭제 답글
직접적인 ( / 25535 ), ( * 255 ) 요런식으로 하는게 아니라. ceil(), floor(), 요런건로 써서 해보세요.
기억이 맞다면 될거같아요..ㅡ_ㅡ;;;
근데 frac() 요건 그래픽카드마다 결과값이 달라서..영...amd에 문의 해도 대답없고;;;
그리고...64 unsigned int 이거 느리지않던가요??
오즈라엘 2010/12/30 11:02 # 수정 삭제
올림, 내림이 무슨 연관이 있는 것인지 ㅠㅠ
ATi나 NVIDIA에 문의 메일은 의미가 없어요ㅋ 절대 대답 안해줌 ㅋ
개발 가이드 문서의 틀린 내용도 바로 바로 수정 안하는 녀석들인데요 뭐 ㅋ
uint 64bit은 아직 속도 체크는 아직 안해봤는데, RT를 낭비하지 않으려면 선택의 여지가 없어서요 ㅎㅎ
냥냥양 2010/12/30 17:33 # 삭제 답글
렌더타겟줄이실라고 그런거죠?
제가 방금 테스트해봤는데, amd계열은 속도가 훌륭하고, nvidia계열은 암울하네요...어흑
오즈라엘 2010/12/31 01:28 # 수정 삭제
" there is no native integer math on GPUs before SM4.0"
이 문장을 제대로 보질 못했었네요 ㅎ 이 문장 대로 제대로 처리를 못해서 7600에서는 결과가 이상하게 나오나봅니다.
아래에 링크된 글들을 보면 흐름과 컨셉은 알겠는데, 왜 frac()이 비트 쉬프트의 의미를 가지게 되는지 이해는 안가더라구요. 소수부의 숫자들이 어떻게 bitwise의 효과를 가지게 되는 건가요ㅜㅜ?
nvidia 계열은 어떤 모델로 테스트해보신건지요? 저는 PF64랑 UINT64랑 별 속도 차이는 없어보이던데''
아직 7600에서는 정상적으로 돌지 않아 비교를 안해봤고 8600이상에서 비교해봤습니다.
냥냥양 2011/01/01 13:55 # 삭제 답글
{
vecEncode = floor( float2( vecEncode.x * 255.0f, vecEncode.y * 255.0f ) );
float fResult = floor( floor( vecEncode.x * 255.0f ) + vecEncode.y ) / 65535.0f;
return fResult;
}
float2 Float2Decode( float fDecode )
{
fDecode = floor( fDecode * 65535.0f );
float2 vecResult;
vecResult.x = floor( fDecode / 255.0f );
vecResult.x /= 255.0f;
vecResult.y = frac( fDecode / 255.0f );
return vecResult;
}
이렇게 대충 코딩해 봤는데 문제는 Decal때문에 AlphaBlend를 하고있는 부분이 깨지네요..ㅠㅠ
오즈라엘 2011/01/03 13:59 # 수정 삭제
그나저나 7600에선 여전히 비정상이네요 ㅠㅠ
냥냥양 2011/01/04 01:04 # 삭제 답글
오즈라엘 2011/01/04 11:30 # 수정 삭제
6,7 시리즈는 좀 까리까리한게 있는데, 개발 가이드 문서(http://developer.download.nvidia.com/GPU_Programming_Guide/GPU_Programming_Guide_Korean.pdf)에서는 A16B16G16R16 텍스쳐가 지원 되지 않는다고 나와 있습니다. 하지만 실제로 caps를 뒤져보면 지원을 하더군요. 애초에 지원을 하지를 말던가.. 원..
오즈라엘 2011/01/07 00:48 # 수정 삭제
냥냥양 2011/01/11 10:15 # 삭제 답글
전 6&7은 버텍스단위에서 라이트계산하고 그림자도 빼고, 포워드로 렌더링하려구요.. 그런거 다 하기엔
넘힘드네요.. 것보다.. m330에서 돌려봤더니 좌절이라. 그쪽 최적화해야겠어요
오즈라엘 2011/01/12 23:13 # 수정 삭제
테라처럼 쌩 디퓨즈만 지원하는 옵을 추가해서 커버하려구요
해상도가 점점 커져가는데 6,7 이넘들은 이를 따라올 수가 없네요.
mx330 말씀하시는 것인가요? 그넘도 지원하시나요 +ㅈ+?
냥냥양 2011/01/15 09:39 # 삭제 답글
오즈라엘 2011/01/15 14:52 # 수정 삭제
노트북용 하면 인텔 GMA가 대박이죠 ㅎ
인텔칩도 스펙상으로는 허용 범위라 신경써야될 밴더가 더 많아졌습니다
OTL