视锥体(frustum)裁剪

原文地址:http://www.linuxgraphics.cn/graphics/opengl_view_frustum_culling.html

背景

视锥体(frustum),是指场景中摄像机的可见的一个锥体范围。它有上、下、左、右、近、远,共6个面组成。在视锥体内的景物可见,反之则不可见。为提高性能,只对其中与视锥体有交集的对象进行绘制。

视锥体

我们计算出视锥体六个面的空间平面方程,将点坐标分别代入六个面的平面方程做比较,则可以判断点是否在视锥体内。

空间平面方程可表示为:

    Ax+By+Cz=0

对于点(x1, y1, z1),有

若 Ax1+By1+Cz1 = 0,则点在平面上;
若 Ax1+By1+Cz1 < 0,则点在平面的一侧;
若 Ax1+By1+Cz1 = 0,则点在平面的另一侧;

求视锥平面系数1

这里介绍的算法,可以直接从世界、观察以及投影矩阵中计算出Viewing Frustum的六个面。它快速,准确,并且允许我们在相机空间(camera space)、世界空间(world space)或着物体空间(object space)快速确定Frustum planes。

我们先仅仅从投影矩阵(project)开始,也就是假设世界矩阵(world)和观察矩阵(view)都是单位化了的矩阵。这就意味着相机位于世界坐标系下的原点,并且朝向Z轴的正方向。

定义一个顶点v(x y z w=1)和一个4*4的投影矩阵M=m(i,j),然后我们使用该矩阵M对顶点v进行转换,转换后的顶点为v‘= (x‘ y‘ z‘ w‘),可以写成这样:

转换后,viewing frustum实际上就变成了一个与轴平行的盒子,如果顶点 v‘ 在这个盒子里,那么转换前的顶点 v 就在转换前的viewing frustum里。在OpenGL下,如果下面的几个不等式都成立的话,那么 v‘ 就在这个盒子里。

   -w‘ < x‘ < w‘
   -w‘ < y‘ < w‘
   -w‘ < z‘ < w‘

可得到如下结论,列在下表里:

我们假设现在想测试 x‘ 是否在左半边空间,只需判断

    -w < x‘

用上面的信息,等式我们可以写成:

    −(v • row4 ) < (v • row1 )

    0 < (v • row4 ) + (v • row1 )

    0 < v • (row4 + row1 )

写到这里,其实已经等于描绘出了转换前的viewing frustum的左裁剪面的平面方程:

    x(m41 + m11) + y(m42 + m12) + z(m43 + m13) + w(m44 + m14) = 0

当W = 1,我们可简单成如下形式:

    x(m41 + m11) + y(m42 + m12) + z(m43 + m13) + (m44 + m14) = 0

这就给出了一个基本平面方程:

    ax + by + cz + d = 0

其中,a = ( m41 + m11) , b = ( m42 + m12 ), c = ( m43 + m13) , d = ( m44 + m14 )

到这里左裁剪面就得到了。重复以上几步,可推导出到其他的几个裁剪面,具体见参考文献1.

需要注意的是:最终得到的平面方程都是没有单位化的(平面的法向量不是单位向量),并且法向量指向空间的内部。这就是说,如果要判断 v 在空间内部,那么6个面必须都满足ax + by + cz + d > 0

到目前为止,我们都是假设世界矩阵( world )和观察矩阵( view )都是单位化了的矩阵。但是,本算法并不想受这种条件的限制,而是希望可以在任何条件下都能使用。实际上,这也并不复杂,并且简单得令人难以置信。如果你 仔细想一下就会立刻明白了,所以我们不再对此进行详细解释了,下面给出3个结论:

  • 1. 如果矩阵 M 等于投影矩阵 P ( M = P ),那么算法给出的裁剪面是在相机空间(camera space)
  • 2. 如果矩阵 M 等于观察矩阵 V 和投影矩阵 P 的组合( M = V * P ),那么算法给出的裁剪面是在世界空间(world space)
  • 3. 如果矩阵 M 等于世界矩阵 W,观察矩阵 V 和投影矩阵 P 的组合( M = W* V * P ),呢么算法给出的裁剪面是在物体空间(object space)

判断节点是否在视锥内

通过各种包围体方法求出近似包围体,对包围体上的各个点对视锥六个面作判断,存在以下三种情况:

  • 如果所有顶点都在视锥范围内,则待判区域一定在视锥范围内;
  • 如果只有部分顶点在视锥范围内,则待判区域与视锥体相交,我们同样视为可见;
  • 如果所有顶点都不在视锥范围内,那么待判区域很可能不可见了,但有一种情况例外,就是视锥体在长方体以内,这种情况我们要加以区分。

基于OpenGL实现

float g_frustumPlanes[6][4];

void calculateFrustumPlanes( void )
{
    float p[16];   // projection matrix
    float mv[16];  // model-view matrix
    float mvp[16]; // model-view-projection matrix
    float t;

    glGetFloatv( GL_PROJECTION_MATRIX, p );
    glGetFloatv( GL_MODELVIEW_MATRIX, mv );

    //
    // Concatenate the projection matrix and the model-view matrix to produce
    // a combined model-view-projection matrix.
    //

    mvp[ 0] = mv[ 0] * p[ 0] + mv[ 1] * p[ 4] + mv[ 2] * p[ 8] + mv[ 3] * p[12];
    mvp[ 1] = mv[ 0] * p[ 1] + mv[ 1] * p[ 5] + mv[ 2] * p[ 9] + mv[ 3] * p[13];
    mvp[ 2] = mv[ 0] * p[ 2] + mv[ 1] * p[ 6] + mv[ 2] * p[10] + mv[ 3] * p[14];
    mvp[ 3] = mv[ 0] * p[ 3] + mv[ 1] * p[ 7] + mv[ 2] * p[11] + mv[ 3] * p[15];

    mvp[ 4] = mv[ 4] * p[ 0] + mv[ 5] * p[ 4] + mv[ 6] * p[ 8] + mv[ 7] * p[12];
    mvp[ 5] = mv[ 4] * p[ 1] + mv[ 5] * p[ 5] + mv[ 6] * p[ 9] + mv[ 7] * p[13];
    mvp[ 6] = mv[ 4] * p[ 2] + mv[ 5] * p[ 6] + mv[ 6] * p[10] + mv[ 7] * p[14];
    mvp[ 7] = mv[ 4] * p[ 3] + mv[ 5] * p[ 7] + mv[ 6] * p[11] + mv[ 7] * p[15];

    mvp[ 8] = mv[ 8] * p[ 0] + mv[ 9] * p[ 4] + mv[10] * p[ 8] + mv[11] * p[12];
    mvp[ 9] = mv[ 8] * p[ 1] + mv[ 9] * p[ 5] + mv[10] * p[ 9] + mv[11] * p[13];
    mvp[10] = mv[ 8] * p[ 2] + mv[ 9] * p[ 6] + mv[10] * p[10] + mv[11] * p[14];
    mvp[11] = mv[ 8] * p[ 3] + mv[ 9] * p[ 7] + mv[10] * p[11] + mv[11] * p[15];

    mvp[12] = mv[12] * p[ 0] + mv[13] * p[ 4] + mv[14] * p[ 8] + mv[15] * p[12];
    mvp[13] = mv[12] * p[ 1] + mv[13] * p[ 5] + mv[14] * p[ 9] + mv[15] * p[13];
    mvp[14] = mv[12] * p[ 2] + mv[13] * p[ 6] + mv[14] * p[10] + mv[15] * p[14];
    mvp[15] = mv[12] * p[ 3] + mv[13] * p[ 7] + mv[14] * p[11] + mv[15] * p[15];

    //
    // Extract the frustum‘s right clipping plane and normalize it.
    //

    g_frustumPlanes[0][0] = mvp[ 3] - mvp[ 0];
    g_frustumPlanes[0][1] = mvp[ 7] - mvp[ 4];
    g_frustumPlanes[0][2] = mvp[11] - mvp[ 8];
    g_frustumPlanes[0][3] = mvp[15] - mvp[12];

    t = (float) sqrt( g_frustumPlanes[0][0] * g_frustumPlanes[0][0] +
                      g_frustumPlanes[0][1] * g_frustumPlanes[0][1] +
                      g_frustumPlanes[0][2] * g_frustumPlanes[0][2] );

    g_frustumPlanes[0][0] /= t;
    g_frustumPlanes[0][1] /= t;
    g_frustumPlanes[0][2] /= t;
    g_frustumPlanes[0][3] /= t;

    //
    // Extract the frustum‘s left clipping plane and normalize it.
    //

    g_frustumPlanes[1][0] = mvp[ 3] + mvp[ 0];
    g_frustumPlanes[1][1] = mvp[ 7] + mvp[ 4];
    g_frustumPlanes[1][2] = mvp[11] + mvp[ 8];
    g_frustumPlanes[1][3] = mvp[15] + mvp[12];

    t = (float) sqrt( g_frustumPlanes[1][0] * g_frustumPlanes[1][0] +
                      g_frustumPlanes[1][1] * g_frustumPlanes[1][1] +
                      g_frustumPlanes[1][2] * g_frustumPlanes[1][2] );

    g_frustumPlanes[1][0] /= t;
    g_frustumPlanes[1][1] /= t;
    g_frustumPlanes[1][2] /= t;
    g_frustumPlanes[1][3] /= t;

    //
    // Extract the frustum‘s bottom clipping plane and normalize it.
    //

    g_frustumPlanes[2][0] = mvp[ 3] + mvp[ 1];
    g_frustumPlanes[2][1] = mvp[ 7] + mvp[ 5];
    g_frustumPlanes[2][2] = mvp[11] + mvp[ 9];
    g_frustumPlanes[2][3] = mvp[15] + mvp[13];

    t = (float) sqrt( g_frustumPlanes[2][0] * g_frustumPlanes[2][0] +
                      g_frustumPlanes[2][1] * g_frustumPlanes[2][1] +
                      g_frustumPlanes[2][2] * g_frustumPlanes[2][2] );

    g_frustumPlanes[2][0] /= t;
    g_frustumPlanes[2][1] /= t;
    g_frustumPlanes[2][2] /= t;
    g_frustumPlanes[2][3] /= t;

    //
    // Extract the frustum‘s top clipping plane and normalize it.
    //

    g_frustumPlanes[3][0] = mvp[ 3] - mvp[ 1];
    g_frustumPlanes[3][1] = mvp[ 7] - mvp[ 5];
    g_frustumPlanes[3][2] = mvp[11] - mvp[ 9];
    g_frustumPlanes[3][3] = mvp[15] - mvp[13];

    t = (float) sqrt( g_frustumPlanes[3][0] * g_frustumPlanes[3][0] +
                      g_frustumPlanes[3][1] * g_frustumPlanes[3][1] +
                      g_frustumPlanes[3][2] * g_frustumPlanes[3][2] );

    g_frustumPlanes[3][0] /= t;
    g_frustumPlanes[3][1] /= t;
    g_frustumPlanes[3][2] /= t;
    g_frustumPlanes[3][3] /= t;

    //
    // Extract the frustum‘s far clipping plane and normalize it.
    //

    g_frustumPlanes[4][0] = mvp[ 3] - mvp[ 2];
    g_frustumPlanes[4][1] = mvp[ 7] - mvp[ 6];
    g_frustumPlanes[4][2] = mvp[11] - mvp[10];
    g_frustumPlanes[4][3] = mvp[15] - mvp[14];

    t = (float) sqrt( g_frustumPlanes[4][0] * g_frustumPlanes[4][0] +
                      g_frustumPlanes[4][1] * g_frustumPlanes[4][1] +
                      g_frustumPlanes[4][2] * g_frustumPlanes[4][2] );

    g_frustumPlanes[4][0] /= t;
    g_frustumPlanes[4][1] /= t;
    g_frustumPlanes[4][2] /= t;
    g_frustumPlanes[4][3] /= t;

    //
    // Extract the frustum‘s near clipping plane and normalize it.
    //

    g_frustumPlanes[5][0] = mvp[ 3] + mvp[ 2];
    g_frustumPlanes[5][1] = mvp[ 7] + mvp[ 6];
    g_frustumPlanes[5][2] = mvp[11] + mvp[10];
    g_frustumPlanes[5][3] = mvp[15] + mvp[14];

    t = (float) sqrt( g_frustumPlanes[5][0] * g_frustumPlanes[5][0] +
                      g_frustumPlanes[5][1] * g_frustumPlanes[5][1] +
                      g_frustumPlanes[5][2] * g_frustumPlanes[5][2] );

    g_frustumPlanes[5][0] /= t;
    g_frustumPlanes[5][1] /= t;
    g_frustumPlanes[5][2] /= t;
    g_frustumPlanes[5][3] /= t;

}

bool isBoundingSphereInFrustum( float x, float y, float z)
{
    for( int i = 0; i < 6; ++i )
    {
        if( g_frustumPlanes[i][0] * x +
            g_frustumPlanes[i][1] * y +
            g_frustumPlanes[i][2] * z +
            g_frustumPlanes[i][3] <= 0)
            return false;
    }

    return true;
}

视锥体(frustum)裁剪

时间: 2024-08-29 06:11:05

视锥体(frustum)裁剪的相关文章

DirectX11 With Windows SDK--20 硬件实例化与视锥体裁剪

前言 这一章将了解如何在DirectX 11利用硬件实例化技术高效地绘制重复的物体,以及使用视锥体裁剪技术提前将位于视锥体外的物体进行排除. 在此之前需要额外了解的章节如下: 章节回顾 18 使用DirectXCollision库进行碰撞检测 19 模型加载:obj格式的读取及使用二进制文件提升读取效率 DirectX11 With Windows SDK完整目录 Github项目源码 硬件实例化(Hardware Instancing) 硬件实例化指的是在场景中绘制同一个物体多次,但是是以不同

Unity3D技术之相机使用技巧-从相机到给定距离的视锥体大小

欢迎来到unity学习.unity培训.unity企业培训教育专区,这里有很多U3D资源.U3D培训视频.U3D教程.U3D常见问题.U3D项目源码,我们致力于打造业内unity3d培训.学习第一品牌. 从相机到给定距离的视锥体大小 从相机到一定距离的视锥体横截面在世界空间中定义为一个矩形,将可视区域框在内.有时,这对计算给定距离下该矩形的大小.或找出给定矩形大小时的距离比较有用.例如,如果一台移动的相机需要始终完整拍摄到一个对象(如玩家),就不能靠得太近,否则物体的一部分拍不到. 在给定距离的

图形渲染管线简介

The Graphics Rendering Pipeline 渲染管线,这章主要讲光栅化渲染管线. 毕业前实习时,也实现过一个简单的软光栅化渲染管线,再复习一下. 在计算机图形学领域,shading指基于表面相对灯光的角度.距灯光的距离.相对于相机的角度和材质的属性等来修改物体/表面/多边形的颜色,进而创造一个具有真实感效果的过程. In computer graphics, shading refers to the process of altering the color of an o

Unity Shader入门精要学习笔记 - 第4章 学习 Shader 所需的数学基础

摘录自 冯乐乐的<Unity Shader入门精要> 笛卡尔坐标系 1)二维笛卡尔坐标系 在游戏制作中,我们使用的数学绝大部分都是计算位置.距离.角度等变量.而这些计算大部分都是在笛卡尔坐标系下进行的. 一个二维的笛卡尔坐标系包含了两个部分的信息: 一个特殊的位置,即原点,它是整个坐标系的中心. 两条过原点的互相垂直的矢量,即X轴和Y轴.这些坐标轴也被称为是该坐标的矢量. OpenGL 和 DirectX 使用了不同的二维笛卡尔坐标系.如下图所示: 2)三维笛卡尔坐标系 在三维笛卡尔坐标系中,

【翻译】View Frustum Culling --3 Clip Space Approach – Extracting the Planes

3.使用裁剪空间的方法提取平面 上一篇中,我们讨论了通过几何的方法提取视锥体的六个片面.在这一篇中,我们继续讨论通过裁剪空间的方法来提取视锥体的平面. 假设现在在世界坐标系中有一点p=(x,yz,1),modelview矩阵记作M,projection矩阵记作P.当点p经过这两个矩阵变换后变到pc=(xc,yc,zc,wc)的位置,即 因为pc点是由齐次坐标表示的,则其正则化后为pcn: 在正则化的裁剪空间中,视锥体的中心与单位立方体的原点对齐,且它的六个平面由以下平面界定: Left Plan

【翻译】 View Frustum Culling --1 View Frustum’s Shape

这是一些列来自lighthouse3d的视锥体裁剪教程.旨在学习总结,及便于查阅. 1.视锥体的形状 在OpenGL中,透视投影是由两个函数定义的gluPerspective和gluLookAt.我们先来回顾下这两个函数的参数 gluPerspective(fov, ratio, nearDist, farDist); gluLookAt(px,py,pz, lx,ly,lz, ux,uy,uz); 照相机的位置在四棱锥的顶点处(px,py,pz) 记为向量p, 观察点的位置在(lx,ly,lz

Libgdx New 3D API 教程之 -- Libgdx中的3D frustum culling

This blog is a chinese version of xoppa's Libgdx new 3D api tutorial. For English version, please refer to >>LINK<< 我要偷懒了,好久没看LibGDX,也很久没看3D,新教程的题目我就不懂了.不过看了课程的内容,相信你们都会理解. 正文: 当渲染一个3d场景时,其中真正可见的对象通常都比总对象数少很多.因此渲染全部的物体,包括那些根本看不到的,即浪费了富贵的GPU时间,

探索未知种族之osg类生物---渲染遍历之裁剪三

前言 在osgUtil::CullVisitor,我们发现apply函数的重载中,有CullVisitor::apply(Group& node),CullVisitor::apply(Switch& node), CullVisitor::apply(LOD& node),CullVisitor::apply(Geode& node),CullVisitor::apply(Node& node)是一样的函数内容.所以这五个函数我们就挑出CullVisitor::a

OpenGL学习进程(5)第三课:视口与裁剪区域

本节是OpenGL学习的第三个课时,下面介绍如何运用显示窗体的视口和裁剪区域:     (1)知识点引入:     1)问题现象: 当在窗体中绘制图形后,拉伸窗体图形形状会发生变化: #include <GL/glut.h> #include <math.h> const float Pi = 3.1415926f; const int n = 1000; const float R = 0.8f; void init(void) { glClearColor(0.0,0.0,0.