Unity shader学习---表面着色器探秘

Unity Shader分为表面着色器(Surface Shader)、顶点着色器(Vertex Shader)和片段着色器(Fragment Shader),这篇文档会为大家介绍表面着色器。

一.结构

和顶点/片元着色器不同的是,表面着色器的CG代码是直接而且也必须写在SubShader中,Unity会在背后为我们生成多个Pass。当然,可以在SubShader一开始处使用Tags来设置该表面着色器使用的标签。
对顶点着色器和片元着色器的进一层封装。主要部分为两个结构体(Input、SurfaceOutput)编译指令(#pragma surface)。其中两个结构体是表面着色器中不同函数之间信息的传递的桥梁,而编译指令是我们和Unity沟通的重要手段。

表面着色器包括4个函数:
(1): 顶点变换函数;
(2): 表面着色函数;
(3): 光照模型;
(4): 最终颜色修改函数;

1.1.编译指令

表面着色器放在CGPROGRAM .. ENDCG块里面,表面着色器必须嵌在子着色器(SubShader)块里面,而不是Pass{}里面。因为表面着色器(Surface Shader)将在多重通道(multiple passes)内编译自己,而不是放在某个Pass中。
编译指令最重要的作用是指明该表面着色器使用的表面函数光照函数,并设置一些可选参数。表面着色器的CG块中的第一句代码往往就是它的编译指令。一般格式如下:

#pragma surface surfaceFunction lightMode[optionalparams]

surfaceFunction:表示指定名称的Cg函数中有表面着色器(surface shader)代码。这个函数的格式应该是这样:void surf(Input IN,inout SurfaceOutput o),其中Input是我们自己定义的结构。Input结构中应该包含所需的纹理坐标(texture coordinates)和表面函数(surfaceFunction)所需要的额外的必须变量。

void surf (Input IN,inout SurfaceOutput o)  
void surf (Input IN,inout SurfaceOutputStandard o)  
void surf (Input IN,inout SurfaceOutputStandardSpecular o)  

SurfaceOutput、SurfaceOutputStandard 和 SurfaceOutputStandardSpecular 都是Unity 内置的结构体,它们需要配合不同的光照模型使用。

LightModel: 使用的光照模式。内置一些基于物理的Standard和StandardSpecular光照模型,以及一些没有基于物理的Lambert(Diffuse)和BlinnPhong(specular)光照模型,当然也可以自己写光照模型。
例如,可以使用下面的函数来定义用于前向渲染中的光照函数:

//用于不依赖视角的光照模型,例如漫反射  
half4 Lighting<Name> (SurfaceOutput s, half3 lightDir, half atten);  
//用于依赖视角的光照模型,例如高光反射  
half4 Lighting<Name> (SurfaceOutput s, half3 lightDir, half3 viewDir,half atten);  

half4 Lighting<Name>(SurfaceOutput s, half4 light);

注意:要使用Lambert,就要搭配用SurfaceOutput,不能用SurfaceOutputStandard,Standard光照模型使用SurfaceOutputStandard作为输出结构体

在编译指令的最后,我们还可以设置一些可选参数。这些可选参数包含了很多非常有用的指令类型,例如,开启/关闭透明度混合/测试,指明自定义的顶点和颜色修改函数,控制生成的代码等。
可选参数
1:alpha: Alpha 混合模式,用户半透明着色器;
2: alphatest: varirableName Alpha测试模式,用户透明镂空着色器。
3: exclude_path:prepass 使用指定的渲染路径;
4: addshadow: 添加阴影投射器和集合通道;
5: dualforward: 将双重光照贴图用于正向渲染路径中;
6: fullforwardshadows 在正向渲染路径中支持的所有的阴影类型,在前向渲染路径中支持所有光源类型的阴影。默认是只有平行光,添加这个参数就可以让点光或聚光灯有阴影渲染。
7: decal: add 附加印花着色器;
8: decal: blend 附加半透明印花着色器;
9: softvegetation 使用表面着色器,仅在Soft Vegetation 开启时被渲染;
10: noambient 不使用任何光照
11: novertexlights 在正向渲染中不适用球面调和光照或逐点光照;
12: nolightmap 在这个着色器上禁用光照贴图;
13: nodirlightmap 在这个着色器上禁用方向光照贴图;
14: noforwardadd 禁用正向渲染添加通道,去掉所有前向渲染的额外Pass,即支持逐像素平行光,其他光源用逐顶点或SH计算。;
15: approxview: 对于有需要的着色器,逐顶点而不是逐像素计算规范化视线方向。
16: halfasview: 将半方向传递到光照函数中。
17: fullforwardshadows:在前向渲染路径中支持所有光源类型的阴影。默认是只有平
行光,添加这个参数就可以让点光或聚光灯有阴影渲染。

18: noshadow:取消所有阴影。
19: exclude_path:deferred, exclude_path:forward, exclude_path:prepass :不需要为特定渲染路径生成代码。

自定义修改函数
除了表面函数和光照模型外,表面着色器还可以支持其他两种自定义的函数:顶点修改函数和最后的颜色修改函数。顶点修改函数允许我们自定义一些顶点属性,例如,把顶点颜色传递给表面函数,或是修改顶点位置,实现某些顶点动画等。最后的颜色修改函数则可以在颜色绘制到屏幕前,最后一次修改颜色值,例如实现自定义的雾效等。
1.vertex:VertexFunction:顶点修改,实现一些顶点动画等。
2.finalcolor:ColorFunction:最终颜色修改,实现雾效等。
3.finalgbuffer:ColorFunction:延迟渲染修改,实现边缘检测等。
4.finalprepass:ColorFunction:prepass base路径修改

1.2.两个结构体

Input结构体 包含了许多表面属性的数据来源,因此,它会作为表面函数的输入结构体。Input支持很多内置的变量名,通过这些变量名,我们告诉Unity需要使用的数据信息。下表给出了Input结构体中内置的变量。

需要注意的是,我们并不需要自己计算上述的各个变量,而只需要在Input结构体中按上述名称严格声明这些变量即可,Unity会在背后为我们准备好这些数据,而我们只需要在表面函数中直接使用它们即可。一个例外的情况是,我们自定义了顶点修改函数,并需要向表面函数中传递一些自定义的数据。例如,为了自定义雾效,我们可能需要在顶点修改函数中根据顶点在视角空间下的位置信息计算雾效混合系数,这样我们就可以在Input结构体中定义一个名为half fog 的变量,把计算结果存储在该变量后进行输出。

有了Input结构体来提供所需要的数据后,我们就可以据此计算各种表面属性。因此,另一个结构体就是用于存储这些表面属性的结构体,即SurfaceOutput、SurfaceOutputStandard 和 SurfaceOutputStandardSpecular,它会作为表面函数的输出,随后会作为光照函数的输入来进行各种光照计算。相比于Input结构体的自由性,这个结构体里面的变量是提前声明好的,不可以增加也不会减少。SurfaceOutput 的声明可以在Lighting.cginc文件中找到:

struct SurfaceOutput {  
    fixed3 Albedo;  
    fixed3 Normal;  
    fixed3 Emission;  
    half Specular;  
    fixed Gloss;  
    fixed Alpha;  
};
struct SurfaceOutputStandard  
{  
    fixed3 Albedo;      // base (diffuse or specular) color  
    fixed3 Normal;      // tangent space normal, if written  
    half3 Emission;  
    half Metallic;      // 0=non-metal, 1=metal  
    half Smoothness;    // 0=rough, 1=smooth  
    half Occlusion;     // occlusion (default 1)  
    fixed Alpha;        // alpha for transparencies  
};  
struct SurfaceOutputStandardSpecular  
{  
    fixed3 Albedo;      // diffuse color  
    fixed3 Specular;    // specular color  
    fixed3 Normal;      // tangent space normal, if written  
    half3 Emission;  
    half Smoothness;    // 0=rough, 1=smooth  
    half Occlusion;     // occlusion (default 1)  
    fixed Alpha;        // alpha for transparencies  
};  

在一个表面着色器中,只需要选择上述三者之一即可,这取决于我们选择使用的光照模型。Unity内置的光照模型有两种,一种是Unity5 之前的、简单的、非基于物理的光照模型,包含了Lambert 和 BlinnPhong;另一种是Unity5 添加的、基于物理的光照模型,包括Standard 和 StandardSpecular ,这种模型会更加符合物理规律,但计算也会复杂很多。如果使用了非基于物理的光照模型,就使用SurfaceOutputStad,否则分别使用SurfaceOutputStandard 和SurfaceOutputStandardSpecular 。其中,SurfaceOutputStandard 结构体用于默认的金属工作流程,对应了Standard 光照函数;而SurfaceOutputStandardSpecular 结构体用于高光工作流程,对应了StandardSpecular 光照函数。
在SurfaceOutput结构体中,部分表面属性有:
1)fixed3 Albedo 对光源的反射率。通常由纹理采样和颜色属性的乘积计算而得。
2)fixed3 Normal 表面法线方向
3)fixed3 Emission 自发光。Unity 通常会在片元着色器最后输出前,使用类似下面的语句进行简单的颜色相加。

c.rgb += o.Emission;  

4)half Specular 高光反射中的指数部分的系数,影响高光反射的计算。例如,如果使用了内置的BlinnPhong 光照函数,它会使用如下语句计算高光反射的强度:

float spec = pow(nh,s.Specular*128.0)*s.Gloss;  

5) fixed Gloss 高光反射中的强度系数。一般在包含了高光反射的光照模型里使用
6) fixed Alpha 透明通道

二.Unity 背后做了什么

Unity在背后会根据表面着色器生成一个包含了很多Pass的顶点/片元着色器。

这些Pass有些是为了针对不同的渲染路径,例如,默认情况下Unity 会为前向渲染路径生成LightMode 为 ForwardBase 和 ForwardAdd 的Pass,为Unity 5 之前的延迟渲染路径生成LightMode 为PrePassBase 和 PrePassFinal 的Pass,为Unity5之后的延迟渲染路径生成LightMode 为 Deferred 的Pass。

还有一些Pass 是用于产生额外的信息。例如,为了给光照映射和动态全局光照提取表面信息,Unity 会生成一个LightMode 为 Meta 的Pass。这些Pass 的生成都是基于我们再表面着色器中的编译指令和自定义的函数,这是由规律可循的。Unity 提供了一个功能,让我们可以对表面着色器自动生成的代码一探究竟:在每个编译完成的表面着色器的面板上,有一个“Show generated code” 按钮,如下图所示。我们只需要单击一下就可以看到Unity为这个表面着色器生成的所有顶点/片元着色器。

以Unity生成的LightMode 为ForwardBase 的Pass为例,它的渲染流水线如下图所示:


表面着色器的渲染计算流水线,黄色的表示可以自定义的函数,灰色表示unity自动生成的计算步骤

Unity对该Pass的自动生成过程大致如下:

  1. 将表面着色器中CGPROGRAM和ENDCG之间的代码复制过来。
  2. Unity根据上述代码生成结构体v2f_surf(顶点着色器的输出)。如果Input定义了一些变量但没有使用,生成的结构体也不会包含该变量。还会包含阴影纹理坐标、光照纹理坐标、逐顶点光照等。
  3. 生成顶点着色器。
      3.1. 如果定义了顶点修改函数,会先调用,或填充自定义Input结构体中的变量。Unity会分析该函数修改的数据,通过Input结构体把修改结果存储到v2f_surf相应变量。
      3.2. 计算v2f_surf中其他变量:顶点位置、纹理坐标、法线方向、逐顶点光照、光照纹理等。
      3.3. 把v2f_surf传递给片元着色器。
  4. 生成片元着色器。
      4.1. 将v2f_surf变量(纹理坐标、视角方向)填充到Input结构体。
      4.2. 调用自定义表面函数,填充SurfaceOutput结构体。
      4.3. 调用光照函数得到初始的颜色值。如果使用内置的Lambert或BlinnPhong光照函数,Unity还会计算动态全局光照,并添加到光照模型的计算。
      4.4. 进行其他颜色叠加。例如没有光照烘培,会添加逐顶点光照的影响。
      4.5. 调用最后的颜色修改函数。

三.CG函数讲解

UnpackNormal()函数
UnpackNormal接受一个fixed4的输入,并将其转换为所对应的法线值(fixed3),并将其赋值给输出的Normal,就可以参与到光线运算中完成接下来的的渲染工作。
调用示例:

o.Normal = UnpackNormal(tex2D(_BumpMap,IN.uv_BumpMap));

saturate()函数
saturate函数的作用是将取值转化为[0,1]之内的一个值。其可选的原型如下:

float saturate(float x);
float1 saturate(float1 x);
float2 saturate(float2 x);
float3 saturate(float3 x);
float4 saturate(float4 x);
half saturate(half x);
half1 saturate(half1 x);
half2 saturate(half2 x);
half3 saturate(half3 x);
half4 saturate(half4 x);
fixed saturate(fixed x);
fixed1 saturate(fixed1 x);
fixed2 saturate(fixed2 x);
fixed3 saturate(fixed3 x);
fixed4 saturate(fixed4 x);

返回值:
如果x取值小于0,则返回值为0
如果x取值大于1,则返回值为1
若x在0到1之间,则直接返回x的值

dot()函数
dot函数的作用用于返回两个向量的标量积,可选原型如下:

float dot(float a, float b);
float dot(float1 a, float1 b);
float dot(float2 a, float2 b);
float dot(float3 a, float3 b);
float dot(float4 a, float4 b);
half dot(half a, half b);
half dot(half1 a, half1 b);
half dot(half2 a, half2 b);
half dot(half3 a, half3 b);
half dot(half4 a, half4 b);
fixed dot(fixed a, fixed b);
fixed dot(fixed1 a, fixed1 b);
fixed dot(fixed2 a, fixed2 b);
fixed dot(fixed3 a, fixed3 b);
fixed dot(fixed4 a, fixed4 b);

tex2D()函数
该函数用于2D纹理采样,其可选原型有

float4 tex2D(sampler2D samp, float2 s)
float4 tex2D(sampler2D samp, float2 s, inttexelOff)
float4 tex2D(sampler2D samp, float3 s)
float4 tex2D(sampler2D samp, float3 s, inttexelOff)
float4 tex2D(sampler2D samp, float2 s,float2 dx, float2 dy)
float4 tex2D(sampler2D samp, float2 s,float2 dx, float2 dy, int texelOff)
float4 tex2D(sampler2D samp, float3 s,float2 dx, float2 dy)
float4 tex2D(sampler2D samp, float3 s,float2 dx, float2 dy, int texelOff)
int4 tex2D(isampler2D samp, float2 s)
int4 tex2D(isampler2D samp, float2 s, inttexelOff)
int4 tex2D(isampler2D samp, float2 s,float2 dx, float2 dy)
int4 tex2D(isampler2D samp, float2 s,float2 dx, float2 dy, int texelOff)
unsigned int4 tex2D(usampler2D samp, float2s)
unsigned int4 tex2D(usampler2D samp, float2s, int texelOff)
unsigned int4 tex2D(usampler2D samp, float2s, float2 dx, float2 dy)
unsigned int4 tex2D(usampler2D samp, float2s, float2 dx, float2 dy,int texelOff)

参数简介
samp - 需要查找采样的对象
s - 需进行查找的纹理坐标
dx - 预计算的沿x轴方向的导数
dy - 预计算的沿y轴方向的导数
texeloff - 添加给最终纹理的偏移量
其返回值为查找到的纹理

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342