Tone mapping进化论

转载自 https://zhuanlan.zhihu.com/p/21983679

这几年,随着拍摄设备、渲染方法和显示设备的发展,HDR慢慢会成为标配。照相机和摄像机可以捕捉到HDR的影响,渲染过程中可以产生HDR的画面。这些内容如果需要显示到LDR的设备上,就需要一个称为tone mapping的过程,把HDR变成LDR。现在高端的显示器和电视也可以直接显示出HDR的内容。然而和LDR不同之处在于,LDR就是一个确定的范围,HDR是一个非常宽广的概念。即便两个都是HDR的,但它们的范围仍可能不同。因此有人把这个称为Variable Dynamic Range(VDR),可变动态范围,因为此H不一定是彼H。所以,即便在一个HDR世界,也仍然需要tone mapping来改变动态范围。

Tone mapping的过去

实际上tone mapping自古以来一直都有,不是计算机图形学的专利。早期因为颜料的对比度有限,达芬奇等的高手会把需要表达的内容用很有限的颜色画出来,即便色彩不真实。而刚发明电影的时候,胶片能表达的亮度范围有限,所以摄影师会把高亮区域和阴影区域向中等亮度方向压缩,发展出了S曲线的映射关系。这些都是tone mapping。

到了计算机图形学发展的时代,最早只有LDR,颜色就是0-255。这个情况一直持续了很长很长时间。到了90年代后期,软硬件的发展才让HDR变成一个可以考虑的问题。所以HDR转LDR的需求也就浮现出来了。随着2000年之后,GPU、游戏和电影特效的突飞猛进,tone mapping也经历了一系列的进化历程。每一个时期的代表作往往有强烈的流派特征,所以这里就按照流派来总结一下tone mapping。

经验派

直到2002年,有篇论文叫Photographic Tone Reproduction for Digital Images,提出了如何构造一个从HDR映射到LDR的方法。该方法也用其作者的名字命名为Reinhard tone mapping。从下图可以看出,用了tone mapping operator后,图片细节得以保留,而线性映射会造成最亮的区域和最暗的区域信息丢失。注意灯和书本。

 

Reinhard tone mapping非常简单,用代码描述就三行。

 

float3 ReinhardToneMapping(float3 color, float adapted_lum) 
{const float MIDDLE_GREY = 1;color *= MIDDLE_GREY / adapted_lum;return color / (1.0f + color);
}

其中color是线性的HDR颜色,adapted_lum是根据整个画面统计出来的亮度。MIDDLE_GREY表示把什么值定义成灰。这个值就是纯粹的magic number了,根据需要调整。Reinhard的曲线是这样的,可以看出总体形状是个S型。

拿一个KlayGE的场景为例,经过Reinhard tone mapping是这样的。

这种tone mapping的方法更多地来自于经验,没什么原理在后面。所以就姑且称它为经验派吧。它的优点是简单直接,把亮的变暗,暗的变量。这样暗处和亮处细节就都出来了。但缺点也很明显,就是灰暗。个个颜色都朝着灰色的方向被压缩了,画面像蒙了一层纱。

虽然有这样的缺点,但那几年一来用HDR渲染的游戏少,二来大家不知道别的方法,所以就一直用着Reinhard tone mapping。

粗暴派

到了2007年,孤岛危机(Crysis)的CryEngine 2,为了克服Reinhard灰暗的缺点,开始用了另一个tone mapping的方法。前面提到了tone mapping就是个S曲线,那么既然你要S曲线,我就搞出一个S曲线。这个方法更简单,只要一行,而且没有magic number。用一个exp来模拟S曲线。

float3 CEToneMapping(float3 color, float adapted_lum) 
{return 1 - exp(-adapted_lum * color);
}

CE的曲线中间的区域更偏向于小的方向,这部分曲线也更陡。

 

这个方法得到的结果比Reinhard有更大的对比度,颜色更鲜艳一些,虽然还是有点灰。

 

 

CE的方法在于快速,并且视觉效果比Reinhard。但是这个方法纯粹就是凑一个函数,没人知道应该如何改进。属于粗暴地合成。

 

拟合派

到了2010年,Uncharted 2公开了它的tone mapping方法,称为Filmic tone mapping。当年我也写过一篇博客讲KlayGE切换到Filmic tone mapping的事情。这个方法的本质是把原图和让艺术家用专业照相软件模拟胶片的感觉,人肉tone mapping后的结果去做曲线拟合,得到一个高次曲线的表达式。这样的表达式应用到渲染结果后,就能在很大程度上自动接近人工调整的结果。

最后出来的曲线是这样的。总的来说也是S型,但增长的区域很长。

 

从结果看,对比度更大,而且完全消除了灰蒙的感觉。 

 

 

而代码就有点复杂了:

 

float3 F(float3 x)
{const float A = 0.22f;const float B = 0.30f;const float C = 0.10f;const float D = 0.20f;const float E = 0.01f;const float F = 0.30f;return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F;
}float3 Uncharted2ToneMapping(float3 color, float adapted_lum)
{const float WHITE = 11.2f;return F(1.6f * adapted_lum * color) / F(WHITE);
}

那些ABCDEF都是多项式的系数,而WHITE是个magic number,表示白色的位置。这个方法开启了tone mapping的新路径,让人们知道了曲线拟合的好处。并且,其他颜色空间的变换,比如gamma矫正,也可以一起合并到这个曲线里来,一次搞定,不会增加额外开销。缺点就是运算量有点大,两个多项式的计算,并且相除。

因为Filmic tone mapping的优异表现,大部分游戏都切换到了这个方法。包括CE自己,也在某个时候完成了切换。 

你们都是渣渣派

在大家以为Filmic tone mapping会统治很长时间的时候,江湖中来了一位异域高手。他认为,你们这帮搞游戏/实时图形的,都是渣渣。让我们电影业来教你们什么叫tone mapping。这位高手叫美国电影艺术与科学学会,就是颁布奥斯卡奖的那个机构。不要以为他们只是个评奖的单位,美国电影艺术与科学学会的第一宗旨就是提高电影艺术与科学的质量。

他们发明的东西叫Academy Color Encoding System(ACES),是一套颜色编码系统,或者说是一个新的颜色空间。它是一个通用的数据交换格式,一方面可以不同的输入设备转成ACES,另一方面可以把ACES在不同的显示设备上正确显示。不管你是LDR,还是HDR,都可以在ACES里表达出来。这就直接解决了VDR的问题,不同设备间都可以互通数据。

然而对于实时渲染来说,没必要用全套ACES。因为第一,没有什么“输入设备”。渲染出来的HDR图像就是个线性的数据,所以直接就在ACES空间中。而输出的时候需要一次tone mapping,转到LDR或另一个HDR。也就是说,我们只要ACES里的非常小的一条路径,而不是纷繁复杂的整套体系。

那么这条路径有多小呢?只要几行,系数来自于Krzysztof Narkowicz的博客文章。

float3 ACESToneMapping(float3 color, float adapted_lum)
{const float A = 2.51f;const float B = 0.03f;const float C = 2.43f;const float D = 0.59f;const float E = 0.14f;color *= adapted_lum;return (color * (A * color + B)) / (color * (C * color + D) + E);
}

看着很像Uncharted 2的做法吧,都是多项式拟合。但是式子比Uncharted的简单,并不需要算两个多项式并相除,只要算一个,一次搞定。它的曲线是这样的。

 

S感很浓,并且比Uncharted的更向小的方向移,即便很小的数值和接近1的数值也有梯度。这样能很好地保留暗处和亮处的细节。至于视觉效果如何呢?看看这个。

 

 

可以看出来,比之前的任何一个都要鲜艳,并且没有因此丢掉细节!当之无愧成为目前最好的tone mapping算法。

 

更好的地方是,按照前面说的,ACES为的是解决所有设备之间的颜色空间转换问题。所以这个tone mapper不但可以用于HDR到LDR的转换,还可以用于从一个HDR转到另一个HDR。也就是从根本上解决了VDR的问题。这个函数的输出是线性空间的,所以要接到LDR的设备,只要做一次sRGB校正。要接到HDR10的设备,只要做一次Rec 2020颜色矩阵乘法。Tone mapping部分是通用的,这也是比之前几个算法都好的地方。

目前一些新的游戏,比如Rise of the Tomb Raider、UE 4.8,也都切换到ACES的tone mapping曲线。

总结

这里介绍了4种有代表性的tone mapping算法,分别代表了各个时期的发展情况。随着HDR显示设备的不断普及,游戏也会越来越多地需要支持输出HDR。所以从业人员最好在此之前先把前导技术都准备好,以免突然需要切换的时候措手不及。

另外,这个tone mapping算法也可以用在HDR照相的处理上。相机的到HDR的线性数据后,通过这个tone mapping处理,会比传统的Reinhard算法效果好得多。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/246243.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

坐标变换过程(vertex transformation)

原文:https://blog.csdn.net/wangdingqiaoit/article/details/51594408 在上面的图中,注意,OpenGL只定义了裁剪坐标系、规范化设备坐标系和屏幕坐标系,而局部坐标系(模型坐标系)、世界坐标系和照相机坐标系都是为了方便用户设计而…

三灯布光法

原文:https://zhuanlan.zhihu.com/p/62307736?utm_sourcewechat_session&utm_mediumsocial&utm_oi919394520523739136 如果将视频影像比喻成一幅画,光线就是画笔,光影造就了影像画面的立体感。本期圈圈就给大家简单介绍一下视频影像…

齐次坐标

本文是一些关于齐次坐标知识的整合。 https://www.sohu.com/a/258317807_100007727 http://www.songho.ca/math/homogeneous/homogeneous.html https://blog.csdn.net/VenoBling/article/details/87794400 https://www.cnblogs.com/csyisong/archive/2008/12/09/1351372.ht…

Unity SRP自定义渲染管线 -- 4.Spotlight Shadows

英文原文:https://catlikecoding.com/unity/tutorials/scriptable-render-pipeline/spotlight-shadows/ 渲染并且读取纹理从光空间(光源角度)渲染为阴影投射(shadow casters)添加一个着色器pass采样阴影贴图支持软阴影…

Unity SRP自定义渲染管线 -- 5.Directional Shadows

原文:https://catlikecoding.com/unity/tutorials/scriptable-render-pipeline/directional-shadows/ 支持多个方向光阴影控制阴影距离定义独立的主光源渲染和采样级联阴影(cascaded shadow map)使用球形剔除1. Shadows for Directional Lig…

浅析Unity中的Enlighten与混合光照

原文https://www.cnblogs.com/murongxiaopifu/p/8553367.html 0x00 前言 在Unity的5.6版本之前的5.x中,主要使用了Geomerics公司的Enlighten【1】来提供实时全局照明以及烘焙全局照明,在5.6之后Unity引入了新的Lightmapper——Progressive来提供烘焙全…

聊聊LightProbe原理实现以及对LightProbe数据的修改

原文链接https://www.cnblogs.com/murongxiaopifu/p/8997720.html 0x00 前言 最近工作比较忙,所以文章已经很久没有更新了。这篇小文的主题也是在出差的高铁上想到,因为最近和一些朋友聊天,发现他们中很多人的项目中都使用了多个实时光源。…

3D游戏的照明设计理论,第3部分:三点照明法的异端与误区

https://zhuanlan.zhihu.com/p/87997570 这是有关如何处理游戏照明的系列文章的一部分。第一部分是关于灯具,第二部分是关于光的形式材料。 在第一部分中,我们首先从文化角度考虑了灯光-灯光在整个历史上对不同的人意味着不同的事物,并且在照…

3D游戏的照明设计理论,第4部分:如何在游戏引擎中照亮游戏世界

从更一般和更概念的角度来看,这是有关我如何处理游戏照明的系列文章的一部分。我在Unity中构建了大部分示例,但这通常适用于任何3D游戏引擎,其中大多数具有类似的照明工具。 我们开始思考了有关光照的文化和概念,在第一部分。在第…

unity shader 变种(多重编译 multi_compile)

一、定义 在unity中我们可以通过使用#pragma multi_compile或#pragma shader_feature指令来为shader创建多个稍微有点区别的shader变体。这个Shader被称为宏着色器(mega shader)或者超着色器(uber shader)。实现原理:…

AndroidStudio导出aar文件给Unity使用

AndroidStudio导出aar文件给Unity使用 本文参考 :http://www.devacg.com/?post548 Demo地址:https://github.com/JulyNine/AndroidToUnity 一、用Android Studio创建个空工程 注意:包名要与Unity中工程的包名不一致,不然打包时…

Unity C# Job System介绍(四) 并行化Job和故障排除(完结)

并行化job ParallelFor jobs​docs.unity3d.com 当调度Jobs时,只能有一个job来进行一项任务。在游戏中,非常常见的情况是在一个庞大数量的对象上执行一个相同的操作。这里有一个独立的job类型叫做IJobParallelFor来处理此类问题。ParallelFor jobs当调…

C# Job System

概述 设计目的:简单安全地使用多线程,随便就能写出高性能代码 收益:FPS更高,电池消耗更低(Burst编译器) 并行性:C# Job System和Unity Native Job System共享工作线程worker threads&#xf…

Unity游戏开发——C#特性Attribute与自动化

这篇文章主要讲一下C#里面Attribute的使用方法及其可能的应用场景。 比如你把玩家的血量、攻击、防御等属性写到枚举里面。然后界面可能有很多地方要根据这个枚举获取属性的描述文本。 比如你做网络框架的时候,一个协议号对应一个类的处理或者一个方法。 比如你做…

Unity c#中Attribute用法详解

举两个例子,在变量上使用[SerializeFiled]属性,可以强制让变量进行序列化,可以在Unity的Editor上进行赋值。 在Class上使用[RequireComponent]属性,就会在Class的GameObject上自动追加所需的Component。 以下是Unity官网文档中找…

走进LWRP(Universal RP)的世界

走进LWRP(Universal RP)的世界 原文:https://connect.unity.com/p/zou-jin-lwrp-universal-rp-de-shi-jie LWRP自Unity2018发布以来,进入大家视野已经有一段时间了,不过对于广大Unity开发者来说,依然相对…

Unity 2017 Game Optimization 读书笔记(1)Scripting Strategies Part 1

1.Obtain Components using the fastest method Unity有多种Getcomponet的方法&#xff1a; GetComponent(string), GetComponent<T>() GetComponent(typeof(T)) 哪种效率最高会跟随Unity版本的变化而变化&#xff0c;对于Unity 2017&#xff0c;本书作者的测试是Ge…

C# 多态相关的文章

一 C# 多态的实现 封装、继承、多态&#xff0c;面向对象的三大特性&#xff0c;前两项理解相对容易&#xff0c;但要理解多态&#xff0c;特别是深入的了解&#xff0c;对于初学者而言可能就会有一定困难了。我一直认为学习OO的最好方法就是结合实践&#xff0c;封装、继承在…

C++ 虚函数和虚表

几篇写的不错的文章&#xff0c;本文是整合了这几篇文章&#xff0c;感谢这些大佬 https://www.jianshu.com/p/00dc0d939119 https://www.cnblogs.com/hushpa/p/5707475.html https://www.jianshu.com/p/91227e99dfd7 多态: 多态是面相对象语言一个重要的特性,多态即让同一…

Unity 2017 Game Optimization 读书笔记(2)Scripting Strategies Part 2

1. Share calculation output 和上一个Tip很像&#xff0c;可以缓存计算结果或者各种信息&#xff0c;避免多次重复的计算&#xff0c;例如在场景里查找一个物体&#xff0c;从文件读取数据&#xff0c;解析Json等等。 容易忽略的点是常常在基类了实现了某个方法&#xff0c;在…