大多游戏程序员和图形程序都知道渲染流水线这个概念,它的本质是将3D的场景映射到显示屏上的一系列操做。它主要分3个阶段:应用程序阶段,几何阶段,光栅化阶段。将摄像机位置,光照,模型的图元输入到几何阶段即是应用程序阶段。进行多边形和顶点操做把3d数据映射到2d的阶段即是几何阶段。给定进过变换和投影以后的顶点,颜色,纹理坐标,给每一个像素正确配色,这个阶段叫光栅化阶段。具体的流水线概念,这篇文章不作详细介绍,这篇文章主要讲解几何阶段中的包围体测试。程序员
几何阶段分几个阶段,流水线上按顺序为顶点数据等执行如下操做c#
而物体剔除和背面消隐是处于模型视图变换的可选操做。数据结构
在模型视图变换阶段时须要将物体坐标系的点先转换到世界坐标系,而后再由世界坐标系转换到视图坐标系(相机坐标系)函数
然而执行世界坐标到相机坐标前,须要进行几个测试。来判断物体对相机是否可见,避免场景过大时形成大量多余的计算,而后准确无误地渲染物体。而这些测试被称为隐藏面消除。工具
执行的测试有2种,一种是背面消隐,一种是包围体测试。测试
背面消隐的原理能够看这篇博客,我这篇就主要讲不多被说起的包围体测试的原理this
原理很简单:就是用一个球体把物体包起来,而后判断球体是否在视景体外,是则丢弃这个物体。‘.net
如今咱们假设有一个物体,包含一组顶点,要找到离中心远最远的顶点,最远顶点到中心点的距离即是球体的半径,代码实现以下3d
class GameObject { public float max_radius = 0;//最大半径 public Mesh mesh;//网格 public Vector3 position = Vector3.zero;//坐标 public Vector3 rotation = Vector3.zero; public Matrix4x4 ObjectToWorldMatrix;//模型-世界矩阵 public GameObject(Mesh mesh,Vector3 position) { this.mesh = mesh; this.position = position; CalculateMaxRadius(); } /// <summary> /// 计算半径 /// </summary> private void CalculateMaxRadius() { int size = mesh.Vertices.Length; for (int i = 0; i < size; ++i) { //计算物体包围球的最大半径 max_radius = System.Math.Max(max_radius, Vector3.DistanceSquare(position, mesh.Vertices[i].m_vertex.position)); } max_radius = (float)System.Math.Sqrt(max_radius); } }
为了便于理解,我用画图工具画了个视景体的图。视景体就是涂画的浅蓝色部门区域。code
不过为了计算,程序里的视景体是个由远裁剪面,近裁剪面,y = ymax(视景体内点y坐标的最大值),y = ymin,x = xmax,x = xmin六个面构成的长方体,咱们只须要判断球体是否在长方体内便可。也就是判断球体的6个临界顶点是否在视景体内便可。
首先咱们判断球体是否在远近裁剪面间
只须要判断球体z坐标最大的点的z坐标值是否在比近裁面的z值小,球体z坐标最小的点的z坐标值是否比远裁面的z值大便可。代码实现以下
//远近裁剪面裁剪 if (SphereCenterPos.z - go.max_radius> camera.zf|| SphereCenterPos.z + go.max_radius < camera.zn) { return go.mesh.CullFlag = true; }
接下来是对左右裁剪面和上下裁剪面的判断,不过因为须要计算xmin,ymin,xmax,ymax,咱们须要先得到摄像头的焦距
焦距就是位于视景体内的黄线长度,也就是视景体的z深度
焦距的计算和摄像头数据结构定义代码以下
class Camera { /// <summary> /// 观察角,弧度 /// </summary> public float fov; /// <summary> /// 宽纵比 /// </summary> public float aspect; /// <summary> /// 近裁平面 /// </summary> public float zn; /// <summary> /// 远裁平面 /// </summary> public float zf; /// <summary> /// 屏幕宽度 /// </summary> public int ScreenHeight; /// <summary> /// 世界-视图 4x4矩阵 /// </summary> public Matrix4x4 WorldToViewMatrix; /// <summary> /// 视图-投影 4x4矩阵 /// </summary> public Matrix4x4 ViewToProjectionMatrix; public Vector3 pos; public Vector3 lookAt; public Vector3 up; /// <summary> /// 焦距 /// </summary> public float FocalLength { get { return (float)(1f/ System.Math.Tan(fov * 0.5f) * ScreenHeight/2); } } }
经过代码咱们能够看到,焦距的计算,只是简单的三角函数的变换,不作详细介绍,不理解能够结合下图来理解
/// <summary> /// 物体剔除-包围球测试 /// </summary> private bool CullObject(GameObject go,Vector3 SphereCenterPos) { if (go.mesh.CullFlag) return true; Camera camera = Rendering_pipeline.MainCamera; //远近裁剪面裁剪 if (SphereCenterPos.z - go.max_radius> camera.zf|| SphereCenterPos.z + go.max_radius < camera.zn) { return go.mesh.CullFlag = true; } float FocalLength = camera.FocalLength;//得到焦距 //左右裁剪面剔除 float z_test = 0.5f * camera.aspect * camera.ScreenHeight * SphereCenterPos.z / FocalLength; if (SphereCenterPos.x - go.max_radius > z_test || SphereCenterPos.x + go.max_radius < -z_test) { return go.mesh.CullFlag = true; } //上下裁剪面剔除 z_test = 0.5f * camera.ScreenHeight * SphereCenterPos.z / FocalLength; if (SphereCenterPos.y - go.max_radius > z_test || SphereCenterPos.y + go.max_radius < -z_test) { return go.mesh.CullFlag = true; } return go.mesh.CullFlag = false; }
程序中的视景体实际上并无成功表明整个视景体,包围球也不必定很好地表明整个物体。并且存在物体部分位于视景体的状况,包围体测试不必定管用。但这不表明包围体测试没有意义,至少在游戏中,能有效避免对不可见的大型区域进行处理。