web3D使用three.js+CSG.js做的墙体添加窗户和门功能
因为之前一直做的是后台,最近接触了下3D,所以用前端three.js做一些项目的工作,代码比较简陋,主要是个人对three.js的一些使用
先展示3D效果模型
本文没有用ThreeBSP.js进行处理,因为我看BSP有版本要求,就没使用,我用的csg.js。
一.使用过程中遇到的问题汇总
1.使用csg对模型进行组合,合并后的材质处理,组合的相对位置处理。
2.添加门和门轴,让门围绕门轴旋转问题。
3.透明窗户的处理
二
1.csg的功能描述 见链接 Constructive Solid Geometry - Three.js Tutorials (sbcode.net)
描述
Union
返回由 A 和 B 组成的新 CSG 实体
A.union(B)
+-------+ +-------+
| | | |
| A | | |
| +--+----+ = | +----+
+----+--+ | +----+ |
| B | | |
| | | |
+-------+ +-------+
Subtract
返回一个新的 CSG 实体,其中 B 从 A 中减去
A.subtract(B)
+-------+ +-------+
| | | |
| A | | |
| +--+----+ = | +--+
+----+--+ | +----+
| B |
| |
+-------+
Intersect
返回 A 和 B 重叠的新 CSG 实体
A.intersect(B)
+-------+
| |
| A |
| +--+----+ = +--+
+----+--+ | +--+
| B |
| |
+-------+
csg的三个基本功能如上所示,我主要说一下开发过程中遇到的问题
第一个问题就是组合中各个模型的坐标问题
一开始我发现组合后相对位置终是居中的,就是没法移动一个模型相对于另外一个模型的位置。导致门和窗户一直在墙体正中间。
用mesh1.position.set设置坐标也不好使。
解决办法:
这样就设置的模型相对墙的位置了。
第二个问题就是设置组合的材质,我目前是把开孔的墙作为一个的模型,门和门轴是一个模型,窗户是一个模型。
如果把门和窗户都组合到墙模型的话不容易改变材质,不方便代码的改动,如果你要做多面墙的话也不方便。可以把多面墙组合到一起。
下图的代码就是给组合完成的对象添加一个纹理,然后设置组合对象的坐标
2.墙体填充门和门轴,让门围绕着门轴旋转 链接ThreeJs入门41-物体旋转的方法和技巧2 - 掘金 (juejin.cn)
旋转默认是按照模型中心旋转的,如果仅对门模型进行rotateY旋转的话,就会出现一半门在里面。一半在外面的情况。我们可以把门和门轴组合起来,然后在让门和门轴旋转就会实现门根据门轴旋转了
材质常见属性
材质属性 | 简介 |
---|---|
color | 材质颜色,比如蓝色0x0000ff |
wireframe | 将几何图形渲染为线框。 默认值为false |
opacity | 透明度设置,0表示完全透明,1表示完全不透明 |
transparent | 是否开启透明,默认false |
three.module.js,OrbitControls.js和CSG.js官网都有对应的文件)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes"> <title>Three.js - Fundamentals with light</title> <style> html, body { height: 100%; margin: 0; font-family: sans-serif; } #c { width: 100%; height: 100%; } #container { position: relative; /* makes this the origin of its children */ width: 100%; height: 100%; overflow: hidden; } </style> </head> <body> <body> <div id="container"> <canvas id="c"></canvas> </div> </body> <script type="importmap"> { "imports": { "three": "../build/three.module.js", "three/addons/": "../build/jsm/" } } </script> <script type="module"> import * as THREE from 'three' import { OrbitControls } from 'three/addons/controls/OrbitControls.js' import { CSG } from '../three-csg-ts/lib/esm/CSG.js' function main() { const canvas = document.querySelector('#c'); const renderer = new THREE.WebGLRenderer({ canvas }); const loader = new THREE.TextureLoader(); const fov = 45; const aspect = 2; // the canvas default const near = 0.1; const far = 100; const camera = new THREE.PerspectiveCamera(fov, aspect, near, far); camera.position.set(0, 10, 20); const controls = new OrbitControls(camera, canvas); const scene = new THREE.Scene(); scene.background = new THREE.Color('white'); //地板 { const planeSize = 40; const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize); const planeMat = new THREE.MeshPhongMaterial({ side: THREE.DoubleSide, color: 0xA9A9A9 }); const mesh = new THREE.Mesh(planeGeo, planeMat); mesh.rotation.x = Math.PI * -.5; scene.add(mesh); } //灯光 { const color = 0xFFFFFF; const intensity = 1; const light = new THREE.DirectionalLight(color, intensity); light.position.set(0, 10, 0); light.target.position.set(-5, 0, 0); scene.add(light); scene.add(light.target); } { //门 const cubeGeo = new THREE.BoxGeometry(5.5, 9.9, 0.2); const cubeMat = new THREE.MeshBasicMaterial( { map: loader.load('./images/door_right.png') }); const mesh = new THREE.Mesh(cubeGeo, cubeMat); mesh.position.set(-2.5, 0, 0); //m门轴 const lineGeo = new THREE.BoxGeometry(0.2, 9.9, 0.2); const lineMat = new THREE.MeshBasicMaterial( { map: loader.load('./images/line.png') }); const linemesh = new THREE.Mesh(lineGeo, lineMat); linemesh.position.set(17.5, 5, 0); linemesh.add(mesh); //旋转角度 //linemesh.rotateY(-Math.PI / 2) scene.add(linemesh); //创建透明玻璃 const windowsGeo = new THREE.BoxGeometry(4, 4, 0.4); const windowsMat = new THREE.MeshStandardMaterial({ map: loader.load('./images/window.png'), color: 0xffffff, opacity: 0.2, transparent: true }); const windowsmesh = new THREE.Mesh(windowsGeo, windowsMat); windowsmesh.position.set(-10, 15, 0); scene.add(windowsmesh); //门洞 const cubeGeo1 = new THREE.BoxGeometry(5.5, 10, 1); const cubeMat1 = new THREE.MeshBasicMaterial({ color: '#8AC' }); const mesh1 = new THREE.Mesh(cubeGeo1, cubeMat1); //墙 const cubeGeo2 = new THREE.BoxGeometry(40, 20, 1); const cubeMat2 = new THREE.MeshBasicMaterial({ color: 0xFFFFFF }); const mesh2 = new THREE.Mesh(cubeGeo2, cubeMat2); //窗户 const cubeGeo3 = new THREE.BoxGeometry(4, 4, 1); const cubeMat3 = new THREE.MeshBasicMaterial({ }); const mesh3 = new THREE.Mesh(cubeGeo3, cubeMat3); //不写默认就是居中,坐标是相对于组合时的坐标 mesh1.position.add(new THREE.Vector3(15, -5, 0)) mesh3.position.add(new THREE.Vector3(-10, 5, 0)) mesh1.updateMatrix() mesh2.updateMatrix() mesh3.updateMatrix() const g1 = CSG.fromMesh(mesh1) const g2 = CSG.fromMesh(mesh2) const g3 = CSG.fromMesh(mesh3) //减去 const cubeSphereIntersectCSG1 = g2.subtract(g1) const cubeSphereIntersectCSG = cubeSphereIntersectCSG1.subtract(g3) //联盟 组合到一起不好改材质 //const cubeSphereIntersectCSG = cubeSphereIntersectCSG2.union(g) //相交 //const cubeSphereIntersectCSG = cubeSphereIntersectCSG2.intersect(g3) //给墙配置一个纹理 loader.load('./images/diffuse_2k.jpg', (texture) => { const material = new THREE.MeshBasicMaterial({ map: texture, }); const cubeSphereIntersectMesh = CSG.toMesh(cubeSphereIntersectCSG, new THREE.Matrix4()) cubeSphereIntersectMesh.material = new THREE.MeshBasicMaterial({ map: texture }) cubeSphereIntersectMesh.position.set(0, 10, 0) scene.add(cubeSphereIntersectMesh) }); } function resizeRendererToDisplaySize(renderer) { const canvas = renderer.domElement; const width = canvas.clientWidth; const height = canvas.clientHeight; const needResize = canvas.width !== width || canvas.height !== height; if (needResize) { renderer.setSize(width, height, false); } return needResize; } function render() { if (resizeRendererToDisplaySize(renderer)) { const canvas = renderer.domElement; camera.aspect = canvas.clientWidth / canvas.clientHeight; camera.updateProjectionMatrix(); } renderer.render(scene, camera); requestAnimationFrame(render); } requestAnimationFrame(render); } main(); </script> </body> </html>