本文章用于帮助自己学习,因此只记录一些个人认为比较重要或者还不够熟悉的内容。
原作者:http://blog.csdn.net/candycat1992/article/

第五章 开始Unity Shader学习之旅

5.1一个最简单的顶点/片元着色器

5.1.1顶点/片元着色器基本结构

Shader “MyShaderName” {Properties {//属性}SubShader{//针对显卡A的SubShaderPass {//设置渲染状态和标签//开始CG代码片段CGPROGRAM//该代码片段的编译指令,例如:#pragma vertex vert#pragma fragment frag// CG代码写在这里//结束CG代码片段ENDCG//其他设置}//其他需要的Pass}SubShader{//针对显卡B的SubShader}//上述SubShader都失败后用于回调的Unity ShaderFallback “VertexLit”
}

其中,最重要的部分是Pass语义块,绝大部分的代码都是写在这个语义块里面的。下面就来创建一个最简单的顶点/片元着色器。

  1. 新建一个场景,把它命名为Scene_5_2。在Unity 的菜单中,选择Window -> Lighting -> Skybox,把该项置为空,去掉天空盒子。
  2. 新建一个 Unity Shader,把它命名为 Chapter5-SimpleShader。
  3. 新建一个材质,把它命名为SimpleShaderMat。把第2步中新建的Unity Shader赋给它。
  4. 新建一个球体,拖曳它的位置以便在Game视图中可以合适地显示岀来。把第3步中新 建的材质拖曳给它。
  5. 双击打开第2步中创建的Unity Shadero删除里面的所有代码,把下面的代码粘贴进去:
 //定义了这个Unity Shader的名字
Shader "Unity Shaders Book/Chapter 5/Simple Shader"
{  SubShader  {  //声明SubShader 和 Pass 语义块Pass  {  CGPROGRAM  //告诉Unity,哪个函数包含了顶点着色器的代码//,哪个函数包含了片元着色器的代码。//其中vert和frag就是指定的函数名,可以是任意自定义的合法函数名。#pragma vertex vert  #pragma fragment frag  //POSITON和SV_POSITION  都是CG/HLSL中的语义(semantics)//POSITON将告诉Unity,把模型的顶点坐标填充到输入参数v中//SV_POSITION将告诉Unity, 顶点着色器的输出是裁剪空间中的顶点坐标。float4 vert(float4 v : POSITION) : SV_POSITION  {  //顶点坐标从模型空间转换到裁剪空间中。//等同于return mul(UNITY_MATRIX_MVP, v);//UNITY_MATRIX_MVP 矩阵是unity内置的模型·观察·投影矩阵。return UnityObjectToClipPos(v);}  //片元着色器代码 //SV_Target也是HLSL 中的一个系统语义//,告诉渲染器,把用户的输出颜色存储到一个渲染目标中fixed4 frag() : SV_Target  {  //直接返回一个白色 return fixed4(1.0,1.0,1.0,1.0);}  ENDCG  }  }
}  

5.1.2模型数据以及着色器通信

在上面的例子中,在顶点着色器中我们使用POSITION语义得到了模型的顶点位置。现在,我们想要得到模型上每个顶点的纹理坐标和法线方向,需要为顶点着色器定义一个新的输入参数,这个参数不再是一个简单的数据类型,而是一个结构体。而在实践中,往往希望从顶点着色器输出一些数据,例如把模型的法线、纹理坐标等传递给片元着色器。修改后代码如下:

//定义了这个Unity Shader的名
Shader "Unity Shaders Book/Chapter 5/Simple Shader"
{  SubShader  {  //声明SubShader 和 Pass 语义块Pass  {  CGPROGRAM  #pragma vertex vert  #pragma fragment frag  //使用一个结构体来定义顶点着色器的输入  struct a2v  {  //POSITION 语义告诉Unity,用模型空间的顶点坐标填充vertext变量  float4 vertex : POSITION;  //NORMAL 语义告诉Unity,用模型空间的法线方向填充normal变量  float3 normal : NORMAL;  //TEXTCOORD0 语义告诉Unity,用模型的第一套纹理坐标填充textcoord变量  //float4 texcoord : TEXTCOORD0;}; //使用一个结构体来定义顶点着色器的输出struct v2f {// SV_POSITION语义告诉Unity, pos里包含了顶点在裁剪空间中的位置信息 float4 pos : SV_POSITION;// COLORO语义可以用于存储颜色信息fixed3 color : COLOR0;};v2f vert(a2v v) : SV_POSITION  {  //声明输出结构  v2f o;  //使用v. vertex来访河模型空间的顶点坐标o.pos = UnityObjectToClipPos(v.vertex);  //v.normal 包含了顶点的法线方向,其分量范围在[-1.0, 1.0]  //下面的代码把分量范围映射到了[0.0, 1.0]  //存储到o.color 中传递给片元着色器  o.color = v.normal * 0.5 + fixed3(0.5, 0.5, 0.5); return o;}  //片元着色器代码 //SV_Target  也是HLSL 中的一个系统语义//,告诉渲染器,把用户的输出颜色存储到一个渲染目标中fixed4 frag() : SV_Target  {  //将插值后的i . color显示到屏幕上return fixed4(i.color,1.0);}  ENDCG  }  }
}  

在上面的代码中,我们声明了一个新的结构体v2f。v2f用于在顶点着色器和片元着色器之间传递信息。需要注意的是,顶点着色器是逐顶点调用的而片元着色器是逐片元调用的。片元着色器中的输入实际上是把顶点着色器的输出进行插值后得到的结果。

5.1.3如何使用属性

材质提供给我们一个可以 方便地调节Unity Shader中参数的方式,通过这些参数,我们可以随时调整材质的效果。而这些 参数就需要写在Properties语义块中
现在,我们有了新的需求。我们想要在材质面板显示一个颜色拾取器,从而可以直接控制模型在屏幕上显示的颜色。为此,我们继续修改上面的代码。

//定义了这个Unity Shader的名字
Shader "Unity Shaders Book/Chapter 5/Simple Shader"
{  Properties  {  //声明一个Color 类型的属性,显示名称是Color Tint。_Color ("Color Tint", Color) = (1.0, 1.0, 1.0, 1.0)  } SubShader  {  //声明SubShader 和 Pass 语义块Pass  {  CGPROGRAM  #pragma vertex vert  #pragma fragment frag  //在CG代码中,我们需要定义一个与属性名称和类型都匹配的变量  fixed4 _Color; //使用一个结构体来定义顶点着色器的输入  struct a2v  {  //POSITION 语义告诉Unity,用模型空间的顶点坐标填充vertext变量  float4 vertex : POSITION;  //NORMAL 语义告诉Unity,用模型空间的法线方向填充normal变量  float3 normal : NORMAL;  //TEXTCOORD0 语义告诉Unity,用模型的第一套纹理坐标填充textcoord变量  //float4 texcoord : TEXTCOORD0;}; //使用一个结构体来定义顶点着色器的输出struct v2f {// SV_POSITION语义告诉Unity, pos里包含了顶点在裁剪空间中的位置信息 float4 pos : SV_POSITION;// COLORO语义可以用于存储颜色信息fixed3 color : COLOR0;};v2f vert(a2v v) : SV_POSITION  {  //声明输出结构  v2f o;  //使用v. vertex来访河模型空间的顶点坐标o.pos = UnityObjectToClipPos(v.vertex);  //v.normal 包含了顶点的法线方向,其分量范围在[-1.0, 1.0]  //下面的代码把分量范围映射到了[0.0, 1.0]  //存储到o.color 中传递给片元着色器  o.color = v.normal * 0.5 + fixed3(0.5, 0.5, 0.5); return o;}  //片元着色器代码 //SV_Target  也是HLSL 中的一个系统语义,告诉渲染器,把用户的输出颜色存储到一个渲染目标中fixed4 frag() : SV_Target  {  //将插值后的i . color显示到屏幕上return fixed4(i.color,1.0);}  ENDCG  }  }
}  

在上面的代码中,我们首先添加Properties语义块中,并在其中声明了一个属性_Color,它的类型是Color,初始值是(1.0,1.0,1.0,1.0),对应白色。为了在CG代码中可以访问它,我们还需要在CG代码片段中提前定义一个新的变量,这个变量的名称和类型必须与Properties语义块中的属性定义相匹配

5.2Unity提供的内置文件和变量

5.2.1内置的包含文件

包含文件(include file),是类似于C++中头文件的一种文件。在Unity中,它们的文件后缀是.cginco。在 Windows 上,它们的位置是:Unity 的安装 路径 /Data/CGIncludes。
在编写Shader时,我们可以使用#include指令把这些文件包含进来,这样我们就可以使用Unity为我们提供的一些非常有用的变量和帮助函数。例如:

CGPROGRAM
// ...
#include "UnityCG.cginc"
//...
ENDCG


UnityCG.cginc是我们最常接触的一个包含文件。在后面的学习中,我们将使用很多该文件提供的结构体和函数,为我们的编写提供方便。例如,我们可以直接使用UnityCGcginc中预定义的结构体作为顶点着色器的输入和输出

除了结构体外,UnityCGcginc也提供了一些常用的帮助函数。

5.3Unity提供的CG/HLSL语义

5.3.1什么是语义

语义实际上就是一个赋给Shader输入和输出的字符串,这个字符串表达了这 个参数的含义。
这些语义可以让Shader知道从哪里读取数据,并把数据输出到哪里
在DirectX 10以后,有了一种新的语义类型,就是系统数值语义(system-value semantics)。 这类语义是以SV开头的,SV代表的含义就是系统数值(system-value)。这些语义在渲染流水线中有特殊的含义

5.3.2Unity支持的语义



上面的语义中,除了SV_POSITION是有特别含义外,其他语义对变量的含义没有明确要求。也就是说,我们可以存储任意值到这些语义描述变量中。通常,如果我们需要把一些自定义的数据从顶点着色器传递给片元着色器,一般选用TEXCOORD0等。

5.3.3如何定义复杂的变量类型

一个语义可以使用的寄存器只能处理4个浮点值(float)。因此,如果想要定义矩阵类型,如float3X、float4X4等变量就需要使用更多的空间。
一种方法是,把这些变量拆分成多个变量,例如对于 float4X4的矩阵类型,可以拆分成4个,float4类型的变量,每个变量存储了矩阵中的一行数据。

5.4DeBug

5.4.1使用假彩色图像

主要思想是,把需要调试的变量映射到[0,1]之间,把它们作为颜色输出到屏幕上, 然后通过屏幕上显示的像素颜色来判断这个值是否正确

5.4.2利用神器:Visual Studio

Visual Studio作为Windows系统下的开发利器,在 Visual Studio 2012 版本中也提供了对 Unity Shader 的调试功能 Graphics Debugger
通过Graphics Debugger,我们不仅可以查看每个像素的最终颜色、位置等信息,还可以对顶点着色器和片元着色器进行单步调试。具体可看相关博文:https://blog.csdn.net/u012632851/article/details/64124352

5.5渲染平台的差异

5.5.1渲染纹理的坐标差异

OpenGL(OpenGL ES也是)中,(0,0)点对应了屏幕的左下角,而在DirectX (Meta 1也是)中,(0,0)点对应了左上角

当使用渲染到纹理技术, 把屏幕图像渲染到一张渲染纹理中时,这种差异会造成纹理翻转的情况。 幸运的是,当在DirectX平台上使用渲染到纹理技术时,Unity会为我们翻转屏幕图像纹理,以便在不同平台上达到一致性。
在一种特殊情况下Unity不会进行这个翻转操作,这种情况就是开启了抗锯齿并在此时使用了渲染到纹理技术。
这种时候,我们就需要自己在顶点着色器中翻转某些渲染纹理(例如深度纹理或其他由脚本传递过来的纹理)的纵坐标,使之都符合DirectX平台的规则。例如:

//判断当前平台是否是DirectX类型的平台
#if UNITY_UV_STARTS_AT_TOP
//判断_MainTex_TexelSize.y是否小于0来检验是否开启了抗锯齿
if (_MainTex_TexelSize.y < 0)
//如果是,对除主纹理外的其他纹理的釆样坐标进行竖直方向上的翻转
uv.y = 1-uv.y;
#endif

5.5.2Shader的语法语义差异

为了让Shader 能够在所有平台上正常工作,应该尽可能使用下面的语义来描述Shader的输入输出变量。

  • 使用SV_POSITION来描述顶点着色器输出的顶点位置
  • 使用SV_Target来描述片元着色器的输出颜色

5.5.3其他平台差异

查看官方文档:
https://docs.unity3d.com/Manual/SL-PlatformDifferences.html

5.6Shader整洁之道

5.6.1 float、half 还是 fixed

在CG/HLSL中,有3种精度的数值类型:float, halffixed。这些精度将决定计算结果的数值范围。

基本建议是,尽可能使用精度较低的类型,因为这可以优化Shader的性能,这一点在移动平台上尤其重要

  • 使用fixed类型来存储颜色和单位矢量
  • 如果要存储更大范围的数据可以选择half类型。
  • 最差情况下再选择使用 float

5.6.2 避免不必要的计算

如果在Shader中进行了过多的运算,使得需要的临时寄存器数目指令数目超过了当前可支持的数目,可能会受到Unity的错误提示。
通常,我们可以通过指定更高等级的Shader Target来消除这些错误。

5.6.3 慎用分支和循环语句

GPU使用了不同于CPU的技术来实现分支语句,在最坏的情况下,花在一个分支语句的时间相当于运行了所有分支语句的时间。因此,不鼓励在 Shader中使用流程控制语句,因为它们会降低GPU的并行处理操作(尽管在现代的GPU上已经有 了改进)。
如果我们在Shader中使用了大量的流程控制语句,那么这个Shader的性能可能会成倍下降。 一个解决方法是,我们应该尽量把计算向流水线上端移动,例如把放在片元着色器中的计算放到 顶点着色器中,或者直接在CPU中进行预计算,再把结果传递给Shader。
一些建议:

  • 分支判断语句中使用的条件变量最好是常数,即在Shader运行过程中不会发生变化;
  • 每个分支中包含的操作指令数尽可能少;
  • 分支的嵌套层数尽可能少。

《Unity Shader入门精要》学习笔记第5章 开始Unity Shader学习之旅相关推荐

  1. 《Unity Shader入门精要》笔记01 前言

    <Unity Shader入门精要>笔记01 前言 --本系列是基于人民邮电出版社<Unity Shader入门精要>(冯乐乐著 )的自学Unity Shader笔记,如果您发 ...

  2. 《Unity Shader入门精要》笔记:初级篇(2)

    本篇博客主要为个人学习所编写读书笔记,不用于任何商业用途,以及不允许任何人以任何形式进行转载. 本篇博客会补充一些扩展内容(例如其他博客链接). 本篇博客还会提供一些边读边做的效果截图.文章内所有数学 ...

  3. 《Unity Shader入门精要》笔记:初级篇(1)

    本篇博客主要为个人学习所编写读书笔记,不用于任何商业用途,以及不允许任何人以任何形式进行转载. 本篇博客会补充一些扩展内容(例如其他博客链接). 本篇博客还会提供一些边读边做的效果截图.文章内所有数学 ...

  4. 《Unity Shader入门精要》笔记:高级篇(3)以及扩展

    本篇博客主要为个人学习所编写读书笔记,不用于任何商业用途,以及不允许任何人以任何形式进行转载. 本篇博客会补充一些扩展内容(例如其他博客链接). 本篇博客还会提供一些边读边做的效果截图.文章内所有数学 ...

  5. 《Unity Shader入门精要》笔记02 第1章+第2章

    基础篇 第1章+第2章 --本系列是基于人民邮电出版社<Unity Shader入门精要>(冯乐乐著 )的自学Unity Shader笔记,如果您发现了本文的纰漏,还望不吝指正. 基础篇 ...

  6. 《机器学习》周志华(西瓜书)学习笔记 第十一章 特征选择与稀疏学习

    机器学习 总目录 第十一章 特征选择与稀疏学习 11.1 子集搜索与评价 给定属性集,其中有些属性可能很关键.很有用,另一些 属性则可能没什么用.我们将属性称为"特征" (feat ...

  7. 《Unity Shader入门精要》笔记:基础篇(2)

    本篇博客主要为个人学习所编写读书笔记,不用于任何商业用途,以及不允许任何人以任何形式进行转载. 本篇博客会补充一些扩展内容(例如其他博客链接). 本篇博客还会提供一些边读边做的效果截图.文章内所有数学 ...

  8. 【unity shader 入门精要 读书笔记】透明

    一.透明 1.透明度测试[Alpha Test] 它采用一种"霸道极端"的机制:只要有一个片元的透明度不满足条件[通常是小于某个阈值],那么它对应的片元就会被舍弃.被舍弃的片元将不 ...

  9. shader入门精要读书笔记20 Unity中的光源类型与衰减计算

    一.光源类型 Unity中4中光源:平行光.点光源.聚光灯.面光源(只在烘焙时发生作用). 光源位置.方向.颜色.强度.衰减等等都与他们的属性息息相关,会影响到Shader. 1.平行光 他没有唯一的 ...

最新文章

  1. python charm下载安装教程-Python及Pycharm安装方法图文教程
  2. Python的函数名作为参数传入调用以及map、reduce、filter
  3. easyUI 添加排序到datagrid
  4. java连接stk外部接口_SLWSTK无线开发工具上的外扩串口如何使用(虚拟串口/VCOM)...
  5. 机器学习基础-逻辑回归-09
  6. hashmap put过程_看完还不懂HashMap算我输(附互联网大厂面试常见问题)
  7. YOLOv3通道+层剪枝,参数压缩98%,砍掉48个层,提速2倍!
  8. 多个漏洞可被用于破坏劫持施耐德 PowerLogic 设备
  9. python高手养成_不要总抱怨它慢了 突破性能瓶颈 找到Python序列筛选数据的最优解...
  10. [翻译] FBLikeLayout
  11. 详述 PROCEDURES_ICD 患者手术情况表 (七)
  12. 如何制定软件项目进度表
  13. Arduino笔记实验(初级阶段)—DHT11温湿度传感器
  14. python怎么读xlsx_python读取xlsx的方法
  15. WEditor USB device is offline
  16. CentOS 7.5 安装MySQL教程
  17. MAC使用技巧之苹果电脑新手最容易犯的20个错误
  18. JDON 论坛上的NETTY贴
  19. IBM_System_x3650服务器固件升级手顺
  20. 函数的引用透明性(referential transparency)

热门文章

  1. 架构道术-架构应该了解的产品故事
  2. 软件有源电力滤波器APF仿真,simulink仿真。三相三线制APF,三相四线制四桥臂APF
  3. 计算机考试慢跑运动,每周4次,每次30分钟,跑步真的会让你变得更聪明
  4. C#利用EXCEL做報表
  5. c#求三角形面积周长公式_C# 定积分求周长面积原理 代码实现
  6. 成都拓嘉辰丰:拼多多网店哪些情况会导致网店被处罚?
  7. 模仿淘宝首页横向导航栏
  8. 职称计算机模拟考试试题,职称计算机考试单选题考试卷模拟考.试题
  9. android studio修改皮肤,Android Studio 自定义皮肤主题和背景
  10. 探地雷达(GPR)详解