首页 > 其他分享 >unit引擎渲染管线之场景遍历

unit引擎渲染管线之场景遍历

时间:2024-12-08 18:33:42浏览次数:6  
标签:遍历 渲染 物体 Unity unit Renderer 剔除 管线 renderer

在Unity的渲染过程中,场景遍历是一个关键步骤,它直接影响到渲染性能和最终的视觉效果。以下是对场景遍历过程的详细说明,包括对象查找、剔除(Culling)以及后续处理的步骤。

1. 对象查找

在每一帧渲染时,Unity会遍历场景中的所有GameObject,查找具有Renderer组件的物体。这个过程包括:

  • 遍历所有GameObject:Unity会从场景中的根对象开始,递归地检查每个子对象,查找是否有Renderer组件(如MeshRenderer、SpriteRenderer等)。
  • 收集可渲染对象:所有找到的具有Renderer组件的对象会被收集到一个列表中,以便后续处理。

2. 剔除(Culling)

剔除是优化渲染性能的重要步骤,Unity在遍历过程中会进行以下几种剔除:

2.1 视锥剔除(Frustum Culling)
  • 视锥体:视锥体是由相机的视野定义的一个空间,只有在这个空间内的物体才会被渲染。
  • 剔除过程:Unity会检查每个Renderer组件的包围盒(Bounding Box)是否与视锥体相交。如果不相交,则该物体被剔除,不会被进一步处理。
2.2 遮挡剔除(Occlusion Culling)
  • 遮挡剔除:遮挡剔除用于判断哪些物体被其他物体遮挡,只有那些可见的物体才会被渲染。
  • 遮挡体:Unity会使用遮挡体(Occlusion Areas)来帮助判断哪些物体是可见的。遮挡剔除通常在场景中使用静态物体进行预计算,以提高性能。

3. 渲染排序

在完成对象查找和剔除后,Unity会对可渲染的物体进行排序,以确定渲染的顺序。排序的依据通常包括:

  • 材质:相同材质的物体会被批处理,以减少绘制调用。
  • 透明度:透明物体通常会在不透明物体之后渲染,以确保正确的视觉效果。

4. 绘制调用(Draw Calls)

经过剔除和排序后,Unity会为每个可渲染的物体发出绘制调用。绘制调用是指向GPU发送渲染指令的过程。每个绘制调用通常包括:

  • 设置状态:设置当前的渲染状态,包括材质、Shader、纹理等。
  • 绘制命令:向GPU发送绘制命令,指示其渲染特定的网格。

5. 后处理

在所有物体都被渲染后,Unity会进行后处理效果的应用。这些效果包括:

  • 抗锯齿:平滑边缘,减少锯齿状效果。
  • 景深:模拟相机的焦距,模糊不在焦点范围内的物体。
  • 色彩校正:调整场景的色彩和对比度。

6. 总结

场景遍历是Unity渲染管线中的一个重要环节,通过对象查找、剔除、渲染排序和绘制调用等步骤,Unity能够高效地渲染场景中的可见物体。理解这一过程有助于开发者优化游戏性能,确保在复杂场景中仍能保持流畅的渲染效果。

在Unity中,场景遍历和渲染的过程是由引擎内部处理的,开发者通常不需要直接编写遍历和剔除的代码。然而,我们可以通过一些C#代码示例来模拟场景遍历的逻辑,帮助理解这个过程。

以下是一个简单的示例,展示如何在Unity中遍历场景中的所有GameObject,查找具有Renderer组件的物体,并进行视锥剔除的基本逻辑。

示例代码

using UnityEngine;

public class SceneTraversal : MonoBehaviour
{
    // 相机的视锥体
    private Camera mainCamera;

    void Start()
    {
        // 获取主相机
        mainCamera = Camera.main;
    }

    void Update()
    {
        // 每帧遍历场景中的所有可渲染对象
        TraverseScene();
    }

    void TraverseScene()
    {
        // 获取场景中的所有Renderer组件
        Renderer[] renderers = FindObjectsOfType<Renderer>();

        foreach (Renderer renderer in renderers)
        {
            // 进行视锥剔除
            if (IsVisible(renderer))
            {
                // 如果可见,可以进行渲染处理
                RenderObject(renderer);
            }
        }
    }

    bool IsVisible(Renderer renderer)
    {
        // 获取物体的包围盒
        Bounds bounds = renderer.bounds;

        // 检查包围盒是否与相机的视锥体相交
        Plane[] planes = GeometryUtility.CalculateFrustumPlanes(mainCamera);
        return GeometryUtility.TestPlanesAABB(planes, bounds);
    }

    void RenderObject(Renderer renderer)
    {
        // 这里可以添加渲染逻辑
        // 例如,设置材质、Shader等
        // 目前只是打印出可见的物体名称
        Debug.Log("Rendering: " + renderer.gameObject.name);
    }
}

代码说明

  1. 获取主相机:在Start方法中,我们获取场景中的主相机。

  2. 遍历场景:在Update方法中,我们调用TraverseScene方法,每帧遍历场景中的所有Renderer组件。

  3. 视锥剔除

    • IsVisible方法中,我们获取每个Renderer的包围盒(bounds)。
    • 使用GeometryUtility.CalculateFrustumPlanes方法计算相机的视锥体平面。
    • 使用GeometryUtility.TestPlanesAABB方法检查包围盒是否与视锥体相交。
  4. 渲染处理:在RenderObject方法中,我们可以添加实际的渲染逻辑。这里我们只是打印出可见物体的名称。

注意事项

  • 这个示例代码是一个简化的版本,实际的Unity渲染管线会更加复杂,涉及到更多的优化和处理。
  • Unity的渲染过程是高度优化的,开发者通常不需要手动处理这些细节,除非在特定情况下需要自定义渲染逻辑。
  • 这个示例主要用于学习和理解场景遍历和视锥剔除的基本概念。

我们可以进一步探讨Unity渲染过程中的其他方面,或者扩展之前的代码示例,以涵盖更多的功能和优化。以下是一些可能的扩展方向:

1. 遮挡剔除(Occlusion Culling)

虽然在上面的示例中我们只实现了视锥剔除,但在实际应用中,遮挡剔除也是一个重要的性能优化手段。Unity提供了内置的遮挡剔除系统,但我们可以模拟一个简单的遮挡剔除逻辑。

示例代码扩展
using UnityEngine;

public class SceneTraversal : MonoBehaviour
{
    private Camera mainCamera;

    void Start()
    {
        mainCamera = Camera.main;
    }

    void Update()
    {
        TraverseScene();
    }

    void TraverseScene()
    {
        Renderer[] renderers = FindObjectsOfType<Renderer>();

        foreach (Renderer renderer in renderers)
        {
            if (IsVisible(renderer) && IsNotOccluded(renderer))
            {
                RenderObject(renderer);
            }
        }
    }

    bool IsVisible(Renderer renderer)
    {
        Bounds bounds = renderer.bounds;
        Plane[] planes = GeometryUtility.CalculateFrustumPlanes(mainCamera);
        return GeometryUtility.TestPlanesAABB(planes, bounds);
    }

    bool IsNotOccluded(Renderer renderer)
    {
        // 简单的遮挡剔除逻辑
        // 这里可以实现更复杂的遮挡检测
        // 例如,检查是否有其他物体在视线之间
        RaycastHit hit;
        Vector3 direction = renderer.transform.position - mainCamera.transform.position;

        // 使用射线检测
        if (Physics.Raycast(mainCamera.transform.position, direction, out hit))
        {
            // 如果射线击中的是其他物体,且该物体不是当前的renderer
            if (hit.collider != renderer.GetComponent<Collider>())
            {
                return false; // 被遮挡
            }
        }
        return true; // 未被遮挡
    }

    void RenderObject(Renderer renderer)
    {
        Debug.Log("Rendering: " + renderer.gameObject.name);
    }
}

2. 物体分组和批处理

为了进一步优化性能,我们可以考虑将相同材质的物体进行分组和批处理。Unity的Static BatchingDynamic Batching可以自动处理这些,但我们也可以手动实现一个简单的批处理逻辑。

示例代码扩展
using System.Collections.Generic;
using UnityEngine;

public class SceneTraversal : MonoBehaviour
{
    private Camera mainCamera;

    void Start()
    {
        mainCamera = Camera.main;
    }

    void Update()
    {
        TraverseScene();
    }

    void TraverseScene()
    {
        Renderer[] renderers = FindObjectsOfType<Renderer>();
        Dictionary<Material, List<Renderer>> materialGroups = new Dictionary<Material, List<Renderer>>();

        foreach (Renderer renderer in renderers)
        {
            if (IsVisible(renderer) && IsNotOccluded(renderer))
            {
                if (!materialGroups.ContainsKey(renderer.material))
                {
                    materialGroups[renderer.material] = new List<Renderer>();
                }
                materialGroups[renderer.material].Add(renderer);
            }
        }

        // 批处理渲染
        foreach (var group in materialGroups)
        {
            RenderBatch(group.Key, group.Value);
        }
    }

    bool IsVisible(Renderer renderer)
    {
        Bounds bounds = renderer.bounds;
        Plane[] planes = GeometryUtility.CalculateFrustumPlanes(mainCamera);
        return GeometryUtility.TestPlanesAABB(planes, bounds);
    }

    bool IsNotOccluded(Renderer renderer)
    {
        RaycastHit hit;
        Vector3 direction = renderer.transform.position - mainCamera.transform.position;

        if (Physics.Raycast(mainCamera.transform.position, direction, out hit))
        {
            if (hit.collider != renderer.GetComponent<Collider>())
            {
                return false; // 被遮挡
            }
        }
        return true; // 未被遮挡
    }

    void RenderBatch(Material material, List<Renderer> renderers)
    {
        // 这里可以实现批处理渲染逻辑
        // 例如,设置材质并一次性绘制所有物体
        foreach (var renderer in renderers)
        {
            // 这里可以添加实际的渲染调用
            Debug.Log("Batch Rendering: " + renderer.gameObject.name);
        }
    }
}

3. 其他优化建议

  • 使用层(Layer)和标签(Tag):可以通过设置层和标签来快速过滤不需要渲染的物体。
  • LOD(Level of Detail):使用LOD技术,根据相机与物体的距离切换不同的模型细节,以提高性能。
  • 合并网格:对于静态物体,可以在编辑器中合并网格,以减少绘制调用。

4. 总结

通过以上的代码示例和扩展,我们可以看到如何在Unity中实现基本的场景遍历、视锥剔除和遮挡剔除的逻辑,以及如何进行简单的批处理渲染。这些概念对于理解Unity的渲染管线和优化游戏性能非常重要。

标签:遍历,渲染,物体,Unity,unit,Renderer,剔除,管线,renderer
From: https://blog.csdn.net/qq_33060405/article/details/144253171

相关文章

  • 二叉树遍历
    前序顺序为根左右递归publicstaticvoidpreLoop(TreeNoderoot){System.out.println(root.value);if(root.left!=null){preLoop(root.left);}if(root.right!=null){preLoop(root.right);}}其他使用栈,以根右左的顺......
  • Unity类银河战士恶魔城学习总结(P168 Snow and Fire file 下雪 和 萤火 的特效 )
    【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili教程源地址:https://www.udemy.com/course/2d-rpg-alexdev/本章节实现了两套场景粒子特效雪的特效萤火特效以上圈红圈的地方都是着重需要修改的地方左侧参数粒子生命周期(StartLifetime)含义:每个粒子的生......
  • Unity类银河战士恶魔城学习总结(P169 Hit and Critical FX暴击和攻击的特效)
    【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili教程源地址:https://www.udemy.com/course/2d-rpg-alexdev/本章节添加了暴击的攻击特效EntityFX.cs添加部分!!!功能分析1.主要功能CreateHitFX方法:根据攻击是否为暴击(_critical),选择不同的特效预制体。生成击......
  • 【Leetcode Top 100】94. 二叉树的中序遍历
    问题背景给定一个二叉树的根节点rootrootroot,返回它的中序遍历。数据约......
  • c语言实现二叉树的创建、遍历(先序、中序、后序)
    二叉树是一种树形数据结构,其中每个节点最多有两个子节点,通常称为左子节点和右子节点。二叉树在计算机科学中具有广泛的应用,如表达式解析、数据存储与检索等。以下是有关二叉树的基本知识。1.二叉树的基本定义节点:二叉树的基本组成单元,包括节点值和指向其子节点的指针(左指......
  • CentOS Docker 及 Docker Engine-Community 安装
    CentOSDocker安装Docker支持以下的64位CentOS版本:CentOS7CentOS8更高版本…使用官方安装脚本自动安装安装命令如下:curl-fsSLhttps://get.docker.com|bash-sdocker--mirrorAliyun手动安装卸载旧版本较旧的Docker版本称为docker或docker-eng......
  • 根据后序遍历完全二叉树构建树并输出中序遍历
    来看这道题:之前编者想了很久,该如何仅根据后序序列建树,在反复研磨遍历的特征后,我突然发现:对于完全二叉树,我们完全可以采用其在线性表示(用数组)的性质解题性质:根节点x, 左子树索引为2x,右子树索引为2x+1且不为空。则,我们只需按后序遍历的特点递归建树即可。上代码:......
  • Unity开发日常记录_6_PC工业仿真项目 使用S7.Net 和 博图TIA Portal V16 和 S7-PLCSIM
    Unity开发日常记录_6_工业仿真项目使用S7.Net和博图TIAPortalV16和S7-PLCSIMAdvancedV3.0和RobotStudio进行工业仿真:本文中开发的几个项目体量比较小,也没有做很多优化,单纯是记录整个项目遇到的问题和对应的解决方案,以及注意事项,为大家做工业仿真项目提供一......
  • 写一个方法遍历指定对象的所有属性
    functionenumerateProperties(obj){constproperties=[];for(constkeyinobj){if(obj.hasOwnProperty(key)){//过滤掉继承的属性properties.push({name:key,value:obj[key]});}}returnproperties;}//......
  • powershell遍历注册dll
    #设置要遍历的根文件夹路径,你可以根据实际情况修改这个路径$rootFolder="C:\script\dlls"#获取该文件夹及其子文件夹下所有的.dll文件$dllFiles=Get-ChildItem-Path$rootFolder-Filter"*.dll"-Recurse#遍历每个找到的.dll文件并尝试注册foreach($dllFilein$dllFi......