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

    包围体测试的局限性

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

  • 相关阅读:
    atitit.nfc 身份证 银行卡 芯片卡 解决方案 attilax总结
    atitit.php 流行框架 前三甲为:Laravel、Phalcon、Symfony2 attilax 总结
    Atitit.执行cmd 命令行 php
    Atitit. 图像处理jpg图片的压缩 清理垃圾图片 java版本
    atitit。企业组织与软件工程的策略 战略 趋势 原则 attilax 大总结
    atitit. 管理哲学 大毁灭 如何防止企业的自我毁灭
    Atitit.java的浏览器插件技术 Applet japplet attilax总结
    Atitit.jquery 版本新特性attilax总结
    Atitit. 软件开发中的管理哲学一个伟大的事业必然是过程导向为主 过程导向 vs 结果导向
    (转)获取手机的IMEI号
  • 原文地址:https://www.cnblogs.com/millionsmultiplication/p/9938011.html
Copyright © 2011-2022 走看看