three.js 模型高亮效果实现说明(结合react)

three.js + react 实现鼠标移入模型高亮选中效果

使用EffectComposer和其附加的渲染效果Passes(如RenderPassOutlinePass)来实现高级渲染效果。首先创建EffectComposer实例,并添加RenderPassOutlinePass,最后在渲染循环中调用EffectComposer的渲染方法。这样可以在保持场景内容不变的情况下,应用光晕效果,增强场景的视觉效果。

EffectComposer效果合成器

EffectComposer( renderer : WebGLRenderer, renderTarget : WebGLRenderTarget )

  • renderer:用于渲染场景的渲染器。
  • renderTarget:(可选)一个预先配置的渲染目标,内部由 EffectComposer 使用。

属性

  • passes,一个用于表示后期处理过程链(包含顺序)的数组。
  • readBuffer,内部读缓冲区的引用。过程一般从该缓冲区读取先前的渲染结果。
  • renderer,内部渲染器的引用。
  • renderToScreen,最终过程是否被渲染到屏幕(默认帧缓冲区)。

常用方法

  • addPass:将传入的过程添加到过程链。
  • insertPass:将传入的过程插入到过程链中所给定的索引处。
  • render:执行所有启用的后期处理过程,来产生最终的帧。
  • reset:重置所有EffectComposer的内部状态。
  • setPixelRatio:设置设备的像素比。
  • setSize:考虑设备像素比,重新设置内部渲染缓冲和过程的大小为(width, height)。

资源文件说明

  • EffectComposer:所有后期处理效果的容器。
  • RenderPass:用于渲染基础场景到一张纹理上,但不会添加至屏幕上。
  • OutlinePass:添加闪烁效果。
  • FXAA/SMAA Pass:可选地加入抗锯齿Pass,如FXAAShader或SMAAPass,提高边缘平滑度。
  • UnrealBloomPass:如果需要,还可以添加UnrealBloomPass以增强光照和视觉效果。

引入资源文件

javascript 复制代码
import * as three from 'three';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass';
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass';
import { GammaCorrectionShader } from 'three/examples/jsm/shaders/GammaCorrectionShader';

创建THREE.EffectComposer

创建一个 THREE.EffectComposer 对象,传入的参数是WebGLRenderer:

// 创建一个webGL对象
let renderer = new THREE.WebGLRenderer({
    //增加下面两个属性,可以抗锯齿
    antialias: true,
    alpha: true,
    logarithmicDepthBuffer: true // 解决模型闪烁问题
});
// 创建一个EffectComposer对象
let composer = new EffectComposer(renderer); 

创建renderPass渲染通道

染器通道RenderPass的作用是指定后处理对应的相机camera和场景scene。

// 创建一个渲染器通道,场景和相机作为参数 
const renderPass = new RenderPass(scene, camera);
// 给EffectComposer添加一个渲染器通道 
composer.addPass(renderPass);

创建OutlinePass高亮通道

给three.js场景中模型添加闪烁效果,如果有多个模型的话,可以通过OutlinePass的选择对象属性.selectedObjects设置。

// 模型边缘发光通道
const v2 = new THREE.Vector2(width, height);
// OutlinePass第一个参数的尺寸和canvas画布保持一致
const outlinePass = new OutlinePass(v2, scene, camera);

outlinePass.visibleEdgeColor.set('#00FF00'); // 呼吸显示颜色 outlinePass.hiddenEdgeColor.set('#00FF00');// 呼吸消失颜色 
outlinePass.edgeStrength = 5; // 边框的亮度强度 
outlinePass.edgeGlow = 0.5; // 光晕[0,1] 
outlinePass.edgeThickness = 3;// 边缘宽度 
outlinePass.pulsePeriod = 2; // 呼吸闪烁速度 
outlinePass.renderToScreen = true; // 设置这个参数的目的是马上将当前的内容输出

// 将OutlinePass通道添加到后处理composer中
composer.addPass(outlinePass);

循环渲染

javascript 复制代码
function renderFn() {
    composer.render(T);
    requestAnimationFrame(renderFn);
}

高亮方法封装

/**
 * 模型移入高亮
 * @param { 区域宽度 } width
 * @param { 区域高度 } height
 * @param { 场景对象 } scene
 * @param { 摄像机对象} camera
 * @param { 渲染回调} renderer
 */
function setModelComposer(width, height, scene, camera, renderer) {
    // 创建一个EffectComposer(效果组合器)对象,在该对象上添加后期处理通道,用于模型高亮
    const composer = new EffectComposer(renderer);
    // 新建一个场景通道,
    const renderPass = new RenderPass(scene, camera);
    // 给EffectComposer添加一个渲染器通道 
    composer.addPass(renderPass);
    // 模型边缘发光通道
    const v2 = new THREE.Vector2(width, height);
    // OutlinePass第一个参数的尺寸和canvas画布保持一致
    const outlinePass = new OutlinePass(v2, scene, camera);
    
    outlinePass.visibleEdgeColor.set('#00FF00'); // 呼吸显示颜色
    outlinePass.hiddenEdgeColor.set('#00FF00');// 呼吸消失颜色
    outlinePass.edgeStrength = 5; // 边框的亮度强度
    outlinePass.edgeGlow = 0.5; // 光晕[0,1]
    outlinePass.edgeThickness = 3;// 边缘宽度
    outlinePass.pulsePeriod = 2; // 呼吸闪烁速度
    outlinePass.renderToScreen = true; // 设置这个参数的目的是马上将当前的内容输出
    // 将OutlinePass通道添加到后处理composer中
    composer.addPass(outlinePass);
    
    // 保持outputEncoding = sRGBEncoding,自定义着色器通道作为参数
    let effectCopy = new ShaderPass(GammaCorrectionShader);
    effectCopy.renderToScreen = true;
    composer.addPass(effectCopy);

    composer.selectedObjectEffect = function (objs) {
        let selectedObjects = [];
        selectedObjects.push(objs);
        outlinePass.selectedObjects = selectedObjects;
    };
    return composer;
}

设置高亮

javascript 复制代码
// 监听鼠标移动事件、获取需要高亮模型,设置高亮
datahubBox.current.addEventListener('mousemove', (event) => {
    let selectObj = getCanvasIntersects(event, composerData, camera, datahubBox.current);
    if (selectObj && selectObj.length > 0) {
        isComposer = true;
        composer.selectedObjectEffect(selectObj[0].object);
    } else {
        isComposer = false;
    }
});

我想要实现的是子模型高亮,所以我这里取子模型的object。

注意:传入的参数是一个数组,传入那些模型,那些模型就能高亮。

每次点击前需要清空composer。
getCanvasIntersects方法参考链接

完整代码

react 复制代码
let scene, camera, renderer, controls;
let stats = null; // 检测动画运行时的帧数
let clock = new THREE.Clock(); // getDelta()方法获得两帧的时间间隔
let FPS = 30;
let renderT = 1 / FPS;
let timeS = 0;

const ThreeModel = observer(() => {
    // 设置灯光
    function setLight() {
        let hemiLightTop = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.5);
        let hemiLightBottom = new THREE.HemisphereLight(0xffffff, 0.5);
        let lightTop = new THREE.DirectionalLight(0xffffff, 0.1);
        let lightAfter = new THREE.DirectionalLight(0xffffff, 0.5);
        hemiLightTop.position.set(0, 2000, 0);
        hemiLightBottom.position.set(0, 0, 0);
        lightTop.position.set(4, 6, 4);
        lightAfter.position.set(0, 0, 2000);
        scene.add(hemiLightTop);
        scene.add(hemiLightBottom);
        scene.add(lightTop);
        scene.add(lightAfter);
        lightTop.castShadow = true;// 光源开启阴影
        lightTop.shadow.mapSize = new THREE.Vector2(1024, 1024);
        lightTop.shadow.bias = -0.001;
    }
    // 渲染函数
    function renderFn() {
        requestAnimationFrame(renderFn);
        if (isComposer && composer) {
            // 组合渲染器,渲染高亮
            composer.render(T);
        } else {
            // 用相机渲染一个场景
            renderer.render(scene, camera);
        }
    }
    useEffect(() => {
        // 监听鼠标移动事件、获取需要高亮模型,设置高亮, composerData需要高亮数据
       datahubBox.current.addEventListener('mousemove', (event) => {
            let selectObj = getCanvasIntersects(event, composerData, camera, datahubBox.current);
            if (selectObj && selectObj.length > 0) {
                isComposer = true;
                composer.selectedObjectEffect(selectObj[0].object);
            } else {
                isComposer = false;
            }
        });
    }, []);
    useEffect(() => {
        // 初始化页面canvas,初始化场景
        // 定义场景
        scene = new THREE.Scene();
        // 灯光
        setLight();
        // 获取盒子宽高设置相机和渲染区域大小
        let width = datahubBox.current.offsetWidth;
        let height = datahubBox.current.offsetHeight;
        let k = width / height;
        // 定义相机
        camera = new THREE.PerspectiveCamera(45, k, 0.25, 100000);
        camera.position.set(-547, 15224, 2195);
        camera.lookAt(scene.position);

        // 创建一个webGL对象
        renderer = new THREE.WebGLRenderer({
            //增加下面两个属性,可以抗锯齿
            antialias: true,
            alpha: true,
            logarithmicDepthBuffer: true // 解决模型闪烁问题
        });
        renderer.setSize(width, height); // 设置渲染区域尺寸
        renderer.setClearColor(0x23284D, 0.0); // 设置颜色透明度
        // 首先渲染器开启阴影
        renderer.shadowMap.enabled = true;
        // 修改渲染模式
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.outputEncoding = THREE.sRGBEncoding;
        renderer.textureEncoding = THREE.sRGBEncoding;
        // 挂载到DOM节点
        datahubBox.current.appendChild(renderer.domElement);
        // 监听鼠标事件
        controls = new THREE.OrbitControls(camera, renderer.domElement);
        //- 拖拽惯性
        controls.enableDamping = true;
        controls.dampingFactor = 0.05;
        // 控制上下旋转范围
        controls.minPolarAngle = 0;
        controls.maxPolarAngle = Math.PI / 2;
        // 限制缩放范围
        controls.minDistance = 0;
        controls.maxDistance = cameraDistanceMax;
        // 高亮设置
        composer = setModelComposer(width, height, scene, camera, renderer);
        // 渲染
        renderFn();
    }, []);
     useEffect(() => {
        // 重置数据
        return () => {
            scene = null;
            camera = null;
            renderer = null;
            controls = null;
            composer = null;
            isComposer = false;
        };
    }, []);
    {/* canvas盒子 */}
    return <div className='ui_model_box' ref={datahubBox}></div>;
});
export default ThreeModel;
相关推荐
小牛itbull2 小时前
ReactPress:重塑内容管理的未来
react.js·github·reactpress
FinGet13 小时前
那总结下来,react就是落后了
前端·react.js
王解16 小时前
Jest项目实战(2): 项目开发与测试
前端·javascript·react.js·arcgis·typescript·单元测试
AIoT科技物语1 天前
免费,基于React + ECharts 国产开源 IoT 物联网 Web 可视化数据大屏
前端·物联网·react.js·开源·echarts
初遇你时动了情1 天前
react 18 react-router-dom V6 路由传参的几种方式
react.js·typescript·react-router
番茄小酱0011 天前
ReactNative中实现图片保存到手机相册
react native·react.js·智能手机
王解1 天前
Jest进阶知识:深入测试 React Hooks-确保自定义逻辑的可靠性
前端·javascript·react.js·typescript·单元测试·前端框架
小牛itbull1 天前
ReactPress—基于React的免费开源博客&CMS内容管理系统
前端·react.js·开源·reactpress
~甲壳虫2 天前
react中得类组件和函数组件有啥区别,怎么理解这两个函数
前端·react.js·前端框架
ReBeX2 天前
【GeoJSON在线编辑平台】(1)创建地图+要素绘制+折点编辑+拖拽移动
前端·javascript·gis·openlayers·webgis