Threejs ShapeGeometry自定義形狀貼圖
最近專案需要在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;
經過以上的程式碼我們就可以得到正確的貼圖了。
<html>
<head>
<title>Three.js Geometry Texture</title>
<style>
body {
margin: 0;
}
var controls = new THREE.OrbitControls(camera, renderer.domElement);
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
;
var angle = Math.atan2(v.y, v.x);
wall.rotateY(angle);
wall.castShadow = true;
scene.add(wall);
}
}
if (area.rows && area.rows.length > 0) {
getColRowMaterial(area, geometry, mesh);
}
camera.lookAt(new THREE.Vector3(0, 0, 0));
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
function assignUVs(geometry) {
geometry.computeBoundingBox();
geometry.faceVertexUvs[0] = [];
for (var i = 0; i < faces.length; i++) {