Shader开发案例精讲

82 阅读21分钟

一.创建第一个Shader

组织架构:

image.png

MeshFilter决定了是什么样的模型,MeshRenderer能够将模型绘制出来,并设置材质,每一个材质对shader的封装,在此基础上还包括变量以及属性的定义,材质就类似我们不同表面的效果。每一个材质都会封装一个具体的shader来执行具体的着色效果。

unity提供的默认shader类型

image.png

  • Standard Surface Shader:标准的表面着色器(一般都使用标准的表面着色器)
  • Unlit Shader:无光照着色器
  • Image Effect Shader: 图像特效着色器
  • Compute Shader:计算着色器(通过计算机GPU数值计算用到的着色器)
  • Shader Variant Collection:

unity在我们新建shader的时候,为我们的shader填充了基本的框架代码,默认是接收纹理贴图的漫反射的shader,我们通过对它进行修改,来学习快速开发自定义shader。

1.安装shader插件

unity创建好的shader文件,使用VS打开的话会出现杂项文件的问题,这时候我们本身安装环境是安装好的,只需要下载好shader的扩展(代码提示)即可。

扩展-管理扩展中找到以下shader扩展工具,下载即可。

image.png

2.shader代码模板介绍

shader文件的路径

    Shader "Custom/StandardDiffuse"

这个shader路径对应的是材质materials的shader选项,根据这个shader文件路径,可以在materials里面的shader选项中找到,同时改变了这个路径之后,对应的目录路径也会随之改变。比如我改成如下路径规范:

    Shader "ShaderCaseStudy/Chapter01/StandardDiffuse"

对应的shader选项会变成这样:

image.png

Properties属性块:

属性块中会声明好我们需要用到的属性,比如颜色,纹理,都是在里面进行定义。同时在属性块中里面定义的属性,可以在unity面板查看器里面可以进行相应的指定和初始化。

    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具体的着色执行

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

        CGPROGRAM
        // 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.0

        sampler2D _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 assumeuniformscaling
        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }

CGPROGRAM以及ENDCG包裹起来的代码称之为CG语言,进行具体的着色操作,一些计算都在里面进行。

3.创建自定义属性

创建自定义属性,默认是下划线开头的。自定义属性结构如下:

(下划线)+属性名(“GUI名称,也就是unity面板上的名称”,“属性的类型”)= 默认值

注意点:当我们有多个对象使用同一个材质的时候,我们改变了材质的属性值后,会影响所有使用这个材质的对象。所有我们需要改变材质的时候,都会习惯的新建一个材质,然后设置不同的值,最后在赋给对象。

常见的属性值类型有:

image.png

    //在这里,我们使用shader代码创建自定义自己的属性
    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
        //自定义属性
        //添加环境光的颜色
        _AmbientColor("Ambient Color",Color) = (1,1,1,1)
        //添加自定义滑块的取值
        _MySliderValue("This is a slider",Range(0,10)) = 2.5
    }

另一边unity面板编辑器也可以接收并使用对应我们创建好的属性:

image.png

实际上在GUI上创建的是Ambient Color和This is a slider这两个元素,在shader文件里面,将他们分别保存在_AmbientColor和_MySliderValue变量里面,这样GUI查看器的属性值和shader属性值相关联,使得它们指向同一个数据。

4.变量关联

自定义好shader属性的同时,我们也需要在SubShader声明一个同名称的变量,使得在SubShader声明的变量和shader里面的属性值相关联。只有通过这种方式才能让CG语言里面的变量和属性里面的变量关联起来。

GUI查看器---》属性块---》CG语言

常用的对应关系有以下几种:

    // Color,Vector   float4,half4,fixed4(数字4表示4个元素)
    //Rnage,Float     float,half,fixed
    //2D              sample2D
    //3D              sample3D
    //Cube            sampleCube

CG语言在脚本中也不允许有代码错误出现,如果有错误,材质会被渲染成无着色的效果。在unity2017之后,无着色效果是用一种纯白色来表示。代码错误提示不像C#出现在编辑器上面,shader的代码错误提示是出现在unity面板编辑器上,双击错误会跳转到VS错误那一行。并且,shader错误不会停止unity的运行,如何判断是否有错误,只能判断是否有着色效果来进行判断。

注意点:对材质的属性值修改是一种永恒的修改,也就是你修改更新值了,就会发生修改值的保存。

二、表面着色器和纹理映射

表面着色器的工作流程:

获取3D模型数据--》传入到修改器里面--》将input作为我们表面函数的输入结构体--》在表面函数中进行计算--》将计算结果填写到表面输出结构体中+从场景中获取的光源信息结合起来进行物理计算(光照模型的计算内容)--》得到屏幕上每一个像素点最终的颜色信息。

image.png

1.漫反射着色

漫反射每一个物体都会有的,是比较简单的一种反射效果,在着色器中比较容易实现,并且在低模游戏中比较常用。

光照模型、以及光照模型对应的参数:

unity5之后,引入了物理渲染,需要提供更多的渲染相关的信息(平滑度、光滑度),Specular:镜面反射

image.png

常用的结构体属性介绍:

image.png

去掉光照,会发现立方体每一个面都是一样的,没有着色效果,如果有了光照之后,漫反射效果就会很明显,每一个方向不一样对应的着色也是不一样的。

image.png

有光照之后的漫反射效果明显:

image.png

漫反射代码详解:

    Shader "ShaderCaseStudy/Chapter02/CustomDiffuse"
{
    Properties
    {
        //漫反射只需要物体表面无光照的颜色
        _Color ("Color", Color) = (1,1,1,1)
    }
    SubShader
    {
    //设置着色器常用标签
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // surface函数告诉我们当前表面处理函数是surf,Standard表示使用的是标准的光照模型,fullforwardshadows表示完整的阴影效果
        #pragma surface surf Standard fullforwardshadows

        // 使用的shader模型是3.0的版本,此版本可以获得更好的光照效果
        #pragma target 3.0

        //表面函数的输入结构体
        struct Input
        {
            float2 uv_MainTex;
        };
        fixed4 _Color;

        UNITY_INSTANCING_BUFFER_START(Props)
        UNITY_INSTANCING_BUFFER_END(Props)

        //表面处理函数,参数分别是输入结构体,输出结构体
        //注意点:不同的光照模型所需要的参数是不一样的,所返回的类型也随之变化
        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // 将输入体获取到的结构信息计算后填充到输出结构体,表面输出结构体在传递到光照模型进行光照计算
            //所以如何更好的计算决定了最后的效果是怎么样的
            fixed4 c =  _Color;
            o.Albedo = c.rgb;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

2.压缩数组

shader里面的代码都是针对于模型的顶点以及像素进行,所以GPU采用并行计算运行的原因。在CG里面有两种类型的变量,一种是单值,一种是压缩数组。

压缩数组变量,例如:float3(3个float类型的变量),int4(4个int类型的变量)

针对于压缩数组,有几种特点:

  • 混写(允许在一行代码中多个元素进行重新排列)
  • 涂抹(单值赋值实际上是给多个变量赋同一个值)
  • 遮蔽

image.png

image.png

_m表示获取的是矩阵里面的成员,必须得加上

3.给着色器添加纹理

新建shader文件,将对应的模型贴图指定到_MainTex属性值当中,然后模型使用对应的这个材质球,即可给着色器添加纹理效果。

image.png

4.通过UV值滚动纹理

    Shader "ShaderCaseStudy/Chapter02/ScrollingShader"
{
    Properties
    {
        _MainTint ("MainTint", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        //创建自定义属性值
        _ScrollingXSpeed("X Scrolling Speed",Range(0,10)) = 2
        _ScrollingYSpeed("Y Scrolling Speed",Range(0,10)) = 2
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows
        #pragma target 3.0

        sampler2D _MainTex;

        //输入结构体中传入主纹理的UV值
        struct Input
        {
            float2 uv_MainTex;
        };

        fixed4 _MainTint;
        fixed _ScrollingXSpeed;
        fixed _ScrollingYSpeed;


        UNITY_INSTANCING_BUFFER_START(Props)
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            //获取主纹理的UV值
            fixed2 scrolledUV = IN.uv_MainTex;
            //获取UV的滚动偏移量
            fixed xScrollValue = _ScrollingXSpeed * _Time; //__Time为float类型,
            fixed yScrollValue = _ScrollingYSpeed * _Time;
            //将偏移量应用到UV上面
            scrolledUV+=fixed2(xScrollValue,yScrollValue);
            //获取纹理上面的信息
            half4 c = tex2D(_MainTex,scrolledUV);
            //赋值到最后的输出结构体里面
            o.Albedo = c.rgb * _MainTint;
            //设置rgb通道
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

四个shader会使用到的内置时间: image.png

5.法线映射

在光线与材质表面交互过程中,朝向起到了非常重要的作用,如果我们面对不同的方向,反射光线的角度也是不同的。即使多个面的颜色是相同的,但是着色效果却是不同的。对于曲面对象来说,就会出现渲染的效果很不理想。为了解决这个问题,光线和材质表面交互我们不考虑朝向,而是使用顶点的法向,也就是三角形三个顶点包含法向信息(顶点可以保存信息),实际上三角形每个顶点上都有自己的法向,这个法向就是三角形顶点线性的差值,这样就实现高模效果的实例。

法线贴图也称为凹凸贴图,法线贴图就是控制模型的凹凸变化的。

    Shader "ShaderCaseStudy/Chapter02/NormalMapShader"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        //创建法线贴图属性,法线贴图默认值是bump,告诉unity这是一个法线贴图
        _NormalTex("Normal map",2D) = "bump"{}
        //创建法线贴图强度
        _NormalMapIntensify("Normal Intensify",Range(0,1)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows

        #pragma target 3.0


        struct Input
        {
            //提供法线映射的UV值
            float2 uv_NormalTex;
        };

        fixed4 _Color;
        sampler2D _NormalTex;
        fixed _NormalMapIntensify;

        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            //对纹理进行采样,并解析,最后得出正确的法线信息
            //UnpackNormal是unity内置提供的辅助函数
            float3 normalMap = UnpackNormal(tex2D(_NormalTex,IN.uv_NormalTex));
            //根据强度大小来调节法线贴图的强度
            normalMap.x *=_NormalMapIntensify;
            normalMap.y *=_NormalMapIntensify;
            //将法线映射填充到标准的输出结构体中
            o.Normal = normalize(normalMap);//normalize作用:标准化为单位矢量
            o.Albedo = _Color.rgb;
            o.Alpha = _Color.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

最终效果:

image.png

6.创建透明材质

    Shader "ShaderCaseStudy/Chapter02/TransparentShader"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
    }
    SubShader
    {
        //Tags用于添加渲染信息的,这些渲染信息描述了如何渲染
        //渲染类型指定为透明类型
        //忽视其他的投影物体
        //设置渲染队列
        Tags { 
        "RenderType"="Transparent"
        "IgnoreProjector"="True"
        "Quene" = "Transparent"
        }
        LOD 200
        Cull Back //剔除背面物体
        CGPROGRAM
        // alpha:fade设置alpha通道的融合(当前材质的所有像素必须跟alpha通道的值与屏幕上现在绘有的像素进行融合,这样才会实现透明效果)
        #pragma surface surf Standard alpha:fade

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
        };

        fixed4 _Color;

        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;

            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

最终效果:

image.png 渲染队列参数详解:

image.png

7.创建全息投影

    Shader "ShaderCaseStudy/Chapter02/HolographicShader"
{
    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
        //定义自定义属性,控制全息投影的强度(边缘效果的强弱)
        _RimEffect("Rim Effect",Range(-1,1)) = 0.25
    }
    SubShader
    {
        Tags { 
        "RenderType"="Transparent"
        "IgnoreProject" = "True"
        "Quene"="Transparent"
        }
        LOD 200

        //不剔除背景
        Cull off
        //不需要光照效果
        Lighting off
        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Lambert alpha:fade

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
            //世界的法线
            float3 worldNormal;
            //视图的方向
            float3 viewDir;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        float _RimEffect;
        // 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 assumeuniformscaling
        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutput o)
        {
            //对主纹理进行采样
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            
            o.Albedo = c.rgb;
            //计算边缘效果的强弱,也就是光线射向模型与模型法线形成的夹角
            //求点积的时候,点积值为0,垂直了,点积值为1,表示垂直了(相同方向),点积值为-1,表示垂直(相反方向)
            float border = 1-abs(dot(IN.viewDir,IN.worldNormal));
            //差值运算,越接近中心,越透明,相反越远中心,就越实体
            float alpha = (border * (1-_RimEffect) + _RimEffect);
            o.Alpha = c.a * alpha;//赋值到表面输出结构体中
        }
        ENDCG
    }
    FallBack "Diffuse"
}

最终效果:

image.png

8.纹理的压缩和融合

lerp插值的运算介绍:

image.png

详细介绍:如果a,b是两个纹理,这时f是一个系数的话,这时候a,b里面每一个像素都通过f同一个系数进行差值。如果f也是一个纹理的话,a,b里面每一个像素的话都会对应f里面每一个相同的位置比例的像素进行不同的差值。

    Shader "ShaderCaseStudy/Chapter02/PackedTextureShader"
{
    Properties
    {
        //默认漫反射的主色调
        _MainTint("Diffuse Tint",Color) = (1,1,1,1)
        //添加颜色
        _ColorA("Terrain A",Color) = (1,1,1,1)
        _ColorB("Terrain B",Color) = (1,1,1,1)
        //添加不同纹理
        _RTexture("Red Channel Texture",2D) = ""{}
        _GTexture("Green Channel Texture",2D) = ""{}
        _BTexture("Blue Channel Texture",2D) = ""{}
        _ATexture("Alpha Channel Texture",2D) = ""{}
        //设置控制融合的纹理
        _BlendTex("Blend Texture",2D) = ""{}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Lambert 

        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_BlendTex;
        };
        float4 _MainTint;
        float4 _ColorA;
        float4 _ColorB;
        sampler2D _RTexture;
        sampler2D _GTexture;
        sampler2D _BTexture;
        sampler2D _ATexture;
        sampler2D _BlendTex;

        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutput o)
        {
            //对融合纹理进行采样
            float4 blendData = tex2D(_BlendTex,IN.uv_BlendTex);
            float4 rTexData = tex2D(_RTexture,IN.uv_BlendTex);
            float4 gTexData = tex2D(_GTexture,IN.uv_BlendTex);
            float4 bTexData = tex2D(_BTexture,IN.uv_BlendTex);
            float4 aTexData = tex2D(_ATexture,IN.uv_BlendTex);

            //计算出最后融合的结果
            float4 finalColor;
            finalColor = lerp(rTexData,gTexData,blendData.g);
            finalColor = lerp(finalColor,bTexData,blendData.b);
            finalColor = lerp(finalColor,aTexData,blendData.a);

            //设置finalColor的alpha值
            finalColor.a = 1.0;

            //差值
            float terrainLayers = lerp(_ColorA,_ColorB,blendData.r);
            finalColor*=terrainLayers;

            //约束值的范围
            finalColor = saturate(finalColor); //约束在有效颜色范围之内
            o.Albedo = finalColor * _MainTint.rgb; // 设置主色调
            o.Alpha = finalColor.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

指定纹理贴图:

image.png

最终效果:

image.png

9.创建贴合地图的圆环

如果地形凹凸不平,我们需要使用圆环自适应凹凸不平的地形.效果如下:

image.png

我们需要创建地形,以及对应的材质。

给自定义地形添加纹理:

image.png

给自定义地形添加自定义材质:

image.png

RediusShader

    Shader "ShaderCaseStudy/Chapter02/RediusShader"
{
    Properties
    {
        //圆环的中心位置
        _Center("Center",Vector) = (0,0,0,0)
        //定义半径的大小
        _Radius("Radius",Float) = 0.5
        //定义圆环的颜色
        _RadiusColor("Radius Color",Color) = (1,0,0,1)
        //定义圆环的宽度
        _RadiusWidth("Radius Width",Float) = 2
        //主纹理
        _MainTex("Terrian Texture",2D) = ""{}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // 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.0

        

        struct Input
        {
            float2 uv_MainTex;
            float3 worldPos;//以世界坐标为中心来绘制圆环
        };
        sampler2D _MainTex;
        float3 _Center;
        float _Radius;
        fixed4 _RadiusColor;
        float _RadiusWidth;


        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            //当前绘制的位置与中心位置的距离
            float d = distance(_Center,IN.worldPos);
            //判断是否处于圆环绘制范围
            if(d>_Radius&&d<_Radius+_RadiusWidth)
            {
                o.Albedo = _RadiusColor;
            }else
            {
                o.Albedo = tex2D(_MainTex,IN.uv_MainTex).rgb;
            }
        }
        ENDCG
    }
    FallBack "Diffuse"
}

RediusController.cs

该脚本实现的作用是移动的时候,圆环跟着移动

    using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RediusController : MonoBehaviour
{
    public Material radiusMaterial;
    public float radius = 1;
    public Color color = Color.white;

    void Update()
    {
        //圆环的设置
        if(radiusMaterial != null)
        {
            radiusMaterial.SetVector("_Center", transform.position);
            radiusMaterial.SetFloat("_Radius", radius);
            radiusMaterial.SetColor("_RadiusColor", color);
        }
    }
}

并且指定现有地形所使用的材质:

image.png

三、光照模型(LightingModel)

光照模型负责接收属性,计算每一个像素的着色效果。unity将这些实现的光照效果隐藏起来,大部分情况下写一个光照模型都需要理解光照反射,折射的物理原理,实现起来复杂。大部分情况下只需要调用unity内置提供的光照模型即可。但是理解光照模型的实现原理对于后面的学习是非常有必要的

1.创建一个自定义的漫反射光照

Lambert经常用于低模游戏中,在手游中非常受欢迎。一个表面反射光的数量取决于入射光线和表面法线的夹角

image.png

点积为0,表示两个矢量是垂直的,也就是夹角为90,如果点积对于1,-1,表示两者是互相平行的,为1表示 平行的方向是一致的,为-1表示平行的方向是相反的。

复杂的平面光线反射:

image.png

注意点:unity版本的不同其光照强度是不同的,在unity4的lambert需要*2,光照强度比较弱,在unity5就不需要乘2

    Shader "ShaderCaseStudy/Chapter03/LambertShader"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}

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

        CGPROGRAM
        // Lambert是unity内置lambert的实现,如果需要手动的只需要加上Simple
        #pragma surface surf SimpleLambert

        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
        };


        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutput o)
        {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }

        //SimpleLambert和LightingSimpleLambert自动关联起来
        //SurfaceOutput表面输出结构体,lightDir:光线的方向 atten:光线的衰减率
        half4 LightingSimpleLambert(SurfaceOutput s,half3 lightDir,half3 atten){
        
        //一个表面反射光的数量取决于入射光线和表面法线的夹角
        //顶点的法线和光线的方向,计算两个之间的夹角
        half NdotL = dot(s.Normal,lightDir);
        //根据夹角大小的不同,计算发射光线的强弱
        half4 c;
        c.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten); //_LightColor0场景中光线的颜色
        c.a = s.Alpha;
        return c;
        
        }

        ENDCG
    }
    FallBack "Diffuse"
}

2.创建卡通着色器

    Shader "ShaderCaseStudy/Chapter03/TooShader卡通着色器"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _RamTex("Ramp",2D) = "white"{}
        _ToonShaderLayer("Toon Shading Layers",Range(2,10)) = 10
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Toon2

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _RamTex;
        struct Input
        {
            float2 uv_MainTex;
        };

        float _ToonShaderLayer;

        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        fixed4 LightingToon(SurfaceOutput s,fixed3 lightDir,fixed atten){
            //法线和光线方向的点积
            half NdotL = dot(s.Normal,lightDir);
            //对坡度映射进行采样
            NdotL = tex2D(_RamTex,fixed2(NdotL,0.5));
            //新建返回的值
            fixed4 c;
            c.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten);
            c.a = s.Alpha;
            return c;

        }
        //通过值来控制卡通着色器
        half4 LightingToon2(SurfaceOutput s,half3 lightDir,half3 viewDir,half atten){
            half NdotL = dot(s.Normal,lightDir);
            half toon = floor(NdotL*_ToonShaderLayer) / (_ToonShaderLayer - 0.5);
            half4 c;
            c.rgb = s.Albedo * _LightColor0 * toon * atten;
            c.a = s.Alpha;
            return c;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

指定纹理:

image.png

最终效果:

image.png

3.创建方式反射模型

一般的物体是由镜面反射和漫反射构成:

image.png

常用的光照函数:

image.png

计算光照的公式:

image.png L:光照方向,R:反射光线,N:法线方向,V:视图方向

    I(方式模型的光照强度) = D(漫反射)+S(镜面反射)
    D = N.L(法线与光照方向的点积)
    S = (R.V)的p次方 (p表示镜面反射的系数,R反射光线的矢量,V是视图方向的矢量)
    R(反射光线的矢量) = 2N.(N.L) - L

对应的方式反射的shader代码:

    Shader "ShaderCaseStudy/Chapter03/PhongSpecShader"
{
    Properties
    {
    //创建漫反射主色调
        _MainTint ("Diffuse Tint", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
    //添加镜面反射的颜色
    _SpecularColor("Specular Color",Color) = (1,1,1,1)
    //设置镜面反射的强度--控制系数
    _SpecPower("Specular Power",Range(0.1,30)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf CustomPhong 

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
        };

        float4 _SpecularColor;
        float4 _MainTint;
        float _SpecPower;


        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutput o)
        {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _MainTint;
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }

        fixed4 LightingCustomPhong(SurfaceOutput s,fixed3 lightDir,half3 viewDir,fixed3 atten){
        //求反射部分,求法线和光线方向的点积
        float NdotL = dot(s.Normal,lightDir);
        //求反射的向量--需要公式,标准化为单位矢量
        float3 reflectionVector = normalize(2.0 * s.Normal * NdotL - lightDir);
        //计算镜面反射的部分--与观察的方向和光线的方向是有关系的
        //先求镜面反射的系数
        float spec = pow(max(0,dot(reflectionVector,viewDir)),_SpecPower);//max忽略负值
        //镜面发射的颜色信息
        float3 finanSpec = _SpecularColor.rgb * spec;
        //最后的效果
        fixed4 c;
        //返回漫反射的颜色+镜面反射的颜色
        c.rgb = (s.Albedo * _LightColor0.rgb * max(0,NdotL) * atten) + (_LightColor0.rgb * finanSpec);
        c.a = s.Alpha;
        return c;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

最终效果:

image.png

4.创建宾氏反射模型

宾氏反射模型与方式反射模型相比,宾氏反射模型表面更光滑一些,方式反射模型不会那么规整。

L:光照方向,R:反射光线,N:法线方向,V:视图方向 H:半向量(L和V的中间向量)

image.png

相关宾式反射模型shader代码:

    Shader "ShaderCaseStudy/Chapter03/BlinnPhongSpecShader"
{
    Properties
    {
        _MainTint ("Main Tint", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        //添加镜面反射的颜色
        _SpecularColor("Specular Color",Color) = (1,1,1,1)
        //设置镜面反射的强度--控制系数
        _SpecPower("Specular Power",Range(0.1,60)) = 3
       
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf CustomBlinnPhong 

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
        };

        float4 _SpecularColor;
        float4 _MainTint;
        float _SpecPower;

        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutput o)
        {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _MainTint;
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        fixed4 LightingCustomBlinnPhong(SurfaceOutput s,fixed3 lightDir,half3 viewDir,fixed atten){
           //法向和光线方向的点积
           float NdotL = max(0,dot(s.Normal,lightDir));
           //半矢量(光线方向和视图方向的中间位置)
           float3 halfVector = normalize(lightDir + viewDir);
           //半矢量和法向量的点积
           float NdotH = max(0,dot(s.Normal,halfVector));
           //镜面反射的系数
           float spec = pow(NdotH,_SpecPower);
           float4 c;
           c.rgb = (s.Albedo * _LightColor0.rgb * NdotL) + (_LightColor0.rgb * _SpecularColor.rgb * spec) * atten;
           c.a = s.Alpha;
           return c;
        
        }

        ENDCG
    }
    FallBack "Diffuse"
}

最终效果:

image.png

image.png

5.创建各项异性反射模型

各项异性反射模型是一种镜像类型,它模拟表面凹槽的定向性,并且在垂直方向上并延伸的镜面反射,这种模型特别适用于模拟拉丝金属效果。

    Shader "ShaderCaseStudy/Chapter03/AnisotropicSpecShader"
{
    Properties
    {
        _MainTint("Diffuse Tint",Color) = (1,1,1,1)
        _MainTex("Base (RGB)",2D) = "white"{}
        _SpecularColor("Specular Color",Color) = (1,1,1,1)
        _SpecularAmount("Specular Amount",Range(0,1)) = 0.5
        //镜面反射的强弱
        _SpecularPower("Specular Power",Range(0,1)) = 0.5
        //各项异性的方向
        _AnisoDir("Anisotropic Direction",2D) = ""{}
        //纹理的偏移控制
        _AnisoOffset("Anisotropic Offset",Range(-1,1)) = -0.2
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf AnisotropicSpec 

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

   
        struct Input
        {
            float2 uv_MainTex;
            float2 uv_AnisoDir;
        };
        sampler2D _MainTex;
        sampler2D _AnisoDir;
        float4 _MainTint;
        float4 _SpecularColor;
        float _AnisoOffset;
        float _SpecularAmount;
        float _SpecularPower;
        
        //创建自定义的输出结构体
        struct SurfaceAnisoOutput{
            fixed3 Albedo;
            fixed3 Normal; //法向量
            fixed3 Emission; //自发光
            fixed3 AnisoDirection; //各项异性方向
            half Specular;//镜面发射系数
            fixed Gloss;//高光系数
            fixed Alpha; //Alpha的值
        };
 
        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceAnisoOutput o)
        {
            //获取每一个像素的信息
            half4 c=tex2D(_MainTex,IN.uv_MainTex) * _MainTint;
            //各项异性的法线信息
            float3 anisoTex = UnpackNormal(tex2D(_AnisoDir,IN.uv_AnisoDir));
            //填充表面输出结构体
            o.AnisoDirection = anisoTex;
            o.Specular = _SpecularAmount;
            o.Gloss = _SpecularPower;
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }

        fixed4 LightingAnisotropicSpec(SurfaceAnisoOutput s,fixed3 lightDir,half3 viewDir,fixed atten){
            //求半矢量
            fixed3 halfVector = normalize(normalize(lightDir)+normalize(viewDir));
            //光线的方向与法线的夹角
            float NdotL = saturate(dot(s.Normal,lightDir));
            //半矢量和法向的点积
            fixed HdotA = dot(normalize(s.Normal+s.AnisoDirection),halfVector);
            //各项异性的属性
            float aniso = max(0,sin(radians((HdotA+_AnisoOffset) *180)));
            //镜面反射系数
            float spec = saturate(pow(aniso,s.Gloss * 128) * s.Specular);
            fixed4 c;
            c.rgb = ((s.Albedo * _LightColor0.rgb * NdotL) +(_LightColor0.rgb * _SpecularColor.rgb * spec)) * atten;
            c.a = s.Alpha;
            return c;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

最终效果:

image.png

四、物理渲染

unity5版本之后引入了物理渲染,PBR工作原理的学习

unity5引入了两个重要概念改变了PBR,unity并没有对它们驰加真实的物理限制,PBR通过使用光照模型强制实行一些物理原则来弥补这些缺陷

1.标准光照模型(standard)

  • 能量守恒(发射光线不能比入射光线多)
  • 维表面散射(粗糙表面反射光线比光滑反射光线更加具有不确定性)
  • 菲涅尔反射(镜面反射出现在滤色角)
  • 表面遮挡(角落或者其他难以照亮的几何体会变得比较暗) 2.全局光照(Global Illumination GI)

1.理解金属度的设置

在传统的着色器里面,创建材质的时候,很容易引入一些非真实的光照条件,从而打破这种真实感,这是因为着色器中的材质里面所有的属性都是无关联的。通过引入金属工作流,unity对物理的外观表现驰加了更多的约束,使得shader的使用者很难创造出一些不符合物理逻辑的材质。在标准的着色器里面,纯粹的金属材质漫反射部分很暗,镜面反射的颜色取决于LB的纹理映射。相反纯粹的非金属材质漫反射部分取决于Albedo纹理映射,镜面反射的颜色取决于入射光的颜色。

image.png

2.给PBR添加透明度

2.1 Transparent

当材质的shader的渲染类型为Transparent的时候,它的透明度强度变化是根据Albedo里面的颜色alpha的通道值来决定的。值越小,透明度也就越明显。

image.png

2.2 fade

image.png

Transparent与fade的区别在于,在alpha通道值为0的时候,Transparent类型下,因为PBR的作用下它是有轮廓的,但是在fade的模式下,alpha值为0的时候,它是完全看不见,并且边缘没有任何的折射效果。

fade渲染模式适用于非真实的对象,比如说一些科幻题材里面的全息图,激光射线,人造光源,幽灵或者是粒子特效经常会用到这种。

2.3 Cutout

材质会按照像素指定它的alpha通道值,所以有些部分会显示出来,有些却显示不出来,根据像素里面的alpha控制每一部分的显示隐藏。

image.png

3.创建镜面反射表面

反射弹针

image.png

反射弹针的设置参数:

image.png