射线和椭球相交准确度的提高

我需要在我的一个大气散射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_l0view_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; } 

将着色器的其余部分移至双倍无效。 唯一可以改进的是doubleinput数据(input是double float ,但GL将其转换为float ),但是我的当前GLSL HW不允许64 bit内插器

Q2。 有没有办法从顶点到片段着色器传递double插值器?

varying dvec4 pixel_pos; 在旧式GLSL或out smooth dvec4 pixel_pos; 在核心configuration文件

Interesting Posts