不幸的是,在 three.js 中没有简单的方法可以做到这一点。
为了正确控制非平凡网格的纹理,您可能需要先对模型进行一些UV-unwrapping。这通常应该由提供网格的人来完成,因为在 blender、3dstudio、cinema4d 等工具中更容易做到这一点。
但是,我假设您由于某种原因没有那个人可用,并且需要在 javascript 中执行此操作。这种方式更有趣。让我们进一步假设,我们只想将图像映射到包的正面和背面,内部和边缘不会被纹理化。
在 three.js 和大多数其他 3D 应用程序中,纹理的精确映射是通过为每个面的每个顶点提供纹理坐标(也称为 UV 坐标)来实现的。这些 UV 坐标(范围从 0 到 1)指定纹理中属于顶点的位置。 (0, 0) 是图像的左下角,(1, 1) 是右上角,与图像的纵横比无关。
因此,对于由两个三角形组成的简单四边形,这将是这样的:
const geometry = new THREE.Geometry();
const vertices = [
// the four vertices ABCD
[0,0,0], [0,1,0], [1,0,0], [1,1,0]
].map(v => new THREE.Vector3(...v));
// setup two triangles ADB and ACD:
//
// B---D
// | / |
// A---C
const normal = new THREE.Vector3(0,0,1);
const faces = [
new THREE.Face3(0, 3, 1, normal),
new THREE.Face3(0, 2, 3, normal)
];
geometry.vertices = vertices;
geometry.faces = faces;
所以,这只是几何本身,但重要的是要知道它是如何为下一步构建的 - UV 坐标:
const uvs = geometry.faceVertexUvs[0];
const vertexUvs = [
[0,0], [0,1], [1,0], [1,1]
].map(p => new THREE.Vector2(...p));
// we need to set the UV-coordinates for both faces separately, using
// the same scheme as before:
uvs[0] = [vertexUvs[0], vertexUvs[3], vertexUvs[1]];
uvs[1] = [vertexUvs[0], vertexUvs[2], vertexUvs[3]];
基本上就是这样。这大致就是THREE.PlaneGeometry 所做的。关于这一点的更多观点:
- three.js 能够处理两组不同的 uv 坐标,这就是为什么会有
geometry.faceVertexUvs[0]。
- 该数组中的索引对应于 faces-array 中的索引,因此
uvs[0] 是第一个面,uvs[1] 是第二个。
- 其中每一个又是一个长度为 3 的数组,对应于该面的顶点。
现在,对于您的包,应该可以找出哪两个面是正面和背面。您需要找到它们并编辑它们的 UV 坐标以匹配您想要查看的图像中的位置(我不知道这些模型的外观如何,所以在这里我无法为您提供更多帮助)。所有其他 UV 坐标都可以设置为某个安全值,例如 (0, 0),然后所有不是正面或背面的东西都将具有纹理左下角像素的颜色。
如果您也想对它们进行纹理处理,您真的应该考虑在适当的 3D 编辑软件中制作纹理贴图。
编辑 (2017-01-15)
查看带有 angular-logo 的图像,左上角的三角形被翻转。这是因为three.js 使用的面顺序或顶点顺序与我在示例中使用的顺序不同。
如果您只是在浏览器控制台或调试器中使用它(这就是我刚刚所做的),那么理解这些东西会很有帮助。据此,来自three.js的平面几何具有以下结构:
// A---B
// | / |
// C---D
vertices = [A, B, C, D]
faces = [Face3(A, C, B), Face3(C, B, D)]
值得注意的是,无论出于何种原因,第一个面是逆时针的,第二个面是顺时针的(我认为这是一种不好的做法,三角形应该始终是逆时针的)。查看几何体的 faceVertexUVs,看看它在这种情况下应该是什么样子(只需在浏览器控制台中输入 new THREE.PlaneGeometry(1,1).faceVertexUvs[0])
现在来说另一点,缩放纹理。您可以尝试的一件事是对顶点使用大于 1 的 UV 值。这将导致纹理绘制得更小。但是,只有在
- 纹理环绕模式是钳制到边缘 (
texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping;)
- 所有纹理都有一个 1 像素宽的中性色边框
否则(这是我推荐的方式)您应该使用画布作为纹理并在实际使用之前准备图像。我这里做了一个简单的例子:http://codepen.io/usefulthink/pen/GrjmrM?editors=0010