初八开工后,笔者又停了下来,今天总算又抽出来了一丢丢的时间继续。今天打算给大家聊聊困扰很多3D开发者的z-fighting叠面闪烁问题。
该问题从严格意义上说,是属于业务问题,因为现实中是不会有完全重叠的两个平面物体存在(总得有厚度差吧,哪怕很小)。所以笔者的想法跟很多博主一样,应该在业务上尽可能避免出现叠面场景的出现,不然问题永远解决不完。
笔者先来跟大家探讨前端开发用得特别频繁,网上文章也最多见的polygonOffset方法。不得不说,这个方法能快速解决大家的燃眉之急。但如果你的项目是规模较大的,需要长时间维护的那种类型,就会发现,这样的方法适应的场景很有限,并且用多了之后多个polygonOffset之间会相互打架。
废话少说,下面上demo。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>three_cameraNear</title>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
<script src="three/build/three.js"></script>
<script src="three/examples/js/controls/OrbitControls.js"></script>
<script src="three/examples/js/libs/dat.gui.min.js"></script>
</head>
<body>
<script>
var scene = new THREE.Scene();
var geometry = new THREE.PlaneGeometry(100, 100);
var srcColor = 0xFF6600;
var material = new THREE.MeshBasicMaterial({color: srcColor});
var mesh = new THREE.Mesh(geometry, material);
material.polygonOffset = true;
material.polygonOffsetFactor = 0;
material.polygonOffsetUnits = 0;
scene.add(mesh);
var geometry2 = new THREE.PlaneGeometry(50, 50);
var srcColor2 = 0xFFFFFF;
var material2 = new THREE.MeshBasicMaterial({color: srcColor2});
var mesh2 = new THREE.Mesh(geometry2, material2);
scene.add(mesh2);
var width = window.innerWidth;
var height = window.innerHeight;
var camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 20000);
camera.position.set(0, 0, 150);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
renderer.setClearColor(0x000000, 1);
document.body.appendChild(renderer.domElement);
var gui = new dat.GUI(),
folderCamera = gui.addFolder("相机"),
propsCamera = {
get '裁剪'() {
return camera.near;
},
set '裁剪'( v ) {
camera.near = v;
camera.updateProjectionMatrix();
},
};
folderCamera.add( propsCamera, '裁剪', 0.01, 0.1 );
folderCamera.open();
var folderPolygonOffset = gui.addFolder("polygonOffset");
var propsPolygonOffset = {
get 'polygonOffsetFactor'() {
return material.polygonOffsetFactor;
},
set 'polygonOffsetFactor'( v ) {
material.polygonOffsetFactor = v;
},
get 'polygonOffsetUnits'() {
return material.polygonOffsetUnits;
},
set 'polygonOffsetUnits'( v ) {
material.polygonOffsetUnits = v;
},
}
folderPolygonOffset.add(propsPolygonOffset, 'polygonOffsetFactor', 0, 5);
folderPolygonOffset.add(propsPolygonOffset, 'polygonOffsetUnits', 0, 5);
folderPolygonOffset.open();
function render() {
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
var controls = new THREE.OrbitControls(camera,renderer.domElement);
controls.addEventListener('change', render);
var raycaster = new THREE.Raycaster();
</script>
</body>
</html>
这段代码创建了两个大小不一样,但是位置上是重叠的两个平面,运行起来的效果如下:
可以看到,在旋转相机的时候,两个面的深度会不停交替,来回闪烁,这就是经典的z-fighting叠面问题。
然后网上给的解决方案是给其中一个面设置上polygonOffset,并且那个被抄到烂大家的文章,给polygonOffset对应的两个参数:polygonOffsetFactor和polygonOffsetUnits都设置为1,那这里我们也这样试试。
调整到1之后,叠面基本消失,场景拉远之后,我们发现还是有些角度产生了叠面。然后我们再把两个参数都调整到2,发现这下好了很多。
然而事实并没有我们想象中那么简单,注意到笔者还加了个裁剪的滑动条。
我们项目最初用的camera.near值是0.001,而不是我们现在给的0.1。我们很意外地发现,这个值对叠面的效果竟然也有影响,并且还不小。
这样下来,影响叠面的因素又多了一个。所以在笔者同事遇到这些问题时,他们都会尽可能地把polygonOffset的两个参数往大里调,从而规避这个问题。
但很不幸,我们项目玩得很花,好些功能会把三四个面叠在一起,这时候,不管参数值调大调小,我们都很容易顾此失彼,无法正确处理多个面之间的冲突。
笔者有段时间费了很大力气去查找跟polygonOffset相关的资料,但天下文章一大抄,找回来的都是webgl官方提供的那条公式m*polygonOffsetFactor+r*polygonOffsetUnits,并且两个不可控的变量m和r也描述得不清楚。
讲得相对深入一点的,是这篇:
深度冲突--threejs(webgl)_polygonoffsetfactor-CSDN博客
以下是公式中几个变量的讲解。
m: 表示最大深度斜率(Maximum Depth Slope)的值,是一个根据当前渲染的多边形相对于视线的角度自动计算出来的值。它基本上表示该多边形表面有多倾斜;如果表面与视线平行,则m值小,如果表面近乎垂直于视线,则m值大。
factor: 这是一个你可以控制的变量,对应于Three.js中的material.polygonOffsetFactor。这个数值会乘以上述的m值,也就是说,它表示深度偏移量将随着多边形的视觉倾斜程度而增加。
r: 这是指解析度,表示深度缓冲区每个单位的更改所能表示的最小深度差异。不同的系统和深度缓冲区的配置可能具有不同的解析度。
units: 同样是一个你可以控制的数值,对应于Three.js中的material.polygonOffsetUnits。这个数值会乘以r,提供了一个恒定的深度偏移,这个偏移与多边形的倾斜无关。
————————————————版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_45705239/article/details/138075785
不得不说,这里的说明让笔者对polygonOffset的理解深刻了一些。尽管在找到这篇文章之前,笔者就已经知道camera的near也是一个因素,但所幸的是,笔者完全可以通过先固定camera.near来以上文为方向单独研究这两个变量了。
下一篇,笔者会跟大家一起探讨这个坑爹玩意儿的工作原理,敬请期待!