https://www.martinpalko.com/triplanar-mapping/
Triplanar Mapping,即三维映射,我们会沿X、Y、Z轴使用一个平面贴图映射一张纹理三次,然后使用与最少拉伸最匹配的三个采样点,基于面朝的角度在这三个采样中混合。理论上,我们不会用有一张拉伸的纹理或硬接缝,我们也不不需要在网格上进行UV映射。
为了实现三维映射,我们首先需要生成三个平面UV集,为此,我们使用片段的世界空间位置。如果我们使用片段的XZ世界空间坐标作为UV坐标来采样,这样就会得到一个从Y轴投影的平面贴图,同理还可以得到使用ZY坐标的X投影,XY坐标的Z投影。
我们还需要一个混合权重来决定使用哪一个UV集。我们根据世界空间顶点的法线来进行。我们可以使用每个轴的绝对值,举个例子,这样如果表面法线指向正向或负向Y轴,我们可以从Y轴投影平面来混合更多的纹理采样。
三维映射的一个缺点在于,因为法线映射依赖于由UV表示的网格的切线,并且由于我们使用世界空间坐标来表示我们的UV,这样网格的切线就会摇摆不定。我们仍可以使用法线贴图来进行光照,只是不正确。
同样,三维映射与传统的UV映射相比,没那么有效率。决定UV坐标和混合权重的计算并不那么耗能,但执行三次就不一样了。当我们只是采样漫反射纹理时这还能接受,但如果我们同样映射高光贴图、细节贴图等,我们就不得不去关注使用的采样点的数量了。
Unity中的实现:
Shader "Unlit/Triplanar Mapping"
{
Properties
{
_AlbedoMap ("Albedo Map", 2D) = "white" {}
_TextureScale("Texture Scale", float) = 1
_TriplanarBlendSharpness("Blend Sharpness", float)=1
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct vertexInput
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct vertexOutput
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 worldPos : TEXCOORD1;
float3 worldNormal : TEXCOORD2;
};
sampler2D _AlbedoMap;
float4 _AlbedoMap_ST;
float _TextureScale;
float _TriplanarBlendSharpness;
vertexOutput vert (vertexInput i)
{
vertexOutput o;
o.vertex = UnityObjectToClipPos(i.vertex);
o.uv = TRANSFORM_TEX(i.uv, _AlbedoMap);
o.worldPos = mul(unity_ObjectToWorld, i.vertex).xyz;
o.worldNormal = UnityObjectToWorldNormal(i.normal);
return o;
}
fixed4 frag (vertexOutput i) : SV_Target
{
// find our UVs for each axis based on world position of the fragment
half2 yUV = i.worldPos.xz / _TextureScale;
half2 xUV = i.worldPos.zy / _TextureScale;
half2 zUV = i.worldPos.xy / _TextureScale;
// do texture samples from our albedo mao with each of the 3 UV set's we've just made.
half3 yDiff = tex2D(_AlbedoMap, yUV);
half3 xDiff = tex2D(_AlbedoMap, xUV);
half3 zDiff = tex2D(_AlbedoMap, zUV);
// get the absolute value of the world normal.
// put the blend weights to the power of BlendSharpness, the higher the value,
// the sharpness the transition between the planar maps will be.
half3 blendWeights = pow(abs(i.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.
fixed4 col = fixed4(xDiff * blendWeights.x + yDiff * blendWeights.y + zDiff * blendWeights.z, 1.0);
return col;
}
ENDCG
}
}
}
为对比效果,我创建了一个普通光照模型相同的shader,然后赋予到相同的模型上。
立方体:
看不出来区别,我们转换为球体:
可以明显看出三维映射时球面的混合情况,提升混合权重可以减弱这种混合不当的情况,但还是会看出三面。
圆柱:
可以发现三维映射的效果更好,排列的更规则,普通的纹理映射会有拉伸感(沿圆柱的纵轴)。
平面:
差别显而易见,普通的纹理映射如果想要达到同样的效果,需要调高拼贴参数。
接下来我们看看应用法线纹理会怎么样。这里就不贴出本人的代码了,因为用三维映射去实现法线贴图效果非常不好。在采样切线空间的法线时,我们需要使用三个面的UV分别去采样,由于涉及一个混合操作,同时混合系数与法线相关,因此会极其的不方便,得到的法线混合效果往往看上去是错误的,很难得到我们想要的效果,因此非常不推荐使用三维映射去采样法线贴图。
总结一下就是,可以使用三维映射去采样漫发射贴图,效果不错,可以避免接缝问题,不过非常不推荐使用三维映射去采样法线贴图进行光照计算,这里最好使用平常的平面映射去采样法线来进行计算。
网友评论