비주얼적으로 단색만을 사용하는 게임을 만들고 있다.
내가 원하는 것은
* 포인트 라이트를 사용해 그림자가 원형으로 퍼져 나간다.
* 빛을 받는 곳은 단색, 받지 않는 곳과 그림자 지는 부분은 검은색으로 나와야 한다.
* 빛의 강도에 따라 색은 원색에서 검은색으로 바뀌어야 한다.
이 조건을 해결하면서 여러 시행착오를 겪었는데, 어떤 과정이 있었는지 써보고자 한다.
_유니티의 기본 쉐이더를 사용
처음 시도한 방법은 포인트 라이트의 intensity를 올려 음영이 지는 부분을 없애고, 화면이 어두워지는 효과를 카메라 앞에 반투명한 상자를 씌우는 식으로 만드는 것이었다. 머티리얼은 기본 제공되는 것에서 metalic을 1로 smooth를 0으로 설정했다.
얼추 작동하는 것 같지만 몇가지 문제가 있다. intensity를 크게 올리면 물체의 색이 다른색으로 바뀐다거나 면이 빛을 비스듬하게 비출 경우 하이라이트가 생겨 하얗게 바뀐다. 그 이외에도 림라이트 같은 유니티의 기능을 제어할 방법이 없었다.
intensity = 1 |
intensity = 100 |
intensity = 1000 |
몇번의 파라미터 조절 후, 파라미터를 설정하는 것으로 이를 해결할 수 없음을 깨닫고 쉐이더를 사용하기로 했다.
_쉐이더를 사용
Shader "Custom/CustomLighting1"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf MyLight noambient
#pragma target 3.0
float4 LightingMyLight(SurfaceOutput s, float3 lightDir, float atten)
{
// 여기에 코드를 작성하자
}
sampler2D _MainTex;
struct Input
{
float2 uv_MainTex;
};
fixed4 _Color;
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_INSTANCING_BUFFER_END(Props)
void surf (Input IN, inout SurfaceOutput o)
{
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
기본으로 제공되는 쉐이더에 필요한 부분을 지우고 넣었다.
먼저 물체에 빛이 비추는 곳은 단색으로 그렇지 못한 곳은 검은색으로 만들고 싶다. 그러기 위해 물체의 노말벡터인 s.Normal과 빛의 방향인 LightDir을 내적한다. 두 벡터를 내적해서 양수가 나오면 둘이 같은 쪽으로 가리키고 0이면 수직, 음수면 반대 방향을 가리킨다. 만약 이를 그대로 적용시키면 기본 쉐이더처럼 부드러운 음영이 나온다. 이를 평평하게 하기 위해 step함수를 사용한다. step(a, value)는 value가 a이상이면 1을 a미만이면 0을 반환한다. 마지막으로 빛의 색과 강도를 반영하기 위해 _LightColor0.rgb를 곱해준다.
float4 LightingMyLight(SurfaceOutput s, float3 lightDir, float atten)
{
float dotv = dot(s.Normal, lightDir);
dotv = step(0, dotv); // 색을 평평하게 한다.
float4 color;
color.rgb = s.Albedo * dotv * _LightColor0.rgb;
color.a = 1.0f;
return color;
}
step함수 적용 전 |
step함수 적용 후 |
빛의 intensity로 어둡게도 만들 수 있다. 하지만 그림자가 생성되지 않는다. 유니티에서 계산해주는 그림자를 쓰기 위해 #pragma surface 뒤에 fullforwardshadows를 넣고, 색에 atten을 곱하자. atten은 빛의 거리, 차폐등을 계산해 실제로 빛이 얼마나 들어오는지를 계산한 값이다. 예를 들어 그림자 지는 부분은 atten이 0으로 전달된다.
#pragma surface surf MyLight noambient fullforwardshadows
float4 LightingMyLight(SurfaceOutput s, float3 lightDir, float atten)
{
float dotv = dot(s.Normal, lightDir);
dotv = step(0, dotv);
float4 color;
color.rgb = s.Albedo * dotv * atten * _LightColor0.rgb;
color.a = 1.0f;
return color;
}
_그림자를 고치기
얼추 마무리가 된거 같지만 마음에 들지 않는 부분이 있다.
흰색 물체 위에 흰색 물체를 둘 경우 색이 겹쳐 보이지 않는 것을 의도했지만, 그림자가 물체 밖으로 튀어나오면서 윤곽선이 생겼다. 이는 유니티가 그림자를 안티 에일리어싱해서 그림자가 번지기 때문인데, 그림자 설정을 Hard Shadow로 바꿔도 해결되지 않는다.
Soft Shadow |
Hard Shadow |
완전히 각진 그림자를 만드려면 어떻게 해야할까? 앞에서 봤듯 그림자에 대한 정보는 atten에 담겨온다. 그러니 저 사이의 부드러운 그림자는 atten이 0과 1 사이의 값이 전달된 것이다. atten에 step함수를 적용시켜 사이에 해당하는 값을 0이나 1로 만들면 정말로 딱딱한 그림자를 만들 수 있다.
float4 LightingMyLight(SurfaceOutput s, float3 lightDir, float atten)
{
float dotv = dot(s.Normal, lightDir);
dotv = step(0, dotv);
atten = step(0.001, atten); // 조금이라도 밝은 부분을 1로 만든다.
float4 color;
color.rgb = s.Albedo * dotv * atten * _LightColor0.rgb;
color.a = 1.0f;
return color;
}
이렇게 간단한 쉐이더를 만들어 보았다. 쉐이더는 기존의 C#과 다른 부분이여서 이질적으로 느껴질 수 있지만, 쉐이더를 사용하면 유니티 기본 기능이 협조하지 않는 여러가지 아이디어를 만들어 낼 수 있다. 만약 표현의 한계를 느낀다면 이를 공부하고 적용시켜보자.
'유니티와 놀기' 카테고리의 다른 글
delegate 사용하기 (0) | 2023.04.04 |
---|---|
코루틴 뒤에 코루틴을 실행시키기 (0) | 2022.11.10 |
체스말을 잡기 (0) | 2022.11.04 |
체스말을 움직이기 (0) | 2022.11.01 |