Unity Shader初识Shader,基础总结!

Posted 九九345

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity Shader初识Shader,基础总结!相关的知识,希望对你有一定的参考价值。

写在前面

这篇博客是对《Unity Shader 入门精要》第3章-Unity Shader基础的学习记录和总结。从这篇开始我就再也不是“纸上谈兵”得学习理论知识啦!

激动的心颤抖的手!

如何在Unity里创建Shader?创建材质?怎么把一个shader赋给材质?shader怎么赋给场景中的游戏对象?...如果你这些问题都有疑问——还不快去学!这篇博客不会讲这些基础的东西了,只会记录我想要写下来保存的内容(见谅~


/2020.4.3/补充

现在已经不用Sublime了,因为Sublime写HLSL太难受,后来干脆买了ShaderlabVSCode | 实用工具 工具 | Unity Asset Store,RMB大概200来块,用起来太香了,,,rendertype、forwardbase一些以前手敲的现在全都有注释。


1 如何设置用Sublime打开Shader

之前在学习Unity的时候需要写C#脚本,当时用的是Visual Studio 2022写的,external tool设定的就是VS2022,结果打开Shader发现,即使添加了ShaderlabVS插件,在VS里写Shader竟然无法自动缩进!每次都要一个tab一个tab的手打(心累),于是转战之前Houdini用的sublime。

于是,我参考下面这篇教程,想要完成同时达到:用VS打开C#,用sublime打开Shader,这一效果的配置

用SublimeText当Unity Shader的编辑器 - meteoric_cry - 博客园 (cnblogs.com)

结果我只完成了在sublime里添加Unity Shader包的操作,怎么都实现不了Unity双击.shader文件自动跳到sublime打开的效果,索性直接用了最粗暴的办法:

直接修改系统打开目标文件的默认程序,.shader文件选择“始终以sublime打开”.cs文件选择“始终以VS 2022打开”

这种粗暴的办法用起来也没啥不妥,但是其实用sublime写Shader也有不爽的地方——不能自动美化格式,但他可以自动缩进呀!相比VS已经够香的了~

关于格式设置

Preferences --> Settings --> 输入:

"word_wrap" : true

就可以实现根据窗口完整展示代码了 

2 我使用的Unity版本和环境

Unity我用的是Unity2021.3.8f1,但《入门精要》书是2016出版用的是Unity5.2.1,上Unity官网看的话,Unity版本3.x --> 4.x --> 5.x --> 2017.x --> ... --> 2022.x,时间线是依次推进的。

系统的话我是用的Win10,Windows是基于DirectX的。

不同Unity版本内置函数的使用

既然提到了Unity版本,这里就先打个预防针,要留意一些Unity不同版本之间内置函数使用的差异性,比如我在学习后面的内容时遇到了:

o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
//把MVP矩阵替换成了后面版本出现的内置函数

o.pos = UnityObjectToClipPos(v.vertex);

shader中的UnityObjectToClipPos和UNITY_MATRIX_MVP

上文中清楚的提到了Unity5.6版本之后,Shader中的:

  • UNITY_MATRIX_MVP会被UnityObjectToClipPos替代
  • UNITY_MATRIX_VP会被UnityWorldToClipPos替代
  • UNITY_MATRIX_P会被UnityViewToClipPos替代

不过,我发现Unity还是很智能的,它会帮你转换写的低版本函数。

3 Unity Shader与Shaderlab

Unity提供了Unity Shader让我们能更加轻松地管理着色器代码以及渲染设置,由于渲染设置包含了很多相对复杂的内容,例如逐片元操作(也叫合并)中——做的很多测试工作(深度测试、模板测试)、开启/关闭混合功能等等,我们不需要复杂和冗长的代码去管理,使得上手就轻松了很多。

总之,Unity Shader意义虽然挺抽象,但它本质上就是一个文本文件,有了它,我们学习和编写着色器的过程变得更加简单。

行,那我们怎么才能去使用这个Unity Shader来实现控制呢?——Unity提供了ShaderLab这样一种说明语言,所有的Unity Shader都由它来编写。既然是说明语言,那一定有某些表说明的“标签”,在ShaderLab中这样的“标签”就是一些嵌套在花括号内部的语义(Properties、SubShader等等),这些语义定义了Unity Shader的结构。啊,还有一点,如果你学习过DirectX,一定知道它用FX效果文件(.fx)来管理渲染方式,它也包含了渲染状态的设置和HLSL代码,可以把ShaderLab和FX效果文件类比着记忆。

4  ShaderLab的语义

ShaderLab的语义定义了整个Unity Shader的结构,语义的话有Properties、SubShader、Fallback语义块,它们三个是最最常用的语义,接下来我将参考《入门精要》中3.3章对语义部分的介绍进行叙述。

4.1 Properties语义块

为什么会说Properties语义块是材质和Unity Shader的桥梁?因为它定义的内容就是一些材质属性(property),这些属性会在Unity的Inspector面板中展现出来,把材质属性在Inspector面板暴露出来,我们调整起来更加方便!

须知:即使我们不在Properties语义块中定义某些属性,只要在后续的代码段中定义变量,也能使用这些属性变量,因此Properties语义块的作用仅仅是为了让属性可视化在Inspector面板中

一个Properties语义块格式通常如下:

Properties 
    Name ("display name", propertyType) = DefaultValue
    Name ("display name", propertyType) = DefaultValue
    ...

其中,

Name——名字,这是我们在Shader中访问它用到的属性名字,通常以一个下划线“_”开头

display name——显示名字,这是显示在Inspector面板中的名字

propertyType——属性类型,这是我们需要指定的类型,下面会举出常见的属性

DefaultValue——默认值,我们需要给每个属性指定默认值

4.1.1 常见的属性类型

Properties语义块支持的属性类型

属性类型默认值的定义语法
Intnumber
Floatnumber
Range(min, max)number

Color

(num, num, num, num)
Vector(num, num, num, num)
2D"defaulttexture"
Cube"defaulttexture"
3D"defaulttexture"

其中, 2D、Cube、3D的""内要么是空的,要么是Unity内置的纹理名称(black、white、bump等),至于它们是什么?在之前的HLSL常用函数介绍中,纹理映射函数就已经说过了纹理有1D(几乎不用了)、2D、Cube和3D。如果要指定纹理属性,需要在顶点着色器中编写计算相应纹理坐标的代码。

4.1.2 演示

Shader "Unlit/3.1"

    Properties
    
        //数字和slider
        _Int("Int",Int) = 2
        _Float("Float",float)=1.5
        _Range("Range",Range(0.0,5.0))=3.0
        //颜色值和向量
        _color("Color",Color)=(1,1,1,1)
        _vector("Vector",Vector)=(2,3,6,1)
        //纹理信息
        _2D ("2D", 2D) = "" 
        _Cube ("Cube", Cube) = "white" 
        _3D ("3D", 3D) = "black" 
    

    FallBack  "Diffuse"

4.2 SubShader语义块

每个Unity Shader都会至少有一个SubShader语义块。由于不同显卡有不同的渲染能力(很好理解,高级的显卡有更多更复杂的计算能力,而低端一点仅支持部分指令),Unity加载当前Unity Shader时会扫描所有的SubShader语义块,选择一个适合当前平台运行的SubShader语义块。如果都不支持,那么就会使用Fallback语义(后续会提到)指定的Unity Shader。

一个SubShader语义块的格式通常如下:

SubShader 
    //可选的
    [Tag]

    //可选的
    [RenderSetup]

    Pass 
        ...
    
    //other passes

4.2.1 渲染状态设置

还记得第3节介绍Unity Shader的时候,介绍了它可以让我们更方便地处理渲染设置吗?这就是在SubShader中实现的。ShaderLab提供了一系列渲染状态的设置指令,可以设置显卡的各种状态,是否开启混合模式、是否开启深度测等等,把冗长的代码凝炼成了RenderSetup的指令!

题外话:这里也正好验证了介绍GPU管线概述提到的——逐片元操作我们可进行配置但不是可编程的,这一特性。

还须注意,

  • 渲染状态和标签都是可选的,即,非必须项
  • SubShader中直接设置的渲染状态会应用于所有的Pass
  • Pass语义块也是可以定义标签[Tag]和渲染状态[RenderSetup]的,但和SubShader中定义的不太一样(后续会讲到)

ShaderLab常见的渲染状态设置选项

状态名称设置指令解释
CullCull Back | Front | Off剔除模式设置:剔除背面/正面/关闭
ZTestZTest Less Great | LEqual | GEqual | Equal | NotEqual | Always设置深度测试使用的函数
ZWriteZWrite On | Off开启/关闭深度写入
BlendBlend SrcFactor DstFactor开启并设置混合模式

4.2.2 SubShader的标签

Tags是一个键值对(Key/Value Pair),键和值都是字符串类型。目的是告诉Unity引擎,当前的SubShader希望如何以及何时渲染当前对象。

还须注意,SubShader的标签类型和接下来讲的Pass语义块的标签类型不一样,不能互通用~

SubShader的标签格式通常如下:

Tags "TagNamel" = "Value1" "TagName2" = "Value2"

SubShader的标签类型

标签类型说明
Queue控制渲染顺序,指定渲染队列,保证所有透明物体可以在所有不透明物体后面被渲染,还可以自定义渲染队列Tags "Queue" = "Transparent"
RenderType对着色器进行分类,例如定义当前着色器是透明or不透明Tags "RenderType" = "Opaque"
DisableBatching直接指明当前是否对该SubShader进行批处理Tags "DisableBatching" = "True"

ForceNoShadowCasting

控制当前SubShader是否投射阴影Tags "ForceNoShadowCasting" = "True"
IgnoreProjector为True,则表示使用当前SubShader的问题不受Projection影响,通常用于半透明物体Tags "IgnoreProjector" = "Ture"
CanUseSpriteAtlas当前SubShader用于精灵sprites时,标签为FalseTags "CanUseSpriteAtlas" = "False"
PreviewType指明如何预览该材质,默认情况是球形,可以改成plane、skybox等Tags "PreviewType" = "Plane"

定义的这些标签可以在Unity Shader的导入设置面板查看到:

4.2.3 Pass语义块

Pass语义块可以说是最重要的部分,几乎整个渲染管线可编程的着色器代码都写在Pass语义块内了,它的格式通常如下:

Pass 
    [Name]
    [Tags]
    [RenderSetup]
    //other code

  • Name——当前Pass的名字

可以是:

Name "MyPassName"

还可以使用ShaderLab的UsePass命令——通过访问其名字,直接使用其他Unity Shader中的Pass,这样可以提高代码的复用性,但须注意用UsePass命令时必须使用大写名字,例如:

UsePass "MyShader/MYPASSNAME"
  • Tags——Pass的标签

前面说了,Pass的标签不同于SubShader的标签,但目的是一样的,都是告诉Unity如何渲染该物体,Pass的标签:

Pass的标签类型

标签类型说明
LightMode定义该Pass在Unity的渲染流水线中的角色Tags "LightMode" = "ForwardBase"
RequireOptions指定当满足某些条件时才渲染该PassTags "RequireOptions" = "SoftVegetation"
  • RenderSetup——设置渲染状态

前面SubShader的状态设置同样适用于Pass。

4.3 Fallback语义块

这个其实就是一个保证:万一当前的显卡实在太low了,现有的所有SubShader都无法运行,FallBack语义块就相当于一个最次备选,给他准备一个Fallback内置的最最low的Shader。

格式通常如下:

Fallback "name"
//or
Fallback Off

Fallback不仅有这一个功能,还能影响阴影的投射。渲染阴影纹理时,Unity需要在Unity Shader中寻找一个阴影投射的Pass,而Fallback刚好有内置的这样一个通用的Pass,有了它我们就不需要再单独写一个Pass了,说明正确设置Fallback是多么的重要!

5 Unity Shader的形式

5.1 表面着色器

学习渲染管线的时候似乎还没听说过这个着色器。

《入门精要》是这么描述的:它是Unity自创的一种着色器代码类型,本质上和顶点/片元着色器是一样的。总结下来有这两个特点,

  • 它需要的代码很少,Unity做了很多工作(Unity会在幕后给它转化成顶点/片元着色器),但渲染的代价较大
  • 表面着色器是被定义在SubShader里,而不是Pass里
  • 被定义在CGPROGRAMENDCG之间

5.2 顶点/片元着色器

他们是老朋友了,在Unity中是用Cg/HLSL语言编写的,灵活性相比表面着色器高很多。这里就不介绍啥代码结构啦,后面当然会再次遇到,到时候再剖析也不迟。

5.3 固定函数着色器(被抛弃

关于“固定”,其实我在PC和手机的主流图形API介绍中就提到了:“OpenGL ES 2.0——引入可编程着色器,弃用了1.x中需对固定功能”,其中的固定功能就是指的这个固定函数着色器,是OpenGL ES 1.1及之前的版本会用的,DX的话是7.0之前,现在只有一些旧设备需要使用啦,所以才会说“被抛弃”。

对于它我们不深入了解,只需要知道有过就行。

Unity Shader入门总结

最近断断续续学习了一些Unity Shader的内容,总结一下,主要学习资料是siki学院的课程。

Unity Shader基本结构

Shader "Unlit/002" //Shader路径
{
    Properties //Shader属性 可在面板修改
    {
        _Int("Int",Int) = 2  
        _Float("Float",float) = 1.5
        _Range("Range",range(0.0,2.0))= 1.0
        _Color("Color",Color) = (1,1,1,1)
        _Vector("Vector",Vector) = (1,4,3,8)
        _MainTex("Texture", 2D) = "white" {}
        _Cube("Cube",Cube) = "white"{}
        _3D("3D",3D) = "black"{}
    }
    SubShader  //子着色器,可以有多个,如果第一个SubShader无法运行就执行第二个,以此类推
    {
        Tags { "RenderType"="Opaque" }  //标签,可选
        LOD 100  //LOD

        Pass //Pass通道,可以有多个
        {
            CGPROGRAM //CG语言开始
            #pragma vertex vert  //定义顶点着色器的函数名为vert
            #pragma fragment frag  //定义片元着色器的函数名为frag    
                
            #include "UnityCG.cginc"  //包含Unity提供的头文件
                
            //POSITION表示将模型顶点坐标传入v,SV_POSITION表示顶点着色器的输出是裁剪空间顶点坐标    
            float4 vert(float4 v:POSITION):SV_POSITION 
            {
                return UnityObjectToClipPos(v);  //将模型坐标转为裁剪空间坐标
            }

            //SV_TARGET表示片元着色器输出颜色到帧缓存中
            fixed4 frag():SV_TARGET
            {
                return fixed4(1,1,1,1);  //直接返回白色
            }
            ENDCG //CG语言结束
        }
    }
    Fallback "VertexLit" //如果以上SubShader都无法使用,则使用VertexLit这个Shader
}

这个最简单的Shader将顶点坐标传入顶点着色器,转换成裁剪空间坐标后输出给片元着色器,片元着色器不做任何处理,直接将每个像素置为白色,效果如图:

技术图片

结构体的使用

Shader "Unlit/003"
{
    Properties
    {
        _Color("Color" , Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            //只有在CGPROGRAM内再次定义一个与属性块内名字与类型相同的变量,属性块对应的变量才能起作用
            fixed4 _Color;

            struct a2v//传入顶点着色器的结构体
            {
                //用模型顶点填充v变量
                float4 vertex:POSITION;
                //用模型的法线填充n变量
                float3 normal:NORMAL;
                //用模型的第一套uv填充texcoord
                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);
                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;
                return fixed4(c, 1);
            }

            ENDCG
        }
    }
}

Shader中使用结构体和C语言类似,然后我们就可以使用结构体来进行顶点着色器和片元着色器的数据传输,上面这个Shader中,在顶点着色器里将法线向量映射到[0,1]区间,然后保存在v2f的color变量里传给片元着色器,片元着色器将color与Shader属性里的_Color相叠加输出。

技术图片

可以看到右边颜色偏蓝,左边偏红,上边偏绿,这是将法线向量作为颜色输出的结果,x分量较大就会偏红,其他两个分量同理。

漫反射Shader

Shader "Unlit/005"
{
    Properties
    {
        _Diffuse("Diffuse", Color) = (1,1,1,1)

    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "Lighting.cginc" //引入灯光相关头文件

            fixed4 _Diffuse;

            struct v2f
            {
                float4 vertex : SV_POSITION;
                fixed3 color: Color;
            };

            v2f vert (appdata_base v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;  //获得环境光颜色
                fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); //将法线转到世界空间
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz); //获得第一个平行光的方向
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal,worldLight));  //lambert光照模型计算
                o.color = diffuse + ambient;
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                return fixed4(i.color,1);
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
}

appdata_base是Unity在UnityCG.cginc中提供的结构体,包含顶点、法线和UV信息。

在顶点着色器中使用Lambert光照模型(关于光照模型的原理这篇先不提)计算漫反射光照,_LightColor0表示场景第一个灯光的颜色,灯光颜色乘以物体固有色乘以世界空间法线和世界空间灯光位置的点积再加上环境光即得到最终的顶点颜色。

也可以在片元着色器中计算光照,如何取舍取决于追求性能还是质量,一般来说顶点数量是小于像素数量的,所以如果追求更好的性能就在顶点着色器中计算,如果想要更好的质量就在片元着色器中计算。

用片元着色器计算漫反射:

Shader "Unlit/006"
{
    Properties
    {
        _Diffuse("Diffuse", Color) = (1,1,1,1)

    }

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
                
            fixed4 _Diffuse;
            
            struct v2f
            {
                float4 vertex : SV_POSITION;
                fixed3 worldNormal: TEXCOORD0;
            };
            
            v2f vert (appdata_base v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                fixed3 worldNormal = UnityObjectToWorldNormal( v.normal);
                o.worldNormal = worldNormal;
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldLightDir,i.worldNormal));
                fixed3 color = ambient + diffuse;
                return fixed4(color,1);
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
}

技术图片

高光反射Shader

Shader "Unlit/008"
{
    Properties
    {
        _Diffuse("Diffuse", Color) = (1,1,1,1)
        _Specular("Specular", Color) = (1,1,1,1)
        _Gloss("Gloss", Range(1,256)) = 5
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct v2f
            {
                float4 vertex : SV_POSITION;
                fixed3 color: Color;
            };

            v2f vert (appdata_base v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);

                fixed3 worldPos = mul(unity_ObjectToWorld, v.vertex);

                //漫反射
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
                fixed3 worldLight = UnityWorldSpaceLightDir(worldPos);
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal,worldLight));

                //高光反射
                fixed3 reflectDir = normalize(reflect(-worldLight,worldNormal));
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(reflectDir,viewDir)),_Gloss);//Phong光照模型计算
                
                o.color = diffuse + ambient + specular;
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                return fixed4(i.color,1);
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
}

高光反射计算使用Phong光照模型,计算反射向量reflectDir时需要注意将WorldLight取反才是正确的入射光方向,观察向量viewDir使用UnityWorldSpaceViewDir得到即可。

技术图片

Blinn-Phong高光反射

Shader "Unlit/010"
{
   Properties
    {
        _Diffuse("Diffuse", Color) = (1,1,1,1)
        _Specular("Specular", Color) = (1,1,1,1)
        _Gloss("Gloss", Range(1,256)) = 5
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct v2f
            {
                float4 vertex : SV_POSITION;
                fixed3 worldNormal: TEXCOORD0;
                float3 worldPos: TEXCOORD1;
            };

            v2f vert (appdata_base v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                fixed3 worldNormal = UnityObjectToWorldNormal( v.normal);
                o.worldNormal = worldNormal;
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                //漫反射
                fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldLightDir,i.worldNormal));

                //高光反射
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                fixed3 halfDir = normalize(worldLightDir + viewDir);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(i.worldNormal,halfDir)),_Gloss);
                
                fixed3 color = ambient + diffuse + specular;
                return fixed4(color,1);
            }
            ENDCG
        }
    }
}

另一种高光反射计算方法,使用灯光向量和观察向量的和halfDir与世界法线进行计算,saturate用于将值限定在0-1之间,这种计算得出的结果高光会更亮一些。

纹理采样

Shader "Unlit/011"
{
     Properties
    {
        _MainTex("MainTex", 2D) = "white" {}
        _Diffuse("Diffuse", Color) = (1,1,1,1)
        _Specular("Specular", Color) = (1,1,1,1)
        _Gloss("Gloss", Range(1,256)) = 5
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            sampler2D _MainTex;
            //用一个纹理加上_ST表示该纹理的缩放和偏移
            //_MainTex_ST.xy存储的是缩放值,而_MainTex_ST.zw存储的是偏移值
            float4 _MainTex_ST;
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct v2f
            {
                float4 vertex : SV_POSITION;
                fixed3 worldNormal: TEXCOORD0;
                float3 worldPos: TEXCOORD1;
                float2 uv : TEXCOORD2;
            };

            v2f vert (appdata_base v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                fixed3 worldNormal = UnityObjectToWorldNormal( v.normal);
                o.worldNormal = worldNormal;
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                //用Unity内置的TRANSFORM_TEX得到纹理坐标
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);//v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                //纹理颜色,用tex2D进行采样
                fixed3 albedo = tex2D(_MainTex, i.uv).rgb;

                //漫反射
                fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
                fixed3 diffuse = _LightColor0.rgb * albedo * _Diffuse.rgb * (dot(worldLightDir,i.worldNormal)*0.5+0.5);

                //高光反射
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                fixed3 halfDir = normalize(worldLightDir + viewDir);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(i.worldNormal,halfDir)),_Gloss);
                
                fixed3 color = ambient + diffuse + specular;
                return fixed4(color,1);
            }
            ENDCG
        }
    }
}

纹理采样Unity提供了相关的方法,TRANSFORM_TEX和tex2D用于计算纹理坐标和纹理颜色,注意必须要声明_MainTex_ST。

技术图片

法线纹理映射

切线空间法线纹理映射

Shader "Unlit/012"
{
     Properties
    {
        _MainTex("MainTex", 2D) = "white" {}
        _BumpMap("Normal Map", 2D) = "bump" {}
        _BumpScale("Bump Scale", float) = 1
        _Diffuse("Diffuse", Color) = (1,1,1,1)
        _Specular("Specular", Color) = (1,1,1,1)
        _Gloss("Gloss", Range(1,256)) = 5
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _BumpMap;
            float4 _BumpMap_ST;
            float _BumpScale;
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct v2f
            {
                float4 vertex : SV_POSITION;
                fixed3 lightDir: TEXCOORD0;
                float3 viewDir: TEXCOORD1;
                float2 uv : TEXCOORD2;
                float2 normalUv : TEXCOORD3;
            };

            v2f vert (appdata_tan v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                o.normalUv = TRANSFORM_TEX(v.texcoord, _BumpMap);

                //求副切线向量
                //float3 binormal = cross(v.normal,v.tangent.xyz) * v.tangent.w;
                //float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);
                TANGENT_SPACE_ROTATION;

                //求切线空间光源方向及视角方向
                o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
                o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;

                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                fixed3 tangentLightDir = normalize(i.lightDir);
                fixed3 tangentviewDir = normalize(i.viewDir);

                fixed4 packedNormal = tex2D(_BumpMap,i.normalUv);

                //需要将法线贴图设置成normal map
                fixed3 tangentNormal = UnpackNormal(packedNormal);
                tangentNormal.xy *= _BumpScale;

                //环境光
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                //纹理颜色
                fixed3 albedo = tex2D(_MainTex, i.uv).rgb;
                
                //漫反射
                fixed3 diffuse = _LightColor0.rgb * albedo * _Diffuse.rgb * (dot(tangentLightDir,tangentNormal)*0.5+0.5);

                //高光反射
                fixed3 halfDir = normalize(tangentLightDir + tangentviewDir);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(tangentNormal,halfDir)),_Gloss);
                
                fixed3 color = ambient + diffuse + specular;
                return fixed4(color,1);
            }
            ENDCG
        }
    }
}

世界空间法线纹理映射

Shader "Unlit/013"
{
       Properties
    {
        _MainTex("MainTex", 2D) = "white" {}
        _BumpMap("Normal Map", 2D) = "bump" {}
        _BumpScale("Bump Scale", float) = 1
        _Diffuse("Diffuse", Color) = (1,1,1,1)
        _Specular("Specular", Color) = (1,1,1,1)
        _Gloss("Gloss", Range(1,256)) = 5
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _BumpMap;
            float4 _BumpMap_ST;
            float _BumpScale;
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float4 uv : TEXCOORD0;
                float4 TtiW0 : TEXCOORD1;
                float4 TtiW1 : TEXCOORD2;
                float4 TtiW2 : TEXCOORD3;
            };

            v2f vert (appdata_tan v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
                o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);

                //计算世界坐标下的顶点位置,法线,切线,副法线
                float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
                fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
                fixed3 worldBinormal = cross(worldNormal,worldTangent) * v.tangent.w;

                //按列摆放得到从切线空间到世界空间的变换矩阵
                o.TtiW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
                o.TtiW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
                o.TtiW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                //求世界坐标
                float3 worldPos = float3(i.TtiW0.w,i.TtiW1.w,i.TtiW2.w);

                //计算时间空间下的光照和视角
                fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));

                //获得法线纹理
                fixed4 packedNormal = tex2D(_BumpMap,i.uv.zw);
                fixed3 tangentNormal = UnpackNormal(packedNormal);
                tangentNormal.xy *= _BumpScale;

                //切线空间法线转换到世界坐标
                fixed3 worldNormal = normalize(float3(dot(i.TtiW0.xyz, tangentNormal),dot(i.TtiW1.xyz, tangentNormal),dot(i.TtiW2.xyz, tangentNormal)));

                //环境光
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                //纹理颜色
                fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb;
                
                //漫反射
                fixed3 diffuse = _LightColor0.rgb * albedo * _Diffuse.rgb * (dot(lightDir,worldNormal)*0.5+0.5);

                //高光反射
                fixed3 halfDir = normalize(lightDir + viewDir);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormal,halfDir)),_Gloss);
                
                fixed3 color = ambient + diffuse + specular;
                return fixed4(color,1);
            }
            ENDCG
        }
    }
}

技术图片

以上是关于Unity Shader初识Shader,基础总结!的主要内容,如果未能解决你的问题,请参考以下文章

Unity Shader基础

Unity Shader基础

Unity Shader 知识点总结

unity shader学习笔记 shader基础结构以及Properties面板

Unity Shader入门精要学习笔记 - 第3章 Unity Shader 基础

unity的色环渐变shader