最近项目需要在3D场景中给自定义的楼层区域进行贴图区分,对于普通的的纯色材质,实现比较简单,但是如果要进行纹理贴图的材质,就有点复杂了,这里写篇文章记录下。
首先看看我们的楼层定义,如何实现自定义区域。其实很简单,我们使用有序的点来定义楼层的平面形状,然后根据平面的定义,自动生成3d的平面区域。
var areaPts = []; for (var idx = 0 ; idx < area.points.length; idx++) { var p = area.points[idx]; var v = new THREE.Vector2(p.px , p.py ); areaPts.push(v); } var areaShape = new THREE.Shape(areaPts); var geometry = new THREE.ShapeGeometry(areaShape);
如果是纯色的贴图我们怎么做,很简单直接设置颜色即可
var material = new THREE.MeshBasicMaterial({ color: color, side: THREE.DoubleSide, transparent: true, opacity: opacity }); var mesh = new THREE.Mesh(geometry, material);
对于贴图,我们使用同样的方法,看看会的到什么效果呢
var texture = new THREE.CanvasTexture(canvas); var material = new THREE.MeshPhongMaterial({ map: texture, side: THREE.DoubleSide });
这里使用canvas作为贴图生成材质,运行后,非常不幸,你不会看到正常的贴图。这是为什么呢?原来我们的模型是根据一个shape生成的ShapeGeometry,所以贴图会采用UV坐标进行贴图,关于UV的解释可以看看这篇文章。所以我们需要计算模型的uv坐标供材质贴图使用。
function assignUVs(geometry) { geometry.computeBoundingBox(); var max = geometry.boundingBox.max, min = geometry.boundingBox.min; var offset = new THREE.Vector2(0 - min.x, 0 - min.y); var range = new THREE.Vector2(max.x - min.x, max.y - min.y); var faces = geometry.faces; geometry.faceVertexUvs[0] = []; for (var i = 0; i < faces.length ; i++) { var v1 = geometry.vertices[faces[i].a], v2 = geometry.vertices[faces[i].b], v3 = geometry.vertices[faces[i].c]; geometry.faceVertexUvs[0].push([ new THREE.Vector2((v1.x + offset.x) / range.x, (v1.y + offset.y) / range.y), new THREE.Vector2((v2.x + offset.x) / range.x, (v2.y + offset.y) / range.y), new THREE.Vector2((v3.x + offset.x) / range.x, (v3.y + offset.y) / range.y) ]); } geometry.uvsNeedUpdate = true; }
算好geomotry的uv坐标后,我们就可以放心大胆的进行贴图了。
function getColRowMaterial(mesh) { var geometry = mesh.geometry; assignUVs(geometry); var area = mesh.userData.area; geometry.computeBoundingBox(); var canvas = getColRowCanvas(area.rows, area.cols, geometry.boundingBox.size().x, geometry.boundingBox.size().y); var texture = new THREE.CanvasTexture(canvas); texture.wrapT = THREE.RepeatWrapping; texture.repeat.y = -1; var material = new THREE.MeshPhongMaterial({ map: texture, side: THREE.DoubleSide }); mesh.material = material; }
需要注意的是我们的图片坐标和uv坐标的Y轴是反的图片Y轴是向下的,UV坐标Y轴是向上的。所以我们需要反向下y。
texture.wrapT = THREE.RepeatWrapping; texture.repeat.y = -1;
经过以上的代码我们就可以得到正确的贴图了。