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 >
<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" target="_blank" rel="external nofollow" >
</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
}
}