写在前面

写这篇文章的时候,我断断续续学习Unity Shader半年了,其实还是个门外汉。我也能体会很多童鞋那种想要学好Shader却无从下手的感觉。在这个期间,我找到一些学习Shader的教程以及一些书籍。我整理在这篇博客里。

什么是Shader

Shader,也就是着色器,它的工作就是读取你的网格并渲染在屏幕上。Shader可以定义一些属性,你会用它来影响渲染模型时所显示的效果。当存储了这些属性的设置时,就是一个Material,材质。
Shader有以下几个种类:
  • Surface Shaders —— 也称为表面着色器。这大概是Unity的骄傲。它去除了大部分“麻烦的工作”,可以适用于很多情况下
  • Fragment Shaders —— 片段着色器。它允许你做更多的工作,但也更难写,而且它还让我们可以做低层的一些东西,像顶点光照,这对于移动设备和多个通道(passes)所必需的更高级的效果会非常有用。

Shader学习资料

嘿嘿,当然还有我正在学的《Unity Shaders and Effects Cookbook》一书(专栏)。
以下将对Surface Shaders进行概述。其中主要参考了Unity Gems

Shader管道

Shader的工作是把一系列几何图形变成2D屏幕上的像素颜色。一个Surface Shader看起来像这样:
但要注意,当你的函数退出之后,并不是得到像素的最终颜色,你还可以通过传递一个法线向量来影响光照。
Fragment Shaders也有类似的管道,但必须包含其中的Vertex Function,并在其中需要做大量的工作以为下一步像素颜色的计算做准备。而Surface Shaders则隐藏了这一步。
以上就是你的Surface Shaders的代码是如何被调用。Surface Shaders的代码需要看起来像这样:
总结一下就是:首先需要定义一些Properties,然后将有一个或多个Subshaders。使用的subshader将取决于所运行的平台。除此之外,还应该指定一个Fallback Shader,如果你的subshaders都不可以在目标设备上运行,那么就会调用这个备用Shader。
每个subshader都至少具有一个Pass,它读入数据处理后再输出数据。你可以使用这些passes执行不同的操作。例如,在Grab Pass中你可以捕捉到显示屏上已经呈现的像素。通过这种方法可以得到一些高级的屏幕效果。另外需要有多个passes的情况是,你需要处理不同的事情,像在创建效果的不同阶段写入或者忽略缓冲区。
当你编写一个Surface Shader时,是直接写在Subshader里面的。系统将会把你的代码编译成多个passes。
虽然着色器是写入到一个2D屏幕上,但是它也保持了每个像素距离摄像机的远近 —— 以此来避免后续渲染的对象在空间上位于已渲染对象的后面,但却覆盖了之前像素的情况出现。

你可以使用Pass中的语句来控制​​这个Z方向的缓存是否对你的着色器代码有影响,或者你的Shader是否写入该缓冲区内,例如: Zwrite Off表明不会更新你的任何输出像素的Z方向的缓存区。

您可以使用这种技术来在其他对象上打孔 —— 通过写入Z缓冲区,但不输出任何实际的像素颜色,那么使用这个Shader的模型背后的对象将不能被写入(因为Z缓冲区中有距离更近的像素了),这样看起来这个对象就像是被打了洞一样。

下面展示了一个最简单的Surface Shader:

下一次,将对上面代码中的几个部分分别做出说明:Properties,Tags,SubShader的结构,Input结构体,surf函数等。

理解Shader代码

接下来,我们将概要地解释Shader中各个部分的作用,进一步理解Shader。

Properties

由上面的代码可以看出,Shader的第一行指明了Shader的名字:“Example/Diffuse Texture”。紧跟着就是Shader的第一个重要组成部分,Properties{ }。我们可以在Properties里面定义一系列属性,这些属性被下面的SubShader所共享,它们可以让你在Unity的面板里直观地控制Shader变量,影响渲染结果。
这些属性的定义格式如下:
_Name ( "Displayed Name", type ) = default value {options}

  • _Name:程序中引用的名字,和我们一般理解的变量名称是一样的。
  • Displayed Name:这个字符串将会出现在Unity材质的编辑面板上。
  • type:该属性的类型。Unity支持以下几种属性类型:
    • Color:表示一个单一的RGBA颜色值;
    • 2D: 表示一张大小为2的次方的纹理贴图,可以使用基于模型UV坐标来进行采样;
    • Rect:表示一张纹理不是2的次方的纹理贴图;
    • Cube:表示一个可用于反射的3D立方体映射贴图,可以进行采样;
    • Range(min, max):一个取值范围在min到max之间的浮点值;
    • Float: 一个可以为任意值的浮点值;
    • Vector:一个4维度的向量。
  • default value:该属性的默认值。
    • Color:使用浮点值表示的(r, g, b, a),例如(1,1,1,1);
    • 2D/Rect/Cube:对于贴图类型的属性,默认值可以是一个空字符串,或者"white", "black", "gray", "bump"这样的字符串;
    • Float/Range:在此范围内的值即可;
    • Vector:以(x,y,z,w)形式表示的4D向量;
  • { options }:只和纹理类型的2D、Rect和Cube相关,它必须至少被指定为{ }。你可以使用空格分隔多个选项,有如下选择:
    • TexGen贴图生成模式:该纹理的自动纹理坐标生成模式。可以为ObjectLinear, EyeLinear, SphereMap, CubeReflect, CubeNormal。这些直接对应了OpenGL中的texgen modes。注意,如果你编写了一个顶点函数,那么可以忽略TexGen。
下面的实例展示了Properties的写法:
//Define a color with a default value of semi-transparent red
_MainColor ("Main Color", Color) = (1,0,0,0.5)
//Define a texture with a default of white
_Texture ("Texture", 2D) = "white" {}

注意,在定义属性时不需要在末尾添加分号";"。

Tags

我们的Surface Shader可以使用Tags进行装饰。这些Tags可以告诉硬件应该什么时候调用你的Shader。
在上述例子里,有:Tags { "RenderType" = "Opaque" },该指令告诉系统在渲染Opaque几何图形时调用我们的Shader。Unity定义了一系列这样的队列。另一个常用的是"RenderType" = "Transparent",这表明你的Shader可能会输出一些半透明或全透明的像素。
其他的tags也同样非常有用,例如"IgnoreProjector"="True",这表明你的对象不会受投影和"Queue"="xxxx"的影响。
Queue tag可以指定何时渲染你的Shader,详情可参见这篇文章

Shader结构

我们来看Shader下面的部分。
可以看出,这部分代码使用CG语言写的,它有点像C语言。你可以去查看Nvidia的文档去了解更多内容。这里将会进行简单的介绍。
其中很重要的变量为float类型和向量(vec)类型,这两种类型可以紧跟一个2、3或者4进行定义。这种类型在图形处理里面非常常见。
//Define a float variable
vec2 coordinate;
//Define a color variable
float4 color;
//Multiply out a color
float3 multipliedColor = color.rgb * coordinate.x;

我们可以使用.xyzw和.rgba符号访问存储在这些变量中的值(颜色、位置、法线等)。

我们还会遇到half和double类型。和字面表示的一样,这两种类型分别是普通float类型大小的一半和两倍。half通常被用于性能限制的情况下。还有一种类型是fixed,它表示固定小数的实数。

从Surface Shader输出信息

因此,每个像素都会调用我们的Surface Shader。系统在模型的每个面片上进行插值,为我们的输入结构设置了当前值。
surf函函数和下面类似:
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
}

显然,我们是为SurfaceOutput中的Albedo属性进行了设置。SurfaceOutput是Unity为我们定义的一种结构体,它的定义如下:

struct SurfaceOutput {
half3 Albedo; //The color of the pixel
half3 Normal; //The normal of the pixel
half3 Emission; //The emissive color of the pixel
half Specular; //Specular power of the pixel
half Gloss; //Gloss intensity of the pixel
half Alpha; //Alpha value for the pixel
};

我们只需要填充上述成员变量,Unity在自动生成passes时会自行判断如何使用它们。

下面我们来解释Shader代码中最关键的部分。
首先,是surf函数的输入参数Input结构:
struct Input {
float2 uv_MainTex;
};

只需要通过Input结构体,我们就可以告诉系统,为当前正在处理的像素得到在MainTex中对应的纹理坐标。如果不止一张纹理,例如还有一张_OtherTexture,我们只需要添加如下代码:

struct Input {
float2 uv_MainTex;
float2 uv_OtherTexture;
};

如果对于这种纹理,我们需要第二组uv坐标,可以添加如下代码:

struct Input {
float2 uv_MainTex;
float2 uv2_OtherTexture;
};

Input结构体通常包含了所有贴图的uv或uv2坐标。但是,如果我们的Shader比较复杂,并且需要了解像素渲染的其他信息,我也就可以通过将其包括在Input结构体中来要求得到这些变量。

  • float3 viewDir:视角方向,用于计算视差效果和边缘照明等;
  • 使用COLOR语义的float4:包含了插值后的逐顶点颜色;
  • float4 screenPos:屏幕空间中的位置;
  • float3 worldPos:世界空间中的位置;
  • float3 worldRefl:如果Surface Shader没有改写o.Normal,将包含了环境反射向量;
  • float3 worldNormal:如果Surface Shader没有改写o.Normal,将包含了环境法线向量;
  • INTERNAL_DATA:当我们需要改写o.Normal时,一些函数,如WorldNormalVector等,需要该变量进行计算;
最后,我们来解释下面这句话:
Sampler2D _MainTex;

对于Properties中定义的每一个属性,你都需要在CG程序中声明一个变量来访问它。而且该变量和属性的名称必须完全一样。

当该属性是一张文字,而你需要在Input结构中得到对应的uv坐标时,uv或者uv2后面的名称也必须和属性名相同。

在上图中,_MainTex对应了一张纹理贴图,它对应的变量类型就是sampler2D类型,只要得到了一个uv坐标,我们就可以在贴图上进行采样得到颜色值。

surf函数中仅用了一个函数:

o.Albedo = tex2d( _MainTex, IN.uv_MainTex).rgb;

它使用Input中得到的该像素对应的_MainTex中的uv坐标,在_MainTex进行采样,得到一个float4类型的颜色值(包括了透明通道)。如果我们需要得到透明通道的值,可以这样做:

float4 texColor = tex2d( _MainTex, IN.uv_MainTex );
o.Albedo = texColor.rgb;
o.Alpha = texColor.a;

总结

呼呼呼,本文主要介绍了Unity中Shader的两种主要类型,并对其他的一种类型Surface Shader做了一个初步的概述。希望通过本文,大家可以有一个大概的认识,当然,深入的学习和理解还有很长的一段路要走啊。

【Unity Shaders】Shader学习资源和Surface Shader概述的更多相关文章

  1. 【Unity Shaders】Diffuse Shading——在Surface Shader中使用properties

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  2. 【Unity Shaders】Diffuse Shading——向Surface Shader添加properties

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  3. 【Unity Shaders】学习笔记——SurfaceShader(十一)光照模型

    [Unity Shaders]学习笔记——SurfaceShader(十一)光照模型 转载请注明出处:http://www.cnblogs.com/-867259206/p/5664792.html ...

  4. 【Unity Shaders】学习笔记——SurfaceShader(十)镜面反射

    [Unity Shaders]学习笔记——SurfaceShader(十)镜面反射 如果你想从零开始学习Unity Shader,那么你可以看看本系列的文章入门,你只需要稍微有点编程的概念就可以. 水 ...

  5. 【Unity Shaders】学习笔记——SurfaceShader(九)Cubemap

    [Unity Shaders]学习笔记——SurfaceShader(九)Cubemap 如果你想从零开始学习Unity Shader,那么你可以看看本系列的文章入门,你只需要稍微有点编程的概念就可以 ...

  6. 【Unity Shaders】学习笔记——SurfaceShader(七)法线贴图

    [Unity Shaders]学习笔记——SurfaceShader(七)法线贴图 转载请注明出处:http://www.cnblogs.com/-867259206/p/5627565.html 写 ...

  7. 【Unity Shaders】学习笔记——SurfaceShader(六)混合纹理

    [Unity Shaders]学习笔记——SurfaceShader(六)混合纹理 转载请注明出处:http://www.cnblogs.com/-867259206/p/5619810.html 写 ...

  8. 【Unity Shaders】学习笔记——SurfaceShader(五)让纹理动起来

    [Unity Shaders]学习笔记——SurfaceShader(五)让纹理动起来 转载请注明出处:http://www.cnblogs.com/-867259206/p/5611222.html ...

  9. 【Unity Shaders】学习笔记——SurfaceShader(四)用纹理改善漫反射

    [Unity Shaders]学习笔记——SurfaceShader(四)用纹理改善漫反射 转载请注明出处:http://www.cnblogs.com/-867259206/p/5603368.ht ...

随机推荐

  1. [Noi2016]网格

    来自FallDream的博客,未经允许,请勿转载,谢谢.   跳蚤国王和蛐蛐国王在玩一个游戏. 他们在一个 n 行 m 列的网格上排兵布阵.其中的 c 个格子中 (0≤c≤nm),每个格子有一只蛐蛐, ...

  2. (转)Ensemble2015安装

    1 IIS安装和windows系统配置 1.1 IIS安装 检查是否安装好了IIS,可在[管理工具]的[服务管理器]中查看,如下图所示表示安装了IIS.   确认IIS已完全安装,点击上图中的Web服 ...

  3. 使用JdbcTemplate 操作PostgreSQL,当where条件中有timestamp类型时,报错operator does not exist: timestamp w/out timezone

    今天遇到一个问题,找了还半天,Google一下,官网显示是一个bug. 思考一番肯定是类型出了问题. Controller: Service:转化时间戳 Dao: 一波转换搞定!

  4. vue+cordova 构建hybrid app

    配了一个 vue + cordova + ionicCli 的 项目 支持 ionic 的脚手架命令 支持 cordova 的 插件 安装使用 支持 webpack 的自动构建 vue 安装了 vue ...

  5. 正则替换replace中$1的用法以及常用正则

    一.repalce定义 用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串. stringObject.replace(regexp/substr,replacement)参数一 ...

  6. C# WMI 远程PC(开机、关机、重启)

    啥也不多说,直接上码: //远程重启方法 public static bool Shutdown(ManagementScope scope) { ObjectQuery query=new Obje ...

  7. Django的配置文件(settings)

    静态文件设置: 一.概述: #静态文件交由Web服务器处理,Django本身不处理静态文件.简单的处理逻辑如下(以nginx为例): # URI请求-----> 按照Web服务器里面的配置规则先 ...

  8. Java语言程序设计-助教篇

    1. 给第一次上课(软件工程)的老师与助教 现代软件工程讲义 0 课程概述 给学生:看里面的第0个作业要求 2. 助教心得 美国视界(1):第一流的本科课堂该是什么样?(看里面的助教部分) 助教工作看 ...

  9. Sqoop-1.4.6 Merge源码分析与改造使其支持多个merge-key

    Sqoop中提供了一个用于合并数据集的工具sqoop-merge.官方文档中的描述可以参考我的另一篇博客Sqoop-1.4.5用户手册. Merge的基本原理是,需要指定新数据集和老数据集的路径,根据 ...

  10. DragVideo,一种在播放视频时,可以任意拖拽的方案

    转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/53638896 前言 项目已开源到 ...