绘制围栏,实际上可以理解为一个空心圆柱体加底部,类似一个碗状的图形。我是通过three.js的圆柱体几何体CylinderGeometry + 三维模型运算ThreeBSP的函数subtract、圆形几何体CircleGeometry结合实现。
在这里插入图片描述
在Three.js中,绘制一个空心圆柱体可以通过创建一大一小两个CylinderGeometry,使用ThreeBSP的函数subtract求两个圆柱体的差集得到。圆柱体的底部则通过绘制一个CircleGeometry得到。最后合理设定两个模型的位置和旋转角度等实现效果。

圆柱几何体API

CylinderGeometry(radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength)

参数说明:

  • radiusTop:圆柱的顶部半径,默认值是1。
  • radiusBottom:圆柱的底部半径,默认值是1。
  • height:圆柱的高度,默认值是1。
  • radialSegments:圆柱侧面周围的分段数,默认为8。
  • heightSegments:圆柱侧面沿着其高度的分段数,默认值为1。
  • openEnded:一个Boolean值,指明该圆锥的底面是开放的还是封顶的。默认值为false,即其底面默认是封顶的。
  • thetaStart:第一个分段的起始角度,默认为0。(three o’clock position)
  • thetaLength:圆柱底面圆扇区的中心角,通常被称为“θ”(西塔)。默认值是2*Pi,这使其成为一个完整的圆柱。

圆形几何体API

CircleGeometry(radius, segments, thetaStart, thetaLength)

参数说明:

  • radius:圆形的半径,默认值为1。
  • segments:分段(三角面)的数量,最小值为3,默认值为8。。
  • thetaStart:第一个分段的起始角度,默认为0。(three o’clock position)。
  • thetaLength:圆形扇区的中心角,通常被称为“θ”(西塔)。默认值是2*Pi,这使其成为一个完整的圆。。

ThreeBSP三维模型运算简述

ThreeBSP是一个用于处理三维模型布尔运算(‌交集、‌并集、‌差集)‌的库,‌它基于Three.js开发,允许开发者对三维模型进行复杂的几何处理。‌ThreeBSP提供了intersect、‌unionsubtract等函数,‌这些函数可以用于处理两个或多个三维模型的布尔运算,‌从而创建出复杂的三维场景和模型。‌此外,‌由于ThreeBSP是基于Three.js的扩展,‌因此它支持Three.js的所有功能和特性。

  • Intersect函数:‌交集,用于计算两个模型的交集部分,‌即计算两个模型重叠的部分。‌这在需要精确控制模型之间的交互时非常有用,‌例如,‌当需要创建一个模型穿过另一个模型时。‌
  • Union函数:‌并集,将两个模型合并成一个模型。‌这在需要创建一个由多个部分组成的大型模型时非常有用,‌例如,‌创建一个由多个小部件组成的复杂机械或建筑结构。‌
  • Subtract函数:‌差集,从一个模型中减去另一个模型的部分或全部。‌这在需要从一个大模型中去除一个小模型时非常有用,‌例如,‌创建一个有洞的物体或在一个物体上切割出一个形状。‌

绘制圆柱体

创建一大一小两个圆柱体。

const radius = 5; // 圆柱体半径
const height = 3; // 圆柱体高度
// 绘制两个圆柱体
const smallCylinderGeom = new THREE.CylinderGeometry(radius - 0.1, radius - 0.1, height, 100, 10, false);
const largeCylinderGeom = new THREE.CylinderGeometry(radius, radius, height, 100, 10, false);

获得空心圆柱体

通过ThreeBSP模型运算求两个圆柱体的差集获得一个新的模型,这个模型就是空心圆柱体。

// ThreeBSP模型运算,将两个或者多个立方体通过模型交集(intersect)、差集(subtract)、并集(union)运算生成新的运算后立方体
const smallCylinderBSP = new ThreeBSP(smallCylinderGeom);  //- 中心圆bsp对象
const largeCylinderBSP = new ThreeBSP(largeCylinderGeom);   //- 外圈圆bsp对象
// 计算两个圆柱体的差集得到空心圆柱体
const intersectionBSP = largeCylinderBSP.subtract(smallCylinderBSP);

绘制圆形

绘制一个圆心几何体设置位置和旋转方向实现围栏底部。

const geometry = new THREE.CircleGeometry(radius, 100);
const material = new THREE.MeshBasicMaterial({
    color: 0xFF0018,
    side: THREE.DoubleSide,
    transparent: true,
    opacity: 0.1
});
const circle = new THREE.Mesh(geometry, material);
circle.position.set(0, -height / 2, 0);
circle.rotateX(Math.PI / 2);

完整代码

注意点:

  1. 圆柱体设置位置后中心点在圆柱体中心位置,而非底部位置,所以设置位置时需要注意,如果设置圆心几何体的位置,例如高度为5,就需要设置高度为-2.5。也可通过设置圆柱体的位置来确定。
  2. 需要引入three-bsp包。
import { useRef, useEffect } from 'react';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
const ThreeBSP = require('jthreebsp')(THREE);

const wall = require('@/static/image/wall.png');
let renderer, controls, scene, camera;

// three.js绘制圆形围栏,实际是空心圆柱体,通过圆柱体几何体CylinderGeometry + 三维模型运算ThreeBSP + 圆形几何体CircleGeometry实现
const Draw3DHollowCylinder = () => {
    const box = useRef(); // canvas盒子
    // 渲染动画
    function renderFn() {
        requestAnimationFrame(renderFn);
        // 用相机渲染一个场景
        renderer.render(scene, camera);
    }
    useEffect(() => {
        if (scene) {
            // 绘制空心圆柱体
            const radius = 5; // 圆柱体半径
            const height = 3; // 圆柱体高度
            // 绘制一大一小两个圆柱体
            const smallCylinderGeom = new THREE.CylinderGeometry(radius - 0.1, radius - 0.1, height, 100, 10, false);
            const largeCylinderGeom = new THREE.CylinderGeometry(radius, radius, height, 100, 10, false);
            // ThreeBSP模型运算,将两个或者多个立方体通过模型交集(intersect)、差集(subtract)、并集(union)运算生成新的运算后立方体
            const smallCylinderBSP = new ThreeBSP(smallCylinderGeom);  //- 中心圆bsp对象
            const largeCylinderBSP = new ThreeBSP(largeCylinderGeom);   //- 外圈圆bsp对象
            // 获取两个圆柱体的差集
            const intersectionBSP = largeCylinderBSP.subtract(smallCylinderBSP);

            // 创建网格模型
            const textureLoader = new THREE.TextureLoader();
            const texture = textureLoader.load(wall);
            texture.wrapS = THREE.RepeatWrapping; // 水平方向如何包裹
            texture.wrapT = THREE.RepeatWrapping; // 垂直方向如何包裹
            // uv两个方向纹理重复数量、看板中重复数量
            texture.repeat.set(15, 1);
            const wireframeMaterial = new THREE.MeshBasicMaterial({
                map: texture,
                transparent: true,
                opacity: 1,
            });
            // 添加样式
            let hollowCylinder = intersectionBSP.toMesh(wireframeMaterial);

            // 添加圆形底座
            const geometry = new THREE.CircleGeometry(radius, 100);
            const material = new THREE.MeshBasicMaterial({
                color: 0xFF0018,
                side: THREE.DoubleSide,
                transparent: true,
                opacity: 0.1
            });
            const circle = new THREE.Mesh(geometry, material);
            // 设置位置
            circle.position.set(0, -height / 2, 0);
            // 设置旋转方向
            circle.rotateX(Math.PI / 2); 
            // 添加组
            const group = new THREE.Group();
            group.name = 'fenceGroup';
            group.add(hollowCylinder);
            group.add(circle);
            // 添加到场景
            scene.add(group);
        }
    }, [scene]);
    // 初始化环境、灯光、相机、渲染器
    useEffect(() => {
        scene = new THREE.Scene();
        // 添加光源
        const ambitlight = new THREE.AmbientLight(0x404040);
        scene.add(ambitlight)
        const sunlight = new THREE.DirectionalLight(0xffffff);
        sunlight.position.set(-20, 1, 1);
        scene.add(sunlight);
        // let axisHelper = new THREE.AxesHelper();
        // scene.add(axisHelper);//坐标辅助线加入到场景中

        // 获取宽高设置相机和渲染区域大小
        const width = box.current.offsetWidth;
        let height = box.current.offsetHeight;
        let k = width / height;
        // 投影相机
        camera = new THREE.PerspectiveCamera(75, k, 0.1, 1000);
        camera.position.set(1, 0, 25);
        camera.lookAt(scene.position);

        // 创建一个webGL对象
        renderer = new THREE.WebGLRenderer({
            //增加下面两个属性,可以抗锯齿
            antialias: true,
            alpha: true
        });
        renderer.setSize(width, height); // 设置渲染区域尺寸
        renderer.setClearColor(0x000000, 1); // 设置颜色透明度
        // 首先渲染器开启阴影
        renderer.shadowMap.enabled = true;
        box.current.appendChild(renderer.domElement);
        // 监听鼠标事件
        controls = new OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;//设置为true则启用阻尼(惯性),默认false
        controls.dampingFactor = 0.05;//值越小阻尼效果越强
        // 渲染
        renderFn();
    }, []);

    return <div className='ui_container_box'>
        three.js绘制3D空心圆柱体。
        <div style={{ width: '100%', height: '100%' }} ref={box}></div>
    </div>;
}

export default Draw3DHollowCylinder;

注意:使用ThreeBSP需要注意three.js的版本,新版的three.js将对应的Geometry删除,所以会找不到对应的模型,需要将three.js版本降低到Geometry还存在的情况,暂时不知其他解决方案。测试修改three版本为124版本及以下才可用。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐