Version: 2019.3
遮挡入口 (Occlusion Portal)
摄像机参考

动态分辨率

动态分辨率是一种摄像机设置,允许动态缩放单个渲染目标,以便减少 GPU 上的工作负载。在应用程序的帧率降低的情况下,可以逐渐缩小分辨率来保持帧率稳定。如果性能数据表明由于应用程序受 GPU 限制而导致帧率即将降低,则 Unity 会触发此缩放。还可以手动触发缩放,方法是抢占应用程序中 GPU 资源消耗特别高的部分,并通过脚本控制缩放。如果缩放逐渐进行,动态分辨率几乎不可察觉。

支持的平台

Unity 在 Xbox One、PS4、Nintendo Switch、iOS、macOS 和 tvOS(仅限 Metal)、Android(仅限 Vulkan)、Windows 独立平台和 UWP(仅限 DirectX 12)上支持动态分辨率。

对渲染目标的影响

使用动态分辨率,Unity 不会重新分配渲染目标。从概念上讲,Unity 会缩放渲染目标;但实际上,Unity 使用锯齿,而且缩小的渲染目标仅使用原始渲染目标的一小部分。Unity 以全分辨率分配渲染目标,然后动态分辨率系统会缩小渲染目标并再次备份,使用的是原始目标的一部分而不是重新分配新目标。

缩放渲染目标

使用动态分辨率时,渲染目标具有 DynamicallyScalable 标志。可通过设置此标志来指明 Unity 是否应该将此渲染纹理作为动态分辨率过程的一部分进行缩放。摄像机还具有 allowDynamicResolution 标志,使用该标志可以这样设置动态分辨率:在只想将动态分辨率应用于不太复杂的场景时,无需覆盖渲染目标。

MRT 缓冲区

在 Camera 组件上启用 Allow Dynamic Resolution 时,Unity 会缩放该摄像机的所有目标。

控制缩放

可以通过 ScalableBufferManager 控制缩放。借助 ScalableBufferManager,可以控制已标记由动态分辨率系统进行扩展的所有渲染目标的动态宽度和高度缩放。

例如,假设应用程序以理想的帧率运行,但在某些情况下,由于粒子增多、后期效果和屏幕复杂性等多重因素的影响,GPU 性能会下降。Unity FrameTimingManager 可以检测 CPU 或 GPU 性能何时开始下降。因此,可使用 FrameTimingManager 计算新的所需宽度和高度缩放以将帧率保持在所需范围内,并将缩放降低到该值以保持性能稳定(立即进行或在设定的帧数内逐渐进行)。当屏幕复杂度降低并且 GPU 性能稳定时,可将宽度和高度缩放提高到事先计算出的 GPU 可以处理的值。

示例

以下示例脚本演示了 API 的基本用法。将该脚本添加到场景中的摄像机,然后在 Camera 设置中选中 Allow Dynamic Resolution。还需要打开 Player 设置(菜单:__Edit > Project Settings__,然后选择 Player 类别)并选中 Enable Frame Timing Stats 复选框。

单击鼠标或用一根手指点击屏幕,分别将高度和宽度分辨率降低 scaleWidthIncrementscaleHeightIncrement 变量中的大小。用两根手指点击会以相同的增量提高分辨率。

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

public class DynamicResolutionTest : MonoBehaviour
{
    public Text screenText;

    FrameTiming[] frameTimings = new FrameTiming[3];

    public float maxResolutionWidthScale = 1.0f;
    public float maxResolutionHeightScale = 1.0f;
    public float minResolutionWidthScale = 0.5f;
    public float minResolutionHeightScale = 0.5f;
    public float scaleWidthIncrement = 0.1f;
    public float scaleHeightIncrement = 0.1f;

    float m_widthScale = 1.0f;
    float m_heightScale = 1.0f;

    // 跨帧保持的动态分辨率算法变量
    uint m_frameCount = 0;

    const uint kNumFrameTimings = 2;

    double m_gpuFrameTime;
    double m_cpuFrameTime;

    // 使用此函数进行初始化
    void Start()
    {
        int rezWidth = (int)Mathf.Ceil(ScalableBufferManager.widthScaleFactor * Screen.currentResolution.width);
        int rezHeight = (int)Mathf.Ceil(ScalableBufferManager.heightScaleFactor * Screen.currentResolution.height);
        screenText.text = string.Format("Scale: {0:F3}x{1:F3}\nResolution: {2}x{3}\n",
            m_widthScale,
            m_heightScale,
            rezWidth,
            rezHeight);
    }

    // 每帧调用一次 Update
    void Update()
    {
        float oldWidthScale = m_widthScale;
        float oldHeightScale = m_heightScale;

        // 一根手指降低分辨率
        if (Input.GetButtonDown("Fire1"))
        {
            m_heightScale = Mathf.Max(minResolutionHeightScale, m_heightScale - scaleHeightIncrement);
            m_widthScale = Mathf.Max(minResolutionWidthScale, m_widthScale - scaleWidthIncrement);
        }

        // 两根手指提高分辨率
        if (Input.GetButtonDown("Fire2"))
        {
            m_heightScale = Mathf.Min(maxResolutionHeightScale, m_heightScale + scaleHeightIncrement);
            m_widthScale = Mathf.Min(maxResolutionWidthScale, m_widthScale + scaleWidthIncrement);
        }

        if (m_widthScale != oldWidthScale || m_heightScale != oldHeightScale)
        {
            ScalableBufferManager.ResizeBuffers(m_widthScale, m_heightScale);
        }
        DetermineResolution();
        int rezWidth = (int)Mathf.Ceil(ScalableBufferManager.widthScaleFactor * Screen.currentResolution.width);
        int rezHeight = (int)Mathf.Ceil(ScalableBufferManager.heightScaleFactor * Screen.currentResolution.height);
        screenText.text = string.Format("Scale: {0:F3}x{1:F3}\nResolution: {2}x{3}\nScaleFactor: {4:F3}x{5:F3}\nGPU: {6:F3} CPU: {7:F3}",
            m_widthScale,
            m_heightScale,
            rezWidth,
            rezHeight,
            ScalableBufferManager.widthScaleFactor,
            ScalableBufferManager.heightScaleFactor,
            m_gpuFrameTime,
            m_cpuFrameTime);
    }

    // 估算下一帧时间并在必要时更新分辨率缩放。
    private void DetermineResolution()
    {
        ++m_frameCount;
        if (m_frameCount <= kNumFrameTimings)
        {
            return;
        }
        FrameTimingManager.CaptureFrameTimings();
        FrameTimingManager.GetLatestTimings(kNumFrameTimings, frameTimings);
        if (frameTimings.Length < kNumFrameTimings)
        {
            Debug.LogFormat("Skipping frame {0}, didn't get enough frame timings.",
                m_frameCount);

            return;
        }

        m_gpuFrameTime = (double)frameTimings[0].gpuFrameTime;
        m_cpuFrameTime = (double)frameTimings[0].cpuFrameTime;
    }
}

另请参阅


  • 2018–09–20 页面已发布

  • 在 2017.4 版中添加了有关动态分辨率的文档

  • 在 2019.1 中添加了对 macOS(仅限 Metal)、Windows 独立平台和 UWP(仅限 DirectX 12)的动态分辨率支持

遮挡入口 (Occlusion Portal)
摄像机参考