three.js工厂案例

  1. 最终效果图

在这里插入图片描述

  1. 给定html页面,作为tag标签展示
<body style="background-color: black;">
  <div id="tag">
    <div style="position:relative;width:400px;height:322px;color: #fff;">
      <img src="background.png" alt="" style="width:100%;position: absolute;left: 0px;top: 0px;">
      <div style="position:absolute;left:48px;top:36px;font-size:16px;">
        <div style="font-size:20px;font-weight: 400;">
          <span>设备A</span>
        </div>
        <div style="margin-top: 30px;">
          <span style="font-weight: 400;margin-left: 80px;font-size: 40px;color: #00ffff;">test</span>
        </div>
        <div style="margin-top: 20px;">
          <span style="color: #ccc;font-weight: 300;">管理</span><span
            style="font-weight: 400;margin-left: 30px;">admin</span>
        </div>
        <div style="margin-top: 10px;">
          <span style="color: #ccc;font-weight: 300;">工号</span><span
            style="font-weight: 400;margin-left: 30px;">123456</span>
        </div>
      </div>
      <div style="position:absolute;left:285px;top:35px;">
        <span style="color: #ffff00;">异常</span>
      </div>
    </div>
  </div>

  <!-- type="importmap"功能:.html文件中也能和nodejs开发环境中一样方式,引入npm安装的js库 -->
  <script type="importmap">
      {
    "imports": {
      "three": "../build/three.module.js",
      "three/addons/":"../three.js-r167/examples/jsm/"
    }
  }
</script>
  <script src="./index.js" type="module">
  </script>
</body>

  1. mode.js

加载3D模型

// 引入Three.js
import * as THREE from 'three';
// 引入gltf模型加载库GLTFLoader.js
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = new GLTFLoader(); //创建一个GLTF加载器
const model = new THREE.Group(); //声明一个组对象,用来添加加载成功的三维场景
loader.load("test.glb", function (gltf) { //gltf加载成功后返回一个对象
    model.add(gltf.scene); //三维场景添加到model组对象中
})
export default model;

// 打印出模型中的所有对象
// gltf.scene.traverse(function (child) {
//   if (child.isMesh) {
//     // 打印对象名称和类型
//     console.log('对象名称:', child.name, ', 对象类型:', child.type);
//   }
// });
  1. tag.js

Three.js 的 WebGLRenderer 只负责渲染 3D 场景,使用 WebGL 在 GPU 上渲染图像。WebGL 渲染和 HTML 的渲染流程是分开的,互相不干扰。HTML 元素是由浏览器的渲染引擎渲染的,和 GPU 渲染的 3D 场景没有直接关联。

所以,普通 HTML 标签不会直接参与 Three.js 的场景渲染,也不会随 3D 场景的变化(如相机位置、旋转等)自动更新位置。

因此引入CSS2DRenderer 专门渲染 HTML/CSS 标签,允许在 WebGL 场景中直接显示和操作 DOM 元素,如显示标注或文本信息。

// 引入CSS2模型对象CSS2DObject
import {
  CSS2DObject
} from 'three/addons/renderers/CSS2DRenderer.js';
const div = document.getElementById('tag');
div.style.top = '-161px'; //指示线端点和标注点重合
// HTML元素转化为threejs的CSS2模型对象
const tag = new CSS2DObject(div);
export default tag;
  1. index.js

    1. 将模型渲染到场景中【内容基本不变,一般只改一下光源,相机位置】
const scene = new THREE.Scene();
//模型对象添加到场景中
scene.add(model);

//辅助观察的坐标系
const axesHelper = new THREE.AxesHelper(100);
scene.add(axesHelper);

//光源设置
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5000, 60, 50);
scene.add(directionalLight);
const ambient = new THREE.AmbientLight(0xffffff, 0.9);
scene.add(ambient);

//渲染器和相机
const width = window.innerWidth;
const height = window.innerHeight;
const camera = new THREE.PerspectiveCamera(30, width / height, 1, 3000);
camera.position.set(82, 53, 165);
camera.lookAt(0, 0, 0);

const renderer = new THREE.WebGLRenderer({
  // 抗锯齿优化
  antialias:true
});

// 防止输出模糊
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);

//解决加载gltf格式模型颜色偏差问题
//设置后处理,该方法无效
renderer.outputEncoding = THREE.sRGBEncoding;

// 渲染循环
function render() {
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}
render();

// 设置相机控件轨道控制器OrbitControls
const controls = new OrbitControls(camera, renderer.domElement);

// 画布跟随窗口变化
window.onresize = function () {
    renderer.setSize(window.innerWidth, window.innerHeight);
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
};

光源的颜色,强度可以通过GUI来直接控制;

其中DirectionalLight为定向光源,用于模拟太阳光或类似的强烈光源,可以产生阴影,且光线有方向性,适用于需要精确阴影的场景;

AmbientLight环境光用于为场景提供均匀的基础光照,所有物体都被均匀照亮,无阴影效果。

import {GUI} from 'three/addons/libs/lil-gui.module.min.js'
// GUI 设置
const gui = new GUI();
const lightFolder = gui.addFolder('光源设置');
const ambientLightFolder = gui.addFolder('环境光设置');

// 光源控制
const lightParams = {
  color: directionalLight.color.getHex(),
  intensity: directionalLight.intensity
};
lightFolder.addColor(lightParams, 'color').name('颜色').onChange((value) => {
  directionalLight.color.setHex(value);
});
lightFolder.add(lightParams, 'intensity', 0, 2).name('光强').onChange((value) => {
  directionalLight.intensity = value;
});

// 环境光控制
const ambientParams = {
  color: ambient.color.getHex(),
  intensity: ambient.intensity
};
ambientLightFolder.addColor(ambientParams, 'color').name('颜色').onChange((value) => {
  ambient.color.setHex(value);
});
ambientLightFolder.add(ambientParams, 'intensity', 0, 2).name('光强').onChange((value) => {
  ambient.intensity = value;
});

对应的也可以控制相机的位置(x,y,z轴),视角等参数,方便在camera.position.set(94, 58, 107);设置相机位置的时候给出合适的值

// GUI 设置相机控制
const cameraFolder = gui.addFolder('相机设置');

// 控制相机位置
cameraFolder.add(camera.position, 'x', -500, 500).name('相机 X 轴');
cameraFolder.add(camera.position, 'y', -500, 500).name('相机 Y 轴');
cameraFolder.add(camera.position, 'z', -500, 500).name('相机 Z 轴');

// 控制相机视角 (fov)
cameraFolder.add(camera, 'fov', 1, 100).name('视角').onChange(() => {
  camera.updateProjectionMatrix(); // 更新相机投影矩阵
});

// 控制相机的近/远剪裁面
cameraFolder.add(camera, 'near', 0.1, 1000).name('近剪裁面').onChange(() => {
  camera.updateProjectionMatrix(); // 更新相机投影矩阵
});
cameraFolder.add(camera, 'far', 1, 5000).name('远剪裁面').onChange(() => {
  camera.updateProjectionMatrix(); // 更新相机投影矩阵
});

2. 添加`EffectComposer`后处理

基本渲染WebGLRendererrender 方法用于将 three.js 场景渲染到屏幕上,直接得到渲染结果。renderer.render(scene, camera);

后期处理的需求: 如果需要对渲染结果应用一些后期处理效果(如模糊、颜色校正、景深等),EffectComposer 能够在渲染之后对图像进行一系列处理效果。

// 创建后处理对象EffectComposer,WebGL渲染器作为参数
const composer = new EffectComposer(renderer);
// 创建一个渲染器通道,场景和相机作为参数
const renderPass = new RenderPass(scene, camera);
// 设置renderPass通道
composer.addPass(renderPass);

// 渲染循环
function render() {
  // 后处理渲染
    composer.render();
    requestAnimationFrame(render);
}
render();
3. 添加描边光效后处理`OutlinePass`
// 创建outlinePass通道
// OutlinePass第一个参数v2的尺寸和canvas画布保持一致
const v2 = new THREE.Vector2(window.innerWidth, window.innerHeight);
const outlinePass = new OutlinePass(v2, scene, camera);
outlinePass.visibleEdgeColor.set(0x00ffff); //模型描边颜色,默认白色 
outlinePass.edgeThickness = 4; //高亮发光描边厚度
outlinePass.edgeStrength = 6; //高亮描边发光强度
outlinePass.pulsePeriod = 2;//模型闪烁频率控制,默认0不闪烁
outlinePass.usePatternTexture = false; // 禁用纹理以获得纯线的效果

composer.addPass(outlinePass);

场景中添加outlinePass之后变得很暗,这是因为当使用threejs后处理功能后,renderer.outputEncoding = THREE.sRGBEncoding会失效,出现颜色偏差。 本质上涉及到线性空间sRGB 空间之间的转换问题;

gltf模型中包含的颜色被导出一般为sRGB颜色空间,而three.js是在线性空间中渲染模型材质,因为three.js会将sRGB转化为Linear;而为了在显示器上正确显示颜色,不出现偏差,又需要将线性空间转化会sRGB空间;

three.js中可以通过伽马矫正来解决gltf模型后处理时候,颜色偏差的问题

// 伽马校正后处理Shader
import {GammaCorrectionShader} from 'three/addons/shaders/GammaCorrectionShader.js';
// ShaderPass功能:使用后处理Shader创建后处理通道
import {ShaderPass} from 'three/addons/postprocessing/ShaderPass.js';

const gammaPass= new ShaderPass(GammaCorrectionShader);
composer.addPass(gammaPass);

报错:着色器环境中未找到sRGBTransferOETF函数,当前版本148,没有去看其余版本是否包含此函数,选择自定义伽马矫正

// 自定义伽马矫正着色器
const gammaCorrectionShader = {
  uniforms: {
      "tDiffuse": { value: null },
      "gammaFactor": { value: 2.2 }
  },
  vertexShader: `
      varying vec2 vUv;

      void main() {
          vUv = uv;
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
      }
  `,
  fragmentShader: `
      varying vec2 vUv;
      uniform sampler2D tDiffuse;
      uniform float gammaFactor;

      void main() {
          vec4 tex = texture2D(tDiffuse, vUv);
          gl_FragColor = vec4(pow(tex.rgb, vec3(1.0 / gammaFactor)), tex.a);
      }
  `
};
4. 添加抗锯齿效果(左抗锯齿前,右后)
// SMAA抗锯齿通道
import {SMAAPass} from 'three/addons/postprocessing/SMAAPass.js';
//获取.setPixelRatio()设置的设备像素比
const pixelRatio = renderer.getPixelRatio();
// width、height是canva画布的宽高度
const smaaPass = new SMAAPass(width * pixelRatio, height * pixelRatio);

composer.addPass(smaaPass);//抗锯齿效果

5. 创建CSS2D渲染器,将标签渲染到webgl页面上
// 创建一个CSS2渲染器CSS2DRenderer
const css2Renderer = new CSS2DRenderer();
css2Renderer.setSize(width, height);
// HTML标签<div id="tag"></div>外面父元素叠加到canvas画布上且重合
css2Renderer.domElement.style.position = 'absolute';
css2Renderer.domElement.style.top = '0px';
//设置.pointerEvents=none,解决HTML元素标签对threejs canvas画布鼠标事件的遮挡
css2Renderer.domElement.style.pointerEvents = 'none';
// 将CSS2DRenderer的DOM元素添加到html的body中,显示在3D画布的上方。
document.body.appendChild(css2Renderer.domElement);

// 渲染循环
function render() {
    css2Renderer.render(scene,camera);
    // renderer.render(scene, camera);
    composer.render();
    requestAnimationFrame(render);
}
render();
6. 鼠标点击事件
    1. `THREE.Raycaster` 是 Three.js 提供的一个用于进行射线投射的工具。用于 3D 空间中检测物体交互的方法,通常用于实现点击、碰撞检测、选择对象等功能。简单来说就是从一个点(发射点)沿某个方向发射射线,并检测射线与场景中物体的交点。
    2. 获取罐的模型对象,可以看到下面有两个子节点设备A和设备B

    3. 设置mesh对象的ancestors属性为它所在的 Group 对象
    4. 创建射线,并且把鼠标点击获取的坐标和相机传入参数,可视化射线,查看射线击中了哪个物体以及交点的位置  
    5. 后续的就是js的逻辑,判断点击获取到的对象,根据对应名字,找到标注A,标注B,将tag标签添加到模型的相应位置,并添加发光描边效果

// 点击事件
let chooseObj = null;

addEventListener('click', function (event) {
  const px = event.offsetX;
  const py = event.offsetY;

  // 屏幕坐标转标准设备坐标
  const x = (px / window.innerWidth) * 2 - 1;
  const y = -(py / window.innerHeight) * 2 + 1;

  // 创建射线
  const raycaster = new THREE.Raycaster();
  raycaster.setFromCamera(new THREE.Vector2(x, y), camera);

  // 获取存储罐模型对象
  const tank = model.getObjectByName('罐');

  // 遍历并设置所有子对象的祖先属性
  for (let i = 0; i < tank.children.length; i++) {
    const group = tank.children[i];
    group.traverse(function (obj) {
      if (obj.isMesh) {
        obj.ancestors = group;
      }
    });
  }

  // 射线交叉检测
  const intersects = raycaster.intersectObjects(tank.children);
  if (intersects.length > 0) {
    const intersectedObject = intersects[0].object.ancestors;

    // 设置发光描边
    outlinePass.selectedObjects = [intersectedObject];

    // 获取模型对应的标注对象
    const labelObject = model.getObjectByName(intersectedObject.name + '标注');
    if (labelObject) {
      // 将标签添加到模型标注对象上
      labelObject.add(tag);
    }

    // 更新当前选中的对象
    chooseObj = intersectedObject;
  } else {
    // 如果有已经选中的对象,移除发光描边和标签
    if (chooseObj) {
      outlinePass.selectedObjects = []; // 清除发光描边

      const labelObject = model.getObjectByName(chooseObj.name + '标注');
      if (labelObject) {
        // 从模型的标注对象中移除标签
        labelObject.remove(tag);
      }

      // 重置选中的对象
      chooseObj = null;
    }
  }
});
  1. CSS3DRenderer

目前的CSS2DRenderer渲染出来的tag标签,无法随着场景相机同步缩放,因此在此基础上,进行3D渲染,基本上把CSS2DRenderer替换成CSS3DRenderer,再设置tag缩放比例,调整位置就行;

tag.scale.set(0.1,0.1,0.1)

给tag加上style:backface-visibility: hidden;即可在模型背面不显示该tag

  1. CSS3DSprite

CSS3DRenderer是一个普通的 3D HTML 对象,跟随 3D 场景中的旋转和缩放,但不会自动面向相机。 CSS3DSprite会始终面向相机,因此更适合用作 3D 场景中的标签或注释。

只需要const tag = new CSS3DSprite(div);将模型转化成精灵对象即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值