GLUT 环面与相机碰撞

GLUT torus colliding with camera

我想实现游戏区域中6个随机扰动的圆环体的碰撞。这是一个简单的 3D space 游戏,使用透视图和第一人称视角。我看到一些堆栈溢出的答案建议计算任何(玩家)到环面单元的距离,如果大于一半或整个单元尺寸,你就会碰撞 +/- 你的坐标系和地图拓扑调整。但是如果我们采用距离,这意味着我们只考虑 z 坐标,所以如果相机移动到那个距离(不考虑 x,y 坐标)它总是认为是错误的碰撞,对吗?

我希望使用 AABB 算法来做到这一点。是否可以将相机位置和环面位置视为 2 个盒子并检查碰撞(盒子到盒子碰撞)或相机作为点和环面作为盒子(点到盒子)?或者有人可以建议最好的方法吗?

下面是我目前试过的代码。

float im[16], m[16], znear = 0.1, zfar = 100.0, fovx = 45.0 * M_PI / 180.0;
glm::vec3 p0, p1, p2, p3, o, u, v;

//p0, p1, p2, p3 holds your znear camera screen corners in world coordinates 
void ChangeSize(int w, int h)
{
    GLfloat fAspect;
    
    // Prevent a divide by zero
    if(h == 0)
        h = 1;
    
    // Set Viewport to window dimensions
    glViewport(0, 0, w, h);
    
    // Calculate aspect ratio of the window
    fAspect = (GLfloat)w*1.0/(GLfloat)h;
    
    // Set the perspective coordinate system
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    
    // field of view of 45 degrees, near and far planes 1.0 and 1000
    //that znear and zfar should typically have a ratio of 1000:1 to make sorting out z depth easier for the GPU
    gluPerspective(45.0f, fAspect, 0.1f, 300.0f);  //may need to make larger depending on project
    // Modelview matrix reset
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    // get camera matrix (must be in right place in code before model transformations)
    glGetFloatv(GL_MODELVIEW_MATRIX, im);    // get camera inverse matrix
    matrix_inv(m, im);                   // m = inverse(im)
    u = glm::vec3(m[0], m[1], m[2]);         // x axis
    v = glm::vec3(m[4], m[5], m[6]);         // y axis
    o = glm::vec3(m[12], m[13], m[14]);         // origin
    o -= glm::vec3(m[8], m[9], m[10]) * znear;   // z axis offset
    // scale by FOV
    u *= znear * tan(0.5 * fovx);
    v *= znear * tan(0.5 * fovx / fAspect);
    // get rectangle coorners
    p0 = o - u - v;
    p1 = o + u - v;
    p2 = o + u + v;
    p3 = o - u + v;

}



void matrix_inv(float* a, float* b) // a[16] = Inverse(b[16])
{
    float x, y, z;
    // transpose of rotation matrix
    a[0] = b[0];
    a[5] = b[5];
    a[10] = b[10];
    x = b[1]; a[1] = b[4]; a[4] = x;
    x = b[2]; a[2] = b[8]; a[8] = x;
    x = b[6]; a[6] = b[9]; a[9] = x;
    // copy projection part
    a[3] = b[3];
    a[7] = b[7];
    a[11] = b[11];
    a[15] = b[15];
    // convert origin: new_pos = - new_rotation_matrix * old_pos
    x = (a[0] * b[12]) + (a[4] * b[13]) + (a[8] * b[14]);
    y = (a[1] * b[12]) + (a[5] * b[13]) + (a[9] * b[14]);
    z = (a[2] * b[12]) + (a[6] * b[13]) + (a[10] * b[14]);
    a[12] = -x;
    a[13] = -y;
    a[14] = -z;
}

//Store torus coordinates
std::vector<std::vector<GLfloat>> translateTorus = { { 0.0, 1.0, -10.0, 1 },   { 0.0, 4.0, -6.0, 1 } , { -1.0, 0.0, -4.0, 1 },
{ 3.0, 1.0, -6.0, 1 }, { 1.0, -1.0, -9.0, 1 } , { 4.0, 1.0, -4.0, 1 } };

GLfloat xpos, ypos, zpos, flagToDisplayCrystal;
//Looping through 6 Torus
    for (int i = 0; i < translateTorus.size(); i++) {
        //Get the torus coordinates
        xpos = translateTorus[i][0];
        ypos = translateTorus[i][1];
        zpos = translateTorus[i][2];
        //This variable will work as a variable to display crystal after collision
        flagToDisplayCrystal = translateTorus[i][3];

        //p0 min, p2 max

        //Creating a square using Torus index coordinates and radius
        double halfside = 1.0 / 2;

//This (xpos+halfside), (xpos-halfside), (ypos+halfside), (ypos-halfside) are //created using Torus index and radius

        float d1x = p0[0] - (xpos + halfside);
        float d1y = p0[1] - (ypos + halfside);
        float d2x = (xpos - halfside) - p2[0];
        float d2y = (ypos - halfside) - p2[1];

    
        //Collision is checking here
        //For square's min z and max z is checking whether equal to camera's min //z and max z
        if ((d1x > 0.0f || d1y > 0.0f || d2x > 0.0f || d2y > 0.0f) && p2[2] == zpos && p0[2] == zpos) {
            //If there is collision update the variable as 0
            translateTorus[i][3] = 0;

        }
        else {

            if (flagToDisplayCrystal == 1) {

                glPushMatrix();
                glEnable(GL_TEXTURE_2D);
                glTranslatef(xpos, ypos, zpos);
                glRotatef(fPlanetRot, 0.0f, -1.0f, 0.0f);
                glColor3f(0.0, 0.0, 0.0);

                // Select the texture object
                glBindTexture(GL_TEXTURE_2D, textures[3]);

                glutSolidTorus(0.1, 1.0, 30, 30);
                glDisable(GL_TEXTURE_2D);
                glPopMatrix();

            }
        }
}

正如我在评论中提到的,您有 2 个选项,要么使用 OpenGL 渲染,要么完全在 CPU 侧计算而不使用它。让我们先从渲染开始:

  1. 渲染你的场景

    但不要使用环面颜色和其他东西,而是使用整数索引(例如 0 个空 space、1 个障碍物、2 个环面......)您甚至可以为世界上的每个对象设置单独的索引,这样您确切知道哪一个被击中等...

    所以:用空颜色清除屏幕,渲染你的场景(使用索引而不是 glColor??(???) 的颜色)没有光照或阴影或其他任何东西。但是 不要交换缓冲区 !!! 因为那样会在屏幕上显示内容并导致闪烁。

  2. 读取渲染的屏幕和深度缓冲区

    您只需使用 glReadPixels 将您的屏幕和深度缓冲区复制到 CPU 端内存(一维数组)让我们称它们为 scr[],zed[].

  3. 扫描 scr[] 颜色匹配圆环索引

    简单地遍历所有像素,如果找到圆环像素,检查它的深度。如果它离相机足够近,你就会发现碰撞。

  4. 正常渲染

    现在再次清除屏幕并用颜色和灯光渲染屏幕...现在您也可以交换缓冲区。

当心深度缓冲区将是非线性的,需要线性化才能获得以世界单位表示的原始深度。有关它的更多信息以及阅读 scr,zed 的示例,请参阅:

  • depth buffer got by glReadPixels is always 1

如果您没有太多圆环,另一种方法要快得多。您只需计算相机 znear 平面和环面之间的交点。归结为 AABB 与矩形相交或圆柱与矩形相交。

但是,如果您不熟悉 3D 矢量数学,您可能很快就会迷路。

假设圆环由 AABB 描述。然后那个和矩形之间的交集归结为检查线(AABB 的每条边)和矩形之间的交集。所以简单地找到平面和线之间的交叉点并检查该点是否在矩形内。

如果我们的矩形是按 CW 或 CCW 顺序 (p0,p1,p2,p3) 并按端点 q0,q1 定义的,则:

n = normalize(cross(p1-p0,p2-p1)) // is rectangle normal
dq = normalize(q1-q0)             // is line direction
q = q0 + dq*dot(dq,p1-p0)         // is plane/line intersection

所以现在只需检查 q 是否在矩形内。有两种方法可以测试 q-edge_startedge_end-edge_start 之间的所有交叉点是否具有相同的方向,或者所有 edge_normalq-edge_point 之间的所有点是否具有相同的符号或零。

问题是 AABB 和矩形必须在同一坐标系中,因此要么使用模型视图矩阵将 AABB 转换为相机坐标,要么使用模型视图的逆矩阵将矩形转换为世界坐标。后者更好,因为你只做一次而不是转换每个圆环的 AABB ...

有关数学方面的更多信息,请参阅:

矩形本身只是从您的相机矩阵(modelviev 的一部分)位置中提取的,x、y 基向量为您提供了矩形的“中心”和轴...大小必须从透视中导出矩阵(或您传递给它的参数,尤其是纵横比、FOV 和 znear)

首先你需要获取相机(视图)矩阵。 GL_MODELVIEW 通常持有:

GL_MODELVIEW = Inverse(Camera)*Rendered_Object

所以您需要在您的代码中找到您的 GL_MODELVIEW 仅包含 Inverse(Camera) 转换的位置,并在那里放置:

        float aspect=float(xs)/float(ys);   // aspect from OpenGL window resolution
        float im[16],m[16],znear=0.1,zfar=100.0,fovx=60.0*M_PI/180.0;
        vec3 p0,p1,p2,p3,o,u,v;             // 3D vectors
        // this is how my perspective is set
//      glMatrixMode(GL_PROJECTION);
//      glLoadIdentity();
//      gluPerspective(fovx*180.0/(M_PI*aspect),aspect,znear,zfar);
        // get camera matrix (must be in right place in code before model transformations)
        glGetFloatv(GL_MODELVIEW_MATRIX,im);    // get camera inverse matrix
        matrix_inv(m,im);                   // m = inverse(im)
        u =vec3(m[ 0],m[ 1],m[ 2]);         // x axis
        v =vec3(m[ 4],m[ 5],m[ 6]);         // y axis
        o =vec3(m[12],m[13],m[14]);         // origin
        o-=vec3(m[ 8],m[ 9],m[10])*znear;   // z axis offset
        // scale by FOV
        u*=znear*tan(0.5*fovx);
        v*=znear*tan(0.5*fovx/aspect);
        // get rectangle coorners
        p0=o-u-v;
        p1=o+u-v;
        p2=o+u+v;
        p3=o-u+v;
        // render it for debug
        glColor3f(1.0,1.0,0.0);
        glBegin(GL_QUADS);
        glColor3f(1.0,0.0,0.0); glVertex3fv(p0.dat);
        glColor3f(0.0,0.0,0.0); glVertex3fv(p1.dat);
        glColor3f(0.0,0.0,1.0); glVertex3fv(p2.dat);
        glColor3f(1.0,1.0,1.0); glVertex3fv(p3.dat);
        glEnd();

基本上将矩阵加载到 CPU 边变量中,像这样反转它:

void matrix_inv(float *a,float *b) // a[16] = Inverse(b[16])
    {
    float x,y,z;
    // transpose of rotation matrix
    a[ 0]=b[ 0];
    a[ 5]=b[ 5];
    a[10]=b[10];
    x=b[1]; a[1]=b[4]; a[4]=x;
    x=b[2]; a[2]=b[8]; a[8]=x;
    x=b[6]; a[6]=b[9]; a[9]=x;
    // copy projection part
    a[ 3]=b[ 3];
    a[ 7]=b[ 7];
    a[11]=b[11];
    a[15]=b[15];
    // convert origin: new_pos = - new_rotation_matrix * old_pos
    x=(a[ 0]*b[12])+(a[ 4]*b[13])+(a[ 8]*b[14]);
    y=(a[ 1]*b[12])+(a[ 5]*b[13])+(a[ 9]*b[14]);
    z=(a[ 2]*b[12])+(a[ 6]*b[13])+(a[10]*b[14]);
    a[12]=-x;
    a[13]=-y;
    a[14]=-z;
    }

并如上所述在考虑透视的情况下计算角...

我像 vec3 一样使用 GLSL,但你可以像 float p0[3],... 一样使用任何 3D 数学甚至自己的。您只需要 +,- 并乘以常数。

现在 p0,p1,p2,p3 在世界坐标中保持您的 znear 相机屏幕角。

[Edit1] 示例

我设法为此整理了一个简单的例子。这里首先使用的支持功能:

//---------------------------------------------------------------------------
void glutSolidTorus(float r,float R,int na,int nb) // render torus(r,R)
    {
    float *pnt=new float[(na+1)*(nb+1)*3*2]; if (pnt==NULL) return;
    float *nor=pnt+((na+1)*(nb+1)*3);
    float ca,sa,cb,sb,a,b,da,db,x,y,z,nx,ny,nz;
    int ia,ib,i,j;
    da=2.0*M_PI/float(na);
    db=2.0*M_PI/float(nb);
    glBegin(GL_LINES);
    for (i=0,a=0.0,ia=0;ia<=na;ia++,a+=da){ ca=cos(a); sa=sin(a);
     for (   b=0.0,ib=0;ib<=nb;ib++,b+=db){ cb=cos(b); sb=sin(b);
        z=r*ca;
        x=(R+z)*cb; nx=(x-(R*cb))/r;
        y=(R+z)*sb; ny=(y-(R*sb))/r;
        z=r*sa;     nz=sa;
        pnt[i]=x; nor[i]=nx; i++;
        pnt[i]=y; nor[i]=ny; i++;
        pnt[i]=z; nor[i]=nz; i++;
        }}
    glEnd();
    for (ia=0;ia<na;ia++)
        {
        i=(ia+0)*(nb+1)*3;
        j=(ia+1)*(nb+1)*3;
        glBegin(GL_QUAD_STRIP);
        for (ib=0;ib<=nb;ib++)
            {
            glNormal3fv(nor+i); glVertex3fv(pnt+i); i+=3;
            glNormal3fv(nor+j); glVertex3fv(pnt+j); j+=3;
            }
        glEnd();
        }

    delete[] pnt;
    }
//---------------------------------------------------------------------------
const int AABB_lin[]=   // AABB lines
    {
    0,1,
    1,2,
    2,3,
    3,0,
    4,5,
    5,6,
    6,7,
    7,4,
    0,4,
    1,5,
    2,6,
    3,7,
    -1
    };
const int AABB_fac[]=   // AABB quads
    {
    3,2,1,0,
    4,5,6,7,
    0,1,5,4,
    1,2,6,5,
    2,3,7,6,
    3,0,4,7,
    -1
    };
void AABBSolidTorus(vec3 *aabb,float r,float R) // aabb[8] = AABB of torus(r,R)
    {
    R+=r;
    aabb[0]=vec3(-R,-R,-r);
    aabb[1]=vec3(+R,-R,-r);
    aabb[2]=vec3(+R,+R,-r);
    aabb[3]=vec3(-R,+R,-r);
    aabb[4]=vec3(-R,-R,+r);
    aabb[5]=vec3(+R,-R,+r);
    aabb[6]=vec3(+R,+R,+r);
    aabb[7]=vec3(-R,+R,+r);
    }
//---------------------------------------------------------------------------
void matrix_inv(float *a,float *b) // a[16] = Inverse(b[16])
    {
    float x,y,z;
    // transpose of rotation matrix
    a[ 0]=b[ 0];
    a[ 5]=b[ 5];
    a[10]=b[10];
    x=b[1]; a[1]=b[4]; a[4]=x;
    x=b[2]; a[2]=b[8]; a[8]=x;
    x=b[6]; a[6]=b[9]; a[9]=x;
    // copy projection part
    a[ 3]=b[ 3];
    a[ 7]=b[ 7];
    a[11]=b[11];
    a[15]=b[15];
    // convert origin: new_pos = - new_rotation_matrix * old_pos
    x=(a[ 0]*b[12])+(a[ 4]*b[13])+(a[ 8]*b[14]);
    y=(a[ 1]*b[12])+(a[ 5]*b[13])+(a[ 9]*b[14]);
    z=(a[ 2]*b[12])+(a[ 6]*b[13])+(a[10]*b[14]);
    a[12]=-x;
    a[13]=-y;
    a[14]=-z;
    }
//---------------------------------------------------------------------------
const int QUAD_lin[]=   // quad lines
    {
    0,1,
    1,2,
    2,3,
    3,0,
    -1
    };
const int QUAD_fac[]=   // quad quads
    {
    0,1,2,3,
    -1
    };
void get_perspective_znear(vec3 *quad) // quad[4] = world coordinates of 4 corners of screen at znear distance from camera
    {
    vec3 o,u,v;                                     // 3D vectors
    float im[16],m[16],znear,zfar,aspect,fovx;
    // get stuff from perspective
    glGetFloatv(GL_PROJECTION_MATRIX,m);            // get perspective projection matrix
    zfar =0.5*m[14]*(1.0-((m[10]-1.0)/(m[10]+1.0)));// compute zfar from perspective matrix
    znear=zfar*(m[10]+1.0)/(m[10]-1.0);             // compute znear from perspective matrix
    aspect=m[5]/m[0];
    fovx=2.0*atan(1.0/m[5])*aspect;
    // get stuff from camera matrix (must be in right place in code before model transformations)
    glGetFloatv(GL_MODELVIEW_MATRIX,im);            // get camera inverse matrix
    matrix_inv(m,im);                               // m = inverse(im)
    u =vec3(m[ 0],m[ 1],m[ 2]);                     // x axis
    v =vec3(m[ 4],m[ 5],m[ 6]);                     // y axis
    o =vec3(m[12],m[13],m[14]);                     // origin
    o-=vec3(m[ 8],m[ 9],m[10])*znear;               // z axis offset
    // scale by FOV
    u*=znear*tan(0.5*fovx);
    v*=znear*tan(0.5*fovx/aspect);
    // get rectangle coorners
    quad[0]=o-u-v;
    quad[1]=o+u-v;
    quad[2]=o+u+v;
    quad[3]=o-u+v;
    }
//---------------------------------------------------------------------------
bool collideLineQuad(vec3 *lin,vec3 *quad)  // return if lin[2] is colliding quad[4]
    {
    float t,l,u,v;
    vec3 p,p0,p1,dp;
    vec3 U,V,W;
    // quad (rectangle) basis vectors
    U=quad[1]-quad[0]; u=length(U); u*=u;
    V=quad[3]-quad[0]; v=length(V); v*=v;
    W=normalize(cross(U,V));
    // convert line from world coordinates to quad local ones
    p0=lin[0]-quad[0]; p0=vec3(dot(p0,U)/u,dot(p0,V)/v,dot(p0,W));
    p1=lin[1]-quad[0]; p1=vec3(dot(p1,U)/u,dot(p1,V)/v,dot(p1,W));
    dp=p1-p0;
    // test if crossing the plane
    if (fabs(dp.z)<1e-10) return false;
    t=-p0.z/dp.z;
    p=p0+(t*dp);
    // test inside 2D quad (rectangle)
    if ((p.x<0.0)||(p.x>1.0)) return false;
    if ((p.y<0.0)||(p.y>1.0)) return false;
    // inside line
    if ((t<0.0)||(t>1.0)) return false;
    return true;
    }
//---------------------------------------------------------------------------
bool collideQuadQuad(vec3 *quad0,vec3 *quad1)   // return if quad0[4] is colliding quad1[4]
    {
    int i;
    vec3 l[2];
    // lines vs. quads
    for (i=0;QUAD_lin[i]>=0;)
        {
        l[0]=quad0[QUAD_lin[i]]; i++;
        l[1]=quad0[QUAD_lin[i]]; i++;
        if (collideLineQuad(l,quad1)) return true;
        }
    for (i=0;QUAD_lin[i]>=0;)
        {
        l[0]=quad1[QUAD_lin[i]]; i++;
        l[1]=quad1[QUAD_lin[i]]; i++;
        if (collideLineQuad(l,quad0)) return true;
        }
    // ToDo coplanar quads tests (not needed for AABB test)
    return false;
    }
//---------------------------------------------------------------------------
bool collideAABBQuad(vec3 *aabb,vec3 *quad) // return if aabb[8] is colliding quad[4]
    {
    int i;
    vec3 q[4],n,p;
    // test all AABB faces (rectangle) for intersection with quad (rectangle)
    for (i=0;AABB_fac[i]>=0;)
        {
        q[0]=aabb[AABB_fac[i]]; i++;
        q[1]=aabb[AABB_fac[i]]; i++;
        q[2]=aabb[AABB_fac[i]]; i++;
        q[3]=aabb[AABB_fac[i]]; i++;
        if (collideQuadQuad(q,quad)) return true;
        }
    // test if one point of quad is fully inside AABB
    for (i=0;AABB_fac[i]>=0;i+=4)
        {
        n=cross(aabb[AABB_fac[i+1]]-aabb[AABB_fac[i+0]],
                aabb[AABB_fac[i+2]]-aabb[AABB_fac[i+1]]);
        if (dot(n,quad[0]-aabb[AABB_fac[i+0]])>0.0) return false;
        }
    return true;
    }
//---------------------------------------------------------------------------

这里是用法(渲染期间):

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    int i;
    float m[16];
    mat4 m0,m1;
    vec4 v4;

    float aspect=float(xs)/float(ys);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0/aspect,aspect,0.1,20.0);
    glMatrixMode(GL_TEXTURE);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    static float anim=180.0; anim+=0.1; if (anim>=360.0) anim-=360.0;

    glEnable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);

    vec3 line[2],quad[4],aabb[8];   // 3D vectors
    get_perspective_znear(quad);

    // store view matrix for latter
    glMatrixMode(GL_MODELVIEW);
    glGetFloatv(GL_MODELVIEW_MATRIX,m);
    m0=mat4(m[0],m[1],m[2],m[3],m[4],m[5],m[6],m[7],m[8],m[9],m[10],m[11],m[12],m[13],m[14],m[15]);
    m0=inverse(m0);
    // <<-- here should be for start that loop through your toruses
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();

    // set/animate torus position
    glTranslatef(0.3,0.3,3.5*(-1.0-cos(anim)));
    glRotatef(+75.0,0.5,0.5,0.0);
    // get actual matrix and convert it to the change
    glGetFloatv(GL_MODELVIEW_MATRIX,m);
    m1=m0*mat4(m[0],m[1],m[2],m[3],m[4],m[5],m[6],m[7],m[8],m[9],m[10],m[11],m[12],m[13],m[14],m[15]);
    // render torus and compute its AABB
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glColor3f(1.0,1.0,1.0);
    glutSolidTorus(0.1,0.5,36,36);
    AABBSolidTorus(aabb,0.1,0.5);
    glDisable(GL_LIGHT0);
    glDisable(GL_LIGHTING);

    // convert AABB to the same coordinates as quad
    for (i=0;i<8;i++) aabb[i]=(m1*vec4(aabb[i],1.0)).xyz;
    // restore original view matrix
    glPopMatrix();

    // render wireframe AABB
    glColor3f(0.0,1.0,0.0);
    glBegin(GL_LINES);
    for (i=0;AABB_lin[i]>=0;i++)
     glVertex3fv(aabb[AABB_lin[i]].dat);
    glEnd();
/*
    // render filled AABB for debug
    glBegin(GL_QUADS);
    for (i=0;AABB_fac[i]>=0;i++)
     glVertex3fv(aabb[AABB_fac[i]].dat);
    glEnd();

    // render quad for debug
    glBegin(GL_QUADS);
    glColor3f(1.0,1.0,1.0);
    for (i=0;QUAD_fac[i]>=0;i++)
     glVertex3fv(quad[QUAD_fac[i]].dat);
    glEnd();
*/
    // render X on colision
    if (collideAABBQuad(aabb,quad))
        {
        glColor3f(1.0,0.0,0.0);
        glBegin(GL_LINES);
        glVertex3fv(quad[0].dat);
        glVertex3fv(quad[2].dat);
        glVertex3fv(quad[1].dat);
        glVertex3fv(quad[3].dat);
        glEnd();
        }

    // <<-- here should be end of the for that loop through your toruses

    glFlush();
    SwapBuffers(hdc);

忽略 GLUT solid torus 函数,因为你已经知道了...这里预览:

红叉表示与屏幕发生碰撞...