three.js+WebGL踩坑经验合集(9.1):polygonOffsetUnits工作原理大揭秘
在本系列博客的8.x部分,笔者给出了polygonOffset中的两个参数polygonOffsetFactor和polygonOffsetUnits需要结合camera的near和far进行调整,这是在实战中发现的问题点,但之前一直都没有去做深入的研究。所以我们的同事在设置入参的具体数值时,一直都处于瞎蒙的状态,也不知道会否改出新的问题。
下面我们先把8.x部分引用的,笔者认为讲得不错得博文。
深度冲突--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
m看起来比r复杂,那本篇我们就先研究r(实际上m笔者也还没研究透)
既然r是恒定值,那我们就拿个简单的,好观察的角度来做演示demo
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>three_polygonOffset</title><style>body {margin: 0;overflow: hidden;}</style><script src="three.js-master/build/three.js"></script><script src="three.js-master/examples/js/libs/dat.gui.min.js"></script><script src="three.js-master/examples/jsm/helpers/VertexNormalsHelper.js"></script>
</head><body><script>var scene = new THREE.Scene();var geometry = new THREE.PlaneGeometry(100, 100);var loader = new THREE.TextureLoader();var map = loader.load("three.js-master/examples/textures/UV_Grid_Sm.jpg")var srcColor = 0xFFFFFF;var material = new THREE.MeshBasicMaterial({ color: srcColor, map: map});var mesh = new THREE.Mesh(geometry, material);material.polygonOffset = true;material.polygonOffsetFactor = 0;material.polygonOffsetUnits = 0;mesh.rotation.y = Math.PI * 0;scene.add(mesh);var geometry2 = new THREE.PlaneGeometry(100, 100);var srcColor2 = 0xFFFFFF;var material2 = new THREE.MeshBasicMaterial({ color: srcColor2, map: map });var mesh2 = new THREE.Mesh(geometry2, material2);mesh2.rotation.y = Math.PI / 4;scene.add(mesh2);var width = window.innerWidth;var height = window.innerHeight;var camera = new THREE.PerspectiveCamera(90, width / height, 0.1, 20000);camera.position.set(0, 0, 100);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', -500, 500);folderPolygonOffset.add(propsPolygonOffset, 'polygonOffsetUnits', -10000, 10000);folderPolygonOffset.open();function render() {renderer.render(scene, camera);requestAnimationFrame(render);}render();</script>
</body></html>
demo中所引用的贴图是three.js官方案例中的网格图片文件,大家可以在源码对应的目录中找到,实在懒得找的,可以这里拿
运行效果如下:
因为r代表的是深度缓冲所能表示的最小深度差异,所以这种值不会很大,想要肉眼看得很清楚,跟r相乘的数值得弄得比较大。所以笔者把范围弄到了-10000到10000
在这个案例中,我们改的是跟屏幕平行的那个面的polygonOffset,可以发现,改polygonOffsetFactor不会有任何变化,而改polygonOffsetUnits时,两个面的交线会随之移动,这就是z深度更改所引起的。
这里可以先给出一个跟m有关的结论,当平面跟屏幕绝对平行时,m始终等于0(尽管本文先不讨论m,但也可以顺带说一下)。
接下来就是r了,我们通过调整polygonOffsetUnits滑条,让交线分别对齐到不同的网格线上,然后算出当前网格线对应的世界坐标,并通过project(camera)算出深度该网格线在深度缓冲中的值
这么说有点抽象,我们来实战一下。
polygonOffsetUnits等于0时,两平面的交线在斜面的第5条竖线
然后我们试着拉动滑条,让两平面的交线落在第6条竖线(往后偏移)
可以看到,当我们把polygonOffsetUnits调到1110时,两交线大致对齐到斜面的第6条竖线。
斜面的旋转角度是45度,斜面的宽高均为100,每一格的长度为10,中间一条线(第5条)的中点的坐标为(0, 0, 0),所以第6条竖线应该往后偏移10个单位。这样,我们就可以根据角度45和长度10这两个条件算出斜面第6条线的中点坐标为
(10*Math.cos(Math.PI/4), 0, -10*Math.cos(Math.PI/4))
然后我们打开控制台,用project(camera)算下这个点在深度缓冲区的z坐标值(包含了除以w)
要知道polygonOffsetUnits给平面赋予的偏移量,我们可以拿polygonOffsetUnits=0,也就是(0,0,0)坐标的深度缓冲值作为对照。
这两个z看起来差别非常地小,做一般业务的时候往往是可以忽略的,但是对于深度缓冲和叠面问题来说就非常敏感。我们来算一下它的差值。
嗯,确实很小,如果按照api文档对polygonOffsetUnits的说明来看,那我们可以给出来的一个关系式就是
r*1110=0.00013208242547479987
当然了,这样算出来的r不太可靠,首先靠这样拖动观察得出来的数值就不准确,而且是不是真的这么线性也不确定。所以我们不妨多弄几个值看看。
比如第7根线是2100
所以这里就有r*2100=0.00024779992519818883
限于阅读者能接受的单篇文章篇幅,其它的网格线就不一一给出截图了,这里笔者把10根网格线对应的polygonOffsetUnits值,projection(camera)的z值,以及算出来的r值汇总成表。
其中r是z偏移量跟polygonOffsetUnits相除的结果,可以看到,r值都在1.19*10^(-7)左右浮动。
这个值有什么特别之处呢?笔者第一反应是2的幂数相关,所以就算了个2的对数看看结果。
-23这个值如果要跟深度缓冲扯上关系,那最合适的就是深度缓冲的位数了,目前深度缓冲区的常用位数是16,24或者32。这样的话,r值就等于2的-(位数-1)次方。之所以要-1,是因为有一位被符号占用了。
笔者在家里和公司的电脑测试,结果都是2的-23次方,也就是说,这两台机器用的都是24位的深度缓冲。事实上,笔者更应该去深挖另外两个位数下r值的表现,或者用FBO多测试一下,但是笔者又想偷懒了,也担心一旦深挖到了更有用的东西,又忍不住一直研究下去,这文章就不知道要什么时候才可以跟大家见面了,所以就先这样,有兴趣的读者可以继续深入探讨。
如果你还记得笔者前面说的near(裁剪)影响polygonOffset的效果,那么你可能还会想着去拉一下相机裁切的滑动条,然后发现前面调整齐的数值又变得不对齐了。
嗯没错,near确实是会对结果造成影响。但现在,我们可以很好地解释为什么了。前面我们算的z值是通过project方法来算的,而project(camera)需要获取camera的projectionMatrix,然后near和far都会作用于projectionMatrix,所以project的结果也会跟着改变。这个时候要继续测试,就得在新的裁剪值下重新捕获数据,有兴趣折腾的小伙伴可以再捣鼓一遍。
下面来小结一下先:
1 平面跟屏幕完全垂直时,polygonOffset的m值恒等于0,只有polygonOffsetUnits产生作用
2 polygonOffsetUnits是跟r相乘进行偏移的,r值等于2^-(深度缓冲位数-1),目前的设备深度缓冲位数默认为24(这个说法不严谨,接受反驳和补充),所以r值一般等于2的-23次方。
有了这样的结论之后,我们在调整polygonOffsetUnits参数的时候就有理论指导了,我们处理的叠面,一般都是顶点坐标值不完全相等,或者稍稍有点角度偏差的重叠面。这时候,我们就可以根据r值进行polygonOffsetUnits的计算让叠面的偏移更加精准。笔者打算放在下一篇,或者等m值都研究出来了再分享出来,让大家久等了!