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部署的,不能单独点开运行。(
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>

  

 有什么心得和问题可以在评论区沟通
posted @ 2023-02-06 14:06  国产小品牌  阅读(794)  评论(0编辑  收藏  举报