十一、Unity 新特性


文档摘要

十一、Unity 新特性 十一、Unity 新特性 1. 可编程渲染管线 (Scriptable Render Pipeline, SRP) 可编程渲染管线 (SRP) 是 Unity 引擎渲染架构的一次重大革新。它允许开发者不再局限于内置渲染管线,而是可以根据项目需求自定义渲染管线。SRP 提供了基础框架,开发者可以使用 C# 脚本和 Shader Graph 来自定义渲染流程,实现各种高级渲染效果和优化策略。SRP 主要包含两种预设管线:通用渲染管线 (Universal Render Pipeline, URP) 和高清渲染管线 (High Definition Render Pipeline, HDRP)。

十一、Unity 新特性

十一、Unity 新特性

1. 可编程渲染管线 (Scriptable Render Pipeline, SRP)

可编程渲染管线 (SRP) 是 Unity 引擎渲染架构的一次重大革新。它允许开发者不再局限于内置渲染管线,而是可以根据项目需求自定义渲染管线。SRP 提供了基础框架,开发者可以使用 C# 脚本和 Shader Graph 来自定义渲染流程,实现各种高级渲染效果和优化策略。SRP 主要包含两种预设管线:通用渲染管线 (Universal Render Pipeline, URP) 和高清渲染管线 (High Definition Render Pipeline, HDRP)。

  • 通用渲染管线 (URP):URP 旨在提供高性能、可扩展的渲染方案,适用于广泛的平台,包括移动设备、Web 和主机平台。它在性能和画面质量之间取得了良好的平衡,是移动游戏和轻量级应用的首选。

  • 高清渲染管线 (HDRP):HDRP 专注于实现电影级别的视觉效果,适用于高端平台,例如 PC 和主机平台。它提供了诸如延迟渲染、体积光照、屏幕空间反射等高级渲染特性,能够呈现逼真、细腻的游戏画面。

1.1 代码实践:使用 URP 创建自定义渲染效果

以下代码示例展示了如何使用 URP 和 Shader Graph 创建一个简单的自定义后处理效果,例如颜色调整。

// 创建一个新的 Shader Graph 资源,命名为 ColorAdjustPostProcess.shadergraph // 在 Shader Graph 中,创建一个 Color Adjust 节点,连接到 Fragment 输出 // 创建一个 C# 脚本,命名为 ColorAdjustPostProcessRendererFeature.cs using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; public class ColorAdjustPostProcessRendererFeature : ScriptableRendererFeature { [System.Serializable] public class ColorAdjustSettings { public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing; public Material colorAdjustMaterial = null; [Range(0f, 1f)] public float saturation = 1f; [Range(0f, 1f)] public float brightness = 0.5f; [Range(0f, 1f)] public float contrast = 0.5f; } public ColorAdjustSettings settings = new ColorAdjustSettings(); private ColorAdjustPostProcessPass colorAdjustPass; public override void Create() { colorAdjustPass = new ColorAdjustPostProcessPass(settings); } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { if (settings.colorAdjustMaterial == null) { Debug.LogWarningFormat("Missing Color Adjust Material. Post Process Pass will not be added."); return; } colorAdjustPass.renderPassEvent = settings.renderPassEvent; renderer.EnqueuePass(colorAdjustPass); } class ColorAdjustPostProcessPass : ScriptableRenderPass { private Material material; private ColorAdjustSettings settings; private RenderTargetIdentifier source; private RenderTargetIdentifier destination; private string profilerTag; public ColorAdjustPostProcessPass(ColorAdjustSettings settings) { this.settings = settings; this.material = settings.colorAdjustMaterial; this.profilerTag = "Color Adjust Post Process"; } public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) { source = renderingData.cameraData.renderer.cameraColorTarget; RenderTextureDescriptor descriptor = renderingData.cameraData.cameraTargetDescriptor; descriptor.depthBufferBits = 0; cmd.GetTemporaryRT(ShaderIDs._TempColorTexture, descriptor, FilterMode.Bilinear); destination = new RenderTargetIdentifier(ShaderIDs._TempColorTexture); } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { if (material == null) return; CommandBuffer cmd = CommandBufferPool.Get(profilerTag); using (new ProfilingScope(cmd, new ProfilingSampler(profilerTag))) { material.SetFloat("_Saturation", settings.saturation); material.SetFloat("_Brightness", settings.brightness); material.SetFloat("_Contrast", settings.contrast); Blit(cmd, source, destination, material); Blit(cmd, destination, source); // 将后处理结果 blit 回 source } context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } public override void OnCameraCleanup(CommandBuffer cmd) { cmd.ReleaseTemporaryRT(ShaderIDs._TempColorTexture); } } }
// ShaderIDs.cs 用于存储 Shader 属性的 ID,避免字符串查找的性能开销 public static class ShaderIDs { public static readonly int _MainTex = Shader.PropertyToID("_MainTex"); public static readonly int _Saturation = Shader.PropertyToID("_Saturation"); public static readonly int _Brightness = Shader.PropertyToID("_Brightness"); public static readonly int _Contrast = Shader.PropertyToID("_Contrast"); public static readonly int _TempColorTexture = Shader.PropertyToID("_TempColorTexture"); }

内容详解:

  1. Shader Graph 创建后处理材质: 首先,我们使用 Shader Graph 创建了一个名为 ColorAdjustPostProcess.shadergraph 的 Shader Graph 资源。在这个 Shader Graph 中,我们添加了 Color Adjust 节点,并将其连接到 Fragment 输出。Color Adjust 节点允许我们在 Shader 中调整图像的饱和度、亮度、对比度等参数。

  2. 创建 Renderer Feature 脚本: ColorAdjustPostProcessRendererFeature.cs 脚本继承自 ScriptableRendererFeature,用于将自定义的后处理 Pass 注入到 URP 的渲染流程中。

    • ColorAdjustSettings 类定义了后处理效果的参数,包括 RenderPassEvent(渲染 Pass 的注入时机)、colorAdjustMaterial(后处理材质)、以及饱和度、亮度、对比度等参数。

    • Create() 方法创建了 ColorAdjustPostProcessPass 实例。

    • AddRenderPasses() 方法将 ColorAdjustPostProcessPass 注入到渲染器的渲染队列中。

  3. 创建 Renderer Pass 脚本: ColorAdjustPostProcessPass 类继承自 ScriptableRenderPass,负责实际的后处理逻辑。

    • OnCameraSetup() 方法在相机渲染设置阶段被调用,用于获取渲染目标纹理,并创建临时渲染纹理。

    • Execute() 方法是渲染 Pass 的核心,它在指定的 RenderPassEvent 时机被调用。在这个方法中,我们:

      • CommandBufferPool 获取一个 Command Buffer,用于记录渲染命令。

      • 使用 ProfilingScope 标记性能分析区域。

      • ColorAdjustSettings 中的参数传递给后处理材质。

      • 使用 Blit() 函数执行后处理操作。Blit() 函数将源纹理 (source) 拷贝到目标纹理 (destination),并应用指定的材质。这里我们先将源纹理 Blit 到临时纹理,应用后处理效果,然后再将临时纹理 Blit 回源纹理,实现后处理效果的叠加。

    • OnCameraCleanup() 方法在相机渲染清理阶段被调用,用于释放临时渲染纹理。

  4. ShaderIDs 脚本: ShaderIDs.cs 脚本定义了 Shader 属性的 ID,使用 Shader.PropertyToID() 方法将 Shader 属性名称转换为唯一的 ID 整数。这样做可以避免在运行时进行字符串查找,提高性能。

  5. 使用 Renderer Feature: 要使用这个自定义后处理效果,需要在 URP Renderer 资源中添加 ColorAdjustPostProcessRendererFeature。在 Inspector 面板中,可以配置 Color Adjust Material 为我们创建的 ColorAdjustPostProcess.shadergraph 材质,并调整饱和度、亮度、对比度等参数。

1.2 Mermaid 图表:URP 渲染管线流程

内容详解:

  • Camera (相机): 渲染流程的起点,相机决定了场景中哪些物体需要被渲染。

  • Culling (裁剪): 剔除相机视野之外的物体,以及被遮挡的物体,减少不必要的渲染计算。

  • Render Queue Sorting (渲染队列排序): 根据物体的渲染队列 (Render Queue) 对物体进行排序,确保正确的渲染顺序,例如先渲染不透明物体,再渲染透明物体。

  • Shadow Pass (阴影 Pass): 渲染阴影贴图,用于计算阴影效果。

  • Depth Pre-pass (深度预处理 Pass): 可选的 Pass,用于提前渲染场景的深度信息,可以用于优化某些渲染效果,例如屏幕空间反射。

  • Opaque Pass (不透明物体 Pass): 渲染场景中的不透明物体。

  • Post-processing Pass (后处理 Pass): 应用各种后处理效果,例如颜色校正、Bloom、景深等。我们自定义的 Color Adjust Post Process Pass 就属于这个阶段。

  • Transparent Pass (透明物体 Pass): 渲染场景中的透明物体。

  • Output to Screen (输出到屏幕): 将最终渲染结果输出到屏幕。

1.3 SRP 的优势:

  • 高度可定制性: 开发者可以完全控制渲染流程,根据项目需求定制渲染管线,实现各种独特的渲染效果。

  • 跨平台一致性: SRP 可以更好地保证项目在不同平台上的渲染效果一致性。

  • 性能优化: 通过自定义渲染管线,开发者可以针对特定平台和项目需求进行性能优化。

  • 模块化设计: SRP 采用模块化设计,易于扩展和维护。

2. Visual Scripting (可视化脚本)

Visual Scripting (可视化脚本) 是 Unity 引擎推出的一种无需编写代码即可创建游戏逻辑的方式。它使用节点图 (Node Graph) 的形式,将复杂的代码逻辑可视化,开发者可以通过拖拽节点、连接节点来创建游戏行为和交互。Visual Scripting 降低了游戏开发的门槛,使得非程序员也能参与到游戏逻辑的开发中,同时也提高了程序员的开发效率,可以快速原型设计和迭代。

2.1 代码实践:使用 Visual Scripting 创建简单的物体移动

以下示例展示了如何使用 Visual Scripting 创建一个简单的物体移动逻辑,使物体在按下空格键时向上跳跃。

  1. 安装 Visual Scripting 包: 在 Unity Package Manager 中搜索 "Visual Scripting" 并安装。

  2. 创建 Visual Scripting Graph: 在 Project 窗口中右键点击,选择 "Create" -> "Visual Scripting" -> "Script Graph",命名为 "ObjectMovementGraph"。

  3. 创建 Flow Machine 组件: 在场景中选择一个 GameObject,例如 Cube,然后添加 "Flow Machine" 组件。将 "ObjectMovementGraph" 拖拽到 Flow Machine 组件的 Graph 属性中。

  4. 编辑 Visual Scripting Graph: 双击 "ObjectMovementGraph" 打开 Visual Scripting 编辑器。

    • 添加 Input - Keyboard Input 节点: 右键点击空白区域,选择 "Add Node" -> "Input" -> "Keyboard Input",选择 "Space" 键。

    • 添加 Branch 节点: 右键点击空白区域,选择 "Add Node" -> "Logic" -> "Branch"。将 "Keyboard Input" 节点的 "Bool" 输出连接到 "Branch" 节点的 "Condition" 输入。

    • 添加 Transform - Translate 节点: 右键点击空白区域,选择 "Add Node" -> "Transform" -> "Translate"。将 "Branch" 节点的 "True" 输出连接到 "Translate" 节点的 "Control Input" 输入。设置 "Translate" 节点的 "Vector" 输入为 (0, 1, 0),表示向上移动。

    • 连接 GameObject: 将 "Translate" 节点的 "Target" 输入连接到 Flow Machine 组件的 GameObject。可以直接拖拽场景中的 Cube GameObject 到 "Target" 输入上,或者在 Graph Inspector 中选择 "Self"。

2.2 Mermaid 图表:Visual Scripting 节点图

内容详解:

  • Keyboard Input (Space) 节点: 监听空格键的按下事件,当空格键被按下时,输出 True 值。

  • Branch 节点: 条件分支节点,根据输入的布尔值 (Condition) 决定执行哪个分支。当 Condition 为 True 时,执行 True 分支;当 Condition 为 False 时,执行 False 分支。

  • Translate (0, 1, 0) 节点: 平移物体的位置。设置 Vector 输入为 (0, 1, 0) 表示沿 Y 轴正方向移动,即向上移动。

  • GameObject: 表示要操作的 GameObject,这里是 Cube 物体。

2.3 Visual Scripting 的优势:

  • 可视化编程: 通过节点图的方式可视化代码逻辑,降低了编程门槛,易于理解和维护。

  • 快速原型设计: 可以快速搭建游戏逻辑原型,验证游戏机制和玩法。

  • 非程序员友好: 使得美术、设计师等非程序员也能参与到游戏逻辑的开发中。

  • 提高开发效率: 对于简单的逻辑,使用 Visual Scripting 可以比编写代码更快。

  • 易于学习和上手: Visual Scripting 的学习曲线相对平缓,容易上手。

3. Addressable Asset System (可寻址资源系统)

Addressable Asset System (可寻址资源系统,简称 Addressables) 是 Unity 引擎用于管理和加载资源的新系统。它取代了传统的 Resources 文件夹和 AssetBundle,提供了一种更灵活、更高效的资源管理方案。Addressables 允许开发者通过 "地址" (Address) 来引用和加载资源,而无需关心资源的物理路径和打包方式。它可以用于管理各种类型的资源,例如场景、Prefab、纹理、音频、Shader 等。

3.1 代码实践:使用 Addressables 加载 Prefab

以下代码示例展示了如何使用 Addressables 加载一个 Prefab 资源,并实例化到场景中。

  1. 安装 Addressables 包: 在 Unity Package Manager 中搜索 "Addressables" 并安装。

  2. 配置 Addressables Groups: 打开 Addressables Groups 窗口 (Window -> Asset Management -> Addressables -> Groups)。创建一个新的 Group,例如 "Prefabs"。

  3. 标记 Prefab 为 Addressable: 在 Project 窗口中选择一个 Prefab 资源,例如 "MyPrefab",然后在 Inspector 面板中点击 "Addressable" 按钮,将其添加到 "Prefabs" Group 中。可以自定义资源的 Address,默认情况下 Address 为资源的名称。

  4. 编写加载代码:

using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; public class LoadPrefabAddressable : MonoBehaviour { public string prefabAddress = "MyPrefab"; // Addressables 中配置的 Prefab 地址 public Transform spawnPoint; async void Start() { AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>(prefabAddress); await handle.Task; // 等待资源加载完成 if (handle.Status == AsyncOperationStatus.Succeeded) { GameObject prefab = handle.Result; Instantiate(prefab, spawnPoint.position, spawnPoint.rotation); } else { Debug.LogError("Failed to load prefab: " + prefabAddress); } Addressables.Release(handle); // 释放资源句柄 } }

内容详解:

  1. 加载资源: Addressables.LoadAssetAsync<GameObject>(prefabAddress) 方法用于异步加载指定地址的资源。prefabAddress 是我们在 Addressables Groups 中配置的 Prefab 资源的地址,例如 "MyPrefab"。该方法返回一个 AsyncOperationHandle<GameObject> 类型的异步操作句柄。

  2. 等待加载完成: await handle.Task 用于等待异步资源加载操作完成。

  3. 检查加载状态: handle.Status == AsyncOperationStatus.Succeeded 用于检查资源是否加载成功。

  4. 获取加载结果: handle.Result 获取加载成功的资源,这里是 GameObject 类型的 Prefab。

  5. 实例化 Prefab: Instantiate(prefab, spawnPoint.position, spawnPoint.rotation) 将加载的 Prefab 实例化到场景中的指定位置。

  6. 释放资源句柄: Addressables.Release(handle) 用于释放资源句柄,当资源不再需要使用时,应该及时释放资源句柄,以便 Addressables 系统可以进行资源管理和内存优化。

3.2 Mermaid 图表:Addressables 工作流程

内容详解:

  • Assets (资源): 项目中的各种资源,例如 Prefab、纹理、音频等。

  • Addressable Groups (可寻址资源组): 用于组织和管理 Addressable 资源的逻辑分组。可以将相关的资源放在同一个 Group 中,方便管理和打包。

  • Catalog (地址目录): Addressables 系统维护一个 Catalog,用于存储资源地址和资源实际位置的映射关系。在运行时,通过 Catalog 来查找资源。

  • Build Pipeline (构建管线): Addressables 系统在构建时,会根据 Group 的配置,将 Addressable 资源打包成 Bundles (Asset Packs)。

  • Bundles (资源包): 打包后的资源包,包含 Addressable 资源。

  • Runtime Loading (运行时加载): 在运行时,通过资源的 Address 从 Bundles 中加载资源。

3.3 Addressables 的优势:

  • 灵活的资源管理: 通过 Address 地址来引用资源,解耦了资源引用和资源物理路径,提高了资源管理的灵活性。

  • 高效的资源加载: Addressables 提供了异步加载、资源缓存、依赖管理等机制,提高了资源加载效率和运行时性能。

  • 减少内存占用: Addressables 可以根据需要加载和卸载资源,减少内存占用。

  • 热更新支持: Addressables 可以方便地实现资源的热更新,无需重新构建整个应用程序。

  • 简化 AssetBundle 管理: Addressables 封装了 AssetBundle 的复杂性,简化了 AssetBundle 的创建和管理。

4. 新输入系统 (New Input System)

Unity 新输入系统 (New Input System) 是对旧输入系统的一次全面升级。它提供了更强大、更灵活、更易用的输入管理方案。新输入系统支持各种输入设备,例如键盘、鼠标、手柄、触摸屏、VR 控制器等,并提供了统一的输入事件处理机制。它采用 Input Actions 的概念,将物理输入动作 (例如按下按键、移动鼠标) 与游戏逻辑动作 (例如跳跃、射击) 解耦,使得输入管理更加清晰和模块化。

4.1 代码实践:使用 Input Actions 控制角色移动

以下代码示例展示了如何使用 Input Actions 来控制角色的移动。

  1. 安装 Input System 包: 在 Unity Package Manager 中搜索 "Input System" 并安装。

  2. 创建 Input Actions 资源: 在 Project 窗口中右键点击,选择 "Create" -> "Input Actions",命名为 "PlayerInputActions"。

  3. 配置 Input Actions: 双击 "PlayerInputActions" 打开 Input Actions 编辑器。

    • 创建 Action Map: 默认情况下会创建一个名为 "Player" 的 Action Map。

    • 创建 Actions: 在 "Player" Action Map 中,创建两个 Action: "Move" 和 "Jump"。

      • Move Action: Action Type 选择 "Value",Control Type 选择 "Vector2"。添加两个 Binding:"/wasd" 和 "/leftStick"。

      • Jump Action: Action Type 选择 "Button",添加一个 Binding:"/space" 和 "/buttonSouth"。

    • 保存 Input Actions 资源。

  4. 生成 C# 代码: 在 Input Actions 编辑器中,点击 "Generate C# Class",生成 C# 脚本 "PlayerInputActions.cs"。

  5. 编写控制代码:

using UnityEngine; using UnityEngine.InputSystem; public class PlayerController : MonoBehaviour { private PlayerInputActions playerInputActions; private Vector2 moveInput; public float moveSpeed = 5f; public float jumpForce = 10f; private Rigidbody rb; private bool isGrounded; void Awake() { playerInputActions = new PlayerInputActions(); rb = GetComponent<Rigidbody>(); } void OnEnable() { playerInputActions.Player.Enable(); playerInputActions.Player.Move.performed += OnMovePerformed; playerInputActions.Player.Move.canceled += OnMoveCanceled; playerInputActions.Player.Jump.performed += OnJumpPerformed; } void OnDisable() { playerInputActions.Player.Disable(); playerInputActions.Player.Move.performed -= OnMovePerformed; playerInputActions.Player.Move.canceled -= OnMoveCanceled; playerInputActions.Player.Jump.performed -= OnJumpPerformed; } void FixedUpdate() { Move(); } void OnMovePerformed(InputAction.CallbackContext context) { moveInput = context.ReadValue<Vector2>(); } void OnMoveCanceled(InputAction.CallbackContext context) { moveInput = Vector2.zero; } void OnJumpPerformed(InputAction.CallbackContext context) { if (isGrounded) { Jump(); } } void Move() { Vector3 movement = new Vector3(moveInput.x, 0f, moveInput.y) * moveSpeed * Time.fixedDeltaTime; rb.MovePosition(rb.position + movement); } void Jump() { rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse); isGrounded = false; } void OnCollisionEnter(Collision collision) { if (collision.gameObject.CompareTag("Ground")) { isGrounded = true; } } }

内容详解:

  1. 创建 Input Actions 资源: 我们创建了一个 "PlayerInputActions.inputactions" 资源,并在其中定义了 "Move" 和 "Jump" 两个 Action。 "Move" Action 绑定了键盘的 WASD 键和手柄的左摇杆,"Jump" Action 绑定了键盘的空格键和手柄的 South Button。

  2. 生成 C# 代码: 通过 Input Actions 编辑器生成了 "PlayerInputActions.cs" 脚本,这个脚本包含了访问 Input Actions 的 C# 代码。

  3. PlayerController 脚本: PlayerController.cs 脚本负责处理角色控制逻辑。

    • playerInputActions = new PlayerInputActions(); 创建了 "PlayerInputActions" 类的实例。

    • playerInputActions.Player.Enable(); 启用了 "Player" Action Map。

    • 事件订阅: 通过事件订阅的方式处理输入事件。

      • playerInputActions.Player.Move.performed += OnMovePerformed; 当 "Move" Action 被触发 (例如按下 WASD 键或移动左摇杆) 时,调用 OnMovePerformed 方法。

      • playerInputActions.Player.Move.canceled += OnMoveCanceled; 当 "Move" Action 被取消 (例如松开 WASD 键或停止移动左摇杆) 时,调用 OnMoveCanceled 方法。

      • playerInputActions.Player.Jump.performed += OnJumpPerformed; 当 "Jump" Action 被触发 (例如按下空格键或手柄 South Button) 时,调用 OnJumpPerformed 方法。

    • 事件处理方法:

      • OnMovePerformed(InputAction.CallbackContext context) 方法从 context 中读取 Vector2 类型的输入值,赋值给 moveInput 变量。

      • OnMoveCanceled(InputAction.CallbackContext context) 方法将 moveInput 重置为 Vector2.zero。

      • OnJumpPerformed(InputAction.CallbackContext context) 方法在角色处于地面状态时调用 Jump() 方法。

    • Move() 方法: 根据 moveInput 值计算移动方向和距离,使用 Rigidbody.MovePosition() 方法移动角色。

    • Jump() 方法: 给角色施加向上的 Impulse 力,实现跳跃效果,并将 isGrounded 设置为 false。

    • OnCollisionEnter() 方法: 检测角色与地面碰撞,将 isGrounded 设置为 true。

4.2 Mermaid 图表:新输入系统架构


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U