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

three.js+WebGL踩坑经验合集(10.1):镜像问题又一坑——THREE.InstancedMesh的正反面显示问题

本打算写9.3,拿个案例说明polygonOffsetUnits和polygonOffsetFactor这两个参数的计算方法的,但发现其枯燥程度远超作者想象,加上最近项目里又来镜像的bug了,并且这次还跟之前的对象类型不一样。这次是THREE.InstancedMesh。那今天就先让这个事情插个队。

正式开始前,先简单介绍下这种类型的对象。它用于把geometry和material完全一致,仅在位置和颜色有区分的很多个物体合并到同一个批次进行渲染,从而减少drawcall提升性能。webgl的核心api为drawElementsInstanced。我们的项目也用到了,同时也踩到了坑,所以就借着博客给大家分享出来,力求让后人少走弯路。

我们先用最普通的方法弄6个正方形的面片,只是把变换信息用矩阵代替设置position, rotation和scale,便于稍后切换成InstancedMesh。


<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>测试InstancedMesh的镜像问题</title><style>body {margin: 0;overflow: hidden;}</style><!--引入three.js三维引擎--><script src="three.js-master/build/three.js"></script><script src="three.js-master/examples/js/controls/OrbitControls.js"></script>
</head><body><script>/*** 创建场景对象Scene*/var scene = new THREE.Scene();var ambient = new THREE.AmbientLight(0x999999);scene.add(ambient);/*** 创建网格模型*/var geometry = new THREE.PlaneGeometry(100, 100, 1);var material = new THREE.MeshLambertMaterial({color: 0xFF3300, side: THREE.FrontSide});var matrixes = [];for(var j = 0; j < 2; j ++){for(var i = 0; i < 3; i ++){var posX = (i - 1) * 150;var posY = (j - 0.5) * 150;var rotationX = (i - 1) * Math.PI / 5;var rotationY = (j - 0.5) * Math.PI / 5;var rotationXMatrix = new THREE.Matrix4();rotationXMatrix.makeRotationX(rotationX);var rotationYMatrix = new THREE.Matrix4();rotationYMatrix.makeRotationY(rotationY);var matrix = new THREE.Matrix4();matrix.premultiply(rotationXMatrix);matrix.premultiply(rotationYMatrix);matrix.setPosition(posX, posY, 0);matrixes.push(matrix);}}for(let matrix of matrixes){var mesh = new THREE.Mesh(geometry, material);mesh.matrix.copy(matrix);mesh.matrix.decompose(mesh.position, mesh.quaternion, mesh.scale)scene.add(mesh);}/*** 光源设置*///点光源var light = new THREE.DirectionalLight(0xffffff);light.position.set(0, 0, 300); //点光源位置scene.add(light); //点光源添加到场景中/*** 相机设置*/var width = window.innerWidth; //窗口宽度var height = window.innerHeight; //窗口高度var k = width / height; //窗口宽高比//创建相机对象var camera = new THREE.PerspectiveCamera(60, k, 0.2, 1000);camera.position.set(0, 0, 600);/*** 创建渲染器对象*/var renderer = new THREE.WebGLRenderer({antialias: true});renderer.setSize(width, height);//设置渲染区域尺寸renderer.setClearColor(0x000000, 1); //设置背景颜色document.body.appendChild(renderer.domElement); //body元素中插入canvas对象function render() {renderer.render(scene, camera);}render();setInterval("render()",20);var controls = new THREE.OrbitControls(camera,renderer.domElement);//创建控件对象controls.addEventListener('change', render);//监听鼠标、键盘事件// 辅助坐标系  参数250表示坐标系大小,可以根据场景大小去设置var axisHelper = new THREE.AxesHelper(250);scene.add(axisHelper);</script>
</body>
</html>

运行效果如下图所示,面片只在正面显示,跑到后面就看不见了。

然后我们改用InstancedMesh,并且加个布尔变量作为开关。

var useInstancing = true;if(useInstancing)
{var instanceCount = matrixes.length;var instancedMesh = new THREE.InstancedMesh(geometry, material, instanceCount);for(var i = 0; i < instanceCount; i ++){instancedMesh.setMatrixAt(i, matrixes[i]);}scene.add(instancedMesh);
}else
{for(let matrix of matrixes){var mesh = new THREE.Mesh(geometry, material);mesh.matrix.copy(matrix);mesh.matrix.decompose(mesh.position, mesh.quaternion, mesh.scale)scene.add(mesh);}
}   

这个时候,我们会发现效果没有任何变化,但是加到场景中的mesh数量从6个变成1个了,并且mesh的material也没有因此变成数组,所以drawCall降了,性能好了。这当中的重点api是setMatrixAt,也是InstancedMesh类设置单个实例旋转缩放平移等变换的唯一入口,所以前面没有再用以前常用的rotation,position等属性。

然而这个优化在我们项目里面出bug了,因为有的模型需要镜像,比如要把scale.x设置为-1。在这个案例里面,我们给上面3个面片的矩阵加一个x方向为-1的缩放。

for(var j = 0; j < 2; j ++)
{for(var i = 0; i < 3; i ++){var posX = (i - 1) * 150;var posY = (j - 0.5) * 150;var rotationX = (i - 1) * Math.PI / 5;var rotationY = (j - 0.5) * Math.PI / 5;var rotationXMatrix = new THREE.Matrix4();rotationXMatrix.makeRotationX(rotationX);var rotationYMatrix = new THREE.Matrix4();rotationYMatrix.makeRotationY(rotationY);var matrix = new THREE.Matrix4();if(j == 1){matrix.makeScale(-1, 1, 1);}matrix.premultiply(rotationXMatrix);matrix.premultiply(rotationYMatrix);matrix.setPosition(posX, posY, 0);matrixes.push(matrix);}
}

再次运行,上面那一排面片看不见了,要把相机旋转到背面才可见。

但是如果我们把useInstancing设置为false,用回不合批的做法是没这个问题的。

普通渲染方法之所以还可见,原因在笔者早期的一篇博文里面已有讲述。

three.js+WebGL踩坑经验合集(5.2):THREE.Mesh和THREE.Line2在镜像处理上的区别_three line2-CSDN博客

笔者把这篇博文里面提及的几处代码都搬过来。

改变面的正反显示,用的是webgl层上的gl.frontFace方法。如果object.matrixWorld为负定矩阵(参考three.js+WebGL踩坑经验合集(6.1):负缩放,负定矩阵和行列式的关系(2D版本)-CSDN博客)

则正反显示会发生变更。

但是这个地方并没有针对InstancedMesh中的matrixAt进行调整,而且做这种调整也是不合理的,因为如果一个InstancedMesh里面既有正定矩阵也有负定矩阵,那么不管怎么改都会显示不全。而gl.frontFace的设置是以Mesh为单位(多材质的则以材质),不能再往单个实例拆分(拆了就相当于没合批)

那这个问题是不是改成双面就能好呢?我们看一下效果。

显示是显示了,但是颜色变暗,旋转到背面会发现,镜像后,面片的向光面和背光面反了。

且不说双面的性能问题,效果都是错误的,怎么弄都没意义。

我们不妨再试试把material的side改成BackSide看看。

这下好了,全显示背光面,如果我们把useInstancing再次关掉用回普通渲染又会是啥样的呢?

嗯,确实,这个时候就应该全显示背光面,但是背光面都应该在相机旋转到后面的时候才可见,而不是一半一半地显示着。

不管从原理还是现象来看,我们都不应该在把正定矩阵和负定矩阵都往同一个THREE.InstancedMesh上去放,哪怕使用了双面。

所以我们的项目对同一个geometry和material的物体都弄了两个THREE.InstancedMesh,按照矩阵的正负分开存放。

var instanceCount = matrixes.length;
material.side = THREE.FrontSide;
var instancedMesh = new THREE.InstancedMesh(geometry, material, instanceCount >> 1);
for(var i = 0; i < instanceCount >> 1; i ++)
{instancedMesh.setMatrixAt(i, matrixes[i]);
}
scene.add(instancedMesh);
var material_mirror = material.clone();
material_mirror.side = THREE.BackSide;
var instancedMesh_mirror = new THREE.InstancedMesh(geometry, material_mirror, instanceCount >> 1);
for(var i = instanceCount >> 1; i < instanceCount; i ++)
{instancedMesh_mirror.setMatrixAt(i - (instanceCount >> 1), matrixes[i]);
}
scene.add(instancedMesh_mirror);
}else
{
for(let matrix of matrixes)
{var mesh = new THREE.Mesh(geometry, material);mesh.matrix.copy(matrix);mesh.matrix.decompose(mesh.position, mesh.quaternion, mesh.scale)scene.add(mesh);
}

运行效果如下,最起码面的正反显示对了,但是向光和背光依然是错的,只不过这个时候我们处理起来就可以方便很多,不用顾此失彼。

本来想在这一篇把向光和背光的问题都写上,无奈光是个可见的问题就占了这么多篇幅,那么我们先来小结一下,向光背光的问题就留到下一篇。

1 普通的Mesh在使用MeshLambert等跟光照有关的材质时,three.js通过全局矩阵的佚(正负性)把显隐和向光背光性都处理得相当不错。

2 THREE.InstancedMesh可以把多个不同矩阵,但是geometry和material相同的物体合到一个批次进行渲染来提高性能,但是遇到负定矩阵的时候,显示和向光背光性都会有bug,并且无法用很简单的方法来规避,哪怕是开启双面。

3 使用进行性能优化的时候,如果同时包含正定矩阵和负定矩阵的物体,则应该创建两个THREE.InstancedMesh,把正负性相同的物体放到一起。

分开存放以后,我们再到下一篇研究光照相关的问题,大家先慢慢消化一下。光照那里涉及的问题比显隐可要复杂不少的呢。

http://www.xdnf.cn/news/19540.html

相关文章:

  • 机器学习-时序预测2
  • 基于FPGA+DSP数据采集处理平台的搭建
  • 【Vue2 ✨】Vue2 入门之旅(四):生命周期钩子
  • Unity核心概念③:Inspector窗口可编辑变量
  • C++/QT day3(9.1)
  • 深度学习中常用的激活函数
  • 关系型数据库——GaussDB的简单学习
  • Spring Boot 和 Spring Cloud 的原理和区别
  • 对于牛客网—语言学习篇—编程初学者入门训练—复合类型:BC141 井字棋及BC142 扫雷题目的解析
  • Composefile配置
  • 瑞芯微RK3576平台FFmpeg硬件编解码移植及性能测试实战攻略
  • 查看LoRA 哪个适配器处于激活状态(67)
  • 单片机元件学习
  • 设计模式:代理模式(Proxy Pattern)
  • 有N个控制点的三次B样条曲线转化为多段三阶Bezier曲线的方法
  • 【开题答辩全过程】以 基于微信小程序的校园二手物品交易平台的设计与实现为例,包含答辩的问题和答案
  • 8K4K图像评估平台
  • 【系统架构设计(七)】 需求工程之:面向对象需求分析方法:统一建模语言(UML)(下)
  • 像信号处理一样理解中断:STM32与RK3399中断机制对比及 Linux 驱动开发实战
  • 数组(4)
  • QMainWindow使用QTabWidget添加多个QWidget
  • 【数学建模学习笔记】数据标准化
  • LeetCode刷题记录----74.搜索二维矩阵(Medium)
  • 构建无广告私人图书馆Reader与cpolar让电子书库随身携带
  • 站在巨人的肩膀上:gRPC通过HTTP/2构建云原生时代的通信标准
  • Unity游戏打包——打包流程
  • 【C++】类型转换详解:显式与隐式转换的艺术
  • Vue2存量项目国际化改造踩坑
  • Ansible变量的定义与使用
  • 安卓11 12系统修改定制化_____常用的几种修改固件 实现指定 “运行内存” 显示