很多很多的3D带编辑器的软件都会有网格来辅助物体的放置
比如说Unity
这东西在编辑放置物体的时候是个很强的参照,不得不有
那怎么做呢?
方案一:
自然是铺一个巨大巨大的正方形在地上啦,然后采样...,铺纹理就行啦
对于网格这种纹理,不需要真的去画一张,在Shader上用代码生成就行了
最简单的思路如下
float grid(vec2 uv,float gridSize,float lineWidth){
vec2 g =abs(mod(uv,gridSize));
float w = min(g.x,g.y);
return step(w,lineWidth);
//if(w<lineWidth)return 1;
//return 0;
}
//in main
void main(){
colorOut = vec4(1,1,1,1.0 - grid(uv,5,0.2));
}
这样子简单是不假...但是呢,问题在于这里的线粗是固定的,这意味着在屏幕最远方的线是0.2粗,而最近的线也是0.2粗,那么..就会很奇怪,没有近大远小了。
简单的优化思路是利用glsl的求导函数对线宽做一个缩放
vec2 derivative = fwidth(uv);
lineWidth = lineWidth * max(derivative.x,derivative.y);
然而这个还是不能用...原因是生成的格子会有严重的锯齿...很哈人
如这样:
但是呢IQ大神指出: 对于格子状的程序生成纹理过滤是有解析解的,如这篇文章所讲述
于是就有了这个Shadowtoy
当然为了方便控制其中的参数,我对他做了一点点的改进
float gridTextureGradBox( in vec2 p,float scale,float lineWidth)
{
const float N = 10.0; // grid ratio
// filter kernel
vec2 w = fwidth(p) + 0.01;
w *= scale;
p *= scale;
// analytic (box) filtering
vec2 a = p + 0.5*w;
vec2 b = p - 0.5*w;
vec2 i = (floor(a)+min(fract(a)*N,lineWidth)-
floor(b)-min(fract(b)*N,lineWidth))/(N*w);
//pattern
return (1.0-i.x)*(1.0-i.y);
}
进一步提出一个需求,在编辑器里很多都是大格子套小格子,实现如此只需要拿两个函数一重叠就好力
void main(){
float bigGrid = gridTextureGradBox(fragPos3D.xz,0.5,0.05);
float smallGrid = gridTextureGradBox(fragPos3D.xz,5,0.05);
outColor = vec4(.8, .8, .8,1 - 0.5*smallGrid - 0.5*bigGrid );
}
这种方法有很大的问题哦,就是格子覆盖的范围会被你用的正方形大小限制~
而且呢,uv身为浮点数,一但过大,精度问题嘛嘿嘿嘿
这时候就有了方案二:
既然要做个无限大的格子
嗯...无限大,其实就是你无论怎么移动视角都能看到,那么也就是在屏幕空间内生成就行啦!!!!
基于此,在程序内只要传入一个覆盖了屏幕空间的四边形,如下:
std::vector<fm::vector3> vecs{
{1, 1, 0},{-1, -1, 0},
{-1, 1, 0},{-1, -1, 0},
{1, 1, 0},{1, -1, 0}
};
std::vector<unsigned int> indices{2,1,0,5,4,3};
然后VBO,VAO,顶点属性,绑定啥的...不写了
那么在vertex shader中,只需要算出这个四边形对应在y=0平面上的部分就好啦
这实际上是个反投影过程
vec3 UnprojectPoint(dvec3 p) {
vec4 unprojectedPoint = vec4(InvViewMat*InvProjMat * dvec4(p, 1.0)) ;
return unprojectedPoint.xyz / unprojectedPoint.w;
}
但是由于我们缺失了深度值,还需要进一步的计算
把点分别反投影到近平面和远平面上,两投影点连线交于Y=0上,即为目标顶点。
vec3 eval_interscetion_xz(vec3 nearP,vec3 farP){
double t = - nearP.y / (farP.y - nearP.y);
return vec3(nearP + t * (farP - nearP));
}
这里会出现几种情况,在这我用正方形演示(因为我不会做棱柱而且正交相机也是可行的o((>ω< ))o
第一种是无相交点
那么就不会显示格子...GPU会帮我们处理错误的
第二种是有几点出了相机范围
那么GPU会帮我们裁剪的,也不用担心
第三种是所有点都在里面
那么就更没问题了!
所以该方案完全可行
那接下来就是需要把该计算好的点重新进行mvp变换,得到正确的深度,再把点送入gl_Position
而这几点,就是在y=0平面上,且大小刚好覆盖整个屏幕的四边形
拿到此处的深度很重要,你不会希望格子跑到其他物体上头去的
void main() {
nearPoint = UnprojectPoint(dvec3(aPos.xy, -1.0)).xyz; // unprojecting on the near plane
farPoint = UnprojectPoint(dvec3(aPos.xy, 1.0)).xyz; // unprojecting on the far plane
gl_Position =vec4( ProjMat * ViewMat * vec4(vIntersection,1.0));
gl_Position = gl_Position / gl_Position.w;
}
这里还有点点的小问题
第一呢就是理论上透视除法GPU会帮着做,但是我不除就会有BUG
第二个问题呢就是如果我把交点的世界坐标送入Fragment shader插值会出错,所以需要把近平面交点和远平面交点传过去再进行计算。。。。
这两个问题花了我一天,一天!!!>︿<
这个是bug拉满的版本的演示
很好的CULT视频,使我大脑旋转(
然后在Fragment Shader中在此计算出交点,利用交点来计算grid纹理
//Before main
in vec3 nearPoint;
in vec3 farPoint;
out vec4 outColor;
//in main
float t = -nearPoint.y / (farPoint.y - nearPoint.y);
vec3 fragPos3D = nearPoint + t * (farPoint - nearPoint);
float bigGrid = gridTextureGradBox(fragPos3D.xz,0.5,0.05);
float smallGrid = gridTextureGradBox(fragPos3D.xz,5,0.05);
outColor = vec4(.8, .8, .8,1 - 0.5*smallGrid - 0.5*bigGrid );
实际上还有点点的瑕疵,那就是太远处的格子叠在一块了很白很难看
用一个指数雾解决
float expFog(float dist){
return exp2(-0.001 * dist * dist);
}
//in main
outColor = outColor * (expFog(distance(vec3(CamPos),fragPos3D)));
再叠加个x,y坐标有颜色,然后就得到了...
还不错欸,舒服
实际上这次很大的灵感来源于
这篇文章 如何绘制一个无限大的网格
但我很不喜欢它里面的一些处理,包括把深度的计算延后到了FS中,这是很大的性能开销,简直扯淡,能放到vs里做的为什么要送到后面去?多了多少次次矩阵乘法没点数?
而且里面对于边缘消失的处理也不是很...好,还得算一个线性深度,开销++,给一个摄像机距离能咋样?
然后我找到了原文章How to make an infinite grid
原来如此...
那么代码就不再汇总了,Apex启动!