three.js+WebGL踩坑经验合集(1):THREE.Line无故消失的元凶

在项目开发过程中,笔者两次遇到同事的一个提问,我场景中的Line在相机旋转到某些角度或者移动到某些位置的时候会无故消失。由于业务场景复杂,所以这两位同事都是先花费了大量时间排查业务问题,然后才找我求助。这个问题抽象出来的一个测试单元代码如下所示:

javascript 复制代码
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>threeLine</title>
  <style>
    body {
      margin: 0;
      overflow: hidden;
    }
  </style>
  <script src="three/build/three.js"></script>
  <script src="three/examples/js/controls/OrbitControls.js"></script>
</head>

<body>
  <script>
    var scene = new THREE.Scene();
	
	var geometry = new THREE.BufferGeometry();

	var material = new THREE.LineBasicMaterial({color: 0x000000, linewidth: 1});
    var line = new THREE.Line(geometry, material);
    scene.add(line);
	
    var width = window.innerWidth; 
    var height = window.innerHeight; 
    var k = width / height; 
    var s = 200; 
    var camera = new THREE.PerspectiveCamera(60, 1, 0.1, 20000);
    camera.position.set(100, 600, 300); 
	camera.lookAt(0, 500, 400); 

    var renderer = new THREE.WebGLRenderer();
    renderer.setSize(width, height);
    renderer.setClearColor(0xffffff, 1); 
    document.body.appendChild(renderer.domElement); 
	
    function render() {
	  renderer.render(scene, camera);
	  requestAnimationFrame(render);
	}
	
	render();
	
	var controls = new THREE.OrbitControls(camera,renderer.domElement);
	controls.addEventListener('change', render);
	
	var axisHelper = new THREE.AxesHelper(250);
	scene.add(axisHelper);	
	
	function onClick(){	
		var points = [new THREE.Vector3(0, -100, -100), new THREE.Vector3(0, -100, 100), new THREE.Vector3(0, 100, 100), new THREE.Vector3(0, 0, -100), new THREE.Vector3(0, -100, -100)];
		geometry.setFromPoints(points);
	}
	
	window.addEventListener("click", onClick);

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

这是个绘图功能,通过在场景上点击鼠标在对应的位置上描点连线来显示绘制的轮廓。这里简化了一下,就在场景中心绘制一个固定尺寸的矩形。

然后我们试着通过滚轮把相机拉近拉远,奇怪的事情发生了,拉到比较近的时候线条突然无故消失,重新拉远又能看见了。

笔者本身就算不上精通three.js和WebGL,第一次遇到这个求助的时候也是花了不少时间在引擎源码上断点找问题。发现隐藏了它的代码在这里:

发现这根线就是被视锥剔除过滤掉了,three.js为了优化性能,会让不在画布可视范围(严格来说是个3D的视锥体)内的物体全部不参与渲染,这个线就是这样被过滤走的。

但是当时笔者没有细看,误以为只是视锥剔除算法为了性能而检测得比较粗糙,只检测了物体中心点,所以当时我给同事的解决方案是关闭线条的视锥剔除,代码如下:

javascript 复制代码
line.frustumCulled = false;

设置以后,视锥剔除就会因为这个判断而不执行了:

有了经验之后,再有别的同事问我,我就三秒钟给他解决掉类似的问题。

视锥剔除(frustumCulled)默认值为true,这对于大多数物体来说都适用,引擎内唯一默认关闭的是InstancedMesh,多个相同几何体和材质的物体,可以通过这个对象进行合批渲染(减drawCall)来提升性能。开启这种物体的视锥剔除,很可能会因为合批的物体数量太大而增加cpu的开销,性能方面得不偿失。

言归正传,对于Line来说,关闭视锥剔除的代价是性能的下降。但是为了结果没办法。

然而这次写博客笔者才发现自己的这个做法是错误的,事实上three.js的视锥剔除并不是只检查中心点,而是检查了包围球,检测依据是球心和半径而并非只看中心点。

所以更地道的解决方案是:修改了线的顶点后,重新手动计算一次包围球,代码如下:

javascript 复制代码
function onClick(){	
    var points = [new THREE.Vector3(0, -100, -100), new THREE.Vector3(0, -100, 100), new THREE.Vector3(0, 100, 100), new THREE.Vector3(0, 0, -100), new THREE.Vector3(0, -100, -100)];
    geometry.setFromPoints(points);
	//更新包围球,确保视锥剔除的结果准确无误
	geometry.computeBoundingSphere();
}

简单小结一下:

解决线条乃至其他物体在某些角度下消失的方法有两种:

1 把物体的视锥剔除关闭

2 物体的包围球发生变更时重新手动计算一次

物体的几何信息变更频繁的话适合方案1,否则选择方案2。

可以看到,three.js出于性能考虑,没有把修改顶点和更新包围球两个操作合并到一个方法中,这会让萌新们觉得three.js学习门槛高,易用性差,或者bug很多。

而事实上,three.js是在易用性和性能之间寻求一个平衡点。而且这些东西只要踩过一次坑,后面再遇到类似问题的时候就会更加得心应手。

这也是笔者创作这一专栏的初衷,如果你们有幸在首次遇到这方面问题的时候能够通过搜索引擎找到笔者的文章,那也许第一次踩坑就能快速解决问题了。

相关推荐
八荒启·交互动画6 小时前
Web特效011—Web特效的定义与边界为什么忽快忽慢:时间、频率与采样的秘密
webgl·网页特效·八荒启-交互动画·八荒启·web特效
八荒启·交互动画8 小时前
Web特效008—一个 `getContext(‘webgl‘)`,浏览器到底交出了什么?
webgl·八荒启-交互动画
xhload3d1 天前
图扑智慧工厂 | 继电器产线仿真态势管控平台
物联网·低代码·webgl·数字孪生·可视化·智慧工厂·工业互联网·hightopo
新的瑞拉公主5 天前
Unity游戏发布微信小游戏:从构建到提审
unity·webgl·微信小游戏·开放数据域
慧都小妮子6 天前
实时大屏掉帧排查:WebGL 图表库选型的 5 项可实测检查
webgl·scichart.js·实时大屏掉帧·前端图表选型
❀͜͡傀儡师6 天前
移动端 360° 商品展示方案:从 20MB 雪碧图到 800KB 丝滑视频
ffmpeg·webgl·sprite
平行云7 天前
国产GPU云渲染适配实战:驱动兼容、编码调优与多路并发
unity·ue5·webrtc·webgl·实时云渲染·云桌面·像素流送
平行云10 天前
实时云渲染信创架构解析:从GPU池化到全栈适配的技术演进
linux·unity·docker·ue5·webgl·数字孪生·实时云渲染
yyt36304584111 天前
KLineChartQuant:GLSL 的精度问题及 RTC 解法
前端·vue.js·react.js·交互·图形渲染·webgl·数据可视化
灵境(虚幻知音)13 天前
WebGL 不支持>1px的线绘制,那 Cesium 的带宽度的线是怎么画出来的?
webgl·cesium·着色器