D3D Frustum六个裁剪平面生成原理

2024-01-27 04:32

本文主要是介绍D3D Frustum六个裁剪平面生成原理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


概念介绍

Frustum裁剪是CLOD中很重要的一个算法,很多文章都是一句话就过去,或者直接给出代码。但是数学推导很少给出,本文章的目的就是解释大家看这些代码中的疑问。
透视投影是将相机空间中的点从视锥体(view frustum)变换到规则观察体(Canonical View Volume,CCV)中,即是世界空间的视锥体(view frustum)中的任何一个点,如果经过投影矩阵变换后,它必定规则观察体(Canonical View Volume,CCV)中,也即是在(-1,-1,0) ~ (1,1,1)之间的值如图1所示
这里 我们定义
P0=(-1,-1,0)
P1=(1,-1,0)
P2=(1,-1,1)
P3=(-1,-1,1)
P4=(-1,1,0)
P5=(1,1,0)
P6=(1,1,1)
P7=(-1,1,1)
我们通过这8个点构建6个面,通过3点共面,假设面是Pos0,pos1,pos2构成,u=Pos1-Pos0,v=Pos2-Pos0,那么法向量n=u×v。则d=-(n×Pos0)。得到每个平面的平面公式n,d,从而得到A,B,C,D,(n的xn,yn,zn,d,就是a,b,c,d)。这里要注意构造面试后P点的顺序,d3D是按顺时针来构造面。法线都是由frustum里到外。
Near: (P0,P4,P5) n=(0,0,-1),d=0 0x+0y-1z+0=0
Far: (P2,P6,P7) n=(0,0,1),d=-1 0x+0y+1z-1=0
Left: (P0,P3,P7) n=(-1,0,0),d=-1 -1x+0y+0z-1=0
Right: (P1,P5,P6) n=(1,0,0),d=-1 1x+0y+0z-1=0
Top: (P4,P7,P6) n=(0,1,0),d=-1 0x+1y+0z-1=0
Bottom: (P0,P1,P2) n=(0,-1,0),d=-1 0x-1y+0z-1=0
我们假设这六个平面中某个平面上存在一个点P’(x’,y’,c’,1),那它的平面方程为A’x’ + B’y’+ C’z’+ D’= 0,在进行投影变换之前的坐标为P(x,y,z,1),在世界空间中的平面方程Ax + By + Cz + D = 0,如果P‘点在CVV中,那点P(x,y,z,1)必定在view Frustum中。
原理一
1.推理
由介绍我们可以得到下面的等式
|A’|                      |A|
(x’,y’,c’,1) × |B’| = 0 (x,y,z,1) × |B| = 0
|C’|                      |C|
|D’|                      |D|
同时(x,y,c,1) × Tvproj= (x‘,y’,z‘,1)
注意Tviewproj矩阵=Tview×Tproj(即是摄像机矩阵和投影矩阵相乘。是将世界坐标转换到视平面的变化矩阵)
结合着3个等式我们可以得到
|A|                     |A’|
|B| =Tviewproj × |B’|  (等式1)
|C|                    |C’|
|D|                    |D’|
通过等式1,我们可以求出6个面在世界坐标系下的平面方程Ax + By + Cz + D = 0
2代码
void CFrustum::InitFrustum1(const D3DXMATRIX& aoViewMatrix,const D3DXMATRIX& aoProjMatrix)
{
D3DXMATRIX loComboMatrix;
D3DXMATRIX loInvComboMatrix;
D3DXMatrixMultiply(&loComboMatrix,&aoViewMatrix,&aoProjMatrix);
// 求得view * proj的逆矩阵.
D3DXMatrixInverse(&loInvComboMatrix, NULL, &loComboMatrix );
// 如果经过投影矩阵,所有的三维世界坐标的点都变为(-1,-1,0) ~ (1,1,1)之间的值.
// 将同次空间的临界值填入moCVVPos.
moCVVPos[0].x = -1.0f; moCVVPos[0].y = -1.0f; moCVVPos[0].z = 0.0f;
moCVVPos[1].x = 1.0f; moCVVPos[1].y = -1.0f; moCVVPos[1].z = 0.0f;
moCVVPos[2].x = 1.0f; moCVVPos[2].y = -1.0f; moCVVPos[2].z = 1.0f;
moCVVPos[3].x = -1.0f; moCVVPos[3].y = -1.0f; moCVVPos[3].z = 1.0f;
moCVVPos[4].x = -1.0f; moCVVPos[4].y = 1.0f; moCVVPos[4].z = 0.0f;
moCVVPos[5].x = 1.0f; moCVVPos[5].y = 1.0f; moCVVPos[5].z = 0.0f;
moCVVPos[6].x = 1.0f; moCVVPos[6].y = 1.0f; moCVVPos[6].z = 1.0f;
moCVVPos[7].x = -1.0f; moCVVPos[7].y = 1.0f; moCVVPos[7].z = 1.0f;
//将cvv坐标转换回世界坐标
for(int i = 0; i < 8; i++ )
D3DXVec3TransformCoord( &moCVVPos[i], &moCVVPos[i], &loInvComboMatrix );
// 通过得到的世界坐标制作平截头体平面.
// 向量由平截头体内部指向外部的平面.
D3DXPlaneFromPoints(&moFrustumPlane[FRUSTUM_PLANE_FRONT], moCVVPos , moCVVPos+4, moCVVPos+5); // 近平面(near)
D3DXPlaneFromPoints(&moFrustumPlane[FRUSTUM_PLANE_BACK], moCVVPos+2, moCVVPos+6, moCVVPos+7); // 远平面(far)
D3DXPlaneFromPoints(&moFrustumPlane[FRUSTUM_PLANE_LEFT], moCVVPos , moCVVPos+3, moCVVPos+7); // 左平面(left)
D3DXPlaneFromPoints(&moFrustumPlane[FRUSTUM_PLANE_RIGHT], moCVVPos+1, moCVVPos+5, moCVVPos+6); // 右平面(right)
D3DXPlaneFromPoints(&moFrustumPlane[FRUSTUM_PLANE_TOP], moCVVPos+4, moCVVPos+7, moCVVPos+6); // 上平面(top)
D3DXPlaneFromPoints(&moFrustumPlane[FRUSTUM_PLANE_BOTTOM], moCVVPos , moCVVPos+1, moCVVPos+2); // 下平面(bottom)
}
上面的代码很容易理解,完全按原理来实现,但是当我们每次调用这个函数,我们每次都要求Tvproj矩阵的逆矩阵,同时还要重新用点来构造每个frustum平面,并且调用的是D3DXPlaneFromPoints函数。我们还有更好的方法吗?下面我们接着优化我们的代码,优化之前我们接着分析
原理二
1.推理
我们知道在P(x,y,z,1) 经过Tviewproj矩阵变换后得到点P’(x’,y’,z’,w’),这个点在前面的推导过程中,保证是在frustum的某个平面上的.这个时候我们知道如果对P的每个分量都除以w’就可以把P’归一化到一个长方体的空间,即x’/w’, y’/w’在[-1,1]区间,z’/w’在[0,1]区间,所以如果有一个投影转换后的点P1’,它的x1’,y1’在[-w’,w’]区间,z1’在[0,w’]区间,这个点肯定就在视锥体内.
|a11,a12,a13,a14|
假设Tviewproj = [v0,v1,v2,v3] = |a21,a22,a23,a24|
|a31,a32,a33,a34|
|a41,a42,a43,a44|
其中其中V0,v1,v2,v3是四个列向量.
P ’= P×Tviewproj = (P?v0, P?v1, P?v2, P?v3 ) = (x’,y’,z’,w’)
根据上面的x的范围我们有:
-w’ <= x’ 就是 -p’?v3 <= p’?v0 就是 p’?v0 + p’?v3 <= 0 得到 P’?(v0+v3) <= 0
把列向量换成矩阵的元素有
(x,y,z,1).(m_11 + m_14, m12 + m24, m13 + m34, m14 + m44 ) <= 0
就是
(m_11+m_14)*x + (m_12 + m_24)*y + (m_13 + m_34)*z + (m_14 + m_44)*1 <=0
简单地看这是一个 A*x + B*y + C*z + D*1 <= 0 描述了一个半空间,就是平面A*x + B*y + C*z + D*1 = 0右边的空间
所以我们知道是锥体的左裁减面为
(m_11+m_14)*x + (m_12 + m_24)*y + (m_13 + m_34)*z + (m_14 + m_44)*1= 0.
相应地可以计算出 6个裁减面
Left= (v0 + v3).
Right =(v3 - v0)
Bottom =(v3 + v1)
Top = (v3 - v1 )
Near = (v2)
Far = (v3 - v2)
裁减的时候把点带入公式Ax+By+Cz+Dw看大与0还是小与0就可以知道在平面的里面还是外面,在实际计算中需要对A,B,C,D进行归一化。因为我们在处理坐标的时候,点几乎都是经过归一化的。
2.代码
void CFrustum::InitFrustum(const D3DXMATRIX& aoViewMatrix,const D3DXMATRIX& aoProjMatrix)
{
D3DXMATRIX loComboMatrix;
D3DXMatrixMultiply(&loComboMatrix,&aoViewMatrix,&aoProjMatrix);
// calculate the planes
// Near
D3DXPLANE* lpPlane = &moFrustumPlane[FRUSTUM_PLANE_NEAR];
lpPlane->a = loComboMatrix._14 + loComboMatrix._13;
lpPlane->b = loComboMatrix._24 + loComboMatrix._23;
lpPlane->c = loComboMatrix._34 + loComboMatrix._33;
lpPlane->d = loComboMatrix._44 + loComboMatrix._43;
D3DXPlaneNormalize(lpPlane,lpPlane);
// Far
lpPlane = &moFrustumPlane[FRUSTUM_PLANE_FAR];
lpPlane->a = loComboMatrix._14 - loComboMatrix._13;
lpPlane->b = loComboMatrix._24 - loComboMatrix._23;
lpPlane->c = loComboMatrix._34 - loComboMatrix._33;
lpPlane->d = loComboMatrix._44 - loComboMatrix._43;
D3DXPlaneNormalize(lpPlane,lpPlane);
//Left
lpPlane = &moFrustumPlane[FRUSTUM_PLANE_LEFT];
lpPlane->a = loComboMatrix._14 + loComboMatrix._11; // Left
lpPlane->b = loComboMatrix._24 + loComboMatrix._21;
lpPlane->c = loComboMatrix._34 + loComboMatrix._31;
lpPlane->d = loComboMatrix._44 + loComboMatrix._41;
D3DXPlaneNormalize(lpPlane,lpPlane);
// Right
lpPlane = &moFrustumPlane[FRUSTUM_PLANE_RIGHT];
lpPlane->a = loComboMatrix._14 - loComboMatrix._11;
lpPlane->b = loComboMatrix._24 - loComboMatrix._21;
lpPlane->c = loComboMatrix._34 - loComboMatrix._31;
lpPlane->d = loComboMatrix._44 - loComboMatrix._41;
D3DXPlaneNormalize(lpPlane,lpPlane);
// Top
lpPlane = &moFrustumPlane[FRUSTUM_PLANE_TOP];
lpPlane->a = loComboMatrix._14 - loComboMatrix._12;
lpPlane->b = loComboMatrix._24 - loComboMatrix._22;
lpPlane->c = loComboMatrix._34 - loComboMatrix._32;
lpPlane->d = loComboMatrix._44 - loComboMatrix._42;
D3DXPlaneNormalize(lpPlane,lpPlane);
// Bottom
lpPlane = &moFrustumPlane[FRUSTUM_PLANE_BOTTOM];
lpPlane->a = loComboMatrix._14 + loComboMatrix._12; // Bottom
lpPlane->b = loComboMatrix._24 + loComboMatrix._22;
lpPlane->c = loComboMatrix._34 + loComboMatrix._32;
lpPlane->d = loComboMatrix._44 + loComboMatrix._42;
D3DXPlaneNormalize(lpPlane,lpPlane);
}
第2种方法在没有看见之间,先看见的是代码 http://www.racer.nl/reference/vfc_markmorley.htm ,自己想了好久都没考虑清楚。最后看见解释,鄙视自己的数学逻辑推理。3d看来困难的就在于思考方式,同样的代码处理,第二种方式明显快很多。
判断裁剪
我们已经有了裁剪体的方程,当我们需要裁剪一个顶点的时候,这六个方程已经足够了。但是我们要判断一个区域的可见性时,我们进行一些额外的计算。如图2所示,一个物体和投影体的关系大致可以分为:包围、被包围、相交和相离四种情况。图中最大的浅蓝色的矩形包围了整个投影体。深绿色的小矩形则完全被投影体包围。浅绿色的矩形和投影体相交。这三种情况下物体都是可以被看到的。剩下红色的矩形则和投影体相离、只有它完全不可见
图2
当处理节点的可见性的时候,由于节点的不规则性。我们还需要引入包围体的概念。所谓的包围体,就是用一个比较简单的几何体去度量另外一个比较复杂的几何体,让它刚好能包围另外一个几何体。比较合适的包围体外形有矩形、正方形和球体。其中球体处理最为简单,但是近似度也最差。我们为每一个节点都建立一个包围体,只要测试这个包围体,我们就可以决定一个节点的可见性,由于包围体肯定大于这个节点,因此我们可以保证不会有任何可见的节点被裁剪在投影体之外
BOOL CFrustum::PointInFrustum( float afX, float afY, float afZ )
{
// A*x+B*y+C*z+D = 0 is in plane,
for(int i = 0; i < FRUSTUM_PLANE_COUNT; i++ )
{
D3DXPLANE &loPlane = moFrustumPlane[i];
//减少函数调用
//if(D3DXPlaneDotCoord(&moFrustumPlane[i], &D3DXVECTOR3(afX, afY, afZ)) < 0.0f)
if(loPlane.a * afX + loPlane.b * afY + loPlane.c * afZ + loPlane.d < 0.0f)
return FALSE;
}
return TRUE;
}
BOOL CFrustum::SphereInFrustum( float afX, float afY, float afZ, float AfRadius )
{
// A*x+B*y+C*z+D = -radius is in plane,
for(int i = 0; i < FRUSTUM_PLANE_COUNT; i++ )
{
D3DXPLANE &loPlane = moFrustumPlane[i];
//减少函数调用,直接用公式运算
//if(D3DXPlaneDotCoord(&moFrustumPlane[i], &D3DXVECTOR3(afX, afY, afZ)) < -AfRadius)
if( loPlane.a * afX + loPlane.b * afY + loPlane.c * afZ + loPlane.d <= -AfRadius )
{
return false;
}
}
return TRUE;
}
BOOL CFrustum::CubeInFrustum( float afX, float afY, float afZ, float aiSize,BOOL & abIsCompletelyContained )
{
float lfAlfaX = afX + aiSize;
float lfDeltaX = afX - aiSize;
float lfAlfaY = afY + aiSize;
float lfDeltaY = afY - aiSize;
float lfAlfaZ = afZ + aiSize;
float lfDeltaZ = afZ - aiSize;
DWORD ldwNumPointInFrustum = 0;
for(int i = 0; i < FRUSTUM_PLANE_COUNT; i++ )
{
int j = 8;
BOOL lbIsInAllPlanes = TRUE;
D3DXPLANE &loPlane = moFrustumPlane[i];
//if(D3DXPlaneDotCoord(&loPlane[i], &D3DXVECTOR3(lfDeltaX,lfDeltaY, lfDeltaZ)) < 0.0f)
if(loPlane.a * lfDeltaX + loPlane.b * lfDeltaY + loPlane.c * lfDeltaZ + loPlane.d < 0.0f)
{
lbIsInAllPlanes = FALSE;
j--;
}
//if(D3DXPlaneDotCoord(&loPlane[i], &D3DXVECTOR3(lfAlfaX,lfDeltaY, lfDeltaZ)) < 0.0f)
if(loPlane.a * lfAlfaX + loPlane.b * lfDeltaY + loPlane.c * lfDeltaZ + loPlane.d < 0.0f)
{
lbIsInAllPlanes = FALSE;
j--;
}
//if(D3DXPlaneDotCoord(&loPlane[i], &D3DXVECTOR3(lfDeltaX,lfAlfaY, lfDeltaZ)) < 0.0f)
if(loPlane.a * lfDeltaX + loPlane.b * lfAlfaY + loPlane.c * lfDeltaZ + loPlane.d < 0.0f)
{
lbIsInAllPlanes = FALSE;
j--;
}
//if(D3DXPlaneDotCoord(&loPlane[i], &D3DXVECTOR3(lfAlfaX,lfAlfaY, lfAlfaZ)) < 0.0f)
if(loPlane.a * lfAlfaX + loPlane.b * lfAlfaY + loPlane.c * lfDeltaZ + loPlane.d < 0.0f)
{
lbIsInAllPlanes = FALSE;
j--;
}
//if(D3DXPlaneDotCoord(&loPlane[i], &D3DXVECTOR3(lfDeltaX,lfDeltaY, lfAlfaZ)) < 0.0f)
if(loPlane.a * lfDeltaX + loPlane.b * lfDeltaY + loPlane.c * lfAlfaZ + loPlane.d < 0.0f)
{
lbIsInAllPlanes = FALSE;
j--;
}
//if(D3DXPlaneDotCoord(&loPlane[i], &D3DXVECTOR3(lfAlfaX,lfDeltaY, lfAlfaZ)) < 0.0f)
if(loPlane.a * lfAlfaX + loPlane.b * lfDeltaY + loPlane.c * lfAlfaZ + loPlane.d < 0.0f)
{
lbIsInAllPlanes = FALSE;
j--;
}
//if(D3DXPlaneDotCoord(&loPlane[i], &D3DXVECTOR3(lfDeltaX,lfDeltaY, lfAlfaZ)) < 0.0f)
if(loPlane.a * lfDeltaX + loPlane.b * lfDeltaY + loPlane.c * lfAlfaZ + loPlane.d < 0.0f)
{
lbIsInAllPlanes = FALSE;
j--;
}
//if(D3DXPlaneDotCoord(&loPlane[i], &D3DXVECTOR3(lfAlfaX,lfAlfaY, lfAlfaZ)) < 0.0f)
if(loPlane.a * lfAlfaX + loPlane.b * lfAlfaY + loPlane.c * lfAlfaZ + loPlane.d < 0.0f)
{
lbIsInAllPlanes = FALSE;
j--;
}
// if none contained, return FALSE.
if(0 == j)
return FALSE;
// update counter if they were all in front of plane.
if(lbIsInAllPlanes)
++ldwNumPointInFrustum;
}
abIsCompletelyContained = (BOOL)(ldwNumPointInFrustum == FRUSTUM_PLANE_COUNT);
return TRUE;
}

这篇关于D3D Frustum六个裁剪平面生成原理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/649058

相关文章

Spring Cloud Hystrix原理与注意事项小结

《SpringCloudHystrix原理与注意事项小结》本文介绍了Hystrix的基本概念、工作原理以及其在实际开发中的应用方式,通过对Hystrix的深入学习,开发者可以在分布式系统中实现精细... 目录一、Spring Cloud Hystrix概述和设计目标(一)Spring Cloud Hystr

nginx生成自签名SSL证书配置HTTPS的实现

《nginx生成自签名SSL证书配置HTTPS的实现》本文主要介绍在Nginx中生成自签名SSL证书并配置HTTPS,包括安装Nginx、创建证书、配置证书以及测试访问,具有一定的参考价值,感兴趣的可... 目录一、安装nginx二、创建证书三、配置证书并验证四、测试一、安装nginxnginx必须有"-

Java实战之利用POI生成Excel图表

《Java实战之利用POI生成Excel图表》ApachePOI是Java生态中处理Office文档的核心工具,这篇文章主要为大家详细介绍了如何在Excel中创建折线图,柱状图,饼图等常见图表,需要的... 目录一、环境配置与依赖管理二、数据源准备与工作表构建三、图表生成核心步骤1. 折线图(Line Ch

浅析如何使用Swagger生成带权限控制的API文档

《浅析如何使用Swagger生成带权限控制的API文档》当涉及到权限控制时,如何生成既安全又详细的API文档就成了一个关键问题,所以这篇文章小编就来和大家好好聊聊如何用Swagger来生成带有... 目录准备工作配置 Swagger权限控制给 API 加上权限注解查看文档注意事项在咱们的开发工作里,API

Java使用POI-TL和JFreeChart动态生成Word报告

《Java使用POI-TL和JFreeChart动态生成Word报告》本文介绍了使用POI-TL和JFreeChart生成包含动态数据和图表的Word报告的方法,并分享了实际开发中的踩坑经验,通过代码... 目录前言一、需求背景二、方案分析三、 POI-TL + JFreeChart 实现3.1 Maven

MySQL中的MVCC底层原理解读

《MySQL中的MVCC底层原理解读》本文详细介绍了MySQL中的多版本并发控制(MVCC)机制,包括版本链、ReadView以及在不同事务隔离级别下MVCC的工作原理,通过一个具体的示例演示了在可重... 目录简介ReadView版本链演示过程总结简介MVCC(Multi-Version Concurr

MybatisGenerator文件生成不出对应文件的问题

《MybatisGenerator文件生成不出对应文件的问题》本文介绍了使用MybatisGenerator生成文件时遇到的问题及解决方法,主要步骤包括检查目标表是否存在、是否能连接到数据库、配置生成... 目录MyBATisGenerator 文件生成不出对应文件先在项目结构里引入“targetProje

Python使用qrcode库实现生成二维码的操作指南

《Python使用qrcode库实现生成二维码的操作指南》二维码是一种广泛使用的二维条码,因其高效的数据存储能力和易于扫描的特点,广泛应用于支付、身份验证、营销推广等领域,Pythonqrcode库是... 目录一、安装 python qrcode 库二、基本使用方法1. 生成简单二维码2. 生成带 Log

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制

Redis主从复制的原理分析

《Redis主从复制的原理分析》Redis主从复制通过将数据镜像到多个从节点,实现高可用性和扩展性,主从复制包括初次全量同步和增量同步两个阶段,为优化复制性能,可以采用AOF持久化、调整复制超时时间、... 目录Redis主从复制的原理主从复制概述配置主从复制数据同步过程复制一致性与延迟故障转移机制监控与维