새소식

개발/Shader

[Snow Footprint] 03.Wave Deformer에 Tessellation 적용

  • -

기존 포스팅 01, 02번을 통해 Deformer와 Tessellator의 개념을 익혔습니다.

오늘은 그 과정에서 만들었던 Wave Deformer 예제를 Tessellator를 통해 

위와 같았던 결과물을

위와 같이 보다 완성도 있게 바꾸는 작업에 대해 포스팅해보겠습니다.

1. 소스 원문

먼저 소스 코드 전체를 보여드린 후 부분 별로 차근차근 설명을 덧 붙이겠습니다.

Shader "Custom/WaveDeformer"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_Amplitude ("Amplitude", Float) = 0
		_Frequency ("Frequency", Float) = 0
		_Speed ("Speed", Float) = 1
		_Anchor ("Anchor Position", Vector) =(0,0,0,0)
		_Cover ("Map Axis Size", Float) = 10
		_MinEdgeLength("Minimum Edge length", Range(0.01,1)) = 0.1
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100
		Pass
		{
			Name "DEFERRED"
			Tags {"LightMode" = "Deferred"}

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma hull hs
			#pragma domain ds
			#pragma geometry gs
			#pragma multi_compile_prepassfinal
			#pragma target 5.0
			
			// Physically based Standard lighting model, and enable shadows on all light types
			#include "HLSLSupport.cginc"
			#include "UnityCG.cginc"
			#include "UnityShaderVariables.cginc"
			#include "Tessellation.cginc"
			#include "Lighting.cginc"
			#include "UnityPBSLighting.cginc"
			#define MinM 0.03
			#define EPS  0.045 //define the EPS

			struct appdata
			{
				float4 vertex : POSITION;
				float4 tangent : TANGENT;
				float3 normal : NORMAL;
				float2 texcoord : TEXCOORD0;
				float2 texcoord1 : TEXCOORD1;
				float2 texcoord2 : TEXCOORD2;
			};

			struct PS_INPUT
			{
				float4 vertex : SV_POSITION;
				float3 worldPos : TEXCOORD0;
				half3 worldNormal : TEXCOORD1;
				float2 uv : TEXCOORD4;
			};
			struct PS_OUTPUT
			{
				half4 outDiffuse: SV_Target0;
				half4 outSpecular : SV_Target1;
				half4 outNormal : SV_Target2;
				half4 outEmission : SV_Target3;
			};

			struct HS_INPUT
			{
				float4 vertex : INTERNALTESSPOS;
				float4 tangent : TANGENT;
				float3 normal : NORMAL;
				float2 texcoord : TEXCOORD0;
				float3 worldPos : TEXCOORD3;
			};

			struct HS_PER_PATCH_OUTPUT
			{
				float edges[3] : SV_TessFactor;
				float inside : SV_InsideTessFactor;
			};

			struct DS_INPUT
			{
				float4 vertex : INTERNALTESSPOS;
				float4 tangent : TANGENT;
				float3 normal : NORMAL;
				float2 texcoord : TEXCOORD0;
				float3 worldPos : TEXCOORD3;
			};

			struct DS_OUTPUT
			{
				float4 vertex : POSITION;
				float3 worldPos: TEXCOORD0;
				half3 worldNormal : TEXCOORD1;
				float2 uv : TEXCOORD4;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			float4 _Anchor;
			float _Speed;
			float _Frequency;
			float _Amplitude;
			float _Cover;
			float _MinEdgeLength;

			////HELPER FUNCTIONS
			float4 WaveFunc (float4 pos, float3 relativePos)
			{
				float dist = sqrt(relativePos.x * relativePos.x + relativePos.z * relativePos.z);
				if (dist <= 0.0001)
					pos.y = pos.y + _Amplitude * sin (_Frequency * dist - _Time.y * _Speed) * _Cover * 2;
				else
					pos.y = pos.y + _Amplitude * sin (_Frequency * dist - _Time.y * _Speed) / dist;
				return pos;
			}

			HS_INPUT vert (appdata i)
			{	
				HS_INPUT o;
				o.vertex = i.vertex;
				o.tangent = i.tangent;
				o.normal = i.normal;
				o.texcoord = i.texcoord;
				o.worldPos = mul(unity_ObjectToWorld,i.vertex).xyz; // 테셀레이션 Stage에서 사용할 Vertex들은 Projection 되면안되기에 변환한 값만 따로 담는다.
				return o;
			}

			HS_PER_PATCH_OUTPUT hsconst (InputPatch v)
			{
				HS_PER_PATCH_OUTPUT o;
				float3 worldPos0 = mul(unity_ObjectToWorld, v[0].vertex).xyz;
				float3 worldPos1 = mul(unity_ObjectToWorld, v[1].vertex).xyz;
				float3 worldPos2 = mul(unity_ObjectToWorld, v[2].vertex).xyz;

				float factor0 = distance(worldPos1, worldPos2) / _MinEdgeLength;
				float factor1 = distance(worldPos2, worldPos0) / _MinEdgeLength;
				float factor2 = distance(worldPos0, worldPos1) / _MinEdgeLength;
				factor0 *= step (5, factor0);
				factor1 *= step (5, factor1);
				factor2 *= step (5, factor2);
				float factor = max(1.f, (factor0 + factor1 + factor2) / 3.f);

				o.edges[0] = factor;
				o.edges[1] = factor;
				o.edges[2] = factor;
				o.inside = (o.edges[0] + o.edges[1] + o.edges[2]) / 3;
				return o;
			}

			// tessellation hull shader
			[UNITY_domain("tri")]
			[UNITY_partitioning("integer")]
			[UNITY_outputtopology("triangle_cw")]
			[UNITY_patchconstantfunc("hsconst")]
			[UNITY_outputcontrolpoints(3)]
			DS_INPUT hs (InputPatch i,
						 uint pointID : SV_OutputControlPointID,
						 uint PatchID : SV_PrimitiveID)
			{
				DS_INPUT o;
				o.vertex = i[pointID].vertex;
				o.normal = i[pointID].normal;
				o.tangent = i[pointID].tangent;
				o.texcoord = i[pointID].texcoord;
				o.worldPos =i[pointID].worldPos;
				return o;
			}

			[domain("tri")]
			DS_OUTPUT ds (HS_PER_PATCH_OUTPUT i, 
						  const OutputPatch vi, 
						  float3 bary : SV_DomainLocation)
			{
				DS_OUTPUT o;
				// Get BS Values
				o.vertex = vi[0].vertex * bary.x + vi[1].vertex * bary.y + vi[2].vertex * bary.z;
				o.worldPos = vi[0].worldPos*bary.x + vi[1].worldPos*bary.y +vi[2].worldPos*bary.z;
				float3 normal = vi[0].normal*bary.x + vi[1].normal*bary.y + vi[2].normal*bary.z;
				float4 tangent = vi[0].tangent*bary.x + vi[1].tangent*bary.y + vi[2].tangent*bary.z;
				float3 binormal = cross(normal, tangent.xyz);
				o.uv = vi[0].texcoord*bary.x + vi[1].texcoord*bary.y + vi[2].texcoord*bary.z;
				// Set Default world values
				float3 wNormal = UnityObjectToWorldNormal(normal);
				float3 wTangent = UnityObjectToWorldDir(tangent.xyz);
				float tangentSign = tangent.w*unity_WorldTransformParams.w;
				float3 wBinormal = cross(wNormal, wTangent) * tangentSign;
				// Make virual vertice
				float4 pointAtBinormal_world = float4(o.worldPos + wBinormal*EPS,0);
				float4 pointAtTangent_world = float4(o.worldPos + wTangent*EPS,0);
				// Deform 3 Vertice (one real vertex, two virtual vertice)
				float3 relativePos;
				relativePos.x = (_Anchor.x + o.vertex.x) / _Cover - 0.5;
				relativePos.z = (_Anchor.z + o.vertex.z) / _Cover - 0.5;
				o.vertex = WaveFunc(o.vertex, relativePos);

				relativePos.x = (_Anchor.x + pointAtBinormal_world.x) / _Cover - 0.5;
				relativePos.z = (_Anchor.z + pointAtBinormal_world.z) / _Cover - 0.5;
				pointAtBinormal_world = WaveFunc(pointAtBinormal_world, relativePos);

				relativePos.x = (_Anchor.x + pointAtTangent_world.x) / _Cover - 0.5;
				relativePos.z = (_Anchor.z + pointAtTangent_world.z) / _Cover - 0.5;
				pointAtTangent_world = WaveFunc(pointAtTangent_world, relativePos);
				// Recalculate Normal
				float3 new_worldPos = mul(unity_ObjectToWorld, o.vertex).xyz;
				float3 new_wTangent = normalize(pointAtTangent_world - new_worldPos);
				float3 new_wBinormal = normalize(pointAtBinormal_world - new_worldPos);
				float3 new_wNormal = tangentSign*normalize (cross(new_wTangent, new_wBinormal));
				// Set Data
				o.worldNormal = new_wNormal;
				o.worldPos = new_worldPos;
				return o;
			}

			[maxvertexcount(3)]
			void gs(triangle DS_OUTPUT input[3], inout TriangleStream stream)
			{
				PS_INPUT o[3];
				for (int i = 0; i < 3; i++)
				{
					o[i].vertex = UnityObjectToClipPos(input[i].vertex);
					o[i].uv = input[i].uv;
					o[i].worldNormal = input[i].worldNormal;
					o[i].worldPos = input[i].worldPos;
					stream.Append(o[i]);
				}
			}

			PS_OUTPUT frag (PS_INPUT i)
			{
				PS_OUTPUT o;

				half3 worldNormal = i.worldNormal;
				o.outNormal = half4(worldNormal, 0);

				float3 worldPos = i.worldPos;

				#ifndef USING_DIRECTIONAL_LIGHT
				fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
				#else
				fixed3 lightDir = _WorldSpaceLightPos0.xyz;
				#endif

				fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));

				SurfaceOutputStandard s; // create a surfaceoutput
				//s.Albedo = worldNormal;
				s.Albedo = tex2D(_MainTex, i.uv);
				s.Normal = o.outNormal;
				s.Emission = 0.0;
				s.Alpha = 1;
				s.Metallic = 0.5;
				s.Smoothness = 0.5;
				s.Occlusion = 0;

				// Setup lighting environment
				half atten = 1;
				UnityGI gi;
				UNITY_INITIALIZE_OUTPUT (UnityGI, gi);
				gi.indirect.diffuse = 0;
				gi.indirect.specular = 0;
				gi.light.color = 0;
				gi.light.dir = lightDir;
				gi.light.ndotl = LambertTerm(worldNormal, gi.light.dir);

				UnityGIInput giInput;
				UNITY_INITIALIZE_OUTPUT(UnityGIInput, giInput);
				giInput.light = gi.light;
				giInput.worldPos = worldPos;
				giInput.worldViewDir = worldViewDir;
				giInput.atten = atten;

				giInput.lightmapUV = 0.0;
				giInput.ambient.rgb = 0.0;

				giInput.probeHDR[0] = unity_SpecCube0_HDR;
				giInput.probeHDR[1] = unity_SpecCube1_HDR;
				#if UNITY_SPECCUBE_BLENDING || UNITY_SPECCUBE_BOX_PROJECTION
				giInput.boxMin[0] = unity_SpecCube0_BoxMin;
				#endif
				#if UNITY_SPECCUBE_BOX_PROJECTION
				giInput.boxMax[0] = unity_SpecCube0_BoxMax;
				giInput.probePosition[0] = unity_SpecCube0_ProbePosition;
				giInput.boxMax[1] = unity_SpecCube1_BoxMax;
				giInput.boxMin[1] = unity_SpecCube1_BoxMin;
				giInput.probePosition[1] = unity_SpecCube1_ProbePosition;
				#endif

				LightingStandard_GI(s, giInput, gi);

				o.outEmission = LightingStandard_Deferred(s, worldViewDir, gi, o.outDiffuse, o.outSpecular, o.outNormal);
				#ifndef UNITY_HDR_ON
				o.outEmission.rgb = exp2(-o.outEmission.rgb);
				#endif
				UNITY_OPAQUE_ALPHA(o.outDiffuse.a);
				return o;
			}
			ENDCG
		}
	}
}

2. Properties


Properties
{
	_MainTex ("Texture", 2D) = "white" {}
	_Amplitude ("Amplitude", Float) = 0
	_Frequency ("Frequency", Float) = 0
	_Speed ("Speed", Float) = 1
	_Anchor ("Anchor Position", Vector) =(0,0,0,0)
	_Cover ("Map Axis Size", Float) = 10
	_MinEdgeLength("Minimum Edge length", Range(0.01,1)) = 0.1
}

기존  Deformer 예제에서와 거의 비슷하지만 _MinEdgeLength 프로퍼티가 추가됐습니다.
이 프로퍼티는 이후 hull const shader에서 Edge를 얼마나 나눌지를 결정하는데 사용 할 것 입니다.

3. Pass

해당 패스는 Deferred 랜더링을 사용 할 것 이며, 해당 래더링에서 Lighting 효과를 내기 위해선 다음과 같은 cginc 를 인클루드할 필요가 있습니다.

	Name "DEFERRED"
	Tags {"LightMode" = "Deferred"}
	// Physically based Standard lighting model, and enable shadows on all light types
	#include "HLSLSupport.cginc"
	#include "UnityCG.cginc"
	#include "UnityShaderVariables.cginc"
	#include "Tessellation.cginc"
	#include "Lighting.cginc"
	#include "UnityPBSLighting.cginc"
	#define EPS  0.045 //  근사수치 기법에 사용될 상수

또한 Tessellation을 위한 Stage에 해당하는 함수를 #pragma를 통해 다음과 같이 지정합니다.

	#pragma vertex vert
	#pragma fragment frag
	#pragma hull hs
	#pragma domain ds
	#pragma geometry gs
	#pragma multi_compile_prepassfinal
	#pragma target 5.0

geometry 는 tesselletion 작업에 필요한 Stage는 아니지만, 이후 Snow Footprint 분석을 할 때 필요하기에 미리 연습해보기 위해 사용했습니다.

이제 각 스테이지에서 인풋과 아웃풋에 사용되는 데이터 스트럭쳐를 정의해줍니다.

	struct appdata // Vertex Shader의 Input Parameter의 형식입니다.
	{
		float4 vertex : POSITION;
		float4 tangent : TANGENT; // CG에선 기본적으로 tangent가 sementic으로 지원됩니다.
		float3 normal : NORMAL;
		float2 texcoord : TEXCOORD0;
		float2 texcoord1 : TEXCOORD1;
		float2 texcoord2 : TEXCOORD2;
	};

	struct PS_INPUT // Pixel Shader (fragment shader)의 Input Parameter의 형식입니다.
	{
		float4 vertex : SV_POSITION;
		float3 worldPos : TEXCOORD0;
		half3 worldNormal : TEXCOORD1;
		float2 uv : TEXCOORD4;
	};
	struct PS_OUTPUT  // Pixel Shader의 return 형식입니다.
	{
		half4 outDiffuse: SV_Target0;  // Surface가 주변 광 등, 조명 색상 정보가 포함됩니다.
		half4 outSpecular : SV_Target1;// 프리즈널 효과에 의해 부곽되는 부분의 색상 정보가 포함됩니다.
		half4 outNormal : SV_Target2;  // Surface의 법선 백터를 담습니다.
		half4 outEmission : SV_Target3;// emission 정도를 포함합니다.
	};

	struct HS_INPUT  // Hull Main Shader의 Input Parameter 형식입니다.
	{
		float4 vertex : INTERNALTESSPOS;
		float4 tangent : TANGENT;
		float3 normal : NORMAL;
		float2 texcoord : TEXCOORD0;
		float3 worldPos : TEXCOORD3;
	};

	struct HS_PER_PATCH_OUTPUT // Hull Const Shader의 Input Parameter 형식입니다.
	{
		float edges[3] : SV_TessFactor; // triangle을 사용하기에 edge가 3개며 얼마나 분해할지가 담깁니다.
		float inside : SV_InsideTessFactor;
	};

	struct DS_INPUT  // Tessellator Stage의 결과가 담깁니다. 중심좌표로 부터의 가중치는 따로 들어옵니다.
	{
		float4 vertex : INTERNALTESSPOS; // Tessellator에 의해 생긴 정점의 Local 좌표
		float4 tangent : TANGENT;
		float3 normal : NORMAL;
		float2 texcoord : TEXCOORD0;
		float3 worldPos : TEXCOORD3;
	};

	struct DS_OUTPUT // Domain Shader의 return 형식이자 Geometry Shader의 paramter 입력 형식입니다.
	{
		float4 vertex : POSITION;
		float3 worldPos: TEXCOORD0;
		half3 worldNormal : TEXCOORD1;
		float2 uv : TEXCOORD4;
	};

기존 예제와 중복되는 부분은 생략하고 Vertex Shader 부터 살펴보겠습니다.

	HS_INPUT vert (appdata i)
	{	
		HS_INPUT o;
		o.vertex = i.vertex;
		o.tangent = i.tangent;
		o.normal = i.normal;
		o.texcoord = i.texcoord;
		o.worldPos = mul(unity_ObjectToWorld,i.vertex).xyz; // 테셀레이션 Stage에서 사용할 Vertex들은 Projection 되면안되기에 변환한 값만 따로 담는다.
		return o;
	}

기존 Tesselletor 포스팅에서 설명했듯, Vertex Shader는 프로젝션에 대한 역할을 수행하지 않고 hull shader에서 처리할 데이터를 넘겨주는 역할만하여 비교적 간단해졌습니다.
이제 이 vert의 return 값은 hull shader stage에서 병렬적으로 처리됩니다.
먼저 main을 살펴보겠습니다.

	// tessellation hull shader
	[UNITY_domain("tri")]
	[UNITY_partitioning("integer")]
	[UNITY_outputtopology("triangle_cw")]
	[UNITY_patchconstantfunc("hsconst")]
	[UNITY_outputcontrolpoints(3)]
	DS_INPUT hs (InputPatch<HS_INPUT, 3> i,
	  	uint pointID : SV_OutputControlPointID,
		uint PatchID : SV_PrimitiveID)
	{
		DS_INPUT o;
		o.vertex = i[pointID].vertex;
		o.normal = i[pointID].normal;
		o.tangent = i[pointID].tangent;
		o.texcoord = i[pointID].texcoord;
		o.worldPos =i[pointID].worldPos;
		return o;
	}

위에 기제된 keyword를 살펴보면 기존 포스팅에서 이야기했던 내용들이 포함되어있음을 알 수 있습니다.
또한 Hull Constant Shader를 "hsconst"라는 이름으로 정의한 것을 확인 할 수 있죠.

먼저 하나의 인풋 패치는 삼각형이 될 것 이며 이에 따라, 3개의 정점을 가져와 계산을 하게 됩니다.
이 각각의 정점 인덱스는 pointID를 통해 구별하게 됩니다.

 우리는 모든 평면을 골고루 Smoothing 할 것임으로 별다른 조치 없이 Domain Shader로 결과를 넘깁니다.

다음은 각 edge를 얼마나 분해할 것인지를 정하는 Hull Constant Shader 입니다.

	HS_PER_PATCH_OUTPUT hsconst (InputPatch<HS_INPUT, 3> v)
	{
		HS_PER_PATCH_OUTPUT o;
		float3 worldPos0 = mul(unity_ObjectToWorld, v[0].vertex).xyz;
		float3 worldPos1 = mul(unity_ObjectToWorld, v[1].vertex).xyz;
		float3 worldPos2 = mul(unity_ObjectToWorld, v[2].vertex).xyz;

		float factor0 = distance(worldPos1, worldPos2) / _MinEdgeLength;
		float factor1 = distance(worldPos2, worldPos0) / _MinEdgeLength;
		float factor2 = distance(worldPos0, worldPos1) / _MinEdgeLength;
		factor0 *= step (5, factor0); // step : 2번째 인자가 1번째 인자보다 크면 0을, 작으면 1을 리턴합니다.
		factor1 *= step (5, factor1);
		factor2 *= step (5, factor2);
		float factor = max(1.f, (factor0 + factor1 + factor2) / 3.f);

		o.edges[0] = factor;
		o.edges[1] = factor;
		o.edges[2] = factor;
		o.inside = (o.edges[0] + o.edges[1] + o.edges[2]) / 3;
		return o;
	}

병렬적으로 처리되기에, 똑같은 parameter인 v를 받으며, 각 정점간의 거리를 _MinEdgeLength만큼 나눠 Edge를 몇 도막 낼지를 결정합니다.
더불어, 저는 step을 이용해 5도막 이하인 factor는 나누지 않도록 하여 조금 더 성능을 끌어올리고자 step 함수를 사용하는 부분을 추가했습니다.

Tesselletor Stage 는 프로그래머가 조작 할 수 없으니 바로 Domain Shader로 넘어가게 됩니다.

	[domain("tri")]
	DS_OUTPUT ds (HS_PER_PATCH_OUTPUT i, 
				  const OutputPatch vi, 
				  float3 bary : SV_DomainLocation)
	{
		DS_OUTPUT o;
		// Get BS Values
		o.vertex = vi[0].vertex * bary.x + vi[1].vertex * bary.y + vi[2].vertex * bary.z;
		o.worldPos = vi[0].worldPos*bary.x + vi[1].worldPos*bary.y +vi[2].worldPos*bary.z;
		float3 normal = vi[0].normal*bary.x + vi[1].normal*bary.y + vi[2].normal*bary.z;
		float4 tangent = vi[0].tangent*bary.x + vi[1].tangent*bary.y + vi[2].tangent*bary.z;
		float3 binormal = cross(normal, tangent.xyz);
		o.uv = vi[0].texcoord*bary.x + vi[1].texcoord*bary.y + vi[2].texcoord*bary.z;
		// Set Default world values
		float3 wNormal = UnityObjectToWorldNormal(normal);
		float3 wTangent = UnityObjectToWorldDir(tangent.xyz);
		float tangentSign = tangent.w*unity_WorldTransformParams.w;
		float3 wBinormal = cross(wNormal, wTangent) * tangentSign;
		// Make virual vertice
		float4 pointAtBinormal_world = float4(o.worldPos + wBinormal*EPS,0);
		float4 pointAtTangent_world = float4(o.worldPos + wTangent*EPS,0);
		// Deform 3 Vertice (one real vertex, two virtual vertice)
		float3 relativePos;
		relativePos.x = (_Anchor.x + o.vertex.x) / _Cover - 0.5;
		relativePos.z = (_Anchor.z + o.vertex.z) / _Cover - 0.5;
		o.vertex = WaveFunc(o.vertex, relativePos);

		relativePos.x = (_Anchor.x + pointAtBinormal_world.x) / _Cover - 0.5;
		relativePos.z = (_Anchor.z + pointAtBinormal_world.z) / _Cover - 0.5;
		pointAtBinormal_world = WaveFunc(pointAtBinormal_world, relativePos);
		relativePos.x = (_Anchor.x + pointAtTangent_world.x) / _Cover - 0.5;
		relativePos.z = (_Anchor.z + pointAtTangent_world.z) / _Cover - 0.5;
		pointAtTangent_world = WaveFunc(pointAtTangent_world, relativePos);
		// Recalculate Normal
		float3 new_worldPos = mul(unity_ObjectToWorld, o.vertex).xyz;
		float3 new_wTangent = normalize(pointAtTangent_world - new_worldPos);
		float3 new_wBinormal = normalize(pointAtBinormal_world - new_worldPos);
		float3 new_wNormal = tangentSign*normalize (cross(new_wTangent, new_wBinormal));
		// Set Data
		o.worldNormal = new_wNormal;
		o.worldPos = new_worldPos;
		return o;
	}

위 Domain Shader 에선 대표적으로 다음과 같은 4가지 작업이 이루어집니다.

  • 중심좌표와 연관된 데이터 캐싱
  • 근사 수치 기법에 사용되는 정점들의 Deform
  • Normal의 재계산
  • world 좌표계로의 projection
순서대로 살펴보면, 저희는 domain shader의 parameter로 넘어오는 bary 값을 통해 중심좌표를 구할 수 있습니다.
주석 Get BS Values 밑 부분이 그 역할을 수행하고 있고요.
이렇게 구한 Object좌표계의 중심좌표, World 좌표계의 중심좌표, normal, tangent, binormal 값은 Deform 되야 합니다.
하지만 이전 포스팅 01에서 봤던 것과 같이 Wave Doformer는 선형 변환이 아닙니다. 따라서 저희는 근사 수치 기법을 사용할 필요가 있습니다.
그래서 저는 pointAtBinormal_world, pointAtTangent_world 이렇게 2개의 추가 변수를 만들었습니다. 
그 후 Helper Function으로 정의되어있는 WaveFunc를 사용해 Deform된 좌표를 통해 normal을 재계산하면 domain shader의 역할은 끝납니다.

※ Geometry Shader는 이 예제에선 아무런 역할을 하지 못 하기 때문에, 설명을 생략했습니다.

다음은 Pixel Shader(frament shader) 입니다.
기존 예제와 가장 큰 차이를 보이는 부분이며, 이 부분의 내용은 이번 이후에도 따로 포스팅하여 보충 공부를 해보도록 하겠습니다.


	PS_OUTPUT frag (PS_INPUT i)
	{
		PS_OUTPUT o;
		half3 worldNormal = i.worldNormal;
		o.outNormal = half4(worldNormal, 0);

		float3 worldPos = i.worldPos;

		#ifndef USING_DIRECTIONAL_LIGHT
		fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
		#else
		fixed3 lightDir = _WorldSpaceLightPos0.xyz;
		#endif

		fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));

		SurfaceOutputStandard s; // create a surfaceoutput
		s.Albedo = tex2D(_MainTex, i.uv);
		s.Normal = o.outNormal;
		s.Emission = 0.0;
		s.Alpha = 1;
		s.Metallic = 0.5;
		s.Smoothness = 0.5;
		s.Occlusion = 0;

		// Setup lighting environment
		half atten = 1;
		UnityGI gi;
		UNITY_INITIALIZE_OUTPUT (UnityGI, gi);
		gi.indirect.diffuse = 0;
		gi.indirect.specular = 0;
		gi.light.color = 0;
		gi.light.dir = lightDir;
		gi.light.ndotl = LambertTerm(worldNormal, gi.light.dir);

		UnityGIInput giInput;
		UNITY_INITIALIZE_OUTPUT(UnityGIInput, giInput);
		giInput.light = gi.light;
		giInput.worldPos = worldPos;
		giInput.worldViewDir = worldViewDir;
		giInput.atten = atten;

		giInput.lightmapUV = 0.0;
		giInput.ambient.rgb = 0.0;

		giInput.probeHDR[0] = unity_SpecCube0_HDR;
		giInput.probeHDR[1] = unity_SpecCube1_HDR;
		#if UNITY_SPECCUBE_BLENDING || UNITY_SPECCUBE_BOX_PROJECTION
		giInput.boxMin[0] = unity_SpecCube0_BoxMin;
		#endif
		#if UNITY_SPECCUBE_BOX_PROJECTION
		giInput.boxMax[0] = unity_SpecCube0_BoxMax;
		giInput.probePosition[0] = unity_SpecCube0_ProbePosition;
		giInput.boxMax[1] = unity_SpecCube1_BoxMax;
		giInput.boxMin[1] = unity_SpecCube1_BoxMin;
		giInput.probePosition[1] = unity_SpecCube1_ProbePosition;
		#endif

		LightingStandard_GI(s, giInput, gi);

		o.outEmission = LightingStandard_Deferred(s, worldViewDir, gi, o.outDiffuse, o.outSpecular, o.outNormal);
		#ifndef UNITY_HDR_ON
		o.outEmission.rgb = exp2(-o.outEmission.rgb);
		#endif
		UNITY_OPAQUE_ALPHA(o.outDiffuse.a);
		return o;
	}

위 소스에서 추가적으로 설명이 필요한 부분을 댓글에 적어주시거나, jsm1505104@gmail.com 으로 메일 보내주시면 이후 포스팅에 정리해놓도록 하겠습니다.


이후 포스팅에선 본 목표였던, 눈 바닥 위에 발자국 찍기! Snow Footprint 프로젝트을 분석하는 과정을 다루도록 하겠습니다.


Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.