贵阳市网站建设_网站建设公司_Spring_seo优化
2025/12/20 14:59:06 网站建设 项目流程

Scaling Up and Down这篇教程介绍如何解耦屏幕分辨率与渲染分辨率

  • 支持缩放

  • 支持每个摄像机不同的缩放

  • 在 post fx 之后恢复缩放,避免失真

1 Variable Resolution

程序运行在固定的分辨率下,一些程序允许在运行时更改分辨率,但这需要重新初始化图形设备。一种更灵活的方式是不改变程序分辨率,而是改变摄像机渲染的缓冲区的分辨率。这会影响除了最后绘制到 frame buffer 的整个渲染过程,最后的绘制会执行缩放后匹配程序的分辨率。

缩放 buffer 尺寸可以降低渲染的片段数量,从而提升性能。比如可以以全分辨率渲染UI,而以较低的分辨率渲染3D场景。可以动态的调整缩放,以获得一个能接受的帧率。最后,可以通过增大缩放,来实现超采样,以降低锯齿,该技术就是 SSAA 抗锯齿。

1.1 Buffer Settings

首先向 CameraBufferSettings 中增加一个滑动条来调整 scale,最小值是 0.1,最大值是 2,因为如果用 bilinear 插值缩放回屏幕分辨率,缩放超过2对于提升图像质量就没什么帮助了,甚至由于会跳过很多像素,下采样到屏幕分辨率时,图像质量还会降低。同时默认值设置为 1。

// CameraBufferSettings.cs public class CameraBufferSettings { ... [Range(0.1f,2f)] public float renderScale; } // CustomRenderPipelineAsset.cs public partial class CustomRenderPipelineAsset : RenderPipelineAsset { ... [SerializeField] CameraBufferSettings cameraBuffer = new CameraBufferSettings() { allowHDR = true, renderScale = 1.0f, }; ... }

1.2 Scaled Rendering

在 CameraRenderer 中跟踪是否使用缩放渲染,根据配置的值是否等于 1 来确定。实际上缩放太小的话(比如<1%)基本上没什么效果,因此当差异大于 1% 时才使用缩放渲染。

缩放后,buffer size 和摄像机组件上的 size 不同,因此用 Vector2Int 类型记录下 buffer size。

判定是否缩放渲染后,在 PrepareForSceneWindow 中,如果是 Scene Camera,就关闭缩放渲染。因为 Scene 窗口是用来编辑场景的,不需要缩放。

缩放渲染同样需要我们渲染到中间缓冲区,因此需要设置相关状态。

public void Render(...) { ... useHDR = cameraBufferSettings.allowHDR && camera.allowHDR; // 启用缩放渲染,缩放至少要达到 1% float renderScale = cameraBufferSettings.renderScale; useScaledRendering = renderScale <= 0.99f || renderScale >= 1.01f; if (useScaledRendering) { bufferSize.x = (int)(camera.pixelWidth * renderScale); bufferSize.y = (int)(camera.pixelHeight * renderScale); } else { bufferSize.x = camera.pixelWidth; bufferSize.y = camera.pixelHeight; } PrepareForSceneWindow(); ... } partial void PrepareForSceneWindow() { if (camera.cameraType == CameraType.SceneView) { ScriptableRenderContext.EmitWorldGeometryForSceneView(camera); useScaledRendering = false; } } void Setup() { ... useIntermediateBuffer = useScaledRendering || useDepthTexture || useColorTexture || postFXStack.IsActive; ... }

1.3 BufferSize

创建 color/depth attachment buffer,以及 color/depth texture 时,使用记录的尺寸创建。

... buffer.GetTemporaryRT(colorAttachmentId, bufferSize.x, bufferSize.y, 32, FilterMode.Bilinear, useHDR ? RenderTextureFormat.DefaultHDR : RenderTextureFormat.Default); buffer.GetTemporaryRT(depthAttachmentId, bufferSize.x, bufferSize.y, 32, FilterMode.Point, RenderTextureFormat.Depth); ... void CopyAttachments() { if(useColorTexture) { buffer.GetTemporaryRT(colorTextureId, bufferSize.x, bufferSize.y, 0, FilterMode.Bilinear, useHDR ? RenderTextureFormat.DefaultHDR : RenderTextureFormat.Default); ... if(useDepthTexture) { buffer.GetTemporaryRT(depthTextureId, bufferSize.x, bufferSize.y, 32, FilterMode.Point, RenderTextureFormat.Depth); ... }

现在,可以在关闭 postFX 的情况下,设置 render scale,并在 Game 视口放大,可以清楚的看到像素的变化。

降低 render scale 可以提升渲染速度,降低图像质量。增大 render scale 则相反。需要注意的是,当关闭 postFX 时,render scale 需要专门的中间 buffer,以及额外的绘制,因此需要额外的工作量。

重新缩放到屏幕分辨率是在 final draw 时自动完成的,只是简单的双线性上采样或下采样。唯一奇怪的结果涉及 HDR 值,这些值似乎破坏了插值结果。可以在高亮的黄色球上看到这样的结果,稍后会加以处理。

1.4 Fragment Screen UV

缩放后引入了一个BUG:采样颜色和深度贴图时出错了。通过粒子的扰动可以看到这些错误,这是因为使用了错误的屏幕空间 UV 坐标。

这是因为 Unity 向 _ScreenParams 中写入的是屏幕的分辨率,而不是我们的 buffer 的尺寸。我们定义一个新的 _CameraBufferSize 来传递正确的值,来解决该问题

static int bufferSizeId = Shader.PropertyToID("_CameraBufferSize"); ... public void Render(...) { ... // 让 lighting 作为 SampleName 的子项目 buffer.BeginSample(SampleName); buffer.SetGlobalVector(bufferSizeId, new Vector4(1f/bufferSize.x, 1f/bufferSize.y, bufferSize.x, bufferSize.y)); ExecuteBuffer(); ... }

在 Fragment.hlsl 声明该常量,并在计算屏幕UV时使用我们传入的值。因为我们已经在CPU端计算了 buff size 的倒数,因此在 shader 中改为乘法。

float4 _CameraBufferSize; Fragment GetFragment(float4 positionSS) { ... f.screenUV = positionSS.xy * _CameraBufferSize.xy; ... }

_ScreenParams 的后2个分量,存储了分辨率的倒数+1,这个 1 在其它一些用途下可以节省一个加法运算,我们如果用该值,则需要减 1。

1.5 Scaled Post FX

调整缩放也需要调整 post FX,否则会得到意料之外的结果,因此 post FX 也需要引用 buffer size,将参数传递给 PostFXStack.setup 接口,然后 PostFXStack 将该参数缓存下来。然后在 DoBloom 中使用 buffer size。

Bloom 的效果依赖分辨率,调整 render scale 会改变其结果。在迭代次数少时可以很容易的看到这种结果:降低 render scale 会导致 bloom 效果范围变大,增大 render scale 则范围变小。将 bloom 迭代次数设置到最大,这种差异就不那么明显了,但是调整缩放时导致的分辨率变化,会产生闪烁的效果。

特別是如果 render scale 是逐步调整的,我们希望尽量保证 bloom 效果的稳定性。通过让 bloom 金字塔的底层从摄像机分辨率开始,就是一个可行的办法。因此我们在 BloomSettings 中加入一个选项,来忽略 render scale,以避免这种不希望的效果,在 DoBloom 时根据选项确定 bloom 工作的尺寸

public struct BloomSettings { ... public bool ignoreRenderScale; } ... Vector2Int bufferSize; ... public void Setup(ScriptableRenderContext context, Camera camera, PostFXSettings settings, bool useHDR, Vector2Int bufferSize, CameraSettings.FinalBlendMode finalBlendMode) { this.bufferSize = bufferSize; ... } ... bool DoBloom(int sourceId) { buffer.BeginSample("Bloom"); var bloom = settings.Bloom; int width, height; if(bloom.ignoreRenderScale) { width = camera.pixelWidth; height = camera.pixelHeight; } else { width = bufferSize.x / 2; height = bufferSize.y / 2; } ... // 绘制到结果图像 buffer.GetTemporaryRT(bloomResultId, bufferSize.x, bufferSize.y, 0, FilterMode.Bilinear, format); Draw(fromId, bloomResultId, finalPass); ... }

对比下两张图,上图 ingore render scale = false,不同 render scale 效果不同,下图 ignore render scale = true,不同 render scale 效果基本一致。

1.6 Render Scale per Camera

接下来我们让每个摄像机可以设置自己的 render scale,或者使用 CameraBufferSettings 中的管线全局配置。我们将相关的配置参数定义到 CameraSettings 中:

首先定义摄像机 render scale 配置模式,包括:

  • inherit 使用全局配置

  • multiply 摄像机参数与全局参数相乘作为 render scale

  • override 使用摄像机配置

然后定义 render scale 的值。

同时定义 GetRenderScale 函数,以外部(全局)的 render scale 作为参数,根据 render scale mode 返回相应的 render scale。

public class CameraSettings { ... public enum RenderScaleMode { Inherit, Multiply, Override} public RenderScaleMode renderScaleMode = RenderScaleMode.Inherit; [Range(0.1f,2f)] public float renderScale = 1.0f; public float GetRenderScale(float rpRenderScale) { return renderScaleMode == RenderScaleMode.Inherit ? rpRenderScale : renderScaleMode == RenderScaleMode.Override ? renderScale : rpRenderScale * renderScale; } }

在 CameraRenderer.Render 中,调用上面定义的函数获得 render scale。因为函数内部可能会执行乘法,使结果不在 0.1 - 2 之间,因此调用 clamp 函数确保值的范围:

... float renderScale = cameraSettings.GetRenderScale(cameraBufferSettings.renderScale); useScaledRendering = renderScale <= 0.99f || renderScale >= 1.01f; if (useScaledRendering) { renderScale = Mathf.Clamp(renderScale, 0.1f, 2f); bufferSize.x = (int)(camera.pixelWidth * renderScale); bufferSize.y = (int)(camera.pixelHeight * renderScale); } else ...

如下图,底下的摄像机和上面中间的摄像机,render scale 分别为 0.8 和 1

2 Rescaling

如果设置 render scale 为 1 以外的值,那么所有内容都会被缩放,除了最后绘制到 camera target buffer。

如果没有开启 post FX,则需要一个简单的 copy 来缩放回最终尺寸。如果开启了 post FX,则由其 final draw 来完成回复缩放的操作。然而,通过 final draw 来完成恢复缩放的操作也有一些缺点。

2.1 Current Approach

目前的方法有些缺点:

  • 首先,无论是 upscaling 或 downscaling,对于亮度超过 1 的HDR颜色,总是会走样。插值仅在 LDR 时是平滑的。HDR 插值的结果依然大于1,看起来像是完全没有混合。例如 0 和 10 的平均值是 5,在LDR中 0 和 1 的平均值是 1,而不是我们期望的 0.5。

  • 通过 final pass 恢复缩放的第二个问题是 color correction 被应用在了插值过的颜色上,而不是原本的颜色。这会引入异常的颜色条带。最明显的是在阴影和高光之间插值的中间色调,由于将很强的颜色调整到了中间色调导致非常显眼,比如下图会导致其呈红色。

2.2 Rescaling in LDR

HDR 粗糙的边缘,以及 color correction 走样,都是由于在 color correction 和 tone mapping 前进行HDR颜色插值导致的(导致处理的是插值后的颜色,而不是原本的颜色)。因此解决方案是先执行 color correction 和 tone mapping,再在 LDR 中通过一个 copy pass 恢复缩放。在 PostFXStack.shader 中增加一个 rescale pass 来完成这个步骤。该 pass 也是一个简单的 copy pass,允许配置 blend mode,同时增加 pass 对应的枚举值。现在有两个 final pass,因此需要给 DrawFinal 增加一个参数,指定用哪个 pass。

Pass { Name "Final Rescale" Blend [_FinalSrcBlend] [_FinalDstBlend] HLSLPROGRAM #pragma target 3.5 #pragma vertex DefaultPassVertex #pragma fragment CopyPassFragment ENDHLSL }

检测当没有 scale 时,直接 DrawFinal。

否则需要绘制两次:首先获取一个匹配缩放过的分辨率的 RT 来执行 color grading and tone mapping,也就是 Final Pass,同时混合模式为 One Zero。然后再执行 rescale pass。

void DoColorGradingAndToneMapping(int sourceId) { ... // 没有缩放,直接 draw final if(bufferSize.x == camera.pixelWidth) { DrawFinal(sourceId, Pass.Final); } // 有缩放,现在缩放过的尺寸上进行 color grading and tone mapping,然后在通过 final rescale pass 赋值到 camera target else { buffer.SetGlobalFloat(finalSrcBlendId, 1f); buffer.SetGlobalFloat(finalDstBlendId, 0f); buffer.GetTemporaryRT(finalResultId, bufferSize.x, bufferSize.y, 0, FilterMode.Bilinear, RenderTextureFormat.Default); Draw(sourceId, finalResultId, Pass.Final); DrawFinal(finalResultId, Pass.FinalRescale); buffer.ReleaseTemporaryRT(finalResultId); } buffer.ReleaseTemporaryRT(colorGradingLUTId); }

现在有 render scale 时 HDR 颜色也对了

color grading 也不会导致颜色条带了

我们的方法仅在启用了 post FX 时有效,未启用时我们假定没有 color grading ,也没有 HDR,因此没有上面的问题。

2.3 Bicubic Sampling

当 render scale 过小时,图像会有色块的感觉。我们之前为 bloom 添加 bicubic 上采样选项来改善图像质量。在 rescale 时也可以用该方法来提升质量,添加该 bicubicRescaling 选项到 CameraBufferSettings 中。

在 PostFXStackPasses.hlsl 中,添加对应的shader常量,并根据该常量决定采样方式。修改 shader 中的 Rescale pass 使用该 fragment

bool _CopyBicubic; float4 FinalRescalePassFragment(Varyings intput) : SV_Target { if (_CopyBicubic) return GetSourceBicubic(input.screenUV); else return GetSource(input.screenUV); }

在 PostFXStack.cs 中,用参数传入 CameraBufferSettings 中的参数,并同步给 shader 常量参数

... buffer.SetGlobalFloat(bicubicRescalingId, bicubicRescaling ? 1f : 0f); DrawFinal(finalResultId, Pass.FinalRescale); ...

2.4 Only Bicubic Upscaling

Bicubic rescaling 在上采样时可以提升质量,但是在下采样时,效果就不明显了,因此将 bicubic rescaling 改为三种状态:off,up only,up and down,然后对相关逻辑做调整就可以了

bool isBicubicRescaling = bicubicRescaling == CameraBufferSettings.BicubicRescalingMode.UpAndDown || (bicubicRescaling == CameraBufferSettings.BicubicRescalingMode.UpOnly && bufferSize.x < camera.pixelWidth); buffer.SetGlobalFloat(bicubicRescalingId, isBicubicRescaling ? 1f : 0f);

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询