首页 / 资讯中心 / 文章详情

游戏引擎基础架构:内存管理、数据结构与数学库设计核心

游戏引擎基础架构:内存管理、数据结构与数学库设计核心 ★ FEATURED ARTICLE
1. 项目概述为什么“引擎基础架构”是游戏开发的隐形脊柱你打开Unity或Unreal拖一个Cube进场景点一下Play——画面动了。看起来很简单。但背后这个Cube从内存里被读出来、被数学库计算顶点位置、被渲染管线调度、被内存管理器盯住生命周期、被事件系统监听鼠标点击……整个过程像一场精密交响而指挥这场交响的不是某个按钮而是引擎基础架构。它不露脸却决定一切帧率能不能稳在60加载会不会卡顿内存会不会一夜之间爆掉多人联机时同步逻辑靠不靠谱。我做过7个商业项目从2D像素RPG到开放世界MMO最深的体会是写业务逻辑可以快调架构问题永远慢功能上线容易架构重构伤筋动骨。这期讲的“引擎基础架构”不是泛泛而谈的分层图而是把引擎拆开后你亲手摸得到的五根主梁核心子系统组织方式、内存管理策略、数据结构选型逻辑、数学库设计边界、以及它们如何咬合运转。它面向两类人一类是刚跳出教科书、想搞懂“为什么Unity用ECS不用传统OOP”的中级程序员另一类是技术负责人正为团队下一个自研引擎立项纠结——该用C还是Rust该自己写内存池还是套现成方案该用std::vector还是定制紧凑数组这些选择没有标准答案但有硬约束游戏对实时性、确定性、内存局部性的要求远高于Web或后台服务。比如一个GameObject在Unity里占多少字节Unreal的UObject反射系统为什么必须牺牲一点性能换编辑器友好C虚函数表在每帧调用上万次时带来的缓存抖动怎么规避这些不是理论题是每天真正在啃的骨头。接下来我会用真实项目中的架构决策现场带你一层层剥开这根“隐形脊柱”。2. 架构设计底层逻辑为什么游戏引擎不能照搬Web或OS架构2.1 游戏引擎的四大硬约束决定了它必须“反常识”很多刚转行做游戏的开发者第一反应是“既然微服务能解耦那我也把渲染、物理、AI拆成独立服务”或者“Linux内存管理那么成熟直接移植过来不就行了”——这是典型用其他领域经验套游戏场景。结果往往是架构越做越重帧率越跑越低。根本原因在于游戏引擎面临四条无法妥协的硬约束它们共同定义了架构的“基因”。第一确定性实时性Deterministic Real-time。这不是“尽量快”而是“必须在16.67ms内完成一帧所有计算”。Web服务允许请求排队、GC暂停几秒游戏里一次GC停顿就掉一帧连续掉帧玩家立刻感知卡顿。所以引擎架构的第一原则是一切设计服务于可预测的执行时间。这意味着避免动态内存分配new/malloc在主循环中出现因为堆分配耗时不可控拒绝基于回调的异步模型如Node.js的Event Loop它无法保证回调执行顺序和时机物理模拟必须用固定时间步长Fixed Timestep哪怕渲染帧率波动物理演算节奏也不能乱。第二内存局部性Memory Locality压倒一切。CPU缓存行是64字节一次加载。如果一个对象的成员变量分散在内存各处CPU取数据要反复跳地址缓存命中率暴跌。我在做一款俯视角RTS时单位AI逻辑用传统OOP写每个Unit类含Position、Health、State、Pathfinder指针……结果Profile显示L3缓存未命中率高达42%。后来改成SOAStructure of Arrays所有Position存连续数组所有Health存另一块连续内存所有State再一块……同样逻辑缓存未命中率降到8%帧率提升23%。这就是为什么现代引擎如Unity DOTS、Unreal ECS强制推行数据导向设计——不是为了炫技是CPU硬件特性逼出来的生存法则。第三热更新与编辑器协同需求。Web服务发版重启就行游戏必须支持“边改边玩”美术调材质参数、策划改数值表、程序加新技能都不重启引擎。这就要求架构必须支持运行时热重载Hot Reload和反射元数据Reflection Metadata。但反射本身有开销所以引擎架构要在“编辑器友好”和“运行时性能”间找平衡点。比如Unreal用UHTUnreal Header Tool在编译期生成反射代码避免运行时解析Unity早期用Mono反射后来为性能砍掉部分功能转向IL2CPP静态分析。这种取舍直接体现在你写的C#脚本能否被编辑器识别、能否在Play Mode下修改生效。第四跨平台一致性与底层控制权。Unity打包iOS/Android/PC一键搞定背后是抽象层如Unity的Graphics API Abstraction Layer在起作用。但抽象不是万能的——Metal和Vulkan的资源屏障Barrier语义不同ARM Mali GPU和NVIDIA RTX的纹理采样精度有差异。所以引擎架构必须留出“逃逸舱口”Escape Hatch允许高级用户绕过抽象层直接调用原生API。这导致架构设计上必然存在“通用层”和“平台特化层”的分界且分界点要足够清晰。我们曾为某款主机游戏定制渲染器发现Unity的URPUniversal Render Pipeline在PS5上无法启用某些GPU指令最后只能在URP源码里打补丁而补丁点正是架构预留的平台钩子Platform Hook。提示当你看到“分布式架构”“微服务”这类热词时请先问自己这个设计是否满足上述四条硬约束如果答案是否定的那它大概率不适合游戏引擎核心。分布式解决的是高并发、高可用问题游戏引擎解决的是单机极致性能与确定性问题——二者目标南辕北辙。2.2 基础架构的五层金字塔从底向上每一层都卡着性能咽喉我把游戏引擎基础架构画成一座五层金字塔越往下越接近硬件改动成本越高越往上越贴近业务迭代越快。理解这个分层才能明白为什么“内存管理”和“数学库”看似底层实则牵一发而动全身。第五层硬件抽象层HAL, Hardware Abstraction Layer这是金字塔地基负责统一封装GPU、音频芯片、输入设备等。关键不是“封装多全”而是“封装多薄”。比如图形API抽象DirectX 12/Vulkan/Metal都要求显式管理内存屏障、资源状态转换。如果HAL层在状态转换时偷偷加一层判断逻辑一帧可能多出上千次分支预测失败。我们曾用一个简单宏定义替代if-else判断资源状态单帧节省12μs——别小看这12微秒它够CPU执行约3万条指令。HAL层代码必须近乎零开销通常用模板特化Template Specialization或编译期条件编译#ifdef实现避免运行时多态。第四层核心系统层Core Systems这是引擎的“脏腑”包括内存管理器、任务调度器、事件总线、资源管理系统。它们不直接画像素但决定整台机器能否健康运转。举个例子内存管理器若没设计好后续所有系统都会中毒。我们曾遇到一个Bug加载100个角色模型后内存碎片率达65%再加载第101个时分配失败。根源不在模型加载逻辑而在内存管理器的页分配策略——它用First-Fit算法长期运行后小空闲块散落各处。换成Buddy System后碎片率稳定在12%以下。这说明核心系统层的设计缺陷会以指数级放大到上层。第三层数学与工具层Math Utilities包含向量/矩阵/四元数库、几何算法射线检测、AABB碰撞、序列化工具等。这里的关键矛盾是精度、性能、易用性三者不可兼得。比如四元数插值Slerp比线性插值Lerp更精确但计算开销高3倍。在骨骼动画中我们最终选择混合方案关键帧用Slerp保证精度中间帧用优化版Lerp带误差补偿。数学库不是越“全”越好而是越“准”越好——准指在特定场景下用最小代价达成所需精度。第二层子系统层Subsystems渲染、物理、音频、网络、AI等模块在此层实现。它们通过核心系统层获取服务如内存分配、事件发布并向上提供统一接口。架构重点在于解耦与协作协议。例如渲染系统不直接读取GameObject的位置而是订阅Transform组件的变更事件物理系统计算完新位置后通过事件总线广播由渲染系统消费。这种松耦合让替换物理引擎如从PhysX换到Havok只需重写物理子系统不影响渲染逻辑。第一层应用层Application Layer这是开发者天天打交道的地方Editor界面、脚本API、Asset Pipeline。它的设计哲学是“让正确的事做起来最简单让错误的事做起来很难”。比如Unity的[RequireComponent]特性强制脚本依赖的组件自动添加Unreal的Blueprint节点连线语法错误在编辑时就标红。这一层不追求技术炫酷而追求降低认知负荷——毕竟90%的开发者时间花在调逻辑、修Bug而不是写架构。这五层不是静态堆叠而是动态咬合。比如数学库的SIMD指令优化必须与内存管理器的对齐策略配合如果Vector4f要求16字节对齐而内存池分配时不保证对齐SIMD指令就会触发General Protection Fault。再比如任务调度器的纤程Fiber切换需要HAL层提供上下文保存/恢复的汇编指令支持。所以谈“架构”不能只说某一层必须看它们如何协同。3. 内存管理游戏引擎的呼吸系统不是分配器那么简单3.1 为什么malloc/free是游戏开发的“慢性毒药”新手常问“C自带new/delete为啥还要自己写内存管理器”答案很残酷因为标准库分配器是为通用场景设计的而游戏是极端场景。我拿一个真实案例说明某款移动端AR游戏启动后内存占用从80MB缓慢涨到320MB30分钟后崩溃。用Android Profiler抓帧发现不是内存泄漏而是堆碎片化。原因在于每帧创建大量临时Vector3用于射线检测用完delete但小对象频繁分配释放导致堆内存像瑞士奶酪。标准malloc的Free List管理在这种模式下效率断崖下跌。更致命的是缓存不友好。malloc分配的内存块地址随机相邻对象物理距离远。而游戏里一个渲染批次要遍历1000个Mesh数据如果每个Mesh对象分散在内存各处CPU缓存行反复加载性能雪崩。我们做过对比测试std::vectorGameObject* 存指针vs. 自定义紧凑数组存GameObject结构体。后者L1缓存命中率高3.2倍Draw Call提交速度提升41%。所以游戏引擎内存管理的核心目标不是“分配快”而是“确定性、局部性、可控性”。它必须回答三个问题何时分配—— 主循环中禁止malloc只允许在加载/卸载场景时批量分配何处分配—— 不同类型数据走不同内存池Pool避免互相污染如何复用—— 对象销毁不立即归还内存而是进入Free List等待下次复用。3.2 五种主流内存池设计适用场景与踩坑实录3.2.1 固定大小内存池Fixed-Size Pool这是最常用、最稳妥的方案。原理简单预分配一大块内存按固定大小如64字节切成N个小块用单链表管理空闲块。分配O(1)释放O(1)无碎片。适用场景高频创建销毁的小对象如粒子、事件消息、临时计算向量。实操要点块大小必须是2的幂如32/64/128字节便于位运算快速索引每个池只服务一种类型避免类型混淆比如不能用64字节池存128字节对象必须设置池容量上限防止无限增长。我们给粒子池设上限5000超限时丢弃最老粒子。踩坑记录曾有个池块大小设为72字节刚好放一个含3个float的结构结果CPU缓存行64字节每次读取浪费8字节带宽。后来调整为64字节多余字段挪到附属数据区带宽利用率提升17%。3.2.2 分级内存池Hierarchical Pool当对象大小不一时固定池会浪费空间。分级池按大小分组小对象128B用固定池中对象128B~2KB用Slab分配器大对象2KB走页分配器。适用场景复杂游戏需同时管理UI控件小、动画Clip中、纹理Atlas大。实操要点Slab分配器按“页”Page通常4KB管理每页切若干相同大小的Slot大对象页分配器用mmapLinux或VirtualAllocWindows避免侵占主堆关键是层级切换阈值——我们设128B为小/中分界2KB为中/大分界经Profiler验证最均衡。踩坑记录Slab页内Slot数量计算错误导致一页剩余空间不足一个Slot却无法利用。解决方案页头存Slot偏移表而非固定公式计算牺牲8字节内存换100%空间利用率。3.2.3 帧内存池Frame Pool专为“一帧内创建一帧后销毁”的临时对象设计。原理每帧开始时重置指针所有分配从当前指针开始线性推进帧结束时整个池一键重置无需逐个释放。适用场景每帧计算的临时数据如剔除结果列表、光照计算中间变量、UI布局缓存。实操要点池大小需预估峰值我们按历史最高帧内存需求20%冗余必须支持“帧内多次重置”比如渲染线程和逻辑线程各用一个Frame Pool重置操作是原子的避免多线程竞争。踩坑记录初期没做大小预估某特效爆发时Frame Pool溢出触发fallback到malloc导致帧率毛刺。后来加入溢出监控超限时Log警告并降级特效。3.2.4 对象池Object Pool管理完整对象实例不仅分配内存还负责构造/析构。对象销毁时不清除内存只调用析构函数下次分配时调用构造函数复用。适用场景构造/析构开销大的对象如网络连接、物理刚体、复杂UI控件。实操要点对象必须支持“Reset()”方法将状态恢复到初始池需维护活跃对象列表便于调试时查看避免在Reset中做耗时操作如文件IO否则违背池设计初衷。踩坑记录某UI弹窗对象池Reset时重新加载本地化文本耗时波动大。改为预加载所有文本到全局缓存Reset只做指针赋值耗时从3ms降到0.02ms。3.2.5 线程局部存储池TLS Pool为每个线程独享内存池彻底消除锁竞争。适用于多线程任务系统如Job System。适用场景Worker线程频繁分配临时数据如寻路计算节点、网格体细分结果。实操要点TLS池大小需按线程数×单线程峰值预估主线程和Worker线程池分离避免主线程阻塞影响渲染TLS池内存不跨线程共享需设计跨线程数据传递协议如通过Ring Buffer。踩坑记录TLS池未设上限某线程异常卡死其TLS池持续增长直至OOM。解决方案为每个TLS池配Watchdog线程定期检查大小并告警。内存池类型分配速度碎片风险适用对象典型大小线程安全固定大小池★★★★★无小对象粒子、事件32~128B需加锁或无锁链表分级池★★★★☆低多尺寸对象混合小/中/大三级各级独立锁帧内存池★★★★★无临时数据一帧生命周期动态预估无锁单线程对象池★★★☆☆无构造开销大对象刚体、UI可变需加锁TLS池★★★★★无Worker线程临时数据按线程预估无锁线程独享3.3 内存布局优化让CPU缓存成为你的盟友内存管理不仅是“分和收”更是“怎么放”。游戏引擎里数据布局Data Layout比算法复杂度更能决定性能。我们曾重构一个NPC行为树系统算法没变只是把BehaviorTree节点从struct Node { int type; float value; Node* next; } 改为struct Node { int type; Node* next; float value; }仅调整字段顺序L3缓存未命中率下降19%AI逻辑帧耗时减少8.3ms。核心原则热字段前置冷字段后置对齐优先。“热字段”指每帧高频访问的字段如Transform的position.xyz“冷字段”指低频访问或仅初始化时用的字段如Mesh的boundingBox所有结构体按最大字段对齐如含double则8字节对齐含AVX向量则32字节对齐。实战技巧结构体拆分Splitting把一个大结构体拆成多个小结构体按访问频率分组。例如GameObject拆为Transform每帧读、Renderable渲染线程读、Scriptable逻辑线程读各自内存连续。数组结构SoA替代结构体数组AoS传统AoS[ {x,y,z}, {x,y,z}, ... ]SoA[x,x,x,...], [y,y,y,...], [z,z,z,...]。SoA让SIMD指令一次处理4个xAoS则需4次加载。内存预取Prefetching对顺序访问的大数组在循环中提前prefetch下一批数据。我们对骨骼动画关键帧数组加_prefetchCPU预取命中率从68%升至92%。注意不要盲目追求“100%缓存命中”。有些场景下为减少分支预测失败而接受略低命中率反而整体更快。性能优化永远是权衡的艺术。4. 数据结构选型不是“学过就行”而是“用对才活”4.1 游戏场景下的数据结构黄金法则教科书里红黑树、AVL树、跳表都是“优秀”的数据结构。但在游戏引擎里“优秀”不等于“适用”。我见过太多项目为追求“理论最优”在关键路径上用std::map存100个UI元素结果每帧遍历耗时2.3ms——而换成std::vector线性查找只要0.17ms。原因很简单游戏数据规模小、访问模式固定、缓存敏感。所以选型第一原则是能用数组不用链表能用线性不用对数能用特化不用通用。黄金法则一小规模1000用线性大规模10000再考虑树/哈希。UI元素、场景物体、音效实例通常几百到几千个vectorfind_if足够快std::unordered_map哈希表虽平均O(1)但哈希冲突、桶重建、内存不连续实际比vector慢我们统计过1000个元素内vector线性查找比unordered_map快1.8倍。黄金法则二顺序访问用数组随机访问用哈希范围查询用排序数组。渲染队列按渲染顺序遍历必须用连续数组资源管理按Asset ID随机查找用哈希表但自己实现避开STL开销时间轴动画关键帧需按时间戳范围查询如t∈[1.2,1.5]排序数组二分查找比红黑树快3倍且内存更紧凑。黄金法则三避免指针间接访问拥抱数据局部性。std::list每个节点heap分配指针跳跃缓存灾难std::vector 连续存储CPU预取高效即使T很大也优先用vectorT* separate data array而非vector 确保指针数组连续。4.2 六种高频数据结构实战解析与参数调优4.2.1 紧凑动态数组Compact Dynamic Array这是游戏引擎的“瑞士军刀”替代std::vector。核心改进容量增长策略不按1.5倍而按2的幂128→256→512避免频繁realloc内存对齐所有分配按16字节对齐适配SIMD无异常安全禁用异常用assert替代减少代码体积。实操参数初始容量128覆盖95%小容器场景最小扩容阈值size capacity * 0.75时扩容避免空间浪费内存池绑定小数组256B走固定池大数组走页分配器。性能对比处理10万个TransformCompact Array比std::vector快14%内存占用少22%因对齐优化和无异常开销。4.2.2 开放寻址哈希表Open-Addressing Hash Table替代std::unordered_map。用线性探测Linear Probing而非链地址法保证所有桶连续存储缓存友好。实操要点负载因子上限0.7超过则rehash探测序列用Robin Hood hashing减少聚集删除标记用特殊值如-1标记已删除桶避免探测链断裂。参数调优初始桶数5122^9质数桶数对哈希分布影响小2的幂更易位运算哈希函数Murmur3 for 32-bit速度快且分布均匀内存布局桶数组连续Key/Value紧邻存储避免指针跳转。踩坑记录初期用质数桶数如509哈希计算需取模比位运算慢3倍。改为2的幂后用mask bucket_count - 1hash mask替代hash % bucket_count单次查找提速40%。4.2.3 排序数组Sorted Array用于需要范围查询或有序遍历的场景如动画关键帧、事件时间轴、LOD距离表。优势内存最紧凑无指针开销二分查找O(log n)稳定无哈希冲突CPU预取高效顺序访问快。实操技巧插入/删除用memmove移动数据而非链表式插入对小数组64元素用线性查找比二分更快分支预测开销小用哨兵值Sentinel简化边界判断如首元素设为-∞末元素设为∞。性能实测1000个关键帧排序数组二分查找比std::set快5.2倍内存占用少68%。4.2.4 空间分割结构Spatial Partitioning加速碰撞检测、视锥剔除。不用八叉树Octree或BVHBounding Volume Hierarchy而用网格哈希Grid Hash——简单、高效、易并行。原理将世界划分为规则网格每个格子存物体ID列表。查询时只查目标物体所在格子及邻近8格。参数调优网格尺寸设为平均物体包围盒尺寸的1.5倍平衡格子数量与每格物体数格子存储用紧凑数组非链表动态物体每帧更新格子归属用原子操作避免锁。实测效果10000个动态物体Grid Hash剔除耗时0.8msOctree耗时3.2ms且Grid Hash代码量仅1/5。4.2.5 环形缓冲区Ring Buffer用于跨线程数据传递如渲染线程读取逻辑线程生成的Draw Call列表。关键设计无锁Lock-Free用原子变量管理读写指针大小2的幂便于位运算取模生产者/消费者分离避免ABA问题用版本号Version Number。实操陷阱缓冲区满时不能阻塞生产者会卡主线程需丢弃旧数据或返回失败消费者读取时需检查读指针是否追上写指针避免读到未写入数据。性能100万次跨线程传递Ring Buffer延迟均值0.03μsmutex保护的queue延迟均值12μs。4.2.6 位域集合Bitset管理海量布尔状态如10000个NPC的“是否可见”、“是否受伤”、“是否AI激活”。优势内存极致压缩10000个bool仅需1250字节10000/8批量操作快AND/OR/XOR指令一次处理64位缓存友好连续位操作CPU预取高效。实操扩展动态大小Bitset用vectoruint64_t实现支持resize查找第一个置位用__builtin_ctzllGCC或_BitScanForwardMSVC与SIMD结合用AVX2指令一次处理256位。效果管理10000个状态Bitset内存占用比vector 少40%批量置位操作快8倍。数据结构适用场景时间复杂度内存开销缓存友好实战推荐度紧凑动态数组通用容器顺序访问O(1)增删尾O(n)查找低★★★★★★★★★★开放寻址哈希表随机查找ID映射平均O(1)中★★★★☆★★★★☆排序数组范围查询有序遍历O(log n)查找极低★★★★★★★★★☆网格哈希空间查询碰撞剔除O(1)平均低★★★★☆★★★★★环形缓冲区跨线程通信O(1)低★★★★☆★★★★☆位域集合海量布尔状态O(1)位操作极低★★★★★★★★★☆5. 数学库设计精度、性能与API易用性的三角平衡5.1 游戏数学库的三大死敌精度陷阱、SIMD滥用、API割裂数学库常被当成“轮子”但它是引擎的“神经传导系统”。写错一个叉积符号角色就朝天飞矩阵乘法顺序颠倒摄像机就倒着走。更隐蔽的陷阱是过度追求精度或性能反而损害整体体验。精度陷阱浮点数不是实数。一个常见Bug用float计算世界坐标当坐标值167772162^24时最低有效位丢失物体在远处抖动。我们曾为某太空游戏修复此问题将世界坐标拆为“区域坐标局部坐标”区域坐标用double存储大范围局部坐标用float保证精度切换时无缝拼接。SIMD滥用AVX指令一次算4个float但前提是数据16字节对齐、连续存储、无依赖。若强行把散乱数据塞进SIMD寄存器反而比标量慢。我们试过用AVX加速粒子位置更新结果因数据不连续性能下降12%。后来重构为SoA布局再上AVX提速3.8倍。API割裂数学库接口若与引擎其他部分不一致开发者天天写转换代码。比如引擎用左手坐标系数学库用右手每调用一次都要翻转Z轴或者数学库Vector3的构造函数是Vector3(x,y,z)而引擎内部约定是Vector3(x,z,y)Y轴向上这种割裂每天都在制造Bug。5.2 核心数学类型设计详解从Vector到Quaternion5.2.1 Vector3/Vector4不只是x/y/z/w设计目标构造函数极简Vector3 a(1,2,3); Vector3 b {1,2,3};运算符重载直观a b, a * 2.0f, a.dot(b);SIMD就绪内存布局必须16字节对齐且w字段填充即使3D也存4分量。内存布局struct alignas(16) Vector3 { float x, y, z; float w; // padding for SIMD, not used in 3D ops }; // sizeof(Vector3) 16, 可直接加载到__m128关键优化dot/cross等常用函数内联避免函数调用开销cross积用SSE指令_mm_mul_ps _mm_sub_ps实现比标量快2.1倍重载[]操作符但标注[[deprecated]]强制用.x/.y/.z提高可读性。5.2.2 Matrix4x4行主序还是列主序这是信仰问题OpenGL用列主序Column-MajorDirectX用行主序Row-Major。引擎必须统一我们选列主序因为数学教材、GLSL、HLSL默认列主序矩阵乘法符合直觉M * v矩阵左乘向量与GPU Shader兼容避免CPU端转置。内存布局struct Matrix4x4 { // Column-major: m[0] is first columns x,y,z,w float m[16]; // [0]col0.x, [1]col0.y, [2]col0.z, [3]col0.w, ... };性能关键transpose()函数必须高效用SSE shuffle指令而非循环赋值inverse()不用通用高斯消元针对正交矩阵如View/Projection用转置替代multiply()针对“矩阵×向量”特化比通用矩阵乘法快5倍。5.2.3 Quaternion旋转的终极解法但别乱用四元数避免万向节锁但计算开销比欧拉角大。使用铁律存储和插值用Quaternion用户编辑和调试用Euler提供toEuler/fromEuler转换不用Quaternion表示方向如朝向用Vector3更直观。Slerp优化标准Slerp需acos、sin、cos开销大我们用Normalized Linear InterpolationNlerp 二次修正quat nlerp normalize(a * (1-t) b * t); float cosTheta dot(a, b); float theta acos(cosTheta); float k theta / sin(theta); // precomputed for common t return nlerp * (1 k * (1 - cosTheta));精度损失0.1°速度是Slerp的3.2倍。5.2.4 AABB/OBB碰撞检测的基石布局决定生死AABBAxis-Aligned Bounding Box成员min.x/min.y/min.z, max.x/max.y/max.z内存布局两个Vector3连续存储共24字节关键函数intersect()用分离轴定理6次比较即知是否相交。OBBOriented Bounding Box
阅读完成 · 觉得有帮助?
咨询建站