threejs——多重场景渲染

2024-02-02 10.11.43.gif

前言

摸鱼时发现了这threejs实现的效果,从效果图中可以看出来,在滚动页面的时候,模型在进入不同的场景,或者说进入不同的页面,渲染模式改变了,下面我们一步一步拆解出这种效果是怎么实现的,首先第一步,先找到一个合适的模型,我是直接下载的这个原地址的飞机模型,我们加入一些自己的思考和实践来实现出这个效果。希望大家能够跟着一起做,效果实现起来很简单,但是过程中遇到的小细节,还是需要好好把握一下,本文在 gitee仓库 有代码,每个节点都有tag,有没讲明白的地方,可以偷看一下代码。

准备工作

模型下载下来是.obj格式的,这里用OBJLoader来加载模型,提取公共方法

```ts // 加载OBJ模型 export function loadObj(url: string) { return new Promise ((resolve, reject) => { new OBJLoader() .load(url, function (object) { resolve(object) }); }) }

```

加载模型

ts // 加载模型 const loadModel = async () => { const model = await loadObj('./model/1405+Plane_1.obj') }

创建场景

场景创建的代码这里就不赘述了,之前的历史文章也都提到很多次,用到的api包括场景Scene,渲染器WebGLRenderer,透视相机PerspectiveCamera,环境光AmbientLight,平行光DirectionalLight,轨道控制器OrbitControls

将加载好的模型,添加到场景中,我们得到如下的效果

2024-02-02 16.32.07.gif

渲染两种不同材质的模型

以已经加载的模型为基础,创建出一份线稿文件,并加载到场景中,创建线稿代码

修改一下loadModel方法,调用创建线稿的方法

```ts // 加载模型 const loadModel = async () => { const model = await loadObj('./model/1405+Plane_1.obj') const line = createLine(model) model.traverse((child) => { if (child.isMesh) { child.material = mat.clone(); } })

/** 模型加载出来以后再渲染场景 */
renderer && renderer.setAnimationLoop(render);// 修改分场景的背景色 方便区分
scene_1.background = new THREE.Color("#ff99cc")scene_1.add(lightGroup)
scene_1.add(model)
line.position.x = 100
scene_1.add(line)

} ```

createLine

ts const createLine = (model) => { const edges = new THREE.EdgesGeometry(model.children[0].geometry, 20); let line = new THREE.LineSegments(edges); return line }

这里线稿的方法写的很粗糙,在之前的文章threejs渲染高级感可视化涡轮模型 中有详细的介绍如何创建不同效果的线稿和如何利用线稿做一些threjs通道方法的应用,感兴趣的同学可以关注一下

这样我们就得到了一个原模型和一个线稿模型

image.png

以上代码地址:v.plane.1.0.1

创建分场景

目前这两个模型是加载在同一个场景scene_1下,所以看到的是两个同时存在的模型,下面将详细讲解如何将两个模型分别放在不同的场景,并且同步进行变换

创建多个场景

进行多个场景创建,要提前了解一下 WebGLRenderer的API裁剪setScissor和裁剪检测setScissorTest, setScissor支持四个参数,可以视作两个对角坐标点的x,y,比如 setScissor(0,0,window,innerWidht,window,innerHeight),前两组坐标视为起点的x和y,后两组视为结束点的x和y的坐标,以这两个点之间连垂直水平的直线组成的矩形区域既视作为裁剪区域

比如下面这张gif图,鼠标移动时,有一个裁剪区域跟随鼠标移动,渲染不同的材质,原理是相同的。

2024-02-02 16.54.29.gif

那么接下来我们要为之前的代码改造一下,首先创建两个场景scene_1scene_2

ts var scene_1 = new THREE.Scene() var scene_2 = new THREE.Scene() 接下来要做一个分屏,浏览器窗口的上半部分为scene1的裁剪区,下半部分为scene2的裁剪区

为renderer 添加setScissorTest属性

ts let renderer if (canvas) { renderer = new THREE.WebGLRenderer({ // 渲染器 ... renderer.setScissorTest(true); }

定义屏幕尺寸

```ts const width = window.innerWidth; const height = window.innerHeight;

```

计算每个分屏的起点和终点

```ts let render = () => { ... renderer.setScissor(0, 0, width, height / 2); renderer.render(scene_1, camera);

renderer.setScissor(0, height / 2, width, height);
renderer.render(scene_2, camera);

}

``` scene1的渲染区域将屏幕左上角到屏幕中间裁剪掉,scene2从屏幕中间到屏幕右下角裁剪掉

将之前创建的线稿模型line添加到scene_1

```ts // 加载模型 const loadModel = async () => { ... scene1.add(line) scene1.add(lightGroup) scene2.add(model) scene2.add(lightGroup)

} ```

于是你得到了下面这个效果

2024-02-02 17.09.18.gif

以上代码地址:v.plane.1.0.2

结合滚动变换场景

回头看看前面文章开头的效果图,在页面滚动的时候,到了某一个节点才渲染线稿,所以裁切区域肯定不是固定的,要监听页面的滚动,为了能让页面能够滚动,将创建两个屏幕那么大的div,再将渲染3d视图的canvas浮动起,去除背景颜色,让后面的页面内容能够显现出来

将背景透明化

要让scene的背景透明化,使用renderer的一个参数即可alpha: true

改造一下创建渲染器的方法,并去掉scene_1scene_2的背景色

/** * 渲染器 */ let renderer if (canvas) { renderer = new THREE.WebGLRenderer({ // 渲染器 alpha: true, ... }) .. }

滚动元素

```html

```

```css * { margin: 0; padding: 0; } /* 将3d视图浮动起来 */

threeMain {

position: fixed;
top: 0;
left: 0;

} body { overflow: hidden auto; } [id^=scene_] { width: 100vw; height: 100vh; margin: auto; }

scene_1 {

background: rgb(255, 243, 189);

}

scene_2 {

background: rgb(30, 28, 20);

} `` 由于页面滚动和轨道控制器冲突,我们使用controls.enabled = false`先暂时将轨道控制器禁用掉

2024-02-02 17.32.47.gif

监听滚动并修改渲染器的裁剪区域

接下来要做的就是渲染器的 裁剪区域跟随滚动位置而改变

在render中获取到#scene_2元素的top值,通过计算,使场景scene_1和场景scene_2在切割的时候以#scene_2的头部位置分割,

性能优化

这里简单提一嘴关于性能方面的问题,setAnimationLoop回调方式在WebGLAnimation.js文件中也是使用requestAnimationFrame实现的,在屏幕刷新时对场景进行绘制的循环,如果项目中没有动画的需求,或者不考虑方法的特殊功能性,可以选择性的不去使用这个方法,像目前这个页面,每次更新都是根据页面的滚动做相应的操作,那完全可以在监听窗口的滚动事件去调用render函数,这样能够保证页面静态时候的不占用过多的浏览器资源

那么我们改造一下代码

loadModal文件

ts // 加载模型 const loadModel = async () => { ... /* * 模型加载出来以后再渲染场景 * 这里将循环调用注释掉 */ // renderer && renderer.setAnimationLoop(render); ... // 在加载完模型后,第一次调用渲染函数,让场景渲染出来 render() } loadModel()

改造render函数

```ts // 获取#scene2dom元素 const scene2dom = document.querySelector('#scene2'); // 监听滚动事件 window.addEventListener('scroll', () => { if (scene2dom) { // 在页面滚动的时候调用render渲染函数 render() } }) let render = () => { ... if (scene2dom) { // 获取#scene2距离屏幕的高度 let topStr = scene2_dom.getBoundingClientRect().top const top = Number(topStr) || 0

// 计算场景切割的交叉点renderer.setScissor(0, 0, width, height - top);renderer.render(scene_1, camera);renderer.setScissor(0, height - top, width, height);renderer.render(scene_2, camera);
}

}

```

现阶段效果

2024-02-02 18.11.34.gif

以上代码地址v.plane.1.0.3

飞行路径

给飞机设计一条飞行路径,在页面滚动的时候,可以让飞机沿轨道飞行。

模拟出一段飞机运动的轨迹,并组合一下数据,通过fetch请求到json的数据

```ts

fetch('./path.json').then((res) => res.json()).then((data) => { pathData = data console.log(data); loadModel() }) ```

image.png

得到大概992条数据,通过计算#scene_2的top值和height的值得到一个百分比,如果从一个滚动屏幕的距离为1 那么top/height就是当前滚动的位置,Math.floor(Math.floor((1 - top / h) * 100) / 100 * pathData.length)计算出当前飞机应该在的位置的索引,继续改造一下render方法

``` ts let render = () => { ... if (scene2dom) { // 获取#scene2距离屏幕的高度 let topStr = scene2_dom.getBoundingClientRect().top const top = Number(topStr) || 0;

const h = height// 获取飞机所在位置的索引const index = Math.floor(Math.floor((1 - top / h) * 100) / 100 * pathData.length);// 取到飞机的位置信息const item = pathData[index]if (item) {const { position, rotation, } = itemconst line = scene_1.getObjectByName("line")// 同时变化model和line的位置和旋转角度if (line && position && rotation) {line.position.copy(new THREE.Vector3(position.x, position.y, position.z))line.rotation.copy(new THREE.Euler(rotation._x, rotation.y, rotation.z, rotation._order))}const model = scene_2.getObjectByName("model")if (model && position && rotation) {model.position.copy(new THREE.Vector3(position.x, position.y, position.z))model.rotation.copy(new THREE.Euler(rotation._x, rotation.y, rotation.z, rotation._order))}}

...

}

} ```

最终效果

2024-02-03 14.59.09.gif

以上代码地址 v.plane.1.0.4

PS

image.png

不知不觉这一年也更新了一些文章,房贷吗的仓库也得到的51个star,感谢大家的支持...

历史文章

# threejs进阶 通过json数据创建立体地图并实现下钻及返回 # threejs基础——判断物体遮挡方案

# threejs开发可视化数字城市效果

# threejs渲染高级感可视化涡轮模型

# 写一个高德地图巡航功能的小DEMO

# three.js 打造游戏小场景(拾取武器、领取任务、刷怪)

# threejs 打造 world.ipanda.com 同款3D首页

# three.js——物理引擎

# three.js——镜头跟踪

# threejs 笔记 03 —— 轨道控制器

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据集标注工具anylabeling解析

最近帮助其他课题组的学姐标注数据集,课题组使用的是anylabeling软件,相比于其他数据标注软件,例如labelme等,anylabeling软件使用时可以选择不同的模型,可以做到在图片上点几个点的轮廓,模型将自动识别出大…

人脸数据集:The Database of Faces (ATT)

参考来源: To evaluate the protection provided by our constructions against inference attacks, we use the AT&T Faces data set [17]. It consists of a total of 400 tightly cropped face images, showing 10 different angles for each of 40 human subjects. E…

在Linux中如何理解页表和进程地址

1、进程地址是进程读取资源的窗口 2、页表决定了进程真实拥有的资源情况 3、合理的对进程地址空间页表进行资源划分,就可以对进程的资源进行分类 这个过程应该如何去理解呢请看下面的图 我们知道程序被加载到进程中,会产生相应的PCB,并且…

css新手教程

css新手教程 课程:14、盒子模型及边框使用_哔哩哔哩_bilibili 一.什么是CSS 1.什么是CSS Cascading Style Sheet 层叠样式表。 CSS:表现(美化网页) 字体,颜色,边距,高度,宽度&am…

通俗易懂理解通道注意力机制(CAM)与空间注意力机制(SAM)

重要说明:本文从网上资料整理而来,仅记录博主学习相关知识点的过程,侵删。 一、参考资料 通道注意力,空间注意力,像素注意力 通道注意力机制和空间注意力机制 视觉 注意力机制——通道注意力、空间注意力、自注意力…

Shell脚本监控进程异常终止并重启

首先介绍一下我现在的需求,我服务器上挂了一个用python编写的kook机器人,但有时候机器人程序会异常终止,不知道什么原因,因此需要监控机器人程序是否有在运行,如果没有就重启机器人程序。 可以写一个Shell脚本&#x…

Excel中将16进制数转化成10进制(有/无符号)

Excel中将16进制数转化成10进制(有/无符号) Excel或者matlab中常用XXX2XXX进行不同进制的转换 16进制转10进制(无符号数):HEX2DEC 16进制转10进制(有符号数): FA46为例&#xff0c…

如何彻底卸载MySQL【可以解决问题】

[序]写在前面 相信很多小伙伴都遇到了以前版本的MySQL没有卸载干净而导致新版本的MySQL无法安装的情况,今天小编带你彻底解决这个令人头痛的问题(本人也有亲身经历!希望能够给大家带来一点点帮助) 注:本文部分图片来自…

shell脚本中的变量,运算符

1.脚本格式 我们一般将shell脚本写在xxx.sh文件中,执行的时候bash/sh xxx.sh 注意文件路径 xxx.sh文件中的第一行为 #!/usr/bin/bash 注代表我们使用的是bin文件夹下的bash解释器(此条为注释语句,不写也可以) 2.echo用法 相当与print 示例1&…

《国色芳华》爆红网络,杨紫的“唐妆”惊艳四座。

♥ 为方便您进行讨论和分享,同时也为能带给您不一样的参与感。请您在阅读本文之前,点击一下“关注”,非常感谢您的支持! 文 |猴哥聊娱乐 编 辑|徐 婷 校 对|侯欢庭 在中国的电视剧市场近几年的趋势中,仙侠剧的热度逐…

Blender使用Rigify和Game Rig Tool基础

做动画需要的几个简要步骤: 1.建模 2.绑定骨骼 3.绘制权重 4.动画 有一个免费的插件可以处理好给引擎用:Game Rig Tool 3.6和4.0版本的 百度网盘 提取码:vju8 1.Rigify是干嘛用的? 》 绑定骨骼 2.Game Rig Tool干嘛用的&#xf…

2017 年全国职业院校技能大赛高职组“信息安全管理与评估”赛项任务书(笔记解析)

1. 网络拓扑图 2. IP 地址规划表 3. 设备初始化信息 阶段一 任务1:网络平台搭建 1 根据网络拓扑图所示,按照 IP 地址参数表,对 WAF 的名称、各接口 IP 地址进 行配置。 主机名称 模式选择:透明模式 IP 地址:匹配参数表 WAF IP 地址 子网掩码 网口列表: eth0 和 eth1 2…