Spe*_*tre 6 shader geometry artifacts glsl floating-accuracy
我需要提高矿井大气散射GLSL片段着色器之一的功能的精度,该着色器可计算单射线与轴对齐的椭球之间的交点。
这是矿山大气散射着色器的核心功能。旧的原始着色器已启用,floats并且可以正常渲染,但是添加缩放后,我发现距离相对较小时会失去精度。在浮子上,地球的可用距离仅为0.005 AU(天文单位)。因此,我尝试将关键功能移植到该端口上,double并且它有所帮助,因此现在可用距离约为1.0 AU(带有较小的伪像)
这是doubleFragment Shader中函数的版本(旧版本的源代码已弃用!!!)
#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 r.x is elipsoid rx^-2, r.y = ry^-2 and r.z=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*r.x)
+(dp.y*dp.y*r.y)
+(dp.z*dp.z*r.z); a*=2.0;
b=(p0.x*dp.x*r.x)
+(p0.y*dp.y*r.y)
+(p0.z*dp.z*r.z); b*=2.0;
c=(p0.x*p0.x*r.x)
+(p0.y*p0.y*r.y)
+(p0.z*p0.z*r.z)-1.0;
d=((b*b)-(2.0*a*c));
if (d<0.0) return false;
d=sqrt(d);
l0=(-b+d)/a;
l1=(-b-d)/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;
}
Run Code Online (Sandbox Code Playgroud)
输出是从p0到交点的距离

输入和输出变量的精度为浮点数(足够)
移植到Double后的外观如下

所以问题是:Q1。如何提高此功能的准确性?
view_depth_l0,view_depth_l1是+/- 20 m距离|p0|=100 AU那将是理想的,现在对于10 AU距离来说似乎是+/- 5 km,这很糟糕。即使是10倍的精确计算,对任何想法都将是巨大的进步?
[edit1] l0,l1范围
我错误的进行float转换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; }
Run Code Online (Sandbox Code Playgroud)
在这之前:
view_depth_l0=float(l0);
view_depth_l1=float(l1);
return true;
}
Run Code Online (Sandbox Code Playgroud)
l0,l1无论如何,着色器手柄的其余部分都将作为相对值,因此结果如下:

对于不超过10.0 AU的距离,现在就可以了(伪像仅在非常高的变焦下才可见),新伪像最有可能在其他地方引起,因此在我有时间和意愿的情况下必须进一步研究。
[edit2]将dp沿dp移近(0,0,0)
实现需要相对昂贵的归一化和长度函数,并且不进行范围移位(edit1)的结果要比原始函数好一些,但改进不是太大。使用范围平移(edit1)时,结果与之前相同,因此并非如此。我的结论是,所有剩余的工件不是由视图部门功能本身引起的。
我将尝试将着色器移植到#version 400 + fp64整个对象上,以检查输入数据是否没有过多地舍入为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 r.x is elipsoid rx^-2, r.y = ry^-2 and r.z=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 a.l.l+b.l+c=0; l0,l1=?;
a=(dp.x*dp.x*r.x)
+(dp.y*dp.y*r.y)
+(dp.z*dp.z*r.z);
b=(p0.x*dp.x*r.x)
+(p0.y*dp.y*r.y)
+(p0.z*dp.z*r.z); b*=2.0;
c=(p0.x*p0.x*r.x)
+(p0.y*p0.y*r.y)
+(p0.z*p0.z*r.z)-1.0;
// discriminant d=sqrt(b.b-4.a.c)
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=(-b-d)/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;
}
Run Code Online (Sandbox Code Playgroud)
将其余的着色器移植为两倍没有效果。唯一可以改善此问题的是double输入数据(输入是double但GL将其转换为float),但是当前的GLSL硬件不允许64 bit插补器
Q2。有没有办法将double插值器从顶点传递到片段着色器?
像varying dvec4 pixel_pos;在旧式GLSL或out smooth dvec4 pixel_pos;在核心配置文件