Untiy Shader学习基础(build in管线)

发布时间 2023-07-07 14:15:57作者: CatSevenMillion

1.渲染流水线

  流水线的任务是从3D模型出发,绘制出一个2D的屏幕场景。

  渲染流水线一共分为三个阶段:1.应用阶段,主要作用是准备好场景数据,执行Culling操作,设置每个模型的渲染状态,输出渲染图元给下一个阶段 2.几何阶段,决定绘制的图元是什么,要怎么样绘制。并将数据变换到屏幕上,将数据与着色等信息传递给下一个阶段 3.光栅化阶段,使用上一个阶段的数据,产生像素渲染到屏幕上。

  在流水线阶段,两个可完全编程也是我们最长使用到的着色器是:顶点着色器(Vertex Shader)片元着色器(Fragment Shader)

  顶点着色器输入来自于CPU,处理的单位是顶点,并且无法创建销毁顶点也无法得知顶点与顶点之间的关系。由于这样的相互独立性,导致了顶点着色器可以并行处理顶点。顶点着色器完成的主要功能是:坐标变换和逐顶点光照。 坐标变换:把顶点坐标从模型空间转换到齐次空间。逐顶点光照:计算顶点颜色。

  片元着色器:片元着色器的输入数据是光栅化阶段中三角形遍历后的数据这一阶段可以完成很多重要的渲染技术,其中最重要的技术之一是纹理采样(要实现纹理采样,需要在顶点着色器阶段输出每个顶点对应的纹理坐标)。片元着色器可以完成很多重要效果,但是它仅可以影响单个片元,也就是说不能把自己的任何结果直接发送给邻居。

  虽然流水线的过程比较复杂,但是Unity为我们封装了很多功能,绝大多数情况下,我们只需要在Untiy Shader中设置输入,编写顶点、片元着色器,设置设置一些状态就可以实现绝大多数想要的功能了。

2.HLSL,GLSL,Cg

  这三个是着色语言,专门用于编写Shader。常见的有DirectX的HLSL,OpenGL的GLSL,NVIDIA的Cg。在Unity中使用Build in渲染管线使用的是Cg语言,URP和HDRP 的Shader Lab 采用的是HLSL语言。

3.Unity Shader

  3.1 材质与Unity Shader

  Unity中我们需要配合材质(Material)和Unty Shader才能实现一个渲染效果,一般基本流程如下:

    1)创建一个材质

    2)创建一个Unity Shader,并把它赋给创建的材质

    3)把材质赋给想要渲染的对象

    4)面板中调整Untiy Shader参数,实现想要的效果

  Unity中的材质需要结合Mesh或者粒子系统才能工作。Unity在创建Shader时会为我们提供多种选项。Unlit Shader会产生一个不包含光照的基本顶点/片元着色器。Image Effect Shader为我们实现各种屏幕后处理效果提供了一个模版。Compute Shader会产生一种特殊的,利用GPU并行性进行一些与常规渲染流水线无关的计算。Standard Surface Shader为我们提供了典型的表面着色器的实现方法。  

  3.2 Shader Lab

  Unity的Shader Lab是Untiy为我们提供的高层次的渲染抽象层。它使用一些嵌套在花括号中的语义来描述一个Unity Shader的文件结构。例如Properties语句块中定义了着色器所需的各种属性。Untiy会将它结构编译成真正的代码和Shader文件,开发者只需要和Untiy Shader打交道。

  一个Unity Shader的基础结构如下:

Shader "ShaderName"{
    Properties {
    //属性
    }
    SubShader {
    //显卡A使用的子着色器
    }
    SubShader {
    //显卡B使用的子着色器
    }
    Fallback  "VertexLit"
}

   3.3 材质与Shader Lab的桥梁:Properties

  Properties包含了一系列属性,它的语义块定义如下:

Properties {
    Name ("display name", Properties) = DefaultValue
    Name ("display name", Properties) = DefaultValue
    //更多属性
}

  如果我们需要在Shader中访问它们,就需要每个属性的名字(Name),这些名字通常由下划线开始。显示名字(display name)则是出现在材质面板上的名字。除此之外我们还需要指定它们的属性(PropertyType)。每个属性还需要设置默认值

  Properties支持的属性有以下:包括Int,Float,Range数字类型。Vector四维数组。2D,Cube,3D三种纹理类型,默认是字符串+花括号,字符串可以为空或者是“white”“black”“gray”“bump”内置纹理名称。

Properties
    {
        _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,5,1)
        //
        _2D ("2D",2D) = "" {}
        _Cube ("Cube",Cube) = "white" {}
        _3D ("3D",3D) = "black"{}
    }

赋给材质球后效果:

   3.4 重量级成员 SubShader

  每一个Unity Shader文件可以有多个SubShader,但最少一个。Unity在加载时会扫描所有的SubShader语义块,选择一个能够在目标平台上运行的SubShader。如果都不支持的话就会使用FallBack语义指定的Unity Shader。这样做的目的是因为不同显卡有不同的能力,为了兼容更多的设备。

  SubShader语义块定义如下:

SubShader {
    // 可选
    [Tags]

    // 可选
    [RenderSetup]

    Pass {

    }
    //Other Pass
}

  SubShader中定义了一系列Pass以及可选的状态[RenderSetup]标签[Tags]设置。每个Pass定义了一次完整的渲染流程,但是Pass过多可能会导致渲染性能下降。因此尽可能要减少Pass的使用。

  状态设置:

|状态名称|设置指令|解释|
|–|–|
|Cull|Cull Back | Front |Off|设置裁剪模式|
|ZTest|ZTest Less Greater | LEqual | GEqual | Equal | NotEqual | Always |设置深度测试时使用的函数|
|ZWrite|ZWrite On | Off|开启/关闭深度写入|
|Blend|Blend SrcFactor DstFactor|开启并设置混合模式|

  状态设置可以设置显卡的各种状态,并且设置了的渲染状态会影响到所有的Pass。如果不想这样,我们可以在Pass语义块中单独设置。
  SubShader标签:

   标签是一组键值对,它们用来告诉渲染引擎,我们应该如何渲染这个对象。

  Pass语义块,如下:

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

  我们可以定义Pass的名称: Name “MyPass” , 通过这个名称我们可以在其他的Unity Shader的Pass :  UsePass “MyShader/MYPASSNAME”

       我们还可以设置Pass的标签和设置,告诉引擎我们应该如何渲染该物体。

  3.5 后路Fallback

  跟在SubShader后面可以是一个Fallback指令。它用来告诉Untiy如果上面所有的SubShader都行不通那就使用这个最低级的Shader。

  语义如下:

Fallback “name”
// 或者
Fallback Off
// 使用最低级Shader
Fallback "VertexLit"

 

当然除此之外,你应当还需要具备矢量,矩阵的一定基础。

  顶点的空间变换过程:

  一个顶点最开始在模型空间上被定义,之后会被变换到世界空间(无限大的一个唯一空间)上,之后会从世界空间变换到观察空间上,我们可以通过平移整个观察空间,让摄像机远点位于世界坐标远点,坐标轴与前者重合。之后从观察空间变换到裁剪空间,裁剪空间的目的是方便地对渲染图元进行裁剪。一般是使用视椎体来决定的,被剔除的部分后续不会被渲染。当所有裁剪操作执行完毕就可以从裁剪空间变换到屏幕空间上了。

  法线变换,法线是需要我们特殊处理的一种方向矢量。游戏中,模型往往一个顶点会携带额外信息,顶点法线就是其中的一种信息。法线在模型变换时可能会有不与面垂直,我们可以利用与切线永远垂直的特性重新确定法线。