当前位置: 首页 > news >正文

Unity CullingGroup详解

【原理】

这里的Culling是为了将不可见的物体提前剔除,通常用简单的包围球或包围盒代理复杂的物体(只有视锥剔除,没有遮挡剔除)。

也可以通过物体本身的Renderer.bounds 与 Camera.Frustum来判断物体是否可见

而在CullingGroup中可以批量测试,相比逐个比较能减少函数调用与重复开销,效率更高。

实现细节在 native 引擎层,具体会利用连续内存布局来提高缓存友好性并减少 per-object 开销

【使用】

调用CullingGroup.SetBoundingSpheres(BoundingSphere[] array) 设置需要做可见性剔除的一批物体

其中BoundingSphere表示物体的包围盒,核心数据是float radius 、Vector3 position,可通过对物体预计算获取

调用CullingGroup.SetBoundingSphereCount(int count)设置检测长度,一般来说是默认全检测

CullingGroup.targetCamera用于指定视锥剔除的相机

CullingGroup.SetBoundingDistances(float[] distances)设置距离带

CullingGroup.SetDistanceReferencePoint(Vector3 point)设置距离参考点

距离带把以某个参考点为基准的距离空间划分为若干离散区间,用于把每个包围球分配到一个距离索引。这个索引不会改变物体的可见性判断(视锥剔除是独立的),而是给一个按距离分级的轻量信息,方便做自定义 LOD、渐隐、渲染/阴影优先级、资源流、网络同步等决策。

通常距离参考点被设置为相机或角色位置,距离是参考点到包围球中心的距离,设置一个升序的阈值数组distances = {d0, d1, …, dN-1},则会产生 N+1 个距离带

  • band 0:距离 ∈ [0, d0]
  • band 1:距离 ∈ (d0, d1]
  • band N:距离 ∈ (dN-1, +∞)

距离带只适合粗粒度的(N 通常少于 5),需要更细粒度时考虑用LODGroup

CullingGroup 提供 OnStateChanged 回调(CullingGroup.StateChanged delegate),当某个条目状态变化(可见性/距离带变化)时会触发,这种变化通常是dirty的,会合并多次变化。可以使用 CullingGroup.QueryIndices/IsVisible在Update中每帧查询对象是否可见。

 一定要调用 Dispose() 释放 CullingGroup(OnDisable/OnDestroy),否则会泄漏 native 资源

【代码示例】

class CullingGroupManager
{private CullingGroup cullingGroup;private BoundingSphere[] spheres;private float[] distances; // 距离带阈值(非包含关系,按升序)private Vector3 distanceReferencePoint;// 可选:当对象较多时,避免每帧分配新的数组,重用它们private Renderer[] targetRenderers;void Init(){if (distanceReferencePoint == null && Camera.main != null)distanceReferencePoint = Camera.main.transform.position;if (targetRenderers == null || targetRenderers.Length == 0){Debug.LogWarning("CullingGroupExample: No target renderers assigned.");return;}// 初始化 CullingGroupcullingGroup = new CullingGroup();cullingGroup.targetCamera = Camera.main; // 可选:指定用于视锥剔除的相机cullingGroup.onStateChanged += OnStateChanged;// 创建并填充包围球数组(重用)int count = targetRenderers.Length;spheres = new BoundingSphere[count];for (int i = 0; i < count; i++){// 以 Renderer.bounds.center 作为包围球中心,radius 设为 bounds.extents 最大分量或自定义Bounds b = targetRenderers[i].bounds;float radius = Mathf.Max(b.extents.x, b.extents.y, b.extents.z);spheres[i] = new BoundingSphere(b.center, radius);}cullingGroup.SetBoundingSpheres(spheres);cullingGroup.SetBoundingSphereCount(spheres.Length);// 设置距离带(举例:0 ~ 30 为近,30 ~ 80 为中,80+ 为远)distances = new float[] { 30f, 80f };cullingGroup.SetBoundingDistances(distances);if (distanceReferencePoint != null)cullingGroup.SetDistanceReferencePoint(distanceReferencePoint);}// 若对象会移动,实时更新包围球位置(建议只在需要时更新)void Update(){if (cullingGroup == null || spheres == null) return;// 更新包围球的位置(避免重新分配 spheres 数组)for (int i = 0; i < spheres.Length; i++){Bounds b = targetRenderers[i].bounds;spheres[i].position = b.center;// 如果对象变形/scale 变化影响半径,也可以更新 spheres[i].radius}// 将更新后的数组提交给 CullingGroupcullingGroup.SetBoundingSpheres(spheres);}// 回调:每当某个条目状态变化(可见性或距离带变更)会触发private void OnStateChanged(CullingGroupEvent sphereEvent){int index = sphereEvent.index;if (index < 0 || index >= targetRenderers.Length) return;Renderer r = targetRenderers[index];// 可见性切换if (sphereEvent.isVisible){// 恢复渲染r.enabled = true;}else{// 不可见时关闭 Renderer(或做其它逻辑)r.enabled = false;}// 距离带(currentDistance 从 0 开始表示第 0 个带,或等于 distances.Length 表示超出所有带)int distBand = sphereEvent.currentDistance;// 示例:根据距离带调整材质或 LOD 行为(演示用)// if (distBand == 0) { 切换高质量 LOD }// else if (distBand == 1) { 切换中等 LOD }// else { 切换低质量 / 仅占位渲染 }}void OnDisable(){Cleanup();}void OnDestroy(){Cleanup();}private void Cleanup(){if (cullingGroup != null){cullingGroup.onStateChanged -= OnStateChanged;cullingGroup.Dispose();cullingGroup = null;}}
}

【局限性】

 CullingGroup是给静态对象使用的,但实际使用时不可避免用于动态对象,这导致需要在C#端重新设置包围球数组

适合中小规模批量剔除(数百到几千条目),用法简单,跨渲染管线可用

但其实现偏向主线程使用,频繁大量更新(成千上万且每帧更新)会产生主线程开销和 GC 压力。

需要使用做动态剔除支持,其可以与Job+NativeArray 协同,用于解决大规模剔除的 CPU 开销问题

public class DynamicCullingGroup
{// 在 Inspector 中把要管理的 transforms / renderers 填上public Camera referenceCamera;public Transform[] targets;public Renderer[] targetRenderers;// 距离带阈值示例public float[] distanceThresholds = new float[] { 30f, 80f };// 内部数据private CullingGroup cg;private NativeArray<BoundingSphere> spheres; // 持久化用于 JObs 与 DynamicCullingGroupprivate NativeArray<float> distances;private TransformAccessArray transformAccessArray;private JobHandle updateHandle;void Init(){if (referenceCamera == null && Camera.main != null) referenceCamera = Camera.main;if (targets == null || targets.Length == 0){Debug.LogWarning("No targets assigned.");return;}int count = targets.Length;// 1) 分配 NativeArray 并初始化 radius(center 会在 Job 中更新)spheres = new NativeArray<BoundingSphere>(count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);for (int i = 0; i < count; i++){float radius = 1f;if (targetRenderers != null && i < targetRenderers.Length && targetRenderers[i] != null){Bounds b = targetRenderers[i].bounds;radius = Mathf.Max(b.extents.x, b.extents.y, b.extents.z);}// 初始 center 设为 transform.positionspheres[i] = new BoundingSphere(targets[i].position, radius);}// 2) 创建 TransformAccessArray(用于并行读取 transform)transformAccessArray = new TransformAccessArray(targets);// 3) 创建 DynamicCullingGroup 并设置初始参数cg = new CullingGroup();if (referenceCamera != null) cg.targetCamera = referenceCamera; cg.SetBoundingSphereCount(count);// 将 distances 转为 NativeArray 并设置(注意按升序)distances = new NativeArray<float>(distanceThresholds.Length, Allocator.Persistent);for (int i = 0; i < distanceThresholds.Length; i++) distances[i] = distanceThresholds[i];cg.SetBoundingDistances(distances.GetUnsafePtr);if (referenceCamera != null) cg.SetDistanceReferencePoint(referenceCamera.transform);// 把 NativeArray 传给 DCG(第一次写入)cg.SetBoundingSpheres(spheres);// 订阅状态变化回调(不同 Unity 版本回调签名可能不同)cg.onStateChanged += OnStateChanged;}// Job:并行更新包围球的 center(从 transform 中读取)struct UpdateSpheresJob : IJobParallelForTransform{public NativeArray<BoundingSphere> spheres;public void Execute(int index, TransformAccess transform){var old = spheres[index];// 只更新 center,保留原 radiusspheres[index] = new BoundingSphere(transform.position, old.radius);}}void Update(){if (spheres.IsCreated == false) return;// 1) 安排 job 更新所有包围球的 centervar job = new UpdateSpheresJob { spheres = spheres };updateHandle = job.Schedule(transformAccessArray);// 2) 等待 Job 完成,然后在主线程把更新的 NativeArray 传入 DCGupdateHandle.Complete();cg.SetBoundingSpheres(spheres);}// 回调:响应可见性或距离带变化void OnStateChanged(CullingGroupEvent ev){int idx = ev.index;bool visible = ev.isVisible;int distanceBand = ev.currentDistance;if (targetRenderers != null && idx >= 0 && idx < targetRenderers.Length && targetRenderers[idx] != null){// 根据可见性或距离带简单切换 Renderer 开关targetRenderers[idx].enabled = visible;//可以进一步根据 distanceBand 做 LOD、阴影开关等逻辑// e.g. if (distanceBand >= 2) targetRenderers[idx].shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;}}void OnDisable(){// 取消订阅以避免回调在 Dispose 后被触发if (cg != null) cg.onStateChanged -= OnStateChanged;}void OnDestroy(){// 确保 Job 已完成,再释放 Native 资源if (updateHandle.IsCompleted == false) updateHandle.Complete();if (cg != null){cg.Dispose();cg = null;}if (spheres.IsCreated) spheres.Dispose();if (distances.IsCreated) distances.Dispose();if (transformAccessArray.isCreated) transformAccessArray.Dispose();}
}

http://www.dtcms.com/a/603301.html

相关文章:

  • 建设银行曲江支行网站上海信息公司做网站
  • 监理网站广东省公路建设公司网站
  • C语言编译器哪个好用 | 选择适合自己的C语言编译器提升编程效率
  • 企业做宣传网站多少钱制作网页之前必须先建立什么
  • 遗传算法求解TSP旅行商问题python代码实战
  • Servlet 网页重定向
  • Eclipse 生成 jar 包
  • 学Java第四十三天——Map双列集合
  • 淮南服装网站建设地址房产网站如何做
  • 北京牛鼻子网站建设公司网站建设kaituozu
  • 服务器A需要调用银行的https://xx.xx.xx.xx:10000/infoBatch接口 nginx作为中间转发 怎么配置
  • 中国源码网游戏开服衡阳网站优化方案
  • 商品网站开发南宁做网站
  • Linux之中断子系统-中断控制器的中断函数gic_handle_irq分析(5)
  • 社交网站开发平台建工在线
  • Agentic AI TASK02 Reflection Design and Pattern
  • 撰写网站专题活动策划方案网站备案域名购买
  • 2014 吉林省赛题解 | CCUT应用OJ题解——F[X] + X = N
  • 如何做网站使用手册两个网站如何使用一个虚拟主机
  • 【云运维】Kubernetes 安装(基于Containerd+Calico)
  • 芜湖高端网站建设网站推广 济南
  • 公共部门网站建设维护网站挂黑链
  • wordpress怎么做的wifi优化大师下载
  • 免费婚庆网站模板嵌入式网站开发培训
  • 网站关键词先后开发公司安全管理组织机构图
  • 网站建设域名什么意思wordpress插件点不开
  • wordpress怎样建立多站点手机百度账号登录个人中心
  • 2025-11-12[第三大的数、将x减到0的最小操作数]
  • 基层建设刊物网站建筑工程网签备案合同
  • 强化学习基础概念与核心算法全解析