没有递归光线追踪就不可能反射和折射? [英] Reflection and refraction impossible without recursive ray tracing?

查看:50
本文介绍了没有递归光线追踪就不可能反射和折射?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

解决方案

我设法将 back-raytracing 转换为适用于 GLSL 的迭代过程评论.它离优化还很远,我还没有实现所有的物理东西(没有斯内尔定律等......),但作为概念证明它已经可以工作了.我在片段着色器和 CPU 侧代码中做所有的事情,只是以 32 位非钳位浮点纹理 的形式发送 uniforms 常量和场景GL_LUMINANCE32F_ARB 渲染只是一个QUAD覆盖整个屏幕.

  1. 穿越场景

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

 enum _fac_type_enum{_fac_triangles=0,//r,g,b,a, n, 三角形计数, { x0,y0,z0,x1,y1,z1,x2,y2,z2 }_fac_spheres,//r,g,b,a, n, 球体计数, { x,y,z,r }};const GLfloat _n_glass=1.561;const GLfloat _n_vacuum=1.0;GLfloat 数据[]={//r, g, b, a, n, type,count0.2,0.3,0.5,0.5,_n_glass,_fac_triangles, 4,//四面体//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,};

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

  1. 架构

顶点着色器只是初始化视图的角光线(开始位置和方向),它被插值,所以每个片段代表反向光线追踪过程的开始光线.

迭代反向光线追踪

所以我创建了一个静态"光线列表并使用起始光线对其进行初始化.Iteration 分两步完成,首先是反向光线追踪:

  1. 从第一个列表中循环遍历所有光线
  2. 找到最近的路口与场景...

将位置、表面法线和材料属性存储到ray struct

  1. 如果找到交集并且不是最后一次递归"图层添加反射/折射光线以列在最后.

还将它们的索引存储到处理过的光线 struct

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

  1. 向后遍历所有递归级别
  2. 对于匹配实际递归层的每条光线
  3. 计算光线颜色

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

现在第一条光线应该包含你想要输出的颜色.

使用的制服:


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

预览:

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

  • 这个版本解决了一些几何、精度、域问题和错误.我实现了反射和折射,如测试射线的调试绘图所示:

    在调试视图中,只有立方体是透明的,最后一条没有击中任何物体的光线被忽略.所以你可以看到光线分裂......由于全反射角度,光线在立方体内部结束并且出于速度原因我禁用了物体内部的所有反射.

    用于交叉路口检测的 32 位 floats 与距离有关,因此您可以使用 64 位 doubles 代替,但在这种情况下速度会显着下降.另一种选择是重写方程以使用在这种情况下更精确的相对坐标.

    这里是 float 着色器源:

    顶点:

    //------------------------------------------------------------------#version 420 核心//------------------------------------------------------------------统一的浮动方面;统一浮点焦距;统一 mat4x4 tm_eye;vec2 pos 中的布局(位置 = 0);输出平滑 vec2 txt_pos;//屏幕上的碎片位置 <-1,+1>用于调试打印输出平滑 vec3 ray_pos;//射线开始位置输出平滑 vec3 ray_dir;//射线开始方向//------------------------------------------------------------------无效主(无效){vec4 p;txt_pos=pos;//透视投影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);}//------------------------------------------------------------------

    片段:

    //------------------------------------------------------------------#version 420 核心//------------------------------------------------------------------//光线追踪器版本:1.000//------------------------------------------------------------------在平滑的 vec3 ray_pos 中;//射线开始位置在平滑的 vec3 ray_dir 中;//射线开始方向统一浮点数 n0;//相机原点的折射率统一 int fac_siz;//方形纹理 x,y 分辨率大小统一 int fac_num;//纹理中有效浮点数统一的 sampler2D fac_txr;//场景网格数据纹理out layout(location=0) vec4 frag_col;//---------------------------------------------------------------------------//#define _debug_print#define _reflect#define _refract//---------------------------------------------------------------------------#ifdef _debug_print在 vec2 txt_pos 中;//碎片屏幕位置 <-1,+1>统一的 sampler2D txr_font;//ASCII 32x8 字符字体纹理单元统一浮点 txt_fxs,txt_fys;//字体/屏幕分辨率比const int _txtsiz=64;//文本缓冲区大小int txt[_txtsiz],txtsiz;//文本缓冲区及其实际大小vec4 txt_col=vec4(0.0,0.0,0.0,1.0);//txt_print() 的颜色界面bool _txt_col=false;//txt_col 是否激活?void txt_decimal(vec2 v);//将 vec3 打印到 txtvoid txt_decimal(vec3 v);//将 vec3 打印到 txtvoid txt_decimal(vec4 v);//将 vec3 打印到 txtvoid txt_decimal(float x);//将 float x 打印到 txtvoid txt_decimal(int x);//将 int x 打印到 txtvoid txt_print(float x0,float y0);//在 x0,y0 [chars] 处打印 txt#万一//---------------------------------------------------------------------------无效主(无效){const vec3 light_dir=normalize(vec3(0.1,0.1,1.0));const float light_iamb=0.1;//点偏移const float light_idir=0.5;//定向光振幅const vec3 back_col=vec3(0.2,0.2,0.2);//背景颜色const float _zero=1e-6;//避免与射线起点相交const int _fac_triangles=0;//r,g,b, refl,refr,n, 类型, 三角形计数, { x0,y0,z0,x1,y1,z1,x2,y2,z2 }const int _fac_spheres =1;//r,g,b, refl,refr,n, 类型, 球数, { x,y,z,r }//光线场景交点结构_射线{vec3 pos,dir,nor;vec3 列;float refl,refr;//反射、折射强度系数浮动 n0,n1,l;//折射指数 (start,end) , 射线长度int lvl,i0,i1;//递归级别,反射,折射};const int _lvls=5;const int _rays=(1<<_lvls)-1;_ray 射线[_rays];内部光线;vec3 v0,v1,v2,pos;vec3 c,col;浮动参考,参考;浮动 tt,t,n1,a;int i0,ii,num,id;//fac 纹理访问vec2 st;int i,j;float ds=1.0/float(fac_siz-1);#define fac_get 纹理(fac_txr,st).r;st.s+=ds;我++;j++;如果 (j==fac_siz) { j=0;st.s=0.0;st.t+=ds;}//入队开始射线射线[0].pos=ray_pos;ray[0].dir=normalize(ray_dir);ray[0].nor=vec3(0.0,0.0,0.0);射线[0].refl=0.0;射线[0].refr=0.0;射线[0].n0=n0;射线[0].n1=1.0;射线[0].l = 0.0;射线[0].lvl=0;射线[0].i0=-1;射线[0].i1=-1;射线=1;//调试打印区域#ifdef _debug_printbool _dbg=false;浮动 dbg_x0=45.0;浮动 dbg_y0 = 1.0;浮动 dbg_xs=12.0;浮动 dbg_ys=_rays+1.0;dbg_xs=40.0;dbg_ys=10;浮动 x=0.5*(1.0+txt_pos.x)/txt_fxs;x-=dbg_x0;浮动 y=0.5*(1.0-txt_pos.y)/txt_fys;y-=dbg_y0;//在 bbox 里面?如果 ((x>=0.0)&&(x<=dbg_xs)&&(y>=0.0)&&(y<=dbg_ys)){//打印在_dbg=真;//预设调试射线ray[0].pos=vec3(0.0,0.0,0.0)*2.5;ray[0].dir=vec3(0.0,0.0,1.0);}#万一//循环所有排队的光线对于 (i0=0;i0<光线;i0++){//遍历所有对象//找到它们与 ray[i0] 之间最近的前向交点//将其存储到 ray[i0].(nor,col)//将其存储到 pos,n1t=tt=-1.0;ii=1;射线[i0].l=0.0;ray[i0].col=back_col;pos=ray[i0].pos;n1=n0;对于 (st=vec2(0.0,0.0),i=j=0;i0;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;浮动 t,u,v,det,idet;//计算射线三角形交点e1=v1-v0;e2=v2-v0;//计算平面法向量p=cross(ray[i0].dir,e2);det=dot(e1,p);//射线平行于平面如果 (abs(det)<1e-8) 继续;idet=1.0/det;r=ray[i0].pos-v0;u=dot(r,p)*idet;如果 ((u<0.0)||(u>1.0)) 继续;q=cross(r,e1);v=dot(ray[i0].dir,q)*idet;如果 ((v<0.0)||(u+v>1.0)) 继续;t=dot(e2,q)*idet;if ((t>_zero)&&((t<=tt)||(ii!=0))){ii=0;tt=t;//存储颜色,n ...射线[i0].col=c;射线[i0].refl=refl;射线[i0].refr=refr;//重心插值位置t=1.0-u-v;pos=(v0*t)+(v1*u)+(v2*v);//计算正常(暂时存储为目录)e1=v1-v0;e2=v2-v1;ray[i0].nor=cross(e1,e2);}}如果(id==_fac_spheres)for (;num>0;num--){浮动 r;v0.x=fac_get;v0.y=fac_get;v0.z=fac_get;r=fac_get;//计算与球体(v0,r)相交的光线(p0,dp)的l0长度//其中 rr= r^-2浮动 aa,bb,cc,dd,l0,l1,rr;vec3 p0,dp;p0=ray[i0].pos-v0;//设置球体中心为 (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));如果 (dd<0.0) 继续;dd=sqrt(dd);l0=(-bb+dd)/aa;l1=(-bb-dd)/aa;如果(l0<0.0)l0=l1;如果(l1<0.0)l1=l0;t=min(l0,l1);如果 (t<=_zero) t=max(l0,l1);if ((t>_zero)&&((t<=tt)||(ii!=0))){ii=0;tt=t;//存储颜色,n ...射线[i0].col=c;射线[i0].refl=refl;射线[i0].refr=refr;//位置,正常pos=ray[i0].pos+(ray[i0].dir*t);射线[i0].nor=pos-v0;}}}射线[i0].l=tt;ray[i0].nor=normalize(ray[i0].nor);//从 pos 和 ray[i0].nor 拆分光线if ((ii==0)&&(ray[i0].lvl<_lvls-1)){t=dot(ray[i0].dir,ray[i0].nor);//反映#ifdef _reflectif ((ray[i0].refl>_zero)&&(t<_zero))//不反射内部对象{射线[i0].i0=射线;射线[射线]=射线[i0];射线[射线].lvl++;射线[射线].i0=-1;射线[射线].i1=-1;射线[射线].pos=pos;ray[rays].dir=ray[rays].dir-(2.0*t*ray[rays].nor);射线[射线].n0=射线[i0].n0;射线[射线].n1=射线[i0].n0;射线++;}#万一//折射#ifdef _refract如果 (ray[i0].refr>_zero){射线[i0].i1=射线;射线[射线]=射线[i0];射线[射线].lvl++;射线[射线].i0=-1;射线[射线].i1=-1;射线[射线].pos=pos;t=dot(ray[i0].dir,ray[i0].nor);if (t>0.0)//退出对象{射线[射线].n0=射线[i0].n0;射线[射线].n1=n0;v0=-ray[i0].nor;t=-t;}else{//输入对象射线[射线].n0=n1;射线[射线].n1=射线[i0].n0;射线[i0].n1=n1;v0=ray[i0].nor;}n1=射线[i0].n0/射线[i0].n1;tt=1.0-(n1*n1*(1.0-t*t));如果 (tt>=0.0){ray[rays].dir=(ray[i0].dir*n1)-(v0*((n1*t)+sqrt(tt)));射线++;}}#万一}else if (i0>0)//如果没有命中则忽略最后一条射线{射线[i0]=射线[射线-1];射线--;i0--;}}//回溯光线交叉点并计算输出颜色 col//lvl 升序排序,所以从末尾回溯对于 (i0=rays-1;i0>=0;i0--){//定向+环境光t=abs(dot(ray[i0].nor,light_dir)*light_idir)+light_iamb;t*=1.0-ray[i0].refl-ray[i0].refr;射线[i0].col.rgb*=t;//反映ii=射线[i0].i0;如果 (ii>=0) ray[i0].col.rgb+=ray[ii].col.rgb*ray[i0].refl;//折射ii=射线[i0].i1;如果 (ii>=0) ray[i0].col.rgb+=ray[ii].col.rgb*ray[i0].refr;}col=ray[0].col;//调试打印#ifdef _debug_print/*如果(_dbg){txtsiz=0;txt_decimal(_lvls);txt[txtsiz]='';txt 大小++;txt_decimal(射线);txt[txtsiz]='';txt 大小++;txt_decimal(_rays);txt_print(dbg_x0,dbg_y0);对于 (ii=0;iifloat(txtsiz))||(y<0.0)||(y>1.0)) 返回;//获取目标 ASCII 的字体纹理位置i=int(x);//txt 中的字符索引x-=浮点(i);i=txt[i];x+=float(int(i&31));y+=float(int(i>>5));x/=32.0;y/=8.0;//字符纹理中的偏移量txt_col=texture(txr_font,vec2(x,y));_txt_col=true;}//---------------------------------------------------------------------------#万一//---------------------------------------------------------------------------

    代码尚未优化,但我想让物理首先正常工作.仍然没有实现 Fresnell,而是使用了 refl,refr 材料系数.

    你也可以忽略调试打印的东西(它们被#define封装).

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

    ray.beg();//r g b rfl rfr nray.add_material(1.0,1.0,1.0,0.3,0.0,_n_glass);ray.add_box ( 0.0, 0.0, 6.0,9.0,9.0,0.1);ray.add_material(1.0,1.0,1.0,0.1,0.8,_n_glass);ray.add_sphere(0.0, 0.0, 0.5,0.5);ray.add_material(1.0,0.1,0.1,0.3,0.0,_n_glass);ray.add_sphere(+2.0, 0.0, 2.0,0.5);ray.add_material(0.1,1.0,0.1,0.3,0.0,_n_glass);ray.add_box (-2.0, 0.0, 2.0,0.5,0.5,0.5);ray.add_material(0.1,0.1,1.0,0.3,0.0,_n_glass);ray.add_tetrahedron(0.0, 0.0, 3.0,-1.0,-1.0, 4.0,+1.0,-1.0, 4.0,0.0、+1.0、4.0);射线.end();

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

    附言

    如果你感兴趣,这里是我的立体 3D 背光线追踪器:

    这里是这个Mesh"的新版本支持半球对象的光线追踪器:

    I am writing a GPU-based real-time raytracing renderer using a GLSL compute shader. So far, it works really well, but I have stumbled into a seemingly unsolvable problem when it comes to having both reflections and refractions simultaneously.

    My logic tells me that in order to have reflections and refractions on an object, such as glass, the ray would have to split into two, one ray reflects off the surface, and the other refracts through the surface. The ultimate colours of these rays would then be combined based on some function and ultimately used as the colour of the pixel the ray originated from. The problem I have is that I can't split the rays in shader code, as I would have to use recursion to do so. From my understanding, functions in a shader cannot be recursive because all GLSL functions are like inline functions in C++ due to compatibility issues with older GPU hardware.

    Is it possible to simulate or fake recursion in shader code, or can I even achieve reflection and refraction simultaneously without using recursion at all? I can't see how it can happen without recursion, but I might be wrong.

    解决方案

    I manage to convert back-raytracing to iterative process suitable for GLSL with the method suggested in my comment. It is far from optimized and I do not have all the physical stuff implemented (no Snell's law etc ...) yet but as a proof of concept it works already. I do all the stuff in fragment shader and CPU side code just send the uniforms constants and scene in form of 32 bit non-clamped float texture GL_LUMINANCE32F_ARB The rendering is just single QUAD covering whole screen.

    1. passing the scene

    I decided to store the scene in texture so each ray/fragment has direct access to whole scene. The texture is 2D but it is used as linear list of 32 bit floats. I decided this format:

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

    You can add/change any type of object. This example holds just single semi transparent bluish tetrahedron. You could also add transform matrices more coefficients for material properties etc ...

    1. Architecture

    the Vertex shader just initialize corner Rays of the view (start position and direction) which is interpolated so each fragment represents start ray of back ray tracing process.

    Iterative back ray tracing

    So I created a "static" list of rays and init it with the start ray. The Iteration is done in two steps first the back ray tracing:

    1. Loop through all rays in a list from the first
    2. Find closest intersection with scene...

    store the position, surface normal and material properties into ray struct

    1. If intersection found and not last "recursion" layer add reflect/refract rays to list at the end.

    also store their indexes to the processed ray struct

    Now your rays should hold all the intersection info you need to reconstruct the color. To do that:

    1. loop through all the recursion levels backwards
    2. for each of the rays matching actual recursion layer
    3. compute ray color

    so use lighting equations you want. If the ray contains children add their color to the result based on material properties (reflective and refractive coefficients ...)

    Now the first ray should contain the color you want to output.

    Uniforms used:


    tm_eyeview camera matrix
    aspectview ys/xs aspect ratio
    n0 empty space refraction index (unused yet)
    focal_length camera focal length
    fac_siz resolution of the scene square texture
    fac_num number of floats actually used in the scene texture
    fac_txr texture unit for the scene texture

    Preview:

    The fragment shader contains my debug prints so you will need also the texture if used see the QA:

    ToDo:


    add matrices for objects, camera etc.
    add material properties (shininess, reflection/refraction coefficient)
    Snell's law right now the direction of new rays are wrong ...
    may be separate R,G,B to 3 start rays and combine at the end
    fake SSS Subsurface scattering based on ray lengths
    better implement lights (right now they are constants in a code)
    implement more primitives (right now only triangles are supported)

    [Edit1] code debug and upgrade

    I removed old source code to fit inside 30KB limit. If you need it then dig it from edit history. Had some time for more advanced debugging for this and here the result:

    this version got resolved some geometrical,accuracy,domain problems and bugs. I got implemented both reflections and refractions as is shown on this debug draw for test ray:

    In the debug view only the cube is transparent and last ray that does not hit anything is ignored. So as you can see the ray split ... The ray ended inside cube due to total reflection angle And I disable all reflections inside objects for speed reasons.

    The 32bit floats for intersection detection are a bit noisy with distances so you can use 64bit doubles instead but the speed drops considerably in such case. Another option is to rewrite the equation to use relative coordinates which are more precise in this case of use.

    Here the float shaders source:

    Vertex:

    //------------------------------------------------------------------
    #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);
        }
    //------------------------------------------------------------------
    

    Fragment:

    //------------------------------------------------------------------
    #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
    //---------------------------------------------------------------------------
    

    The code is not optimized yet I wanted to have the physics working correctly first. There are still not Fresnells implemented but refl,refr coefficients of material are used instead.

    Also you can ignore the debug prints stuff (they are encapsulated by #define).

    I build a small class for the geometry texture so I can easily set up scene objects. This is how the scene was initiated for the preview:

    ray.beg();
    //                 r   g   b rfl rfr   n
    ray.add_material(1.0,1.0,1.0,0.3,0.0,_n_glass); ray.add_box   ( 0.0, 0.0, 6.0,9.0,9.0,0.1);
    ray.add_material(1.0,1.0,1.0,0.1,0.8,_n_glass); ray.add_sphere( 0.0, 0.0, 0.5,0.5);
    ray.add_material(1.0,0.1,0.1,0.3,0.0,_n_glass); ray.add_sphere( +2.0, 0.0, 2.0,0.5);
    ray.add_material(0.1,1.0,0.1,0.3,0.0,_n_glass); ray.add_box   ( -2.0, 0.0, 2.0,0.5,0.5,0.5);
    ray.add_material(0.1,0.1,1.0,0.3,0.0,_n_glass);
    ray.add_tetrahedron
        (
         0.0, 0.0, 3.0,
        -1.0,-1.0, 4.0,
        +1.0,-1.0, 4.0,
         0.0,+1.0, 4.0
        );
    ray.end();
    

    It is important so computed normals are facing out of objects because that is used for detecting inside/outside object crossings.

    P.S.

    If you're interested here is my volumetric 3D back ray tracer:

    Here newer version of this "Mesh" Raytracer supporting hemisphere objects:

    这篇关于没有递归光线追踪就不可能反射和折射?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆