美文网首页
UnityShader:入门篇

UnityShader:入门篇

作者: 忆中异 | 来源:发表于2021-06-29 10:20 被阅读0次

今天我们来具体了解UnityShader及ShaderLab。


ShaderLab

学习UnityShader首先要了解ShaderLab。
基础

ShaderLab有效的组织了不同类型的文件导Unity中,列如纹理,顶点着色器,片元着色器等。而在一般的OpenGL或者DirectX当中,这些都是分离的,需要通过OpenGL或者DX的API一步一步的加载。ShaderLab对这些操作进行了非常高度的封装,用户只需要考虑着色器的实现细节即可。

从设计上来说,ShaderLab类似于CgFX和Direct3D Effects(.FX)语言,他们都定义了要显示一个材质所需要的所有东西,而不仅仅是着色器代码。

在Unity中,所有的UnityShader都是使用ShaderLab来编写的,ShaderLab是Unity提供,用于编写UnityShader的一种说明性语言,其用来描述一个UnityShader文件的结构。我们先来看看ShaderLab的写法:

Shader "ShaerName"{//UnityShader名称
    Properties{
        //属性,定义着色器需要调整的参数,纹理等
    }
    SubShader {
        //显卡A使用的子着色器
    }
    SubShader {
        //显卡B使用的子着色器
    }
    FallBack "Diffuse"  //上述着色器都不管用是,就使用Unity默认的表面着色器
}

在这个结构中包含了许多渲染所需的数据,如使用“Properties”定义着色器所需的各种属性,这些属性会在材质面板中显示。 Unity会根据使用的平台,来把这些结构编译成实际的代码和Shader文件。 我们来详细的分析上述伪代码的语义含义和用途

1.第一行是定义UnityShader的名称,定义了其名称后我们就可以在使用此Shader的材质选择中找到这个名称。着色器的文件名和其中的内容可以毫无关联,我们需要在着色器中定义其名称,该名称也是该着色器唯一的编号。
我们可以组合字符与“/”来控制它在Shader面板下拉菜单的位置,比如我们定义下面的名称

Shader "MyShaders/TestShader"

就可以在Shader下拉菜单中找到该Shader的名称


image.png
image.png

2.Properties中包含了一系列的属性,这些属性也将会在材质面板中体现,申明这些属性可以更方便的在材质面板中调整材质的属性。Properties语义块的定义通常如下:

Properties{
    Name("displayname",PropertyType) = DefaultValue
    Name("displayname",PropertyType) = DefaultValue
}

其中displayname指的是在材质面板中所显示的label,PropertyType是属性的类型,是纹理,还是一个浮点数或者一个Slider等,DefaultValue即默认值啦。下表是ShaderLab支持的属性类型。


image.png

例如:

Shader "MyShaders/TestShader"
{
    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
    }

    FallBack "Diffuse"
}
image.png

3.SubShader,每一个UnityShader文件可以包含多个SubShader语义块,但最少需要一个。当Unity需要加在此Shader时,Unity会扫描所有的SubShader,然后依次调用直到找到能够在目标平台上运行的SubShader。如果没有,Unity就会使用Fallback语义制定的UnityShader。
SubShader的结构通常如下:

SubShader{
    // optional
    [Tags]
    // optional
    [RenderSetup]
    Pass{

    }
    //other passes
}

SubShader中定义了一系列Pass以及可选的状态和标签,每个Pass定义了一次完整的渲染流程,但如果Pass的数目过多,往往会造成渲染性能的下降。

SubShader中的设置会应用到所有的Pass中,不过在Pass中可以继续设定,以覆盖SubShader中的设定。


image.png
  • SubShader标签:SubShader的标签是一个键值对(key/value pair),它的键和值都是字符串类型。这些键值对是SubShader和渲染引擎之间沟通的桥梁。它们用于设定SubShader希望如何渲染目标对象
    image.png
    4.Pass语义块通常包含如下内容:
Pass{
    [Name]
    [Tags]
    [RenderSetup]
    // other codes
}

通过Name可以定义该pass的名称, 通过UsePass命令可以直接使用其他UnityShader中的pass比如

UsePass "MyShader/MYPASSNAME"

这样可以提高代码的复用性。需要注意的是,Unity内部会把所有的Pass名称转换为大写字母表示,因此在使用UsePass命令时必须使用大写形式的名字。

另外,我们可以给Pass设置它自己的渲染状态,但它的标签不同于SubShader的标签。


image.png

除了普通的pass之外,UnityShader还支持一些特殊的pass,一遍进行代码的复用或者实现更加复杂的效果。

  • UsePass,已经提到过,复用之前的代码
  • GrabPass,该pass负责抓取屏幕并将结果储存在一张纹理中,以用于后续的pass来处理。

5.Fallback语义
它定义了这个着色器最次的一种形式,也就是如果上面所有的子着色器都用不了的话,就会用Fallback语义定义的着色器,你也可以关闭Fallback,这样一来就相当于是,如果所有的SubShader都不管用的话,那就随他去吧。


UnityShader

1.Unity Sahder的模板

image.png
标准着色器模板信息
image.png
image.png

2.UnityShader结构
接下来我们通过一个Unity默认的顶点/片元着色器代码来进一步了解UnityShader。

image

在如下UnityShader中,写了关键位置的注释,读者可以对照阅读从而进一步了解UnityShader

//Shader名称
Shader "Hidden/NewImageEffectShader"
{
    //申明所需的属性
    Properties
    { 
        //属性名为_MainTex,面板所显示的名称为Texture,2D只属性的类型
        //"white" 属性默认值
        _MainTex ("Texture", 2D) = "white" {}
    }
    //一个Shader程序至少有一个SubShader,系统在渲染时会依次调用,直到找到匹配的SubShader,否则使用最后默认指定的Shader
    SubShader
    {
        //Cull Off:关闭阴影剔除 
        //ZWrite :将像素的深度写入深度缓存中   
        //Always:将当前深度值写到颜色缓冲中 
        Cull Off ZWrite Off ZTest Always

        //渲染通道
        Pass
        {
            //Shader代码段开始,着色器的代码需要定义在CGPROGRAM-ENDCG之间
            CGPROGRAM
            //指定顶点着色器
            #pragma vertex vert
            //指定片元着色器
            #pragma fragment frag
            //引入Unity内置定义
            #include "UnityCG.cginc"

            //定义顶点着色器输入结构体
            struct appdata
            {
                //float4是思维向量,这里相当于告诉渲染引擎,这个属性代表的含义
                float4 vertex : POSITION; //把模型的顶点坐标填充到 vertex
                //纹理
                float2 uv : TEXCOORD0;//纹理坐标填充到uv
            };

            //与上边类似,这里使用一个结构体来定义顶点着色器的输出
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;//裁剪空间中的顶点坐标
            };

            //Vertex 顶点函数实现
            v2f vert (appdata v)
            {
                v2f o;
                //传递进来的顶点坐标是模型坐标系中的坐标值,需要经过矩阵转换车成裁剪空间坐标
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                //将计算后的结果输出给渲染引擎,底层会根据具体的语义去做对应的处理
                return o;
            }

            sampler2D _MainTex;

            //fragment 片元函数实现
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                col.rgb = 1 - col.rgb;
                return col;
            }
            ENDCG
        }
    }
}

我们通过一个实际的例子来看看UnityShader在实际项目中如何使用,这里我通过UnityShader来实现漫反射。

漫反射,是投射在粗糙表面上的光向各个 方向反射的现象。当一束平行的入射光线射到粗糙的表面时,表面会把光线向着四面八方反射,所以入射线虽然互相平行 ,由于各点的法线方向不一致,造成反射光线向不同的方向无规则地反射,这种反射称之为“漫反射”或“ 漫射 ”。这种反射的光称为漫射光。
漫反射光照是用于对那些被物体表面随机散射到各个方向的辐射度进行建模的。在漫反射中,视角的位置是不重要的因为漫反射是完全随机的,因此可以认为在任何反射方向上的分布都是一样的。但是入射光线的角度很重要。
漫反射符合兰伯特定律(Lambert’s law):反射光线的强度与表面法线和光源方向之间夹角的余弦值成正比,因此,漫反射的计算如下:(C-light · M-diffuse)max(0,n·I)即光照颜色 漫反射颜色 max(0,法向量光照方向)
使用Lambert进行漫反射渲染,在Shader中有两种写法,一种是逐顶点着色,另一种是逐像素着色

使用兰伯特定律进行漫反射渲染,在UnityShader中有两种写法,一种是逐顶点着色,另一种是逐像素着色。首先是逐顶点:

Shader "AlbertShader/VertexDiffuse"
{
    Properties
    {
        //漫反射颜色初始为白色
        _DiffuseColor("Color",Color)=(1,1,1,1)
    }
    SubShader
    {
        Pass
        {
            Tags{ "LightMode"="ForwardBase" }
            CGPROGRAM
            //声明顶点着色器
            #pragma vertex vert
            //声明片元着色器
            #pragma fragment frag
            //引入Unity内置光照函数库
            #include "Lighting.cginc"
            //定义外部属性-漫反射颜色
            float4 _DiffuseColor;
            //定义顶点着色器输入结构体
            struct appdata
            {
                //顶点坐标
                float4 vertex : POSITION;
                //顶点法线
                float3 normal : NORMAL;
            };
            //定义顶点着色器输出结构体
            struct v2f
            {
                //像素坐标
                float4 vertex : SV_POSITION;
                //临时变量:颜色
                fixed3 color : COLOR;
            };
            //顶点函数实现
            v2f vert (appdata v)
            {
                //定义顶点输出结构体对象
                v2f o;
                //顶点坐标转换到屏幕像素坐标
                o.vertex = UnityObjectToClipPos(v.vertex);
                //获取环境光
                float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                //将顶点法线转换到世界空间下,并做归一化处理
                float3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
                //将光照方向转换到世界空间下,并做归一化处理
                float3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                //使用公式运算
                fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * saturate(dot(worldNormal,worldLight));
                //结合漫反射和环境光
                o.color = ambient + diffuse;
                //返回结果
                return o;
            }
            fixed4 frag (v2f i) : SV_Target
            {
                //输入顶点颜色
                return fixed4(i.color,1.0);
            }
            ENDCG
        }
    }
}

然后是逐像素:

Shader "AlbertShader/PixelDiffuse"
{
    Properties
    {
        _DiffuseColor("Color",Color)=(1,1,1,1)
    }
    SubShader
    {
        Pass
        {
            Tags{ "LightMode"="ForwardBase" }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            float4 _DiffuseColor;
            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                fixed3 worldNormal : TEXCOORD0;
            };

            v2f vert (appdata v)
            {
                v2f o;
                //把世界空间下的法线传给片元着色器
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3  worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * saturate(dot(worldNormal,worldLightDir));
                fixed3 color = diffuse + ambient;
                return fixed4(color,1.0);
            }
            ENDCG
        }
    }
}

最后我们来看一下加入漫反射Shader后的效果

image

相关文章

网友评论

      本文标题:UnityShader:入门篇

      本文链接:https://www.haomeiwen.com/subject/pophultx.html