Unity Shader入门精要学习笔记 - 第5章 开始 Unity Shader 学习之旅

Unity Shader入门精要学习笔记 - 第5章 开始 Unity Shader 学习之旅

本系列为UnityShader入门精要读书笔记总结,
原作者博客链接:http://blog.csdn.net/candycat1992/article/
书籍链接:http://product.dangdang.com/23972910.html

第5章 开始 Unity Shader 学习之旅

5.1 使用的软件和系统

这些相关案例使用的Unity为5.6.1版本,系统为W10系统。

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

Shader "MyShaderName"  
{  
    Properties  
    {  
        //属性  
    }  
    SubShader  
    {  
        //针对显卡A的SubShader  
        Pass  
        {  
            //设置渲染状态和标签  
            //开始CG代码片段  
            CGPROGRAM  
            //该代码的预编译指令,例如:  
            #pragma vertex vert  
            #pragma fragment frag  
            //CG代码写在这儿  
            ENDCG  
            //其他设置  
        }  
    }  
    SubShader  
    {  
        //针对显卡B的SubShader  
    }  
}  

其中,最重要的部分是Pass语义块,我们绝大部分的代码都是写在这个语义块里面的。下面我们来创建一个最简单的顶点/片元着色器。
1)新建一个场景,命名为Scene_5_2,如下:
Unity Shader入门精要学习笔记 - 第5章 开始 Unity Shader 学习之旅_第1张图片
可以看到,场景中已经包含了一个摄像机、一个平行光。而且场景的背景不是纯色的,而是一个天空盒子。我们可以Window->Lighting->SkyBox,把该项设置为空,去掉天空盒子。
2)新建一个Unity Shader,把它命名为Chapter5-SimpleShader。
3)新建一个材质球,把它命名为SimpleShaderMat。把第2步中新建的Unity Shader赋给它。
4)新建一个球体,拖拽它的位置以便在Game视图中更可以合适地显示出来。把第3步中新建的材质拖拽给它。
5)双击打开第2步中创建的Unity Shader。删除里面所有的代码。把下面的代码粘贴进去。
熟悉Unity的可以直接使用作者的源代码案例,直接删掉Shader部分,我们只实现Shader部分就好。

Shader "Unity Shaders Book/Chapter 5/Simple Shader"  
{  
    //定义了这个Unity Shader的名字
    SubShader  
    {  
        //我们声明了SubShader 和 Pass 语义块
        Pass  
        {  
            CGPROGRAM  
            //它们告诉Unity,哪个函数包含了顶点着色器的代码,
            //哪个函数包含了片元着色器的代码。其中vert和frag就是我们指定的函数名
            #pragma vertex vert  
            #pragma fragment frag  

            //顶点着色器代码 :后边代表参数类型 
            float4 vert(float4 v : POSITION) : SV_POSITION  
            {  
                //顶点坐标从模型空间转换到裁剪空间中。
                //UNITY_MATRIX_MVP 矩阵是unity 内置的模型·观察·投影矩阵。
                //等同于return mul(UNITY_MATRIX_MVP, v);
                return UnityObjectToClipPos(v);
            }  

            //片元着色器代码 
            fixed4 frag() : SV_Target  
            {  
                //SV_Target  也是HLSL 中的一个系统语义,告诉渲染器,把用户的输出颜色存储到一个渲染目标中
                //直接返回一个白色 
                return fixed4(1.0,1.0,1.0,1.0);
            }  
            ENDCG  
        }  
    }  
}  

POSITION 将告诉Unity,把模型的顶点坐标填充到参数v中,SV_POSITION 将告诉Unity,顶点着色器的输出是裁剪空间中的顶点坐标。

保存并返回Unity查看结果,如下:
Unity Shader入门精要学习笔记 - 第5章 开始 Unity Shader 学习之旅_第2张图片

在顶点着色器我们使用了POSITION 语义得到了模型的顶点位置 。那么,如果我们想要得到更多模型数据怎么办呢?
现在,我们想得到模型上每个顶点的纹理坐标和法线方向。这个需求是很常见的,我们需要使用纹理坐标来访问纹理,而法线可用于计算光照。因此,我们需要为顶点着色器定义一个新的输入参数,这个参数不再是一个简单的数据类型,而是一个结构体。修改后的代码如下:

    SubShader  
    {  
        //我们声明了SubShader 和 Pass 语义块
        Pass  
        {  
            CGPROGRAM  
            //它们告诉Unity,哪个函数包含了顶点着色器的代码,
            //哪个函数包含了片元着色器的代码。其中vert和frag就是我们指定的函数名
            #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;  
                //COLOR0 语义可以用于存储颜色信息  
                fixed3 color : COLOR0;  
            };  

            //顶点着色器代码 :后边代表参数类型 
            v2f vert(a2v v) 
            {  
                //声明输出结构  
                v2f o;  
                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;
            }  

            //片元着色器代码 
            fixed4 frag(v2f i) : SV_Target  
            {  
                //将插值后的i.color显示到屏幕  
                return fixed4(i.color,1.0); 
            }  
            ENDCG  
        }  
    }  

保存并返回Unity查看结果,如下:
Unity Shader入门精要学习笔记 - 第5章 开始 Unity Shader 学习之旅_第3张图片

a2v 中 a 表示应用,v 表示顶点着色器。a2v的意思是把数据从应用阶段传递到顶点着色中。
顶点着色器的输出结构中,必须包含一个变量,它的语义是 SV_POSITION 。否则,渲染器将无法得到裁剪空间中的顶点坐标。
在实践中,我们往往希望从顶点着色去输出一些数据。例如把模型的法线、纹理坐标等传递给片元着色器。这就涉及顶点着色器和片元着色器之间的通信。
v2f用于在顶点着色器和片元着色器之间传递信息。

现在,我们有了新的需求,我们想要在材质面板显示一个颜色拾取器,从而可以直接控制模型在屏幕上显示的颜色。为此,我们继续修改上面的代码。

   Properties  
    {  
        //声明一个Color 类型的属性  
        _Color ("Color Tint", Color) = (1.0, 1.0, 1.0, 1.0)  
    }  
    //定义了这个Unity Shader的名字
    SubShader  
    {  
        //我们声明了SubShader 和 Pass 语义块
        Pass  
        {  
            CGPROGRAM  
            //它们告诉Unity,哪个函数包含了顶点着色器的代码,
            //哪个函数包含了片元着色器的代码。其中vert和frag就是我们指定的函数名
            #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 : TEXCOORD0;
            };  

            struct v2f  
            {  
                //SV_POSITION语义告诉Unity,pos 里包含了顶点在裁剪空间中的位置信息  
                float4 pos : SV_POSITION;  
                //COLOR0 语义可以用于存储颜色信息  
                fixed3 color : COLOR0;  
            };  

            //顶点着色器代码 :后边代表参数类型 
            v2f vert(a2v v) 
            {  
                //声明输出结构  
                v2f o;  
                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;
            }  

            //片元着色器代码 
            fixed4 frag(v2f i) : SV_Target  
            {  
                fixed3 c = i.color;  
                c *= _Color.rgb;
                //将插值后的i.color显示到屏幕  
                return fixed4(c,1.0); 
            }  
            ENDCG  
        }  
    }  

在上面的代码中,我们首先添加了Properties语义块中,并在其中声明了一个属性_Color,它的类型是Color,初始值是(1.0,1.0,1.0,1.0),对应白色。为了在CG代码中可以访问它,我们还需要再CG代码片段中提前定义一个新的变量,这个变量的名称和类型必须与Properties语义块中的属性定义相匹配。
Unity Shader入门精要学习笔记 - 第5章 开始 Unity Shader 学习之旅_第4张图片
可以看到属性面板多了一个颜色调节。

5.3 强大的援手:Unity提供的内置文件和变量

为了方便开发者开发,Unity提供了很多内置文件。这些文件包含了很多提前定义的函数、变量和宏等。
包含文件是类似于C++中头文件的一种文件。在Unity中,它的后缀是.cgnic。在编写Shader时,我们可以使用#include指令把这些文件包含进来,这样我们就可以使用Unity为我们提供的一些非常有用的变量和帮助函数。例如:

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

我们可以在官网(https://unity3d.com/cn/get-unity/download/archive)上选择下载->内置着色器来直接下载这些文件,下图显示了由官网压缩包得到的文件。
Unity Shader入门精要学习笔记 - 第5章 开始 Unity Shader 学习之旅_第5张图片
CGIncludes 文件夹中包含了所有的内置包含文件;
DefaultResouces 文件夹中包含了一些内置组件或功能所需要的Unity Shader,例如一些GUI元素使用的Shader;
DefaultResourcesExtra 则包含了所有Unity中内置的Unity Shader;
Editor 文件夹目前只包含了一个脚本文件,它用于定义Unity5 引入的 Standard Shader 所用的材质面板

我们也可以从Unity应用程序中直接找到CGIncludes文件夹。在Windows上,它的位置是:
Unity的安装路径/Data/CGIncludes。
Unity Shader入门精要学习笔记 - 第5章 开始 Unity Shader 学习之旅_第6张图片

下表给出了CGIncludes 中主要包含文件以及它们的用处。
Unity Shader入门精要学习笔记 - 第5章 开始 Unity Shader 学习之旅_第7张图片
可以看出,有一些文件即使我们没有包含进来,Unity也会帮我们自己包含。
UnityCG.cgnic文件是我们最常接触的一个文件。下表给出了一些结构体的名称和包含的变量。

Unity Shader入门精要学习笔记 - 第5章 开始 Unity Shader 学习之旅_第8张图片
除了上述结构体外,UnityCG.cgnic也提供了一些常用的帮助函数。下表给出了一些函数名和它们的描述
Unity Shader入门精要学习笔记 - 第5章 开始 Unity Shader 学习之旅_第9张图片

5.4 Unity提供的CG/HLSL语义

需要注意的是,Unity并没有支持所有的语义。
通常情况下,这些输入输出变量并不需要有特别的意义,也就是说,我们可以自行决定这些变量的用途。例如在上面的代码中,定点着色器的输出结构体中,我们用COLOR0去描述color变量。color变量本身存储了什么,Shader流水线并不关心。其实任何东西都是数据,我们只是给他一个外在的名字,具体我们拿来这个数据怎么用,完全可以自己决定,只要格式是相同的。

Unity Shader入门精要学习笔记 - 第5章 开始 Unity Shader 学习之旅_第10张图片
Unity Shader入门精要学习笔记 - 第5章 开始 Unity Shader 学习之旅_第11张图片
这里写图片描述

如何定义一个复杂的变量类型?

struct v2f  
{  
    float4 pos : SV_POSITION;  
    fixed3 color0 : COLOR0;  
    fixed4 color1 : COLOR1;  
    half value0 : TEXCOORD0;  
    float2 value1 : TEXCOORD1;  
};  

5.5 程序员的烦恼:Debug

相关的博文连接:
http://blog.csdn.net/u012632851/article/details/64124352
http://blog.csdn.net/wpapa/article/details/51204347

5.6 渲染平台的差异

Unity 在渲染平台的差异
OpenGL 和 DirectX 在屏幕空间坐标存在差异,如下图
Unity Shader入门精要学习笔记 - 第5章 开始 Unity Shader 学习之旅_第12张图片
Shader的语法语义差异
更多的差异,可以查看官方文档
https://docs.unity3d.com/Manual/SL-PlatformDifferences.html

5.7 Shader整洁之道

在CG/HLSL中,有3种精度的数值类型:float,half和fixed。这些精度将决定计算结果的数值范围。下表给出了3种精度在通常情况下的数值范围。
Unity Shader入门精要学习笔记 - 第5章 开始 Unity Shader 学习之旅_第13张图片
一个基本建议是,尽可能使用精度较低的类型,因为这可以优化Shader 的性能,这一点在移动平台上尤其重要。从它们大体的值域范围来看,我们可以使用fixed 类型来存储颜色和单位矢量,如果要存储更大范围的精度可以选择half类型,最差情况下再选择使用float、如果我们的目标平台是移动平台,一定要确保在真实的手机上测试我们的Shader,这点非常重要。

Shader中进行了过多的运算,使得需要的临时寄存器数目或指令数目超过了当前可支持的数目。通常,我们可以通过制定更高级的Shader Target 来消除这些错误。下表给出了Unity 目前支持的Shader Target。
Unity Shader入门精要学习笔记 - 第5章 开始 Unity Shader 学习之旅_第14张图片
如果我们再Shader 中使用了大量的流程控制语句,那么这个Shader 的性能可能会成倍下降。一个解决方法是,我们应该尽量把计算向流水线上端移动,例如把放在片元着色器中的计算放到顶点着色器中,或者直接在CPU中进行预计算,再把结果传递给Shader。当然,有时我们不可避免地要使用分支语句来进行计算,那么一些建议是:
分支判断语句中使用的条件变量最好是常数,即在Shader运行过程中不会发生变化;
每个分支中包含的操作指令数尽可能少;
分支的嵌套层数尽可能少。
另外在Shader编程过程中,不要除以0。

你可能感兴趣的:(Unity,Shader)