Unity로 실습하는 Shader (5) - Flat Shading
플랫 셰이더(flat shader)란 것도 있었군요. ^^ 다양한 장면을 연출하다 보면 일부러 이런 효과를 주는 경우도 있을 것입니다.
Flat Shading
; https://catlikecoding.com/unity/tutorials/advanced-rendering/flat-and-wireframe-shading/
Unity flat shader?
; http://makegamessa.com/discussion/3788/unity-flat-shader
아래의 shader 코드는 빛에 따른 음영이 아닌, 상단과 하단의 색을 정해 주면 그에 따라 그레이디언트처럼 shading을 합니다.
Shader "My/flatShader"
{
Properties
{
_BaseCol("Base colour", Color) = (1,1,1,1)
_TopCol("Top colour", Color) = (1,1,1,1)
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float4 _TopCol, _BaseCol;
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 worldPos : TEXCOORD0;
};
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float3 x = ddx(i.worldPos);
float3 y = ddy(i.worldPos);
float3 norm = -normalize(cross(x, y));
float l = saturate(dot(norm, float3(0, 1, 0)));
fixed4 col = lerp(_BaseCol, _TopCol, l);
return col;
}
ENDCG
}
}
}
여기서, pixel shader에서만 사용할 수 있는
ddx와
ddy는 각각 World 좌표계를 기준으로 x, y 좌표에 대한 편미분 값(간단히 말해 기울기 값)을 반환합니다.
float3 x = ddx(i.worldPos);
float3 y = ddy(i.worldPos);
해당 기울기 값을 vector 취급해 외적을 한 후 정규화를 시키면 결국 x, y 기울기가 반영된 정규 벡터 값을 얻을 수 있습니다.
float3 norm = -normalize(cross(x, y));
그 값을 y 축에 대해 내적을 하면, 즉 y 축과의 코사인 각으로 값을 얻어내면 그 값이 곧 삼각형 폴리곤마다 갖게 되는 법선 벡터와 다를 바가 없습니다.
float l = saturate(dot(norm, float3(0, 1, 0)));
그리고 그 값을 기준으로 Model의 상/하위에 정해진 색상 구간으로 보간을 해주면,
fixed4 col = lerp(_BaseCol, _TopCol, l);
적당한 Flat 단위 별 색상이 입혀지게 되는 것입니다. 예를 들어, 상단을 흰색, 하단을 회색 계열로 하면 다음과 같이 렌더링됩니다.
텍스처를 입혀본 버전은, 상단 색상만 지정하도록 바꿨는데 역시 빛에 따른 음영을 넣지 않은 코드입니다.
Shader "My/flatTextureShader"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_TopCol("Top colour", Color) = (1,1,1,1)
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float4 _TopCol, _BaseCol;
sampler2D _MainTex;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 worldPos : TEXCOORD0;
float2 uv : TEXCOORD1;
};
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float3 x = ddx(i.worldPos);
float3 y = ddy(i.worldPos);
fixed4 textureColor = tex2D(_MainTex, i.uv);
float3 norm = -normalize(cross(x, y));
float l = saturate(dot(norm, float3(0, 1, 0)));
fixed4 col = lerp(textureColor, _TopCol, l);
return col;
}
ENDCG
}
}
}
상단만 흰색으로 지정하니 다음과 같이 렌더링되었습니다.
그 외에 다음의 글이 도움이 될 것입니다.
CG: Specify a variable not to be interpolated between vertex and fragment shader
; https://stackoverflow.com/questions/13876763/cg-specify-a-variable-not-to-be-interpolated-between-vertex-and-fragment-shader
참고로, 처음 소개했던 "
Flat Shading" 글에서는 ddx, ddy를 이용한 방법이 아닌, geometry shader를 이용한 것으로 바꿨다고 합니다. 역시나, 가능한 shader만으로 자유로운 표현을 하려면 geometry의 힘을 종종 빌려야만 할 것 같습니다. ^^
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]