three.js + react 实现鼠标移入模型高亮选中效果
使用EffectComposer和其附加的渲染效果Passes(如RenderPass和OutlinePass)来实现高亮渲染效果。首先创建EffectComposer实例,并添加RenderPass和OutlinePass,最后在渲染循环中调用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:用于渲染基础场景到一张纹理上,但不会添加至屏幕上。
不同功能后处理通道
threejs提供了很多后处理通道,想实现什么样的后期处理效果,需要调用threejs对应的后处理通道扩展库。
- OutlinePass:添加闪烁效果。
- FXAA/SMAA Pass:可选地加入抗锯齿Pass,如FXAAShader或SMAAPass,提高边缘平滑度。
- UnrealBloomPass:Bloom发光,如果需要,还可以添加以增强光照和视觉效果。
- GlitchPass:画面抖动效果。
创建THREE.EffectComposer
引入EffectComposer.js。
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';
WebGL渲染器执行渲染方法.render()会得到一张图像,如果你需要对一个webgl渲染器的渲染结果进行后期处理,就把WebGLRenderer作为EffectComposer的参数传入。
// 创建一个webGL对象
let renderer = new THREE.WebGLRenderer({
//增加下面两个属性,可以抗锯齿
antialias: true,
alpha: true,
logarithmicDepthBuffer: true // 解决模型闪烁问题
});
// 创建一个EffectComposer对象
let composer = new EffectComposer(renderer);
渲染器通道renderPass
染器通道RenderPass的作用是指定后处理对应的相机camera和场景scene。
引入渲染器通道RenderPass.js
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass';
创建renderPass渲染器通道
// 创建一个渲染器通道,场景和相机作为参数
const renderPass = new RenderPass(scene, camera);
// 给EffectComposer添加一个渲染器通道
composer.addPass(renderPass);
OutlinePass高亮通道
OutlinePass可以给指定的某个模型对象添加一个高亮发光描边效果。
引入OutlinePass.js。
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass';
创建OutlinePass通道,并把创建好的OutlinePass通道添加到后处理composer中。
// 模型边缘发光通道
const v2 = new THREE.Vector2(width, height);
// OutlinePass第一个参数的尺寸和canvas画布保持一致
const outlinePass = new OutlinePass(v2, scene, camera);
// 将OutlinePass通道添加到后处理composer中
composer.addPass(outlinePass);
OutlinePass属性.selectedObjects。
给three.js场景中模型添加闪烁效果,如果有多个模型的话,可以通过OutlinePass的选择对象属性.selectedObjects设置。
// 一个模型对象
outlinePass.selectedObjects = [mesh];
// 多个模型对象
outlinePass.selectedObjects = [mesh1,mesh2,group];
OutlinePass描边样式
OutlinePass有很多控制高亮外边框样式的属性,下面介绍几个比较常用的属性。
outlinePass.visibleEdgeColor.set(0x00FF00); // 可见边框颜色
outlinePass.edgeStrength = 5; // 高亮描边发光强度
outlinePass.edgeGlow = 0.5; // 光晕[0,1]
outlinePass.edgeThickness = 3;// 高亮发光描边厚度(边缘宽度)
outlinePass.pulsePeriod = 2; // 模型闪烁频率控制
描边颜色
.visibleEdgeColor设置模型边缘高亮描边显示颜色(修改OutlinePass默认边框效果),默认白色。
// 模型边缘高亮边框颜色,默认白色
outlinePass.visibleEdgeColor.set(0x00FF00);
.hiddenEdgeColor设置隐藏边框的颜色,当物体不在屏幕内时显示的颜色,默认白色。
outlinePass.hiddenEdgeColor.set(0x00FF00);// 隐藏边框的颜色
描边厚度.edgeThickness
设置轮廓边缘描边厚度,默认值1。
outlinePass.edgeThickness = 3;
描边亮度.edgeStrength
设置高亮描边的发光强度,默认值3。
outlinePass.edgeStrength = 5;
光晕强度.edgeGlow
增强或减弱物体边缘的光晕效果,值的范围为0-1之间,0表示没有光晕,1表示最大光晕强度。
outlinePass.edgeGlow = 0.5;
描边闪烁.pulsePeriod
控制描边的闪烁频率,默认0不闪烁。
outlinePass.pulsePeriod = 2;
OutlinePass属性.renderToScreen
用于控制是否将渲染结果输出到屏幕上,当设置为true时,OutlinePass会将渲染结果直接输出到屏幕上,使得高亮效果立即显示在用户界面上。
outlinePass.renderToScreen = true;
循环渲染
渲染循环中后处理EffectComposer执行.render(),会调用webgl渲染器执行.render(),也就是说renderer.render(scene, camera)不用再执行。
function renderFn() {
composer.render();
requestAnimationFrame(renderFn);
}
gltf后处理颜色异常(伽马校正)
加载gltf模型如果出现颜色偏差,需要设置renderer.outputEncoding解决。如果你使用threejs后处理功能EffectComposer,renderer.outputEncoding会无效,自然会出现颜色偏差。
renderer.outputEncoding = THREE.sRGBEncoding;
GammaCorrectionShader.js的功能就是进行伽马校正,具体点说就是可以用来解决gltf模型后处理时候,颜色偏差的问题。
// 伽马校正后处理Shader
import {GammaCorrectionShader} from 'three/addons/shaders/GammaCorrectionShader.js';
引入ShaderPass.js,使用后处理Shader创建后处理通道
import {ShaderPass} from 'three/addons/postprocessing/ShaderPass.js';
threejs并没有直接提供伽马校正的后处理通道,提供了一个伽马校正的Shader对象GammaCorrectionShader,这时候可以把Shader对象作为ShaderPass的参数创建一个通道。
// 创建伽马校正通道
const gammaPass= new ShaderPass(GammaCorrectionShader);
composer.addPass(gammaPass);
高亮方法封装
/**
* 模型移入高亮
* @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(0x00FF00); // 可见边框颜色
outlinePass.hiddenEdgeColor.set(0x00FF00); // 隐藏边框颜色
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 gammaPass = new ShaderPass(GammaCorrectionShader);
gammaPass.renderToScreen = true;
composer.addPass(gammaPass);
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();
} 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;