转载请注明出处为KlayGE游戏引擎,本文的永久链接为http://www.klayge.org/?p=3602

上一篇总结了SMAA和Clustered shading这两个改进,本篇将讲一下如何支持HDR显示设备,以及一些性能优化。

HDR显示设备

HDR电视已经不是什么新鲜事了,HDR显示器也已经问世。这些HDR显示设备,能达到的最大亮度远超过传统显示器。不过这里存在很大的误解,很多人以为HDR显示器等于在显示器里做tone mapping。然而并不是。HDR显示器并不需要做额外处理,直接拿HDR数据去显示。而tone mapping等,仍然是在程序里做。

传统的后处理流程

原先KlayGE里的post process是这样的。

HDR pp

HDR的渲染结果进入ACES tone mapping,变成LDR的数据,在经过SMAA、gamma矫正和颜色调整后输出到LDR显示设备。

HDR显示设备的重要参数

首先需要提一下HDR显示设备里的几个参数。第一是paper white,表示白色的亮度,单位为尼特(nits)。第二个是max luminance,表示显示设备能达到的最大亮度,单位也是nits。在LDR设备上,paper white和max luminance是一样的,一般是100 nits。而在HDR显示设备上,paper white还是100左右,max luminace往往可以到400,甚至1000。这个需要根据具体设备来调整,但用400其实没啥问题。

DXGI方面的设置

需要支持HDR输出,第一个需要修改的是back buffer的格式。这里可以用10.10.10.2或者16.16.16.16fp的格式,DXGI就会知道你要输出一个HDR的back buffer。

之后,还需要把输出的颜色空间设置对。这需要Windows 10 v1703以上。

IDXGISwapChain4* sc4;
if (SUCCEEDED(swap_chain->QueryInterface(IID_IDXGISwapChain4, reinterpret_cast<void**>(&sc4))))
{
    UINT color_space_support;
    if (SUCCEEDED(sc4->CheckColorSpaceSupport(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020, &color_space_support))
        && (color_space_support & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT))
    {
        sc4->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
    }

    sc4->Release();
}

经过了这些步骤,DXGI就会把back buffer的数据通过HDR10的格式发送给HDR显示设备。

针对HDR显示设备的后处理

如果要输出到HDR显示设备,实际上和之前的流程没太大区别。首先,tone mapping仍然用ACES的,但不是从HDR给tone mapping成LDR,而是从一个HDR给tone mapping成另一个HDR范围。这个也经常被称为VDR,可变动态范围。这个ACES tone mapping按理说应该用不同的一组系数,但我目前只是简单地在原有系数的基础上乘上一个scale。这个scale是这么算出来的:

hdr_rescale = log2(1 – N * paper_white / max_luminance) / log2(1 – N);

其中N = 0.25。

经过tone mapping之后,照常做SMAA,但不需要做gamma correction。Color grading需要特别调整,原先的查找表不一定还能用。

UI的混合

在LDR的流程里,一般是在最后才和UI在back buffer里,通过硬件混合。而现在则需要自己在shader里完成这个混合,因为UI仍然是LDR的,范围在paper white之内。而渲染数据则在max luminance之内,可能大于paper white。并且,两者混合后的结果需要在P2020颜色空间。

        float4 color = color_tex.Sample(linear_sampler, tc);
	float4 overlay = overlay_tex.Sample(linear_sampler, tc);
	overlay.rgb = SRGBToLinear(overlay.rgb) * paper_white;

	color.rgb = color.rgb * max_luminance;
	color.rgb = lerp(color.rgb, overlay.rgb, overlay.a);

	return float4(LinearToSt2084(Rec709ToRec2020(color.rgb) / 10000.0), 1);

经过这样的混合和颜色空间转换,输出的数据就能符合HDR10的要求。引擎渲染所得的HDR数据就可以直接送到HDR显示设备上,得到超过LDR的鲜亮颜色。但需要注意的是,因为很多HDR电视会针对LDR输入做特别的颜色优化,而对HDR数据则不处理。结果经常发现LDR的输入反而更“好看”。

KlayGE很可能成为了第一个支持HDR显示设备的开源引擎。

性能优化

ShaderObject的重构

之前RenderEffect已经有过一次重构,把RenderEffect拆出了一个RenderEffectTemplate的类,用来存放无状态的成员变量。这样,来自同一个fxml的不同的RenderEffect对象可以共享同一个RenderEffectTemplate。以达到降低内存消耗和提高速度的目的。

这次ShaderObject也采用了同样的思想进行重构。不过因为ShaderObject类之内没有无状态成员,真正的内容都在各个子类里。所以这次没办法从基类进行重构,只能把D3D11/D3D12/OpenGL/OpenGLES插件里的ShaderObject拿来做这样的重构。得到的好处有,但小于之前对RenderEffect的重构。

基于任务的渲染框架

KlayGE里的Deferred rendering框架是在每一帧的第一个pass里,生成一个任务队列,用一个编码表示任务的类型和ID。之后的每个pass解码出这些信息后,在一个很大的switch case里调用具体的渲染代码。这个编码解码和分支,其实完全是个浪费。现在把它改成一个抽象的任务基类和各种任务类型的派生类。任务队列里直接就存着任务对象,每个pass直接调用任务即可。省去了这些没必要的浪费,同时代码也变的更清晰易读。

D3D12插件的优化

虽然KlayGE挺早就有D3D12插件了,但一直只是能用而已,并没有和其他渲染插件一样花时间和精力去做好。在Win10 v1703上,debug模式里可以看到存在大量错误信息,其中大部分来自于资源的状态有错。原先的做法是,当需要用到一个资源的时候,就把它设置成所需的状态,用完再设置回去。但这有两个问题。一是很容易遗漏,一旦漏了某个地方,就会体现在debug的出错信息里。另一个问题是状态设置并不快,这么大量频繁地变换状态,会造成明显的性能下降。

这次的修改增加了一个D3D12Resource的基类,buffer和texture都从那里派生出来。在D3D12Resource内跟踪每一个subresource的状态,只要在使用前把需要修改的改了就行。这样修改状态的次数大大减少,D3D12插件的性能也提升了近1倍。

总结

本系列简单地描述了一下KlayGE 4.11里渲染系统的改进。新版本会很快发布,敬请期待。