GPU ray casting (single pass) with 3d textures in spherical coordinates

后端 未结 1 731
鱼传尺愫
鱼传尺愫 2021-01-15 15:30

i\' am implementing an algorithm of volume rendering \"GPU ray casting single pass\". For this, i used a float array of intensity values as 3d textures ( this 3d textures de

相关标签:
1条回答
  • 2021-01-15 16:02

    I do not know what and how are you rendering. There are many techniques and configurations which can achieve them. I am usually using a single pass single quad render covering the screen/view while geometry/scene is passed as texture. As you have your object in a 3D texture then I think you should go this way too. This is how its done (Assuming perspective, uniform spherical voxel grid as a 3D texture):

    1. CPU side code

      simply render single QUAD covering the scene/view. To make this more simple and precise I recommend you to use your sphere local coordinate system for camera matrix which is passed to the shaders (it will ease up the ray/sphere intersections computations a lot).

    2. Vertex

      here you should cast/compute the ray position and direction for each vertex and pass it to the fragment so its interpolated for each pixel on the screen/view.

      So the camera is described by its position (focal point) and view direction (usually Z- axis in perspective OpenGL). The ray is casted from the focal point (0,0,0) in camera local coordinates into the znear plane (x,y,-znear) also in camera local coordinates. Where x,y is the pixel screen position wit aspect ratio corrections applied if screen/view is not a square.

      So you just convert these two points into sphere local coordinates (still Cartesian).

      The ray direction is just substraction of the two points...

    3. Fragment

      first normalize ray direction passed from vertex (as due to interpolation it will not be unit vector). After that simply test ray/sphere intersection for each radius of the sphere voxel grid from outward to inward so test spheres from rmax to rmax/n where rmax is the max radius your 3D texture can have and n is ids resolution for axis corresponding to radius r.

      On each hit convert the Cartesian intersection position to Spherical coordinates. Convert them to texture coordinates s,t,p and fetch the Voxel intensity and apply it to the color (how depends on what and how are you rendering).

      So if your texture coordinates are (r,theta,phi)assuming phi is longitude and angles are normalized to <-Pi,Pi> and <0,2*Pi> and rmax is the max radius of the 3D texture then:

      s = r/rmax
      t = (theta+(Pi/2))/Pi
      p = phi/(2*PI)
      

      If your sphere is not transparent then stop on first hit with not empty Voxel intensity. Otherwise update ray start position and do this whole bullet again until ray goes out of the scene BBOX or no intersection occurs.

      You can also add Snell's law (add reflection refraction) by splitting ray on object boundary hits...

    Here are some related QAs using this technique or having valid info that will help you achieve this:

    • GLSL atmospheric scattering this is almost the same as you should do.
    • ray and ellipsoid intersection accuracy improvement math for the intersections
    • Curved Frosted Glass Shader? sub surface scattering
    • GLSL back raytrace through 3D mesh reflections and refractions in geometry inside 2D texture
    • GLSL back raytrace through 3D volume 3D Cartesian volume inside 3D texture

    [Edit1] example (after the input 3D texture was finally posted

    So when I put all the stuff above (and in comments) together I come up with this.

    CPU side code:

    //---------------------------------------------------------------------------
    //--- GLSL Raytrace system ver: 1.000 ---------------------------------------
    //---------------------------------------------------------------------------
    #ifndef _raytrace_spherical_volume_h
    #define _raytrace_spherical_volume_h
    //---------------------------------------------------------------------------
    class SphericalVolume3D
        {
    public:
        bool _init;         // has been initiated ?
        GLuint txrvol;      // SphericalVolume3D texture at GPU side
        int xs,ys,zs;
    
        float eye[16];      // direct camera matrix
        float aspect,focal_length;
    
        SphericalVolume3D()    { _init=false; txrvol=-1; xs=0; ys=0; zs=0; aspect=1.0; focal_length=1.0; }
        SphericalVolume3D(SphericalVolume3D& a)   { *this=a; }
        ~SphericalVolume3D()   { gl_exit(); }
        SphericalVolume3D* operator = (const SphericalVolume3D *a) { *this=*a; return this; }
        //SphericalVolume3D* operator = (const SphericalVolume3D &a) { ...copy... return this; }
    
        // init/exit
        void gl_init();
        void gl_exit();
    
        // render
        void glsl_draw(GLint prog_id);
        };
    //---------------------------------------------------------------------------
    void SphericalVolume3D::gl_init()
        {
        if (_init) return; _init=true;
        // load 3D texture from file into CPU side memory
        int hnd,siz; BYTE *dat;
        hnd=FileOpen("Texture3D_F32.dat",fmOpenRead);
        siz=FileSeek(hnd,0,2);
            FileSeek(hnd,0,0);
        dat=new BYTE[siz];
            FileRead(hnd,dat,siz);
            FileClose(hnd);
        if (0)
            {
            int i,n=siz/sizeof(GLfloat);
            GLfloat *p=(GLfloat*)dat;
            for (i=0;i<n;i++) p[i]=100.5;
            }
    
        // copy it to GPU as 3D texture
    //  glClampColorARB(GL_CLAMP_VERTEX_COLOR_ARB, GL_FALSE);
    //  glClampColorARB(GL_CLAMP_READ_COLOR_ARB, GL_FALSE);
    //  glClampColorARB(GL_CLAMP_FRAGMENT_COLOR_ARB, GL_FALSE);
        glGenTextures(1,&txrvol);
        glEnable(GL_TEXTURE_3D);
        glBindTexture(GL_TEXTURE_3D,txrvol);
        glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R,GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER,GL_NEAREST);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER,GL_NEAREST);
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_MODULATE);
        xs=384;
        ys= 15;
        zs=768;
        glTexImage3D(GL_TEXTURE_3D, 0, GL_R16F, xs,ys,zs, 0, GL_RED, GL_FLOAT, dat);
        glBindTexture(GL_TEXTURE_3D,0);
        glDisable(GL_TEXTURE_3D);
        delete[] dat;
        }
    //---------------------------------------------------------------------------
    void SphericalVolume3D::gl_exit()
        {
        if (!_init) return; _init=false;
        glDeleteTextures(1,&txrvol);
        }
    //---------------------------------------------------------------------------
    void SphericalVolume3D::glsl_draw(GLint prog_id)
        {
        GLint ix;
        const int txru_vol=0;
        glUseProgram(prog_id);
        // uniforms
        ix=glGetUniformLocation(prog_id,"zoom"        ); glUniform1f(ix,1.0);
        ix=glGetUniformLocation(prog_id,"aspect"      ); glUniform1f(ix,aspect);
        ix=glGetUniformLocation(prog_id,"focal_length"); glUniform1f(ix,focal_length);
        ix=glGetUniformLocation(prog_id,"vol_xs"      ); glUniform1i(ix,xs);
        ix=glGetUniformLocation(prog_id,"vol_ys"      ); glUniform1i(ix,ys);
        ix=glGetUniformLocation(prog_id,"vol_zs"      ); glUniform1i(ix,zs);
        ix=glGetUniformLocation(prog_id,"vol_txr"     ); glUniform1i(ix,txru_vol);
        ix=glGetUniformLocation(prog_id,"tm_eye"      ); glUniformMatrix4fv(ix,1,false,eye);
    
        glActiveTexture(GL_TEXTURE0+txru_vol);
        glEnable(GL_TEXTURE_3D);
        glBindTexture(GL_TEXTURE_3D,txrvol);
    
        // this should be a VAO/VBO
        glColor4f(1.0,1.0,1.0,1.0);
        glBegin(GL_QUADS);
        glVertex2f(-1.0,-1.0);
        glVertex2f(-1.0,+1.0);
        glVertex2f(+1.0,+1.0);
        glVertex2f(+1.0,-1.0);
        glEnd();
    
        glActiveTexture(GL_TEXTURE0+txru_vol);
        glBindTexture(GL_TEXTURE_3D,0);
        glDisable(GL_TEXTURE_3D);
        glUseProgram(0);
        }
    //---------------------------------------------------------------------------
    #endif
    //---------------------------------------------------------------------------
    

    call init on app start when GL is already inited, exit before app exit while GL still works and draw when needed... The code is C++/VCL based so port to your environment (file access, strings, etc..) I also use the 3D texture in binary form as loading 85MByte ASCII file is a bit too much for my taste.

    Vertex:

    //------------------------------------------------------------------
    #version 420 core
    //------------------------------------------------------------------
    uniform float aspect;
    uniform float focal_length;
    uniform float zoom;
    uniform mat4x4 tm_eye;
    layout(location=0) in vec2 pos;
    
    out smooth vec3 ray_pos;    // ray start position
    out smooth vec3 ray_dir;    // ray start direction
    //------------------------------------------------------------------
    void main(void)
        {
        vec4 p;
        // perspective projection
        p=tm_eye*vec4(pos.x/(zoom*aspect),pos.y/zoom,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);
        }
    //------------------------------------------------------------------
    

    its more or less a copy from the volumetric ray tracer link.

    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 int         vol_xs,     // texture resolution
                        vol_ys,
                        vol_zs;
    uniform sampler3D   vol_txr;    // scene mesh data texture
    out layout(location=0) vec4 frag_col;
    //---------------------------------------------------------------------------
    // 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;
        }
    //---------------------------------------------------------------------------
    const float pi =3.1415926535897932384626433832795;
    const float pi2=6.2831853071795864769252867665590;
    float atanxy(float x,float y) // atan2 return < 0 , 2.0*M_PI >
            {
            int sx,sy;
            float a;
            const float _zero=1.0e-30;
            sx=0; if (x<-_zero) sx=-1; if (x>+_zero) sx=+1;
            sy=0; if (y<-_zero) sy=-1; if (y>+_zero) sy=+1;
            if ((sy==0)&&(sx==0)) return 0;
            if ((sx==0)&&(sy> 0)) return 0.5*pi;
            if ((sx==0)&&(sy< 0)) return 1.5*pi;
            if ((sy==0)&&(sx> 0)) return 0;
            if ((sy==0)&&(sx< 0)) return pi;
            a=y/x; if (a<0) a=-a;
            a=atan(a);
            if ((x>0)&&(y>0)) a=a;
            if ((x<0)&&(y>0)) a=pi-a;
            if ((x<0)&&(y<0)) a=pi+a;
            if ((x>0)&&(y<0)) a=pi2-a;
            return a;
            }
    //---------------------------------------------------------------------------
    void main(void)
        {
        float a,b,r,_rr,c;
        const float dr=1.0/float(vol_ys);       // r step
        const float saturation=1000.0;          // color saturation voxel value
        vec3  rr,p=ray_pos,dp=normalize(ray_dir);
        for (c=0.0,r=1.0;r>1e-10;r-=dr)         // check all radiuses inwards
            {
            _rr=1.0/(r*r); rr=vec3(_rr,_rr,_rr);
            if (_view_depth(p,dp,rr))           // if ray hits sphere
                {
                p+=view_depth_l0*dp;            // shift ray start position to the hit
                a=atanxy(p.x,p.y);              // comvert to spherical a,b,r
                b=asin(p.z/r);
                if (a<0.0) a+=pi2;              // correct ranges...
                b+=0.5*pi;
                a/=pi2;
                b/=pi;
                // here do your stuff
                c=texture(vol_txr,vec3(b,r,a)).r;// fetch voxel
                if (c>saturation){ c=saturation; break; }
                break;
                }
            }
        c/=saturation;
    
        frag_col=vec4(c,c,c,1.0);
        }
    //--------------------------------------------------------------------------- 
    

    its a slight modification of the volumetric ray tracer link.

    Beware that I assume that the axises inside the texture are:

    latitude,r,longitude
    

    implied by the resolutions (longitude should be double resolution of the latitude) so if it does not match your data just reorder the axises in fragment ... I have no clue what the values of the Voxel cell mean so I sum them like intensity/density for the final color and once saturation sum reached stop the raytrace but instead you should your computation stuff you intend.

    Here preview:

    I used this camera matrix eye for it:

    // globals
    SphericalVolume3D vol;
    // init (GL must be already working)
    vol.gl_init();
    
    // render
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glDisable(GL_CULL_FACE);
    
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0,0.0,-2.5);
    glGetFloatv(GL_MODELVIEW_MATRIX,vol.eye);
    vol.glsl_draw(prog_id);
    
    glFlush();
    SwapBuffers(hdc);
    
    // exit (GL must be still working)
    vol.gl_init();
    

    The ray/sphere hit is working properly, also the hit position in spherical coordinates are working as should so the only thing left is the axis order and color arithmetics ...

    0 讨论(0)
提交回复
热议问题