视锥体(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[][]; void calculateFrustumPlanes( void )
{
float p[]; // projection matrix
float mv[]; // model-view matrix
float mvp[]; // 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[ ] = mv[ ] * p[ ] + mv[ ] * p[ ] + mv[ ] * p[ ] + mv[ ] * p[];
mvp[ ] = mv[ ] * p[ ] + mv[ ] * p[ ] + mv[ ] * p[ ] + mv[ ] * p[];
mvp[ ] = mv[ ] * p[ ] + mv[ ] * p[ ] + mv[ ] * p[] + mv[ ] * p[];
mvp[ ] = mv[ ] * p[ ] + mv[ ] * p[ ] + mv[ ] * p[] + mv[ ] * p[]; mvp[ ] = mv[ ] * p[ ] + mv[ ] * p[ ] + mv[ ] * p[ ] + mv[ ] * p[];
mvp[ ] = mv[ ] * p[ ] + mv[ ] * p[ ] + mv[ ] * p[ ] + mv[ ] * p[];
mvp[ ] = mv[ ] * p[ ] + mv[ ] * p[ ] + mv[ ] * p[] + mv[ ] * p[];
mvp[ ] = mv[ ] * p[ ] + mv[ ] * p[ ] + mv[ ] * p[] + mv[ ] * p[]; mvp[ ] = mv[ ] * p[ ] + mv[ ] * p[ ] + mv[] * p[ ] + mv[] * p[];
mvp[ ] = mv[ ] * p[ ] + mv[ ] * p[ ] + mv[] * p[ ] + mv[] * p[];
mvp[] = mv[ ] * p[ ] + mv[ ] * p[ ] + mv[] * p[] + mv[] * p[];
mvp[] = mv[ ] * p[ ] + mv[ ] * p[ ] + mv[] * p[] + mv[] * p[]; mvp[] = mv[] * p[ ] + mv[] * p[ ] + mv[] * p[ ] + mv[] * p[];
mvp[] = mv[] * p[ ] + mv[] * p[ ] + mv[] * p[ ] + mv[] * p[];
mvp[] = mv[] * p[ ] + mv[] * p[ ] + mv[] * p[] + mv[] * p[];
mvp[] = mv[] * p[ ] + mv[] * p[ ] + mv[] * p[] + mv[] * p[]; //
// Extract the frustum's right clipping plane and normalize it.
// g_frustumPlanes[][] = mvp[ ] - mvp[ ];
g_frustumPlanes[][] = mvp[ ] - mvp[ ];
g_frustumPlanes[][] = mvp[] - mvp[ ];
g_frustumPlanes[][] = mvp[] - mvp[]; t = (float) sqrt( g_frustumPlanes[][] * g_frustumPlanes[][] +
g_frustumPlanes[][] * g_frustumPlanes[][] +
g_frustumPlanes[][] * g_frustumPlanes[][] ); g_frustumPlanes[][] /= t;
g_frustumPlanes[][] /= t;
g_frustumPlanes[][] /= t;
g_frustumPlanes[][] /= t; //
// Extract the frustum's left clipping plane and normalize it.
// g_frustumPlanes[][] = mvp[ ] + mvp[ ];
g_frustumPlanes[][] = mvp[ ] + mvp[ ];
g_frustumPlanes[][] = mvp[] + mvp[ ];
g_frustumPlanes[][] = mvp[] + mvp[]; t = (float) sqrt( g_frustumPlanes[][] * g_frustumPlanes[][] +
g_frustumPlanes[][] * g_frustumPlanes[][] +
g_frustumPlanes[][] * g_frustumPlanes[][] ); g_frustumPlanes[][] /= t;
g_frustumPlanes[][] /= t;
g_frustumPlanes[][] /= t;
g_frustumPlanes[][] /= t; //
// Extract the frustum's bottom clipping plane and normalize it.
// g_frustumPlanes[][] = mvp[ ] + mvp[ ];
g_frustumPlanes[][] = mvp[ ] + mvp[ ];
g_frustumPlanes[][] = mvp[] + mvp[ ];
g_frustumPlanes[][] = mvp[] + mvp[]; t = (float) sqrt( g_frustumPlanes[][] * g_frustumPlanes[][] +
g_frustumPlanes[][] * g_frustumPlanes[][] +
g_frustumPlanes[][] * g_frustumPlanes[][] ); g_frustumPlanes[][] /= t;
g_frustumPlanes[][] /= t;
g_frustumPlanes[][] /= t;
g_frustumPlanes[][] /= t; //
// Extract the frustum's top clipping plane and normalize it.
// g_frustumPlanes[][] = mvp[ ] - mvp[ ];
g_frustumPlanes[][] = mvp[ ] - mvp[ ];
g_frustumPlanes[][] = mvp[] - mvp[ ];
g_frustumPlanes[][] = mvp[] - mvp[]; t = (float) sqrt( g_frustumPlanes[][] * g_frustumPlanes[][] +
g_frustumPlanes[][] * g_frustumPlanes[][] +
g_frustumPlanes[][] * g_frustumPlanes[][] ); g_frustumPlanes[][] /= t;
g_frustumPlanes[][] /= t;
g_frustumPlanes[][] /= t;
g_frustumPlanes[][] /= t; //
// Extract the frustum's far clipping plane and normalize it.
// g_frustumPlanes[][] = mvp[ ] - mvp[ ];
g_frustumPlanes[][] = mvp[ ] - mvp[ ];
g_frustumPlanes[][] = mvp[] - mvp[];
g_frustumPlanes[][] = mvp[] - mvp[]; t = (float) sqrt( g_frustumPlanes[][] * g_frustumPlanes[][] +
g_frustumPlanes[][] * g_frustumPlanes[][] +
g_frustumPlanes[][] * g_frustumPlanes[][] ); g_frustumPlanes[][] /= t;
g_frustumPlanes[][] /= t;
g_frustumPlanes[][] /= t;
g_frustumPlanes[][] /= t; //
// Extract the frustum's near clipping plane and normalize it.
// g_frustumPlanes[][] = mvp[ ] + mvp[ ];
g_frustumPlanes[][] = mvp[ ] + mvp[ ];
g_frustumPlanes[][] = mvp[] + mvp[];
g_frustumPlanes[][] = mvp[] + mvp[]; t = (float) sqrt( g_frustumPlanes[][] * g_frustumPlanes[][] +
g_frustumPlanes[][] * g_frustumPlanes[][] +
g_frustumPlanes[][] * g_frustumPlanes[][] ); g_frustumPlanes[][] /= t;
g_frustumPlanes[][] /= t;
g_frustumPlanes[][] /= t;
g_frustumPlanes[][] /= t; } bool isBoundingSphereInFrustum( float x, float y, float z)
{
for( int i = ; i < ; ++i )
{
if( g_frustumPlanes[i][] * x +
g_frustumPlanes[i][] * y +
g_frustumPlanes[i][] * z +
g_frustumPlanes[i][] <= )
return false;
} return true;
}
视锥体(frustum)裁剪的更多相关文章
- DirectX11 With Windows SDK--20 硬件实例化与视锥体裁剪
前言 这一章将了解如何在DirectX 11利用硬件实例化技术高效地绘制重复的物体,以及使用视锥体裁剪技术提前将位于视锥体外的物体进行排除. 在此之前需要额外了解的章节如下: 章节回顾 18 使用Di ...
- 齐次坐标概念&&透视投影变换推导
http://daehgib.blog.163.com/blog/static/1861071422011579551134/ 透视投影是3D固定流水线的重要组成部分,是将相机空间中的点从视锥体(fr ...
- 透视投影(Perspective Projection)变换推导
透视投影是3D固定流水线的重要组成部分,是将相机空间中的点从视锥体(frustum)变换到规则观察体(Canonical View Volume)中,待裁剪完毕后进行透视除法的行为.在算法中它是通过透 ...
- Shadow Mapping 的原理与实践(一)
早在上世纪七十年代末,Williams在他的“Casting Curved Shadows on Curved Surface”一文中提出了名为Shadow Map的阴影生成技术.之后,他人在此基础上 ...
- Shadow Mapping 的原理与实践 【转】
早在上世纪七十年代末,Williams在他的“Casting Curved Shadows on Curved Surface”一文中提出了名为Shadow Map的阴影生成技术.之后,他人在此基础上 ...
- three.js一个简单demo学些和讲解
叉口裁剪球体案例 二话不说先上效果图: 全部代码带注释 <!DOCTYPE html> <html lang="en"> <head> < ...
- three.js学习笔记--基础知识
基础知识 从去年开始就在计划中的three.js终于开始了 历史介绍 (摘自ijunfan1994的转载,感谢作者) OpenGL大概许多人都有所耳闻,它是最常用的跨平台图形库. WebGL是基于Op ...
- Unity Occlusion Culling 遮挡剔除研究
本文章由cartzhang编写,转载请注明出处. 所有权利保留. 文章链接:http://blog.csdn.net/cartzhang/article/details/52684127 作者:car ...
- 【转】《Unity Shader入门精要》冯乐乐著 书中彩图
为方便个人手机学习时候查阅,从网上转来这些彩图. 如属过当行为,联系本人删除. 勘错表 http://candycat1992.github.io/unity_shaders_book/unity_s ...
随机推荐
- FIS-PLUS百度前端框架使用过程
1.如果后端开发语言是php,那么前端fis框架用FIS-PLUS,如果是java则用jello 2.FIS-PLUS使用步骤 1.安装nodejs 2.安装 fis npm install -g f ...
- Lintcode:Longest Common Subsequence 解题报告
Longest Common Subsequence 原题链接:http://lintcode.com/zh-cn/problem/longest-common-subsequence/ Given ...
- Python3玩转单链表——逆转单向链表pythonic版
[本文出自天外归云的博客园] 链表是由节点构成的,一个指针代表一个方向,如果一个构成链表的节点都只包含一个指针,那么这个链表就是单向链表. 单向链表中的节点不光有代表方向的指针变量,也有值变量.所以我 ...
- CentOS7 yum 安装mysql 5.6
原文:https://www.cnblogs.com/zhangyin6985/p/6307219.html 转自:http://www.centoscn.com/mysql/2016/0315/68 ...
- RabbitMQ基础组件和SpringBoot整合RabbitMQ简单示例
交换器(Exchange) 交换器就像路由器,我们先是把消息发到交换器,然后交换器再根据绑定键(binding key)和生产者发送消息时的路由键routingKey, 按照交换类型Exchange ...
- 基于HTML5功能强大的滑块幻灯片
分享一款功能强大的HTML5滑块幻灯片.这是一款基于jQuery+HTML5实现的图片切换幻灯片代码.效果图如下: 在线预览 源码下载 实现的代码. html代码: <section cla ...
- 基于jQuery功能非常强大的图片裁剪插件
今天我们要来介绍一款基于jQuery功能非常强大的图片裁剪插件,这款jQuery图片裁剪插件可以选择裁剪框的尺寸比例,可以设置高宽尺寸,同时可以设置图片翻转角度,当然也支持图片的缩放,裁剪框也可以用鼠 ...
- Android基础——Fragment控制切换多个页面
今天接着上一篇文章,讲解一下Fragment的控制,主要是切换View和页面替换等操作.还有就是如何获取Fragment的管理对象,以及与Activity的通信方式. (PS:新建的QQ群,有兴趣可以 ...
- spark1.6内存管理
Spark从1.6.0版本开始,内存管理模块就发生了改变,旧版本的内存管理模块是实现了StaticMemoryManager 类,现在被称为"legacy"."Leg ...
- C语言 · 字符串的展开
算法训练 字符串的展开 时间限制:1.0s 内存限制:256.0MB 在初赛普及组的“阅读程序写结果”的问题中,我们曾给出一个字符串展开的例子:如果在输入的字符串中,含有类似于“d ...
