# 第三章 UnityShader 基础

正式进入 shader 学习了,好耶!

使用 UnityShader 的流程:

  1. 创建一个材质
  2. 创建一个 UnityShader,为材质添加
  3. 把材质赋给对象
  4. 调整 shader 属性

image-20211130223524763

# 创建 Shader

Unity 包含了多种 shader 模板供我们使用。

Standard Surface Shader标准光照模型
Unlit Shader不包含光照(但包含雾效)
mage Effect Shader实现各种屏幕后处理效果

Unity Shader 的导入面板还可以方便地查看其使用的渲染队列(Render queue)、是否关闭批处理(Disablebatching)、属性列表(Properties)等信息。

# ShaderLab

ShaderLab 是 Unity 为我们抽象的一种 shader 语言,可以更方便的通用。

image-20211130224529408

# 基本结构

Shader "ShaderName"{
	Properties{
		// 属性
        
        Name("display name", PropertyType)=DefaultValue
	}
	SubShader{
		//A 着色器
	}
	SubShader{
		//B 着色器
		
	}
	Fallback "VertexLit"
}

# 属性类型

image-20211130225328452

根据以上属性得到的 Shader 放置在材质上效果:

image-20211201122154053

# SubShader

每个 Unityshader 可以包含多个 SubShader, 最少要有一个

加载时会自动选择第一个可以执行的 SubShader,如果都不支持的话,使用 FallBack 指定的 shader。(为了解决不同显卡的差异)

SubShader{
    // 可选的
    [Tages]
    // 可选的
    [RenderSetup]
    
    Pass {
        
    }
    //other Passes
}

每个 Pass 定义了一次完整的渲染流程,Pass 过多,性能下降。

# 状态设置

在 SubShader 中我们可以设置渲染的一些状态。

image-20211201123019333

在 SubShader 中设置的状态会应用到所有的 Pass,也可以在 Pass 中单独设置。

Tags{"TagName1"="Value1" "TagName2"="CC"} //Tags 是一些 str 键值对

# SubShader 标签类型

image-20211201123359479

这些标签只能在 SubShader 中申明不可在 Pass 中申明

# Pass

Pass{
    [Name]
    [Tags]
    [RenderSetup]
    //Other
    }

可以通过 Name 来使用 Pass,例如:

UsePass "MyShader/PASSNAME"

默认 Unity 会将所有 Pass 名称转换为大写,所以使用时需要使用大写名称

# Pass 标签类型

image-20211201124000121

  • ・UsePass:如我们之前提到的一样,可以使用该命令来复用其他 Unity Shader 中的 Pass;

  • ・GrabPass:该 Pass 负责抓取屏幕并将结果存储在一张纹理中,以用于后续的 Pass 处理(详见 10.2.2 节)。

# FallBack

在 SubShader 后定义,如果上述的都不支持,用这个最低级的 Shader 吧!

Fallback "name"
// 或者
Fallback off

FallBack 还会影响阴影的透射

# Unity Shader 形式

着色器代码可以写在 SubShader 中(表面着色器做法),也可以写在 Pass 语义块中(顶点 / 片元着色器和固定函数着色器的做法)。

Shader "MyShader"{
    Properties{
        // 所需的各种属性
        
    }
    SubShader{
        // 真正意义上的 shader 代码会放在这里
        // 表面着色器(Surface Shader)
        // 顶点 / 片元着色器(Vertex/Fragment Shader)
        // 固定函数着色器(Fixed Function Shader)
    }
    SubShader{
        // 和上一哥 SubShader 类似
    }
}

# 表面着色器 (Surface Shader)

是 Unity 自己创造的一种着色器代码类型,需要的代码量少,渲染代价比较大

在背后,任然转换为顶点片元着色器。

Shader "Custom/Simple"{
    SubShader{
        Tags{"RenderType"= "Opaque"}
        CGPROGRAM
        #pragma suface surf lambert
            struct Input{
                float4 color: COLOR;
            }
        void surf(Input In,in out SurfaceOutput o){
            o.Albedo=1;
        }
        ENDCG
    }
    Fallback "Diffuse"
}

表面着色器不需要关心,有多少个 Pass,每个 Pass 如何渲染

CGPROGRAM 和 ENDCG 之间的代码是使用 CG/HLSL 编写的,也就是说,我们需要把 CG/HLSL 语言嵌套在 ShaderLab 语言中。

# 顶点片元着色器(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
        }
    }
}

顶点 / 片元着色器的代码也需要定义在 CGPROGRAM 和 ENDCG 之间,但不同的是,顶点 / 片元着色器是写在 Pass 语义块内,而非 SubShader 内的.

# 固定函数着色器

对于一些比较老旧的设备,不支持可编程管线着色器,就需要使用固定函数着色器

# 使用哪种形式?

  • 如果你想和各种光源打交道,你可能更喜欢使用表面着色器,但需要小心它在移动平台的性能表现。
  • 如果你需要使用的光照数目非常少,例如只有一个平行光,那么使用顶点 / 片元着色器是一个更好的选择。
  • 最重要的是,如果你有很多自定义的渲染效果,那么请选择顶点 / 片元着色器。

至此,第三章完!