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 |
1 | three.module.js,OrbitControls.js和CSG.js官网都有对应的文件 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | <! 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 > |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!