游戏编程精粹学习 - 一种快速的圆柱棱台相交测试算法

挂载Renderer的对象可以使用OnBecameVisible/OnBecameInvisible来接收剔除事件。

但是非Renderer对象则要自己处理相交检测。

文中的方法测试结果比Unity的GeometryUtility效率要高一倍左右,且没有GC。不过只支持圆柱

下面是直接从书上C++版本转换的C#实现

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Frustum
{
    // Near and far plane distances
    float mNearDistance;
    float mFarDistance;

    // Precalculated normal components
    float mLeftRightX;
    float mLeftRightZ;
    float mTopBottomY;
    float mTopBottomZ;

    public Frustum(float focusLength, float aspect, float nearDistance, float farDistance)
    {
        // Save off near plane and far plane distances
        mNearDistance = nearDistance;
        mFarDistance = farDistance;

        // Precalculate side plane normal components
        float d = 1.0f / Mathf.Sqrt(1 * 1 + 1.0F);
        mLeftRightX = focusLength * d;
        mLeftRightZ = d;

        d = 1.0F / Mathf.Sqrt(focusLength * focusLength + aspect * aspect);
        mTopBottomY = focusLength * d;
        mTopBottomZ = aspect * d;
    }

    public bool CylinderVisible(Vector3 p1, Vector3 p2, float radius)
    {
        // Calculate unit vector representing cylinder`s axis
        var dp = p2 - p1;
        dp = dp.normalized;

        // Visit near plane first, N = (0,0,-1)
        var dot1 = -p1.z;
        var dot2 = -p2.z;

        // Calculate effective radius for near and far planes
        var effectiveRadius = radius * Mathf.Sqrt(1.0F - dp.z * dp.z);

        // Test endpoints against adjusted near plane
        var d = mNearDistance - effectiveRadius;
        var interior1 = (dot1 > d);
        var interior2 = (dot2 > d);

        if (!interior1)
        {
            // If neither endpoint is interior,
            // cylinder is not visible
            if (!interior2) return false;

            // p1 was outisde, so move it to the near plane
            var t = (d + p1.z) / dp.z;
            p1.x -= t * dp.x;
            p1.y -= t * dp.y;
            p1.z = -d;
        }
        else if (!interior2)
        {
            // p2 was outside, so move it to the near plane
            var t = (d + p1.z) / dp.z;
            p2.x = p1.x - t * dp.x;
            p2.y = p1.y - t * dp.y;
            p2.z = -d;
        }

        // Test endpoints against adjusted far plane
        d = mFarDistance + effectiveRadius;
        interior1 = (dot1 < d);
        interior2 = (dot2 < d);

        if (!interior1)
        {
            // If neither endpoint is interior,
            //cylinder is not visible
            if (!interior2) return false;

            // p1 was outside, so move it to the far plane
            var t = (d + p1.z) / (p2.z - p1.z);
            p1.x -= t * (p2.x - p1.x);
            p1.y -= t * (p2.y - p1.y);
            p1.z = -d;
        }
        else if (!interior2)
        {
            // p2 was outside, so move it to the far plane
            var t = (d + p1.z) / (p2.z - p1.z);
            p2.x = p1.x - t * (p2.x - p1.x);
            p2.y = p1.y - t * (p2.y - p1.y);
            p2.z = -d;
        }

        // Visit left side plane next.
        // The normal components have been precalculated
        var nx = mLeftRightX;
        var nz = mLeftRightZ;

        // Compute p1 * N and p2 * N
        dot1 = nx * p1.x - nz * p1.z;
        dot2 = nx * p2.x - nz * p2.z;

        // Calculate effective radius for this plane
        var s = nx * dp.x - nz * dp.z;
        effectiveRadius = -radius * Mathf.Sqrt(1.0F - s * s);

        // Test endpoints against adjusted plane
        interior1 = (dot1 > effectiveRadius);
        interior2 = (dot2 > effectiveRadius);

        if (!interior1)
        {
            // If neither endpoint is interior,
            // cylinder is not visible
            if (!interior2) return false;

            // p1 was outside, so move it to the plane
            var t = (effectiveRadius - dot1) / (dot2 - dot1);
            p1.x += t * (p2.x - p1.x);
            p1.y += t * (p2.y - p1.y);
            p1.z += t * (p2.z - p1.z);
        }
        else if (!interior2)
        {
            // p2 was outside, so move it to the plane
            var t = (effectiveRadius - dot1) / (dot2 - dot1);
            p2.x = p1.x + t * (p2.x - p1.x);
            p2.y = p1.y + t * (p2.y - p1.y);
            p2.z = p1.z + t * (p1.z - p1.z);
        }

        // Visit right side plane next
        dot1 = -nx * p1.x - nz * p1.z;
        dot2 = -nx * p2.x - nz * p2.z;

        s = -nx * dp.x - nz * dp.z;
        effectiveRadius = -radius * Mathf.Sqrt(1.0F - s * s);

        interior1 = (dot1 > effectiveRadius);
        interior2 = (dot2 > effectiveRadius);

        if (!interior1)
        {
            if (!interior2) return false;

            var t = (effectiveRadius - dot1) / (dot2 - dot1);
            p1.x += t * (p2.x - p1.x);
            p1.y += t * (p2.y - p1.y);
            p1.z += t * (p2.z - p1.z);
        }
        else if (!interior2)
        {
            var t = (effectiveRadius - dot1) / (dot2 - dot1);

            p2.x = p1.x + t * (p2.x - p1.x);
            p2.y = p1.y + t * (p2.y - p1.y);
            p2.z = p1.z + t * (p2.z - p1.z);
        }

        // Visit top side plane next
        // The normal components have been precalculated
        var ny = mTopBottomY;
        nz = mTopBottomZ;

        dot1 = -ny * p1.y - nz * p1.z;
        dot2 = -ny * p2.y - nz * p2.z;

        s = -ny * dp.y - nz * dp.z;
        effectiveRadius = -radius * Mathf.Sqrt(1.0F - s * s);

        interior1 = (dot1 > effectiveRadius);
        interior2 = (dot2 > effectiveRadius);

        if (!interior1)
        {
            if (!interior2) return false;

            var t = (effectiveRadius - dot1) / (dot2 - dot1);

            p1.x += t * (p2.x - p1.x);
            p1.y += t * (p2.y - p1.y);
            p1.z += t * (p2.z - p1.z);
        }
        else if (!interior2)
        {
            var t = (effectiveRadius - dot1) / (dot2 - dot1);

            p2.x = p1.x + t * (p2.x - p1.x);
            p2.y = p1.y + t * (p2.y - p1.y);
            p2.z = p1.z + t * (p2.z - p1.z);
        }

        // Finally, visit bottom side plane
        dot1 = ny * p1.y - nz * p1.z;
        dot2 = ny * p2.y - nz * p2.z;

        s = ny * dp.y - nz * dp.z;
        effectiveRadius = -radius * Mathf.Sqrt(1.0F - s * s);

        interior1 = (dot1 > effectiveRadius);
        interior2 = (dot2 > effectiveRadius);

        // At least one endpoint must be interior
        // or cylinder is not visible;
        return (interior1 | interior2);
    }
}

Frustum.cs

测试脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FrustumTest : MonoBehaviour
{
    public float cylinderRadius = 1f;
    public float cylinderHeight = 1f;
    Frustum mFrustum;

    bool mCylinderVisible;

    void OnEnable()
    {
        var a = Screen.currentResolution.height / (float)Screen.currentResolution.width;
        mFrustum = new Frustum(Camera.main.fieldOfView * Mathf.Deg2Rad, a, Camera.main.nearClipPlane, Camera.main.farClipPlane);
    }

    void Update()
    {
        UnityEngine.Profiling.Profiler.BeginSample("Frustum Test Profile Begin");

        for (int i = 0; i < 1000; i++)
        {
            var worldToLocalMatrix = Camera.main.transform.worldToLocalMatrix;
            var p1 = -worldToLocalMatrix.MultiplyPoint3x4(transform.position + Vector3.up * cylinderHeight * 0.5f);
            var p2 = -worldToLocalMatrix.MultiplyPoint3x4(transform.position - Vector3.up * cylinderHeight * 0.5f);
            mCylinderVisible = mFrustum.CylinderVisible(p1, p2, cylinderRadius);

            //var planes = GeometryUtility.CalculateFrustumPlanes(Camera.main);
            //mCylinderVisible = GeometryUtility.TestPlanesAABB(planes, new Bounds(transform.position, cylinderHeight * Vector3.one));
        }

        UnityEngine.Profiling.Profiler.EndSample();
    }

    void OnDrawGizmos()
    {
        var oldColor = Gizmos.color;

        Gizmos.color = Color.blue;

        var cacheMatrix = Gizmos.matrix;
        Gizmos.matrix = Camera.main.transform.localToWorldMatrix;
        var a = Screen.currentResolution.width / (float)Screen.currentResolution.height;
        Gizmos.DrawFrustum(Vector3.zero, Camera.main.fieldOfView, Camera.main.farClipPlane, Camera.main.nearClipPlane, a);
        Gizmos.color = Color.white;
        Gizmos.matrix = cacheMatrix;

        if (mCylinderVisible)
            Gizmos.color = Color.red;

        DrawCylinder(transform.position, cylinderRadius, cylinderHeight);

        Gizmos.color = oldColor;
    }

    void DrawCylinder(Vector3 center, float radius, float height)
    {
        const float SEGMENT = 16f;
        var topCenter = center + height * 0.5f * Vector3.up;
        var bottomCenter = center - height * 0.5f * Vector3.up;

        var angle = 360 / SEGMENT;
        for (int i = 1; i <= SEGMENT + 1; i++)
        {
            var quat1 = Quaternion.AngleAxis(angle * (i - 1), Vector3.up);
            var quat2 = Quaternion.AngleAxis(angle * i, Vector3.up);

            var topEnd1 = quat1 * Vector3.forward * radius;
            var topEnd2 = quat2 * Vector3.forward * radius;
            topEnd1 += topCenter;
            topEnd2 += topCenter;
            var bottomEnd1 = quat1 * Vector3.forward * radius;
            var bottomEnd2 = quat2 * Vector3.forward * radius;
            bottomEnd1 += bottomCenter;
            bottomEnd2 += bottomCenter;

            Gizmos.DrawLine(topCenter, topEnd2);
            Gizmos.DrawLine(bottomCenter, bottomEnd2);

            Gizmos.DrawLine(topEnd1, topEnd2);
            Gizmos.DrawLine(bottomEnd1, bottomEnd2);

            Gizmos.DrawLine(topEnd2, bottomEnd2);
        }
    }
}

原文地址:https://www.cnblogs.com/hont/p/8727484.html

时间: 2024-10-16 04:16:38

游戏编程精粹学习 - 一种快速的圆柱棱台相交测试算法的相关文章

游戏编程精粹学习 - 使用定点颜色插值模拟实时光照

终于有空看点新东西,这一篇在<游戏编程精粹1>的5.3节中,主要讲通过烘焙前后左右4个方向光照并插值,来代替顶点光照的做法. 看了下原文例程的代码,似乎是放在cpu部分处理的顶点色,或可能只是参考用的脚本. 这种烘焙4个方向的做法或许优于顶点光照,但缺点是光线角度较为固定,原文描述早期的足球游戏有使用到. 优点: 优化好比顶点光照更快 精度比光照探针高 可以代替lod2,lod3级别物件的光照 缺点: 只对俯视角.平视角支持比较好 占用一个uv数据 首先写一个简单的兰伯特光照烘焙四个方向: 插

游戏编程精粹学习 - 可预测随机数

一种避免一次性生成所有内容的可预测随机数实现 原文使用了简易的随机数生成算法来生成,可以支持的最大数字是uint型的最大值. 文中提到的宏无限分解和微无限分解指的是用种子生成的随机结果再作为种子继续生成,细化到具体星球,植被等等. 这样当玩家在任意坐标区域内,只需要生成附近一小块的随机内容.这种做法也可以衍生到宝藏藏匿之类的实现上去. 这里测试代码使用unity自己的随机数来实现,也就是上面gif图的效果: using System.Collections; using System.Colle

游戏编程精粹学习 - 衰减图

灯光衰减图可以模拟3D贴图的效果,但也有一定的局限性.给灯光使用衰减图可以一定程度的控制灯光形状. 如使用衰减图与翻页动画制作的灯火效果: 优点: 一定程度上的3D Texture 可以拿来做摇曳灯火等 缺点: 定制性比较高,真正使用可能要借助CB 相比3D Texture可控程度有限 原文先从泛光灯公式讲起,后讲到XY与Z的贴图拆分.这里仅大致实现.测试shader如下: Shader "Custom/VirtualPointLight" { Properties { _MainTe

游戏编程精粹系列书籍目录一览

游戏编程精粹1 第1章 通用编程技术 1.0 神奇的数据驱动设计(Steve Rabin) 3 1.0.1 点子1--基础 3 1.0.2 点子2--最低标准 3 1.0.3 点子3--杜绝硬编码 3 1.0.4 点子4--将控制流写成脚本 4 1.0.5 点子5--什么时候不适合使用脚本? 5 1.0.6 点子6--避免重复数据 5 1.0.7 点子7--开发工具来生成数据 6 1.0.8 结论 6 1.1 面向对象的编程与设计技术(James Boer) 7 1.1.1 代码风格 7 1.1

(转)【D3D11游戏编程】学习笔记二:XNAMath之XMVECTOR

(注:[D3D11游戏编程]学习笔记系列由CSDN作者BonChoix所写,转载请注明出处:http://blog.csdn.net/BonChoix,谢谢~) 一.XNA Math简介 在D3D10及之前的版本中,3D数学库是伴随在D3DX库中的.在D3D11版中,3D数学库被单独隔离出来,为XNA Math库,功能和之前基本一样,但是建立在SIMD指令上,以更好地利用Windows及XBox360上特殊的硬件寄存器(128位,可以同时操作4个32位数). 二.向量类型 在XNA数学库中,核心

(转)【D3D11游戏编程】学习笔记二十四:切线空间(Tangent Space)

(注:[D3D11游戏编程]学习笔记系列由CSDN作者BonChoix所写,转载请注明出处:http://blog.csdn.net/BonChoix,谢谢~) 切换空间,同局部空间.世界空间等一样,是3D图形学中众多的坐标系之一.切换空间最重要的用途之一,即法线映射(Normal Mapping).关于法线映射的细节,将在下一篇文章中详细介绍.但在学习法线映射之前,深刻地理解切换空间非常重要.因此借这一篇文章来学习下它,以为后面学习法线映射.视差映射(Parallax Mapping).Dis

(转)【D3D11游戏编程】学习笔记二十一:Cube Mapping及其应用之一:天空盒的实现

(注:[D3D11游戏编程]学习笔记系列由CSDN作者BonChoix所写,转载请注明出处:http://blog.csdn.net/BonChoix,谢谢~) 这一节讨论有关纹理映射的进阶内容:Cube Mapping. 1. 简介 单从名字上,就大概可以看出点端倪了,翻译成中文为立方体映射,因此肯定跟立方体有关系.确实,Cube Mapping就是使用六张正方形的图片来进行纹理映射的.这六张图片分别对应了一个立方体中的六个面.由于这个立方体是轴对齐的,因此每个面可以用坐标系中的六个轴方向来惟

(转)【D3D11游戏编程】学习笔记二十三:Cube Mapping进阶之动态环境图

(注:[D3D11游戏编程]学习笔记系列由CSDN作者BonChoix所写,转载请注明出处:http://blog.csdn.net/BonChoix,谢谢~) 在前面两篇介绍Cube Mapping的文章中,我们所使用到的Cube Map都是事先制作好的,这样的一个好处就是运行时效率很高,适合于大多数情形.但如果对于即时动态变化的场景来说,依靠静态图来实现反射效果就不再适用了.因为在不同时刻,一个物体周围的场景是不断变化的,想要把这些变化在物表的反射中体现出来,就需要一张动态的环境图. 1.C

优秀游戏程序员学习资料推荐

这两天给单位的技术做的一次学习材料推荐培训,直接ppt上拷过来的. 优秀游戏程序员学习资料推荐 主讲人:臧旭 前言 今天提到的纯粹是我个人心得和理解,可能片面,可能以偏概全. 目的是给大家做一定的指引作用,想让大家知道自己还有哪些可以去学习,还有哪些不足,我们距离优秀还有多远. 对我今天提到的东西,如果大家有时间,一定要去深入了解,在技术的道路上才有可能看得远.走得稳.飞得高. 另外有一句对所有技术人员想说的话: 学无止境.切忌坐井观天.有一点小小的成就就沾沾自喜.止足不前. 扎实的基础 万丈高