336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.


들어가며

물리기반렌더링( Physically based rendering, PBR, Physically based shading, PBS, 이하 PBR, PBS)이라는 키워드가 떠오른 것은 꽤 오래 되었고 이미 PC나 콘솔에서는 널리 적용되었습니다. 최근 출시한 메탈기어솔리드5는 그 정점을 보여주고 있습니다.

하지만, 모바일이 주력 시장인 국내 게임 시장에서는 관심 밖이였지요. 하지만 최근들어 국내에서도 PBR에 대한 관심이 많아지고 있습니다. 이러한 관심의 가장 큰 이유는 모바일 하드웨어의 성능 발전일 것입니다. PBR이 기본적으로 소모하는 비용을 충분히 감당한 만큼의 성능을 내는 기기들이 보편화 되고 있는 것입니다. 비단 최신 기종이 아니더라도 캐릭터에게만 PBS를 사용하는 등 부분적으로만 사용하여 성능을 절약할 수도 있습니다. 하지만, PBS 쉐이더를 사용하지 않고도 PBS 느낌을 낼 수 있다면 더욱 많은 성능을 절약할 수 있을것입니다. 물론, 초실사 렌더링을 지향하는 게임이라면 100% PBS로 렌더링을 해야겠지요. 하지만 대부분의 모바일 게임들은 그렇지가 않습니다. 근래의 모바일 게임에서 PBS사용하고자 함은 좀 더 멋진 그래픽을 표현하고자 함이 주 목적일일 뿐이지 꼭 물리적으로 정확한 그래픽을 표현하고자 함이 주 목적은 아니라고 생각합니다. 특히, 그 초점은 금속 재질 표현에 맞춰져 있습니다. 비 금속 재질보다는 금속 재질에 촛점을 맞춰서 금속 부분과 금속이 아닌 부분을 함께 표현하고자 함이 목적인 것으로 생각됩니다. 

이러한 관점을 기준으로 느낌을 내는 쉐이더를 만져보보았습니다. 그 결과물인 PBR 대응 머티리얼 텍스쳐들을 그대로 사용하는 가벼운 쉐이더를 소개해드릴까 합니다. MatCap 쉐이더에 Metallic factor를 적용해서 PBS같은 느낌을 내는 방식입니다. 다음 스크린샷 이미지에서 렌더링 되고 있는 케릭터와 발판은 PBR 대응으로 만들어진 텍스쳐들을 그대로 사용하고 있으며 실시간 라이팅이 아닌 MatCap 텍스쳐 기반으로 처리되어있습니다. 데모를 구글 플레이스토어에 올려놓았으므로 기기에 설치하여 확인해보실 수 있습니다. 

다운로드 링크 : https://play.google.com/store/apps/details?id=com.ozproject.demo1



MatCap 쉐이더

우선, MatCap 쉐이더를 기반으로 하고 있으므로 MatCap 쉐이더에 대해 간략하게 설명을 드리도록 하겠습니다. MatCap 쉐이더는 모바일에서 유용하게 사용할 수 있는 이미지 기반 라이팅 중 하나입니다. MatCap은 Material Capture의 약자인데, Material Capture는 현실 세계의 라이팅을 수집해서 캡쳐하기 위한 구체를 의미합니다. 보통 CG 영상이나 이미지에서 실사 렌더링을 위한 라이팅 참고 자료로 사용하기 위한 용도로 만들어집니다.

MatCap 쉐이더는 이러한 Material Capture를 그대로 텍스쳐(이를 MatCap 텍스쳐라 부릅니다)로 만들어서 라이팅 결과로 활용하는 것입니다. 이를 이용하면 재질에 따른 라이팅을 미리 텍스쳐로 만들어놓기 때문에 실시간으로 라이팅 연산을 않고도 실사적인 라이팅을 처리할 수 있습니다. 

출처 : https://shaderforge.userecho.com/topic/416166-it_matrix/

MatCap 쉐이더는 유니티에 내장된 쉐이더는 아니지만 구글링이나 유니티 에셋스토어에서 쉽게 구할 수 있습니다. 이 글에서 소개하고자 하는 쉐이더는 에셋스토어에 있는 MatCap 쉐이더를 기반으로 작업하였습니다. 쉐이더 링크 : https://www.assetstore.unity3d.com/en/#!/content/8221


Metallic

PBS에서의 특징중 하나는 금속과 비금속을 수치로 표현하는 것입니다. 빛이 사물에 닿으면 일부는 흡수되었다가 방출되고 나머지는 바로 반사되 튕겨나갑니다. 이 중 흡수되었다가 방출출되는 빛이 일반적으로 말하는 디퓨즈 영역이고 바로 튕겨나가는 빛이 일반적으로 말하는 스페큘라 영역이 되는 것입니다.

금속은 빛이 닿으면 이러한 디퓨즈 영역이 없이 완전 반사가 일어납니다. 즉, 표면이 금속에 가까울수록 반사의 비중이 높고 금속이 아닌 비전도체에 가까울 수록 디퓨즈의 비중이 높아집니다. 

이미지 출처 : http://docs.unity3d.com/kr/current/Manual/StandardShaderMaterialCharts.html

PBS 머리티얼에서는 이를 반영하여 얼마나 금속에 가까운지의 정도를 나타내는 Metallic 텍스쳐를 사용합니다.

기존의 MatCap 쉐이더는 MatCap 텍스쳐를 한 장만 사용합니다. 이를 Metallic 텍스쳐를 사용하기 위해서 MatCap 텍스쳐를 두 장을 사용하도록 변경해줍니다. 금속성이 0인 경우의 MatCap 텍스쳐와 완전 금속인 경우의 MatCap 두 개의 MatCap을 사용하여 Metallic에 따라 비중을 반영해주면 되는 것입니다. 


파라미터

또한, 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) 등 카메라의 방향이 고정된 채로 게임이 진행되는 경우가 대부분이므로 모바일서는 큰 제약 사항이 되지는 않을 것입니다.



Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.


Variation

게임에는 기본적으로 플레이어가 존재하고, 그 플레이어에게 방해가 되는 장애물이 존재합니다. 퍼즐류가 아닌 이상 이러한 장애물은 몹 또는 몬스터의 형태로 나타나게 되고, 게임의 재미를 좌우하는데 결정적인 역할을 하는 존재가 됩니다.

이미지 : 엔젤 스톤

이러한 몹의 종류가 적으면 사용자는 금새 지루해지게 됩니다. 때문에, 개발자는 매우 다양한 모습과 패턴을 갖도록 만들어야 합니다. 예를 들어 좀비 형태의 몹을 만든다 가정했을 때, 모자를 쓴 좀비, 창을 든 좀비, 뚱뚱한 좀비 등등 좀비라는 큰 카테고리 안에서도 다양한 모습의 좀비를 만들어내야 합니다. 이를 흔히 "다양화(variation)시킨다" 라고 표현합니다.

이미지 : PVZ

하지만 이 때 문제는 리소스의 용량 크기입니다. 완전히 각각의 종류마다 완전히 새로운 리소스로 따로 따로 제작하게되면 리소스의 크기가 커질 수 밖에 없게 됩니다.때문에, 이러한 다양화(variation) 작업을 거칠 때 리소스를 최소한으로 사용하는 방법을 사용해야만 합니다.


부품 모듈화

그 방법 중 하나가 각각 파츠별로 모듈화 시키는 방법입니다. 각 파츠별로 부품 모듈을 만들어 놓고 프라모델이나 레고처럼 모듈을 조립하여 다양한 모습을 만들어 내는 것입니다. 예를 들어 로보트를 팔, 다리, 몸통, 총구 등으로 나누어 여러 종류로 제작한 뒤 이를 조립하여 다양한 종류의 로보트를 만들어 내는 것입니다. 보통 로보트나 비행기 등 강체로 이루어진 것 들이 이러한 조립식으로 많이 만들어집니다. 에셋 스토어에서도 마찬가지로 이러한 조립식 에셋들을 많이 찾아볼 수 있습니다. 

이미지 : 에셋스토어


텍스쳐 변경

또 다른 방법으로는 텍스쳐만 바꾸는 방법도 존재합니다. 동일한 메시에 텍스쳐만 바꿔서 다른 모습을 연출해주게 되는 것입니다. 주로 인간형 케릭터에게 의상을 다양화 시킬 때 이러한 방법을 사용합니다.  

이미지 : 에셋스토어


파츠 컬러

이 글에서 소개할 또 다른 방법으로는 파츠 컬러를 변경하는 방법입니다. 앞서 방금 언급한 텍스쳐를 바꾸는 방식이나 메시 모듈들 조립하는 방식과는 달리, 메시와 텍스쳐는 교체하지 않고 하나의 텍스쳐와 하나의 메시만 사용하는 방식입니다. 때문에 앞서 언급한 방식들에 비해서 리소스를 크게 아낄 수 있습니다. 다만, 텍스쳐를 통채로 바꾸는 것이 아니기 때문에 전체적인 패턴과 모양은 그대로 유지하기 때문에 한계가 있습니다. 대신 전체적인 혹은 부분적인 색상을 바꿀 수 있기 때문에 주로 의상의 염색 시스템에서 활용되기도 합니다. 

이미지 : 레이더즈

또한 다양화 작업 말고도 RTS나 MOBA 등 대전 게임에서 팀 구분을 위한 색상으로 활용될 수도 있습니다.

이미지 : 스타크래프트2

이 글에서는 이러한 파츠 컬러 방식을 구현하는 것에 대하여 자세히 설명을 해볼까 합니다. 기본 원리는 간단합니다. 메시의 기본 albedo 색상 외 컬러를 변경할 마스크 영역을 준비합니다. 단순한 파츠 컬러 적용 여부를 나타내는 크레이스케일 값이기만 하면 되기 때문에 albedo 텍스쳐의 알파 채널에 이를 활용하면 됩니다. 물론, 대신 불투명 오브젝트에만 적용이 가능해진다는 제약은 있습니다.

그 예로 다음과 같은 로보트 Kyle에 이를 적용하여 보겠습니다.

이 모델은 불투명으로 만들어져 있어서 실제로는 다음과 같은 albedo 텍스쳐의 RGB만 사용합니다.

이 텍스쳐의 Alpha 채널을 다음과 같이 칠하여 파츠 컬러의 마스킹으로 사용하도록 합니다. 다음 이미지는 GIMP툴에서 보여주는 Alpha 채널의 모습입니다. 검은색이 1을 나타내고 회색의 체크무늬가 0인 영역을 의미합니다.

이 Alpha 채널의 역의 값을 마스킹으로 사용하여 (위 이미지의 회색 체크무늬 영역이 파츠 컬러가 되도록) 적용하는 것이 최종 목표입니다. 샘플 프로젝트를 올려두었습니다. 이 링크를 통해 다운받으셔서 확인해보실 수 있습니다.

쉐이더가 적용 된 마테리얼에서 Parts Color 색상을 변경할 수 있습니다.

이 색상을 바꾸면 이에 따라서 로봇의 파츠 컬러가 변경되어 보여집니다.


이 방식은 원리도 간단하기 때문에 쉐이더 코드도 크게 복잡하지 않습니다. 샘플 프로젝트에 포함 된 LambertUseColorPart.shader를 살표보도록 하겠습니다.

27번째 라인에서 albedo 텍스쳐의 값을 읽은 뒤 29번째 라인에서 alpha 채널의 값을 뒤집어줍니다. 이는 작업자의 성향에 따라 파츠 컬러 적용 값을 1로 둘것인가 0으로 둘 것인가에 따라 다릅니다. 머티리얼의 Invert Parts Color Mask 체크 여부에 따라 수행 여부가 달라집니다.

35번째 라인이 이 쉐이더의 핵심 구문입니다. 파츠컬러의 마스킹 값에 따라서 흰색과 지정된 색상 값을 보간합니다. 그후 그 값을 albedo와 곱하여 최종 albedo 값을 결정합니다. 결과적으로, 마스크가 1이면 albedo에 지정 컬러를 곱한 결과가 됩니다. 반대로 마스크가 0이면 원래의 albedo 색상을 그대로 사용합니다.

만일 머티리얼에서 Interpolated Parts Color가 체크되어 있다면 35번째 라인 대신 33번째 라인이 수행됩니다. 이는 albedo값과 지정 색상 값을 바로 보간합니다. 이는 흰색과 보간한 뒤 albedo에 곱해주는 결과와는 미묘한 차이를 가집니다.

다음 로보트의 몸통 이미지는 각각 33번 라인과 35번 라인을 수행 했을 때의 차이를 보여주고 있습니다. 첫 번 째 이미지는 35번 라인이 수행된 모습입니다. albedo와 곱하여 사용한 결과이기 때문에 albedo 자체에 새겨놓은 AO 라이팅이나 얼룩등이 그대로 살아있습니다. 만일 텍스쳐 자체에 라이팅이나 AO등을 새겨놓는 식으로 작업한다면  Interpolated Parts Color가 비활성화 되어야합니다.


이 두 번 째 이미지는 그 반대로 33번 라인이 수행된 모습입니다. albedo와 바로 보간해서 사용하기 때문에 마스크가 1인 영역은 albedo 텍스쳐의 모습을 완전히 없애버립니다. 첫 번 째의 이미지와는 달리 몸통에 페인트가 덧 칠해진 것 같은 느낌입니다. PBS 대응 텍스쳐는 albedo에 AO 및 라이팅이 포함되어서는 안되기 때문에, 만일 PBS 대응으로 작업하는 이미지라면 이 모습이 용이할 수도 있습니다. (예를 들어서 금속 물질 위에 칠해져있는 페인트가 벗겨져 있는 모습이라면) 



한계

이 쉐이더는 알파채널을 이용하여 한번에 한가지 색상만 적용합니다. 웬만한 경우에서는 한 가지 색상만 적용해줘도 크게 무리는 없습니다. 만일 푸품 모듈화 방식과 같이 사용한다면 더 다양한 조합을 만들 수도 있을 것입니다.

하지만 단일 메시만 사용하는 경우라면 단일 색상만 적용 가능 한 것이 조금 답답할 수도 있겠습니다. 그러한 경우에는 albedo 텍스쳐의 alpha 채널을 마스크로 활용하는 것이 아니라, 아예 마스크 텍스쳐를 별도로 제작하면 더욱 다양하게 활용이 가능합니다. 색상을 4가지를 지정하고 마스크 텍스쳐의 r,g,b,a 채널 모두 각각의 마스킹으로 활용하면 4가지 색상을 적용할 수 있습니다. 다만 추가적인 텍스쳐가 사용되므로 약간의 성능 감소는 있을 수 있습니다. 그렇다 하더라도 잘만 조작하면 적은 텍스쳐 메모리만으로도 훨씬 다양한 다양화(variation) 효과를 볼 수 있기 때문에 충분한 가치가 있습니다.

쉐이더 작성이 익숙하신 분이라면 쉐이더를 직접 수정하여 사용하시면 되고, 그렇지 않은 분이시라면 에셋 스토어에서 받아서 사용하시면 됩니다. 이 에셋이 제가 설명드린 것과 비슷한 작동을 수행하는 쉐이더를 사용하고 있습니다. ( 사실, 컬러 4개 지정하는 쉐이더를 올릴 까 했는데, 이 에셋이 유료로 판매되는 것을 보고나니 상도덕(?) 상 그러지를 못하겠네요;;)

이미지 : 에셋스토어


샘플 프로젝트 다운로드 링크 : https://drive.google.com/file/d/0B70AOyGiQJsGY2dEakU4Y2Q4WWs/view?usp=sharing



Posted by ozlael
,