Triplanar 笔记
这个笔记主要记录自 CatlikeCoding-Triplanar 上面的文章很长而且有很多跟 Triplanar 无关的内容,其实 triplanar 和核心内容就是很简单的几行代码,所以看我这个笔记就可以了。
小学生都能看懂的视频版,求三连
什么是 Triplanar?为什么要用 Triplanar?
一般来说我们想要映射纹理到我们的模型上,可以用根据每个顶点的 uv 坐标来映射纹理。但这不是唯一的方法,而 triplanar 就是另一种映射贴图的方式。有时候,uv 坐标映射的方式可能无法达到我们想要的效果(比如造成贴图拉伸)甚至根本没有 uv 坐标,这些情况大多在用代码生成模型时,动态改变模型时发生。Triplanar 的原理就是用顶点位置作为 uv 坐标,用法线作为权重,来实现贴图的映射,因为映射已 xyz 三个平面分别映射,所以叫做 tri-planar。
《A Short Hike》中的 triplanar 应用

先放源码
Shader "Custom/Triplanar" {
Properties {
_TopTex("TopTexture", 2D) = "white" {}
_SideTex("SideTexture", 2D) = "white" {}
_BlendOffset("BlendOffset",Range(0,0.5)) = 0.25
_BlendExponent ("Blend Exponent", Range(1, 8)) = 2
}
SubShader {
Tags { "RenderType"="Opaque" }
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityStandardBRDF.cginc"
#include "Assets/Plugins/GumouKit/cgincs/gfunc.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal:NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 normal:TEXCOORD1;
float4 worldpos : TEXCOORD2;
};
fixed _BlendOffset;
half _BlendExponent;
sampler2D _TopTex;
sampler2D _SideTex;
float4 _TopTex_ST,_SideTex_ST;
struct TriUV{
float2 xUV,yUV,zUV;
};
TriUV GetTriUV (float4 worldpos) {
TriUV triUV;
triUV.xUV = worldpos.zy;
triUV.yUV = worldpos.xz;
triUV.zUV = worldpos.xy;
return triUV;
}
fixed3 GetTriWeights(fixed3 normal){
fixed3 weights = abs(normal); //因为 normal 可能有负数
weights = saturate(weights-_BlendOffset); //控制权重
weights = pow(weights,_BlendExponent); //进一步控制权重
return weights/(weights.x+ weights.y+ weights.z); //使 xyz 相加=1
}
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.normal = UnityObjectToWorldNormal(v.normal);
o.worldpos = mul(unity_ObjectToWorld,v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//triplanar
TriUV triuv = GetTriUV(i.worldpos);
fixed4 colx = tex2D(_SideTex,triuv.xUV * _SideTex_ST.xy + _SideTex_ST.zw);
fixed4 coly = tex2D(_TopTex,triuv.yUV * _TopTex_ST.xy + _TopTex_ST.zw);
fixed4 colz = tex2D(_SideTex,triuv.zUV * _SideTex_ST.xy + _SideTex_ST.zw);
fixed3 weights = GetTriWeights(i.normal);
fixed4 tricol = colx*weights.x + coly*weights.y +colz*weights.z;
return tricol;
}
ENDCG
}
}
}用顶点位置作为 uv 坐标
先来映射一个面
fixed4 frag (v2f i) : SV_Target
{
fixed4 colx = tex2D(_SideTex,i.worldpos.zy);
fixed4 coly = tex2D(_TopTex,i.worldpos.xz);
fixed4 colz = tex2D(_SideTex,i.worldpos.xy);
//return colx;
//return colx;
return colx;
}


可以写成一个函数方便使用
struct TriUV{
float2 xUV,yUV,zUV;
};
TriUV GetTriUV (float4 worldpos) {
TriUV triUV;
triUV.xUV = worldpos.zy;
triUV.yUV = worldpos.xz;
triUV.zUV = worldpos.xy;
return triUV;
}
fixed4 frag (v2f i) : SV_Target
{
TriUV triuv = GetTriUV(i.worldpos);
fixed4 colx = tex2D(_SideTex,triuv.xUV);
fixed4 coly = tex2D(_TopTex,triuv.yUV);
fixed4 colz = tex2D(_SideTex,triuv.zUV);
}合在一起的效果
fixed4 frag (v2f i) : SV_Target
{
TriUV triuv = GetTriUV(i.worldpos);
fixed4 colx = tex2D(_SideTex,triuv.xUV);
fixed4 coly = tex2D(_TopTex,triuv.yUV);
fixed4 colz = tex2D(_SideTex,triuv.zUV);
return (colx+coly+colz)/3;
}
用法线作为权重
fixed3 GetTriWeights(fixed3 normal){
fixed3 weights = abs(normal); //因为 normal 可能有负数
return weights/(weights.x+ weights.y+ weights.z); //使 xyz 相加=1
}
......
fixed4 frag (v2f i) : SV_Target
{
.....
fixed3 weights = GetTriWeights(i.normal);
fixed4 tricol = colx*weights.x + coly*weights.y +colz*weights.z;
return tricol;
}
控制权重
Properties {
.....
_BlendOffset("BlendOffset",Range(0,0.5)) = 0.25
}
.....
fixed _BlendOffset;
....
fixed3 GetTriWeights(fixed3 normal){
fixed3 weights = abs(normal); //因为 normal 可能有负数
weights = saturate(weights-_BlendOffset); //控制权重
return weights/(weights.x+ weights.y+ weights.z); //使 xyz 相加=1
}
....
fixed4 frag (v2f i) : SV_Target
{
.....
fixed3 weights = GetTriWeights(i.normal);
fixed4 tricol = colx*weights.x + coly*weights.y +colz*weights.z;
return tricol;
}

进一步控制权重
Properties {
.....
_BlendOffset("BlendOffset",Range(0,0.5)) = 0.25
_BlendExponent ("Blend Exponent", Range(1, 8)) = 2
}
.....
fixed _BlendOffset;
half _BlendExponent;
....
fixed3 GetTriWeights(fixed3 normal){
fixed3 weights = abs(normal); //因为 normal 可能有负数
weights = saturate(weights-_BlendOffset); //控制权重
weights = pow(weights,_BlendExponent); //进一步控制权重
return weights/(weights.x+ weights.y+ weights.z); //使 xyz 相加=1
}
....
fixed4 frag (v2f i) : SV_Target
{
.....
fixed3 weights = GetTriWeights(i.normal);
fixed4 tricol = colx*weights.x + coly*weights.y +colz*weights.z;
return tricol;
}

接下来我们可以试试映射多张贴图,我们 y 轴用 _TopTex 草地贴图,x 和 z 轴用 _MainTex 石头贴图。
Properties {
.....
_MainTex ("Texture", 2D) = "white" {}
_TopTex("",2D) = "white"{}
}
...
fixed4 frag (v2f i) : SV_Target
{
fixed4 colx = tex2D(_MainTex,i.worldpos.zy);
fixed4 coly = tex2D(_TopTex,i.worldpos.xz);
fixed4 colz = tex2D(_MainTex,i.worldpos.xy);
}
最后我们再实现一下纹理的缩放和位移
float4 _MainTex_ST,_SideTex_ST;
.....
fixed4 frag (v2f i) : SV_Target
{
fixed4 colx = tex2D(_MainTex,i.worldpos.zy * _MainTex_ST.xy + _MainTex_ST.zw);
fixed4 coly = tex2D(_TopTex,i.worldpos.xz * _SideTex_ST.xy + _SideTex_ST.zw);
fixed4 colz = tex2D(_MainTex,i.worldpos.xy * _MainTex_ST.xy + _MainTex_ST.zw);
...
}把缩放设置为 5 看一下效果

结束。


暂无关于此文章的评论。