前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Unity通用渲染管线(URP)系列(十三)——颜色分级(Playing with Colors)

Unity通用渲染管线(URP)系列(十三)——颜色分级(Playing with Colors)

作者头像
放牛的星星
发布2021-01-11 14:30:49
3.9K0
发布2021-01-11 14:30:49
举报
文章被收录于专栏:壹种念头壹种念头壹种念头

目录

· 1 颜色调整

· 1.1 颜色分级在色调映射之前

· 1.2 设置

· 1.3 后曝光

· 1.4 对比度

· 1.5 颜色滤镜

· 1.6 色相偏移

· 1.7 饱和度

· 2 更多的控制

· 2.1 白平衡

· 2.2 分离色调

· 2.3 通道混合

· 2.4 Shadows Midtones Highlights

· 2.5 ACES色彩空间

· 3 LUT

· 3.1 LUT分辨率

· 3.2 渲染到2D LUT纹理

· 3.3 LUT颜色矩阵

· 3.4 Log C LUT

· 3.5 最终的Pass

· 3.6 LUT 条纹

本文重点内容: 1、执行颜色分级 2、复制multiple URP/HDRP颜色分级工具 3、使用颜色LUT

这是有关创建自定义可脚本渲染管道的系列教程的第13部分。这次,我们将添加各种用于颜色分级的工具。

本教程是CatLikeCoding系列的一部分,原文地址见文章底部。

本教程使用Unity 2019.4.10f1制作。

(调整颜色来营造心情)

1 颜色调整

目前,我们仅将色调映射应用于最终图像,以使HDR颜色处于可见的LDR范围内。但这不是调整图像颜色的唯一原因。视频,照片和数字图像的色彩调整大致分为三个步骤。首先是色彩校正,其目的是使图像与观察场景时的图像相匹配,以补偿介质的局限性。其次是颜色分级,即获得与原始场景不匹配且不需要逼真的预期外观。这两个步骤通常合并为一个颜色分级步骤。之后是色调映射,用于将HDR颜色映射到显示范围。

仅应用色调映射的话,除非图像非常明亮,否则图像趋于变少彩色。ACES可以稍微增加深色的对比度,但是不能替代色阶。本教程以neutral色调映射为基础。

(没有颜色调整的图像,neutral色调映射)

1.1 颜色分级在色调映射之前

在色调映射之前进行颜色分级。在色调映射Pass之前,将其函数添加到PostFXStackPasses。最初只将颜色组件限制为60。

在色调映射过程中调用此功能,而不是仅在那个地方限制颜色。添加一个新通道,不进行色调映射,但具有颜色分级。

添加相同的Pass到着色器和PostFXStack。通过枚举,在其他色调映射的Pass之前。然后调整PostFXStack。DoToneMapping使得None模式使用它自己的Pass而不是Copy。

现在,ToneMappingSettings.Mode枚举需要从零开始。

1.2 设置

复制URP和HDRP的Color Adjustments后处理工具的功能。第一步是将其配置结构添加到PostFXSettings中。我使用System进行添加是因为我们需要多次添加Serializable属性。

URP和HDRP的颜色分级功能相同。我们将以相同的顺序添加相同的颜色分级配置选项。首先是Post Exposure,即不受限制的浮动。之后是Contrast,滑块从-100到100。下一个选项是Color Filter,它是没有alpha的HDR颜色。接下来是Hue Shift,它是另一个滑块,但从−180°到+ 180°。最后一个选项是Saturation,也是从-100到100的滑块。

默认值全为零,但color filter应为白色。这些设置不会更改图像。

(颜色调整的设置选项)

我们同时进行颜色分级和色调映射,因此将重命名PostFXStack.DoToneMapping重构为DoColorGradingAndToneMapping。我们还将在此处大量访问PostFXSettings的内部类型,因此让我们使用静态PostFXSettings进行添加以使代码更短。然后添加一个ConfigureColorAdjustments方法,我们在其中获取颜色调整设置并在DoColorGradingAndToneMapping的开始处调用它。

using static有什么作用? 它类似于使用名称空间,但使用的是类型。它可以直接访问类或结构的所有常量,静态和类型成员,而无需完全限定它们。

设置一个着色器矢量和颜色来进行颜色调整。颜色调整矢量分量是曝光,对比度,色相偏移和饱和度。曝光以停止为单位进行度量,这意味着我们必须将曝光值增大为2的幂次方。还将对比度和饱和度转换为0~2范围,并将色相偏移为-1~1。滤镜必须位于线性色彩空间中。

我不会显示附加的着色器属性标识符。

1.3 后曝光

在着色器侧,添加矢量和颜色。我们将所有调整置于自己的功能中,并从后曝光开始。创建一个ColorGradePostExposure函数,将颜色与曝光值相乘。然后在限制颜色之后在ColorGrade中应用曝光。

(post exposure -2和2)

后曝光的原理是,它模仿相机的曝光,但是在所有其他后效果之后,紧接所有其他颜色分级之前应用。这是一种不现实的艺术工具,可用于调整曝光而不会影响其他效果(例如Bloom)。

1.4 对比度

第二个调整是对比度。我们通过从颜色中减去均匀的中间灰度,然后通过对比度进行缩放,然后在中间添加中间灰度来应用它。使用ACEScc_MIDGRAY作为灰色。

什么是ACEScc? ACEScc是ACES颜色空间的对数子集。中间灰度值为0.4135884。

为了获得最佳结果,此覆盖在Log C中,而不是在线性色彩空间中完成。我们可以使用Color Core Library文件中的LinearToLogC函数将线性转换为Log C,然后使用LogCToLinear函数将其转换为LogC。

(线性 和 Log C)

当对比度增加时,这会导致颜色分量变暗,从而使以后的调整变得混乱。因此,请在ColorGrade中调整对比度后消除负值。

(对比度,-50和50)

1.5 颜色滤镜

接下来是颜色滤镜,简单地将其与颜色相乘。它适用于负数,所以我们可以在消除它们之前应用它。

(浅青色滤色镜,消除了大多数红色光)

1.6 色相偏移

URP和HDRP在颜色滤镜之后执行色相转换,我们将使用相同的调整顺序。通过RgbToHsv将颜色格式从RGB转换为HSV,将色相偏移添加到H,然后通过HsvToRgb转换回来,可以调整颜色的色调。由于色相是在0~1的色轮上定义的,因此如果色相超出范围,我们必须将其截断。我以为此使用RotateHue,将调整后的色相,零和1作为参数传递给它。这必须在消除负值之后计算。

(180°色相转换)

1.7 饱和度

最后的调整是饱和度。首先借助亮度功能获得颜色的亮度。然后,像对比度一样计算结果,只是用亮度而不是中间灰度而不是在Log C中计算。这可能再次产生负值,因此请从ColorGrade的最终结果中删除这些值。

(饱和度,-100和100)

2 更多的控制

颜色调整工具不是URP和HDRP提供的唯一颜色分级选项。我们将添加更多支持,再次复制Unity的方法。

2.1 白平衡

白平衡工具可以调节图像的感知温度。它有两个用于−100~100范围的滑块。第一个是温度,用于使图像更冷或更热。第二个是Tint,用于调整温度转换后的颜色。将其设置结构添加到PostFXSettings中,默认值为零。

(白平衡设置)

我们可以使用一个矢量着色器属性就足够了,可以通过从核心库调用ColorUtils.ColorBalanceToLMSCoeffs并传递其温度和色度来获得。在PostFXStack中的专用配置方法中进行设置,并在DoColorGradingAndToneMapping中的ConfigureColorAdjustments之后调用。

在着色器端,我们通过将颜色乘以LMS颜色空间中的矢量来应用白平衡。可以使用LinearToLMS和LMSToLinear函数转换为LMS并返回。在曝光后和对比度之前应用它。

什么是LMS颜色空间? 它将颜色描述为人眼中三种感光锥类型的响应。 低温会使图像变蓝,而温暖温度会使图像变黄。通常使用较小的调整,但我会显示一些极值以使效果显而易见。

(温度 -100 和100)

Tint可用于补偿不希望的色彩平衡,将图像推向绿色或品红色。

(Tint 为-100和100)

2.2 分离色调

分离色调工具用于分别为图像的阴影和高光着色。一个典型的示例是将阴影推向冷蓝色,将高光推向暖橙色。

为它创建一个具有两个LDR颜色(不含alpha)的设置结构,用于阴影和高光。它们的默认值为灰色。还包括一个余额-100~100滑块,默认值为零。

(Split toning 设置)

将两种颜色都发送到PostFXStack中的着色器,将它们保留在伽玛空间中。平衡值可以存储在一种颜色的第四部分中,缩放到-1~1范围。

在着色器端,我们将在近似的伽玛空间中执行分色处理,将颜色先提高到2.2的倒数,再提高到2.2的颜色。这样做是为了匹配Adobe产品的色调。在消除负值之后,在滤镜之后进行调整。

我们通过在颜色和阴影色调之间进行柔光混合,然后再加上高光色调来应用色调。我们可以两次使用SoftLight函数。

我们通过在混合之前在neutral0.5和自身之间插值来将着色限制在各自的区域。对于高光,我们基于饱和亮度加上再次达到饱和的平衡来进行。对于阴影,我们使用相反的方法。

(用蓝色和橙色分割色调,无需进行调整即可进行比较)

2.3 通道混合

我们将支持的另一个工具是通道混合器。它允许你组合输入的RGB值以创建新的RGB值。例如,可以交换R和G,从G中减去B,或将G添加到R中以将绿色推向黄色。

通道混合器本质上是3×3转换矩阵,默认矩阵为单位矩阵。对于红色,绿色和蓝色配置,我们可以使用三个Vector3值。Unity的控件为每种颜色显示一个单独的选项卡,每个输入通道具有−100~100滑块,但是我们将直接直接显示矢量。这些行用于输出颜色,而XYZ列用于RGB输入。

(通道混合器的设置矩阵)

将这三个向量发送到GPU。

并在着色器中执行矩阵乘法。拆分色调后执行此操作。之后,让我们再次消除负值,因为负权重可能会产生负色通道。

(绿色在GB之间分离,B在RGB之间分离)

2.4 Shadows Midtones Highlights

我们将支持的最终工具是Shadows Midtones Highlights。它的工作原理类似于拆分色调,不同之处在于它还可以调整中间调并使阴影和高光区域解耦,从而使它们可配置。

Unity的控件显示了色盘和区域权重的可视化,但是我们将使用三个HDR色域和四个滑块,分别用于阴影的开始和结束以及高光过渡区域。阴影强度从头到尾降低,而高光强度从头到尾增加。我们将使用0~2范围,以便可以稍微进入一点HDR。默认情况下,颜色为白色,我们将使用与Unity相同的区域默认值,阴影的默认区域设置为0~0.3,高光的默认区域设置为0.55~1。

为什么我们不能使用色盘? Unity没有可包含在编辑器中的默认色盘编辑器部件。URP和HDRP都包含自己的(尽管等效)版本。区域的GUI也是自定义的。

将这三种颜色发送到GPU,转换为线性空间。区域范围可以打包在单个向量中。

在着色器中,我们将颜色分别乘以三种独立的颜色,每种颜色按其自身的权重进行缩放,对结果求和。权重基于亮度。使用smoothstep函数,阴影权重从1开始并在其开始和结束之间减小到零。高亮显示的权重从零增加到一。中间调权重等于一个减去其他两个权重。这个想法是阴影和高光区域不会重叠(或只有一点),因此中间色调的重量永远不会变为负值。但是,我们不会在检查器中强制执行此操作,就像我们不强制让开始一定在结束前面一样。

(蓝色的阴影,分色的中间调,黄色高光)

Unity控件的色盘工作相同,除了它们限制输入的颜色并允许更精确的拖动之外。在没有限制的情况下,使用HVS颜色选择器模式调整颜色以某种程度上模仿此功能。

Color Curves工具和Lift Gamma Gain工具呢? Color Curves是一个功能强大的工具,可用于多种效果,包括使除单一颜色之外的所有颜色均饱和。但是,它依赖于自定义曲线编辑器,需要重做很多工作。因此,它不在本教程中。

2.5 ACES色彩空间

使用ACES色调映射时,Unity在ACES颜色空间而不是线性颜色空间中执行大多数颜色分级,以产生更好的结果。让我们也这样做。

曝光后和白平衡始终应用于线性空间。对比之处在于差异。将一个布尔useACES参数添加到ColorGradingContrast。如果使用ACES,请先从线性转换为ACES,然后再转换为ACEScc颜色空间,而不是LogC。我们可以通过unity_to_ACES和ACES_to_ACEScc进行转换。调整对比度之后,通过ACEScc_to_ACES和ACES_to_ACEScg转换为ACEScg,而不是返回线性空间。

什么是ACEScg? ACEScg是ACES颜色空间的线性子集。

从现在开始,在进行颜色分级的对比度步骤之后,我们将进入线性或ACEScg颜色空间。除了应使用ACEScg空间中的AcesLuminance计算亮度外,其他所有操作仍然相同。介绍一个亮度函数变量,该变量根据是否使用ACES来调用正确的函数。

ColorGradeSplitToning使用亮度,为其赋予useACES参数并将其传递给Luminance。

对ColorGradingShadowsMidtonesHighlights执行相同的操作。

以及ColorGradingSaturation。

然后将参数也添加到ColorGrade,这次默认设置为false。将其传递给需要它的功能。适当时,应通过ACEScg_to_ACES将最终颜色转换为ACES颜色空间。

现在调整ToneMappingACESPassFragment,以表明它使用ACES。由于ColorGrade的结果将位于ACES颜色空间中,因此可以直接传递给ACESTonemap。

为了说明二者的不同之处,这里是使用ACES色调映射进行的比较,该对比度具有增加的对比度和调整的阴影,中间色调和高光。

(ACES色彩映射,颜色分级分别在ACES和线性色彩空间)

3 LUT

每个像素执行所有颜色分级步骤是很多工作。我们可以制作可能仅适用于更改某些内容的步骤的变体,但这需要大量的关键字或通道。相反,我们可以做的是将颜色分级烘焙到查找表(简称LUT)中,并对其进行采样以转换颜色。LUT是3D纹理,通常为32×32×32。与直接对整个图像进行颜色分级相比,填充该纹理并在以后对其进行采样要少得多。URP和HDRP使用相同的方法。

3.1 LUT分辨率

通常,颜色LUT分辨率为32就足够了,但让我们对其进行配置。这是一种质量设置,将其添加到CustomRenderPipelineAsset,然后用于所有颜色分级。我们将使用一个枚举提供16、32和64作为选项,然后将其作为整数传递给管道构造函数。

为什么不允许任意分辨率? URP和HDRP允许高达65的任意LUT分辨率,但是当我们将使用的方法不使用2的幂时,LUT采样会出错。URP也受此困扰。

在CustomRenderPipeline中追踪颜色LUT分辨率,并将其传递给CameraRenderer.Render方法。

它将被传递给PostFXStack.Setup。

PostFXStack会对其进行持有。

(颜色LUT 分辨率)

3.2 渲染到2D LUT纹理

LUT是3D的,但常规着色器无法渲染3D纹理。因此,通过将2D切片连续放置,我们将使用宽的2D纹理来模拟3D纹理。因此,LUT纹理的高度等于配置的分辨率,其宽度等于分辨率的平方。使用默认的HDR格式,获得具有该大小的临时渲染纹理。在DoColorGradingAndToneMapping中配置颜色分级后,执行此操作。

从现在开始,我们将把颜色分级和色调映射都渲染到LUT。重命名现有的色调映射通道,以便ToneMappingNone变为ColorGradingNone,依此类推。然后使用适当的传递绘制到LUT而不是摄影机目标。然后,将信号源复制到摄像机目标,以获取未经调整的图像作为最终结果,并释放LUT。

现在,我们绕过了颜色分级和色调映射,但是帧调试器显示我们在最终副本之前绘制了图像的扁平版本。

(扁平化的图像)

3.3 LUT颜色矩阵

为了创建合适的LUT,我们需要用颜色转换矩阵填充它。为此,我们可以通过调整色阶Pass 函数来使用从UV坐标派生的颜色,而不是对源纹理进行采样。添加一个GetColorGradedLUT,它将获取颜色并立即执行颜色分级。然后,Pass函数仅需在其上应用色调映射。

我们可以通过GetLutStripValue函数找到LUT输入颜色。它需要UV坐标和我们需要发送到GPU的颜色分级lut参数向量。

四个矢量参数值分别是LUT高度,0.5除以宽度,0.5除以高度以及高度除以其自身再减去一。

(LUT 没有颜色分级)

(LUT 没有ACES)

(LUT Reinhard 色调映射)

URP不会单独进行色调映射吗? URP将颜色分级和色调映射都烘焙到LUT中以进行HDR渲染,但单独进行色调映射以进行LDR渲染。但是,色调映射对于LDR渲染没有多大意义,因此我没有对其进行特殊处理。

3.4 Log C LUT

我们获得的LUT矩阵位于线性颜色空间中,仅覆盖0~1范围。为了支持HDR,我们需要扩展此范围。可以通过将输入颜色解释为Log C空间来实现此目的。范围扩大到略低于59。

(存储后在线性和LogC色彩空间下的强度)

(Log C颜色 没有色调映射)

(Log C颜色 ACES 色调映射)

(Log C颜色 Reinhard色调映射)

与线性空间相比,Log C为最暗的值增加了更多分辨率。它超过了大约0.5的线性值。之后,强度迅速上升,因此矩阵分辨率下降很多。覆盖HDR值是必需的,但是如果我们不需要这些值,则最好保留线性空间,否则将浪费几乎一半的分辨率。向着色器添加布尔值以控制此值。

仅当使用HDR并应用了色调映射时,才启用Log C模式。

因为我们不再依赖渲染的图像,所以我们不再需要将范围限制为60。它已经受到LUT范围的限制。

我们仍然需要为LUT传递指定源纹理吗? 我们不使用它,但是Draw需要一个源,因此我们仍然必须传递一些东西过去。

3.5 最终的Pass

为了应用LUT,我们引入了一个新的最终Pass。它需要做的就是获取源颜色并对其应用颜色分级LUT。在单独的ApplyColorGradingLUT函数中执行此操作。

我们可以通过ApplyLut2D函数应用LUT,该函数负责将2D LUT条解释为3D纹理。它需要LUT纹理和采样器状态作为参数,然后是饱和的输入颜色(适当时在线性或Log C空间中),最后又是参数向量,这次只有三个分量。

在这种情况下,参数值是一个除以LUT宽度,一个除以高度,以及高度减一。现在,使用最终Pass在最终Draw之前设置它们。

我们是否需要在每帧重新创建LUT? 仅对LUT纹理进行颜色分级和色调映射比对渲染图像的所有像素分别进行的工作要少得多。进一步的优化将是缓存LUT。但是,确定是否需要刷新LUT会变得很复杂,尤其是当支持每个摄像机的不同设置或混合设置时。因此,我们坚持每次渲染摄像机时都重新创建LUT的简单方法。URP和HDRP也可以这样做。

3.6 LUT 条纹

尽管我们现在使用LUT进行颜色分级和色调映射,但结果应与以前相同。但是,由于LUT具有有限的分辨率,并且我们使用双线性插值对其进行采样,因此它将平滑的颜色过渡转换为线性带。对于分辨率为32的LUT,这通常并不明显,但是在具有极端HDR颜色渐变的区域中,条纹可能变得可见。一个示例是上一教程的色调映射场景中强度为200的聚光灯的衰减,该照明照亮了均匀的白色表面。

(颜色条纹 LUT分辨率为16和32)

通过临时切换到sampler_point_clamp采样器状态,可以非常明显地看到带区。这关闭了LUT的2D切片内部的插值。相邻切片之间仍然存在插值,因为ApplyLut2D通过对两个切片进行采样并在它们之间进行混合来模拟3D纹理。

(点采样,LUT分辨率为16和32)

如果条纹太明显,则可以将分辨率提高到64,但是通常只需要稍微改变一下颜色就可以隐藏它。如果你需要寻找非常细微的颜色过渡中的条纹失真,则由于8位帧缓冲区限制,你更有可能找到条纹,这不是LUT引起的,可以通过抖动减轻,但这是另一个主题。

下一章,多摄像机。

欢迎扫描二维码,查看更多精彩内容。点击 阅读原文 可以跳转原教程。

本文翻译自 Jasper Flick的系列教程

原文地址:

https://catlikecoding.com/unity/tutorials

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-12-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 壹种念头 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档