最近项目需要在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;
经过以上的代码我们就可以得到正确的贴图了。
在线demo