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设置坐标也不好使。

解决办法:

                //不写默认就是居中,坐标是相对于组合时的坐标
 
                mesh1.position.add(new THREE.Vector3(15, -5, 0))
                mesh3.position.add(new THREE.Vector3(-10, 5, 0))
                mesh1.updateMatrix()
                mesh3.updateMatrix()

               这样就设置的模型相对墙的位置了。

第二个问题就是设置组合的材质,我目前是把开孔的墙作为一个的模型,门和门轴是一个模型,窗户是一个模型。

如果把门和窗户都组合到墙模型的话不容易改变材质,不方便代码的改动,如果你要做多面墙的话也不方便。可以把多面墙组合到一起。

下图的代码就是给组合完成的对象添加一个纹理,然后设置组合对象的坐标

     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)
                });
 

2.墙体填充门和门轴,让门围绕着门轴旋转 链接ThreeJs入门41-物体旋转的方法和技巧2 - 掘金 (juejin.cn)

旋转默认是按照模型中心旋转的,如果仅对门模型进行rotateY旋转的话,就会出现一半门在里面。一半在外面的情况。我们可以把门和门轴组合起来,然后在让门和门轴旋转就会实现门根据门轴旋转了

                //门

                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);
               //门轴
                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);
 
透明材质要实现玻璃效果使用MeshLambertMaterial

材质常见属性

材质属性简介
color 材质颜色,比如蓝色0x0000ff
wireframe 将几何图形渲染为线框。 默认值为false
opacity 透明度设置,0表示完全透明,1表示完全不透明
transparent 是否开启透明,默认false

 
 
 
 
 
 
 //创建透明玻璃
                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);
 
结尾:上述就是我目前开发过程遇到的问题,下面附上html的代码,运行我用的是node.js部署的,不能单独点开运行。(
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>

  

 有什么心得和问题可以在评论区沟通
posted @   国产小品牌  阅读(1216)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示