shader总结八

来源:互联网 发布:图解网络硬件pdf微盘 编辑:程序博客网 时间:2024/05/22 09:00

摘自冯乐乐《shader入门精要》


立方体纹理:

立方体纹理一共包含了6张图像,这些图像对应了一个立方体的6个面,

立方体纹理在实时渲染中有很多应用,最常见的是用于天空盒子以及环境映射。

天空盒子是游戏中用于模拟背景的一种方法。天空盒子这个名字包含了两个信息:它是用于模拟天空的,它是一个盒子。当我们在场景中使用了天空盒子时,整个场景就被包围在一个立方体内。这个立方体的每个面使用的技术就是立方体纹理映射技术

除了天空盒子,立方体纹理最常见的用处是用于环境映射。通过这种方法,我们可以模拟出金属质感的材质。

在Unity5中,创建用于环境映射的立方体纹理的方法有三种:第一种方法是直接由一些特殊布局的纹理创建;第二种方法是手动创建一个Cubemap资源,再把6张图赋给它;第三种方法是由脚本生成。

如果使用第一种方法,我们需要提供一张具有特殊布局的纹理,例如类似立方体展开图的交叉布局、全景布局等。然后,我们只需要把该纹理的Texture Type 设置为Cubemap即可,Uniry 会为我们做好剩下的事情。在基于物理的渲染中,我们通常会使用一张HDR图像来生成高质量的Cubemap。

第二种方法是 Unity5之前的版本中使用的方法。我们首先需要在项目资源中创建一个Cubemap,然后把6张纹理拖拽到她的面板中。在Unity5中,官方推荐使用第一种方法创建立方体纹理,这是因为第一种方法可以对纹理数据进行压缩,而且可以支持边缘修正、光滑反射和HDR等功能。

前两种方法都需要我们提前准备好立方体纹理的图像,它们得到的立方体纹理往往是被场景中的物体所公用的。但在理想情况下,我们希望根据物体在场景中位置的不同,生成它们各自不同的立方体纹理。这时,我们就可以在Unity中使用脚本来创建。这时通过利用Unity提供的Camera.RenderToCubemap函数来实现的。Camera.RenderToCubemap函数可以把从任意位置观察到的场景图像存储到6张图像中,从而创建出该位置上对应的立方体纹理。

准备好了需要的立方体纹理后,我们就可以对物体使用环境映射技术。而环境映射最常见的应用就是反射和折射。


折射:

用斯涅耳定律来计算反射角。


其中η(1)和η(2) 分别是两个介质的折射率

在顶点函数中这样计算:

// 计算折射方向,第一个参数即为入射光线的方向,必须是归一化的矢量                  //第二个参数是表面法线,同样需要归一化                  //第三个参数是入射光线所在介质的折射率和折射光线所在介质的折射率之间的比值                  o.worldRefr = refract(-normalize(o.worldViewDir), normalize(o.worldNormal), _RefractRatio);  
在片元函数的cube采样不管是反射光还是折射光算法都一样做法:

 //对立方体纹理的采样需要使用texCUBE函数                  fixed3 reflection = texCUBE(_Cubemap,i.worldRefl).rgb*_ReflectColor;                  UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);                  //使用_ReflectAmount 来混合漫反射颜色和反射颜色,并和环境光照相加后返回。                  fixed3 color = ambient + lerp(diffuse,reflection,_ReflectAmount)*atten;                  return fixed4(color,1.0);  
在实时渲染中,我们经常会使用菲涅尔反射来根据视角方向控制反射程度。通俗地讲,菲涅尔反射描述了一种光学现象,即当光线照射到物体表面时,一部分发生反射,一部分进入物体内部,发生折射或散射。被反射的光和入射光之间存在一定的比率关系,这个比率关系可以通过菲涅尔等式进行计算。一个经常使用的例子是,当你站在湖边,直接低头看脚边的水面时,你会发现水几乎是透明的,你可以直接看到水底的小鱼和石子;但是,当你抬头看远处的水面时,会发现几乎看不到水下的情况,而只能看到水面反射的环境。这就是所谓的菲涅尔效果。


在实时渲染中,我们通常会使用一些近似公式来计算。其中一个著名的近似公式就是Schlick 菲涅耳近似等式:


 fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb;                  fixed fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(worldViewDir, worldNormal), 5);                  fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir));                  fixed3 color = ambient + lerp(diffuse, reflection, saturate(fresnel)) * atten;                  return fixed4(color, 1.0); 


渲染纹理

渲染到纹理(Render To Texture, RTT)的作用:

为了实现一些特殊的效果,比如常见的环境映射,简单的说,想象你有一个光滑的球体,它应该是可以反射周围环境的,这就是环境映射。


在之前的学习中,一个摄像机的渲染结果会输出到颜色缓冲中个,并显示到我们的屏幕上。现代的GPU允许我们把整个三维场景渲染到一个中间缓冲中,即渲染目标纹理(RTT),而不是传统的帧缓冲或后备缓冲。与之相关的是多重渲染目标(MRT),这种技术指的是GPU允许我们把场景同时渲染到多个渲染目标纹理中,而不再需要为每个渲染目标纹理单独渲染完整的场景。延迟渲染就是使用多重渲染目标的一个应用。

Unity为渲染目标纹理定义了一种专门的纹理类型——渲染纹理。在Unity中使用渲染纹理通常有两种方式:一种方式是在Project 目录下创建一个渲染纹理,然后把某个摄像机的渲染目标设置成该渲染纹理,这样一来该摄像机的渲染结果就会实时更新到渲染纹理中,而不会显示在屏幕上。使用这种方法,我们还可以选择渲染纹理的分辨率、滤波模式等纹理属性。另一种方式是在屏幕后处理时使用GrabPass命令或OnRederImage函数来获取当前屏幕图像,Unity会把这个屏幕图像放到一张和屏幕分辨率等同的渲染纹理中,下面我们可以在自定义的Pass中把它们当成普通的纹理来处理,从而实现各种屏幕特效。


我们先学习如何使用渲染纹理来模拟镜子效果。步骤如下:

创建一个四边形(Quad),调整它的位置和大小,它将作为镜子,在Project视图下创建一个渲染纹理,命名为"MirrorTexture",为了得到从镜子触发观察到的场景图像,创建一个摄像机,使它的显示图像是镜子图像,把之前创建的MirrorTexture拖拽到该摄像机的Target Texture上。

镜子实现的原理很简单,它使用一个渲染纹理作为输入属性,并把该渲染纹理在水平方向上翻转后直接显示到物体上即可

 //顶点着色器中计算纹理坐标              v2f vert(a2v v) {                  v2f o;                  o.pos = mul(UNITY_MATRIX_MVP, v.vertex);                  o.uv = v.texcoord;                  //翻转x分量的纹理坐标。这是因为,镜子里显示的图像都是左右相反的                  o.uv.x = 1 - o.uv.x;                  return o;              }              //在片元着色器中对渲染纹理进行采样和输出              fixed4 frag(v2f i) : SV_Target {                  return tex2D(_MainTex, i.uv);              }  
在unity中,我们还可以在Unity Shader 中使用一种特殊的Pass 完成获取屏幕图像的目的,这就是GrabPass。当我们再Shader中定义了一个GrabPass后,Unity会把当前屏幕的图像绘制在一张纹理中,以便我们在后续的Pass中访问它。我们通常会使用GrabPass来实现诸如玻璃等透明材质的模拟,与使用简单的透明混合不同,使用GrabPass可以让我们队物体后面的图像进行更复杂的处理,例如使用法线来模拟折射效果,而不再是简单的和原屏幕颜色进行混合。
需要注意的是,在使用GrabPass 的时候,我们需要额外小心物体的渲染队列设置。正如之前所说,GrabPass 通常渲染透明物体,尽管代码里并不包含混合指令,但我们往往仍然需要把物体的渲染队列设置成透明队列(即"Queue"="Transprent")。这样才可以保证当渲染该物体时,所有的不透明物体都已经被绘制在屏幕上,从而获得正确的屏幕图像。


屏幕后处理:

渲染完整个场景得到屏幕图像后,再对这个图像进行一系列操作,实现各种屏幕特效。使用这种技术,可以为游戏画面添加更多艺术效果,例如景深、运动模糊等。

Unity为我们提供了这样一个方便的接口OnRenderImage函数

MonoBehaviour.OnRenderImage(RenderTexture src,RenderTexture dest) 
当我们再脚本中声明此函数后,Unity会把当前渲染得到的图像存储在第一个参数对应的源渲染纹理中,通过函数中的一系列操作后,再把目标渲染纹理,即第二个参数对应的渲染纹理显示到屏幕上。在OnRenderImage函数中,我们通常是利用Graphics.Blit函数来完成对渲染纹理的处理。

因此,要在Unity 中实现屏幕后处理效果,过程通常如下:我们首先需要再摄像机中添加一个用于屏幕后处理的脚本。在这个脚本中,我们会实现OnRenderImage函数来获取当前屏幕的渲染纹理。然后,再调用Graphics.Blit 函数使用特定的Unity Shader 来对当前图像进行处理,再把返回的渲染纹理显示到屏幕上。对于一些复杂的屏幕特效,我们可能需要多次调用Graphics.Blit 函数来对上一步的输出结果进行下一步处理。
 

边缘检测

作用:通过边缘检测可以输出一张图的轮廓,因为轮廓部分的灰度插值最大,我们检测灰度插值最大的部分就可以输出从左到右的图像

                                    

最直观的想法就是求微分,



而在实际的图像处理中,用差分的方法进行边缘检测必须使差分的方向和边缘的方向相垂直,这就需要对图像的不同方向分别进行差分运算,增加了运算量。

对于下面三种算子,第一个算子矩阵对应

 I( i , j )      I( i , j+1)

 I( i+1, j )  I( i+1 , j+1)

第二第三个算子自己对应一下


         摘自https://www.cnblogs.com/ronny/p/4001910.html


边缘检测通过差分运算运算量较大,因此可以利用一些边缘检测算子对图像进行卷积操作。

在图像处理中,卷积操作指的就是使用一个卷积和对一张图像中的每个像素进行一系列操作。卷积核(也被称为边缘检测算子通常是一个四方形网格结构,该区域内每个网格都有一个权重值。当对图像中的某个像素进行卷积时,我们会把卷积核的中心放置于该像素上,翻转核之后再一次计算核中的每个元素和覆盖的图像像素值的乘积并求和,得到的结果就是该位置的新像素值。

这样的计算实现很多常见的图像处理效果,例如图像模糊、边缘检测等。例如,如果我们想要对图像进行均值模糊,可以使用一个3×3的卷积核,核内每个元素的值均为1/9。

相邻像素之间的差值可以用梯度来表示,边缘处的梯度绝对值会比较大,

以上的3种常见的边缘检测算子包含了两个方向的卷积核,分别用于检测水平方向和竖直方向上的边缘信息。在进行边缘检测时,我们需要对每个像素分别进行一次卷积计算,得到两个方向上的梯度值G(x)和G(y),而整体的梯度可按下面的公式计算而得:


当得到梯度G后,我们就可以据此来判断哪些像素对应了边缘(梯度值越大,越有可能是边缘点)。、


高斯模糊

所谓"模糊",可以理解成每一个像素都取周边像素的平均值

下图分别是原图、模糊半径3像素、模糊半径10像素的效果。模糊半径越大,图像就越模糊。


既然每个点都要取周边像素的平均值,那么应该如何分配权重呢?如果使用简单平均,显然不是很合理,因为图像都是连续的,越靠近的点关系越密切,越远离的点关系越疏远。因此,加权平均更合理,距离越近的点权重越大,距离越远的点权重越小。正态分布显然是一种可取的权重分配模式。在图形上,正态分布是一种钟形曲线,越接近中心,取值越大,越远离中心,取值越小。正态分布是一维的,图像都是二维的,所以我们需要二维的正态分布


使用一个N×N的高斯核对图像进行卷积滤波,就需要N×N×W×H(W和H分别是图像的宽和高)次纹理采样。当N的大小不断增加时,采样次数会变得非常巨大。幸运的是,我们可以把这个二维高斯函数拆分成两个一维函数。也就是说,我们可以使用两个一维的高斯核(上图中的右图)先后对图像进行滤波,它们得到的结果和直接使用二维高斯核是一样的,但采样次数只需要2×N×W×H.

我们可以进一步观察到,两个一维高斯核中包含了很多重复的权重,对弈一个大小为5的一维高斯核,我们实际只需要记录3个权重即可。

我们将会使用上述5×5的高斯核对原图像进行高斯模糊。我们将先后调用两个Pass,第一个Pass将会使用竖直方向的一维高斯核对图像进行滤波,第二个Pass再使用水平方向的一维高斯核对图像进行滤波,得到最红的目标图像。在实现中,我们还将利用图像缩放来进一步提高性能,并通过调整高斯滤波的应用次数来控制模糊程度。


Bloom效果

又称“全屏泛光”是一种比较廉价的“伪HDR”效果;使用了Bloom效果后,画面的对比会得到增强,亮的地方曝光也会得到加强,画面也会呈现一种朦胧,梦幻的效果,


HDR,本身是High-Dynamic Range(高动态范围)简单说,就是超越普通的光照的颜色和强度的光照。


Bloom的实现原理非常简单:我们首先根据一个阈值提取出图像中较亮的区域,把它们存储在一张渲染纹理中,再利用高斯模糊对这张渲染纹理进行模糊处理,模拟光线扩散的效果,最后再将其和原图像进行混合,得到最终的效果。


运动模糊

在一些诸如赛车类型的游戏中,为画面添加运动模糊是一种常见的处理方法。

运动模糊的实现方法有很多种。一种实现方法是利用一块累积缓存来混合多张连续的图像。当物体快速移动产生多张图像后,我们取它们之间的平均值作为最后的运动模糊图像。然而,这种暴力的方法对性能的消耗很大,因为想要获取多张帧图像往往意味着我们需要在同一帧里渲染多次场景。另一种应用广泛的方法是创建和使用速度缓存,这个缓存中存储了各个像素当前的运动速度,然后利用该值来决定模糊的方向和大小。