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

news2024/9/24 13:14:28

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以增强光照和视觉效果。

引入资源文件

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

循环渲染

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

设置高亮

// 监听鼠标移动事件、获取需要高亮模型,设置高亮
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方法参考链接

完整代码

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;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1993346.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL中的索引——适合创建索引的情况

1.适合创建索引的情况 1、字段的数值有唯一性的限制 2、频繁作为 WHERE 查询条件的字段 某个字段在 SELECT 语句的 WHERE 条件中经常被使用到&#xff0c;那么就需要给这个字段创建索引了。尤其是在数据量大的情况下&#xff0c;创建普通索引就可以大幅提升数据查询的效率。 …

AI学习记录 - 如何进行token理论知识,以GPT2为举例

AI学习记录已经发了十几篇&#xff0c;大佬们可以看看&#xff0c;如果有帮助动动小手点赞 token入门版&#xff0c;有空会更新具体代码操作&#xff0c;能学到一点东西的话&#xff0c;大佬们点个赞&#xff01;&#xff01;&#xff01; GPT4当中&#xff0c;我们提问问题是…

免费【2024】springboot 甘肃旅游工艺品商城的设计与实现

博主介绍&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…

ubuntu20.04 环境搭建教程

1&#xff1a; Ubuntu 版本说明 我使用版本为 ubuntu20.04 ->镜像文件网上下载最新版本 mirrors.huaweicloud.com/ubuntu-releases/20.04.6/ Ubuntu 其他说明 Ubuntu 安装的位置不建议放到 C 盘(除非你只有一个 C 盘) Ubuntu 需要 120G 的空间 2&#xff1…

从“萝卜快跑”到“东敏快跑”,百度未来路在何方?

在资本市场的波澜中&#xff0c;百度再次被推上了风口浪尖。近日&#xff0c;百度“老板娘”马东敏的减持行为不仅引发了网友的广泛讨论&#xff0c;更让百度Robotaxi&#xff08;无人驾驶出租车&#xff09;的商业化前景蒙上了一层阴影。 7月1日&#xff0c;马东敏悄然减持了百…

Swagger的介绍与使用(一)

一. 简介 OpenAPI 规范&#xff08;以前称为 Swagger 规范&#xff09;是 REST API 的 API 描述格式。 Swagger 是一个规范且完整的框架&#xff0c;用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。 Swagger 的目标是对 REST API 定义一个标准且和语言无关的接口&am…

Journyx项目管理软件 soap_cgi.pyc XXE漏洞复现

0x01 产品简介 Journyx-Journyx成立于1996年,提供自托管项目管理解决方案ProjectXecute。主要功能包括资源跟踪、待办事项列表、任务分配以及与MS Project的集成。要运行ProjectXecute,需要Windows 2003或更高版本、IIS Web服务器和Intel处理器。也可以在Linux、Solaris、AI…

AI资本泡沫要来了么?——Coatue EMW 2024会议摘要

引言 随着生成式人工智能的快速发展&#xff0c;科技领域正经历着一场前所未有的变革。然而&#xff0c;伴随这一热潮而来的&#xff0c;是关于AI资本泡沫的激烈讨论。历史上&#xff0c;每一次技术革命都带来了巨大机遇&#xff0c;同时也伴随着泡沫和风险。本文将通过Coatue…

windows远程连接银河麒麟系统中的人大金仓数据库

以下来自人大金仓的官方&#xff1a; 概述 由微软提出的ODBC&#xff08;开放式数据库互连&#xff09;&#xff0c;为访问数据库提供统一的接口&#xff08;API&#xff09;。其调用流程为&#xff1a;首先应用程序调用驱动管理器以加载与数据库相对应的ODBC驱动&#xff0c;…

P35算数转换作业讲解

1.单选题 B 2.单选题 A 3.编程题 4.编程题 5.编程题 6.编程题 1.单选题 D

Java八股文面试全套真题-下

Java八股文面试全套真题-下 七、Java多线程7.1、线程的基础知识7.1.1、线程和进程的区别&#xff1f;7.1.2、并行和并发有什么区别&#xff1f;7.1.3、创建线程的四种方式7.1.4、Runnable 和 Callable有什么区别7.1.5、线程的 run()和 start()有什么区别&#xff1f;7.1.6、线程…

GLM大模型的机器翻译能力测试

背景介绍 最近想对GLM-4今年发布的几个大模型 glm-4-0520&#xff0c;glm-4-air以及glm-4-flash简单评测一下它们的机器翻译能力&#xff0c;由于这几个大模型的容量和训练数据都有区别&#xff0c;所以它们的翻译能力也是不同的。我们这里就分别选择一些有趣的&#xff0c;有…

Qt使用lupdate工具生成.ts文件

Qt提供了lupdate工具&#xff0c;用于从源代码中提取需要翻译的字符串【1】&#xff0c;并生成或更新.ts文件 注解【1】&#xff1a;使用tr()函数&#xff08;或者QCoreApplication::translate()等其他相关的翻译函数&#xff09;来标记所有需要翻译的文本。例如&#xff1a; …

MySQL4多表查询 内连接

多表查询 数据准备 CREATE DATABASE db4; USE db4; -- 创建部门表 create table if not exists dept(deptno varchar(20) primary key , -- 部门号name varchar(20) -- 部门名字 );-- 创建员工表 create table if not exists emp(eid varchar(20) primary key , -- 员工编号…

【蝉联】摩斯再次获得“中国隐私计算市场份额第一”

蝉联第一 8月2日&#xff0c;全球领先的IT市场研究和咨询公司IDC发布了《中国隐私计算平台厂商市场份额&#xff0c;2023》报告。蚂蚁集团凭借商用隐私计算平台摩斯&#xff08;MORSE&#xff09;&#xff0c;以 35.3%的市场份额蝉联第一。 2023年&#xff0c;中国隐私计算平台…

CSP 2023 普及组第一轮 - CSP/S 2023初试题 基础部分解析

第 1 题 在 C 中&#xff0c;下面哪个关键字用于声明一个变量&#xff0c; 其值不能被修改?&#xff08;B) A. unsigned B. const C. static D. mutable 【const声明的变量不可修改】 第 2 题 八进制数 12345670(8) 和 07654321(8) 的和为&#xff08;D&#xff09; A. 222222…

智能工单派单:助力家政、售后服务、维修安装等行业高效管理

在家政、售后服务、维修安装等行业&#xff0c;任务的派单、管理和客户服务一直是关键环节。自从我们公司使用了搭贝低代码平台的工单派单应用&#xff0c;这些流程变得更加流畅和高效&#xff0c;显著提升了整体运营效率。 1. 提高工作效率 &#x1f4c8; 通过搭贝平台&#…

力扣面试经典算法150题:多数元素

多数元素 今天的题目是力扣面试经典150题中的数组的简单题: 多数元素 题目链接&#xff1a;https://leetcode.cn/problems/majority-element/description/?envTypestudy-plan-v2&envIdtop-interview-150 题目描述 给定一个大小为 n 的数组 nums&#xff0c;其中包含 n 个…

ESP32开发板单向点对点ESP-NOW无线通信

ESP32开发板单向点对点ESP-NOW无线通信 简介读取ESP32接收方Receiver的MAC地址ESP32发送方Sender程序ESP32接收方Receiver程序ESP-NOW通信验证总结 简介 本例程通过两个ESP32开发板实现单向点对点ESP-NOW无线通信&#xff0c;一个ESP32开发板作为Sender发送方&#xff0c;另一…

CSP-J复赛 模拟题6 解析

此题为水题不讲解 根据无解析写代码1&#xff1a; #include <bits/stdc.h> using namespace std; string s; int main(){getline(cin,s);int lens.length();for(int i0;i<len;i){if(s[i]>A && s[i]<Z){s[i]32;}else if(s[i]>a && s[i]<z…