稀土掘金技术社区 2024年10月30日
threejs渲染高级感可视化风力发电车模型
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文介绍使用Three.js开发风力发电机物联可视化系统,包括着色器效果、多种动画、模型材质问题及相关解决方法,还涉及贝塞尔曲线、齿轮动画等内容,文末提供源码及相关案例链接。

🎨着色器效果:给模型增加光感的动态光影,通过创建顶点着色器和片元着色器实现,uniform声明变量u_center可在render方法中动态修改中心位置,实现动态光效,且着色器中有参数可自定义修改。

🔪切割动画:使用数学库平面THREE.Plane和属性constant,通过修改constant值实现动画,从模型的box3包围盒的min值至max值做补间动画,图中还添加了切割线辅助线。

💡模型材质问题:风车模型开启transparent=true时,计算透明度深度会出现问题,需设置depthWrite = true,开启深度缓存区,renderOrder = -1,使透明对象和不透明对象相对独立渲染。

📈自定义动画贝塞尔曲线:使用tweenjs内置运动曲线,也可通过CubicBezier类自己设置动画的贝塞尔曲线,控制动画执行曲线,通过四个关键点位信息绘制三次贝塞尔曲线。

⚙️齿轮动画:模型中自带动画,源码中有包含多种功能的动画播放类方法HandleAnimation,在render中调用相关方法实现动画播放。

原创 孙_华鹏 2024-10-30 08:30 重庆

点击关注公众号,“技术干货” 及时达!

点击关注公众号,“技术干货” 及时达!



本文使用threejs开发一款风力发电机物联可视化系统,包含着色器效果、动画、补间动画和开发过程中使用模型材质遇到的问题,内含大量gif效果图,

视频讲解及源码见文末

技术栈

一镜到底动画

一镜到底 (1).gif

切割动画

切割动画.gif

线稿动画

线稿动画.gif

外壳透明度动画

外壳透明度动画.gif

展开齿轮动画

展开齿轮动画.gif

发光线条动画

发光线条.gif

着色器

文中用到一个着色器,就是给模型增加光感的动态光影

创建顶点着色器 vertexShader:

varying vec2 vUv;void main() {    vUv = uv;    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);}

创建片元着色器 vertexShader:

varying vec2 vUv;uniform vec2 u_center; // 添加这一行  
void main() { // 泡泡颜色 vec3 bubbleColor = vec3(0.9, 0.9, 0.9); // 乳白色 // 泡泡中心位置 vec2 center = u_center; // 计算当前像素到泡泡中心的距离 float distanceToCenter = distance(vUv, center); // 计算透明度,可以根据实际需要调整 float alpha = smoothstep(0.1, 0.0, distanceToCenter);
gl_FragColor = vec4(bubbleColor, alpha);

创建着色器材质  bubbleMaterial

export const bubbleMaterial = new THREE.ShaderMaterial({    vertexShader: vertexShader,    fragmentShader: fragmentShader,    transparent: true, // 开启透明    depthTest: true, // 开启深度测试    depthWrite: false, // 不写入深度缓冲    uniforms: {        u_center: { value: new THREE.Vector2(0.3, 0.3) } // 添加这一行      },});

从代码中可以看到 uniform声明了一个变量u_center,目的是为了在render方法中动态修改中心位置,从而实现动态光效的效果,

具体引用 render 方法中

 // 更新中心位置(例如,每一帧都改变)      let t = performance.now() * 0.001;     bubbleMaterial.uniforms.u_center.value.x = Math.sin(t) * 0.5 + 0.5; // x 位置基于时间变化      bubbleMaterial.uniforms.u_center.value.y = Math.cos(t) * 0.5 + 0.5; // y 位置基于时间变化

官网案例 # Uniform,详细介绍了uniform的使用方法,支持通过变量对着色器材质中的属性进行改变

光影着色器.gif

从模型上可能看不出什么,下面的图是在一个圆球上加的这个效果

光影着色器-球体.gif

着色器中有几个参数可以自定义也可以自己修改,float alpha = smoothstep(0.6, 0.0, distanceToCenter);中的smoothstep 是一个常用的函数,用于在两个值之间进行平滑插值。具体来说,smoothstep(edge0, edge1, x) 函数会计算 x 在 edge0 和 edge1 之间的平滑过渡值。当 x 小于 edge0 时,返回值为 0;当 x 大于 edge1 时,返回值为 1;而当 x 在 edge0 和 edge1 之间时,它返回一个在 0 和 1 之间的平滑过渡值。

切割动画

切割动画使用的是数学库平面THREE.Plane和属性constant,通过修改constant值即可实现动画,从normal法向量起至constant的距离为可展示内容。

从原点到平面的有符号距离。默认值为 0.

constant取模型的box3包围盒的min值,至max值做补间动画,以下是代码示意

const wind = windGltf.sceneconst boxInfo = wind.userData.box3Info;
const max = boxInfo.worldPosition.z + boxInfo.max.zconst min = boxInfo.worldPosition.z + boxInfo.min.z let tween = new TWEEN.Tween({ d: min - 0.2 }) .to({ d: max + 0.1 }, 1000 * 2) .start() .onUpdate(({ d }) => { clippingPlane.constant = d })

详看切割效果图


切割动画.gif


图中添加了切割线的辅助线,可以通过右侧的操作面板显示或隐藏。

模型材质需要注意的问题

由于齿轮在风车的内容部,并且风车模型开启了transparent=true,那么计算透明度深度就会出现问题,首先要设置 depthWrite = true,开启深度缓存区,renderOrder = -1

这个值将使得scene graph(场景图)中默认的的渲染顺序被覆盖, 即使不透明对象和透明对象保持独立顺序。渲染顺序是由低到高来排序的,默认值为0

threejs的透明材质渲染和不透明材质渲染的时候,会互相影响,而调整renderOrder顺序则可以让透明对象和不透明对象相对独立的渲染。

depthWrite对比

depthwrite对比.jpeg

renderOrder 对比

renderOrder 对比.jpeg

自定义动画贝塞尔曲线

众所周知,贝塞尔曲线通常用于调整关键帧动画,创建平滑的、曲线的运动路径。本文中使用的tweenjs就内置了众多的运动曲线easing(easingFunction?: EasingFunction): this;类型,虽然有很多内置,但是毕竟需求是无限的,接下来介绍的方法就是可以自己设置动画的贝塞尔曲线,来控制动画的执行曲线。

具体使用

// 使用示例const controlPoints = [    { x: 0 },    { x: 0.5 },    { x: 2 },    { x: 1 }];const cubicBezier = new CubicBezier(controlPoints[0], controlPoints[1], controlPoints[2], controlPoints[3]);let tween = new TWEEN.Tween(edgeLineGroup.scale).to(windGltf.scene.scale.clone().set(1, 1, 1), 1000 * 2).easing((t) => {    return cubicBezier.get(t).x}).start().onComplete(() => {    lineOpacityAction(0.3)    res({ tween })})

在tween的easing的回调中添加一个方法,方法中调用了cubicBezier,下面就介绍一下这个方法

源码

[p0] – 起点  [p1] – 第一个控制点  [p2] – 第二个控制点  [p3] – 终点
export class CubicBezier {    private p0: { x: number; };    private p1: { x: number; };    private p2: { x: number; };    private p3: { x: number; };
constructor(p0: { x: number; }, p1: { x: number; }, p2: { x: number; }, p3: { x: number; }) { this.p0 = p0; this.p1 = p1; this.p2 = p2; this.p3 = p3; }
get(t: number): { x: number; } { const p0 = this.p0; const p1 = this.p1; const p2 = this.p2; const p3 = this.p3;
const mt = 1 - t; const mt2 = mt * mt; const mt3 = mt2 * mt; const t2 = t * t; const t3 = t2 * t;
const x = mt3 * p0.x + 3 * mt2 * t * p1.x + 3 * mt * t2 * p2.x + t3 * p3.x;
return { x }; }}

CubicBezier支持get方法,通过四个关键点位信息,绘制三次贝塞尔曲线,参数t在0到1之间变化,当t从0变化到1时,曲线上的点从p0平滑地过渡到p3

mt = 1 - t;:这是t的补数(1减去t)。mt2 = mt * mt; 和 mt3 = mt2 * mt;:计算mt的平方和立方。t2 = t * t; 和 t3 = t2 * t;:计算t的平方和立方。

这是通过取四个点的x坐标的加权和来完成的,其中权重是基于t的幂的。具体来说,p0的权重是(1-t)^3p1的权重是3 * (1-t)^2 * tp2的权重是3 * (1-t) * t^2,而p3的权重是t^3

{ x: 0 },{ x: 0.5 },{ x: 2 },{ x: 1 } 这组数据形成的曲线效果是由start参数到end的两倍参数再到end参数

具体效果如下

贝塞尔曲线.gif

齿轮

齿轮动画

模型中自带动画

齿轮动画数据.jpeg

源码中有一整套的动画播放类方法,HandleAnimation,其中功能包含播放训话动画,切换动画,播放一次动画,绘制骨骼,镜头跟随等功能。

具体使用方法:

   // 齿轮动画     /**     *      * @param model 动画模型     * @param animations 动画合集     */    motorAnimation = new HandleAnimation(motorGltf.scene, motorGltf.animations)    // 播放动画 take 001 是默认动画名称    motorAnimation.play('Take 001')

在render中调用

motorAnimation && motorAnimation.upDate()

齿轮展开(补间动画)

补间动画在齿轮展开时调用,使用的tweenjs,这里讲一下定位运动后的模型位置,使用# 变换控制器(TransformControls),代码中有封装好的完整的使用方法,在TransformControls.ts中,包含同时存在轨道控制器时与变换控制器对场景操作冲突时的处理。

使用方法:

/** * @param mesh 受控模型 * @param draggingChangedCallback  操控回调 */ TransformControls(mesh, ()=>{    console.log(mesh.position)})
齿轮展开定位.jpeg

齿轮发光

发光效果方法封装在utls/index.ts中的unreal方法,使用的是threejs提供的虚幻发光通道RenderPass,UnrealBloomPass,以及合成器EffectComposer,方法接受参数如下


// params 默认参数const createParams = { threshold: 0, strength: 0.972, // 强度 radius: 0.21,// 半径 exposure: 1.55 // 扩散};
/** * * @param scene 渲染场景 * @param camera 镜头 * @param renderer 渲染器 * @param width 需要发光位置的宽度 * @param height 发光位置的高度 * @param params 发光参数 * @returns */

调用方法如下:


const { finalComposer: F, bloomComposer: B, renderScene: R, bloomPass: BP } = unreal(scene, camera, renderer, width, height, params) finalComposer = F bloomComposer = B renderScene = R bloomPass = BP bloomPass.threshold = 0

除了调用方法还有一些需要调整的地方,比如发光时模型什么材质,又或者不发光时又是什么材质,这里需要单独定义,并在render渲染函数中调用

 if (guiParams.isLight) {        if (bloomComposer) {            scene.traverse(darkenNonBloomed.bind(this));            bloomComposer.render();        }        if (finalComposer) {            scene.traverse(restoreMaterial.bind(this));            finalComposer.render();        }    }

scene.traverse的回调中,检验模型是否为发光体,再进行材质的更换,这里用的标识是 object.userData.isLighttrue时,判定该物体为发光物体。其他物体则不发光

回调方法

function darkenNonBloomed(obj: THREE.Mesh) {    if (bloomLayer) {        if (!obj.userData.isLight && bloomLayer.test(obj.layers) === false) {            materials[obj.uuid] = obj.material;            obj.material = darkMaterial;        }    }
}
function restoreMaterial(obj: THREE.Mesh) { if (materials[obj.uuid]) { obj.material = materials[obj.uuid]; // 用于删除没必要的渲染 delete materials[obj.uuid]; }}

再场景的右上角我们新增了几个参数,用来调整线条的发光效果,下面通过动图看一下,图片有点大,请耐心等待加载

调试发光效果.gif

好啦,本篇文章到此,如看源码有不明白的地方,可私信~

最近正在筹备工具库,以上可视化常用的方法都将涵盖在里面

three.js——商场楼宇室内导航系统 内附源码

three.js——可视化高级涡轮效果+警报效果 内附源码

高德地图巡航功能 内附源码

three.js——3d塔防游戏 内附源码

three.js+物理引擎——跨越障碍的汽车 可操作 可演示

百度地图——如何计算地球任意两点之间距离 内附源码

threejs——可视化地球可操作可定位

three.js 专栏

jvideo

源码 :

https://www.aspiringcode.com/content?id=17142274819552&uid=b4bfbecae1b542ebb15f421d4852a362

体验地址:

https://display.aspiringcode.com:8888/html/17142274819552/

点击关注公众号,“技术干货” 及时达!


阅读原文

跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Three.js 风力发电机 可视化系统 着色器 动画
相关文章