[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
다음은 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 프로젝트을 분석하는 과정을 다루도록 하겠습니다.
'개발 > Shader' 카테고리의 다른 글
[Image Effect] 유니티 블러(Blur) 이미지 효과- 01 기본 셰이더 (0) | 2021.01.03 |
---|---|
[Snow Footprint] 04.프로젝트 분석 (完) (0) | 2017.12.25 |
[Snow Footprint] 02.Tessellator - 02 (Tessellator and Domain Stage) (0) | 2017.12.16 |
[Snow Footprint] 02.Tessellator - 01 (Hull-Shader) (0) | 2017.12.16 |
[Game Jams] [Snow Footprint] 01. Deformer (0) | 2017.12.09 |
소중한 공감 감사합니다