关于Three.js中ExtrudeGeometry使用图片材质时遇到的问题及解决办法

849 阅读2分钟

关于Three.js中ExtrudeGeometry使用图片材质时遇到的问题及解决办法

问题发现

在使用ExtrudeGeometry并添加图片材质的时候发现,图片只存在很小的一块。
而在Three的其他几何中,如BoxGeometry中,图片会完美的拉伸到整个面上。

BoxGeometry中的图片材质效果
image.png

ExtrudeGeometry中的效果,红圈处为整个图片,其他全是一个像素点的延申
image.png

探究问题

研习Three和Webgl文档,发现图片的拉伸效果是依靠的UV坐标来判断的,ExtrudeGeometry存在一个UVGenerator参数,BoxGeometry并没有,所以ExtrudeGeometry需要自己设置UV坐标转换函数?

查看了UV坐标的相关资料,大致意思是一张图建立一个坐标系(0,0)到(1,1)的大小,每一个坐标代表一个颜色,所以UV值应该为0-1

然后翻看three的源码,ExtrudeGeometry和BoxGeometry类进行对比,两者都对UV进行了运算然后传入到着色器中。直接看也看不出啥名堂,输出看看。

image.png

输出UV数组之后发现,ExtrudeGeometry的UV坐标数组完全就是three的坐标系,而BoxGeometry是正确的UV坐标。

然后在细看源码,发现ExtrudeGeometry在不提供UVGenerator的时候,使用的世界坐标UV转换,就是把传入的顶点坐标变成了二维矩阵而已。

解决问题

先尝试转换成0和1是否能解决问题,直接在Three源码中修改,数次尝试之后发现,把uv数组遍历小于等于0的全置0,大于0的全置1,然后就得到了想要的效果。

接下去就是使用UVGenerator参数去生成想要的结果了。
然后three文档上关于UVGenerator就这么一句话
UVGenerator — Object. object that provides UV generator functions
而实际上看源码才能发现还需要generateSideWallUV和generateTopUV两个属性。真好呀。

直接照抄WorldUVGenerator,把每个数组的值做一次0,1判断,然后生成0,1的二维矩阵。

UVGenerator: {
    generateTopUV: (geometry, vertices, indexA, indexB, indexC) => {
        const a_x = vertices[indexA * 3] <= 0 ? 0 : 1;
        const a_y = vertices[indexA * 3 + 1] <= 0 ? 0 : 1;
        const b_x = vertices[indexB * 3] <= 0 ? 0 : 1;
        const b_y = vertices[indexB * 3 + 1] <= 0 ? 0 : 1;
        const c_x = vertices[indexC * 3] <= 0 ? 0 : 1;
        const c_y = vertices[indexC * 3 + 1] <= 0 ? 0 : 1;
        return [new Vector2(a_x, a_y), new Vector2(b_x, b_y), new Vector2(c_x, c_y)];
    },
    generateSideWallUV: (geometry, vertices, indexA, indexB, indexC, indexD) => {
        const a_x = vertices[indexA * 3]  <= 0 ? 0 : 1;
        const a_y = vertices[indexA * 3 + 1] <= 0 ? 0 : 1;
        const a_z = vertices[indexA * 3 + 2] <= 0 ? 0 : 1;
        const b_x = vertices[indexB * 3] <= 0 ? 0 : 1;
        const b_y = vertices[indexB * 3 + 1] <= 0 ? 0 : 1;
        const b_z = vertices[indexB * 3 + 2] <= 0 ? 0 : 1;
        const c_x = vertices[indexC * 3] <= 0 ? 0 : 1;
        const c_y = vertices[indexC * 3 + 1] <= 0 ? 0 : 1;
        const c_z = vertices[indexC * 3 + 2] <= 0 ? 0 : 1;
        const d_x = vertices[indexD * 3] <= 0 ? 0 : 1;
        const d_y = vertices[indexD * 3 + 1] <= 0 ? 0 : 1;
        const d_z = vertices[indexD * 3 + 2] <= 0 ? 0 : 1;

        if (Math.abs(a_y - b_y) < Math.abs(a_x - b_x)) {
            return [new Vector2(a_x, 1 - a_z), new Vector2(b_x, 1 - b_z), new Vector2(c_x, 1 - c_z), new Vector2(d_x, 1 - d_z)];
        } else {
            return [new Vector2(a_y, 1 - a_z), new Vector2(b_y, 1 - b_z), new Vector2(c_y, 1 - c_z), new Vector2(d_y, 1 - d_z)];
        }
    }
}

成功!

image.png

PS

UV生成器的写法具体情况具体使用吧。具体意思是generateTopUV返回的是顶面和底面的每个三角形的三个顶点对应的UV,generateSideWallUV返回的是每个侧面的四个顶点对应的UV,所以最终要的效果是每个面对应上(0,0)(0,1)(1,1)(1,0)四个UV坐标,可以参考我实际应用中写的generateSideWallUV

generateSideWallUV: (geometry, vertices, indexA, indexB, indexC, indexD) => {
    const a_x = vertices[indexA * 3]
    const a_y = vertices[indexA * 3 + 1]
    const a_z = vertices[indexA * 3 + 2]
    const b_x = vertices[indexB * 3]
    const b_y = vertices[indexB * 3 + 1]
    const b_z = vertices[indexB * 3 + 2]
    const c_x = vertices[indexC * 3]
    const c_y = vertices[indexC * 3 + 1]
    const c_z = vertices[indexC * 3 + 2]
    const d_x = vertices[indexD * 3]
    const d_y = vertices[indexD * 3 + 1]
    const d_z = vertices[indexD * 3 + 2]

    if (Math.abs(a_y - b_y) > Math.abs(a_x - b_x)) {
      return [
        new Vector2(b_x > c_x ? 0 : 1, a_z > b_z ? 1 : 0),
        new Vector2(a_x > d_x ? 1 : 0, c_z > d_z ? 1 : 0),
        new Vector2(b_x > c_x ? 1 : 0, c_z > d_z ? 0 : 1),
        new Vector2(a_x > d_x ? 0 : 1, a_z > b_z ? 0 : 1)
      ]
    } else {
      return [
        new Vector2(b_y > c_y ? 0 : 1, a_z > b_z ? 1 : 0),
        new Vector2(a_y > d_y ? 1 : 0, c_z > d_z ? 1 : 0),
        new Vector2(b_y > c_y ? 1 : 0, c_z > d_z ? 0 : 1),
        new Vector2(a_y > d_y ? 0 : 1, a_z > b_z ? 0 : 1)
      ]
    }
}