月度归档:2019年06月

你的硬盘是如何储存数据的

这是你的个人电脑,里面的硬盘则是你的命根子,藏着你多年以来积累的文档、照片、视频和游戏。

1

这块硬盘会以 0 和 1 组成的二进制形式默默储存着各种数据,随时等待着被你写入或者读取。

硬盘分为机械硬盘和固态硬盘。对于机械硬盘,最重要的结构是这些两面涂有磁性材料的磁盘,在工作时会以每分钟 7200 转的速度旋转。

2

写入数据时,距离盘面 3 纳米的磁头会利用电磁铁,改变磁盘上磁性材料的极性来记录数据,两种极性分别对应 0 或 1 。

3

而读取数据时,旁边的读取器可以识别磁性材料的不同极性,再还原成 0 或 1 。而读取数据时,旁边的读取器可以识别磁性材料的不同极性,再还原成 0 或 1 。

5

一片磁盘分为若干个磁道,每个磁道又分为各个扇区。扇区是磁盘存储的最小数据块,大小一般是 512 字节。

6

因此,磁头要想读取某个文件,必须在电机驱动下,先找到对应的磁道,再等磁盘转到对应扇区才行,一般会有十几毫秒的延迟,这就让机械硬盘在读取分散于磁盘各处的数据时,速度将大幅降低。

7

基于电路的固态硬盘则不用担心这种延迟。固态硬盘储存数据靠的是闪存。基于电路的固态硬盘则不用担心这种延迟。固态硬盘储存数据靠的是闪存。

在工作时,数据会通过接口进入主控制器,经处理后再分配到闪存中储存。

8

闪存的基本存储单元是浮栅晶体管,主要有这些结构。闪存的基本存储单元是浮栅晶体管,主要有这些结构。

9

其中的浮栅被二氧化硅包裹,和上下绝缘,在断电时也能够保存电子,当电子数量高于一个中间值就表示 0 ,低于中间值就表示 1 。

10

晶体管每次写入数据前都要先擦除,在 P 极上加一个电压,浮栅中原有的电子会因为量子隧穿效应通过绝缘层被吸出来,让浮栅中的电子数量低于中间值,还原成 1 ;

11

如果要写入 0 ,就在控制极加一个电压,让电子穿过绝缘层再注回浮栅,使电子数量高于中间值,表示 0 。

12

但在读取时,闪存无法直接得知浮栅中有多少电子,只能曲线救国。但在读取时,闪存无法直接得知浮栅中有多少电子,只能曲线救国。

我们首先要知道,往控制极加一定大小的电压,会导通这两个 N 极。控制极上的电压越大,N 极间的电流也越大。

13

然而,存储 0 的浮栅,相比存储 1 的浮栅,有更多的电子,会抵消控制极上的电压,所以控制极需要更大的电压才能导通两个 N 极。

15

因此,当我们不知道浮栅中有多少电子时,就可以往控制极加一个中间值电压,如果两个 N 极导通,就能反推出浮栅中的电子较少,识别为 1;如果没有导通,就说明浮栅中的电子较多,识别为 0 。

16

传统的单阶存储单元 SLC ,电子数量只有两种状态,只能保存一比特的数据。而多阶存储单元 MLC、TLC 和 QLC ,它们的电子数量有 4~16 种状态,一个单元可保存 2~4 比特。

多阶存储单元大大降低了固态硬盘单位容量的成本,但也影响了硬盘寿命和性能。

17

晶体管擦写数据时,二氧化硅绝缘层会困住一部分电子,这些电子的累积会逐渐抵消控制极上的电压,使得控制极为了导通两个 N 极所需的电压越来越大,当这个偏移超过中间值,那么读取时也就无法分辨 0 和 1 。

18

而多阶存储单元由于不同状态之间分得非常细,也就更容易受这种偏移的影响,所以从 SLC 到 QLC ,它们总的擦写次数呈几何级数递减。

19

相比机械硬盘可无限次擦写,断电后数据可保存十年,固态硬盘着实算是消耗品,储存的数据通常在断电一年后就会因浮栅内的电子衰减而彻底丢失数据。

20

不过,一块消费级的 MLC 或 TLC 固态硬盘也足够你至少使用 5 年,且使用体验远超机械硬盘,读写速度可达后者的十倍以上。此外,由于没有复杂的机械结构,固态硬盘工作时也更安静、更抗震。

21

而机械硬盘在长期使用后,各种金属部件的老化会让读写速度像挤奶一样细水长流。

22

况且,机械硬盘长久保存数据也未必是好事。比如本·拉登在死亡六年之后,CIA 公布了这位著名恐怖分子的硬盘数据,里面就存有大量的阿拉伯语字幕的日本动漫。

 

来源: https://tech.sina.com.cn/d/i/2019-06-27/doc-ihytcerk9711573.shtml

WIFI信号选哪个频率好

无线路由器工作的频段一般分为2.4GHz和5.0GHz两个频段。

一、2.4GHz是早期无线路由器普遍采用的频段,一直延续到现在,用于学校、商业等办公区域的无线连接技术,传输速率可达54Mbit/s,工作距离100m,采用直接序列扩频(DSSS)的方式。

二、5GHz是新的无线协议, 频率、速度,抗干扰都比2.4GHz强很多,现在5GHz也开始得到广泛应用。

扩展资料

5.0GHz频率的优点

一、解决网络拥堵:

Wi-Fi这个高速公路正变得拥挤不堪,面对越来越复杂的使用需求,旧的技术标准变得捉襟见肘,5G Wi-Fi要解决的就是这样的问题。

二、提升播放质量:

视频流量的爆发性成长以及与日俱增的无线装置,加重了Wi-Fi网络负担,导致用户消费者在观看影片时很容易遇到播放不顺畅、影片下载时间冗长等问题。

5G Wi-Fi每秒传输速度可达125MB,让每秒下载速度约为30~45MB的高清电影传输不成问题。

三、让手机更省电:

5G Wi-Fi另一大优点是节能——由于同一时间传送的内容更多,设备也能更快地进入低功率的省电模式。

四、信号品质更好:

目前2.4GHz频段Wi-Fi网络上“奔跑”的不仅仅有手机、平板、笔记本电脑、掌上游戏机,还有各种各样的移动设备。

大量设备堆积在一个狭小的频段中很容易彼此干扰。

国内5G频段使用较少,无线电干扰大为降低,信号品质有极大提升。

  • 无线路由器

2.4GHz

  1. 1

    2.4GHz工作的频率范围为2.400—2.4835GHz,一共有14个信道可用,但是中国规定了最多只能用13个信道。

  2. 2

    每个子信道频宽为22MHz;相邻信道的中心频点间隔5MHz;所以相邻的多个信道存在频率重叠(如1信道与2、3、4、5信道有频率重叠),即相互之间有干扰影响无线信号质量;整个频段范围内只有3个信道(1、6、11)互不干扰。

  3. 3

    相比于5G频率,2.4G频率优点在于:覆盖广,穿墙性好——由于频率低,在空气或障碍物中传播时衰减小,所以传播的更远;适配性广——基本上所有支持无线的产品都支持2.4GHz无线;

  4. 4

    2.4GHz的缺点也比较明显:家电、无线设备大多使用2.4G频段,导致无线环境拥挤,相互之间干扰较大,导致网速体验较差。

  5. 5

    所以在使用2.4GHz的信道时,需要先确认附近的其他无线使用的信道,尽量避开与其它无线设备的信道相同,如果无法避开的话尽量选择选择被占用较少的信道(下载wifi信道分析的app就可以查看到当前环境中的无线信道占用情况),网络模式选择带802.11n的模式。

5GHz

  1. 1

    5GHz的频率范围就比较广,但是中国规定只能使用的频率范围为5735~5835GHz,即149~165信道,每个信道之间相互不干扰。

  2. 2

    5GHz的优点:信号频宽较宽,无线环境比较干净,干扰少,网速稳定,且5G可以支持更高的无线速率,理论最高可达3.47Gbps。

  3. 3

    缺点:5G信号由于频率较高,所以在空气或障碍物中传播时衰减较大,覆盖距离一般比2.4G信号小。虽然现在越来越多的设备支持5GHz无线,但是还是有部分设备不支持。

  4. 4

    所以在配置5GHz时,先确认当前环境中的信道占用情况,尽量选择占用较少的信道,由于目前大部分无线路由器都支持了802.11ac,所以网络模式尽量选择802.11ac模式,如果没有则选择802.11n模式。

    END

总结

  1. 1

    综上所述的话,个人推荐优先选择使用5GHz频率的无线信号,保证比较好的网速体验;当然如果你有设备支持5GHz的,则只有选择2.4Hz频率,然后进行信道的设置;但是还是要根据你个人的实际情况来进行选择。

来源: https://jingyan.baidu.com/article/066074d6122ff5c3c21cb0f4.html

角色皮肤渲染技术

前言

游戏中的角色渲染技术随着近几年来硬件机能的增长已经被大范围的应用在了各类AAA大作中,本文会取一些游戏为例,分类概述游戏中的角色渲染技术。由于整个角色渲染的话题会比较长,这个主题会用两篇来阐述,第一篇主要讨论有关角色皮肤的渲染,第二篇会着重讨论角色毛发和其他的渲染。

次表面散射

当光线从一种介质射向另外一种介质时,根据其行进路线,可以被分为两个部分:一部分光线在介质交界处发生了反射, 并未进入另外一种介质,另外一部分光线则进入了另一种介质。反射部分的光照的辐射亮度(radiance)和入射光照的辐射照度(irradiance)的比例是一个和入射角度、出射角度相关的函数,这个函数就被称之为双向反射分布函数(BRDF)。相应的,穿越介质的那部分光照的辐射亮度和辐射照度的比例就被称之为双向透射分布函数(BTDF)。这两部分出射光的辐射亮度总和和入射光的辐射照度的比例就被叫做双向散射分布函数(BSDF),即BSDF = BRDF + BTDF

双向散射函数如果我们把光线行进的路线分为反射和透射,反射用R表示,透射用T表示,那么光线从一个点到另外一个点之间行进的路线就可以用R和T表示,比如BRDF描述的路径就是R,BTDF描述的路径就是TT,除此之外可能还会出现TRT,TRRRT等光照路线,由此我们可以想见,在光线入射点的附近应该有许多的出射光线。实际渲染中,如果光线出射点的位置和入射点相距不足一个像素,我们就认为入射点和出射点位置相同,这时候当前像素的光照只受其自身影响;如果入射点和出射点相距超过一个像素,则表示某个像素的光照结果不仅仅受当前像素影响,同时还受附近其他像素的光照影响,这就是我们常说的次表面散射效果了

《Real-Time Rendering》一书中对次表面散射的阐释。红色区域表示一个像素的大小,当出射光线集中分布在红色区域内时,则认为次表面散射效果可以忽略,当出射光线较为均匀地分布在绿色区域内时,则需要单独考虑次表面散射效果。

皮肤的实时渲染原理

皮肤是一个多层结构,其表面油脂层贡献了皮肤光照的主要反射部分,而油脂层下面的表皮层和真皮层则贡献了主要的次表面散射部分。

根据观察[1],次表面散射的光线密度分布是一个各向同性的函数,也就是说一个像素受周边像素的光照影响的比例只和两个像素间的距离有关。这个密度分布函数在有些地方称为diffusion profile,用R(r)来表示。实际上所有材质都存在次表面散射现象,区别只在于其密度分布函数R(r)的集中程度,如果该函数的绝大部分能量都集中在入射点附近(r=0),就表示附近像素对当前像素的光照贡献不明显,可以忽略,则在渲染时我们就用漫反射代替,如果该函数分布比较均匀,附近像素对当前像素的光照贡献明显,则需要单独计算次表面散射。据此次表面散射的计算可以分为两个部分:

(1)对每个像素进行一般的漫反射计算。

(2)根据diffusion profile和(1)中的漫反射结果,加权计算周围若干个像素对当前像素的次表面散射贡献。

由此可以简单地理解为diffusion profile就是一张权重查找表,不同的皮肤渲染方法,通常就是对diffusion profile的不同近似。我们根据加权计算所在的空间,将皮肤的渲染方法分为图像空间的方法屏幕空间的方法两类。

同一个diffusion profile在图像空间和屏幕空间的irradiance map效果和渲染结构对比示意图。由于图像空间内一般像素计算负担较大(计算复杂度和模型个数正相关),并且针对每一个次表面散射效果的模型都需要若干张贴图,显存开销也较大。而屏幕空间的计算复杂度和模型个数无关,且只需要一张屏幕大小的贴图,因此目前主流方案均采用屏幕空间的次表面散射。

图像空间的方法

图像空间的方法一般会将模型在其贴图空间内展开,具体来说一般有三步:

(1)在顶点着色器中将模型的UV坐标作为屏幕位置输出,同时输出模型每个顶点的世界坐标位置,在像素着色器中对每个像素进行漫反射光照的计算(如有阴影则需要将阴影考虑在内),得到所谓的irradiance map

(2)对irradiance map进行一次或多次卷积操作,每次的卷积核由diffusion profile来确定,并生成若干张卷积后的图像。(可能需要多个pass来完成)

(3)将卷积操作后的图像每个像素的值作为次表面散射的结果,再结合镜面反射(specular reflectance)计算出像素的最终颜色。

图像空间的算法示意图基于高斯模糊的近似

上图是一种称之为dipole approximation[1]的diffusion profile,我们接下来讨论的大部分渲染方法都是基于dipole approximation的理论。根据上图我们可以看出来,这个密度分布函数能够很好的用若干个高斯函数的加权和来近似,因此这里提到的第一种做法[2]就是基于多pass的多次高斯模糊,并最终加权的方法。[2]是一个标准的图像空间的做法,关于[2]中的方法,值得一提的是关于接缝的处理和irradiance的计算。

图像空间的做法产生的一个问题就是接缝,由于UV总会有缝合的位置,因此在缝合处必定会因为高斯模糊而混入背景颜色(比如下图是黑色):

修正这个现象的一个方法是修改背景色(在graphics API中修改clearColor的颜色值),将clearColor的颜色设定为接近皮肤的颜色,这样在混合的过程中就不容易出现明显的接缝。在irradiance map的计算过程中,比较常用的做法就是直接用dot(N, L) * diffuseColor作为irradiance的值,然后对其进行模糊作为最终的散射颜色,[2]采用了模糊前加模糊后的值作为最终的计算结果,类似这样:

[公式]
上面的Gi表示的是第i个高斯模糊,拆分成两项主要是用来模拟皮肤油脂层下面的一层非常薄的吸收层,这层将少部分光线吸收并直接反射出来,因此这部分光线并不需要参与到次表面散射的加权中。

更快速简洁的近似[2]能够实现非常不错的效果, 但从算法分析来看效率比较差,存储开销也很高。对每个模型的绘制来说,需要一个irradiance map的pass,五次高斯模糊操作(一个二维高斯模糊再拆分为两个一维高斯模糊,相当于10个pass),再加上最终合成的pass,总计就需要1+10+1=12个pass,并且每个pass中都有相当大量的采样操作。方法[3]在[2]的基础上进行了算法的改进,具体来说,[3]主要改进了两个部分:

(1)简化diffusion profile,卷积操作的采样数大幅减少,从每个像素10 * 7(每个高斯模糊pass用到7个sample)= 70个采样降低到了12个采样,并且这12个采样在一个pass中完成(原来需要10个pass完成)。

(2)减少了irradiance map中无效像素的数量,这里用到的技巧是,如果dot(N, V) < 0,则表示该像素位于视角背面,也就是不可见的,就不需要对其进行光照的计算,据此,[3]的方法中输出的屏幕坐标为:

(u, v, dot(N, V) * 0.5 + 0.5),

在对irradiance map进行clear操作时,可以设定depth的clear值为0.5,并将depthTest的方法设置为GEQUAL,这样,只有朝向摄像机的像素才会被渲染,其余被剔除(相当于利用可编程管线配合深度测试实现了背面剔除的效果)。在模糊的pass中,则只对可见像素进行采样和加权。这样就进一步减少了采样数。

方法[3]的性能较之于[2]有大幅度提高,因此被顽皮狗运用在了他们《神秘海域2》的过场动画中[4]。但根据我实现后效果对比来看,[3]的效果较之于[2]还是有比较明显的差异,因此只能说是牺牲了效果实现了游戏中要求的实时性。

屏幕空间的方法

屏幕空间的方法类似于图像空间的方法,只是计算irradiance时输出的位置不是UV坐标而是模型的投影坐标此外还需要将屏幕空间中属于皮肤的材质的像素用stencil buffer标记出来,然后对标记出的皮肤材质进行若干次卷积操作,卷积核的权重由diffusion profile确定,卷积核的大小则需要根据当前像素的深度(d(x,y))及其导数(dFdx(d(x,y))和dFdy(d(x,y)))来确定。

屏幕空间的算法示意图方法[5]在diffusion profile的近似方面和方法[2]没有本质上的区别,唯一的区别仅在于前者在屏幕空间,后者在图像空间。由于采样空间发生了变化,因此屏幕空间的方法需要根据当前像素的深度及其导数信息,对周边像素的采样UV进行横向和纵向的缩放,具体的缩放系数计算可以阅读原文。Unreal Engine3和CryEngine3就是采用了上述方案。

基于SVD分解的屏幕空间方法

屏幕空间的方法的好处是算法的时间复杂度可以说是一个固定开销,和皮肤材质的模型个数无关,缺点是这个固定开销比较高。例如方法[5]提到的,需要在屏幕大小的贴图上进行12次高斯模糊,实际上仍然是不小的开销(在待渲染皮肤材质较少的情况下甚至可能比图像空间的方法效率更差),因此方法[6]在方法[5]的基础上提出了屏幕空间框架下的另外一个diffusion profile的简化思路。从工程的角度来说,方法[6]将diffusion profile的近似用两个一维的卷积操作来表示,每个一维卷积根据最终效果可以用11-25个采样点不等,而卷积核的大小则由当前像素的深度以及用户定义的皮肤柔和程度(sssWidth)决定。从原理的角度来说,不同于方法[2]和[5]将diffusion profile用若干个高斯函数的加权和来表示,方法[6]把diffusion profile看做一个矩阵利用奇异值分解(SVD)的方法将其分解为一个行向量和一个列向量并且保证了分解后的表示方法基本没有能量损失(只是我的直观理解,欢迎数学好的同学给我解释一下详细的数学推导过程)。目前来看,方法[6]是兼顾了性能和效果的最佳方案。

预积分的方法[7]

图像空间的方法和屏幕空间的方法很大程度上都是通过周边像素对当前像素的光照贡献来实现次表面散射的效果,从原理上区别不大,方法之间的区别通常只是在于如何去近似diffusion profile,在性能和效果上有一个较好的trade off。而Pre-Integrated Skin Shading的方法则不同于上述方法,是一个从结果反推实现的方案。我们在观察次表面散射效果可以发现:

(1)次表面散射的效果主要发生在曲率较大的位置(或者说光照情况变化陡峭的位置),而在比较平坦的位置则不容易显现出次表面散射的效果(比如鼻梁处的次表面散射就比额头处的次表面散射效果要强)

(2)在有凹凸细节的部位也容易出现次表面散射,这一点其实和(1)说的是一回事,只是(1)中的较大曲率是由几何形状产生的,而(2)中的凹凸细节则一般是通过法线贴图来补充。

结合以上两个观察,[7]的思路是把次表面散射的效果预计算成一张二维查找表(具体的预计算方法可以查看这篇文章[8]),查找表的参数分别是dot(N, L)和曲率,因为这两者结合就能够反映出光照随着曲率的变化。

上图中1/r表示的就是曲率,文中也给出了相应的计算方法:[公式]

上图右边就是曲率显示出来的效果,可以看出类似额头这样的位置曲率是比较小的,而鼻子等位置的曲率就比较大。

上述方法解决了(1)的问题,但对于(2)提到的一些凹凸起伏的细节,由于它不是由几何造型产生的,因此无法用上述曲率计算的方法确认其是否有明显的次表面散射效果,因此作者进一步结合了bent normal的方法来实现这些细节处的次表面散射效果。

Bent Normal其实不是专门用来处理次表面散射专有的方法,可以应用于很多预计算复杂光照的情况,简单的说就是把包含AO,阴影,次表面散射之类的复杂光照信息pre-bake到法线里面,然后计算光照时使用pre-bake得到的法线,结合正常的光照计算方法,就能得到比较复杂的光照效果。

[7]中提到的bent normal的方案大致来说就是对法线贴图进行模糊的操作,以实现类似次表面散射的泛光效果,然后在计算最终光照的时候,使用原始的法线贴图和模糊后的法线贴图的线性插值结果作为最终的bent normal,不同的是,它会分RGB通道设置不同的混合权重,然后混合出三个bent normal,类似这样:

float3 Nhigh = mul(normMapHigh.xyz, TangentToWorld);
float3 Nlow = mul(normMapLow.xyz, TangentToWorld);
float3 rS = Nhigh ;
float3 rN = lerp(Nhigh , Nlow , tuneNormalBlur.r);
float3 gN = lerp(Nhigh , Nlow , tuneNormalBlur.g);
float3 bN = lerp(Nhigh , Nlow , tuneNormalBlur.b);

float3 NdotL = float3(dot(rN, L), dot(gN, L), dot(bN, L));

bent normal的方法也被《神秘海域2》应用在他们非过场动画的渲染部分,稍微提供一点SSS的效果。

画面效果至今也是我觉得相当巅峰的《教团1886》在他们的渲染角色渲染部分[9]也使用了pre-integrated的方法,但是具体方法不是很清楚,希望了解的朋友可以告诉我。

高光的计算

在[10]中提到了一种用于皮肤高光渲染的名为Kelemen/Szirmay-Kalos specular BRDF的模型,并将其中一些参数预计算成了查找表用于加速计算该BRDF,该模型计算皮肤油脂层的高光效果从视觉上要远好于简单的Blin-Phong BRDF模型。具体公式可参见GPU Gems3的相关文章。

透射(transmittance)的计算

我接触到的很多美术都把次表面散射和透射混为一谈,实际上这两者是皮肤渲染中两个完全不同的技术点。按照我们之前的光路分析,次表面散射反应的是TRT,TRRRT这类光路(奇数次反射),也就是入射光和出射光在介质的同一侧,而透射(或者我们常说的透光)则是反应TT,TRRT这类光路(偶数次反射),也就是入射光和出射光分布在介质的两侧。

在渲染技术上,这两者也完全不同,透射的实现思路相对比较直观,也容易理解,可以分为两步:(1)计算光照在进入半透明介质时的强度

(2)计算光线在介质中经过的路径长度

(3)根据路径长度和BTDF来计算出射光照的强度,这里BTDF可以简化为一个只和光线路径长度相关的函数

方法[10]就是一个基于屏幕空间的透射计算方法,它除了需要延迟渲染常用的GBuffer(用到的信息包含depth, normal和albedo,主要用来近似入射的光照强度)外,还需要计算透射用到的光源视角的深度图(一般是主光源的深度图)以及相关的光源摄像机矩阵。这个深度图和矩阵用来计算光源在从当前像素位置射出时经过的光线路径长度。因为BTDF随着路径的衰减比较快(指数平方的衰减),因此只有在较薄的结构(比如人的耳朵或者手掌边缘)上才能看到明显的透射效果,而当半透明物体背面被其他物体挡住的时候,体现在深度图上还是光线路径较长,因此也不会产生错误的透射效果。方法[11]用出射点法线的反方向近似背面入射点的法线方向进行入射光强度的计算,具体的计算公式可见原文。

总结

皮肤的实时渲染在近十年来一直算是一个比较热门的话题,每年也有不少相关的算法和改进的提出,从目前的游戏中呈现的效果来看,皮肤可以说已经达到了非常逼真的程度。从这里历数的各个方法出发可以看出皮肤渲染随着其他渲染技术的演化,例如从图像空间的方法到屏幕空间的方法的演化很大程度上要归功于延迟渲染管线的普及以及硬件性能的提高。尽管屏幕空间的方法较为主流,但上面提到的方法在特定的环境下也都仍然有应用范围,另外这些方法在核心原理上并无太大区别,可以根据具体的情景和需求做选择。

来源:https://zhuanlan.zhihu.com/p/27014447

次表面散射

次表面散射,次表面散射简称3S,是光射入非金属材质后在内部发生散射,最后射出物体并进入视野中产生的现象,是指光从表面进入物体经过内部散射,然后又通过物体表面的其他顶点出射的光线传递过程。

次表面散射

继续写之前的基于物理着色系列,这一篇谈谈次表面散射(Subsurface Scattering)的理论以及渲染的算法。次表面散射是现实中一种非常常见的材质外观,所有有着半透明外观的物体,例如玉石,大理石,蜡烛,可乐,苹果,牛奶(见下图)包括人类的皮肤,等等。现实中这种半透明的材质其实非常普遍,所以如何为它们的外表建立正确的数学模型是照片级别真实感渲染的重要一部分。

下图则是电影《功夫熊猫3》中的一幕,画面中的玉石也是次表面散射材质的典型例子之一。

与此同时,次表面散射现象的模拟也比在前三篇文章里介绍过的一般的表面反射复杂很多,因为要正确的模拟这种现象,光线不止再物体的表面发生散射,而是会先折射到物体内部,然后再物体内部发生若干次散射,直到从物体表面的某一点射出,例如下图中的(b)。所以对于次表面散射性质的材质来说,光线出射的位置和入射的位置是不一样的,而且每一点的亮度取决于物体表面所有其他位置的亮度,物体的形状,厚度等。之前文章里介绍的表面散射都是基于BSDF模型(双向散射分布函数),而BSDF只能用于描述物体表面某一点的散射性质,所以它无法描述像次表面散射这种现象。

Participating Media

要模拟这样的现象,最简单,最精确,也是计算量最大,最慢的方法,就是直接在物体内部的空间求解带有Participating Media的渲染方程。为了文章的完整性,这里简单介绍一下Participating Media的数学定义,想详细了解可以参考pbrt第11章[1]。

Participlating Media的存在,意味着传统的渲染方程不再是传统的以场景中所有表面为定义域的积分,因为光线在有介质的空间中传播的时候,不用和表面接触,也会被空间中的介质所吸收,散射。通常我们用一下几个参数来描述空间中介质的性质。吸收系数[公式],和散射系数[公式]。前者描述了空间中每单位长度中光子被吸收的概率,后者则描述了每单位长度中光子被散射、既传播方向被改变的概率。散射系数包括了其他方向的光线入射到当然方向的概率,也包括当前方向出射到其他方向的概率。另一个参数是[公式],就是衰减系数,它是吸收系数和散射系数的和,代表了单位距离内光子出射到其他方向或者被吸收掉的总概率。所以单位距离内辐射度的变化率可以写成:

[公式]
第一眼看去貌似有些吓人,实际上说的就是,在介质中,单位长度上Radiance强度的变化会因为被介质吸收而减少,同时一部分能量会随机向外散射出去,最后来自各个其他方向的光线也有一定概率散射到当前方向。其中方程最右边积分中的[公式]是phase function,可以理解成空间中的BSDF,定义了空间中一点两个方向间发生散射的概率。另一个值得一提的是,每个频率的光线都有对应的系数。也就是说在用RGB渲染时,每个颜色都要有各自的吸收和散射系数。

这篇文章封面的图片以及下面这张图都是用直接用模拟Participating Media的方法渲染出的玉石。玉石材质是由一个无色的,折射率为1.6的电介材质表面包裹着绿色散射系数较大的均匀介质。由于下图中介质的散射系数要远大于封面中的,所以光线散射的更加频繁,散射间传递的距离更短,导致外观比封面中的玉石更加厚密。

渲染Participating Media的方法有很多,例如最简单的Volumetric Path Tracing,Volumetric Photon Mapping等等。两个参考,[2]是最简单的Volumetric Path Tracing的tutorial,[3]是State of the art的Participating media estimator。因为话题很大,这里不再展开,以后有时间会再专门写写相关的内容。

BSSRDF的定义

模拟次表面散射的另一大类方法就是通过BSSRDF(Bidirectional Surface Scattering Reflectance Distribution Function)对材质建模。和BRDF的不同之处在于,BSSRDF可以指定不同的光线入射位置和出射的位置。Jensen在2001年的论文[4]可以说那是次表面材质建模最重要的一篇论文,推导了许多重要的物理公式,计算模型,渲染时的参数转换,以及测量了许多生活中常见材质的散射系数等等。大部分后来的论文都是在基于这篇文章中的理论的一些提升。不过这篇论文中有大量的数学以及物理公式,对没有一定数学基础和没有接触过材质建模,体渲染的人来说读起来很快就会摸不着头脑。好在大部分公式的意义都在于推导和证明BSSRDF的计算,所以这篇文章会挑出几个和实现次表面散射直接相关的重要公式简单介绍,如果想了解全部细节,还是请参考[4]原文。

BSSRDF的意义在于快速的近似。对于许多吸收系数特别低,散射系数特别高的材质来说(大部分浑浊的半透明物体例如牛奶,大理石,苹果,肉等等),一束光线会在物体内部散射成千上万次散射,才会彻底被吸收。直接用最暴力的Path Tracing去模拟则意味着每个Sample都需要散射上千次,收敛速度很慢。

对于之前的BRDF来说,一次反射光照的计算是在光线交点的法线半球上的球面积分。而对于BSSRDF来说,每一次反射在物体表面上每一个位置都要做一次半球面积分,是一个嵌套积分。

[公式]其中BSSRDF的定义是:

[公式]其中包括了光纤入射和出射材质表面时候的折射Fresnel项[公式],一个归一化的[公式]项,和一个diffuse项[公式]。除了diffuse项以外,其余的项都非常直观。下面就着重说说这个diffuse项。首先[公式]只接受一个标量参数,这个参数的意义是光线入射位置和初设位置的曼哈顿距离。直观的理解就是,BSSRDF尝试将光线在物体表面内部中数千次的散射后所剩余的能量用一个基于入射点和出射点之间距离的函数去近似。

这个近似则是基于几个假设。第一个假设是,次表面散射的物体是一个曲率为零的平面。第二个假设是,这个平面的厚度,大小都是无限。第三个假设是,平面内部的介质参数是均匀的,最后一个假设是光线永远是从垂直的方向入射表面。正因为有这些假设,所以很容易把出射光的强度与出射点和入射点之间的距离用一个函数去近似。下图是用unbiased monte carlo方法模拟的一束光照在平面均匀介质

当然实际渲染中这种平面不存在,所有模型都不是无限厚度的,也都是有各种形状的,现实中所有物体内部的散射系数也都肯定不会是均匀的。这也是BSSRDF近似渲染的最主要的错误来源。下图是我在EDXRay里用两种完全不同的方法渲染的颜色,材质以及介质参数完全一样的两个佛像。左边的佛像是基于BSSRDF,右边的则是无偏的Participating Media,两者还是有细微的差别的。BSSRDF因为假设所有表面都的厚度都是无限的,在实际模型中如果有厚度比较薄的地方,误差会更为明显。这也是为什么BSSRDF渲染的结果相比Participating Media要暗淡一些。除了这些误差以外,BSSRDF也没办法渲染出焦散,无法正确的渲染表面以下的几何模型(注意封面shader ball左侧在球内部的写有sub-surface的黑色模型)。

那么究竟[公式]的定义是什么呢。理论上来说它需要尽可能的只通过离光线入射点的距离就算出出射点的能量。首先在场景及其简化的情况下,例如一个点光源完全被包围在散射系数均匀的介质中,散射的能量和光源的距离的关系可以用下面的公式算出来。

[公式]
但是这个积分并没有办法解析的求解,数值方法求得的解也会发散。再加上没有任何公式能直接根据距离准确的计算任何在有限空间内,表面形状任意时候的次表面散射的能量。所以寻求某种近似是必须的。其中一个这些年非常流行的近似算法就是用求解Diffusion方程的方法。

[公式],

相比上面的精确解,Diffusion的近似简单了许多,只是一个距离自然指数除以距离。这个方程也是Jensen 2001[4]中Dipole近似的核心。实际上过去的所有次表面散射相关的论文都在不断的提出新的方法来更准确的理解这个方程的性质和提高它的精确度。

然而这篇文章并不想着重介绍Dipole[4]以及其延伸算法例如Multipole[5],Directional Dipole[6]。所以读到这也不必太在意上面两个眼花缭乱的公式。

Normalized Diffusion

Pixar的Christensen(曾经差点和他成为同事)在去年发表的一篇论文中[7],试验出了一个函数,它没有基于任何Diffusion或者光线传递的理论,就直接可以比几乎以往所有的算法都能更精确的直接逼近介质渲染方程的正确解。不仅如此,这个方程本身只含有两个指数函数,实现和计算都非常简单。而且它还可以的解析的求Cumulative Distribution Function(CDF),渲染时要Importance Sampling也非常方便。实际上上面红色的佛像就是用这个方法渲染的。当然它同样也要满足以上提倒的那几个假设。因为这个函数从0到[公式]的积分永远是1,所以这个方法的名字叫做Normalized Diffusion。它的定义如下:

[公式]

就是这么简单。其中[公式]是一个控制这个函数形状的参数。我们只需要根据不同的介质散射系数计算出适合的[公式],就可以非常准确的逼近Participating Media的无偏解。下图截自[7],意思就是说上面那个如此简单的公式可以做到比传统的数个复杂许多的算法更精确度逼近基于蒙特卡洛暴力积分的无偏解。A是材质表面的反射率(既RGB值)。注意,原来的Diffusion方法所计算的都只包括了材质中的多重散射(Multiple Scattering),直接散射(Single Scattering)则要单独用Ray Marching的方法求解。而Normalized Diffusion在函数里就默认包含了直接散射,实现和计算上都更加简单。

Normalized Diffusion所接受的介质参数和传统的有所不同。通常描述介质用的是吸收系数[公式],和散射系数[公式]。但是由于它们和最终材质外观的联系并不是一个线性关系,下面的表格可以看出[公式][公式]与材质的颜色的关系非常摸不着头脑。所以Artists并不能很方便的直接调整这些系数。所以其实大部分渲染器都会把这些系数映射成材质表面的Albedo[公式]和平均散射距离[公式](diffuse mean free path)供Artists调整。不仅如此,也只有这也,才可以将纹理上的颜色应用到次表面散射材质上去。

从Albedo和平均散射距离到介质系数的映射在[4]中就有介绍。[公式]
其中[公式]是reduced albedo,[公式][公式][公式]则是散射系数受到phase function影响之后的reduced scattering coefficient。[公式],[公式][公式]则和表面的折射率有关,[公式][公式]

平均散射距离[公式],而[公式]

上面那堆公式就足够能解出材质的散射系数。这里可以看出Normalized Diffusion另一个优势是,方程的输入直接就是Diffuse Albedo和平均散射距离,不需要求解上方那些复杂的公式。

[公式]值本身的计算,也是通过函数拟合。给定一个材质的反射率[公式],以及介质的平均散射距离[公式],再假定光线是以光束的形式垂直射入物体的话,[公式]。想了解其他拟合的计算方法,例如光线是以漫射的形式入射表面等,请参考[7]。

因为这里的[公式]积分时永远等于1,所以渲染的时候只要直接乘以材质的Diffuse Albedo就可以了。当平均散射距离为0的时候,会收敛到和漫反射材质一样的结果。下图是五只用Normalized Diffusion渲染的兔子,平均散射距离从左到右由小变大。

终于,文章写到这里,BSSRDF的公式的定义终于介绍完整了!最后简单的谈谈具体如何用BSSRDF进行采样和渲染。

BSSRDF重要性采样

利用BSSRDF渲染的方法大致有两种。Jensen在[4]中提出了一种直接在物体的入射点周围根据[公式]重要性采样出射点的方法。接着Jensen又在[8]中提出了一种基于点云的方法,先将许多点随机分布在次表面的模型上,然后在每个点预计算irradiance,最后利用这些点云信息快速查找积分。这个算法的优势是速度比前者快两三个数量级,缺点则有额外的内存开销,需要多一个预计算的Pass,实现起来较为麻烦,不适合progressive渲染,同时点云的分布影响渲染质量,容易在animation中出现flickering等等。

所以这里介绍的方法还是基于直接对BSSRDF进行重要性采样。Solid Angle在2013年提出一个多轴投射的采样方法[9]。首先根据[公式]在入射点周围的圆盘上根据[公式]的分布采样一个距离[公式],接着将这个点垂直投影到模型的表面上。因为BSSRDF是在物体的表面积上积分,所以在遇到和入射点法线方向不同的表面时,样本的pdf需要乘以入射和出射点法线的点积。当入射与出射表面接近垂直的时候,pdf就会非常小。所以[9]中的做法是用一定的概率在binormal和tangent方向进行投影,然后用多重重要性采样计算样本的pdf,就可以在凹凸不平的表面达到更快的收敛速度。

至于对散射距离的采样,Normalized Diffusion的CDF可以直接用下面的公式计算:[公式].

只不过采样需要的[公式]无法解析的表达,这里可以直接用预计算查表的方法解决。计算的时候让[公式]等于1,查表时乘以实际的[公式]就能得到散射距离。有了以上采样的方法,就可以把次表面散射采样的路径作为一种特殊路径集成到Path tracing或者Bidirectional path tracing的积分中去。

最后放多几张次表面散射材质的美图。本文中所有渲染结果均使用本人自己开发的渲染器EDXRay。

 

来源: https://zhuanlan.zhihu.com/p/21247702

rocketmq的NameServer模块

[mq@dev1 bin]$ sh mqnamesrv -help
usage: mqnamesrv [-c <arg>] [-h] [-n <arg>] [-p]
-c,--configFile <arg> Name server config properties file
-h,--help Print help
-n,--namesrvAddr <arg> Name server address list, eg: 192.168.0.1:9876;192.168.0.2:9876
-p,--printConfigItem Print all config item

 

rmq的 namesrv服务的详细配置信息如下(采用下面命令, 可以打印全部配置信息)

sh mqnamesrv -p

rocketmqHome=/home/mq/tmp5/xb-rmq-server-4.5.1
kvConfigPath=/home/mq/namesrv/kvConfig.json
configStorePath=/home/mq/namesrv/namesrv.properties
productEnvName=center
clusterTest=false
orderMessageEnable=false
listenPort=9876
serverWorkerThreads=8
serverCallbackExecutorThreads=0
serverSelectorThreads=3
serverOnewaySemaphoreValue=256
serverAsyncSemaphoreValue=64
serverChannelMaxIdleTimeSeconds=120
serverSocketSndBufSize=65535
serverSocketRcvBufSize=65535
serverPooledByteBufAllocatorEnable=true
useEpollNativeSelector=false

部分信息的介绍如下(信息搜集于网络)

NamesrvConfig是NameServer的配置类

这个类放在了common模块中,但是几乎都是被NameServer模块引用,这里就当在NameServer模块中讲

主要讲解字段,方法都是get和set
字段
rocketmqHome

private String rocketmqHome = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY, System.getenv(MixAll.ROCKETMQ_HOME_ENV));

RocketMQ home 目录
如果没有指定的话,默认值为系统环境变量ROCKETMQ_HOME
通过System.getenv获取,可以在~/.profile中export
或者可以在配置文件中指定rocketmqHome=***

kvConfigPath

private String kvConfigPath = System.getProperty("user.home") + File.separator + "namesrv" + File.separator + "kvConfig.json";

KvConfig的配置文件路径
默认为System.getProperty("user.home")/namesrv/kvConfig.json

configStorePath

private String configStorePath = System.getProperty("user.home") + File.separator + "namesrv" + File.separator + "namesrv.properties";

NamesrvConfig(以及NettyServerConfig)的配置文件路径
默认为System.getProperty("user.home")/namesrv/namesrv.properties

productEnvName

private String productEnvName = "center";

clusterTest为true时work
用于构造ClusterTestRequestProcessor

clusterTest

private boolean clusterTest = false;

代表不同的模式
决定NamesrvController#registerProcessor()注册处理器时
用的是ClusterTestRequestProcessor 还是 DefaultRequestProcessor

orderMessageEnable

是否开启顺序消息功能

private boolean orderMessageEnable = false;

问题
productEnvName有什么用
clusterTest,orderMessageEnable是如何实现对应功能
refer

http://bboyjing.github.io/2017/04/21/RocketMQ%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B9%8B%E3%80%90rocketmq-namesrv%E3%80%91/

http://hiant.github.io/2016/08/26/rocketmq-0x5/ clusterTest作用
1.1. NamesrvStartup 处理流程

设置版本号
com.alibaba.rocketmq.namesrv.processor.DefaultRequestProcessor#processRequest
com.alibaba.rocketmq.broker.processor.AdminBrokerProcessor#callConsumer

设置Socket缓冲区
如果没有设置 SEND 缓冲区:NettySystemConfig.SocketSndbufSize = 2048
如果没有设置 RECV 缓冲区:NettySystemConfig.SocketRcvbufSize = 2048

FastJson版本冲突检测
com.alibaba.rocketmq.common.conflict.PackageConflictDetect#detectFastjson

命令行参数解析
使用 com.alibaba.rocketmq.srvutil.ServerUtil#parseCmdLine 解析命令行,如果解析失败,则程序退出

配置文件加载
命令行中包含 -c 选项,则加载指定的 properties 配置文件,并通过 com.alibaba.rocketmq.common.MixAll#properties2Object 设置 NamesrvConfig、NettyServerConfig。其中,如果配置中没有设置 ListenPort,默认为 9876

输出配置参数列表
命令行中包含 -p 选项,则通过 com.alibaba.rocketmq.common.MixAll#printObjectProperties(org.slf4j.Logger, java.lang.Object) 输出 NamesrvConfig、NettyServerConfig 的声明字段,输出完成后程序退出

配置 NamesrvConfig
通过 com.alibaba.rocketmq.common.MixAll#properties2Object 将命令行参数加到 NamesrvConfig

配置 logback
logback 配置文件固定:namesrvConfig.getRocketmqHome() + "/conf/logback_namesrv.xml"。
注意:此前会先判断 namesrvConfig.getRocketmqHome() 未设置时,会导致程序退出。不过这种情况仅发生在直接运行
com.alibaba.rocketmq.namesrv.NamesrvStartup#main,如果是脚本启动,会自动将该值设置为 mqnamesrv 脚本的上上层目录

实例化并初始化 NamesrvController
使用 NamesrvConfig、NettyServerConfig 实例化 NamesrvController 然后调用 com.alibaba.rocketmq.namesrv.NamesrvController#initialize 初始化,初始化失败时程序退出
否则增加 ShutdownHook ,在程序退出时记录日志,并调用 com.alibaba.rocketmq.namesrv.NamesrvController#shutdown 释放资源

启动 NamesrvController
调用 com.alibaba.rocketmq.namesrv.NamesrvController#start 启动线程工作

1.2. NamesrvController 初始化流程

加载 KvConfig
如果 NamesrvConfig 中 kvConfigPath 路径指定的文件内容不为空,则将文件存储的 json 内容加载进Map:HashMap<String/* Namespace */, HashMap<String/* Key */, String/* Value */>>

实例化 NettyRemotingServer
rocketmq-remoting 模块中的 com.alibaba.rocketmq.remoting.netty.NettyRemotingServer#NettyRemotingServer(com.alibaba.rocketmq.remoting.netty.NettyServerConfig, com.alibaba.rocketmq.remoting.ChannelEventListener)

构造 RemotingExecutorThread 线程池
使用 java.util.concurrent.Executors#newFixedThreadPool(int, java.util.concurrent.ThreadFactory) 构造固定数目线程池,数目由 NettyServerConfig.getServerWorkerThreads 决定

注册
NamesrvConfig 中 clusterTest 为 true 时,调用 com.alibaba.rocketmq.remoting.RemotingServer#registerDefaultProcessor 将一个 com.alibaba.rocketmq.namesrv.processor.ClusterTestRequestProcessor 和 RemotingExecutorThread 线程池绑定起来;否则将一个 com.alibaba.rocketmq.namesrv.processor.DefaultRequestProcessor 和 RemotingExecutorThread 线程池绑定起来。绑定到 NettyRemotingServer 的 defaultRequestProcessor 上

两者区别: ClusterTestRequestProcessor 继承自 DefaultRequestProcessor 并重写了 getRouteInfoByTopic 方法,RouteInfoManager 中未能获取到 TopicRouteData 时,去集群上查询一次

启动定时任务
5s 后启动,每隔 10s 执行一次:清理失效的 Broker 信息
1m 后启动,每隔 10m 执行一次:在日志中 INFO 级别输出 KvConfig 的内容

1.3. NamesrvController 启动流程

实际上是启动 NamesrvController 的 NettyRemotingServer

构造 DefaultEventExecutorGroup
线程数目由 NettyServerConfig.getServerWorkerThreads 决定,线程名:"NettyServerCodecThread_" + this.threadIndex.incrementAndGet()

构造 BOSS 线程
线程数固定为1,线程名:NettyBoss_1

构造 WORKER 线程
如果是 Linux,且 NettyServerConfig 中 useEpollNativeSelector 为 true,返回一个 EpollEventLoopGroup,线程数目由 NettyServerConfig.getServerSelectorThreads 决定,线程名:String.format("NettyServerEPOLLSelector_%d_%d", threadTotal, this.threadIndex.incrementAndGet());否则返回一个 NioEventLoopGroup,线程数目由 NettyServerConfig.getServerSelectorThreads 决定,线程名:String.format("NettyServerNIOSelector_%d_%d", threadTotal, this.threadIndex.incrementAndGet())

配置 ServerBootstrap
ChannelOption.SO_BACKLOG:1024
ChannelOption.SO_REUSEADDR:true
ChannelOption.SO_KEEPALIVE:false
ChannelOption.SO_SNDBUF:nettyServerConfig.getServerSocketSndBufSize()
ChannelOption.SO_RCVBUF:nettyServerConfig.getServerSocketRcvBufSize()
ChannelOption.TCP_NODELAY:true 设置的是childOption
localAddress:new InetSocketAddress(this.nettyServerConfig.getListenPort())
childHandler:
NettyEncoder:编码
NettyDecoder:解码
IdleStateHandler:空闲处理,空闲时间由 nettyServerConfig.getServerChannelMaxIdleTimeSeconds() 决定
NettyConnetManageHandler:连接管理,记录连接日志,并处理连接事件(空闲事件)
NettyServerHandler:业务逻辑处理。根据 RemotingCommand 的 type 调用 com.alibaba.rocketmq.remoting.netty.NettyRemotingAbstract#processRequestCommand 或 com.alibaba.rocketmq.remoting.netty.NettyRemotingAbstract#processResponseCommand
NettyServerConfig 未将 serverPooledByteBufAllocatorEnable 设置为 false,则 ChannelOption.ALLOCATOR 设置为 PooledByteBufAllocator.DEFAULT,即默认开启 Netty 的池化 ByteBuf

启动 ServerBootstrap
调用 io.netty.bootstrap.AbstractBootstrap#bind()

启动定时任务
3s 后启动,每隔 1s 执行一次:处理超时请求