中文第一计算机图形学社区OpenGPU 版权所有2007-2018

 找回密码
 注册

扫一扫,访问微社区

搜索
查看: 6985|回复: 53

老师不教的图形学知识系列

[复制链接]
发表于 2016-3-12 15:12:52 | 显示全部楼层 |阅读模式
老师不教的图形学知识系列

第一回 人眼对图像误差的敏感性

技术总是有取舍的,为了速度,有时不得不牺牲质量。早期的计算机图形,分辨率和色彩数都非常有限,比如在640x480 16色和320x240 256色两种模式间取舍,应该选哪一个呢?实验结果是,对照片图像,人眼对颜色比分辨率更敏感。在16位色(65535色)出现后,很快就成了必然的选择。

但32位色出现后,人们又纠结了,因为和16位色的效果差异并不明显,但数据量和带宽却翻倍了。不过,纠结的时间并不长,因为16位色虽然对照片看不太出差别,但计算机渲染的图像有很多平滑的颜色渐变,在16位色下会表现出明显的颜色变化阶梯。于是,32位色成了必然的选择。

另一种经典的误差是3D渲染几何面片的边缘锯齿。这个基本就是为了性能而作的牺牲。 在过去的低分辨率时代,3D的像素块是典型特征,也是2D游戏仍然可以在图像质量上碾压3D的因素之一。之后,3D硬件引入了MSAA。这是一种类似离线软件渲染使用的super-sampling,但又只对三角形边界线上的像素做super-sampling的优化,因此性能上比super-sampling好很多,但frame buffer的存储需求一点都没有省。但如果仅用来平滑模型边缘,这个代价还是很大的。

有人曾认为,当显示器分辨率大幅提高后,边缘锯齿就不再明显,就不用特别处理了。但事实却不是这样,即使高分辨率下,人眼依然能快速发现锯齿。因为,人眼对颜色和亮度的快速变化最敏感,而边缘锯齿恰好是颜色亮度跳变的地方。尤其对于动画,运动的边界锯齿线非常吸引注意力。

另一方面,由于各种deferred渲染技术的兴起,以及其难于和MSAA结合的特点,人们开始探索在不增加采样的图像层面做选择性模糊,来平滑边缘锯齿。这些方法虽然不及MSAA的质量,但在如今的高分辨率显示下,质量提升完全够用,且性能比MSAA好很多,大约已经成了必然选择。


 楼主| 发表于 2016-4-12 20:32:32 | 显示全部楼层
第八回 矩阵

一个经典的误解就是认为OpenGL是列矩阵而DX是行矩阵。其实两者核心功能都和矩阵类型无关。尤其是OpenGL,其一开始就是被特意设计成同时兼容行矩阵和列矩阵的。原因在于,当年的数学库大都是行矩阵,因为CPU处理行矩阵更高效;而数学则习惯上用列矩阵,人们希望用更接近数学的方式描述。所以GL规范特意选择了列优先存储的内存布局,这样既可以用列主序矩阵作规范说明,又可以直接使用行主序、行优先存储的矩阵的内存指针作为输入,因为其内存布局恰好等价。

在支持了dot指令的较新的CPU上,列矩阵的主要操作的性能已经可以匹敌行矩阵。但软件行业对CPU新指令的使用普遍有n多年的延迟,比如古老的SSE2指令如今才普遍使用。再加上大量经典软件、数学库是行矩阵,所以也很难有大的变化。只有GPU这边,其指令集对列矩阵在性能上更有优势。

因为OpenGL规范的影响力,有些不明真相的群众以列矩阵为真理,却实现了行优先存储的列矩阵的数学库。这种设计不但性能不好,而且调用OpenGL时还得转置一下。

左手系矩阵和右手系矩阵,是另一个常见的被误解的地方。其实,只有投影矩阵区分左手系和右手系,其他矩阵对左右手系都是一样的,没区别。早期DX是以左手系为标准,自版本8以来就不管左右手了。现在基本都选右手系。

只包含旋转变换的矩阵,有个非常好的特性,它的逆矩阵和它的转置矩阵式一样的,所以可以通过转置来实现非常高效的求逆。
 楼主| 发表于 2016-3-23 20:31:59 | 显示全部楼层
外传 谈理想

这个比较难理解:理想的实现不取决于自己,而取决于别人。

投入这一行,大多抱着理想,想着做个牛叉的或引擎、或游戏、或渲染器等等,而后现实很骨感,甚至终老无所成。有不少技术不错的人,有着自己独到的私人作品,而却怀才不遇。问题在于,你需要别人的成功,来成就自己的成功。

你创造了牛叉的技术,则需要别人的引擎使用且成功;你创造了牛叉的引擎,需要别人使用且做出成功的游戏;你创造了牛叉的游戏,需要别人运营且成功赚到了钱。如果和你相关的人没有成功的能力和福分,也必然没有你的份。反过来,如果他们有这份能力和福分,没有你他们也一样能成功。即便他们缺少牛叉的图形技术,他们也能以出色的设计,让古董级图形技术发挥光彩,以出色的推广运营,让产品名气大作。

那么,自己不成功的原因就是没找到好的合作者么?这一点,反而取决于自己。须知,同气相投。自己是个失败者,则更容易遇上失败者;埋怨别人的,也多被别人埋怨。所以,当从修缮自身开始。

故而,若要实现自己理想,当帮助别人实现理想,别人成功,即是自己成功,此即无为之用。
发表于 2016-3-12 16:07:50 | 显示全部楼层
LZ继续哈哈,我也想开一个这种系列
发表于 2016-3-12 23:48:03 | 显示全部楼层
归根结底人脑不能实时处理整个视网膜输入只能输入曲线色块之类图元(体现为视线焦点的扫描过程)。
输入过程还是不可编程管线控制的。线条等基本图元的敏感度非常高。
发表于 2016-3-13 05:50:18 | 显示全部楼层
Jedimaster 发表于 2016-3-12 16:07
LZ继续哈哈,我也想开一个这种系列

你俩一起弄啊
 楼主| 发表于 2016-3-13 08:52:35 | 显示全部楼层
第二回 颜色是不平等的

大家都知道,色光的三原色是红绿蓝,所以像素的颜色就是这三个分量组成。但这三个纯色的亮度其实是不同的,具体差别和显示器硬件有关,但一般软件上假定红绿蓝的亮度比是 30:59:11。绿色最亮,而蓝色最暗。所以可知,HSV或HSL这颜色模型的亮度分量,其实是不靠谱的,只能做近似模型。

颜色其实是个非常复杂的领域,不同东西颜色展示范围都不一样,显示器和打印机的颜色范围不一样,不同显示器的颜色范围也不一样,同一款显示器在不同的设置下颜色也不一样,同样设置下但环境光不同的情况下也不一样,甚至完全一样的配置和环境下,不同人看到的也不一样。所以会出现,明明在自己电脑上配的妥妥的颜色,到别人电脑上显示得跟屎一样的问题。

传统上,业界主要用gamma值来表示这个差别。gamma值只是表示亮度随RGB的值的变化曲线。这种方法比较粗糙,但早期基本够用。但很快PC就统一到sRGB标准的2.2的gamma值(Mac是1.8),准确的说是2.2左右,不同显示器还是有差别的。注意sRGB不是线性的。更细化一点,RGB三个分量的gamma也会不一样,所以就有了三个分量gamma的用法。再往后,映射关系更加复杂,于是便有了显示器的color profile。但是,除了专业人士,很少有用户在意自己的color profile是否真的和显示器匹配。所以对于美工而言,请一定把你们的显示器准确调校到sRGB。

gamma值不为1,也就意味着亮度并不是随着RGB值线性增长,比如128的亮度并不是64的两倍。所以,当对图像的处理需要亮度的线性变化时,我们就需要线性RGB颜色空间。sRGB之所以被设计成非线性的2.2的gamma,是因为人眼对暗色的分辨力明显强于亮色,所以分配更多的位给暗色。由于sRGB的定义中还有一个线性的中间转换步骤,有人误解成sRGB是线性的,这种错误希望不要再继续传播。

一般肉眼看这个世界,看到的大部分颜色是反光色,而显示器的颜色范围,包含较多的发光色。比如RGB的纯色红绿蓝,都是发光色,用他们着色的物体,都会感觉不像自然的东西,有些扎眼。发光色合理出现的地方,是灯、火、闪耀的高光反射、光晕等地方。在打印的时候,这些发光色都是无法原样打印出来的,都会变成暗淡一些的最接近的反光色。

因为人眼对颜色亮度的感受和环境有关,所以High Dynamic Range也成了一个重要方向。HDR要用更多的位数来记录颜色分量的亮度值,在显示时根据需要部分地映射到普通的RGB值域,以便模拟在不同光线环境下的人眼感受。
发表于 2016-3-13 21:35:27 | 显示全部楼层
目前的Post Effect AA对于一个像素宽的线框处理得不好啊,所以工业软件很难用上这种AA,还是得用MSAA。

 楼主| 发表于 2016-3-14 13:22:24 | 显示全部楼层
MagicRen 发表于 2016-3-13 21:35
目前的Post Effect AA对于一个像素宽的线框处理得不好啊,所以工业软件很难用上这种AA,还是得用MSAA。

...

线框反锯齿不用这种方法,太浪费。
用一个包围线的矩形,和一个三个像素的纹理,三个像素分别为 透明、白、透明。
 楼主| 发表于 2016-3-14 20:08:47 | 显示全部楼层
第三回 D3D和GL里的线性RGB支持是如何工作的

首先,如果纹理被指定为sRGB,则硬件纹理采样单元在读到像素值后,将其转换到线性RGB,再插值,传给shader。也就是说shader里拿到的是线性RGB,alpha分量不受影响。其次,如果RenderTarget被制定为sRGB,则pixel shader的输出会被当作线性RGB,而被从线性RGB转换到sRGB。以上两点组合起来,就是线性RGB的完整流程。

另外,D3D9的sRGB支持不靠谱。
发表于 2016-3-15 19:51:37 | 显示全部楼层
很好的资料,有实际工作经验才能知道的
 楼主| 发表于 2016-3-15 20:15:06 | 显示全部楼层
mayingzhen 发表于 2016-3-15 10:43
请问一下“D3D9的sRGB支持不靠谱” 这个具体指的是哪方面的,之前分析一个D3D9做的游戏,它就是没有用硬件 ...

太久不用D3D9了,具体的也不清楚了。反正它的sRGB支持是broken的,不用就完了,就像你说的在shader里自己做就好。
 楼主| 发表于 2016-3-15 20:26:43 | 显示全部楼层
第四回 视景体的形状和景深其实是不准确的

很多人应该都遇到过远裁剪面的一个问题:屏幕边缘最远处的东西,转到屏幕中间就被剪裁掉了。这是投影的问题,从世界空间到投影空间的变换,是把梯形拉扯成了方形。于是,在投影空间里,相机到远裁剪面左中右的距离是一样的,即使在世界空间中他们的距离其实不一样。所以,depth buffer里的深度,也不对应世界空间的距离,而是在投影空间的深度。

如果试图用更准确的投影方法,就不能利用简单的矩阵乘法了,性能就成问题了。一个简单的解决方法是,渲染输出世界空间的depth texture,然后在pixel shader里读取这个纹理做像素级剪裁或fog视距消隐。对于各种deferred渲染架构来说,depth texture是必须品,所以没有额外代价。如今的显卡大多支持读取depth buffer,来避免deferred架构渲染额外的depth texture。但depth buffer里的值是对应投影空间距离的。要利用这个功能,就必须在pixel shader里先把depth值反算到世界空间。这样节省了一个render target texture的output,但之后读取时每个像素都要多了额外的解码和坐标变换,哪个更好就要按自己程序特点权衡了。
发表于 2016-3-16 12:48:52 | 显示全部楼层
熊猫加油,过来学习学习
发表于 2016-3-16 13:23:13 | 显示全部楼层
lidudu 发表于 2016-3-14 13:22
线框反锯齿不用这种方法,太浪费。
用一个包围线的矩形,和一个三个像素的纹理,三个像素分别为 透明、白 ...

这种方法增加了很多primitive数量,性能下降也挺厉害的吧。
以1000万三角形为例,大约是2000万的线段。
如果要实时渲染这个数据,在GS里把它展成三角形就是4000万...有点可怕的数字...
如果是CPU展开,那这个时间也很难做到实时。
这样和直接MSAA的方式来比较,恐怕也没什么优势了吧。

就是说像maya和3dsmax这样的软件,如果要提供一种低开发成本的AA方案,看起来还是MSAA比较好用一点。Post AA我觉得还是游戏里用比较好。
 楼主| 发表于 2016-3-16 15:54:35 | 显示全部楼层
MagicRen 发表于 2016-3-16 13:23
这种方法增加了很多primitive数量,性能下降也挺厉害的吧。
以1000万三角形为例,大约是2000万的线段。
...

你要画1000万三角形的边线,屏幕都会填满,不好看了。CAD软件边线都有消隐机制,不会傻傻地全画。这种边线就是要给人看清楚的,过犹不及。而且就算需要全画,千万级根线对工作站级显卡也不是什么大事,这种及其简单的纹理贴图shader开销很小,工作量全在顶点处理上,而顶点处理和你用不用这个方法差异不大。何况MSAA开销也不小,而且是全视图的所有渲染都要承受其开销。

更何况,GL本身的线条反走样,驱动内就是用这种方法实现的。
 楼主| 发表于 2016-3-16 20:24:04 | 显示全部楼层
第五回 后处理中的亮度调整

最合理的近似方法是用光源做光照计算再叠加,但在后处理阶段往往拿不到光照参数,所以多用Photoshop那种图像处理的方法。正确的近似是用加法,加上一个亮度值。如果要改变颜色,这个亮度值可以与光源颜色取模。关键的是要用加法。这种方法的缺点是,加上一个正数后,等价于表达颜色的位数减少了,超过255的值都被钳制到255,也就是降低了颜色分辨率。

有些网帖讲用乘法,其实是不对的。乘法的效果是调整对比度,而不是亮度。尽管调整对比度也会带来亮度上的变化,但会导致较大的颜色偏移,以及颜色的过饱和。

由于色深位数有限,后处理可能引入不少颜色分辨率的损失。有时增强一下对比度也是一种技术手法。但要注意,调整对比度不是简单的乘法,而是首先将分量偏移到以0为亮度中点,再做乘法,再偏移回去,大致就是C' = (C - 0.5) * scale + 0.5,设C在0到1内。

如果渲染过程应用了HDR,frame buffer的RGB有着较大的值域,比如每分量16位,则用HDR的方法将其一部分映射到屏幕的RGB值域,不但可以方便地调整亮度,而且基本去除了颜色分辨率的损失,在任何亮度下都可以保留足够的细节。
发表于 2016-3-17 07:23:08 | 显示全部楼层
lidudu 发表于 2016-3-16 20:24
第五回 后处理中的亮度调整

最合理的近似方法是用光源做光照计算再叠加,但在后处理阶段往往拿不到光照参 ...

学习...我一直以来都是用的乘法 (╯‵□′)╯︵┻━┻
发表于 2016-3-17 10:31:30 | 显示全部楼层
本帖最后由 EddyGun 于 2016-3-17 10:33 编辑
congy 发表于 2016-3-16 21:58
DX9 纹理采样状态可以设置是否 sRGB 采样,不必自己在 Shader 里手动转换,但最终输出时要手动转换 Pixel ...

最终输出也不需要手动,设置 SRGBWriteEnable 为 1 即可。
 楼主| 发表于 2016-3-17 13:04:49 | 显示全部楼层
本帖最后由 lidudu 于 2016-3-17 13:08 编辑

关于D3D9下SRGB的问题:

一、
理论上,pixel shader输出线性RGB后,alpha blend的公式应该为
finalOutput = LinearToSrgb(srcColor * srcAlpha + SrgbToLinear(dstColor) * (1 - srcAlpha))
但D3D9级GPU的公式为错误的
finalOutput = LinearToSrgb(srcColor * srcAlpha + dstColor * (1 - srcAlpha))

其次,在SRGB纹理做bilinear采样时,应该先读取4个像素,转换到线性,之后再做bilinear filter。而D3D9硬件是先bilinear filter后才转线性RGB。

二、
SRGB和线性RGB间的转换,不是简单的取指数,实际公式要复杂一些。但取指数是个合理的近似。
利用硬件的SRGB时,用的是精确公式,而且会利用显示器的color profile,理论上被在shader里自己做合理得多,而且不增加开销。
但是,用户显示器的gamma和其color profile未必吻合,其本身就有可能是错的。何况美工有可能就是要用点特别的gamma,所以shader里自己做gamma校正仍然可行。

三、
SRGB的显示器并不是严格的SRGB,只是在SRGB附近。实际的gamma大约在2.0-2.4

都是你们逼我又学了一遍D3D9……
发表于 2016-3-17 22:26:44 | 显示全部楼层
lidudu 发表于 2016-3-17 13:04
关于D3D9下SRGB的问题:

一、

感谢dudu大神解惑,终于明白项目中以前用硬件SRGB时发现的几种瑕疵的缘由。

另外想问一下,你提到的这些D3D9硬件的特性有什么具体的资料可以阅读吗?
发表于 2016-3-18 20:37:23 | 显示全部楼层
本帖最后由 EddyGun 于 2016-3-18 20:39 编辑
congy 发表于 2016-3-18 17:42
另外 DX9 提供纹理采样硬件级的 sRGB 转换,但对于比如顶点色、材质颜色等非纹理采样转换的颜色,就需要手 ...

嗯,不过 G-Buffer 数据应该不用转回到SRGB空间吧,我觉得MRT上的限制对生成 G-Buffer 没什么影响,当然我这是题外话,我现在明白手动的必要性了。
 楼主| 发表于 2016-3-18 20:58:22 | 显示全部楼层
第六回 高斯模糊

模糊处理在很多特效中都会用到,而高斯模糊是最有科学道理的方法。简单的说,就是每个像素在模糊时,对周围像素位置的贡献服从高斯分布。高斯分布曲线是从负无穷到正无穷的,实际上不算那么多,算到权重足够小的距离就好了,这个距离就是高斯模糊的半径。

以这个半径的两倍,就是直径,为边长,创建一个方形数组,每个元素就是该点预先计算出的权重。这个东西就是高斯模糊的卷积核。卷积是什么?不知道的去复习高数。对我们来说,用这个卷积核和要被处理的图像做输入,模糊后的图像做输出的函数,就是卷积函数。说白了就是对每个像素做附近像素的加权累加。

这个操作的时间复杂度比较悲剧。好在可以分解到横竖两个方向分别做。就是把二维卷积分解成横竖两次一维卷积,大大提高速度。这就是经典的算法。

按科学方法,一本正经地应用高斯模糊,大多需要用很大的模糊半径,结果会慢得扯蛋,不能满足实时需求。一般实际中,会选一个不太大的半径,然后反复重复执行多遍模糊,这样结果仍然不错,却可以快很多。

其实,有些效果根本不必那么科学,不是非得用高斯模糊,直接用线性衰减加权,甚至不加权的暴力平均,可以以很低的开销得到多次大半径高斯模糊的模糊效果。

评分

1

查看全部评分

 楼主| 发表于 2016-3-19 15:11:53 | 显示全部楼层
外传

Y 形是挤的,I 形是真大

人家只想快乐地扯蛋,你们不要那么认真啦
发表于 2016-3-21 18:17:30 | 显示全部楼层
下边呢,等好久了
 楼主| 发表于 2016-3-21 19:44:21 | 显示全部楼层
第七回 Premultiplied Alpha

很多人了解到这个东东是因为纹理图像的透明边界采样问题,即透明像素的颜色分量仍然会参与混合,而导致颜色误差。理想上,硬件纹理采样单元应该先转premultiplied alpha,再混合,以便使像素的颜色贡献按alpha权重比例混合。但目前的图形架构已经定型了,而且将premultiply的步骤提前到纹理创建时做,从效率上说更好。更何况,我们的纹理也未必都是RGBA的,与其把硬件做得更复杂,不如软件来解决。

当然,premultiplied alpha的用途不止如此。其最经典的用途是图层混合。图层混合是个很基本的操作,比如Vista开始有的desktop composition,Photoshop的图层功能。但在3D领域很少用到,因为我们大都是往不透明的背景上一层一层地画,所以我们用的混合公式,其实是背景色alpha固定为1而化简后的公式。

当我们需要cache一个有半透明的图层,以便之后再往背景上画时,就需要用premultiplied alpha的像素格式的render target了。为什么不能用普通的RGBA呢?因为render target有半透明时,就不能再用化简后的公式,输出的混合公式就变得很复杂:
outputAlpha = (1 - sa) * da + sa
outputColor = (dc * da * (1-sa) + sc * sa) / (da * (1-sa) + sa)
而用premultiplied alpha格式则很简单,可以直接利用硬件的Blend:
outputColor = (1 - sa) * dc + sc
发表于 2016-3-22 08:27:20 | 显示全部楼层
lidudu 发表于 2016-3-21 19:44
第七回 Premultiplied Alpha

很多人了解到这个东东是因为纹理图像的透明边界采样问题,即透明像素的颜色分 ...

我帮dudu大牛补充一下:premultiplied alpha最大的好处是
1. 使alpha blending满足结合律,即 A blending (B blending C) 等于 (A blending B) blending C,这对于类似VFX是很有用的。
2. 使mimmap在bleding的时候也是正确的,这个可以看到不做premultiply的物体在用mipmap的时候能看到边界
3. 对于png这个纹理格式,在blending的时候可以正确的表示。
发表于 2016-3-22 08:42:21 | 显示全部楼层
lidudu 发表于 2016-3-18 20:58
第六回 高斯模糊

模糊处理在很多特效中都会用到,而高斯模糊是最有科学道理的方法。简单的说,就是每个像 ...

帮dudu大牛补充一下:
高斯分布是自然界中最广泛的分布,我们生活中的很多事物都是符合高斯分布的(准确的说是正态分布),从信号处理的角度,任何信号,经过一定次数的卷积以后,就会变成高斯分布,所以就拿dudu说的这个加权模糊来说,简单的box filtering(比方说就是4个像素的和),如果你进行这个操作一定次数以后,效果就是和用高斯一样的。高斯另外一个好处就像dudu大牛说的那样,是可以分解的,就是二维分解成两个一维操作,这是别的filter没有的属性
发表于 2016-3-22 08:56:38 | 显示全部楼层
本帖最后由 DavidLee80 于 2016-3-22 08:57 编辑

帮dudu大牛补充一点:
1. 人眼接收处理颜色信号或者说光有两个特殊的结构,分别是rod和cone,cone负责接收颜色,rod负责intensity。rod的灵敏度是cone的10倍以上,而这两个结构,都可以进一步细分为S,M,L,分别负责response短波,中波,长波。呵呵,这里,我们会想一下那些颜色空间,是不是大部分都是三元素的。
2. MSAA和super-sampling比的好处,在hw这边其实就是无论你设置的msaa是几级,pixel shader都是只invoke一次,这就比super-sampling节省很多开销,所以hw普遍使用msaa,而非纯正的super-sampling
 楼主| 发表于 2016-3-22 19:39:52 | 显示全部楼层
热烈欢迎补充指正
 楼主| 发表于 2016-3-22 19:52:02 | 显示全部楼层
说到人眼对亮度和颜色敏感性的差异,视频压缩是充分利用了这一点的。视频的亮度分量和颜色分量是分开压缩的,颜色分量会使用高得多的压缩率,也就是说,会有更多的质量损失,以致在颜色边界,通常是溢出边界的。但是,视觉上却不大受影响,因为人眼识别边界主要靠亮度分量,对颜色的错误不是太在意。

人眼对边界的识别和计算机图像处理有些相似的地方,都会对图像做一个边界增强的sharpening filter。所以,在边界处感知的对比差异会大于实际的差异。所以,通过使用边界,可以改变大脑对颜色亮度的认知,产生错觉。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|关于我们|小黑屋|Archiver|手机版|中文第一计算机图形学社区OpenGPU

GMT+8, 2018-12-14 02:51 , Processed in 0.124652 second(s), 21 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表