当前位置: 首页 > wzjs >正文

甘肃省住房城乡建设厅网站东道设计公司招聘

甘肃省住房城乡建设厅网站,东道设计公司招聘,网站建设行业努力都看不到效果,怎么用PHP做网站留言板本文介绍如何在 React Three Fiber(R3F)框架中,为 3D 模型添加 “点击扩散波” 交互效果 —— 当用户点击模型表面时,从点击位置向外扩散多层彩色光圈,增强场景的交互反馈和视觉吸引力。我们将基于 CityModel 组件的实…

本文介绍如何在 React Three Fiber(R3F)框架中,为 3D 模型添加 “点击扩散波” 交互效果 —— 当用户点击模型表面时,从点击位置向外扩散多层彩色光圈,增强场景的交互反馈和视觉吸引力。我们将基于 CityModel 组件的实现,拆解点击检测、3D 坐标获取、扩散动画等核心技术点,用通俗易懂的方式讲解实现思路。

本文基于前文介绍的如何生成光波基础上,在这个博客中我介绍了如何生成光波

用 React Three Fiber 实现 3D 城市模型的扩散光圈特效-CSDN博客

一、效果概述

在 3D 城市模型(CityModel 组件)中,实现以下交互效果:

  • 用户用鼠标左键点击模型任意位置时,触发扩散波动画
  • 扩散波从点击位置出发,向外围呈环形扩散
  • 包含多层不同颜色的光圈(暖红→橙黄→浅蓝),每层光圈有不同的扩散速度和范围
  • 光圈随扩散逐渐变淡,最终消失

这种效果可用于:

  • 3D 场景中的交互反馈(如点击选中、位置标记)
  • 模拟信号传播、能量扩散等业务场景
  • 增强用户操作的视觉引导

二、核心实现步骤

1. 点击检测:判断用户是否点击了 3D 模型

要实现点击交互,需使用 Three.js 的 Raycaster(射线检测器),从相机发射 “射线”,检测是否与模型相交。

核心代码片段

// CityModel 组件中处理点击事件
const handleClick = (event: MouseEvent) => {// 仅响应左键点击if (event.button !== 0) return;// 1. 将屏幕坐标转换为 Three.js 标准化设备坐标(NDC)pointer.current.x = (event.clientX / window.innerWidth) * 2 - 1;pointer.current.y = -(event.clientY / window.innerHeight) * 2 + 1;// 2. 从相机发射射线,检测与模型的交点raycaster.current.setFromCamera(pointer.current, camera);const intersects = raycaster.current.intersectObject(modelRef.current, true);// 3. 如果点击到模型,触发扩散波if (intersects.length > 0) {// 获取点击位置的 3D 坐标const clickPosition = intersects[0].point.clone();setClickPosition(clickPosition);// 激活扩散波setIsApertureActive(true);// 1秒后关闭,允许再次触发setTimeout(() => setIsApertureActive(false), 1000);}
};

技术解析

  • 屏幕坐标(像素)需转换为标准化设备坐标(范围 -1 到 1),才能被 Three.js 识别
  • Raycaster 模拟 “视线”,intersectObject 方法检测射线是否与模型相交
  • intersects[0].point 是射线与模型表面的交点(即点击的 3D 位置),通过 setClickPosition 保存

2. 扩散波载体:用圆柱几何体模拟光圈

扩散波的视觉载体是 DiffuseAperture 组件,本质是一个 “空心圆柱”:

  • 用 CylinderGeometry 创建圆柱,设置 openEnded: true 隐藏上下底面,仅保留侧面
  • 通过动态调整圆柱的半径(大小)和透明度,实现 “扩散消失” 效果

核心原理

// DiffuseAperture 组件的几何体定义(简化版)
<cylinderGeometryargs={[initialRadius, // 初始半径initialRadius, // 底部半径(与顶部相同,保证是正圆)height, // 圆柱高度(光圈厚度)64, // 径向分段数(数值越大,光圈边缘越平滑)1, // 高度分段数true, // 开口(无上下底,仅保留侧面)]}
/>

通俗解释:就像用一张纸条卷成空心圆环,剪掉上下两个圆形底面,只剩中间的环形侧面 —— 这个侧面就是我们看到的 “光圈”。

3. 扩散动画:让光圈动起来

扩散波的 “动” 包含两个核心变化:

  • 半径增大:从初始大小逐渐变大(扩散)
  • 透明度降低:从清晰逐渐变淡(消失)

动画逻辑代码(DiffuseAperture 组件内部):

// 每帧更新动画状态
useFrame((_, delta) => {if (!isActive) return;// 1. 半径随时间增大(扩散)currentRadius += expandSpeed * delta;meshRef.current.scale.set(currentRadius, 1, currentRadius);// 2. 透明度随时间降低(消失)currentOpacity -= fadeSpeed * delta;materialRef.current.opacity = Math.max(currentOpacity, 0);// 3. 超出最大范围后重置if (currentRadius > maxRadius || currentOpacity <= 0) {resetAperture(); // 重置为初始状态}
});

参数作用

  • expandSpeed:控制扩散速度(值越大,扩散越快)
  • fadeSpeed:控制淡出速度(值越大,消失越快)
  • maxRadius:控制最大扩散范围

4. 多层叠加:增强视觉层次感

通过同时渲染多个参数不同的 DiffuseAperture 组件,形成多层扩散波:

// CityModel 组件中渲染多层扩散波
{isApertureActive && (<>{/* 内层:暖红色,扩散慢,范围小 */}<DiffuseAperturecolor="#ff6b3b"initialRadius={0.1}maxRadius={15}expandSpeed={2}fadeSpeed={0.1}position={clickPosition} // 定位到点击位置/>{/* 中层:橙黄色,速度中等 */}<DiffuseAperturecolor="#ffc154"initialRadius={0.2}maxRadius={18}expandSpeed={2.5}fadeSpeed={0.7}position={clickPosition}/>{/* 外层:浅蓝色,扩散快,范围大 */}<DiffuseAperturecolor="#609bdf"initialRadius={0.3}maxRadius={22}expandSpeed={3}fadeSpeed={0.8}position={clickPosition}/></>
)}

层次感设计

  • 颜色:从暖色调(内)到冷色调(外),视觉上有区分度
  • 速度:外层比内层扩散快,避免重叠
  • 范围:外层比内层扩散得更远,形成 “波纹” 效果

5. 位置同步:让光圈从点击处出发

通过 position 属性,将扩散波定位到用户点击的 3D 位置:

// 在 CityModel 中渲染扩散波时传递位置
<DiffuseApertureposition={clickPosition} // 点击位置的 3D 坐标// 其他参数...
/>

关键点clickPosition 是通过第一步的射线检测获取的 3D 坐标,确保光圈 “从点击处冒出”。

 6.光波完整代码

 核心通过isActive控制是否扩散,通过position动态更新光波的位置。

import { useFrame } from '@react-three/fiber'
import * as THREE from 'three'
import { useRef, useMemo, useState, useEffect } from 'react'// 扩散光圈组件
export const DiffuseAperture = ({color = ' 0x4C8BF5', // 光圈颜色initialRadius = 0.5, // 初始半径maxRadius = 10, // 最大扩散半径expandSpeed = 2, // 扩散速度(半径增长速率)fadeSpeed = 0.8, // 淡出速度(透明度降低速率)textureUrl, // 侧面纹理贴图URL(可选)height = 1.5, // 从 0.1 增大到 1.5(根据场景比例调整)isActive = false, // 控制是否激活扩散position = new THREE.Vector3(0, 0, 0), // 初始位置
}: {color?: stringinitialRadius?: numbermaxRadius?: numberexpandSpeed?: numberfadeSpeed?: numberheight?: numbertextureUrl?: stringisActive?: boolean;position?: THREE.Vector3;
}) => {const apertureRef = useRef<THREE.Mesh>(null)const radiusRef = useRef(initialRadius) // 跟踪当前半径const opacityRef = useRef(1) // 跟踪当前透明度const [isExpanding, setIsExpanding] = useState(isActive);// 监听 isActive 变化,控制扩散状态useEffect(() => {if (isActive) {setIsExpanding(true);radiusRef.current = initialRadius;opacityRef.current = 1;}}, [isActive]);// 创建圆柱侧面材质(带纹理支持)const material = useMemo(() => {const textureLoader = new THREE.TextureLoader()const materialParams: THREE.MeshBasicMaterialParameters = {color: new THREE.Color(color),transparent: true, // 启用透明度side: THREE.DoubleSide, // 确保侧面可见}// 若提供纹理URL,加载纹理并应用if (textureUrl) {const texture = textureLoader.load(textureUrl)materialParams.map = texture}return new THREE.MeshBasicMaterial(materialParams)}, [color, textureUrl])// 每帧更新圆柱状态(半径增大+透明度降低)useFrame(() => {if (!apertureRef.current || !isExpanding) return;// 1. 更新半径(逐渐增大)radiusRef.current += expandSpeed * 0.016 // 基于帧率的平滑增长apertureRef.current.scale.set(radiusRef.current, // X轴缩放(控制半径)1, // Y轴不缩放(保持高度)radiusRef.current, // Z轴缩放(控制半径))// 2. 更新透明度(逐渐降低)opacityRef.current -= fadeSpeed * 0.016material.opacity = Math.max(opacityRef.current, 0) // 不小于0// 3. 当完全透明或超出最大半径时,重置状态(循环扩散)if (radiusRef.current > maxRadius || opacityRef.current <= 0) {setIsExpanding(false);}})return (<mesh ref={apertureRef} position={position}>{/* 圆柱几何体:顶面和底面隐藏,仅保留侧面 */}<cylinderGeometryargs={[initialRadius, // 顶部半径initialRadius, // 底部半径(与顶部相同,确保是正圆柱)height, // 圆柱高度(厚度)64, // 径向分段数(越高越平滑)1, // 高度分段数true, // 开口(无顶面和底面)]}/><primitive object={material} /></mesh>)
}

三、在 CityModel 中集成的完整逻辑

  1. 初始化模型:加载 3D 模型,计算包围盒并居中,设置相机位置
  2. 绑定事件:在 useEffect 中绑定鼠标点击事件 handleClick
  3. 状态管理:用 isApertureActive 控制扩散波的显示 / 隐藏,clickPosition 存储点击位置
  4. 条件渲染:当 isApertureActive 为 true 时,渲染三层 DiffuseAperture 组件,位置设为 clickPosition

 通过setTimeout让光波持续1秒,并设置状态未非活跃,让光波消失。

import { useGLTF } from '@react-three/drei'
import { useThree } from '@react-three/fiber'
import { useEffect, useRef, useState } from 'react'
import * as THREE from 'three'
import { useModelManager } from '../../../utils/viewHelper/viewContext'
import { DiffuseAperture } from '../../WaveEffect'export const CityModel = ({ url }: { url: string }) => {const { scene } = useGLTF(url)const modelRef = useRef<THREE.Group>(null)const helper = useModelManager()const { camera } = useThree()const raycaster = useRef(new THREE.Raycaster())const pointer = useRef(new THREE.Vector2())// 控制光圈激活状态const [isApertureActive, setIsApertureActive] = useState(false)const [clickPosition, setClickPosition] = useState<THREE.Vector3>(new THREE.Vector3(),)// 存储所有创建的边缘线对象const edgeLines = useRef<Map<string, THREE.LineSegments>>(new Map())// 绑定点击事件useEffect(() => {window.addEventListener('click', handleClick)return () => window.removeEventListener('click', handleClick)}, [])// 添加边缘高亮效果const addHighlight = (object: THREE.Mesh) => {if (!object.geometry) return// 创建边缘几何体const geometry = new THREE.EdgesGeometry(object.geometry)// 创建边缘线材质const material = new THREE.LineBasicMaterial({color: 0x4c8bf5, // 蓝色边缘linewidth: 2, // 线宽})// 创建边缘线对象const line = new THREE.LineSegments(geometry, material)line.name = 'surroundLine'// 复制原始网格的变换line.position.copy(object.position)line.rotation.copy(object.rotation)line.scale.copy(object.scale)// 设置为模型的子对象,确保跟随模型变换object.add(line)edgeLines.current.set(object.uuid, line)}// 处理点击事件const handleClick = (event: MouseEvent) => {if (event.button !== 0) return// 计算点击位置的标准化设备坐标pointer.current.x = (event.clientX / window.innerWidth) * 2 - 1pointer.current.y = -(event.clientY / window.innerHeight) * 2 + 1// 执行射线检测raycaster.current.setFromCamera(pointer.current, camera)const intersects = raycaster.current.intersectObject(modelRef.current, true)// 如果点击到模型,触发扩散效果if (intersects.length > 0) {// 记录点击位置(这里简化为模型中心,也可以用 intersects[0].point)setIsApertureActive(true)const clickPosition = intersects[0].point.clone()setClickPosition(clickPosition)// 300ms后重置激活状态,允许再次触发setTimeout(() => setIsApertureActive(false), 1000)}}// 模型加载后初始化useEffect(() => {if (!modelRef.current) returnaddModel()const box = new THREE.Box3().setFromObject(modelRef.current)const center = new THREE.Vector3()box.getCenter(center)const size = new THREE.Vector3()box.getSize(size)// 2. 将模型中心移到世界原点(居中)modelRef.current.position.sub(new THREE.Vector3(center.x, 0, center.z)) // 反向移动模型,使其中心对齐原点const maxDim = Math.max(size.x, size.y, size.z)const fov = 100const cameraZ = Math.abs(maxDim / 2 / Math.tan((Math.PI * fov) / 360))camera.position.set(0, maxDim * 0.3, cameraZ * 1)camera.lookAt(0, 0, 0)// 遍历模型设置通用属性并标记可交互modelRef.current.traverse((child) => {if (child instanceof THREE.Mesh) {child.castShadow = truechild.receiveShadow = truechild.material.transparent = true// 标记为可交互(后续可通过此属性过滤)child.userData.interactive = truechild.material.color.setStyle('#040912')addHighlight(child)// 保存原始材质(用于后续恢复或高亮逻辑)if (!child.userData.baseMaterial) {child.userData.baseMaterial = child.material // 存储原始材质}}})}, [modelRef.current])// 添加模型到管理器const addModel = () => {if (modelRef.current) {helper.addModel({id: '模型1',name: '模型1',url: url,model: modelRef.current,})}}return (<><primitive object={scene} ref={modelRef} />{/* 扩散光圈:位于模型中心,与模型平面平行 */}{/* 内层光圈:暖红色系,扩散范围最小,亮度最高 */}{ isApertureActive &&<><DiffuseAperturecolor="#ff6b3b" // 内层暖红(鲜艳)initialRadius={0.1}maxRadius={15} // 最小扩散范围expandSpeed={2} // 中等扩散速度fadeSpeed={0.1} // 较慢淡出(停留更久)height={0.01}isActive={isApertureActive}position={clickPosition}/>{/* 中层光圈:橙黄色系,衔接内外层 */}<DiffuseAperturecolor="#ffc154" // 中层橙黄(过渡色)initialRadius={0.2}maxRadius={18} // 中等扩散范围expandSpeed={2.5} // 稍快于内层fadeSpeed={0.7} // 中等淡出速度height={0.01}isActive={isApertureActive}position={clickPosition}/>{/* 外层光圈:蓝紫色系,扩散范围最大,亮度最低 */}<DiffuseAperturecolor="#609bdf" // 外层浅蓝(冷色)initialRadius={0.3}maxRadius={22} // 最大扩散范围expandSpeed={3} // 最快扩散速度fadeSpeed={0.8} // 最快淡出(快速消失)height={0.01}isActive={isApertureActive}position={clickPosition}/></>}</>)
}

总结

实现 3D 模型点击扩散波效果的核心步骤:

  1. 用 Raycaster 检测点击,获取 3D 位置
  2. 用空心圆柱(CylinderGeometry)作为光圈载体
  3. 通过 useFrame 逐帧更新半径和透明度,实现扩散消失动画
  4. 叠加多层不同参数的光圈,增强视觉层次

这种效果充分利用了 Three.js 的几何变换和着色器能力,结合 React 的状态管理,实现了流畅的 3D 交互体验。掌握这些技巧后,可扩展出更复杂的交互效果,如路径动画、区域高亮等。


文章转载自:

http://qukQHUQB.tnzwm.cn
http://SQ6f7pqW.tnzwm.cn
http://Kjpozh8D.tnzwm.cn
http://rhdvIxPB.tnzwm.cn
http://bPc8V14u.tnzwm.cn
http://YdtouuJW.tnzwm.cn
http://5VbrjFiz.tnzwm.cn
http://zXQfFKgr.tnzwm.cn
http://x8RrWr1T.tnzwm.cn
http://qVvUEkQu.tnzwm.cn
http://qfcfwQ3H.tnzwm.cn
http://NYA5ir3M.tnzwm.cn
http://UuVucXeZ.tnzwm.cn
http://SaOB0nt8.tnzwm.cn
http://Dg1YIqt0.tnzwm.cn
http://3gUpJ1Nq.tnzwm.cn
http://lpDeOOMm.tnzwm.cn
http://DMV26W8M.tnzwm.cn
http://kTSNrrXp.tnzwm.cn
http://QRJW2INP.tnzwm.cn
http://Gcozg2IV.tnzwm.cn
http://nMeQX5wb.tnzwm.cn
http://nxoQUrpX.tnzwm.cn
http://Y2Zo6xxL.tnzwm.cn
http://1qkljM86.tnzwm.cn
http://NDcm4L5z.tnzwm.cn
http://6DV3YP3L.tnzwm.cn
http://UmPTzOjp.tnzwm.cn
http://FPmTO8s8.tnzwm.cn
http://pewV7sBs.tnzwm.cn
http://www.dtcms.com/wzjs/667680.html

相关文章:

  • 公司网站制作北京那家公司好手机软件开发教程视频
  • 模板网站的域名是什么意思哪家公司网站做得好
  • 建设一下网站要求提供源码网站发布到ftp
  • 网站中竖导航栏怎么做手机版 演示 网站 触摸
  • 网站产品原型图免费咨询病情
  • 国外网站建设企业无锡网站的建设
  • 百合网网站建设与策划h5 服装网站模板
  • 百度做网站电话多少搜索关键词可以过得网站
  • 广州信科做网站网站买卖需要注意什么
  • 廊坊网站建设兼职设计有限公司
  • 淘宝流量网站济南做企业网站的公司
  • 广州网站建设泸州wordpress删除不了插件
  • 网站建设彩票小企业网站建设地点
  • 会议网站建设方案wordpress无法访问站点
  • 关于网站建设的广告词合肥房产信息网官网
  • 做网站服务好在家怎么提升学历
  • 制作网站搭建网站项目怎么样教育学会网站建设项目
  • 找公司做网站需要注意什么上海医疗器械网站前置审批
  • 网站建议怎么写微商城网站建设效果
  • 网站运营这么做门户网站如何做谷歌seo
  • 360浏览器免费网站邢台123信息网
  • 网站建设 风险防控电商创客网站建设方案
  • 河南科兴建设有限公司网站漫蛙漫画网页版链接
  • 旅游网站建设的功能做图片为主的网站对服务器的要求
  • 毕设 网站开发百度百家官网入口
  • 响应页手机网站源码网站建设空间主机的选择
  • asp 课程教学网站开发做网站字体规范
  • 怀化网站优化公司有哪些开放平台api
  • 怎么建网站?怎么在网上做网站
  • 做资讯网站怎么挣钱免费代理加盟好项目