射线和椭球体相交精度提高

Ray and ellipsoid intersection accuracy improvement

提问人:Spektre 提问时间:8/24/2014 最后编辑:Hasan HaghniyaSpektre 更新时间:6/14/2022 访问量:2822

问:

我需要提高我的一个大气散射 GLSL 片段着色器的功能精度,该着色器计算单射线和轴对齐椭球体之间的交集。

这是矿山大气散射着色器的核心功能。旧的原始着色器已打开,正常渲染很好,但是在添加缩放后,我发现距离相对较小时,精度会丢失。在浮标上,地球的可用距离仅为0.005 AU(天文单位)。所以我尝试将关键函数移植到它,它很有帮助,所以现在可用距离约为 1.0 AU(有小伪影)floatsdouble

这是 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 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;
    }
  • 输入是椭球体的射线和半径^-2

  • 输出是从 p0 到交叉点的距离

    geometric overview

  • 输入和输出变量的精度是浮点的(就足够了)

这是移植到 Double 后的样子

artifacts

所以问题是:Q1。如何提高此功能的准确性?

  • 的目标精度 ,表示距离view_depth_l0view_depth_l1+/- 20 m|p0|=100 AU

这将是理想的,现在似乎是 +/- 5 公里的 10 AU 距离,这很糟糕。即使是 10 倍的精确计算也会向前迈出一大步,有什么想法吗?

[编辑1] 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

artifacts

对于高达10.0 AU的距离,现在很好(伪像只有在非常高的变焦下才明显),新的伪像很可能是在其他地方造成的,所以当我有时间和意愿时,必须进一步研究。

[编辑2] 沿 dp 向 (0,0,0) 移动 p0

实现需要相对昂贵的归一化和长度函数,没有范围偏移(edit1)的结果比原始函数好一点,但改进不是太大。使用范围偏移 (edit1) 时,结果与以前相同,因此这不是方式。我的结论是,所有剩余的伪影都不是由视图部门功能本身引起的。

我将尝试将着色器移植到整个事情上,以检查输入数据是否没有被浮点数舍入太多#version 400 + fp64

[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;
    }

将着色器的其余部分移植到 double 不起作用。唯一可以改善这一点的是输入数据(输入是,但 GL 将其转换为 ),但当前 GLSL HW 不允许插值器doubledoublefloat64 bit

问题2.有没有办法将插值器从顶点传递到片段着色器?

类似于旧式 GLSL 或核心配置文件varying dvec4 pixel_pos;out smooth dvec4 pixel_pos;

几何体 GLSL 着色器 浮动精度 伪影

评论

4赞 Sneftel 8/24/2014
二次方程的标准公式没有很好的数值稳定性。请参阅 aip.de/groups/soe/local/numres/bookfpdf/f5-6.pdf,了解计算根的更好方法。
3赞 Spektre 8/24/2014
@Sneftel(+1)不知道这一点。但是在实现它之后,结果看起来是一样的。所以问题出在别处......(可能是三点产品)我认为将点 p0 移近椭球体(仅当太远时)可以解决问题,稍后将进行调查

答: 暂无答案