当前位置:   article > 正文

【Unity Shaders】Vertex & Fragment Shader入门_unity vertex shader 传参给 fragment shader

unity vertex shader 传参给 fragment shader


写在前面


三个月以前,在一篇讲卡通风格的Shader的最后,我们说到在Surface Shader中实现描边效果的弊端,也就是只对表面平缓的模型有效。这是因为我们是依赖法线和视角的点乘结果来进行描边判断的,因此,对于那些平整的表面,它们的法线通常是一个常量或者会发生突变(例如立方体的每个面),这样就会导致最后的效果并非如我们所愿。如下图所示:



因此,我们有一个更好的方法来实现描边效果,也就是通过两个pass进行渲染——首先渲染对象的背面,用黑色略微向外扩展一点,就是我们的描边效果;然后正常渲染正面即可。而我们应该知道,surface shader是不可以使用pass的。


如果我们想要使用上述方法实现描边,我们就需要写另一种shader——fragment shader。和surface shader相比,这种shader需要我们编写更多的代码,处理更多的事情,但也可以让我们更加了解shader是如何工作的。而之前的一篇文章也分析过,其实surface shader的背后也是生成了对应的vertex&fragment shader。


这篇文章主要参考了Unity Gems里的一篇文章,但正如文章评论里所说,有些技术比如求attenuation稳重方法已经“过时”,因此本文会对这类问题以及一些作者没有说清的问题给予说明。在查资料的时候,发现由于Unity背后做了太多事,定义了很多变量、函数和宏,而又没有给出详尽的使用说明,写起来实在太头大了。。。同样,本篇内容仅供参考。



Vertex & Fragment Shaders


Vertex & Fragment Shaders的工作流程如下图所示(简略版,来自Unity Gems):


所以,看起来也没那么难啦~我们只需要编写两个函数就可以喽~


我们来分析下它的流程。首先,vertex program收到系统传递给它的模型数据,然后把这些处理成我们后续需要的数据(但至少要包含这些顶点的位置信息)进行输出。其他的输出数据比如有,纹理的UV坐标以及其他需要传递给fragment program的数据。然后,系统对vertex program输出的顶点数据进行插值,并将插值结果传递给fragment program。最后,fragment program根据这些插值结果计算最后屏幕上的像素颜色。


在本篇文章,我们首先会学习编写一个简单的diffuse & diffuse bumped shader。然后再来具体看如何编写一个具有多个passes的shader。



Diffuse, Vertex Lit Fragment Shader


开始的开始,我们首先需要在SubShader中使用Pass {}关键字定义一个pass。一个Pass可以为该阶段定义一系列的tags。例如,我们可以剔除(Cull)背面或者正面,控制是否写入Z buffer等。我们的diffuser shader将会剔除背面。具体可见官网


下面是我们的Pass定义:

  1. Pass {
  2. Tags { "LightMode" = "Vertex" }
  3. Cull Back
  4. Lighting OnCGPROGRAM
  5. #pragma vertex vert
  6. #pragma fragment frag
  7. #pragma multi_compile_fwdbase
  8. #include "UnityCG.cginc"
  9. // More code here
  10. ENDCG
  11. }

在上面的代码里,我们定义了一个pass,设定LightMode为Vertex,告诉它打开光源并且剔除背面。然后,我们定义了CG程序的开头部分,指定了vertex和fragment programs的名字。最后,我们包含了Unity定义的一个文件,以便在后面的CG程序中可以使用某些函数和变量。


LightMode是个非常重要的选项,因为它将决定该pass中光源的各变量的值。如果一个pass没有指定任何LightMode tag,那么我们就会得到上一个对象残留下来的光照值,这并不是我们想要的。其他各个LightMode的具体含义可以参见官网(很重要,一定要去看,特别是对于每个Pass的细节解释,一定要点进去看!!!),这里做一个简单的解释。

  • LightMode=Vertex:会设置4个光源,并按亮度从明到暗进行排序,它们的值会存储在unity_LightColor[n], unity_LightPosition[n], unity_LightAtten[n]这些数组中。因此,[0]总会得到最亮的光源。

  • LightMode=ForwardBase: _LightColor0将会是主要的directional light的颜色。

  • LightMode=ForwardAdd:和上面一样, _LightColor0将是该逐像素光源的颜色。



Vertex Lit是什么


在我们写shader的时候有很多选择——我们可以定义多个passes,其中每一个pass处理一个光源,这样来处理所有的光源;或者我们选择逐顶点处理所有的光源(在一个pass里处理掉),然后再对它们进行插值。很明显,后面这种方式会快很多,因为它仅仅需要一个pass就可以了,而前一个方式需要更多的passes。


如果我们写了一个Vertex Lit shader,那么我们就会按照第二种方式那样,一次考虑所有的光源对顶点的影响。如果我们写了一个多passes的shader,那么它就会被多次调用,每次针对一个光源,考虑该光源对模型的影响。


对于Vertex Lit,Unity已经为我们编写了一些辅助函数,我们会在后面看到。



The Vertex Program


下面,我们正式开始编写代码。首先,我们需要定义vertex program。而它需要得到模型的相关信息作为输入,因此,我们定义下面的结构:

  1. struct a2v {
  2. float4 vertex : POSITION;
  3. float3 normal : NORMAL;
  4. float4 texcoord : TEXCOORD0;
  5. };

这个结构定义依赖某些语法,即那些“:XXX”样子的值。我们的变量叫什么并不重要,但这些“:XXX”语法则说明系统将使用哪些值去填充它们。这里,我们通过上述代码可以得到了model space中的顶点位置、法线方向以及纹理坐标。


在fragment shaders里,空间(spaces)的概念是非常重要的。空间重要是指坐标的相对位置。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/107708
推荐阅读
相关标签
  

闽ICP备14008679号