만화케릭터를 강조하기 위하여 외곽 효과를 주는 것을 toon shading, cell shding 이라고 합니다.
이번에 해볼 예제는 외곽선 그리기(toon shading) 입니다.
외곽선을 그리는 방법에는 림라이트 방법을 사용해도 되지만, 2 pass 렌더링 사용하는 방법도 가능합니다.
2 pass란 동일한 픽셀에 2번 렌더링 한다는 의미 입니다.
렌더링 한다고 해서 반드시 컬러값을 업데이트 하는 것은 아닙니다.
프래그먼트 쉐이더에서 아무일도 하지 않으면 변화가 없어 보입니다.
그러면 왜 하느냐?
백버퍼에 특정 값을 기록하여 진짜 렌더링하는 패스에서 해당 값을 사용하기도 하며,
이번 예제처럼 덧그리기도 할수도 있습니다.
그럼 2 pass 렌더링을 해보러 가봅시다.
원리를 먼저 생각해보면요
사각형이 그려져 있는데, 사각형 보다 약간 큰 사각형이 바로 뒤에 있으면
앞에 있는 사각형이 뒤에 있는 사각형의 가장자리는 가리게 되고, 끝부분만 남게 될 것입니다.
아래 처럼 2 번 렌더링 합니다.
1) 모델을 약간만 크게 만들어서 뒤에 그립니다.
2) 모델을 원래 크기로 앞에 그립니다.
약간만 크게 만드는 방법은 어떻게 할까요 ?
버텍스 셰이더에서 정점을 노말방향 * coeff 값을 더해 주면 됩니다.
void vert(inout appdata_full v)
{
v.vertex.xyz = v.vertex.xyz + v.normal.xyz * 0.01;
}
뒤에 그리는 방법은?
컬링 방법을 사용합니다.
프론트 페이스 컬링 (cull front) 을 하게 되면 앞면이 없어지고, 백페이스 컬링(cull back) 을 하면 뒷면이 없어집니다.
보통 렌더링 할때 불투명한 오브젝트의 뒷면은 그리지 않기 때문에 디폴트로 백페이스 컬링이 설정되어 있습니다.
예를 들어 A 조금 큰 모델 (전부 검정), B 원래 크기 모델 (원래 색상) 이라고 가정해보면,
A - cull back, B - cull front :
--> A 렌더링 vertex의 영역이 넓기 때문에 검은색으로 B를 다 가려버립니다.
A - cull front, B - cull back :
--> A 검은색은 뒷면만 렌더링 되고, B 원래 크기 모델 색상은 앞면에 그려지면서 외곽 효과가 잘 그려집니다.
Shader "Custom/Toon"
{
Properties
{
......
}
SubShader
{
Tags { "RenderType"="Opaque" }
// 1nd pass
cull back
CGPROGRAM
..........
ENDCG
// 2nd pass
cull front
CGPROGRAM
.........
ENDCG
}
FallBack "Diffuse"
}
첫번째 렌더링은 fragment shader에서 검은색만 리턴하면 되고,
두번째 렌더링은 custom lighting을 적용시켜 봅니다.
디퓨즈 텀 음영을 자연스럽게 보간 하지 않고 영역별로 끊어지게 만들고 싶으면
n dot l 조명 값에 따라 범위를 지정하고 대표 음영 값을 넣어주면 됩니다.
float ndotl = dot(s.Normal, lightDir) * 0.5 + 0.5;
if (ndotl > 0.4)
{
ndotl = 1;
}
else if (ndotl > 0.2 && ndotl <= 0.4)
{
ndotl = 0.7;
}
else
{
ndotl = 0.5;
}
물론 if문을 쓰지 않고 다른 방법을 써도 됩니다.
3개의 대표 음영을 값을 얻어 내기도 합니다. ( 1 / 3, 2 / 3, 3 / 3 )
ndotl *= 3;
ndotl = ceil(ndotl) / 3;
전체 코드 입니다.
Shader "Custom/Toon"
{
Properties
{
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_BumpMap ("BumpMap", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
cull back
CGPROGRAM
#pragma surface surf Nolight Lambert vertex:vert noshadow noambient
#pragma target 3.0
void vert(inout appdata_full v)
{
v.vertex.xyz = v.vertex.xyz + v.normal.xyz * 0.01;
}
struct Input
{
float4 color:Color;
};
void surf (Input IN, inout SurfaceOutput o)
{
}
float4 LightingNolight(SurfaceOutput s, float3 lightDir, float atten)
{
return float4(0, 0, 0, 1);
}
ENDCG
cull front
// 2nd pass
CGPROGRAM
#pragma surface surf Toon
#pragma target 3.0
sampler2D _MainTex;
sampler2D _BumpMap;
struct Input
{
float2 uv_MainTex;
float2 uv_BumpMap;
};
void surf(Input IN, inout SurfaceOutput o)
{
fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
o.Albedo = c.rgb;
o.Alpha = c.a;
}
float4 LightingToon (SurfaceOutput s, float3 lightDir, float atten) {
float ndotl = dot(s.Normal, lightDir) * 0.5 + 0.5;
if (ndotl > 0.4)
{
ndotl = 1;
}
else
{
ndotl = 0.5;
}
float4 final;
final.rgb = s.Albedo * ndotl * _LightColor0.rgb;
final.a = s.Alpha;
return final;
}
ENDCG
}
FallBack "Diffuse"
}
'Unity - Surface Shader' 카테고리의 다른 글
5. 알파블렌딩과 컷아웃 (3) | 2023.11.20 |
---|---|
4. Cubemap을 사용하여 반사매질을 표현해보자. (0) | 2023.11.19 |
2. Blin - phong Specular + Rim Lighiting (0) | 2023.11.11 |
1. Hologram (0) | 2023.11.10 |
시작하면서... (0) | 2023.11.10 |