基于Intel Atom芯片图形管线的OpenGL等高线实现问题咨询
嘿,我来帮你拆解这三个问题,都是片段着色器里容易踩的细节坑,咱们一个个说:
1. 高度无变化时为何仍出现等高线?
首先得区分两种“平坦”情况:
- 如果模型绝对平坦,所有顶点高度完全一致:理论上
heightVal是常量,fract(heightVal*0.1)会得到一个固定的小数,而fwidth(heightVal*0.1)会返回0(因为屏幕空间里这个值没有任何变化)。这时候smoothstep(0, 0, f)会返回0(GLSL规范里当两个边缘参数相等时返回0),整个平面应该会被染成黑色,不会有等高线。但你看到了等高线,大概率是第二种情况: - 模型存在微小的高度误差或平缓坡度:比如建模时的顶点高度有浮点精度偏差,或者模型本身有极其平缓的倾斜,再或者——你用的是
gl_Position.y作为高度值!gl_Position是裁剪空间坐标,哪怕模型平坦,相机视角变化、投影变换都会让这个值产生微小波动,这些波动被fract放大后,就会出现看起来像等高线的细密条纹。
2. 移动相机时等高线为何随之移动?
核心原因是你用了裁剪空间的gl_Position.y作为高度值!gl_Position是经过模型视图投影矩阵变换后的结果,相机移动时,视图矩阵会跟着变,每个顶点的gl_Position.y都会发生变化,传递到片段着色器的heightVal自然也会改变,fract(heightVal*0.1)的结果跟着变,等高线就会随相机移动了。
解决办法很简单:把heightVal改成模型空间或世界空间的高度值,也就是地形本身的实际高度,而不是经过投影变换后的裁剪空间坐标——后者和相机强绑定,根本不适合用来表示地形的真实高度。
3. fract函数与高度变化的关联机制是什么?
fract(x)的作用就是取x的小数部分,比如fract(5.7)=0.7,fract(20.2)=0.2,哪怕x是负数,它也会返回正的小数部分(比如fract(-1.3)=0.7)。
在你的着色器里,heightVal*0.1是把高度值缩小了10倍,这样每间隔10个单位的真实高度,heightVal*0.1就会从整数增加1,fract的结果就会从0到1循环一次。比如高度从0到10,fract结果从0到1;高度10到20,fract又从0到1,以此类推。
这个循环特性刚好用来标记等高线的位置:当f(fract的结果)接近0时,说明当前高度刚好是10的整数倍。再配合fwidth(计算屏幕空间中heightVal*0.1的变化率,让线条宽度在不同视角下保持一致)和smoothstep(生成平滑的过渡边缘),就得到了你看到的等高线效果。
内容的提问来源于stack exchange,提问作者Kadir Erdem Demir

