效果图1:在Unity地形中使用,左边为三向贴图效果,右边为地形自带贴图效果。
效果图2:一般树干模型,右边为三向贴图效果,左边为一般贴图效果。
效果图3:球体,左边为三向贴图效果,右边为一般贴图效果,能看出UV边界。
在一般的贴图方法中,模型顶点uv值传入顶点着色器,进行插值后传入片段着色器,在片段着色器内使用tex2D(texture,uv)对2D材质进行采样即可。在World Space UV-mapping中,不使用uv值,而是使用当前像素的世界坐标的两个分量来进行贴图,例如tex2D(texture,worldPos_xy)。
由于tex2D会自动对第二个参数进行0-1取值范围的限制,因此一般面积大于1的2D平面会出现一种repeat的效果,可以将uv值除以一个变量进行缩放。Debug渲染出来测试一下
Shader "Unlit/TriPlanarDebug"
{
Properties
{
_TextureScale ("Texture Scale",float) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
float4 w_Vertex : FLOAT;
};
float _TextureScale;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.w_Vertex=mul(unity_ObjectToWorld,v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(fmod(abs(i.w_Vertex.x)/_TextureScale,1.0f),fmod(abs(i.w_Vertex.z)/_TextureScale,1.0f),0,0);
}
ENDCG
}
}
}
同样的plane,左边plane的左下角起始地址为世界坐标000,_TextureScale为1。左侧颜色rg分量为物体的世界坐标xz值,右边颜色rg分量为物体的uv值。
上图将左侧Shader的_TextureScale改为10,左右两边颜色的rg分量完全相同。
上图将左侧plane向上移动5个单位,rg分量发生了变化。
这种贴图方法的特点是物体在用于采样的坐标系分量平面中放大时贴图不会出现拉伸。当物体移动或旋转时,贴图会发生变化,根据新的世界坐标重新进行材质采样。在2D场景中,对一个静态不动的物体,调好参数,也可以达到和一般uv贴图相同的效果。
上图,平面以worldPos_xz进行tex2D(),物体缩小时贴图无拉伸。
以世界坐标贴图方法为基础,将像素的世界坐标(x,y,z)组合为三组UV:xy,xz,yz。用三组UV值分别对同一个材质进行采样, 然后以法线方形作为权重对采样的颜色进行混合。以法线当权重的意义是,当像素的法线面向y轴时,它会显示由xz采样的颜色,当面向x轴时,将会显示yz采样的颜色,在面向xy轴45度夹角时将会显示由xz,yz采样出的颜色的混合颜色。以此种方式对物体所有转角处进行混合过渡。如有不规则复杂形状物体,如地形,树木,同时贴图材质是壁纸地面这种类型,使用这种贴图方法可以简单的处理转角接口缝隙明显的问题,同样材质可用在不同形状的模型上而效果大概相同,也可使用在需要动态变形的物体上。
以下的代码还引入了一个_TriplanarBlendSharpness变量作为normal的指数,当_TriplanarBlendSharpness大于1并递增时,法线向量三个分量的差距会不断加大,对于贴图效果来讲既是更倾向于使用同一次采样着色,优点是颜色更接近于原图,缺点是有些地方会出现接缝处。当_TriplanarBlendSharpness等于1时,则是按角度对不同采样进行混合,优点是不会出现缝隙处,缺点是像素法线非直角处与原图颜色有误差。
_TriplanarBlendSharpness=1
代码:
vertex shader版本:
Shader "Unlit/Tri-Planar"
{
Properties
{
_DiffuseMap ("Main Map ", 2D) = "white" {} //不命名为_DiffuseMap无法在Unity地形中使用
_TextureScale ("Texture Scale",float) = 1
_TriplanarBlendSharpness ("Blend Sharpness",float) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal:NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
float3 normal:NORMAL;
float4 wVertex : FLOAT;
};
float _TextureScale;
sampler2D _DiffuseMap;
float4 _DiffuseMap_ST;
float _TriplanarBlendSharpness;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _DiffuseMap);
UNITY_TRANSFER_FOG(o,o.vertex);
o.normal=mul(unity_ObjectToWorld,v.normal);
o.wVertex=mul(unity_ObjectToWorld,v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
float3 bWeight=(pow(abs(normalize(i.normal)),_TriplanarBlendSharpness));
//example: sharpness=2: 0,0.25,1--->0,0.0625,1
bWeight=bWeight/(bWeight.x+bWeight.y+bWeight.z);
float4 xaxis_tex=tex2D(_DiffuseMap,i.wVertex.zy/_TextureScale);
float4 yaxis_tex=tex2D(_DiffuseMap,i.wVertex.xz/_TextureScale);
float4 zaxis_tex=tex2D(_DiffuseMap,i.wVertex.xy/_TextureScale);
fixed4 tex=xaxis_tex*bWeight.x+yaxis_tex*bWeight.y+zaxis_tex*bWeight.z;
// apply fog
UNITY_APPLY_FOG(i.fogCoord, tex);
return tex;
}
ENDCG
}
}
}
surface shader版本(网上转载):
Shader "TriplanarTutorial/Triplanar_Final"
{
Properties
{
_DiffuseMap ("Diffuse Map ", 2D) = "white" {}
_TextureScale ("Texture Scale",float) = 1
_TriplanarBlendSharpness ("Blend Sharpness",float) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma target 3.0
#pragma surface surf Lambert
sampler2D _DiffuseMap;
float _TextureScale;
float _TriplanarBlendSharpness;
struct Input
{
float3 worldPos;
float3 worldNormal;
};
void surf (Input IN, inout SurfaceOutput o)
{
// Find our UVs for each axis based on world position of the fragment.
half2 yUV = IN.worldPos.xz / _TextureScale;
half2 xUV = IN.worldPos.zy / _TextureScale;
half2 zUV = IN.worldPos.xy / _TextureScale;
// Now do texture samples from our diffuse map with each of the 3 UV set's we've just made.
half3 yDiff = tex2D (_DiffuseMap, yUV);
half3 xDiff = tex2D (_DiffuseMap, xUV);
half3 zDiff = tex2D (_DiffuseMap, zUV);
// Get the absolute value of the world normal.
// Put the blend weights to the power of BlendSharpness, the higher the value,
// the sharper the transition between the planar maps will be.
half3 blendWeights = pow (abs(IN.worldNormal), _TriplanarBlendSharpness);
// Divide our blend mask by the sum of it's components, this will make x+y+z=1
blendWeights = blendWeights / (blendWeights.x + blendWeights.y + blendWeights.z);
// Finally, blend together all three samples based on the blend mask.
o.Albedo = xDiff * blendWeights.x + yDiff * blendWeights.y + zDiff * blendWeights.z;
}
ENDCG
}
}
参考:
Use Tri-Planar Texture Mapping for Better Terrain --Brent Owens
https://gamedevelopment.tutsplus.com/articles/use-tri-planar-texture-mapping-for-better-terrain–gamedev-13821
Triplanar Mapping --MARTIN PALKO
http://www.martinpalko.com/triplanar-mapping/
———————————————————————————
维护日志:
2017-9-20:将“三平面式”改为“三向式”
2020-2-27:review