Three 之 three.js (webgl)简单的渲染策略(简单性能优化/主要根据渲染控制 render 渲染调用)

Three 之 three.js (webgl)简单的渲染策略(简单性能优化/主要根据渲染控制 render 渲染调用)

目录

Three 之 three.js (webgl)简单的渲染策略(简单性能优化/主要根据渲染控制 render 渲染调用)

一、简单介绍

二、实现原理

三、注意事项

四、效果预览

五、实现步骤

六、关键代码


一、简单介绍

Three js 开发的一些知识整理,方便后期遇到类似的问题,能够及时查阅使用。

本节介绍, three.js (webgl)渲染策略上进行必要的合理规划 render 的调用,从而性能上的优化,特别是在移动端,避免不必要的屏幕render 刷新,从而避免卡顿和发热情况,如果有不足之处,欢迎指出,或者你有更好的方法,欢迎留言。

three js 相关的性能优化介绍可参见博文:Three 之 three.js (webgl)性能优化、提高帧率的思路/方向整理_仙魁XAN的博客-CSDN博客_webgl 性能

二、实现原理

1、主要原理是在 animation 中 requestAnimationFrame 合理的调用render 次数,来达到渲染对应 cpu 和 gpu 的调用,从而达到性能优化的目的

2、rightNowRefreshRenderOnce 立即渲染一次,即是立即刷新屏幕一次,比例加载模型完成等情况可以立即刷新一次等

3、enableFrameRender/disableFrameRender 持续帧渲染,例如动画,声音等情况需要持续刷新的时候使用等

4、updateStrategyRender 中添加之前在 animation 调用的render 和相关函数、参数即可,保证合理的 render 刷新次数

                requestAnimationFrame( animate );
				simpleRenderStategyTool.updateStrategyRender(()=>{		
					// 旋转效果
					// object.rotation.x += 0.03;
					object.rotation.y += 0.06 ;
					renderer.render( scene, camera );
				})

三、注意事项

1、rightNowRefreshRenderOnce ,其实只要 renderer 函数即可,但是可能有时达不到理想渲染效果,故添加时间渲染,避免一些一次render 没有刷新的情况,这里的时间,可以根据自己需要在调整

四、效果预览

五、实现步骤

1、为了方便学习,这里是基于 Github 代码,进行开发的,大家可以下载官网代码,很多值得学习的案例

GitHub - mrdoob/three.js: JavaScript 3D Library.

gitcode:mirrors / mrdoob / three.js · GitCode

2、创建简单渲染策略工具脚本,来管理渲染

3、在上面的基础上,添加一个 html ,用来实现案例效果,引入相关包 

4、创建基础场景

5、添加 UI 调节描边的参数,调整不同效果

6、完成其他的基础代码编写,运行脚本,效果如下 

六、关键代码

1、TestSimpleRenderStategyTool.html

<!DOCTYPE html>
<html lang="en">
	<head>
		<title>16TestSimpleRenderStategyTool</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<link type="text/css" rel="stylesheet" href="main.css">
	</head>
	<body>

		<!-- Import maps polyfill -->
		<!-- Remove this when import maps will be widely supported -->
		<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>

		<script type="importmap">
			{
				"imports": {
					"three": "../../../build/three.module.js"
				}
			}
		</script>

		<script type="module">
			// 引入 three 基础库
			import * as THREE from 'three';
			// GUI
			import { GUI } from '../../jsm/libs/lil-gui.module.min.js';
			import Stats from '../../jsm/libs/stats.module.js';
			
			import { SimpleRenderStrategyTool } from './RenderStrategy/SimpleRenderStrategyTool.js'

			let camera, renderer, scene;
			let object;
			let simpleRenderStategyTool;
			let stats;
			
			const params = {
				enableFpsRender: false,
				enableRightNowRender: false
			
			};

			init();
			animate();

			function init() {

				// 渲染器
				renderer = new THREE.WebGLRenderer();
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( window.innerWidth, window.innerHeight );
				renderer.setClearColor('#cccccc');
				document.body.appendChild( renderer.domElement );

				// camera
				camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
				camera.position.z = 8;

				// 场景
				scene = new THREE.Scene();

				// 添加物体到场景中
				object = new THREE.Object3D();
				scene.add( object );
				const geometry = new THREE.SphereGeometry( 1, 4, 4 );
				const material = new THREE.MeshPhongMaterial( { color: '#ff3399', flatShading: true } );
				const material2 = new THREE.MeshPhongMaterial( { color: '#880088' } );
				const mesh = new THREE.Mesh( geometry, material );
				mesh.position.set(-2,0,0)
				object.add(mesh)
				const mesh2 = new THREE.Mesh( geometry, material2 );
				mesh2.position.set(2,0,0)
				object.add(mesh2)

				// 添加环境光
				scene.add( new THREE.AmbientLight( 0x222222 ) );

				// 添加方向光
				const light = new THREE.DirectionalLight( 0xffffff );
				light.position.set( 1, 1, 1 );
				scene.add( light );

				// 窗口尺寸变化监听
				window.addEventListener( 'resize', onWindowResize );
				
				simpleRenderStategyTool = new SimpleRenderStrategyTool(new THREE.Clock(),20)
				
				createGUI()
				
				stats = new Stats();
				document.body.appendChild( stats.dom );
				
				// 刷新一次画面
				simpleRenderStategyTool.rightNowRefreshRenderOnce()
				
			}
			
			function createGUI() {
			
				const gui = new GUI( { name: 'simple Render Startegy setting' } );
				gui.add( params, 'enableFpsRender').onChange((value)=>{if(value){
					simpleRenderStategyTool.enableFrameRender()
				}else{
					simpleRenderStategyTool.disableFrameRender()
				}});
				gui.add( params, 'enableRightNowRender' ).onChange((value)=>{
					if(value){
						simpleRenderStategyTool.rightNowRefreshRenderOnce()
					}
				});
			
			}

			function onWindowResize() {

				// camera 更新 aspect
				camera.aspect = window.innerWidth / window.innerHeight;
				camera.updateProjectionMatrix();

				// 渲染器更新大小
				renderer.setSize( window.innerWidth, window.innerHeight );

			}
			

			function animate() {

				requestAnimationFrame( animate );
				simpleRenderStategyTool.updateStrategyRender(()=>{		
					// 旋转效果
					// object.rotation.x += 0.03;
					object.rotation.y += 0.06 ;
					renderer.render( scene, camera );
				})
				// 旋转效果
				// object.rotation.x += 0.005;
				// object.rotation.y += 0.01;
				// renderer.render( scene, camera );
				
				stats.update();
			}

		</script>
	</body>
</html>

2、SimpleRenderStrategyTool.js

/**
 * 简单渲染策略工具
 * 1、立即渲染一次
 * 2、frameRender 帧渲染(动画...)
 */
export class SimpleRenderStrategyTool {
	/**
	 * @param {Object} clock three 中的时钟 Clock
	 * @param {Number} fps 每秒渲染次数(整数)
	 */
	constructor(clock, fps=10){

		//计时器
		this.clock = clock !== null? clock : console.error("SimpleRenderStrategyTool's clock is null, please check and need clock of three")

		this.FPS = fps === null ? 10:fps // 设置每秒屏幕刷新次数(上限 60)
		this.singleFrameTime = 1.0 / this.FPS
		this.timeStamp = 0
		this.isFrameFps = false

		// 更新一次渲染策略
		//调用一次可以渲染时长,根据实际情况调整时长(其实只要 renderer 函数即可,但是可能有时达不到理想渲染效果,故添加时间渲染)
		this.timeOutRenderEvent = null
		// 保证下次进来就立即更新一次渲染
		this.timeDeltaCounter = this.singleFrameTime
		this.timeLength = 100 // 1000 ms
		this.rightNowRefreshRenderOnceEnable = false

	}

	/**
	 * 立即更新一次渲染
	 */
	rightNowRefreshRenderOnce() {
	  //设置为可渲染状态
	  this.rightNowRefreshRenderOnceEnable = true
	  // 保证下次进来就立即更新一次渲染
	  this.timeDeltaCounter = this.singleFrameTime

	  //清除上次的延迟器
	  if (this.timeOutRenderEvent) {
	    clearTimeout(this.timeOutRenderEvent)
	  }

	  this.timeOutRenderEvent = setTimeout( () => {
	    this.rightNowRefreshRenderOnceEnable = false
		// 保证下次进来就立即更新一次渲染
		this.timeDeltaCounter = this.singleFrameTime
	  }, this.timeLength)
	}

	/**
	 * 使能 FPS 渲染
	 */
	enableFrameRender() {
	  this.isFrameFps = true
	}

	/**
	 * 停止 FPS 渲染
	 */
	disableFrameRender() {
	  this.isFrameFps = false
	  this.timeStamp = 0
	}

	/**
	 * 外界 animation 调用的渲染函数
	 * @param {Object} updateRenderFunc
	 */
	updateStrategyRender(updateRenderFunc) {
	  this.frameFpsRender(updateRenderFunc)
	  this.updaterightNowRefreshRender(updateRenderFunc)
	}

	/**
	 * 开启指定帧数的刷新渲染
	 * @param {Func} updateRenderFunc  刷新渲染函数
	 */
	frameFpsRender(updateRenderFunc) {
	  if (this.isFrameFps === true) {
	    const delta = this.clock.getDelta() //获取距离上次请求渲染的时间
	    this.timeStamp += delta
	    if (this.timeStamp > this.singleFrameTime) {
	      if (updateRenderFunc != null) {
	        updateRenderFunc()
	      }
	      // 剩余的时间合并进入下次的判断计算 这里使用取余数是因为 当页页面失去焦点又重新获得焦点的时候,delta数值会非常大, 这个时候就需要
	      this.timeStamp = this.timeStamp % this.singleFrameTime
	    }
	  }
	}

	/**
	 * 立即更新一次渲染
	 * @param {Func} updateRenderFunc
	 */
	updaterightNowRefreshRender(updateRenderFunc) {
	  if (this.rightNowRefreshRenderOnceEnable === true) {
		  const delta = this.clock.getDelta() //获取距离上次请求渲染的时间
		  this.timeDeltaCounter += delta
		  if (this.timeDeltaCounter > this.singleFrameTime) {
		    if (updateRenderFunc != null) {
		      updateRenderFunc()
		    }
		    // 剩余的时间合并进入下次的判断计算 这里使用取余数是因为 当页页面失去焦点又重新获得焦点的时候,delta数值会非常大, 这个时候就需要
		    this.timeDeltaCounter = this.timeDeltaCounter % this.singleFrameTime
		  }
	  }
	}

	/**
	 * 清空渲染策略
	 */
	destroyRenderStrategy() {
	  //清除上次的延迟器
	  if (this.timeOutRenderEvent) {
	    clearTimeout(this.timeOutRenderEvent)
	  }

	  disableFrameRender()
	  this.clock = null
	  this.timeStamp = 0
	  this.isFrameFps = false

	  this.timeOutRenderEvent = null
	  this.timeDeltaCounter = 0
	  this.rightNowRefreshRenderOnceEnable = false
	}
}

猜你喜欢

转载自blog.csdn.net/u014361280/article/details/126917339