声明仅是用于学习记录。如有不足欢迎各位大佬批评指正。unity中一个最常见的流程创建一个材质创建一个Unity Shader并把它赋给上一步中创建的材质把材质赋给要渲染的对象在材质面板中调整Unity Shader的属性以得到满意的效果。结构Shader MyShader { Properties { // 所需的各种属性 } SubShader { // 真正意义上的Shader代码会出现在这里 // 表面着色器Surface Shader或者 // 顶点/片元着色器Vertex/Fragment Shader或者 // 固定函数着色器Fixed Function Shader } SubShader { // 和上一个SubShader类似 } }定义shader文件名字Shader Unlit/ceshiproperties它是材质和shader的桥梁仅仅是为了方便在材质面板调整属性。非必须的Name通常以 “_” 开头“display name”是在材质面板显示的名字PropertyType是属性的类型Defaultvalue是默认值Properties{ Name(display name, PropertyType)DefaultValue //可以定义多个属性一样的结构 }Shader Custom/ShaderLabProperties { Properties { // Numbers and Sliders _Int (Int, Int) 2 _Float (Float, Float) 1.5 _Range(Range, Range(0.0, 5.0)) 3.0 // Colors and Vectors _Color (Color, Color) (1,1,1,1) _Vector (Vector, Vector) (2, 3, 6, 1) // Textures _2D (2D, 2D) {} _Cube (Cube, Cube) white {} _3D (3D, 3D) black {} } FallBack Diffuse }SubShader可以包含多个此语义块但至少要有一个。运行时选择第一个可以在目标平台上运行的SubShader定义SubShader { // 可选的标签 [Tags] // 可选的状态 [RenderSetup] //每个Pass定义了一次完整的渲染流程。过多会导致渲染性能下降 Pass { } // Other PasseRenderSetup设置显卡的各种状态Tags是一个键值对字符串类型。SubShader和渲染引擎之间沟通的桥梁。明确怎样以及如何渲染这个对象。一些标签在SubShader中定义和在Pass中定义是不一样的。Tags { TagName1 Value1 TagName2 Value2 }PassPass { [Name]Name MyPassName [Tags] [RenderSetup] // Other code }UsePass可以使用UsePass来调用其他UnityShader的Pass。注使用UsePass命名时必须使用大写形式的名字UsePass MyShader/MYPASSNAMEGrabPass抓取屏幕并将结果存储到一张纹理中用于后续处理Fallback当前面的SubShader都无法运行时就执行此语句块。Fallback name // 或者 Fallback Off三类unityShader表面着色器Surface Shader是unity对顶点/片元着色器的更高一层抽象。示例CGPROGRAM和ENDCG之间的代码是使用Cg/HLSL编写的Shader Custom/Simple Surface Shader { SubShader { Tags { RenderType Opaque } CGPROGRAM #pragma surface surf Lambert struct Input { float4 color : COLOR; }; void surf (Input IN, inout SurfaceOutput o) { o.Albedo 1; } ENDCG } Fallback Diffuse }顶点/片元着色器Vertex/Fragment Shader示例Shader Custom/Simple VertexFragment Shader { SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag float4 vert(float4 v : POSITION) : SV_POSITION { return mul (UNITY_MATRIX_MVP, v); } fixed4 frag() : SV_Target { return fixed4(1.0,0.0,0.0,1.0); } ENDCG } } }固定函数着色器Fixed Function Shader用于那些无法支持可编程渲染管线的GUP。Pass中只能使用ShaderLab语法来编写。Shader Tutorial/Basic { Properties { _Color (Main Color, Color) (1,0.5,0.5,1) } SubShader { Pass { Material { Diffuse [_Color] } Lighting On } } }如何选择shader有明确需求需要固定函数着色器才用它。要和多种光源交互使用表面着色器会方便一定。但在一定平台的优化很差。光照少或有很多自定定义的渲染效果使用顶点/片元着色器好一些。正式编写UnityShader顶点/片元着色器的基本结构绝大多数的代码都在写Pass语义块。Shader MyShaderName { Properties { // 属性 } SubShader { // 针对显卡A的SubShader Pass { // 设置渲染状态和标签 // 开始Cg代码片段 CGPROGRAM // 该代码片段的编译指令例如 #pragma vertex vert #pragma fragment frag // Cg代码写在这里 ENDCG // 其他设置 } // 其他需要的Pass } SubShader { // 针对显卡B的SubShader } // 上述SubShader都失败后用于回调的Unity Shader Fallback VertexLit }每个数据都要定义语义在Unity着色器中语义(Semantics)是GPU识别和处理数据的交通规则它定义了数据的含义和用途。为了让shader有更好的跨平台性对于有特殊含义的变量最好使用以SV开头的系统数值语义进行修饰一个语义可以使用的寄存器只能处理4个浮点值float。因此如果我们想要定义矩阵类型如float3×4、float4×4等变量就需要使用更多的空间。一种方法是把这些变量拆分成多个变量例如对于float4×4的矩阵类型我们可以拆分成4个float4类型的变量每个变量存储了矩阵中的一行数据。语义的两种指定方式顶点着色器的输出语义有两种合法指定方式但不能同时使用方式一返回单一值时直接在函数后指定语义// 正确返回单一的裁剪空间位置 float4 vert(a2v v) : SV_POSITION { return UnityObjectToClipPos(v.vertex); }方式二返回结构体时在结构体成员上指定语义struct v2f { float4 pos : SV_POSITION; // 结构体成员上指定语义 fixed3 color : COLOR0; }; // 正确返回结构体时函数后不指定语义 v2f vert(a2v v) { v2f o; o.pos UnityObjectToClipPos(v.vertex); o.color v.normal * 0.5 fixed3(0.5, 0.5, 0.5); return o; }示例Shader Unity Shaders Book/Chapter 5/Simple Shader { SubShader { Pass { CGPROGRAM #pragma vertex vert //pragma vertex name ,告诉unity哪个函数包含顶点shader的代码 #pragma fragment frag //pragma fragment name ,告诉unity哪个包含片元shader的代码 float4 vert(float4 v : POSITION) : SV_POSITION //vert函数输入参数是一个float4类型的变量v使用语义POSITION标记表示输入的是模型的顶点坐标。返回值是一个float4类型使用语义SV_POSITION标记表示输出的是转换到裁剪空间的顶点坐标。 { return UnityObjectToClipPos(v); //MVP矩阵 // Upgrade NOTE: replaced mul(UNITY_MATRIX_MVP,*) with UnityObjectToClipPos(*) } fixed4 frag() : SV_Target //frag函数无输入参数。返回值是一个float4类型使用语义SV_Target标记表示输出的片元颜色储存到的目标颜色缓冲区。 { return fixed4(1.0, 1.0, 1.0, 1.0); } ENDCG } } }使用结构体向顶点shader输入更多的模型数据Shader Unity Shaders Book/Chapter 5/Simple Shader { SubShader { Pass { CGPROGRAM #pragma vertex vert //pragma vertex name ,告诉unity哪个函数包含顶点shader的代码 #pragma fragment frag //pragma fragment name ,告诉unity哪个包含片元shader的代码 struct a2v //a表示应用applicationv表示顶点着色器vertex shadera2v的意思就是把数据从应用阶段传递到顶点着色器中。 { float4 vertex : POSITION; //POSITION语义告诉unity输入的是模型的顶点坐标 float3 normal : NORMAL; //NORMAL语义告诉unity输入的是模型的法线方向 float4 texcoord : TEXCOORD0; //TEXCOORD0语义告诉unity输入的是模型的第一组纹理坐标 }; float4 vert(a2v v) : SV_POSITION { return UnityObjectToClipPos(v.vertex); //使用v.vertex来访问模型空间的顶点坐标并使用UnityObjectToClipPos函数将其转换成裁剪空间下的顶点坐标。 } fixed4 frag() : SV_Target //frag函数无输入参数。返回值是一个float4类型使用语义SV_Target标记表示输出的片元颜色储存到的目标颜色缓冲区。 { return fixed4(1.0, 1.0, 1.0, 1.0); } ENDCG } } }结构体格式如下一定要有分号结尾struct StructName { Type Name : Semantic; Type Name : Semantic; ....... };使顶点shader和片元shader之间通信片元着色器中的输入实际上是把顶点着色器的输出进行插值后得到的结果。Shader Unity Shaders Book/Chapter 5/Simple Shader { SubShader { Pass { CGPROGRAM #pragma vertex vert //pragma vertex name ,告诉unity哪个函数包含顶点shader的代码 #pragma fragment frag //pragma fragment name ,告诉unity哪个包含片元shader的代码 struct a2v //a表示应用applicationv表示顶点着色器vertex shadera2v的意思就是把数据从应用阶段传递到顶点着色器中。 { float4 vertex : POSITION; //POSITION语义告诉unity输入的是模型的顶点坐标 float3 normal : NORMAL; //NORMAL语义告诉unity输入的是模型的法线方向 float4 texcoord : TEXCOORD0; //TEXCOORD0语义告诉unity输入的是模型的第一组纹理坐标 }; struct v2f //f表示片元着色器fragment shaderv2f的意思就是把数据从顶点着色器传递到片元着色器中。 { float4 pos : SV_POSITION; //SV_POSITION语义告诉unity输入的是顶点着色器输出的顶点坐标经过裁剪空间转换后的坐标 fixed3 color : COLOR0; //COLOR0语义告诉unity输入的是顶点着色器输出的颜色用于片元着色器中。 }; v2f vert(a2v v) //vert函数输入参数是a2v类型的v返回值是v2f类型的o。 { v2f o; o.pos UnityObjectToClipPos(v.vertex); o.color v.normal * 0.5 fixed3(0.5, 0.5, 0.5); //将法线的范围从[-1, 1]转换到[0, 1]以便在片元着色器中使用。 return o; } fixed4 frag(v2f i) : SV_Target //frag函数无输入参数。返回值是一个float4类型使用语义SV_Target标记表示输出的片元颜色储存到的目标颜色缓冲区。 { return fixed4(i.color, 1.0); //将片元颜色设置为顶点颜色并将alpha值设置为1.0不透明。 } ENDCG } } }在材质显示一个颜色拾取器以控制模型在屏幕上的颜色Properties的运用Shader Unity Shaders Book/Chapter 5/Simple Shader { Properties { _Color(Color Tint, Color) (1.0, 1.0, 1.0, 1.0) //定义一个颜色属性名称为_Color显示名称为Color Tint类型为Color默认值为白色。 } SubShader { Pass { CGPROGRAM #pragma vertex vert //pragma vertex name ,告诉unity哪个函数包含顶点shader的代码 #pragma fragment frag //pragma fragment name ,告诉unity哪个包含片元shader的代码 fixed3 _Color; //定义一个与属性名_Color相同名称和类型都匹配的变量用于在顶点着色器中使用。 struct a2v //a表示应用applicationv表示顶点着色器vertex shadera2v的意思就是把数据从应用阶段传递到顶点着色器中。 { float4 vertex : POSITION; //POSITION语义告诉unity输入的是模型的顶点坐标 float3 normal : NORMAL; //NORMAL语义告诉unity输入的是模型的法线方向 float4 texcoord : TEXCOORD0; //TEXCOORD0语义告诉unity输入的是模型的第一组纹理坐标 }; struct v2f //f表示片元着色器fragment shaderv2f的意思就是把数据从顶点着色器传递到片元着色器中。 { float4 pos : SV_POSITION; //SV_POSITION语义告诉unity输入的是顶点着色器输出的顶点坐标经过裁剪空间转换后的坐标 fixed3 color : COLOR0; //COLOR0语义告诉unity输入的是顶点着色器输出的颜色用于片元着色器中。 }; v2f vert(a2v v) //vert函数输入参数是a2v类型的v返回值是v2f类型的o。 { v2f o; o.pos UnityObjectToClipPos(v.vertex); o.color v.normal * 0.5 fixed3(0.5, 0.5, 0.5); //将法线的范围从[-1, 1]转换到[0, 1]以便在片元着色器中使用。 return o; } fixed4 frag(v2f i) : SV_Target //frag函数无输入参数。返回值是一个float4类型使用语义SV_Target标记表示输出的片元颜色储存到的目标颜色缓冲区。 { fixed3 c fixed4(i.color, 1.0); //将片元颜色设置为顶点颜色并将alpha值设置为1.0不透明。 c * _Color.rgb; //将片元颜色乘以颜色属性_Color return fixed4(c, 1.0); //将片元颜色设置为计算后的颜色并返回给unity。 } ENDCG } } }Unity内置文件和变量包含文件的引用CGPROGRAM // ... #include UnityCG.cginc // ... ENDCG内置编辑器文件查看方法团结编辑器自身的内置文件如编辑器脚本、工具、模板等存储在编辑器安装目录中Windows 默认安装路径C:\Program Files\Tuanjie version\Editor\Data\macOS 默认安装路径Applications/Tuanjie version/Tuanjie.app/Contents/Debug使用 “假色彩图像false-color image”使用假色彩技术生成的一种图像可以用来可视化一些数据。主要思想是我们可以把需要调试的变量映射到[0, 1]之间把它们作为颜色输出到屏幕上然后通过屏幕上显示的像素颜色来判断这个值是否正确。示例使用假彩色图像的方式可视化一些模型数据如法线、切线、纹理坐标、顶点颜色以及它们之间的运算结果等。Shader Unity Shaders Book/Chapter 5/False Color { SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc struct v2f { float4 pos : SV_POSITION; fixed4 color : COLOR0; }; v2f vert(appdata_full v) { v2f o; o.pos UnityObjectToClipPos(v.vertex); // Upgrade NOTE: replaced mul(UNITY_MATRIX_MVP,*) with UnityObjectToClipPos(*) // 可视化法线方向 o.color fixed4(v.normal * 0.5 fixed3(0.5, 0.5, 0.5), 1.0); // 可视化切线方向 o.color fixed4(v.tangent.xyz * 0.5 fixed3(0.5, 0.5, 0.5), 1.0); // 可视化副切线方向 fixed3 binormal cross(v.normal, v.tangent.xyz) * v.tangent.w; o.color fixed4(binormal * 0.5 fixed3(0.5, 0.5, 0.5), 1.0); // 可视化第一组纹理坐标 o.color fixed4(v.texcoord.xy, 0.0, 1.0); // 可视化第二组纹理坐标 o.color fixed4(v.texcoord1.xy, 0.0, 1.0); // 可视化第一组纹理坐标的小数部分 o.color frac(v.texcoord); if (any(saturate(v.texcoord) - v.texcoord)) { o.color.b 0.5; } o.color.a 1.0; // 可视化第二组纹理坐标的小数部分 o.color frac(v.texcoord1); if (any(saturate(v.texcoord1) - v.texcoord1)) { o.color.b 0.5; } o.color.a 1.0; // 可视化顶点颜色 //o.color v.color; return o; } fixed4 frag(v2f i) : SV_Target { return i.color; } ENDCG } } }使用 “帧调试器Frame Debugger”在Window - Frame Debugger中打开帧调试器窗口。可以查看渲染该帧时进行的各种渲染事件。渲染平台差异纹理的坐标差异OpenGL 的坐标原点在左下角DirectX 的坐标原点在左上角当我们在开启抗锯齿的同时需要处理多张渲染图像。unity无法帮我们完成各平台之间的纹理图像反转就需要我们手动反转。我们需要自己在顶点着色器中翻转某些渲染纹理例如深度纹理或其他由脚本传递过来的纹理的纵坐标使之都符合DirectX平台的规则。#if UNITY_UV_STARTS_AT_TOP //判断当前平台是否是 DirectX 类型的平台 if (_MainTex_TexelSize.y ; 0) //判断是否开启了抗锯齿。开启抗锯齿后主纹理的纹素大小在竖直方向上变为负值 uv.y 1-uv.y; //将除主纹理外的其他采样坐标进行竖直方向上的翻转 #endifShader的语法差异在Windows平台下编译某些在Mac平台下工作良好的Shader时可能会出现报错。原因是因为DirectX9/11对Shader的语义更加严格造成的。在OpenGL平台上“float4 v float4(0. 0)” 是合法的它将得到一个4个分量都是0.0的float4类型的变量。但在DirectX11平台上我们必须提供和变量类型相匹配的参数数目。表面着色器的顶点函数注意不是顶点着色器有一个使用了out修饰符的参数。DirectX9/11不支持在顶点着色器中使用tex2D函数。Shader的语义差异一些语义在某些平台下是等价的例如SV_POSITION和POSITION。但在另一些平台上这些语义是不等价的。为了让Shader能够在所有平台上正常工作我们应该尽可能使用下面的语义来描述Shader的输入输出变量。使用SV_POSITION来描述顶点着色器输出的顶点位置。一些Shader使用了POSITION语义但这些Shader无法在索尼PS4平台上或使用了细分着色器的情况下正常工作。使用SV_Target来描述片元着色器的输出颜色。一些Shader使用了COLOR或者COLORO语义同样的这些Shader无法在索尼PS4上正常工作。规范 Shader 代码建议数据类型精度选择尽可能使用精度较低的类型因为这可以优化 Shader 的性能尤其是在移动平台。规范语法DirectX 平台对 Shader 的语义有更加严格的要求。避免不必要的计算不同的Shader Target、不同的着色器阶段我们可使用的临时寄存器和指令数目都是不同的。慎用分支和循环语句会降低 GPU 的并行处理操作可能会导致Shader的性能成倍下降。应该尽量把计算向流水线上端移动。如果一定要用分支判断语句中使用的条件变量最好是常数即在Shader运行过程中不会发生变化每个分支中包含的操作指令数尽可能少分支的嵌套层数尽可能少。不要除以 0 会导致在某些平台上 Shader 崩溃。对那些除数可能为0的情况强制截取到非0范围。fixed4 frag(v2f i) : SV_Target { return fixed4(0.0/0.0,0.0/0.0, 0.0/0.0, 1.0); }其他uniformuniform关键词是Cg中修饰变量和参数的一种修饰词它仅仅用于提供一些关于该变量的初始值是如何指定和存储的相关信息这和其他一些图像编程接口中的uniform关键词的作用不太一样。在Unity Shader中uniform关键词是可以省略的。uniform fixed4 _Color;