没有递归光线追踪,反射和折射是不可能的?

时间:2017-03-18 16:02:15

标签: c++ opengl recursion glsl raytracing

我正在使用GLSL计算着色器编写基于GPU的实时光线跟踪渲染器。到目前为止,它的效果非常好,但是当涉及到同时进行反射和折射时,我偶然发现了一个看似无法解决的问题。

我的逻辑告诉我,为了对物体(如玻璃)进行反射和折射,光线必须分成两条,一条光线从表面反射,另一条光线通过表面折射。然后,这些光线的最终颜色将基于某些功能进行组合,并最终用作光线源自的像素的颜色。我遇到的问题是我无法在着色器代码中分割光线,因为我必须使用递归来实现这一点。根据我的理解,着色器中的函数不能递归,因为由于旧GPU硬件的兼容性问题,所有GLSL函数都像C ++中的内联函数。

是否可以在着色器代码中模拟或伪造递归,或者我甚至可以在不使用递归的情况下同时实现反射和折射?我看不出没有递归会怎么样,但我可能错了。

1 个答案:

答案 0 :(得分:3)

我设法使用我的评论中建议的方法将 back-raytracing 转换为适用于 GLSL 的迭代过程。它远非优化,我没有实现所有物理内容(没有Snell法则......)但作为概念证明它已经有效。我在片段着色器和 CPU 端代码中执行所有操作只需以 32位非钳位浮点纹理 {{1}的形式发送uniforms常量和场景渲染只是覆盖整个屏幕的单GL_LUMINANCE32F_ARB

  1. 传递场景

    我决定将场景存储在纹理中,这样每个光线/片段都可以直接访问整个场景。纹理 2D 但它用作32位浮点数的线性列表。我决定这种格式:

    QUAD

    您可以添加/更改任何类型的对象。这个例子只包含一个半透明的蓝色四面体。您还可以为材料属性等添加变换矩阵更多系数...

  2. <强>建筑

    顶点着色器只是初始化视角的角落光线(开始位置和方向),这是内插的,因此每个片段代表背光线跟踪过程的起始光线。

  3. 迭代背光线追踪

    所以我创建了一个&#34;静态&#34;光线列表并使用起始光线初始化它。迭代首先通过两个步骤完成背光跟踪:

    1. 在第一个
    2. 的列表中循环遍历所有光线
    3. 使用场景找到最近的交叉点

      将位置,曲面法线和材质属性存储到光线enum _fac_type_enum { _fac_triangles=0, // r,g,b,a, n, triangle count, { x0,y0,z0,x1,y1,z1,x2,y2,z2 } _fac_spheres, // r,g,b,a, n, sphere count, { x,y,z,r } }; const GLfloat _n_glass=1.561; const GLfloat _n_vacuum=1.0; GLfloat data[]= { // r, g, b, a, n, type,count 0.2,0.3,0.5,0.5,_n_glass,_fac_triangles, 4, // tetrahedron // px, py, pz, r, g, b -0.5,-0.5,+1.0, 0.0,+0.5,+1.0, +0.5,-0.5,+1.0, 0.0, 0.0,+0.5, -0.5,-0.5,+1.0, 0.0,+0.5,+1.0, 0.0, 0.0,+0.5, 0.0,+0.5,+1.0, +0.5,-0.5,+1.0, 0.0, 0.0,+0.5, +0.5,-0.5,+1.0, -0.5,-0.5,+1.0, };

    4. 如果发现交叉点而不是最后一次&#34;递归&#34;图层添加反射/折射光线到最后列出。

      还将其索引存储到已处理的光线struct

    5. 现在你的光线应该包含重建颜色所需的所有交叉信息。要做到这一点:

      1. 向后循环所有递归级别
      2. 匹配实际递归图层的每条光线
      3. 计算光线颜色

        所以使用你想要的照明方程式。如果光线包含儿童,则根据材料属性(反射和折射系数......)将颜色添加到结果中

      4. 现在第一条光线应包含您想要输出的颜色。

        使用的制服:


        struct查看相机矩阵
        tm_eye查看ys / xs宽高比
        aspect空的空间折射率(尚未使用)
        n0相机焦距
        focal_length分辨场景方形纹理
        fac_siz场景纹理中实际使用的浮点数
        fac_num场景纹理的纹理单元

        预览

        preview

        片段着色器包含我的调试打印,因此如果使用的话还需要纹理,请参阅QA:

        <强> TODO:

        为对象,相机等添加矩阵
        添加材料属性(光泽度,反射/折射系数)
        Snell's law 现在新射线的方向是错误的......
        可以将 R,G,B 分开到3个起始射线并在末端组合
        SSS 基于射线长度的次表面散射
        更好地实现灯光(现在它们是代码中的常量) 实现更多基元(现在只支持三角形)

        [Edit1]代码调试和升级

        我删除旧源代码以适应30KB限制。如果您需要,请从编辑历史中挖掘它。有一些时间进行更高级的调试,结果如下:

        preview

        此版本解决了一些几何,准确性,域问题和错误。我实现了反射和折射,如测试光线的调试绘图所示:

        debug view

        在调试视图中,只有多维数据集是透明的,并且忽略没有命中任何内容的最后一条光线。因此,您可以看到光线分裂...由于全反射角度,光线在内部立方体内结束。出于速度原因,我禁用了对象内部的所有反射。

        用于交叉检测的32位fac_txr对距离有点嘈杂,因此您可以使用64位floats代替但在这种情况下速度会大幅下降。另一种选择是重写方程式以使用相对坐标,在这种情况下使用相对坐标更精确。

        此处doubles着色器来源:

        <强>顶点:

         
        float

        <强>片段:

         
        //------------------------------------------------------------------
        #version 420 core
        //------------------------------------------------------------------
        uniform float aspect;
        uniform float focal_length;
        uniform mat4x4 tm_eye;
        layout(location=0) in vec2 pos;
        
        out smooth vec2 txt_pos;    // frag position on screen <-1,+1> for debug prints
        out smooth vec3 ray_pos;    // ray start position
        out smooth vec3 ray_dir;    // ray start direction
        //------------------------------------------------------------------
        void main(void)
            {
            vec4 p;
            txt_pos=pos;
            // perspective projection
            p=tm_eye*vec4(pos.x/aspect,pos.y,0.0,1.0);
            ray_pos=p.xyz;
            p-=tm_eye*vec4(0.0,0.0,-focal_length,1.0);
            ray_dir=normalize(p.xyz);
        
            gl_Position=vec4(pos,0.0,1.0);
            }
        //------------------------------------------------------------------
        

        代码尚未优化但我希望物理工作正常。仍然没有实现Fresnells,而是使用//------------------------------------------------------------------ #version 420 core //------------------------------------------------------------------ // Ray tracer ver: 1.000 //------------------------------------------------------------------ in smooth vec3 ray_pos; // ray start position in smooth vec3 ray_dir; // ray start direction uniform float n0; // refractive index of camera origin uniform int fac_siz; // square texture x,y resolution size uniform int fac_num; // number of valid floats in texture uniform sampler2D fac_txr; // scene mesh data texture out layout(location=0) vec4 frag_col; //--------------------------------------------------------------------------- //#define _debug_print #define _reflect #define _refract //--------------------------------------------------------------------------- #ifdef _debug_print in vec2 txt_pos; // frag screen position <-1,+1> uniform sampler2D txr_font; // ASCII 32x8 characters font texture unit uniform float txt_fxs,txt_fys; // font/screen resolution ratio const int _txtsiz=64; // text buffer size int txt[_txtsiz],txtsiz; // text buffer and its actual size vec4 txt_col=vec4(0.0,0.0,0.0,1.0); // color interface for txt_print() bool _txt_col=false; // is txt_col active? void txt_decimal(vec2 v); // print vec3 into txt void txt_decimal(vec3 v); // print vec3 into txt void txt_decimal(vec4 v); // print vec3 into txt void txt_decimal(float x); // print float x into txt void txt_decimal(int x); // print int x into txt void txt_print(float x0,float y0); // print txt at x0,y0 [chars] #endif //--------------------------------------------------------------------------- void main(void) { const vec3 light_dir=normalize(vec3(0.1,0.1,1.0)); const float light_iamb=0.1; // dot offset const float light_idir=0.5; // directional light amplitude const vec3 back_col=vec3(0.2,0.2,0.2); // background color const float _zero=1e-6; // to avoid intrsection with start point of ray const int _fac_triangles=0; // r,g,b, refl,refr,n, type, triangle count, { x0,y0,z0,x1,y1,z1,x2,y2,z2 } const int _fac_spheres =1; // r,g,b, refl,refr,n, type, sphere count, { x,y,z,r } // ray scene intersection struct _ray { vec3 pos,dir,nor; vec3 col; float refl,refr;// reflection,refraction intensity coeficients float n0,n1,l; // refaction index (start,end) , ray length int lvl,i0,i1; // recursion level, reflect, refract }; const int _lvls=5; const int _rays=(1<<_lvls)-1; _ray ray[_rays]; int rays; vec3 v0,v1,v2,pos; vec3 c,col; float refr,refl; float tt,t,n1,a; int i0,ii,num,id; // fac texture access vec2 st; int i,j; float ds=1.0/float(fac_siz-1); #define fac_get texture(fac_txr,st).r; st.s+=ds; i++; j++; if (j==fac_siz) { j=0; st.s=0.0; st.t+=ds; } // enque start ray ray[0].pos=ray_pos; ray[0].dir=normalize(ray_dir); ray[0].nor=vec3(0.0,0.0,0.0); ray[0].refl=0.0; ray[0].refr=0.0; ray[0].n0=n0; ray[0].n1=1.0; ray[0].l =0.0; ray[0].lvl=0; ray[0].i0=-1; ray[0].i1=-1; rays=1; // debug print area #ifdef _debug_print bool _dbg=false; float dbg_x0=45.0; float dbg_y0= 1.0; float dbg_xs=12.0; float dbg_ys=_rays+1.0; dbg_xs=40.0; dbg_ys=10; float x=0.5*(1.0+txt_pos.x)/txt_fxs; x-=dbg_x0; float y=0.5*(1.0-txt_pos.y)/txt_fys; y-=dbg_y0; // inside bbox? if ((x>=0.0)&&(x<=dbg_xs) &&(y>=0.0)&&(y<=dbg_ys)) { // prints on _dbg=true; // preset debug ray ray[0].pos=vec3(0.0,0.0,0.0)*2.5; ray[0].dir=vec3(0.0,0.0,1.0); } #endif // loop all enqued rays for (i0=0;i0<rays;i0++) { // loop through all objects // find closest forward intersection between them and ray[i0] // strore it to ray[i0].(nor,col) // strore it to pos,n1 t=tt=-1.0; ii=1; ray[i0].l=0.0; ray[i0].col=back_col; pos=ray[i0].pos; n1=n0; for (st=vec2(0.0,0.0),i=j=0;i<fac_num;) { c.r=fac_get; // RGBA c.g=fac_get; c.b=fac_get; refl=fac_get; refr=fac_get; n1=fac_get; // refraction index a=fac_get; id=int(a); // object type a=fac_get; num=int(a); // face count if (id==_fac_triangles) for (;num>0;num--) { v0.x=fac_get; v0.y=fac_get; v0.z=fac_get; v1.x=fac_get; v1.y=fac_get; v1.z=fac_get; v2.x=fac_get; v2.y=fac_get; v2.z=fac_get; vec3 e1,e2,n,p,q,r; float t,u,v,det,idet; //compute ray triangle intersection e1=v1-v0; e2=v2-v0; // Calculate planes normal vector p=cross(ray[i0].dir,e2); det=dot(e1,p); // Ray is parallel to plane if (abs(det)<1e-8) continue; idet=1.0/det; r=ray[i0].pos-v0; u=dot(r,p)*idet; if ((u<0.0)||(u>1.0)) continue; q=cross(r,e1); v=dot(ray[i0].dir,q)*idet; if ((v<0.0)||(u+v>1.0)) continue; t=dot(e2,q)*idet; if ((t>_zero)&&((t<=tt)||(ii!=0))) { ii=0; tt=t; // store color,n ... ray[i0].col=c; ray[i0].refl=refl; ray[i0].refr=refr; // barycentric interpolate position t=1.0-u-v; pos=(v0*t)+(v1*u)+(v2*v); // compute normal (store as dir for now) e1=v1-v0; e2=v2-v1; ray[i0].nor=cross(e1,e2); } } if (id==_fac_spheres) for (;num>0;num--) { float r; v0.x=fac_get; v0.y=fac_get; v0.z=fac_get; r=fac_get; // compute l0 length of ray(p0,dp) to intersection with sphere(v0,r) // where rr= r^-2 float aa,bb,cc,dd,l0,l1,rr; vec3 p0,dp; p0=ray[i0].pos-v0; // set sphere center to (0,0,0) dp=ray[i0].dir; rr = 1.0/(r*r); aa=2.0*rr*dot(dp,dp); bb=2.0*rr*dot(p0,dp); cc= rr*dot(p0,p0)-1.0; dd=((bb*bb)-(2.0*aa*cc)); if (dd<0.0) continue; dd=sqrt(dd); l0=(-bb+dd)/aa; l1=(-bb-dd)/aa; if (l0<0.0) l0=l1; if (l1<0.0) l1=l0; t=min(l0,l1); if (t<=_zero) t=max(l0,l1); if ((t>_zero)&&((t<=tt)||(ii!=0))) { ii=0; tt=t; // store color,n ... ray[i0].col=c; ray[i0].refl=refl; ray[i0].refr=refr; // position,normal pos=ray[i0].pos+(ray[i0].dir*t); ray[i0].nor=pos-v0; } } } ray[i0].l=tt; ray[i0].nor=normalize(ray[i0].nor); // split ray from pos and ray[i0].nor if ((ii==0)&&(ray[i0].lvl<_lvls-1)) { t=dot(ray[i0].dir,ray[i0].nor); // reflect #ifdef _reflect if ((ray[i0].refl>_zero)&&(t<_zero)) // do not reflect inside objects { ray[i0].i0=rays; ray[rays]=ray[i0]; ray[rays].lvl++; ray[rays].i0=-1; ray[rays].i1=-1; ray[rays].pos=pos; ray[rays].dir=ray[rays].dir-(2.0*t*ray[rays].nor); ray[rays].n0=ray[i0].n0; ray[rays].n1=ray[i0].n0; rays++; } #endif // refract #ifdef _refract if (ray[i0].refr>_zero) { ray[i0].i1=rays; ray[rays]=ray[i0]; ray[rays].lvl++; ray[rays].i0=-1; ray[rays].i1=-1; ray[rays].pos=pos; t=dot(ray[i0].dir,ray[i0].nor); if (t>0.0) // exit object { ray[rays].n0=ray[i0].n0; ray[rays].n1=n0; v0=-ray[i0].nor; t=-t; } else{ // enter object ray[rays].n0=n1; ray[rays].n1=ray[i0].n0; ray[i0 ].n1=n1; v0=ray[i0].nor; } n1=ray[i0].n0/ray[i0].n1; tt=1.0-(n1*n1*(1.0-t*t)); if (tt>=0.0) { ray[rays].dir=(ray[i0].dir*n1)-(v0*((n1*t)+sqrt(tt))); rays++; } } #endif } else if (i0>0) // ignore last ray if nothing hit { ray[i0]=ray[rays-1]; rays--; i0--; } } // back track ray intersections and compute output color col // lvl is sorted ascending so backtrack from end for (i0=rays-1;i0>=0;i0--) { // directional + ambient light t=abs(dot(ray[i0].nor,light_dir)*light_idir)+light_iamb; t*=1.0-ray[i0].refl-ray[i0].refr; ray[i0].col.rgb*=t; // reflect ii=ray[i0].i0; if (ii>=0) ray[i0].col.rgb+=ray[ii].col.rgb*ray[i0].refl; // refract ii=ray[i0].i1; if (ii>=0) ray[i0].col.rgb+=ray[ii].col.rgb*ray[i0].refr; } col=ray[0].col; // debug prints #ifdef _debug_print /* if (_dbg) { txtsiz=0; txt_decimal(_lvls); txt[txtsiz]=' '; txtsiz++; txt_decimal(rays); txt[txtsiz]=' '; txtsiz++; txt_decimal(_rays); txt_print(dbg_x0,dbg_y0); for (ii=0;ii<rays;ii++) { txtsiz=0; txt_decimal(ray[ii].lvl); txt_print(dbg_x0,dbg_y0+ii+1); } for (ii=0,st=vec2(0.0,0.0),i=j=0;i<fac_num;ii++) { c.r=fac_get; // RGBA txtsiz=0; txt_decimal(c.r); txt_print(dbg_x0,dbg_y0+ii+1); } if (_txt_col) col=txt_col.rgb; } */ if (_dbg) { float x=dbg_x0,y=dbg_y0; vec3 a=vec3(1.0,2.0,3.0); vec3 b=vec3(5.0,6.0,7.0); txtsiz=0; txt_decimal(dot(a,b)); txt_print(x,y); y++; txtsiz=0; txt_decimal(cross(a,b)); txt_print(x,y); y++; if (_txt_col) col=txt_col.rgb; } #endif frag_col=vec4(col,1.0); } //--------------------------------------------------------------------------- #ifdef _debug_print //--------------------------------------------------------------------------- void txt_decimal(vec2 v) // print vec2 into txt { txt[txtsiz]='('; txtsiz++; txt_decimal(v.x); txt[txtsiz]=','; txtsiz++; txt_decimal(v.y); txt[txtsiz]=')'; txtsiz++; txt[txtsiz]=0; // string terminator } //--------------------------------------------------------------------------- void txt_decimal(vec3 v) // print vec3 into txt { txt[txtsiz]='('; txtsiz++; txt_decimal(v.x); txt[txtsiz]=','; txtsiz++; txt_decimal(v.y); txt[txtsiz]=','; txtsiz++; txt_decimal(v.z); txt[txtsiz]=')'; txtsiz++; txt[txtsiz]=0; // string terminator } //--------------------------------------------------------------------------- void txt_decimal(vec4 v) // print vec4 into txt { txt[txtsiz]='('; txtsiz++; txt_decimal(v.x); txt[txtsiz]=','; txtsiz++; txt_decimal(v.y); txt[txtsiz]=','; txtsiz++; txt_decimal(v.z); txt[txtsiz]=','; txtsiz++; txt_decimal(v.w); txt[txtsiz]=')'; txtsiz++; txt[txtsiz]=0; // string terminator } //--------------------------------------------------------------------------- void txt_decimal(float x) // print float x into txt { int i,j,c; // l is size of string float y,a; const float base=10; // handle sign if (x<0.0) { txt[txtsiz]='-'; txtsiz++; x=-x; } else { txt[txtsiz]='+'; txtsiz++; } // divide to int(x).fract(y) parts of number y=x; x=floor(x); y-=x; // handle integer part i=txtsiz; // start of integer part for (;txtsiz<_txtsiz;) { a=x; x=floor(x/base); a-=base*x; txt[txtsiz]=int(a)+'0'; txtsiz++; if (x<=0.0) break; } j=txtsiz-1; // end of integer part for (;i<j;i++,j--) // reverse integer digits { c=txt[i]; txt[i]=txt[j]; txt[j]=c; } // handle fractional part for (txt[txtsiz]='.',txtsiz++;txtsiz<_txtsiz;) { y*=base; a=floor(y); y-=a; txt[txtsiz]=int(a)+'0'; txtsiz++; if (y<=0.0) break; } txt[txtsiz]=0; // string terminator } //--------------------------------------------------------------------------- void txt_decimal(int x) // print int x into txt { int a,i,j,c; // l is size of string const int base=10; // handle sign if (x<0.0) { txt[txtsiz]='-'; txtsiz++; x=-x; } else { txt[txtsiz]='+'; txtsiz++; } // handle integer part i=txtsiz; // start of integer part for (;txtsiz<_txtsiz;) { a=x; x/=base; a-=base*x; txt[txtsiz]=int(a)+'0'; txtsiz++; if (x<=0) break; } j=txtsiz-1; // end of integer part for (;i<j;i++,j--) // reverse integer digits { c=txt[i]; txt[i]=txt[j]; txt[j]=c; } txt[txtsiz]=0; // string terminator } //--------------------------------------------------------------------------- void txt_print(float x0,float y0) // print txt at x0,y0 [chars] { int i; float x,y; // fragment position [chars] relative to x0,y0 x=0.5*(1.0+txt_pos.x)/txt_fxs; x-=x0; y=0.5*(1.0-txt_pos.y)/txt_fys; y-=y0; // inside bbox? if ((x<0.0)||(x>float(txtsiz))||(y<0.0)||(y>1.0)) return; // get font texture position for target ASCII i=int(x); // char index in txt x-=float(i); i=txt[i]; x+=float(int(i&31)); y+=float(int(i>>5)); x/=32.0; y/=8.0; // offset in char texture txt_col=texture(txr_font,vec2(x,y)); _txt_col=true; } //--------------------------------------------------------------------------- #endif //--------------------------------------------------------------------------- 材料系数。

        此外,您可以忽略调试打印内容(它们由refl,refr封装)。

        我为几何纹理构建了一个小类,因此我可以轻松设置场景对象。这是为预览启动场景的方式:

        #define

        重要的是计算出的法线面向物体,因为它用于检测内部/外部物体交叉。

        <强> P.S。

        如果您对此感兴趣,请参阅我的体积3D背光跟踪器: