射线和椭球相交准确度的提高
我需要在我的一个大气散射GLSL片段着色器中提高函数的精度,这个片段着色器计算单个光线和轴alignment的椭球之间的交点。
这是矿井大气散射着色器的核心function。 旧的原始着色器是在floats
和正常渲染是好的,但增加缩放后,我发现,相对较小的距离精度丢失。 在浮标上,地球的可用距离只有0.005AU(天文单位)。 所以我试图移植的关键functiondouble
,这有助于现在可用的距离大约1.0 AU(具有小的文物)
这是Fragment Shader内部函数的double
版本(旧式源代码使用不赞成的东西!!!)
#extension GL_ARB_gpu_shader_fp64 : enable double abs(double x) { if (x<0.0) x=-x; return x; } // compute length of ray(p0,dp) to intersection with ellipsoid((0,0,0),r) -> view_depth_l0,1 // where rx is elipsoid rx^-2, ry = ry^-2 and rz=rz^-2 float view_depth_l0=-1.0,view_depth_l1=-1.0; bool _view_depth(vec3 _p0,vec3 _dp,vec3 _r) { double a,b,c,d,l0,l1; dvec3 p0,dp,r; p0=dvec3(_p0); dp=dvec3(_dp); r =dvec3(_r ); view_depth_l0=-1.0; view_depth_l1=-1.0; a=(dp.x*dp.x*rx) +(dp.y*dp.y*ry) +(dp.z*dp.z*rz); a*=2.0; b=(p0.x*dp.x*rx) +(p0.y*dp.y*ry) +(p0.z*dp.z*rz); b*=2.0; c=(p0.x*p0.x*rx) +(p0.y*p0.y*ry) +(p0.z*p0.z*rz)-1.0; d=((b*b)-(2.0*a*c)); if (d<0.0) return false; d=sqrt(d); l0=(-b+d)/a; l1=(-bd)/a; if (abs(l0)>abs(l1)) { a=l0; l0=l1; l1=a; } if (l0<0.0) { a=l0; l0=l1; l1=a; } if (l0<0.0) return false; view_depth_l0=float(l0); view_depth_l1=float(l1); return true; }
- input是椭球的光线和半径^ -2
-
输出是从p0到交点的距离
-
input和输出variables的精度是浮点数(这是足够的)
这是移植到Double后的样子
所以问题是:Q1。 如何提高这个function的准确性?
- 目标精确度为
view_depth_l0
,view_depth_l1
为+/- 20 m
,距离|p0|=100 AU
这将是理想的,现在看起来像+/- 10公里的10 AU距离这是不好的。 甚至10倍精确的计算将是巨大的前进任何想法?
l0,l1范围
我错了,浮动转换的view_depth_l0,view_depth_l1
是工件的原因。 把它移到相对距离后,精度提高了很多。 我只是加了这个:
// relative shift to preserve accuracy const double m0=1000000000.0; // >= max view depth !!! if (l0>m0){ a=floor(l0/m0)*m0; a-=m0; if (l1>l0) l1-=a; l0-=a; }
在这之前:
view_depth_l0=float(l0); view_depth_l1=float(l1); return true; }
其余的着色器把l0,l1
作为相对值来处理l0,l1
结果是这样的:
对于高达10.0 AU的距离,现在没有问题(只有在非常高的放大倍数下才会出现伪影),新的伪影很可能在其他地方引起,所以当我得到时间和意志时,将不得不进一步研究。
将p0沿着dp移近(0,0,0)
实现需要相对昂贵的正规化和长度函数,没有范围移位的结果(edit1)比原始函数略好一些,但改进不是太大。 有了范围移位(edit1),结果和以前一样,所以这不是方法。 我的结论是,所有剩余的工件不是由视图部分function本身造成的。
我将尝试将着色器移至#version 400 + fp64
,以检查input数据是否未被float过多
[edit3]实际的源代码
#extension GL_ARB_gpu_shader_fp64 : enable double abs(double x) { if (x<0.0) x=-x; return x; } // compute length of ray(p0,dp) to intersection with ellipsoid((0,0,0),r) -> view_depth_l0,1 // where rx is elipsoid rx^-2, ry = ry^-2 and rz=rz^-2 float view_depth_ll= 0.0, // shift to boost accuracy view_depth_l0=-1.0, // view_depth_ll+view_depth_l0 first hit view_depth_l1=-1.0; // view_depth_ll+view_depth_l1 second hit const double view_depth_max=100000000.0; // > max view depth bool _view_depth(vec3 _p0,vec3 _dp,vec3 _r) { dvec3 p0,dp,r; double a,b,c,d,l0,l1; view_depth_ll= 0.0; view_depth_l0=-1.0; view_depth_l1=-1.0; // conversion to double p0=dvec3(_p0); dp=dvec3(_dp); r =dvec3(_r ); // quadratic equation all+b.l+c=0; l0,l1=?; a=(dp.x*dp.x*rx) +(dp.y*dp.y*ry) +(dp.z*dp.z*rz); b=(p0.x*dp.x*rx) +(p0.y*dp.y*ry) +(p0.z*dp.z*rz); b*=2.0; c=(p0.x*p0.x*rx) +(p0.y*p0.y*ry) +(p0.z*p0.z*rz)-1.0; // discriminant d=sqrt(bb-4.ac) d=((b*b)-(4.0*a*c)); if (d<0.0) return false; d=sqrt(d); // standard solution l0,l1=(-b +/- d)/2.a a*=2.0; l0=(-b+d)/a; l1=(-bd)/a; // alternative solution q=-0.5*(b+sign(b).d) l0=q/a; l1=c/q; (should be more accurate sometimes) // if (b<0.0) d=-d; d=-0.5*(b+d); // l0=d/a; // l1=c/d; // sort l0,l1 asc if (abs(l0)>abs(l1)) { a=l0; l0=l1; l1=a; } if (l0<0.0) { a=l0; l0=l1; l1=a; } if (l0<0.0) return false; // relative shift to preserve accuracy after conversion back float if (l0>view_depth_max){ a=floor(l0/view_depth_max)*view_depth_max; a-=view_depth_max; view_depth_ll=float(a); if (l1>l0) l1-=a; l0-=a; } // conversion back float view_depth_l0=float(l0); view_depth_l1=float(l1); return true; }
将着色器的其余部分移至双倍无效。 唯一可以改进的是double
input数据(input是double
float
,但GL将其转换为float
),但是我的当前GLSL HW不允许64 bit
内插器
Q2。 有没有办法从顶点到片段着色器传递double
插值器?
像varying dvec4 pixel_pos;
在旧式GLSL或out smooth dvec4 pixel_pos;
在核心configuration文件