PISCOnoob

导航

Planar Mapping, Triplanar Mapping

写在前面:

本文章为个人学习笔记,方便以后自己复习,也希望能帮助到他人。

由于本人水平有限难免出现错误,还请评论区指出,多多指教。

部分图元和素材来源于网络,如有侵权请联系本人删除。

参考资料与链接会在文章末尾贴出。

=======================================================================

1.Planar Mapping

我们知道我们可以利用模型的uv采样贴图,但是有些时候我们没有或不想用模型自带的uv,我们可以自己生成一套uv。所谓的Planar Mapping就是利用世界空间中顶点的位置来做uv。

如下面的代码例子,我们用世界空间顶点坐标的zy轴作为uv,相当与我们把贴图在模型的侧面粘上去;如果是xz则是在顶部;xy则是在正面,可以自行脑补三维空间或画图理解。

       v2f vert (a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.pos);
                float4 worldPos = mul(unity_ObjectToWorld,v.pos);
                /*
                    actually we just project the texture on the top of the model(xz),or front(xy),side(zy)
                */
                o.uv = TRANSFORM_TEX(worldPos.zy, _MainTex);
                return o;
            }

            float4 frag (v2f i) : SV_Target
            {
                //float2 uv = normalize(i.uv);
                float4 finalCol = tex2D(_MainTex,i.uv);
                return finalCol;
            }

效果:

缺点就是除了该面之外,其他面会有拉伸和形变,因此应用最多是在一些不那么立体的模型上:

2.Triplanar Mapping

因为Planar Mapping有局限,因此我们引入Triplanar Mapping,简单来说,我们分别再xyz三个轴都做一次纹理采样后,再把结果“贴”到对应的轴上。

这次我们的v2f结构中需要三样东西:

       struct v2f
            {
                float4 pos : SV_POSITION;
                float3 normal : TEXCOORD0;             
                float3 posWS : TEXCOORD1;
            };

依旧用posWS作为uv采样贴图,而normal则用来做为权重。

在fragShader中,先计算三个方向的uv然后从三个方向采样贴图:

         // calculate UV coordinates for three projection
                float2 uv_front = TRANSFORM_TEX(i.posWS.xy,_MainTex);
                float2 uv_side = TRANSFORM_TEX(i.posWS.zy,_MainTex);
                float2 uv_top = TRANSFORM_TEX(i.posWS.xz,_MainTex);

                // sample 3 times using different uv for different direction(front side top)
                float4 tex_front = tex2D(_MainTex,uv_front);
                float4 tex_side = tex2D(_MainTex,uv_side);
                float4 tex_top = tex2D(_MainTex,uv_top);

先把结果加起来输出一下看看,记得结果要除3,不然会太亮:

尽管也会有拉伸,但看起来比Planar Mapping要好一点。

接下来继续改进,我们希望(比如正面)采样后的结果就只贴在正面,更准确地说采样结果对除正面外的地方影响小一点(最理想就是没影响)。因此我们用模型在世界空间中的法线作为权重,与对应结果相乘。注意:法线表示方向有正负,而作为权重我们可以取期绝对值。

          // now we use normal of model as weight for different direction
                float3 weight = i.normal;
                // make sure value of weight is positive
                weight = abs(weight);
                return float4(weight,1);

输出法线看看:

          tex_front *= weight.z;
                tex_side *= weight.x;
                tex_top *= weight.y;

                float4 finalCol = tex_front + tex_side + tex_top;
                return finalCol;

输出结果:

比想象中要亮,查看上述代码,其实就是没有像第一次输出一样除3,结果叠加超出了合理范围。但是我们这里不要简单把结果除3,我们可以调整权重。

ps:如果一开始难以理解某些某些数学公式的操作是正常的,个人建议可以试下代入几个特殊值尝试理解公式到底做了什么。比如一个(1,0.5,0)的向量调整前调整后计算结果有什么不同。

          // make it so the sum of all components is 1, or the output will be brighter than we expected
                weight = weight/(weight.x + weight.y + weight.z);
                // multiply col with its weight
                tex_front *= weight.z;
                tex_side *= weight.x;
                tex_top *= weight.y;

                float4 finalCol = tex_front + tex_side + tex_top;
                return finalCol;

现在看起来正常一点。

我们可以继续调整一下面与面(即不同轴,不同方向)的过渡效果,现在是比较模糊,自然?在某些情况下自然模糊过渡确实是我们想要的。但是这里我们可以设置一个参数控制过渡是模糊还是边界分明。开放一个叫sharpness的参数做幂运算控制过渡效果:

注意pow的位置!不然效果不一样

        //_Sharpness("Sharpness",Range(1,64)) = 1
             float4 frag (v2f i) : SV_Target
            {
                // calculate UV coordinates for three projection
                float2 uv_front = TRANSFORM_TEX(i.posWS.xy,_MainTex);
                float2 uv_side = TRANSFORM_TEX(i.posWS.zy,_MainTex);
                float2 uv_top = TRANSFORM_TEX(i.posWS.xz,_MainTex);

                // sample 3 times using different uv for different direction(front side top)
                float4 tex_front = tex2D(_MainTex,uv_front);
                float4 tex_side = tex2D(_MainTex,uv_side);
                float4 tex_top = tex2D(_MainTex,uv_top);
                // now we use normal of model as weight for different direction
                float3 weight = i.normal;
                // make sure value of weight is positive
                weight = abs(weight);
                //return float4(weight,1);
                // control the transition of boundaries smoother or sharper
                weight = pow(weight,_Sharpness);
                // make it so the sum of all components is 1, or the output will be brighter than we expected
                weight = weight/(weight.x + weight.y + weight.z);
                // multiply col with its weight
                tex_front *= weight.z;
                tex_side *= weight.x;
                tex_top *= weight.y;

                float4 finalCol = tex_front + tex_side + tex_top;
                return finalCol;
            }

输出weight看下:

最终结果:

可以看到边缘过渡很实。

我们甚至可以对于不同的面应用不同的纹理,比如top是草地,side和front是岩石。

 

参考资料:

1.Planar Mapping

2.Triplanar Mapping

3.Triplanar Mapping

posted on 2022-11-01 15:22  PISCOnoob  阅读(104)  评论(0编辑  收藏  举报