关于Three.js中ExtrudeGeometry使用图片材质时遇到的问题及解决办法
问题发现
在使用ExtrudeGeometry并添加图片材质的时候发现,图片只存在很小的一块。
而在Three的其他几何中,如BoxGeometry中,图片会完美的拉伸到整个面上。
BoxGeometry中的图片材质效果
ExtrudeGeometry中的效果,红圈处为整个图片,其他全是一个像素点的延申
探究问题
研习Three和Webgl文档,发现图片的拉伸效果是依靠的UV坐标来判断的,ExtrudeGeometry存在一个UVGenerator参数,BoxGeometry并没有,所以ExtrudeGeometry需要自己设置UV坐标转换函数?
查看了UV坐标的相关资料,大致意思是一张图建立一个坐标系(0,0)到(1,1)的大小,每一个坐标代表一个颜色,所以UV值应该为0-1
然后翻看three的源码,ExtrudeGeometry和BoxGeometry类进行对比,两者都对UV进行了运算然后传入到着色器中。直接看也看不出啥名堂,输出看看。
输出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)];
}
}
}
成功!
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)
]
}
}