zblade 阅读(54) 评论(0)

  在学习了一段时间的Unity Shader后,打算写一些知识总结,便于今后的查找。如有错误,希望大家指出更改。

  本文参照的unity入门精要一书,做一个知识归纳,如有兴趣可以看看其开源的部分,是一本比较好的入门shader书。

一、渲染流水线

  学习shader的知识,最重要的是要理解渲染流水线,基于渲染流水线,才能进一步的理解和学习下面的各个部分的shader。基于 Real-time rendering一书,渲染流水线可以分为三个部分:

    1、应用阶段

    在unity shader中,应用阶段主要完成三个基本的事:

    1)准备渲染的场景和模型;

    2)设置渲染的状态,主要是渲染所用到的材质、shader、贴图等;

    3)对渲染对象做一个粗粒度的剔除操作。

    这一阶段都是在CPU阶段执行的,特别是渲染状态的设置,对于后期的渲染有较大的影响,常见的批处理等操作都是在这阶段完成的。对于CPU阶段的优化,我也翻译了一篇外文如何优化CPU造成的渲染性能问题,可以查看这儿:Unity渲染优化中文翻译(二)——CPU的优化策略

   2、几何阶段

    基于应用阶段的渲染图元(primitives)以及渲染状态的设置,在几何阶段,主要完成的任务是对模型的顶点进行坐标转换,转换到屏幕空间投影坐标系中。此外一些其他的转换操作也可以在这个阶段执行,比如描边涉及的顶点位置偏移,模型空间到切线空间的转换矩阵计算等等。

  3、光栅化阶段

    基于几何阶段得到的顶点坐标数据,这一阶段的主要任务是基于顶点确定三角形,再对三角形进行插值操作,得到片元像素,最后进行逐像素的操作得到像素颜色。通过一些模板测试,深度测试,透明度测试等等最后将可以显示的像素更新到颜色缓存区中,通过双重缓存的机制更新屏幕颜色。

 

二、GPU的渲染流水线

  我们通常接触的更多的是GPU的渲染流水线,CPU主要工作是将数据发送的显存中,然后设置渲染状态(SetPass的操作),然后调用Draw Call。在CPU和GPU的中间有一个指令缓冲队列,CPU插入指令,GPU取出指令进行渲染,所以整体的渲染的工作主要还是在GPU上执行。现阶段的Unity Shader的GPU渲染流水线主要为:

                                                                    

  细分整个流水线,可以参考上面的流程图以及参考书中的过程,可以细分为:

  顶点数据—>顶点着色器—>曲面细分着色器—>几何着色器—>裁剪—>投影—>三角形设置—>三角形遍历—>片元着色器—>逐片元操作—>基本测试—>颜色缓存区

  Unity提供的主要是Surface shader,但是其本质还是顶点/片元着色器的构成,所以很多的时候在实际项目中,应用顶点/片元着色器的居多。我们在编写自己的shader的时候,更多的是在vertex/fragment这两个部分的shader中编写,当然涉及到其他的一些操作比如剔除、透明度、阴影等可以通过一些Unity shader中的语义指令来设置。

  

三、基本的光照知识

  1、渲染路径

    在unity中,主要的渲染路径分为三种:顶点光照渲染路径、延迟渲染路径、前向渲染路径。顶点光照渲染路径现在应用较少,逐渐走出应用的主流,现在应用较多的是前向渲染路径和延迟渲染路径。

    在前向渲染路径中,其基本的操作分为两个部分:forwardBase和forwardAdd,在forwardBase中主要计算逐像素光源的渲染,在forwardAdd中计算其他光源的光照计算。在前向渲染路径中,光照的渲染过程分为两个部分,先进行深度的测试,如果通过深度测试,则执行对应的渲染操作。在前向渲染路径中,每个逐像素光源都会对物体执行一次渲染,所以如果场景中有N个物体,M个光源,就会执行N*M次渲染,显然这对于大场景和多像素光源的应用场景有一定的性能限制。

    在延迟渲染路径中,只会执行2个Pass的渲染操作,第一个pass主要执行基本的深度测试,并进行G-缓冲;在第二个pass中,基于G-缓冲的结果进行具体的光照计算等渲染操作。所以延迟渲染的意思是指渲染操作被延迟到第二个pass中执行了。这样只用2个pass就实现渲染,可以提高渲染的性能。

  2、基本光照模型

    在shader中,应用的最为广泛的光照模型是Phong光照模型:光照 = 环境光 + 自发光+ 漫反射光 + 高光反射光

    1)环境光,通常可以直接利用UNITY_LIGHTMODEL_AMBIENT.xyz获取到,自发光通常没有采用,但是如果模型的顶点中带有颜色,可以在模型将模型的顶点数据导出用作自发光,可以参考这儿:访问顶点颜色

      2)漫反射光

         漫反射是shader中操作最多的一种光照,大部分的光照都是对其进行操作,其基本的计算公式为:

          Ccolor=Cdiffuse * Clight * max(a,dot(Vnormal,Vlight))

         这里解释一下为什么需要max操作,因为光源方向和法线方向夹角未必是锐角,在为钝角的时候得到的点乘结果为负数,这样得到的光照是指模型背面部分的顶点光照计算,这部分计算其实是不需要的,所以都设置为0,max操作就是用来确保最终的计算结果大于等于0。

          对于漫反射,在某些部分可能得到的光照颜色接近于灰暗,在某些情况下需要对光照结果做一个整体亮度的提高,所以V社提出了半Lambert的光照模型:

          Ccolor= 0.5 * Cdiffuse + 0.5;

          其实质就是将整体的计算结果做了一个缩放和偏移后,得到的变化曲线。

          除了半Lambert的操作外,在某些时候,如果我们需要对模型进行一种渐变的处理,可以用一张渐变纹理来改变模型的漫反射计算,我会在后面的渐变纹理中提到这部分的操作。

  3)高光反射

      高光反射是另外一种应用较多的光照,其基本的计算公式为:

       Ccolor = Cspecular * Clight * pow(max(Vreflect,Vview),mgloss)

      Blinn-Phong的光照模型中,高光反射计算公式可以改为:

        Ccolor = Cspecular * Clight * pow(max(Vnormal,Vhalf),mgloss)

     其中Vhalf =normalize( normalize(Vview)  + normalize(Vlight))

     高光反射,主要的是实现对光照的高亮,但是某些时候需要对高亮的结果进行一定的平滑处理,所以可以采用遮罩纹理来处理这样的操作,主要是通过遮罩纹理的某个通道来实现对高光的计算结果进行平滑处理。

  3、基本的阴影和衰减

    Unity中,对于衰减,主要是通过一张光照衰减纹理(_LightTexture0)的采样得到,其采样操作主要是在光照空间中进行。对于阴影,一般不会采用实时阴影的方式(性能耗费太高),但是现在随着技术的发展,越来越多的游戏采用的实时光照,当然这是对整体的光照的优化有一个极其高的情况下。一般情况下都是将场景烘培,对模型自身添加相应的光照设置。

    在unity中,主要是通过一个shadowCaster的shader来实现对阴影的计算,对于物体是否投射阴影可以在其自身的组件上设置。而unity将阴影和衰减的计算合并在一起,主要通过三个基本的内部操作来实现:在a2v的结构体中设置SHADOW_COORDS(n),在vert shader中进行TRANSFER_SHADOW(o),最后在frag shader中进行 UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos),此时得到的atten就是综合了衰减和阴影的结果,可以用其来作为衰减和阴影的因子与最终的光照颜色相乘。