zoukankan      html  css  js  c++  java
  • 讲讲不怎么有用却很有意义的包围体测试

    大多游戏程序员和图形程序都知道渲染流水线这个概念,它的本质是将3D的场景映射到显示屏上的一系列操作。它主要分3个阶段:应用程序阶段,几何阶段,光栅化阶段。将摄像机位置,光照,模型的图元输入到几何阶段便是应用程序阶段。进行多边形和顶点操作把3d数据映射到2d的阶段便是几何阶段。给定进过变换和投影之后的顶点,颜色,纹理坐标,给每个像素正确配色,这个阶段叫光栅化阶段。具体的流水线概念,这篇文章不做详细介绍,这篇文章主要讲解几何阶段中的包围体测试。

    几何阶段简介

    几何阶段分几个阶段,流水线上按顺序为顶点数据等执行以下操作

    1. 模型视图变换:模型空间变换到观察空间
    2. 顶点着色:光照处理
    3. 投影:将模型变换到一个单位立方体内
    4. 裁剪:裁剪不显示的部分
    5. 屏幕映射:映射到屏幕坐标系

    而物体剔除和背面消隐是处于模型视图变换的可选操作。

    包围体测试的价值

    在模型视图变换阶段时需要将物体坐标系的点先转换到世界坐标系,然后再由世界坐标系转换到视图坐标系(相机坐标系)

    然而执行世界坐标到相机坐标前,需要进行几个测试。来判断物体对相机是否可见,避免场景过大时造成大量多余的计算,然后准确无误地渲染物体。而这些测试被称为隐藏面消除。

    执行的测试有2种,一种是背面消隐,一种是包围体测试。

    背面消隐的原理可以看这篇博客,我这篇就主要讲很少被提及的包围体测试的原理

    讲原理了

    原理很简单:就是用一个球体把物体包起来,然后判断球体是否在视景体外,是则丢弃这个物体。‘

    半径的获得

    现在我们假设有一个物体,包含一组顶点,要找到离中心远最远的顶点,最远顶点到中心点的距离便是球体的半径,代码实现如下

    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);
            }
    
    
        }
    

    判断球体是否在视景体外

    为了便于理解,我用画图工具画了个视景体的图。视景体就是涂画的浅蓝色部门区域。

    相机坐标系俯视图

    不过为了计算,程序里的视景体是个由远裁剪面,近裁剪面,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);
                }
            }
     
     
        }
    

    通过代码我们可以看到,焦距的计算,只是简单的三角函数的变换,不做详细介绍,不理解可以结合下图来理解

    y-z平面

    完整代码

    /// <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;
            }
    

    包围体测试的局限性

    程序中的视景体实际上并没有成功代表整个视景体,包围球也不一定很好地代表整个物体。而且存在物体部分位于视景体的情况,包围体测试不一定管用。但这不代表包围体测试没有意义,至少在游戏中,能有效避免对不可见的大型区域进行处理。

  • 相关阅读:
    弹出 提示窗口 背景显示灰色 可移动 模板
    rar文件的格式信息描述(中英文对照)
    android学习笔记48_实现软件国际化,实现文字国际化,和图片国际化
    php 7.2 安装 mcrypt 扩展
    laraveladmin 安装(总结)
    Laravel 精选资源大全
    Laravel 出现 No application encryption key has been specified
    Laravel5.5/6 报错call to undefined function openssl cipher iv length()
    一起谈.NET技术,Silverlight 游戏开发小技巧:动感小菜单2 狼人:
    一起谈.NET技术,你应该知道的15个Silverlight诀窍 狼人:
  • 原文地址:https://www.cnblogs.com/millionsmultiplication/p/9938011.html
Copyright © 2011-2022 走看看