如何在生成的锥化网格上修复Z字形UV贴图伪像?


10

我正在基于曲线创建程序网格,如下图所示,整个曲线的网格尺寸都变小了。

在此处输入图片说明

问题是UV会随着大小的变化而呈锯齿状(当整个曲线的网格大小相同时,UV会完美地起作用)。

在此处输入图片说明

 Vertices.Add(R);
 Vertices.Add(L);
 UVs.Add(new Vector2(0f, (float)id / count));
 UVs.Add(new Vector2(1f, (float)id / count));
 start = Vertices.Count - 4;
          Triangles.Add(start + 0);
                 Triangles.Add(start + 2);
                 Triangles.Add(start + 1);
                 Triangles.Add(start + 1);
                 Triangles.Add(start + 2);
                 Triangles.Add(start + 3);

 mesh.vertices = Vertices.ToArray();
         //mesh.normals = normales;
         mesh.uv = UVs.ToArray();
         mesh.triangles = Triangles.ToArray();

我怎样才能解决这个问题?


什么是idcount?怎么UVs.ToArray()办?您如何将顶点和纹理坐标上传到卡?
user1118321

上下文中的@ user1118321 id是当前段在沿着该条的所有交叉开关中的索引,该交叉开关count总共位于其中。List<T>.ToArray()返回列表中所有条目的类型化数组(所以这里是a Vector2[])。通过在最后几行中将这些数据缓冲区分配给Mesh实例mesh,可以使这些数据缓冲区可用于渲染系统。但这不是代码中特别有趣的部分:如何选择顶点。这些细节将更有用。;)
DMGregory

那是我的假设,但我要求澄清一下,因为有时假设是错误的。过去,我曾经在自己的代码中查看过类似的内容,只是意识到它count实际上是指顶点而不是多边形,反之亦然。只要确保我们认为正在发生的一切都在进行。
user1118321

Answers:


13

典型的UV映射称为仿射变换。这意味着3D空间和纹理空间之间的每个三角形的映射都可以包括旋转,平移,缩放/挤压和倾斜(即,我们可以用齐次矩阵乘法完成的任何操作)

关于仿射变换的事情是,它们在整个域上都是均匀的 -我们在顶点A附近应用的纹理的旋转,平移,缩放和偏斜与我们在顶点B附近在任何一个三角形中应用的相同。在一个空间中平行的线将被映射到在另一个空间中的平行线,永远不会收敛/发散。

但是,您尝试应用的逐渐变细不是统一的-它会将纹理中的平行线映射到网格上的会聚线。这意味着,随着我们向下移动带材,整个带上测量到的纹理的比例会不断变化。这远远超过了2D UV贴图的仿射变换所能精确表示的:在相邻顶点之间插值2D uv坐标将在整个边缘上获得一个一致的比例,即使对角边缘在其沿条带向下移动时也应缩小比例。这种不匹配是造成此曲折曲折的原因。

每当我们想将矩形映射到梯形时,都会出现此问题-将平行边映射到会聚边:没有仿射变换可以做到这一点,因此我们必须逐段近似,以形成可见的接缝。

对于大多数目的,您可以通过添加更多几何图形来最小化效果。沿着条带的长度增加细分的数量,并沿着条带的宽度将条带分成两个或更多个段,并且三角形的对角线以人字形排列,可以使效果不那么明显。只要我们使用仿射变换,它就会一直存在。

但是有一种解决方法。在给定矩形墙壁和地板的情况下,我们可以使用与3D渲染相同的技巧在透视图中绘制梯形:我们使用投影坐标

仿射纹理: 使用锯齿形伪影进行正常仿射纹理化的示例

投影纹理: 投影textring校正工件的示例

为此,我们需要添加第三个uv坐标(uvw)并修改我们的着色器。

给定每个点的比例因子(例如,等于该点的条带宽度),您可以通过以下方式从常规2D uv坐标构造3D投影uvw坐标:

Vector3 uv3 = ((Vector3)uv2) * scale;
uv3.z = scale;

要将这些3D uvw坐标应用于您的网格,您将需要使用Vector3重载Mesh.SetUVs(int channel,List uvs)

并确保更改着色器的输入结构,以期望3D纹理坐标(此处显示为使用默认的未着色着色器):

struct appdata
{
    float4 vertex : POSITION;
    float3 uv : TEXCOORD0; // Change float2 to float 3.
};
// Also do this for the uv sent from the vertex shader to the fragment shader.

您还需要在顶点着色器中剪切出TRANSFORM_TEX宏,因为它需要2D uv:

// o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.uv = v.uv;
// If you define a float4 with the special name _MainTex_ST, 
// you can get the same effect the macro had by doing this:
o.uv.xy = o.uv.xy * _MainTex_ST.xy + _MainTex_ST.zw;

最后,要返回2D纹理坐标以进行纹理采样,只需在片段着色器中除以第三个坐标即可:

float2 uv = i.uv.xy / i.uv.z;

由于我们通过乘以相同的数字从所需的2D坐标中获得了该3D uvw坐标,因此这两个操作被抵消,我们返回到原始的所需2D坐标,但是现在在顶点之间进行了非线性插值。:D

重要的是按片段而不是在顶点着色器中进行此划分。如果每个顶点都完成了,那么我们将回到沿每个边沿线性地对所得坐标进行插值的过程,而我们已经失去了试图通过投影坐标引入的非线性!


嗨,我已经在同一个问题上苦苦挣扎了很长时间了,这似乎恰恰是我正在发生的事情。唯一的区别是,我在Threejs中工作,而不是团结一致。我们设法通过增加三角形的数量来解决该问题,但仍想尝试投影纹理。您有什么想法甚至开始在threejs中实现吗?谢谢!
Dživo耶利奇

1
几乎相同的方式。您需要第三个纹理坐标来存储除数,然后在片段着色器中执行除法。如果您在将此问题应用于案例时遇到问题,则提出一个新问题将使您包括一个代码示例,以显示到目前为止如何绘制网格,可以在此基础上得出答案。
DMGregory

我是否需要将除数存储为第三个坐标(并且由于这种更改结构并切出TRANSFORM_TEX宏,我不知道如何在Threejs中进行操作)还是可以将除数作为属性传递给顶点着色器?然后从那里变化到片段着色器,可以用来分割?
Dživo耶利奇

TRANSFORM_TEX是Unity宏。您在three.js中没有此内容,因此没有需要删节的内容。只要插值的uv坐标和除数到达片段着色器,如何将它们放置在那里并不重要。到目前为止,您是否遇到麻烦将其作为属性/变量提供?
DMGregory

我还没有尝试过,因为我不想浪费时间,直到我确认这是可能的。仅是最后一个问题,除数到达片段着色器后也会被插值,对吗?意味着在两个顶点之间的像素上将是一些插值,而不是原始值?我很难理解为什么要乘以UV再除以UV会有所帮助,但是我会相信您并尝试一下。:slightly_smiling_face:非常感谢您的帮助!
Dživo耶利奇
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.