当前位置: 代码迷 >> 综合 >> 第一篇 初识庐山真面目 ——Unity 3D Shader(2)
  详细解决方案

第一篇 初识庐山真面目 ——Unity 3D Shader(2)

热度:56   发布时间:2023-09-30 16:13:55.0

文章目录

    • 第二章 Unity中的Shader(着色器)的形态
      • 2.1 Unity通过ShaderLab来组织Shader
        • 2.1.1 关键字 Shader
      • 2.1.2 使用SubShader组织Shader的不同实现
      • 2.1.3 SubShader的重要标签
      • 2.1.4 SubShader中的Pass块
      • 2.1.5 Pass块的标签及其名字的意义
      • 2.1.6 使用FallBack保证Shader的广泛使用性\
    • 2.2 UnityShaderLab所支持的编程语言
    • 2.3 Unity中Shader的3种形态
      • 2.3.1 固定管线
      • 2.3.2 可编程的Shader
      • 2.3.3 ShaderLab的骄傲:Surface Shader
    • 2.4 Shader的数据接口:属性和uniform变量
      • 2.4.1 在Properties块中定义属性
      • 2.4.2 通过图形界面操作属性
      • 2.4.3 通过脚本操控属性
      • 2.4.4 矩阵:不能在属性块定义的变量
      • 2.4.5 在Cg代码中使用属性

第二章 Unity中的Shader(着色器)的形态

2.1 Unity通过ShaderLab来组织Shader

作为一款号称跨平台性最好的游戏开发引擎,Unity3D使用Mono这个开源的.NET来实现,对于要适应不同GPU的Shader来说,Unity使用自定义ShaderLab来组织Shader的内容,并会针对不同的平台进行编译。

2.1.1 关键字 Shader

Unity的Shader文件是通过Shader这个关键字开始的,用户可以像目录一样组织Shader的命名。Shader的文件名和引用名不必一样,举例如下:

Shader "FirstShader"{Properties{_MainTex("Base (RGB)", 2D) = "white"{}}SubShader{......}SubShader{......}FallBack "Diffuse"
}

2.1.2 使用SubShader组织Shader的不同实现

真正用于呈现渲染物体的内容是在SubShader中实现的。之所以使用SubShader,是为了能让开发者针对不同性能的显卡编写不同的Shader,因为现实中并不是每个人都用相同的显卡。由于显卡千差万别,为了能让用户编写的游戏最大化地在各种GPU上运行,尽可能的呈现出最好的效果,就用SubShader来组织具体的实现,使Unity在运行时会针对实际的运行环境,从上到下选出最优的SubShader实现来呈现开发者的作品。从理论上来说SubShader的数量是没有限制的,但实际操作中为了减少文件的大小,一般写两三个SubShader就可以了,即针对目前最流行的一代显卡写一个,针对老旧显卡写一个。

2.1.3 SubShader的重要标签

给SubShader添加标签(Tags{}块),其实这就是一系列指示牌,这些标签可以告诉Unity渲染引擎或者其他用户如何认证这个SubShader。下面代码是适用于SubShader的BuildIn类的Tags:

SubShader{
Tags{"Queue"="Geometry""RenderType"="Opaque""IgnoreProjector"="True"}
...
}

Queue就是队列的意思,在这里就是渲染队列的意思,表示希望Uniy引擎在什么时候渲染自己。Queue有5个可选值,它们是Background、Geometry、AlphaTest、Transparent和Overlay,分别对应数字1000、2000、2450、3000和4000。既然可以对应成数字,当然也可以把这些单词当做整型变量来看待,例如:
Tags{“Queue”=“Geometry+1234”}
当混合使用定义的Shader和Unity内置的Shader时,一定要尊重上面的单词和字母的对应关系,因为,unity的内置Shader就是按这个顺序来渲染的。
下面是RenderType标签,其常用的内置值有Opaque、Transparent、TransparentCutout、Background和overlay。这个标签在代替渲染做Post Effects时很重要,Unity内置的Image Effects就是要根据它来决定如何替代渲染。此外,如果想得到一张ZDepth和Normals的快照。一个正确的RenderType是必不可少的。
IgnoreProjector比较简单,值为True时,表示当前物体忽略Projector的影响。
我们还可以为SubShader添加自定义标签,例如:
Tags{“MyTag”=“Lucifer”}
这种做法一般会在做替代渲染时用到。

2.1.4 SubShader中的Pass块

SubShader包装了一个渲染方案,而这个方案是由一个个Pass块来执行的。SubShader可以包含多个Pass块,每一个Pass块都包含了渲染一个几何体的具体代码。

2.1.5 Pass块的标签及其名字的意义

Pass块内置的那些标签都是针对渲染路径的,告诉渲染引擎这个Pass应该在什么渲染路径下被渲染,这些先别着急,我们一点点的提到。
Pass块的Name一般用来引用此Pass。这意味着可以定义一个Pass块,然后在其它Shader的Pass块中多次引用它。例如下面代码:

Shader "SecondShader"{
Subshader{Pass{Name "XGS"Material{Diffuse(1,0.7,0.4,1)Ambient(1,0.7,0.4,1)}Lighting OnSetTextture[ _ ] { combine primary}}
}
}

这个Shader命名了一个Pass块,由于Unity的原因,命名时必须使用大写。下面这个Shader则通过名字应用了此Pass块:

Shader "ThirdShader"{
SubShader{Use "SecondShader/XGS"}
}

2.1.6 使用FallBack保证Shader的广泛使用性\

如果用户所有的SubShader都失败了,为了在用户的计算机上呈现设定的机制,一般会使用FallBack。FallBack是Unity自己预制的Shader实现,一般能够在所有的显卡上运行。我们在开发Shader的时候一般不使用FallBack,只有在实际发布应用时才会为了追求平台的最大适用性而加上。

2.2 UnityShaderLab所支持的编程语言

不说那么多,在unity中我们开发游戏用的是Shader,至于其它的我不会也不敢妄下猜测,有兴趣的话各位请自行百度吧

2.3 Unity中Shader的3种形态

2.3.1 固定管线

固定管线是在旧的GPU能力比较有限时,对Shader的约束比较高的一种形态。为了市场占有率,新一代的显卡仍对其有所选择的进行支持,但是在未来会逐步淘汰。在ShaderLab中,固定管线的形态、语法和NVIDIA的CgFX以及微软的FX文件比较类似。下面是固定管线的一个例子:

Shader "BaseForm_1"{Proteries{_Color ("Main Color", Color) = (1,1,1,0.5)_SpeColor("Spec Color", Color) = (1,1,1,1)_Emission("Emmisive Color", Color) = (0,0,0,0)_Shininess("Shininess", Range(0.01,1)) = 0.7_MainTex("Base (RGB)", 2D) = "white" {}}SubShader{Pass{Material{Diffuse [_Color]Amibent[_Color]Shininess[_Shininess]Specular[_SpecColor]Emission[_Emossion]
}Lighting OnSeparateSpecular OnSetTexture [_MainTex]{ConstantColor [_Color]Combine texture * primary DOUBLE, texture *constant}}}
}

固定管线的相关代码都必须处于一个Pass块中。这里我们先了解其大致形态,具体的我们在日后慢慢详细讲解。

2.3.2 可编程的Shader

如果你想自己处理照明,可以写Vertex+fragment Shader,这是Unity对可编程Shader的一种支持。下面是可编程Shader的一个例子:

Shader "FourthShader"{Properties {_MyTexture ("Texture (RGB)", 2D) = "white" {}_MyColor("Color of Object",Color)=(1,1,1,1)}SubShader {Tags{"Queue"="Geometry" "RenderType"="Opaque" "IgnoreProjector"="True"}pass{CGPROGRAM#pragma vertex vert#pragma fragment frag#pragma target 2.0#include "UnityCG.cginc"sampler2D _MyTexture;float4 _MyColor;struct v2f{float4 pos:SV_POSITION;};v2f vert(appdata_full v){v2f o;o.pos=UnityObjectToClipPos(v.vertex);return o;}float4 frag(v2f i):COLOR{return float4(1,1,1,1);}ENDCG}} FallBack "Diffuse"
}

2.3.3 ShaderLab的骄傲:Surface Shader

如果你想写一个程序能处理不同的照明、点光源、平行光、光照贴图等,又能够处理不同的阴影选项,还同时能在Unity的两个渲染路径(Forward和Deferred)下正常工作,是一件很复杂很烦人的事情。Unity通过Surface Shader把上面这一切复杂性包装了起来 。下面是这样一个Surface Shader的例子,其实也就是在编辑器中创建Shader的默认结果:

Shader "Custom/NewSurfaceShader"
{Properties{_Color ("Color", Color) = (1,1,1,1)_MainTex ("Albedo (RGB)", 2D) = "white" {}_Glossiness ("Smoothness", Range(0,1)) = 0.5_Metallic ("Metallic", Range(0,1)) = 0.0}SubShader{Tags { "RenderType"="Opaque" }LOD 200CGPROGRAM// Physically based Standard lighting model, and enable shadows on all light types#pragma surface surf Standard fullforwardshadows// Use shader model 3.0 target, to get nicer looking lighting#pragma target 3.0sampler2D _MainTex;struct Input{float2 uv_MainTex;};half _Glossiness;half _Metallic;fixed4 _Color;// Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.// See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.// #pragma instancing_options assumeuniformscalingUNITY_INSTANCING_BUFFER_START(Props)// put more per-instance properties hereUNITY_INSTANCING_BUFFER_END(Props)void surf (Input IN, inout SurfaceOutputStandard o){// Albedo comes from a texture tinted by colorfixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;o.Albedo = c.rgb;// Metallic and smoothness come from slider variableso.Metallic = _Metallic;o.Smoothness = _Glossiness;o.Alpha = c.a;}ENDCG}FallBack "Diffuse"
}

在Surface函数surf中,SurfaceOutput是一个包含描述一个物体表面渲染特征的结构,例如:
struct SurfaceOutput{
half3 Albedo;//颜色纹理
half3 Normal;//法线
half3 Emission;//自发光,不受照明影响
half Specular;//高光指数
half Gloss;//光泽度
half Alpha;//Alpha通道
};
Surface Shader最终会被编译为一个复杂的vertex+fragment Shader,不过通过Surface Shader我们所需要做的就是简单的调色,这个说法也许比较笼统,有基础的可以简单的做一下调试理解一下,是在不能理解以后的学习中我们在慢慢介绍如何调试,一下子想把所有的知识点搞明白是不太现实的,不求甚解此时是必须的。关于Surface Shader在以后我们会更详细的的说明。

2.4 Shader的数据接口:属性和uniform变量

如果我们想设定一个特定的值到Shader,即为了在实例化Shader为Material时,可以通过为属性赋值达到创建具体对象的目的,我们需要在Properties块中定义自己的属性。如果以上不能理解的话,随便找本unity3d入门的书,一般第一章就会讲解以上的操作,说白了就是在软件中进行拖拽就ok了。

2.4.1 在Properties块中定义属性

我们能够定义如下属性:
Properties{
_MyTexture (“Texture (RGB)”, 2D) = “white” {}//图片形式的属性
_MyColor(“Color of Object”, Color) = (1,1,1,1)//颜色属性
_MyCube(“Environment map”, Cube) = “white” {}//3D贴图,需要6张图片
_MyVector(“Vector”,vector) = (1,1,1,1)//4个元素的向量
_MyFloat(“Float value”, float) = 1.0//浮点小数
_MyRange(“Another type of float”,range(-13,14))=1.0//限定范围的浮点数
}

2.4.2 通过图形界面操作属性

在Properties块中定义了这些属性后,我们可以在编译器中通过图形界面来给他们赋值。
第一篇 初识庐山真面目 ——Unity 3D Shader(2)

2.4.3 通过脚本操控属性

除了图形界面以外,还能够通过脚本来读取和写入定义的属性。下面是一个C#脚本对属性的读取操作:

public Material mat;
public Texture myPic;
public Color purple;
public Cubemap cube;
public Vector4 vec;
public float val_1;
public float val_2;void Update(){//对在Shader中定义的属性进行设置mat.SetTexture("_MyTexture", mypic);mat.SetColor("_MyColor",purple);mat.SetTexture("_MyCubr",Cube);mat.SetVector("_MyVector",vec);mat.SetFloat("_MyFloat",val_1)'mat.SetFloat("_MyRange",val_2);//对在Shader中定义的属性进行读取myPic = mat.GetTexture("_MyTexture");purple = mat.GetColor("_Mycolor");cube=(Cubemap)mat.GetTexture("Mycube");vec = mat.GetVector("_MyVector");val_1 = mat.GetFloat("_MyFloat");val_2 = mat.GetFloat("_MyRange");}

cube属性是3D贴图,是Texture2D贴图的子类,range属性则是一种限制了范围的float类型,本质和float一样。如果在脚本中进行操作的属性在Shader中不存在,那么Unity只会将此操作忽略,而不会报错

2.4.4 矩阵:不能在属性块定义的变量

矩阵目前是不能再Properties块中定义的,必须首先在Shader中声明它,然后通过脚本来进行读取和写入。下面是在一个Shader中对一个矩阵的声明:
uniformfloat4×4 myMatrix;
下面是在脚本中对该矩阵的读取和写入:
mat.SetMatrix(“myMatrix”,matrix);
matrix = mat.GetMatrix(“myMatrix”?

2.4.5 在Cg代码中使用属性

除了Unity的固定管线因为有其固定语法之外,在Properties块中定义的属性必须在代码中再声明一次才能被使用。它们在Cg代码中的声明方式如下:
sampler2D _MyTexture;
float4 _Mycolor;
samplerCUBE _MyCube;
float4 _MyVector;
float _MyFloat;
float _MyRange;
除了上面提到的这些类型的变量外,还可以使用half4、fixed4和half、fixed,这两种类型的变量在空间上比float4和float分别减少了一半和又一半,因此在精度问题不是很突兀的情况下可以使用它们略微提高性能。

  相关解决方案