Triplanar 기본 구현
월드 포지션 2개 축을 조합해서 UV 좌표로 활용 할 수 있습니다.
아래는 예시입니다.
Varyings vert(Attributes input)
{
Varyings output = (Varyings)0;
float4 positionOS = input.positionOS;
float3 positionWS = TransformObjectToWorld(positionOS.xyz);
float4 positionCS = TransformWorldToHClip(positionWS);
...
float4 frag(Varyings input) : SV_Target
{
float3 positionWS = input.positionWS;
return float4(positionWS.xy + 0.5f, 0,0);
return float4(positionWS.zx + 0.5f, 0,0);
return float4(positionWS.zy + 0.5f, 0,0);
다음 월드 노말 벡터를 픽셀 쉐이더로 확인하면 아래와 같이 각 방향 별로 마스킹 가능합니다.
Varyings vert(Attributes input)
{
Varyings output = (Varyings)0;
float3 normalWS = TransformObjectToWorldNormal(input.normalOS);
...
float4 frag(Varyings input) : SV_Target
{
float3 normalWS = NormalizeNormalPerPixel(input.normalWS);
return float4(normalWS, 0);
월드 포지션 기반 UV와 월드 노말 각 축을 곱하면 아래와 같이 깔끔하게 UV 좌표 계산 가능합니다.
float4 frag(Varyings input) : SV_Target
{
float3 positionWS = input.positionWS;
float3 normalWS = NormalizeNormalPerPixel(input.normalWS);
return float4((positionWS.xy + 0.5f) * normalWS.z, 0,0);
return float4((positionWS.xz + 0.5f) * normalWS.y, 0,0);
return float4((positionWS.zy + 0.5f) * normalWS.x, 0,0);
위에서 계산한 UV 좌표로 각각 3개 샘플링에 사용하면 Triplanar 기본형 완성입니다.
float4 frag(Varyings input) : SV_Target
{
float3 positionWS = input.positionWS + 0.5f;
float3 normalWS = NormalizeNormalPerPixel(input.normalWS);
float4 texColor_X = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, positionWS.zy * _BaseMap_ST.xy + _BaseMap_ST.zw);
float4 texColor_Y = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, positionWS.zx * _BaseMap_ST.xy + _BaseMap_ST.zw);
float4 texColor_Z = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, positionWS.xy * _BaseMap_ST.xy + _BaseMap_ST.zw);
float4 triplanar = texColor_X * normalWS.x + texColor_Y * normalWS.y + texColor_Z * normalWS.z;
float4 finalColor = triplanar;
return finalColor;
}
하지만 아래와 같이 월드 노말 반대 편은 0 이하 값이 되기 때문에 월드 노말에 abs처리하여 음수를 양수로 변환합니다.
다음 아래 Shader Graph의 Triplanar 코드를 참고하여 함수 형태로 만들고 기능을 개선합니다.
https://docs.unity3d.com/Packages/com.unity.shadergraph@16.0/manual/Triplanar-Node.html
float4 TriplanarSample(float3 positionWS, float3 normalWS, float blend,
TEXTURE2D_PARAM(Texture_X, sampler_Texture_X), float4 tilingOffsetX,
TEXTURE2D_PARAM(Texture_Y, sampler_Texture_Y), float4 tilingOffsetY,
TEXTURE2D_PARAM(Texture_Z, sampler_Texture_Z), float4 tilingOffsetZ)
{
float3 Node_Blend = pow(abs(normalWS), blend);
Node_Blend /= dot(Node_Blend, 1.0);
float scrollSpeed = 0;
scrollSpeed = _Time.y;
float4 texColor_X = SAMPLE_TEXTURE2D(Texture_X, sampler_Texture_X, positionWS.zy * tilingOffsetX.xy + (tilingOffsetX.zw * scrollSpeed));
float4 texColor_Y = SAMPLE_TEXTURE2D(Texture_Y, sampler_Texture_Y, positionWS.zx * tilingOffsetY.xy + (tilingOffsetY.zw * scrollSpeed));
float4 texColor_Z = SAMPLE_TEXTURE2D(Texture_Z, sampler_Texture_Z, positionWS.xy * tilingOffsetZ.xy + (tilingOffsetZ.zw * scrollSpeed));
float4 triplanar = texColor_X * Node_Blend.x + texColor_Y * Node_Blend.y + texColor_Z * Node_Blend.z;
return triplanar;
}
float4 frag(Varyings input) : SV_Target
{
float3 positionWS = input.positionWS;
float3 normalWS = NormalizeNormalPerPixel(input.normalWS);
float4 triplanar = TriplanarSample(positionWS + 0.5f, normalWS, 1,
TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap), float4(_BaseMap_ST.xy, 0, 0),
TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap), float4(_BaseMap_ST.xy, 0, 0),
TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap), float4(_BaseMap_ST.xy, 0, 0));
float4 finalColor = triplanar;
return finalColor;
}
Triplanar 월드 포지션 고정하기
Triplanar 기본은 구현되었지만 오브젝트 좌표가 움직여도 월드 포지션 UV는 변하지 않기 때문에 위 결과물 예시와 같이 텍스처 UV가 움직이지 않는 현상이 있습니다.
보통 좌표가 이동하지 않는 배경 오브젝트에는 문제 없지만, 캐릭터와 같이 움직이는 오브젝트에는 문제가 발생할 수 있습니다.
그래서 이동행렬을 고정한 상태로 월드 포지션을 계산하여 Triplanar에 사용한다면 개선 가능합니다.
아래 예시는 이전 빌보드 구현 과정에서 Rotation Transform을 고정하는 부분이 있었는데, 해당 구현 부분을 응용합니다.
https://darkcatgame.tistory.com/137
void GetCustomModelMatrix(out float4x4 outMoveMatrix, out float4x4 outRotationMatrix, out float4x4 outScaleMatrix)
{
float4x4 scaleMatrix;
float4 sx = float4(UNITY_MATRIX_M._m00, UNITY_MATRIX_M._m10, UNITY_MATRIX_M._m20, 0);
float4 sy = float4(UNITY_MATRIX_M._m01, UNITY_MATRIX_M._m11, UNITY_MATRIX_M._m21, 0);
float4 sz = float4(UNITY_MATRIX_M._m02, UNITY_MATRIX_M._m12, UNITY_MATRIX_M._m22, 0);
float scaleX = length(sx);
float scaleY = length(sy);
float scaleZ = length(sz);
scaleMatrix[0] = float4(scaleX, 0, 0, 0);
scaleMatrix[1] = float4(0, scaleY, 0, 0);
scaleMatrix[2] = float4(0, 0, scaleZ, 0);
scaleMatrix[3] = float4(0, 0, 0, 1);
outScaleMatrix = scaleMatrix;
float4x4 rotationMatrix;
rotationMatrix[0] = float4(UNITY_MATRIX_M._m00 / scaleX, UNITY_MATRIX_M._m01 / scaleY, UNITY_MATRIX_M._m02 / scaleZ, 0);
rotationMatrix[1] = float4(UNITY_MATRIX_M._m10 / scaleX, UNITY_MATRIX_M._m11 / scaleY, UNITY_MATRIX_M._m12 / scaleZ, 0);
rotationMatrix[2] = float4(UNITY_MATRIX_M._m20 / scaleX, UNITY_MATRIX_M._m21 / scaleY, UNITY_MATRIX_M._m22 / scaleZ, 0);
rotationMatrix[3] = float4(0, 0, 0, 1);
outRotationMatrix = rotationMatrix;
float4x4 moveMatrix;
moveMatrix[0] = float4(1, 0, 0, UNITY_MATRIX_M._m03);
moveMatrix[1] = float4(0, 1, 0, UNITY_MATRIX_M._m13);
moveMatrix[2] = float4(0, 0, 1, UNITY_MATRIX_M._m23);
moveMatrix[3] = float4(0, 0, 0, UNITY_MATRIX_M._m33);
outMoveMatrix = moveMatrix;
}
float3 TransformObjectToWorld_StaticPosition(float3 positionOS)
{
float4x4 scaleMatrix;
float4x4 rotationMatrix;
float4x4 moveMatrix;
GetCustomModelMatrix(moveMatrix, rotationMatrix, scaleMatrix);
float4x4 modelMatrix_Static = mul(mul(1, rotationMatrix), scaleMatrix);
return mul(modelMatrix_Static, float4(positionOS.xyz, 1)).xyz;
}
Varyings vert(Attributes input)
{
Varyings output = (Varyings)0;
float4 positionOS = input.positionOS;
float3 positionWS = TransformObjectToWorld(positionOS.xyz);
float4 positionCS = TransformWorldToHClip(positionWS);
float3 positionWS_Static = TransformObjectToWorld_StaticPosition(positionOS.xyz);
float3 normalWS = TransformObjectToWorldNormal(input.normalOS);
output.normalWS = normalWS;
output.positionCS = positionCS;
output.uv = input.uv;
output.positionWS_Static = positionWS_Static.xyz;
return output;
}
float4x4 modelMatrix_Static = mul(mul(1, rotationMatrix), scaleMatrix);
빌보드 자료에는 rotationMatrix를 고정했지만 이번엔 이동행렬을 고정하기 때문에 위와 같이 코드를 작성합니다.
Shader "CatDarkGame/Triplanar"
{
Properties
{
[MainTexture] _BaseMap("Texture", 2D) = "white" {}
[MainColor] _BaseColor("Color", Color) = (1, 1, 1, 1)
}
SubShader
{
Tags { "RenderType" = "Opaque" "Queue"="Geometry" "RenderPipeline" = "UniversalPipeline" }
LOD 100
Pass
{
Name "URPUnlit"
Tags {"LightMode" = "SRPDefaultUnlit"}
HLSLPROGRAM
#pragma target 4.5
#pragma exclude_renderers gles d3d9
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST;
float4 _BaseColor;
CBUFFER_END
TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float3 positionWS_Static : TEXCOORD1;
float3 normalWS : TEXCOORD2;
};
void GetCustomModelMatrix(out float4x4 outMoveMatrix, out float4x4 outRotationMatrix, out float4x4 outScaleMatrix)
{
float4x4 scaleMatrix;
float4 sx = float4(UNITY_MATRIX_M._m00, UNITY_MATRIX_M._m10, UNITY_MATRIX_M._m20, 0);
float4 sy = float4(UNITY_MATRIX_M._m01, UNITY_MATRIX_M._m11, UNITY_MATRIX_M._m21, 0);
float4 sz = float4(UNITY_MATRIX_M._m02, UNITY_MATRIX_M._m12, UNITY_MATRIX_M._m22, 0);
float scaleX = length(sx);
float scaleY = length(sy);
float scaleZ = length(sz);
scaleMatrix[0] = float4(scaleX, 0, 0, 0);
scaleMatrix[1] = float4(0, scaleY, 0, 0);
scaleMatrix[2] = float4(0, 0, scaleZ, 0);
scaleMatrix[3] = float4(0, 0, 0, 1);
outScaleMatrix = scaleMatrix;
float4x4 rotationMatrix;
rotationMatrix[0] = float4(UNITY_MATRIX_M._m00 / scaleX, UNITY_MATRIX_M._m01 / scaleY, UNITY_MATRIX_M._m02 / scaleZ, 0);
rotationMatrix[1] = float4(UNITY_MATRIX_M._m10 / scaleX, UNITY_MATRIX_M._m11 / scaleY, UNITY_MATRIX_M._m12 / scaleZ, 0);
rotationMatrix[2] = float4(UNITY_MATRIX_M._m20 / scaleX, UNITY_MATRIX_M._m21 / scaleY, UNITY_MATRIX_M._m22 / scaleZ, 0);
rotationMatrix[3] = float4(0, 0, 0, 1);
outRotationMatrix = rotationMatrix;
float4x4 moveMatrix;
moveMatrix[0] = float4(1, 0, 0, UNITY_MATRIX_M._m03);
moveMatrix[1] = float4(0, 1, 0, UNITY_MATRIX_M._m13);
moveMatrix[2] = float4(0, 0, 1, UNITY_MATRIX_M._m23);
moveMatrix[3] = float4(0, 0, 0, UNITY_MATRIX_M._m33);
outMoveMatrix = moveMatrix;
}
float3 TransformObjectToWorld_StaticPosition(float3 positionOS)
{
float4x4 scaleMatrix;
float4x4 rotationMatrix;
float4x4 moveMatrix;
GetCustomModelMatrix(moveMatrix, rotationMatrix, scaleMatrix);
float4x4 modelMatrix_Static = mul(mul(1, rotationMatrix), scaleMatrix);
return mul(modelMatrix_Static, float4(positionOS.xyz, 1)).xyz;
}
float4 TriplanarSample(float3 positionWS, float3 normalWS, float blend,
TEXTURE2D_PARAM(Texture_X, sampler_Texture_X), float4 tilingOffsetX,
TEXTURE2D_PARAM(Texture_Y, sampler_Texture_Y), float4 tilingOffsetY,
TEXTURE2D_PARAM(Texture_Z, sampler_Texture_Z), float4 tilingOffsetZ)
{
float3 Node_Blend = pow(abs(normalWS), blend);
Node_Blend /= dot(Node_Blend, 1.0);
float scrollSpeed = 0;
scrollSpeed = _Time.y;
float4 texColor_X = SAMPLE_TEXTURE2D(Texture_X, sampler_Texture_X, positionWS.zy * tilingOffsetX.xy + (tilingOffsetX.zw * scrollSpeed));
float4 texColor_Y = SAMPLE_TEXTURE2D(Texture_Y, sampler_Texture_Y, positionWS.zx * tilingOffsetY.xy + (tilingOffsetY.zw * scrollSpeed));
float4 texColor_Z = SAMPLE_TEXTURE2D(Texture_Z, sampler_Texture_Z, positionWS.xy * tilingOffsetZ.xy + (tilingOffsetZ.zw * scrollSpeed));
float4 triplanar = texColor_X * Node_Blend.x + texColor_Y * Node_Blend.y + texColor_Z * Node_Blend.z;
return triplanar;
}
Varyings vert(Attributes input)
{
Varyings output = (Varyings)0;
float4 positionOS = input.positionOS;
float3 positionWS = TransformObjectToWorld(positionOS.xyz);
float4 positionCS = TransformWorldToHClip(positionWS);
float3 positionWS_Static = TransformObjectToWorld_StaticPosition(positionOS.xyz);
float3 normalWS = TransformObjectToWorldNormal(input.normalOS);
output.normalWS = normalWS;
output.positionCS = positionCS;
output.uv = input.uv;
output.positionWS_Static = positionWS_Static.xyz;
return output;
}
float4 frag(Varyings input) : SV_Target
{
float3 positionWS = input.positionWS_Static;
float3 normalWS = NormalizeNormalPerPixel(input.normalWS);
float4 triplanar = TriplanarSample(positionWS + 0.5f, normalWS, 1,
TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap), float4(_BaseMap_ST.xy, 0, 0),
TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap), float4(_BaseMap_ST.xy, 0, 0),
TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap), float4(_BaseMap_ST.xy, 0, 0));
float4 finalColor = triplanar;
return finalColor;
}
ENDHLSL
}
}
}
worldPivot계산하는 방식
Shader "CatDarkGame/Triplanar"
{
Properties
{
[MainTexture] _BaseMap("Texture", 2D) = "white" {}
[MainColor] _BaseColor("Color", Color) = (1, 1, 1, 1)
}
SubShader
{
Tags { "RenderType" = "Opaque" "Queue"="Geometry" "RenderPipeline" = "UniversalPipeline" }
LOD 100
Pass
{
Name "URPUnlit"
Tags {"LightMode" = "SRPDefaultUnlit"}
HLSLPROGRAM
#pragma target 4.5
#pragma exclude_renderers gles d3d9
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST;
float4 _BaseColor;
CBUFFER_END
TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float3 positionWS_Static : TEXCOORD1;
float3 normalWS : TEXCOORD2;
};
float4 TriplanarSample(float3 positionWS, float3 normalWS, float blend,
TEXTURE2D_PARAM(Texture_X, sampler_Texture_X), float4 tilingOffsetX,
TEXTURE2D_PARAM(Texture_Y, sampler_Texture_Y), float4 tilingOffsetY,
TEXTURE2D_PARAM(Texture_Z, sampler_Texture_Z), float4 tilingOffsetZ)
{
float3 Node_Blend = pow(abs(normalWS), blend);
Node_Blend /= dot(Node_Blend, 1.0);
float scrollSpeed = 0;
scrollSpeed = _Time.y;
float4 texColor_X = SAMPLE_TEXTURE2D(Texture_X, sampler_Texture_X, positionWS.zy * tilingOffsetX.xy + (tilingOffsetX.zw * scrollSpeed));
float4 texColor_Y = SAMPLE_TEXTURE2D(Texture_Y, sampler_Texture_Y, positionWS.zx * tilingOffsetY.xy + (tilingOffsetY.zw * scrollSpeed));
float4 texColor_Z = SAMPLE_TEXTURE2D(Texture_Z, sampler_Texture_Z, positionWS.xy * tilingOffsetZ.xy + (tilingOffsetZ.zw * scrollSpeed));
float4 triplanar = texColor_X * Node_Blend.x + texColor_Y * Node_Blend.y + texColor_Z * Node_Blend.z;
return triplanar;
}
Varyings vert(Attributes input)
{
Varyings output = (Varyings)0;
float4 positionOS = input.positionOS;
float3 positionWS = TransformObjectToWorld(positionOS.xyz);
float4 positionCS = TransformWorldToHClip(positionWS);
float3 pivotWS = float3(unity_ObjectToWorld._m03, unity_ObjectToWorld._m13, unity_ObjectToWorld._m23);
float3 normalWS = TransformObjectToWorldNormal(input.normalOS);
output.normalWS = normalWS;
output.positionCS = positionCS;
output.uv = input.uv;
output.positionWS_Static = positionWS.xyz - pivotWS;
return output;
}
float4 frag(Varyings input) : SV_Target
{
float3 positionWS = input.positionWS_Static;
float3 normalWS = NormalizeNormalPerPixel(input.normalWS);
float4 triplanar = TriplanarSample(positionWS + 0.5f, normalWS, 1,
TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap), float4(_BaseMap_ST.xy, 0, 0),
TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap), float4(_BaseMap_ST.xy, 0, 0),
TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap), float4(_BaseMap_ST.xy, 0, 0));
float4 finalColor = triplanar;
return finalColor;
}
ENDHLSL
}
}
}
'Unity > Shader & VFX' 카테고리의 다른 글
URP ScreenSpaceDecal Shader (0) | 2023.07.18 |
---|---|
LinearEyeDepth와 Linear01Depth (0) | 2023.07.13 |
[URP] HLSL SoftParticles Shader (0) | 2023.03.07 |
자동 UV타일링 쉐이더 (1) | 2022.11.11 |
유니티 빌보드 쉐이더 구현 & 분석 (1) | 2022.11.04 |
WRITTEN BY
- CatDarkGame
Technical Artist dhwlgn12@gmail.com