如何使用视图和投影矩阵在OpenGL中进行视锥剔除?

问题描述

我正在尝试对我的体素引擎实施平截头剔除,基本上是在渲染块,我想剔除相机平截头锥体之外的每个块。我尝试了很多在Web上找到的不同方法代码,但仍然无法正常工作。该算法分为两个部分:

•首先,我要从projectionview矩阵中提取视锥平面。

•然后,我要检查每个块是否在内部或与视锥相撞。

行为通常是相同的:当从原点向正方向看时,它似乎起作用,但是当向负方向看时,它没有作用;当我离开原点时,它开始断裂并开始工作废话。另外,在上下选择时,也很奇怪。

这是我的视锥平面提取


    public static Plane[] frustumPlanes(Matrix4f mat,boolean normalize)
    {   
        Plane[] p = new Plane[6];
        p[0] = normalizePlane(mat.m30 + mat.m00,mat.m31 + mat.m01,mat.m32 + mat.m02,mat.m33 + mat.m03); // left
        p[1] = normalizePlane(mat.m30 - mat.m00,mat.m31 - mat.m01,mat.m32 - mat.m02,mat.m33 - mat.m03); // right
        p[2] = normalizePlane(mat.m30 - mat.m10,mat.m31 - mat.m11,mat.m32 - mat.m12,mat.m33 - mat.m13); // top
        p[3] = normalizePlane(mat.m30 + mat.m10,mat.m31 + mat.m11,mat.m32 + mat.m12,mat.m33 + mat.m13); // bottom
        p[4] = normalizePlane(mat.m30 + mat.m20,mat.m31 + mat.m21,mat.m32 + mat.m22,mat.m33 + mat.m23); // near
        p[5] = normalizePlane(mat.m30 - mat.m20,mat.m31 - mat.m21,mat.m32 - mat.m22,mat.m33 - mat.m23); // far
        
        return p;
    }
    
    public static Plane normalizePlane(float A,float B,float C,float D) {

        float nf = 1.0f / (float)Math.sqrt(A * A + B * B + C * C);

        return new Plane(new Vector3f(nf * A,nf * B,nf * C),nf * D);
    }

mat是投影视图矩阵,这是投影矩阵:


    private void createProjectionMatrix() {
        
        float aspectRatio = (float) displayManager.WIDTH / (float) displayManager.HEIGHT;
        float y_scale = (float) ((1f / Math.tan(Math.toradians(FOV / 2f))));
        float x_scale = y_scale / aspectRatio;
        float frustum_length = FAR_PLANE - NEAR_PLANE;

        projectionMatrix = new Matrix4f();
        projectionMatrix.m00 = x_scale;
        projectionMatrix.m11 = y_scale;
        projectionMatrix.m22 = -((FAR_PLANE + NEAR_PLANE) / frustum_length);
        projectionMatrix.m23 = -1;
        projectionMatrix.m32 = -((2 * NEAR_PLANE * FAR_PLANE) / frustum_length);
        projectionMatrix.m33 = 0;
    }

以下是视图矩阵:


public static Matrix4f createViewMatrix(Camera camera) {
        Matrix4f viewMatrix = new Matrix4f();
        viewMatrix.setIdentity();
        
        Matrix4f.rotate((float) Math.toradians(camera.getRotation().x),new Vector3f(1,0),viewMatrix,viewMatrix);
        Matrix4f.rotate((float) Math.toradians(camera.getRotation().y),new Vector3f(0,1,viewMatrix);
        Matrix4f.rotate((float) Math.toradians(camera.getRotation().z),1),viewMatrix);
        
        Vector3f cameraPos = camera.getPosition();
        Vector3f negativeCameraPos = new Vector3f(-cameraPos.x,-cameraPos.y,-cameraPos.z);
        Matrix4f.translate(negativeCameraPos,viewMatrix);
        return viewMatrix;
    }

以下是碰撞检测代码aabb vs plane:


public static int BoxToPlaneCollision(Plane plane,Vector3f[] minMax)
    {
        int result = 2; //Inside
        
                    // planes have unit-length normal,offset = -dot(normal,point on plane)
            int nx = plane.normal.x > 0?1:0;
            int ny = plane.normal.y > 0?1:0;
            int nz = plane.normal.z > 0?1:0;
            
            // getMinMax(): 0 = return min coordinate. 1 = return max.
            float dot = (plane.normal.x*minMax[nx].x) + (plane.normal.y*minMax[nx].y) + (plane.normal.z*minMax[nx].z);
            
            if ( dot < -plane.offset )
                return 0; //Outside
            
            float dot2 = (plane.normal.x*minMax[1-nx].x) + (plane.normal.y*minMax[1-nx].y) + (plane.normal.z*minMax[1-nx].z);
            
            if ( dot2 <= -plane.offset )
                result = 1; //Intersect
        
        return result;
    }

最后,这就是所有的内容


public boolean chunkInsideFrustum(Vector3f chunkPos) {
        Vector3f chunkPosMax = new Vector3f(chunkPos.x + Terrain.CHUNK_SIZE,Terrain.CHUNK_HEIGHT,chunkPos.z + Terrain.CHUNK_SIZE);
        for (int i = 0; i < 6; i++) {

            if(Collider.BoxToPlaneCollision(frustumPlanes[i],new Vector3f[] {chunkPos,chunkPosMax}) == 0)
                return false;
        }
        return true;
    }

我正在将openGL与LWJGL 2(Java)一起使用。

我的问题是: 问题出在哪里?在截锥体平面中提取代码?在碰撞检测中?

我看到人们用投影和modelview矩阵计算平截头体,这种技术呢?更好吗?

非常感谢您的帮助!

编辑

对于第二个问题,我在这里Extracting View Frustum Planes (Gribb & Hartmann method)看到有人发帖:

缺少的部分:

comboMatrix = projection_matrix * Matrix4_Transpose(modelview_matrix)

然后他执行了与提取平面完全相同的算法,但是modelview_matrix是什么?我应该使用哪种型号?

解决方法

在构建链接的pdf的opengl部分中看到的平截头平面时,我发现pdf表示要使用projection*view [注意顺序非常重要]矩阵的列,但对我有用而是改用行(请注意,是只是我还是编辑器部分出现错误)。在LWJGL中,这是Matrix4f的顺序

m00  m10  m20  m30
m01  m11  m21  m31
m02  m12  m22  m32
m03  m13  m23  m33

假设这是您的AABB课程

public final class AABB
{
 final Vector3f
 lower=new Vector3f(),upper=new Vector3f();
 
 public AABB()
 {
  lower.set(-0.5f,-0.5f,-0.5f);
  upper.set(0.5f,0.5f,0.5f);
 }

//Note the methods getHalfDimensions,getCenter and getAxises can be exactly implemented by an OOBB as well to check against plane collision

 public float[] getHalfDimensions(float[] dims)
 {
  if(dims==null){dims=new float[]{0,0};}
  
  dims[0]=(upper.x-lower.x)/2.0f;
  dims[1]=(upper.y-lower.y)/2.0f;
  dims[2]=(upper.z-lower.z)/2.0f;
  
  return dims;
 }

 public Vector3f getCenter(Vector3f result)
 {
  if(result==null){result=new Vector3f();}
  
  result.set(
              (lower.x+upper.x)/2.0f,(lower.y+upper.y)/2.0f,(lower.z+upper.z)/2.0f
            );
  
  return result;
 }

 public Vector3f[] getAxises(Vector3f axises[])
 {
  if(axises==null){axises=new Vector3f[3];}
  
  axises[0]=new Vector3f(1,0);
  axises[1]=new Vector3f(0,1,0);
  axises[2]=new Vector3f(0,1);
  
  return axises;
 }
}

假设这是您的飞机课

public final class Plane
{
 //I prefer to have all the values of an plane but you can decide what works for you
 public final Vector3f origin=new Vector3f();
 public final Vector3f normal=new Vector3f();
 public float distance;
 
 private Plane(Point3f point,Vector3f normal,float distance)
 {
  this.origin.set(point);
  VecMathUtils.normalize(normal,this.normal);
  this.distance=distance;
 }
 
 public static Plane of(Vector4f plane){return new Plane(new Point3f(),new Vector3f(plane.x,plane.y,plane.z),plane.w);}}

 //-distance or +distance can depend see the frustum code for comments
 public float distance(Vector3f point){return Vector3f.dot(normal,Vector3f.sub(point,origin,new Vector3f()))-distance;}

 public boolean intersect(AABB box)
 {
   Point3f center=box.getCenter(null);
   float[] halfDims=box.getHalfDimensions(null);
   Vector3f axises[]=box.getAxises(null,false);
   
   float separation=distance(center)- 
                   (
                    halfDims[0]*Math.abs(Vector3f.dot(normal,axises[0]))+
                    halfDims[1]*Math.abs(Vector3f.dot(normal,axises[1]))+
                    halfDims[2]*Math.abs(Vector3f.dot(normal,axises[2]))
                   );

  return separation>0 //or greater than some epslon
  }
 }

最后是您的(或至少是我的可行版本)Frustum

public final class Frustum
{
 private final ArrayList<Plane> fPlanes=new ArrayList();
 
 public Frustum(Matrix4f viewProj){set(viewProj);}
 public Frustum set(Matrix4f viewProj)
 {
  Vector4f planes[]=//Initialize the 6 planes
  
  //Left
  planes[0].x=viewProj.m03+viewProj.m00;
  planes[0].y=viewProj.m13+viewProj.m10;
  planes[0].z=viewProj.m23+viewProj.m20;
  planes[0].w=viewProj.m33+viewProj.m30;
  
  //Right
  planes[1].x=viewProj.m03-viewProj.m00;
  planes[1].y=viewProj.m13-viewProj.m10;
  planes[1].z=viewProj.m23-viewProj.m20;
  planes[1].w=viewProj.m33-viewProj.m30;
  
  //Top
  planes[2].x=viewProj.m03-viewProj.m01;
  planes[2].y=viewProj.m13-viewProj.m11;
  planes[2].z=viewProj.m23-viewProj.m21;
  planes[2].w=viewProj.m33-viewProj.m31;
  
  //Bottom
  planes[3].x=viewProj.m03+viewProj.m01;
  planes[3].y=viewProj.m13+viewProj.m11;
  planes[3].z=viewProj.m23+viewProj.m21;
  planes[3].w=viewProj.m33+viewProj.m31;
  
  //Near
  planes[4].x=viewProj.m03+viewProj.m02;
  planes[4].y=viewProj.m13+viewProj.m12;
  planes[4].z=viewProj.m23+viewProj.m22;
  planes[4].w=viewProj.m33+viewProj.m32;
  
  //Far
  planes[5].x=viewProj.m03-viewProj.m02;
  planes[5].y=viewProj.m13-viewProj.m12;
  planes[5].z=viewProj.m23-viewProj.m22;
  planes[5].w=viewProj.m33-viewProj.m32;

  for(Vector4f plane:planes)
  {
   float length=1.0f/new Vector3f(plane.x,plane.z).length(); //normalization of the plane can produce varying results. I found that for AABB or OOBB it can be very sensitive[i.e when the edges of the box are touching either sides of the screen it returns false and the box dissapears even though you can still see some parts of it and it appears the box dissapears whereas for sphere it works smoothly]
   
   plane.x*=length;
   plane.y*=length;
   plane.z*=length;
   plane.w*=-length; //either negate this w component or in the plane point distance computation change -distance to +distance,either works
   
   fPlanes.add(Plane.of(plane));
  }
  
  return this;
 }
 

 public boolean contained(AABB box)
 {
  for(Plane plane:fPlanes)
  {
   if(!plane.intersect(box))
   {
    return false;
   }
  }
  return true;
 }
}

这也是我计算矩阵的方式

public class MatrixMath4f
{
 public static Matrix4f perspective(float fov,float znear,float zfar,float aspectRatio,Matrix4f dst)
 {
  if(dst==null){dst=Matrix4f.setZero(new Matrix4f());}
  
  float cot=(float)(1.0f/Math.tan(Math.toRadians(fov/2)));
  float zp=zfar+znear;
  float zm=zfar-znear;
  
  dst.m00=cot/aspectRatio;
  dst.m01=0;
  dst.m02=0;
  dst.m03=0;
  
  dst.m10=0;
  dst.m11=cot;
  dst.m12=0;
  dst.m13=0;
  
  dst.m20=0;
  dst.m21=0;
  dst.m22=-zp/zm;
  dst.m23=-1f;
  
  dst.m30=0;
  dst.m31=0;
  dst.m32=-(2*zfar*znear)/zm;
  dst.m33=0;
  
  return dst;
 }

private static Vector3f normalize(Vector3f src,Vector3f dst)
 {
  if(dst==null){dst=new Vector3f();}
  
  dst.set(src);
  float length=dst.length();
  if(length>0){dst.scale(1.0f/length);}
  
  return dst;
 }
 
//for look at matrix
 public static Matrix4f lookAt(Point3f eyepos,Point3f center,Vector3f upVector,Matrix4f dst)
 {
  dst=Matrix4f.setZero(dst==null?new Matrix4f():dst);
  
  Vector3f
  lookAt=new Vector3f(),right=new Vector3f(1,0),up=new Vector3f(0,0);
  
  normalize(Point3f.sub(center,eyepos,lookAt),lookAt);
  normalize(upVector,up);
  normalize(Vector3f.cross(lookAt,up,right),right);
  normalize(Vector3f.cross(right,lookAt,up),up);
  
  dst.m00=right.x;
  dst.m01=up.x;
  dst.m02=-lookAt.x;
  dst.m03=0f;
  
  dst.m10=right.y;
  dst.m11=up.y;
  dst.m12=-lookAt.y;
  dst.m13=0f;
  
  dst.m20=right.z;
  dst.m21=up.z;
  dst.m22=-lookAt.z;
  dst.m23=0f;
  
  dst.translate((Vector3f)eyepos.toVector(null).scale(-1));
  dst.m33=1;
  
  return dst;
 } 

 //For Euler angles and position
 public static Matrix4f viewTransform(Point3f location,Vector3f eulers,Matrix4f dst)
 {
  dst=Matrix4f.setIdentity(dst==null?new Matrix4f():dst);
  
  dst.rotate((float)Math.toRadians(eulers.x),xaxis);
  dst.rotate((float)Math.toRadians(eulers.y),yaxis);
  dst.rotate((float)Math.toRadians(eulers.z),zaxis);
  dst.translate(location.negate(new Vector3f()));
  
  return dst;
 }
}