赞
踩
三个月以前,在一篇讲卡通风格的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的工作流程如下图所示(简略版,来自Unity Gems):
所以,看起来也没那么难啦~我们只需要编写两个函数就可以喽~
我们来分析下它的流程。首先,vertex program收到系统传递给它的模型数据,然后把这些处理成我们后续需要的数据(但至少要包含这些顶点的位置信息)进行输出。其他的输出数据比如有,纹理的UV坐标以及其他需要传递给fragment program的数据。然后,系统对vertex program输出的顶点数据进行插值,并将插值结果传递给fragment program。最后,fragment program根据这些插值结果计算最后屏幕上的像素颜色。
在本篇文章,我们首先会学习编写一个简单的diffuse & diffuse bumped shader。然后再来具体看如何编写一个具有多个passes的shader。
开始的开始,我们首先需要在SubShader中使用Pass {}关键字定义一个pass。一个Pass可以为该阶段定义一系列的tags。例如,我们可以剔除(Cull)背面或者正面,控制是否写入Z buffer等。我们的diffuser shader将会剔除背面。具体可见官网。
下面是我们的Pass定义:
- Pass {
- Tags { "LightMode" = "Vertex" }
- Cull Back
- Lighting OnCGPROGRAM
-
- #pragma vertex vert
- #pragma fragment frag
-
- #pragma multi_compile_fwdbase
-
- #include "UnityCG.cginc"
-
- // More code here
-
- ENDCG
- }
在上面的代码里,我们定义了一个pass,设定LightMode为Vertex,告诉它打开光源并且剔除背面。然后,我们定义了CG程序的开头部分,指定了vertex和fragment programs的名字。最后,我们包含了Unity定义的一个文件,以便在后面的CG程序中可以使用某些函数和变量。
LightMode是个非常重要的选项,因为它将决定该pass中光源的各变量的值。如果一个pass没有指定任何LightMode tag,那么我们就会得到上一个对象残留下来的光照值,这并不是我们想要的。其他各个LightMode的具体含义可以参见官网(很重要,一定要去看,特别是对于每个Pass的细节解释,一定要点进去看!!!),这里做一个简单的解释。
在我们写shader的时候有很多选择——我们可以定义多个passes,其中每一个pass处理一个光源,这样来处理所有的光源;或者我们选择逐顶点处理所有的光源(在一个pass里处理掉),然后再对它们进行插值。很明显,后面这种方式会快很多,因为它仅仅需要一个pass就可以了,而前一个方式需要更多的passes。
如果我们写了一个Vertex Lit shader,那么我们就会按照第二种方式那样,一次考虑所有的光源对顶点的影响。如果我们写了一个多passes的shader,那么它就会被多次调用,每次针对一个光源,考虑该光源对模型的影响。
对于Vertex Lit,Unity已经为我们编写了一些辅助函数,我们会在后面看到。
下面,我们正式开始编写代码。首先,我们需要定义vertex program。而它需要得到模型的相关信息作为输入,因此,我们定义下面的结构:
- struct a2v {
- float4 vertex : POSITION;
- float3 normal : NORMAL;
- float4 texcoord : TEXCOORD0;
- };
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。