Unity Shader入门精要 --- 阅读笔记 18.07.24

Unity Shader 的形式01.Unity Shader 的最重要的任务还是指定各种着色器所需的代码.02.不管哪种形式,真正意义上的 Shader 代码都需要包含在 ShaerLab 语义块中例:Shader "MyShader"{ Properties { // 所需要的各种属性. } SubS...
摘要由CSDN通过智能技术生成

Unity Shader 的形式

01.Unity Shader 的最重要的任务还是指定各种着色器所需的代码.

02.不管哪种形式,真正意义上的 Shader 代码都需要包含在 ShaerLab 语义块中

例:

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

3.4.1 Unity 的宠儿: 表面着色器

01.表面着色器(Surface Shadre), 是Unity 自己创造的一种着色器代码类型.代码量小,渲染代价大.本质和顶点/片元着色器是一致的.表面着色器可以理解为是 Unity 对顶点/片元着色器的更高一层的抽象.

表面着色器示例代码:

Shader "Custom/Simple Surface Shader"
{
    SubShader
    {
        Tags {"RenderType" = "Opaque" }
        CGPROGRAM
        #pragma surface surf Lambert
        struct Input
        {
            float4 color : COLOR;
        };
        void surf (Input IN, inout SurfaceOutput o)
        {
            o.Albedo = 1;
        }
        ENDCG
    }
    Fallback "Diffuse"
}

从这段代码中我们可以看出,表面着色器被定义在 SubShader 语义块 (而非 Pass 语义块) 中的 CGPROGRAM 和 ENDCG 之间.原因是:表面着色器不需要开发者关心使用多少个 Pass,每个 Pass 如何渲染,这些事情 Unity 会在背后为我们处理好.

02.CGPROGRAM 和 ENDCG 之间的代码是使用 CG/HLSL 编写的,也就是说,我们需要把 CG/HLSL 语言嵌套在 ShaderLab 语言中.注意:这里的 CG/HLSL 是 Unity 经封装后提供的,它的语法和标准的 CG/HLSL 语法几乎一样.但是,有些原生的函数和用法 Unity 并没有提供支持.

3.4.2 顶点/片元着色器

01.在 Unity 中,我们可以使用 CG/HLSL 语言来编写 顶点/片元着色器(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_MAP,v);
            }


            fixed4 frag() : SV_Target
            {
                return fixed4(1.0,0.0,0.0,1.0);
            }
            
            ENDCG
        }
    }
}

顶点/片元着色器是写在 Pass 语义块内的,而非 SubShader 内的.原因是:我们需要自己定义每个 Pass 需要使用的 Shader 代码.

3.4.3 被抛弃的角落:固定函数着色器

01.不支持可编程管线着色器,需要使用固定函数着色器(Fixed Function Shader)来完成渲染.

示例代码:

Shader "Tutorial/Basic"
{
    Properties
    {
        _Color("Main Color",Color) = {1,0.5,0.5,1}
    }
    
    SubShader
    {
        Pass
        {
            Material{ Diffuse [_Color] }
        }
        Lighting On
    }
}

对于固定函数着色器的代码被定义在 Pass 语义块中,这些代码相当于 Pass 中的一些渲染设置.

对于固定函数着色器来说,我们需要完全使用 ShaderLab 的语法 (即使用 ShaderLab 的渲染设置命令) 来编写,而非使用 CG/HLSL.

3.4.4 选择哪种 Unity Shader 形式

01.除非必须要使用固定函数着色器,例如:需要在非常陈旧的机器上运行的游戏.否则请使用可编程管线的着色器,即表面着色器或顶点/片元着色器.

02.如果要和各种光源打交道,可能更喜欢使用表面着色器,但是要小心它在移动平台的性能表现.

03.如果你需要使用的光照数目非常少,例如只有一个平行光,,那么使用顶点/片元着色器是一个更好的选择.

04.最重要的是,如果有很多自定义的渲染效果,请选择顶点/片元着色器.

3.6答疑解惑

3.6.1 Unity Shader != 真正的 Shader

Unity Shader 实际上指的就是一个 ShaderLab 文件 ---- 硬盘上以 .shader 作为文件后缀的一种文件.

在 Unity Shader (或者说是 ShaderLab 文件)里, 我们可以做的事情远多于一个传统意义上的 Shader

01.在传统的 Shader 中,我们仅可以编写特定类型的 Shader,例如:顶点着色器,片元着色器等.而在 Unity Shader 中,我们可以在同一个文件里同时包含需要的顶点着色器和片元着色器代码.

02.在传统的 Shader 中,我们无法设置一些渲染设置.例如是否开启混合,深度测试等,这些是开发者在另外的代码中自行设置的.而在 Unity Shader 中,我们通过一行特定的指令就可以完成这些设置.

03.在传统的 Shader 中,我们需要编写冗长的的代码来设置着色器的输入和输出,要小心的处理这些输入输出的位置对应关系等.而在 Unity Shader 中,我们只需要在特定语句块中声明一些属性,就可以依靠材质来方便的改变这些属性.而且对于模型自带的数据(如顶点位置,纹理坐标,法线等),Unity Shader 也提供了直接访问的方法,不需要开发者自行编码来传给着色器.

Unity Shader 缺点:

<1>由于高度封装性,可以编写的 Shader 类型和语法被限制.

<2>对一些类型的 Shader,曲面细分着色器(Tessellation Shader), 几何着色器(Geometry Shader)等,Unity 的支持相对较差.

 

3.6.2 Unity Shader 和 CG/HLSL 之间的关系

01.CG/HLSL 代码是区别于 ShaderLab 的另外一个世界

通常,CG 的代码片段是位于 Pass 语义快内部的,示例如下:

Pass
{
    // Pass 的标签和状态设置
    
    CGPROGRAM
    // 编译指令,例如:
    #pragma vertex vert
    #pragma fragment frag
    
    // CG 代码

    ENDCG
    // 其他一些设置
}

02.本质上来讲,Unity Shader只有(只存在)两种形式:顶点/片元着色器.

 

第4章 学习 Shader 所需要的数学基础

计算机图形学之所以深奥难懂,很大原因是在于它是建立在虚拟世界上的数学模型.数学渗透到图形学的方方面面,当然也包括 Shader.在学习 Shader 的过程中,我们最常使用的就是矢量和矩阵(即数学的分支之一     线性代数)

 

4.1 背景:农场游戏

 

4.2 笛卡尔坐标系

01.在游戏制作中,我们使用数学绝大部分都是为了计算位置,距离和角度等变量.而这些计算大部分都是在笛卡儿坐标系(Cartesian Coordinate System)下进行的.

 

4.2.1 二维笛卡儿坐标系

01.一个二维笛卡儿坐标系包含了两个部分的信息:

<1>一个特殊的位置,即原点.它是整个坐标系的中心

<2>两条通过原点的相互垂直的矢量,即 X 轴 和 Y 轴.这些坐标轴也被称为是该坐标系的基矢量.

图片来源于转载

 

4.2.2 三维笛卡儿坐标系

01.需要定义3个坐标轴和一个原点.这 3 个坐标轴也被称为是该坐标系的基矢量(basis vector).通常情况下,这三个坐标轴之间是互相垂直的,且长度为一,这样的基矢量被称为标准正交基(orthonormal basis),但这并不是必须的.

02.在一些坐标系中坐标轴之间互相垂直但长度不为1,这样的基矢量被称为正交基(orthogonal basis).

正交可以理解成互相垂直的意思.在矩阵内容中,我们还会看到正交矩阵的概念.

02.两种不同种类的三维笛卡儿坐标系:左手坐标系(left - handed coordinate space) 和 右手坐标系(right - handed coordinate space)

4.2.3 左手坐标系和右手坐标系

01.通过旋转,可以说所有的二维笛卡儿坐标系都是等价的.

02.三维笛卡儿坐标系并不都是等价的.如果两个坐标系具有相同的旋向性(handedness),那么我们就可以通过旋转的方法来让它们的坐标轴指向重合.但是,如果它们具有不同的旋向性,那么就无法达到重合的目的.

03.我们可以利用我们的双手来判断一个坐标系的旋向性.使用左手,左手的食指和大拇指摆出一个"L"的手势,并且让食指向上,大拇指指向右,这时伸出中指,中指是向前的,这就得到了一个左手坐标系.大拇指,食指,中指分别对应了+x,+y,+z轴的方向.同样可以通过右手来得到一个右手坐标系.右手大拇指向左,食指向上,中指向前

04.除了轴向不同之外,左手坐标系和右手坐标系对于正向旋转的定义也不同.既物理中学到的左手法则(left - hand rule) 和 右手法则(right - hand rule).左手坐标系中,这个旋转正方向是由左手法则定义的,而在右手坐标系中则是由右手法则定义的.

05.左右手坐标系之间是可以进行相互转换的.最简单的方法就是把其中一个轴反转,并保持其他两个轴不变.

06.为了达到同样的视觉效果(把一个物体移动到同一个视觉位置上),左右手坐标系在 z 轴上的移动以及旋转方向是不同的.如果使用相同的数学运算(指均向 z 轴某方向移动或均朝旋转正方向旋转等),那么得到的视觉效果就是不一样的.

 

4.2.4 Unity 使用的坐标系

01.对于模型空间和世界空间,Unity 使用的是左手坐标系.(观察一下 Scene 视图的坐标轴的显示)

02.但对于观察空间来说,Unity使用的是右手坐标系.观察空间,通俗来讲就是以摄像机为原点的坐标系.在这个坐标系中,摄像机的前向是 z 轴的负方向,这在与模型空间和世界空间中的定义相反也就是说 z 轴坐标的减少意味着场景深度的增加(在 Unity 中,观察空间使用的是右手坐标系,摄像机的前向是 z 轴的负方向,z 轴越小,物体的深度越大,离摄像机越远)

 

4.3 点和矢量

01.点(point) 是 n 维空间(游戏中主要使用二维和三维空间) 中的一个位置,它没有大小,宽度这类的概念.

02.矢量(vector,也被称为向量) 的定义则复杂一些.矢量存在的意义更多是为了和标量(scalar)区分开来.通常来讲,矢量是指 n 维空间中一种包含了模(magnitude)方向(direction)的有向线段,例如速度,向南80km/h(向南指明了矢量的方向,80km/h指明了矢量的模).而标量只有模没有方向,例如:距离.200m

03.具体来讲

<1>.矢量的模指的是这个矢量的长度.一个矢量的长度可以是任意的非负数.

<2>.矢量的方向则描述了这个矢量在空间中的指向.

矢量的表示方法和点类似.我们可以使用 v = (x,y) 来表示二维矢量,用 v = (x,y,z) 来表示三维矢量,用 v = (x,y,z,w) 来表示四维矢量.

04.一个矢量通常由一个箭头来表示,我们有时会讲到一个矢量的头(head)和尾(tail).矢量的头指的是它的箭头所在的端点,而尾指的是另一个端点处.

05.通常,矢量被用于表示相对于某个点的偏移(displacement),也就是说它是一个相对量.只要矢量的模和方向保持不变,无论放在哪里都是同一个矢量.

4.3.1 点和矢量的区别

01.点和矢量建立联系:可以认为,任何一个点都可以表示成一个从原点出发的矢量.

02.矢量通常用于描述偏移量

03.点是一个没有大小之分的空间中的位置,而矢量是一个有模和方向但没有位置的量.

 

4.3.2 矢量运算

1.矢量和标量的乘法/除法

虽然我们不能把矢量和标量进行相加/相减的运算,但是可以对他们进行乘法运算,结果会的得到一个不同长度且可能方向相反的新的矢量.

公式(只要把矢量的每个和标量相乘即可): kv = (kv.x,kv.y,kv.z)

类似的,一个矢量也可以被一个非零的标量除,这等于和这个标量的倒数相乘.

图片来自于转载

对于乘法来说,矢量和标量的位置可以互换.但对于除法,只能是矢量被标量除,而不能是标量被矢量除,这是没有意义的.

2.矢量的加法和减法

01.可以对两个矢量进行相加或相减,其结果是一个相同维度的新矢量.只需要把两个矢量的对应分量进行相加或相减即可.

图片来源于加载

注意:一个矢量不可以和一个标量相加相加或相减,或者是和不同维度的矢量进行运算.

矢量加法的三角形定则,减法类似

 

图片来源于转载​​​

在图形学中矢量通常用来描述位置的偏移(简称位移).

3.矢量的模

图片来源于转载

4.单位矢量

01.很多时候我们只关心矢量的方向而不是模.例如,在计算光照模型时,我们往往需要得到顶点的法线方向和光源方向,此时我们不关心这些矢量有多长,在这些情况下,我们就需要计算单位矢量(unit vector)

02.单位矢量指的是那些模为 1 的矢量.单位矢量也被称为被归一化的矢量(normalized vector).把任何非零矢量,把它转换成单位矢量的过程就被称为归一化(normalization).

03.对矢量进行归一化,可以用矢量的模除以该矢量来得到

图片来源于转载

04.零矢量(即矢量的每个分量值都为 0,如v = (0,0,0))是不可以被归一化的.因为做除法运算时分母不能为 0.

05.矢量的点积

矢量之间也可以进行乘法,但是和标量之间的乘法有很大不同.标量的乘法有两种最常用的种类:点积(dot product,也被称为内积,inner product) 和 叉积(cross product,也被称为外积,outer product)

图片来源于转载

矢量的点积满足交换率,即 a·b = b·a

点积的几何意义很重要,因为点积几乎应用到了图形学的各个方面.其中一个几何意义就是投影(projection)

图片来源于转载

点积的符号可以让我们知道两个矢量的方向关系.

点积具有一些很重要的性质.

<1> 性质一:点积可结合标量乘法

<2>性质二:点积可结合矢量加法和减法,和性质一类似

<3>性质三:一个矢量和本身进行点积的结果,是该矢量的模的平方.

图片来源于转载

两个矢量的点积可以表示为两个矢量的模相乘,再乘以他们之间夹角的余弦值.

图片来源于转载

6.矢量的叉乘

矢量叉积的结果仍是一个矢量,而非标量.

图片来源于转载

注意:叉积不满足交换律,实际上叉积是满足反交换律的.而且叉积也不满足结合律.

*************** * Page 51 - Page 53 * ********************* 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值