网站空间怎么选择,wordpress 主题 模板 区别,wordpress flickr,承包酒席可以做网站吗上一篇#xff0c;笔者留下了一个问题#xff0c;three.js内置的THREE.Vector3.project方法算出来的结果对于超出屏幕可见范围的点来说错得相当离谱。
three.jsWebGL踩坑经验合集(4.1):THREE.Line2的射线检测问题#xff08;注意本篇说的是Line2#xff0c;同样也不是阈值…上一篇笔者留下了一个问题three.js内置的THREE.Vector3.project方法算出来的结果对于超出屏幕可见范围的点来说错得相当离谱。
three.jsWebGL踩坑经验合集(4.1):THREE.Line2的射线检测问题注意本篇说的是Line2同样也不是阈值方面的问题-CSDN博客
从代码上看project方法和常规shader的实现原理并无太大差异但是为什么错得这么离谱的算法被使用了这么多年都没人去管的呢
原因很简单不管怎么错它都已经出界不可见了所以结果的正确与否对于显示来说并不重要。
我们回顾一下上一篇错得很离谱的那个数值它的xyz都不在0~1的范围内。 但要是想拿它来进行计算的话就完犊子了。就拿上一篇的线来说假设有且只有一个端点出界一个点在(0.5,0.1)另一个点在(-1.2, -1.3)按照预期连线向量应该为(1.7, 1.4)但如果反了变成(1.2, 1.3)那连线向量就会被误算成(-0.7, -1.2)导致后续的结果全部不正确。THREE.Line2射线检测的bug就是这样子来的了。
所以现在我们就来探讨下project方法算不正确的原因。
Vector3的project方法实现代码如下
project(camera) {return this.applyMatrix4(camera.matrixWorldInverse).applyMatrix4(camera.projectionMatrix);
}
然后我们再来研读下applyMatrix4的代码
applyMatrix4(m) {const x this.x, y this.y, z this.z;const e m.elements;const w 1 / (e[3] * x e[7] * y e[11] * z e[15]);this.x (e[0] * x e[4] * y e[8] * z e[12]) * w;this.y (e[1] * x e[5] * y e[9] * z e[13]) * w;this.z (e[2] * x e[6] * y e[10] * z e[14]) * w;return this;}
大体上它是拿矩阵m跟3D点进行矩阵乘法运算但是它还多了一个步骤就是在xyz以外还算了一个w值并且把w值分别乘到xyz上。
这一步是个什么原理呢
透视成像近大远小的特性在数学上大致为反比例的关系。不严谨的说法同一个物体我们肉眼所见的大小s跟物到我们的距离z成反比即s1/z。
z在分母已经无法用矩阵的线性运算进行表达。为了让开发者在处理3D变换的过程中尽可能只跟矩阵打交道GPU渲染底层在xyz的基础上新增一个w变量用于处理透视变换然后称(x, y, z, w)这样的坐标为齐次坐标最终呈现到屏幕上的是(x/w, y/w, z/w)这样的值。
可见THREE.js的Vector3.applyMatrix4中的w跟齐次坐标中的w互为倒数。
笔者不打算给大家探讨投影矩阵的推导过程网上文章一抓一大把。笔者本人也没啥特别的想法这里随便给大家搜个两篇
透视投影矩阵推导
透视投影过程中z值为什么要映射到[0,1]? 笔者只打算给大家列个大纲说明一下GPU渲染的一些数据结构和工作流程
1 大多数时候3D点基础变换的计算都基于矩阵乘法。
2 3D点按道理是应该跟3*3矩阵相乘但是平移是典型的1*3矩阵相加为了统一这两种变换合并为1*4和4*4矩阵相乘。并且扩充的行填充单位矩阵的数值[0,0,0,1]1*4矩阵的最后一列填1
至于为什么合并后会从3*3变成4*4笔者以前有写过2D矩阵的文章那里展示了从2*2变成3*3的过程3*3到4*4可类推。
【原创】《矩阵的史诗级玩法》连载五45度地图砖块所蕴含的矩阵基础知识下_45度地图深度排序-CSDN博客
3 既然合并过程中点的坐标新增了个1那么GPU渲染底层就把它拿过来做透视变换了并且赋予变量w。
4 3D点为齐次坐标(x, y, z, w)物体自身的变换Model相机变换都各自有一个矩阵View投影到屏幕/画布的NDC坐标系Projection会先后作用于该坐标上得到最终结果(x/w, y/w, z/w)3个矩阵合并称为MVP矩阵。
5 齐次坐标的w值初始为1前两个矩阵不会修改w之后P矩阵会修改w从而产生透视效果。
这个大纲还是有点啰嗦了下面再列一下透视投影矩阵的关键点。
1 w跟z通常成正比可能是wz或者w-z。
2 最终呈现的结果变量w在分母中。因此如果w的绝对值很小甚至等于0那么这些位置的坐标绝对值将会非常地大甚至是无穷大。
这么一顿操作之后我们发现问题点似乎就出现在w上。视锥体的边缘会不会正是w0的边界
下面我们不妨就以此为切入点去看看project方法在视锥体边缘的表现。
既然GPU渲染使用的是齐次坐标那么three.js也会配套定义了对应的类Vector4。我们用它来研究将会更加方便。不过Vector4只有applyMatrix4方法没有project我们照着Vector3的抄一个。
这里我们单独用一个简单点的相机来测试顺带加上Vector4的project方法
!DOCTYPE html
html
headmeta charsetUTF-8titlethree_cameraProject/titlestylebody {margin: 0;overflow: hidden;}/stylescript srcthree/build/three.js/scriptscript srcthree/examples/js/controls/OrbitControls.js/script
/headbodyscriptfunction v4Project(v4, camera){return v4.clone().applyMatrix4(camera.matrixWorldInverse).applyMatrix4(camera.projectionMatrix);}var testCamera new THREE.PerspectiveCamera(60, 1, 1, 20000);testCamera.position.set(0, 0, 100);testCamera.updateMatrixWorld();testCamera.updateProjectionMatrix();for(var i 95; i 105; i ){var proj v4Project(new THREE.Vector4(2, 3, i), testCamera)console.log(z i 时坐标为( proj.x , proj.y . proj.z , proj.w ));} /script
/body
/html
这段代码创建了一个z坐标为100的透视相机然后测试一个点坐标在100附近95~105变化时的投影结果。控制台输出如下 我们看到变化的只有z和w并且均为单调递减变化过程还比较平缓。但因为齐次坐标中xyz最终都会除以w所以最终的呈现结果就完全不一样了。分母越接近0结果的绝对值越大越趋向于无穷大并且当分母从正数过渡到负数时坐标值都会从正无穷突变到负无穷从而形成断层数学上这叫无穷间断点。 我们把区间取小一点步长设短一点并且改用Vector3的project方法进行测试
for(var i 99; i 101; i 0.125){var proj new THREE.Vector3(2, 3, i).project(testCamera);console.log(z i 时坐标为( proj.x , proj.y . proj.z ));
} 可以看到从99到100xyz的绝对值都飙升得很快。等于100的时候直接等于无穷大因为此时的w等于0了。超过100就立马来个断层变成负无穷大然后绝对值又急剧下降回来。
从这里我们也可以看出问题的本质
100是相机的z坐标z100时物体刚好贴着相机完全没距离z100时物体在相机背面完全不可见所以这时候不管算出来的是啥值都不能说它错。但与此同时它也是个不可用的数值。用我们技术大佬的话讲这个结果只能用在逻辑的终点而不能是中间的某个环节。
当然了正交相机无需考虑这个问题没透视变换的话w始终等于1不存在断层的情况。
来小结一下
1 GPU渲染底层使用包含w的齐次坐标让开发者仅通过线性变换就能实现近大远小的非线性透视效果非线性部分藏到了底层w是分母。
2 project方法出问题的位置是在透视相机背面跟视锥体范围无关因为一个物体从相机正面到背面的移动过程中分母w从一个很小的正数缓慢过渡到一个很小的负数产生了断层断层后的结果不可用。
3 一个坐标点在透视相机背面是不会有一个正确的NDC坐标值因此这个时候只能用来判断该点是否可见或出界但不能再继续用它进行后续的计算。
4 THREE.Line2的shader通过trimSegment规避了越界的坐标点但是射线检测没有做类似的处理从而导致出界的线条存在射线检测错误的bug。
5 把LineMaterial上的trimSegment方法实现到LineSegments2的raycast方法中问题就得到解决了。
THREE.Line2不是three.js主包的内容而是在examples文件夹里面尽管它也是官方提供的类但是这样的目录规划不得不让笔者感觉到THREE.Line2更像个土八路没入正编的样子。所以这玩意儿还存在别的问题下一篇我会给大家讲讲THREE.Line2的镜像问题这又是一个大坑请大家做好心理准备嘿嘿