[W.I.P] [Tutorials]Understanding allegorithmic substance designer. PART-6

서브스턴스 디자이너 한국 개발자 그룹을 구독 하시려면.
http://subinkorea.blogspot.kr/


christian.kim@allegorithmic.com 로 메일신청.
This issue that some play about Add to custom shader for substance designer.


일단 여기까지 꼭지만 일단 ....

이번 쳅터는 오우거 엔진을 좀 보셨던 분은 아.... 대충 비슷 하구나. 란 느낌이 들거 같네요.

파싱 콘테이너는 xml 로 되어 있고 이것은 오우거 쉐이더 파싱 구조와 유사 합니다.

오우거 엔진에서 사용 되는 .pragram 과 거의 비슷 합니다.

오우거 엔진 에서는 .pragram 에서 matrix semantic definition 이나 uniform variable value definition 등을 define 해 줘야 하거든요.

타입은 xml 인데 쉽게 이해 되실 듯 합니다.

xml 문법은 여기를 참조.


일단 matcap 쉐이더 에서는 라이트에 관한 구현부가 전혀 없기 때문에 주석처리.;






주말 출근 시즌이라 이런 저런 일을 처리 하고 좀 더 추가 해 봅니다.

정말 쥐 오줌처럼 조금씩 추가 하게 되네요. :(

요즘 회사에서 FumeFX 와 Particle Flow 와 씨름 중이라 머리가 폭팔 할 지경 입니다.

사족이 길었군요... ㅋ; 왠지 내용이 길어 보이고 좋군요. -_-;


좀 귀찮기도 하고 해서 커스텀 쉐이딩 쪽은 구글링을 하지 않고 이리 저리 컴파일 해 보면서 테스트 중이랍니다.
일단은 서브스턴스 메뉴얼에 커스텀 쉐이더 추가 규칙이나 내부 API 문서가 있는 거 같진 않은데 안찾아 봐서 모르겠습니다. 누가 좀 찾으시면 링크 좀....

찾았어요.. -_-;

서브스턴스 디자이너 내부에서 이미 정해 져 있는 Output 타입을 살펴 보면 ambient 가 있습니다.
결국 matcap 은 반구형의 2D 암비언트 텍스처 맵이라고 볼 수 있기 때문에 ambient 아웃풋 노드를 만들도록 합니다.

그리고 서브스턴스 디자이너를 위한 glslfx 안에서 선언 해야 하는 usage 키워드와 서브스턴스 디자이너 코어 라이브러리와 에디터 라이브러리 내부에서 구현 된 대리자(아마도…)넘겨 주는 지시자 역시 미리 정의 되어 있는 범주 안에서만 커스터마이징이 가능 한 것으로 보입니다.(일단 여기까지는 제가 대략 추정 한 내용 일 뿐입니다.)



좀 더 진행을 해 보기 전에 몇 가지 확인 하고 넘어 갈 생각인데요.
쉐이더나 glslfx 파일을 수정 후 다시 컴파일 하려면 3D View 를 클릭 한 상태에서 Ctrl + R 을 누르면 제컴파일이 됩니다.

만약 잘못 된 라인이 있다면 그림 처럼 로그를 보여 줍니다.


이렇게 말이죠.
암튼... 전체적인 구성 자체가 왠지 저는 오우거 엔진 기반 같다는 생각이... 떠나질 않네요. ^^


glslfx 에서의 usage 는 확실히 미리 코어 라이브러리에서 정의 되어 있는 키워드 같습니다.
만약 아래와 같이..


<sampler name="ambientHemisphericalMap" usage="ambient"/>


가 아닌


<sampler name="ambientHemisphericalMap" usage="ambientMap"/>


언디파인 된 키워드를 사용 하면 인터페이스에는 sampler name 을 표시 하도록 되어 있네요.


솔직히 코드소스 보면 금방 알텐데... ㅜㅜ; 5분도 안걸리는 건데.... 이틀을 삽질을.;;;
암튼...


http://support.allegorithmic.com/documentation/display/SD41/GLSLFX+Shaders?src=contextnavpagetreemode ( GLSLFX 레퍼런스 )


정의 된 usage 키워드를 사용 하면 아래 처럼.



정의 되지 않는 usage 키워드를 사용 하면 sampler 이름을 문자열로 리턴 해서 사용 하도록 되어 있습니다.
마단 실제 usage 키워드가 정해진 대로만 사용 해야 하는 지는 좀 더 실제 쉐이더 코드를 만들어 보면서 살펴 볼게요.


솔직히 이거 쓰면서.... ㅜㅜ;
쉐이더는 아무것도 아닌데...


XML 데이터의 정확한 규칙을 모르겠음.
이건 뭐 ... 코어 인테페이스 로직에서 뭔 짓을 해 놨는지 알아야..;;; -_-;


암튼... 몇일동안 계속 삽질 중입니다.


여차 저차 하여 기본적인 디퓨즈 컬러와 암비언트 맵이 적용 되도록 쉐이더 코드를 수정 하였습니다.



차 후에 matcap + 2 lighting 을 해보려고... 3dsmax 2015 의 shaderFX 에서 matcap 을 간단히 구현 해 봤습니다.(아래)








일단 코드 테스트를 위해서 그림처럼 RGB 텍스처를 만들도록 합니다.







위와 같은 결과를 얻을 수 있도록 쉐이더 코드를 작성 할 것입니다.
카메라를 돌려도 계속 색의 패턴이 프로젝션 맵핑 되는 것 처럼 보인다면 대략 완성 단계에 가깝다고 보시면 됩니다.


matcap.glslfx 코드.

<?xml version="1.0" encoding="UTF-8"?>
<glslfx version="1.0.0" author="allegorithmic.com">


<!-- TECHNIQUES -->
<technique name="Matcap">
<!-- PROPERTIES -->
<property name="blend_enabled" value="true"/>
<property name="blend_func" value="src_alpha,one_minus_src_alpha"/>
<property name="cull_face_enabled" value="true"/>
<property name="cull_face_mode" value="back"/>


<!-- SHADERS -->
<shader type="vertex" filename="matcap/vs.glsl"/>
<shader type="fragment" filename="matcap/fs.glsl"/>


</technique>


<!-- INPUT VERTEX FORMAT -->
<vertexformat name="iVS_Position" semantic="position"/>
<vertexformat name="iVS_Normal" semantic="normal"/>
<vertexformat name="iVS_UV" semantic="texcoord0"/>
<vertexformat name="iVS_Tangent" semantic="tangent0"/>
<vertexformat name="iVS_Binormal" semantic="binormal0"/>


<!-- SAMPLERS -->
<sampler name="diffuseMap" usage="diffuse"/>
<sampler name="normalMap" usage="normal"/>
<sampler name="matcapMap" usage="emissive"/>
<sampler name="AOMap" usage="ambientocclusion"/>
<sampler name="rimMap" usage="mask"/>




<!-- MATRICES -->
<uniform name="modelViewMatrix" semantic="modelview"/>
<uniform name="worldMatrix" semantic="world"/>
<uniform name="worldViewMatrix" semantic="worldview"/>
<uniform name="projectionMatrix" semantic="projection"/>

<uniform name="worldViewProjMatrix" semantic="worldviewprojection"/>
<uniform name="worldInverseTransposeMatrix" semantic="worldinversetranspose"/>
<uniform name="viewInverseMatrix" semantic="viewinverse"/>


<!-- SCENE PARAMETERS -->
<uniform name="ambientColor" semantic="ambient"/>



<!-- UNIFORMS -->
<uniform name="tiling" guiName="Tiling" default="1" min="1" guiWidget="slider" guiMax="10"/>
<uniform name="heightMapScale" guiGroup="Normal" guiName="Normal Strength" default="1" guiWidget="slider" guiMin="0" guiMax="2" />
<uniform name="matcapPowr" guiGroup="MatCap" guiName="Matcap Strength" default="1" guiWidget="slider" guiMin="0" guiMax="5" />

<uniform name="flipY" guiGroup="Normal" guiName="DirectX Normal" default="true" guiWidget="checkbox" />

</glslfx>






vs.glsl 코드
/////////////////////////////// Vertex shader
#version 120

attribute vec4 iVS_Position;
attribute vec4 iVS_Normal;
attribute vec2 iVS_UV;
attribute vec4 iVS_Tangent;
attribute vec4 iVS_Binormal;

varying vec3 iFS_Normal;
varying vec2 iFS_UV;

varying vec3 TtoV0;
varying vec3 TtoV1;
varying vec3 TtoV2;


varying vec4 iFS_Tangent;
varying vec4 iFS_Binormal;
varying vec4 iFS_PointWS;

uniform mat4 modelViewMatrix;
uniform mat4 worldMatrix;
uniform mat4 worldViewMatrix;
uniform mat4 worldViewProjMatrix;
uniform mat4 worldInverseTransposeMatrix;
uniform mat4 viewInverseMatrix;
uniform mat4 projectionMatrix;


void main()
{
gl_Position = worldViewProjMatrix * iVS_Position;

iFS_UV = iVS_UV;

iFS_Normal = (worldInverseTransposeMatrix * iVS_Normal).xyz;


iFS_Tangent.xyz = (worldInverseTransposeMatrix * iVS_Tangent).xyz;


iFS_Binormal.xyz = (worldInverseTransposeMatrix * iVS_Binormal).xyz;


vec3 biNorm = cross( iFS_Tangent.xyz, iFS_Normal ) * iFS_Tangent.w;

iFS_PointWS.xyz = (worldMatrix * iVS_Position).xyz;

mat3 rotationMat = mat3(iFS_Tangent.xyz , biNorm , iFS_Normal);

// use like unity3d macro based matrix function name
mat3 UNITY_MATRIX_IT_MV = mat3(worldViewMatrix * worldInverseTransposeMatrix ); // inverse transpose matrix for Inverse transpose model view matrix

mat3 TtoV_Mat = UNITY_MATRIX_IT_MV * (rotationMat);

TtoV0 = TtoV_Mat[0];
TtoV1 = TtoV_Mat[1];
TtoV2 = TtoV_Mat[2];


}
//vs shader end

-------------------------------------------------------------------------------
버택스 쉐이더에서 유심히 볼 것은 ...

제가 잘 몰라서 그런지 ... 암튼...
서브스턴스 디자이너의 빌트인 메티릭스 에
Inverse Transpose modelview matix 가 없던 건데요...
보통 어지간한 엔진들은 내부에서 역전치 행렬 공간변환에 대한 것도 정의가 다 되어있는데 말이죠....
아무튼...
mat3 UNITY_MATRIX_IT_MV = mat3(worldViewMatrix * worldInverseTransposeMatrix );
식으로 공간변환을 추가 해 줬습니다.
UNITY_MATRIX_IT_MV 는 유니티 유저이기 때문에 머리에 빨리 들어 오도록 이름을 같게 했습니다.

rotationMat 는 Tangent rotate Transpose 입니다.
유니티 쉐이더랩 에서는 메크로 화 되어 있는데 풀어서 쓰면
mat3 rotationMat = mat3(iFS_Tangent.xyz , biNorm , iFS_Normal);
가 됩니다.

하지만 iFS_Binormal 을 사용 하지 않고 따로 외각을 구하는 cross 함수를 사용 해서
따로 biNorm 을 만들어 주어야 합니다.

vec3 biNorm = cross( iFS_Tangent.xyz, iFS_Normal ) * iFS_Tangent.w;
이렇게 말이죠.

유니티 쉐이더랩에서 메크로화 되어 있는
Transformations 참조.
https://docs.unity3d.com/Documentation/Components/SL-BuiltinValues.html


-------------------------------------------------------------------------------
fs.glsl 코드


//////////////////////////////// Fragment shader
#version 120


varying vec3 TtoV0;
varying vec3 TtoV1;
varying vec3 TtoV2;

varying vec2 iFS_UV;
varying vec3 iFS_Normal;
varying vec3 iFS_Tangent;
varying vec3 iFS_Binormal;
varying vec3 iFS_PointWS;

uniform vec3 Lamp0Pos = vec3(0.0,0.0,70.0);
uniform vec3 Lamp0Color = vec3(1.0,1.0,1.0);
uniform float Lamp0Intensity = 1.0;
uniform vec3 Lamp1Pos = vec3(70.0,0.0,0.0);
uniform vec3 Lamp1Color = vec3(0.198,0.198,0.198);
uniform float Lamp1Intensity = 1.0;

uniform float Ka = 1;
uniform float heightMapScale = 1;
uniform vec3 ambientColor = vec3(0.07,0.07,0.07);
uniform float tiling = 1.0;
uniform bool flipY = true;
uniform float matcapPowr = 1.0;
uniform float RimPowr = 1.0;

uniform sampler2D normalMap;
uniform sampler2D diffuseMap;
uniform sampler2D matcapMap;
uniform sampler2D AOMap;
uniform sampler2D rimMap;

uniform mat4 viewInverseMatrix;


vec3 fixNormalSample(vec3 v)
{
vec3 result = v - vec3(0.5,0.5,0.5);
result.y = flipY ? -result.y : result.y;
return result;
}


vec3 srgb_to_linear(vec3 c)
{
return pow(c,vec3(2.2,2.2,2.2));
}

vec3 linear_to_srgb(vec3 c)
{
return pow(c,vec3(0.4545,0.4545,0.4545));
}

void main()
{

vec3 cameraPosWS = viewInverseMatrix[3].xyz;
vec3 pointToCameraDirWS = normalize(cameraPosWS - iFS_PointWS);
vec3 pointToLight0DirWS = normalize(Lamp0Pos - iFS_PointWS);
vec3 pointToLight1DirWS = normalize(Lamp1Pos - iFS_PointWS);
vec3 normalWS = iFS_Normal;
vec3 tangentWS = iFS_Tangent;
vec3 binormalWS = iFS_Binormal;

// ------------------------------------------
// Update UV
vec2 uv = iFS_UV * tiling;

// ------------------------------------------
// Add Normal from normalMap
vec3 normalTS = texture2D(normalMap,uv).xyz;

vec3 cumulatedNormalWS = normalWS;

vec3 T = normalize(TtoV0.xyz);
vec3 B = normalize(TtoV1.xyz);
vec3 N = normalize(TtoV2.xyz);

if (length(normalTS)>0.0001)
{
normalTS = fixNormalSample(normalTS);
normalTS *= heightMapScale;
//vec3 normalMapWS = normalTS.x * tangentWS + normalTS.y * binormalWS;
vec3 normalMapWS = normalize(N + normalTS.y * T + normalTS.x * B);
cumulatedNormalWS = cumulatedNormalWS + normalMapWS;
cumulatedNormalWS = normalize(cumulatedNormalWS);
}


// Diffuse
vec3 diffuseColor = srgb_to_linear(texture2D(diffuseMap,uv).rgb);

// Matcap
vec2 abmUV;
abmUV.x = (T.x * normalTS.x + B.x * normalTS.y + N.x * normalTS.z) * 0.5 + 0.5;
abmUV.y = (T.y * normalTS.x + B.y * normalTS.y + N.y * normalTS.z) * -0.5 + 0.5;

//ambientOcclusionColor
vec3 AO_TEX = srgb_to_linear(texture2D(AOMap,uv).rgb);

vec3 Rim_TEX = srgb_to_linear(texture2D(rimMap,abmUV.xy).rgb);
Rim_TEX *=RimPowr;

vec3 ambientMapColor = srgb_to_linear(texture2D( matcapMap , abmUV.xy).rgb) * matcapPowr;




// ------------------------------------------
vec3 finalcolor = (diffuseColor.rgb * AO_TEX.rgb) * (ambientMapColor.rgb * ambientColor) + Rim_TEX.rgb;

// Final Color
gl_FragColor = vec4(linear_to_srgb(finalcolor), 1.0);
}






코드 중 아직 좀 미완성 부분도 있고 (AO_TEX 를 활용 한 감마콘트롤은 빠져있어요.)
대충 pseudo code 는 아래와 같긴 합니다.

한번 넣어 보세요.


그리고 전체 코드 중에... 앞으로 버전 2.0 에서 작업 할 예정인 다이나믹 라이트와의 혼용.
라이트를 나중에 또 추가 할 것이기 때문에 기존의 Lamp0 과 Lamp1 에 대한 변수등은 남겨 둔 상태 입니다.

위 코드를 보시면 RimTex 에 대한 부분이 있습니다.
vec3 finalcolor = (diffuseColor.rgb * AO_TEX.rgb) * (ambientMapColor.rgb * ambientColor) + Rim_TEX.rgb;
로 만들었는데요.
가장 마지막에 Rim_TEX.rgb 를 더해 주도록 합니다.




Rim_TEX 는 따로 텍스처를 만들지 않았고 이 전 강좌에서 배웠던 필터를 사용 했고 Transform2D 를 중간에 추가 하여 림라이트의 주방향을 제어 할 수 있도록 하였습니다.

이것은 차 후 유니티 등에서 활용 될 수 있는 연결 변수 일 수 있습니다.





glslfx.xml
의 내용.
Matcap 강도와 림텍스처의 강도에 대한 변수가 추가 되었습니다.


림라이트 텍스처의 패턴 컬러에 기본 색조를 만들고 HSL 값을 변경 하여 그림처럼 다양한 컬러가 들어 간 림텍스처를 만들어 보는 것도 좋은 방법입니다.




이런 식이 될 수 있겠죠.




1차로 나온 결과물 입니다.
여러 값을 수정 해 가면서 적당한 쉐이딩을 찾아 가면 될 듯 합니다.




전체 노드 맵.



모바일용 으로 제작 해 본 Matcap 확장 커스텀 쉐이더 적용 이미지.

서브스턴스 디자이너의 두개의 라이팅은 적용이 되지 않습니다.

사전 정의 된 2D 암비언트 텍스처로 베이스 라이팅 느낌을 주었고 림텍스처를 활용 해서

여러개의 간접광이 비추는 것 처럼 느낌을 내 봤습니다.
최대한 가벼운 상태로 적당한 느낌을 낼 수 있는 모바일 쉐이더 기반이라고 생각 하고 만들어 봤는데요... 어떻신가요?

해당 쉐이더는 유니티 엔진에서도 똑같이 적용 되며 대부분의 GLSL 기반에서 차이 없이 적용이 가능 합니다.

한달여 동안 차곡 차곡 서브스턴스 디자이너를 알아 봤습니다.

인터페이스 부터 시작 해서 필터와 활용... 합성 활용. 그리고 커스텀 쉐이더를 제작해서 서브스턴스에 적용 하고 실제 작업 해 보는 실전까지 살펴 봤습니다.

저는 모바일 게임개발을 하고 있기 때문에 물리기반 쉐이더나 그런 것에 관심이 많지만 실제 작업에서는 어떻게 하면 가볍게.. 그럴사 하게 돌아 가게 해 볼까 라는 것에 더 생각이 맞춰져 있습니다.

여기에 서브스턴스 디자이너의 강력한 기능을 접목 해 보는 것이야 말로 중요 하다고 생각 합니다.

짬을 내서 전체 내용을 동영상으로 만들어 봐야겠네요.... 바쁜 일들이 어서 끝나기 만을....

4월에는 좀 더 확장 된 기능을 바탕으로 또 하나씩 살펴 나가 보겠습니다.

2014년 4월 4일. 회사에서 철야 하면서.... -_-;






Game Developer Leegoon copyright all right reserved since 2010.

Comments