【Unity】ShaderLab语法总结

预备知识

  • 模型文件存储结构(包含了顶点位置以及面的组成信息)



渲染过程

渲染管线的知识,很有必要去冯乐乐的书里看一下,在第9页( 2.3 GPU 流水线 )



不同的渲染管线会有不同的差异,但是大体还是类似,要经过以上的一些步骤。
流水线中,我简化了重点去理解:

  • 首先CPU把顶点数据传递给Shader的顶点着色器(在Unity中就是通过Mesh Renderer组件),开始一次DrawCall。
  • GPU在几何阶段把三维的坐标通过自己定义的顶点函数,按照给定的规则映射到二维的屏幕坐标中。
  • 于是屏幕上就出现了很多个点,但是还没有连成闭合的区域,所以在三角形遍历的时候,就是通过插值的方式在各个点包围的三角形内部填充其他点的信息。
  • 片元着色器把三角形中的每个点给拿过来,通过自定义的规则,为每个像素填充颜色。
  • 填完颜色后,放入缓存,等待后续的测试,例如混合测试。(比如透明物体背后还有一个物体,那么两个物体的颜色怎么混合)。
  • 最后输出屏幕。

可以用以下简化的图来表示,其中Vertex Processor和Pixel Processor中间还有重要的插值的一步,因为顶点的个数是远少于像素的个数的,那么这个输入输出该怎么对应?
如果像素有多少个,至少顶点也要有多少个吧,所以多余的顶点的信息(包括位置,顶点法线,颜色等等)就是通过原本的顶点插值计算出来的。

  • Shader是图形可编程方案的程序片段

  • 渲染管线是一种计算机从数据到最终图形程序的形象描述

  • 材质是商品,Shader是方法,贴图是材料

  • 图形语言包括:
    OpenGL的GLSL
    DirectX的HLSL
    NVIDIA的CG

  • GPU采用并行计算,因此在顶点处理程序中,所有顶点信息可以同时计算,并且耗费相同时间。

  • 渲染管线最终认识的还是Vertex ShaderFragment Shader,Unity发明的Surface Shader只是对这两种Shader的包装。

  • Vertex Shader中,包含顶点坐标转换顶点色顶点法线的计算等工作,例如把模型文件中的点的信息转换成屏幕上的坐标信息等。

  • 渲染的过程中,一些Unity的全局光照设置、主摄像机等等信息都会作为全局变量,供Shader的使用。

  • 每一个像素格都会根据顶点着色器Fragment Shader中的方法计算出结果


ShaderLab 的基本结构

  • ShaderLab语言是对HLSL、CG等语言的封装。更准确的说,HLSL、CG等语言是以内嵌的方式存在于ShaderLab语言中的。所以当遇到内嵌的代码段的时候,其中的语法和其外面包裹着的ShaderLab的语法是不一样的。
shader "name"{
    [Properties]
    SubShaders
    [Fallback]
}

以下的规则仅仅指ShaderLab的语法,不代表其中内嵌的CG、HLSL的语法。

  • ShaderLab中不区分大小写
  • ShaderLab中不需要分号
  • ShaderLab中必须至少要有一个SubShader
  • 由于硬件不同,因此可以有多个SubShader,如果第一个SubShader不支持该硬件,那么就会顺序执行第二个SubShader
  • 如果所有的SubShader都不能执行,那么在Fallback中就会执行最后处理,通常在这里的处理用的都是Unity内建的一些通用的基础Shader

先从连连开ShaderForge开始

  • 这里的连连看插件用的是Shader Forge,但是现在已经不更新了,最后更新是在2018的版本中,但是也可以用。
  • Shader Forge最后生成的Shader代码没有Shader Graph那么多,稍微方便一点,所以先用这个。

用ShaderForge实现兰伯特光照

兰伯特光照是最基础的漫反射模型,其效果如下


在兰伯特光照中,很明显,朝向光的点最亮(灰度为1),背向光的点最暗(灰度为0),中间则介于0~1之间。
通过光的反方向lDir某个点的朝向(该点法向量)nDir的接近程度,就可以得知该点是否面朝光
刚好,数学上的点乘Dot刚好可以用于表示两个向量方向的接近程度,数值越大,两者方向越接近。(两个向量前提都经过了归一化处理)
点乘的定义是:a·b = |a||b|cosθ
当某点面朝光时,θ = 0,那么cosθ = 1。最后点乘结果也是等于1。
模型上的每个点都进行一次 灰度值 = a·b 的运算,就可以得到整个模型经过漫反射之后的外观。

通过上面的描述,我们首先需要获取两个重要的量,一个是全局光照的方向IDir,一个是几何体上的某点的法向量nDir。打开ShaderForge,空白处右键:

这两个数据一个在Lighting下,是全局的,一个在Geometry Data下。



对它们进行点乘,点乘节点位于向量运算Vector Operations里。



点击左上角Compile Shader就可以将ShaderForge中的连接编译成shader代码。

选中创建的shader右键create->material,创建材质。



将材质赋予游戏物体,得到最后效果。



Fixed Function Shader( 了解 )

参考文章:
https://blog.csdn.net/weixin_30699831/article/details/95653586

固定命令的Shader,可以在所有硬件平台上运行,但是功能有限。

固定渲染管线中,主要包含以下代码块:


Shader "Custom/s01"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _Ambient("Ambient",Color) = (0.3,0.3,0.3,0.3)
        _Specular("Specular",Color) = (1,1,1,1)
        _Shininess("Shininess",Float) = 0.3
        _Emission("Emission",Color) = (1,1,1,1)
    }
    SubShader
    {
        Pass{
            Material{
                Diffuse[_Color]
                Ambient[_Ambient]
                Specular[_Specular]
                Shininess[_Shininess]
                Emission[_Emission]
            }
            Lighting on
            SeparateSpecular on
        }
    }
}

CG语法

  • 写在CGPROGRAM和ENDCG包裹的片段中,但是这两个关键字是ShaderLab的语法,不是CG的语法。
  • CG语言区分大小写
  • 通过#pragma vertex 顶点函数名声明顶点着色器
  • 通过#pragma fragment 片元函数名声明片元(像素)着色器
  • 通过#include "库函数名.cginc"声明外部引用的库
数据类型
  • float4:4维向量,分量分别是xyzw。每个分量的占32bit,精确到小数点后6位。
  • half4: 4维向量,每个分量占16bit,表示范围为[-60000,60000],精确到小数点后3位。
  • fixed4:4维向量,分量分别是rgba。每个分量占11bit。表示范围为[-2,2],精度为1/256
    使用分量的时候,可以通过.x/.y/.z/.w/.xy/.xz等等,甚至还可以.xxyy。
    例如以下的赋值方式都合法的:
float4 pos;
float3 temp = pos.xyz;
temp = pos.yzz;
temp = float3(pos.yz,1);

不可以将低维向高维转换,因为缺失了高维的数据。
例如: float3(1,0,0) --x--> float4(1,0,0,?)
可以将高维向低维转换,会直接舍弃高维数据。
例如: float4(1,0,0,1) --√-->float3(1,0,0)

  • 坐标位置通常会用float4表示,由于float4类型的位置可以方便矩阵变换。所以需要将原本float3类型的位置信息扩散到齐次坐标空间。并且分量w可以用于表示该坐标是一个点,还是一个向量。
    点,用float4表示,就是float4(x,y,z,1)。
    向量,用float4表示,就是float4(x,y,z,0)。
  • 颜色通常会用fixed4来表示,因为颜色的表示不需要那么大的精度
  • 齐次坐标空间:将一个原本是n维的向量用一个n+1维向量来表示
各种语义的意义和范围
  1. Unity中Renderer组件传递给顶点着色器的关联语义
    POSITION : 模型空间(相对于模型自身)的顶点坐标,float4类型。分量xyz可用于表示的范围为[-0.5,0.5],分量w的取值为0或1,0代表点,1代表向量。
    NORMAL : 顶点法线,float3类型
    TANGENT :顶点切线,float4类型
    TEXCOORDn :第n套纹理坐标,float2或float4类型
    COLOR : 顶点颜色,fixed4或float4类型

  2. 顶点着色器输出数据的语义
    SV_POSITION :剪裁空间(屏幕空间)下的坐标。左下角为原点(0,0) 横轴的范围是(0,屏幕宽度) 纵轴的范围是(0,屏幕高度)。结构体中必须包含一个用该语义修饰的变量。
    COLOR0 : 通常用于输出第一组顶点颜色,但不是必需的
    COLOR1 : 通常用于输出第二组顶点颜色,但不是必需的
    TEXCOORD0~TEXCOORD7 : 通常用于输出纹理坐标,但不是必需的

  3. 片元着色器输出数据的语义
    SV_TARGET : 最终输出屏幕的颜色值,fixed4或float4类型。有时候这个语义可以写成COLOR,但是由于SV_TARGET在各个平台上都容易被识别,所以还是选用这个。

posted @ 2021-02-06 00:05  JimmyZou  阅读(8)  评论(0编辑  收藏  举报  来源