第一个shader
1.前言
什么是shader?着色器(Shader)是用来实现图像渲染的用来替代固定渲染管线的可编辑程序。在具体学习shader之前,我们有必要了解渲染流水线的概念.
很多计算机图形学的书籍都把渲染管线分为三个阶段:应用程序阶段、几何阶段、光栅化阶段。
2.应用阶段(CPU处理)
想象一下一个模型从场景渲染到屏幕的过程,这其中由CPU和GPU之间相互共同合作完成的,在第一阶段,由CPU开始的,在这一阶段中开发都有3个主要的任务:首先,需要准备好场景数据(摄相机位置、视锥体、模型和光源等)接着,还需要做粗粒度的剔除工作最后,需要设置好每个模型的渲染状态(使用的材质、使用的纹理、使用的Shader等).
在该阶段的末端将产生几何体数据,包括顶点坐标、法向量、纹理坐标、纹理等,通过数据总线传送到图形硬件以供渲染(时间瓶颈),进行几何阶段。
在所有数据准备完成之后,CPU就会调用一个渲染命令来告诉GPU进行渲染,这个命令就是我们熟悉的DrawCall.
3.几何阶段(GPU)
数据由顶点转换到屏幕图像的一系列过程
1)顶点着色器
顶点着色器的处理单位是顶点,每一个顶点都会调用一次顶点着色器,需要注意的是,我们无法在顶点着色器中创建或销毁任何顶点,也无法得到顶点和顶点之间的关系.
顶点着色器主要工作可以概括为“变换三维顶点坐标”和“光照计算”。我们出入到计算机中的是一系列三维坐标点,但我们最终看到的从视点出发观察到的特定点。我们电脑显示器是二维的,GPU所需要做的,就是把三维顶点数据经过转换绘制到二维屏幕上,并让二维画面看起来有3D效果。顶点的变换涉及一系列的坐标系统,顶点变换过程,就是通过各个变化矩阵,把一个坐标系统下的顶点信息,变化到另外一个坐标系统上,从而实现3D的顶点数据最终可以在2D屏幕上进行显示。
一个最基本的顶点着色器的功能是:把顶点从模型空间转换到齐次裁剪空间 , 接着由硬件进行透视除法,得到归一化的设备坐标(Normalized Device Coordinates NDC).
OpenGL 的z值是[-1.1] DirectX的z值是[0,1]
2)裁剪(Clipping)
裁剪掉不在摄像机视野内的区域图元与摄像机有三种关系:完全在视野内,部分在视野内,完全不在视野。完全在视野内的交由传递给下一个流水阶段,完全不在视野的不处理。部分在视野内,要有Clipping处理。这一步不可编程;
3)屏幕映射(Screen Mapping)
把每一个图元的坐标转换到屏幕坐标空间.需要注意的是OpenGL和DirectX的区别,OpenGl的(0,0)点在左下角,DirectX的坐标在左上角.
4.光栅化(GPU阶段)
光栅化就是把顶点数据转换为片元的过程。片元中的每一个元素对应于帧缓冲区中的一个像素。
可以叫做栅格化或者像素化。就是把矢量图形转化成像素点儿的过程。我们屏幕上显示的画面都是由像素组成,而三维物体都是点线面构成的。要让点线面,变成能在屏幕上显示的像素,需要栅格化这个过程。就是从矢量的点线面的描述,变成像素的描述。
- 三角形设置
到目前为止我们得到了一堆顶点的数据,这一步就是根据这些顶点的原始连接关系还原出网格结构。网格由顶点和索引组成,这个阶段就是根据索引将顶点链接到一起,组成线、面单元,然后进行剪裁,如果一个三角形超出屏幕以外,例如两个顶点在屏幕内,一个顶点在屏幕外,这时我们在屏幕上看到的就是一个四边形,然后把这个四边形切成两个小的三角形。
2)三角形遍历
在这个阶段,将检查每个被三角形覆盖了中心(或某样品)的像素,并为和三角形重叠的像素部分生成片段。查找哪些样品或像素在三角形内部这一过程叫做三角形遍历或扫描转换。每个三角形片段的属性都被使用三角形的三个顶点间的插值数据生成。这些属性包括片段的深度和来自几何形状阶段的着色数据。
这一步的输出就是得到一个片元序列.需要注意的是,一个片元不是真正意义上的像素,而是包含了很多状态的集合,这些状态用于计算每个像素的最终颜色.包括屏幕坐标,深度信息,法线,纹理等.
3)片元着色器
片元着色器的输入数据是上一个阶段对顶点着色器的顶点信息插值得到的结果,在这一阶段,可以完成很多重要的渲染技术.例如纹理采样,逐像素的光照等.
4)逐片元操作
在DireectX中,这一阶段也叫输出合并阶段(OutPut-Merger).这一阶段主要要几个任务:
1.决定片元的可见性,例如深度测试,模板测试等.
2.如果通过测试,就需要把这个片元的颜色值和颜色缓冲区中的颜色进行混合.
注意的是,测试顺序不是固定的,比如说,很多GPU会尽可能的把深度测试放在片元着色器之前进行,以节省大量的片元计算(Early-Z)
当模型的图元经过了上面的层层计算和测试后,就会显示到我们的屏幕上.但是,为了我们看到正在光栅化的图元,GPU会使用双重缓存的策略,这意味着,对场景的渲染是在幕后发生的.
5.坐标空间和坐标转换
深色区域就是顶点坐标空间的变换流程.
1)从object space到world space
object space有两层核心含义,第一,object space中的坐标值就是模型文件中的顶点值,这些值是在建立模型时得到的,例如一个.max文件,里面包含的数据就是object space的坐标。第二,object space的坐标与其他物体没有任何参照关系,这是object space和world space区分的关键。world space坐标的实际意义就有有一个坐标原点,物体跟坐标原点相比较才能知道自己的确切位置。例如在unity中,我们将一个模型导入到场景中以后,它的transform就是世界坐标。
将顶点坐标转从模型空间转换到世界空间的过程,叫做模型变换(model transform).
2)从world space到eye space (视角空间(view space),或者叫摄像机空间(Camera space),观察空间)
所谓eye space,就是以摄像机为原点,由视线方向、视角和远近平面,共同组成的一个梯形体,如下图,称之为视锥(viewing frustum)。近平面,是梯形体较小的矩形面,也是靠近摄像机的平面,远平面就是梯形体较大的矩形,作为投影平面。在这个梯形体的内的数据是可见的,超出的部分会被视点去除,也叫视锥剪裁。
将顶点坐标转从世界空间转换到视角空间的过程,叫做观察变换(view transform).
3) 从eye space到project and clip space (裁剪空间(clip space),也叫做齐次裁剪空间)
eye space坐标转换到project and clip space坐标的过程其实就是一个投影、剪裁、映射的过程。因为在不规则的视锥体内剪裁是一件非常困难的事,所以前人们将剪裁安排到一个单位立方体中进行,这个立方体被称为规范立方体(CVV),CVV的近平面(对应视锥体的近平面)的x、y坐标对应屏幕像素坐标(左下角0、0),z代表画面像素深度。所以这个转换过程事实上由三步组成:
(1)用透视变换矩阵把顶点从视锥体变换到CVV中;
(2)在CVV内进行剪裁;
(3)屏幕映射:将经过前两步得到的坐标映射到屏幕坐标系上。
将顶点坐标转从视角空间转换到裁剪空间的过程,叫做投影变换(projection transform),注意,这里并不会进行真正的投影,而是为投影做准备,真正的投影发生在后面的齐次除法过程中,也就是从裁剪空间到屏幕空间的过程.
6.矩阵基本知识
1)矩阵乘法
一个R * N的矩阵A和一个N* C的矩阵B相乘,他们的结果是一个R*C的矩阵,第一个矩阵的列数必须和第二个矩阵的行数相同.
矩阵乘法不满足交换律 A*B!=B*A
矩阵乘法满足结合律 A*B*C=A*(B*C)
行和列相等的矩阵叫方阵.
一个矩阵和它的逆矩阵相乘,结果是一个单位矩阵.
矩阵可以表示一个变化,而逆矩阵可以还原这个变换
Mul(M,V)==mul(V,tranpose(M));//左乘一个矩阵,等于右乘这个矩阵的转置矩阵
https://docs.unity3d.com/Manual/UpgradeGuide54.html
7.Shader的结构
Shader按管线分类一般分为固定渲染管线与可编程渲染管线
(1)固定渲染管线 ——这是标准的几何&光照(Transforming&Lighting)管线,功能是固定的,它控制着世界、视、投影变换及固定光照控 制和纹理混合。T&L管线可以被渲染状态控制,矩阵,光照和采制参数。功能比较有限。基本所有的显卡都能正常运行。
(2)可编程渲染管线——对渲染管线中的顶点运算和像素运算分别进行编程处理,而无须象固定渲染管线那样套用一些固定函数,取代设置参数来控制管线。
unity3d的三种Shader
(1)Fixed function shader 属于固定渲染管线 Shader, 基本用于高级Shader在老显卡无法显示时的Fallback(之后有详细介绍)。
(2)Vertex and Fragment Shader 最强大的Shader类型,属于可编程渲染管线. 使用的是CG/HLSL语法。
(3)Surface Shader Unity3d推崇的Shader类型,使用Unity预制的光照模型来进行光照运算。使用的也是CG/HLSL语法。
standard surface shader包含标准光照模型的表面着色器模板
unlit shader不包含光照但包含雾效的基本顶点/片元着色器
image effect shader实现各种屏幕后处理效果基本模板
compute shader利用gpu的并行性来进行一些与常规渲染无关的计算
Pass,FallBack
8.Properties变量定义
https://docs.unity3d.com/Manual/SL-Properties.html
需要注意的是,尽量使用默认的名字,例如MainTex,Color,_Alpha等,使用默认的变量,就可以在代码里使用默认的变量赋值给shader例如sharedMaterial.mainTexture = texture;
变量例子:
_Float("Float",Float) = 1.5
_Range("Range",Range(0.0,10.0)) = 3.0
_Color ("Color", Color) = (1,1,1,1)
_Vector("Vector",Vector) = (2,3,6,1)
_2D("2D",2D) = ""{}
_Cube("Cube",Cube) = "while"{}
_3D("3D",3D) = "black"{}
9.语义
从应用阶段传递模型数据到顶点着色器时unity支持的常用语义:
float4 vertex : POSITION; //顶点坐标
float4 tangent : TANGENT; // tangent,三角函数的一种,缩写为tan我们很熟悉了,他的值是mesh到表面法线的正切值
float3 normal : NORMAL; //表面法向量,以对象的坐标系标准化至单位长度
float4 texcoord : TEXCOORD0;//纹理坐标系的第0个集合
float4 texcoord1 : TEXCOORD1; //纹理坐标系的第1个集合
fixed4 color : COLOR;//颜色,通常为常数
Unity内建的预定义输入结构体:
只要引用UnityCg.cginc头文件(目录Unity > Editor > Data > CGIncludes下)就可以使用预先设定好的结构体直接使用,他们分别有appdata_base appdata_tan和appdata_full:
struct appdata_full {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 texcoord1 : TEXCOORD1;
float4 texcoord2 : TEXCOORD2;
float4 texcoord3 : TEXCOORD3;
#if defined(SHADER_API_XBOX360)
half4 texcoord4 : TEXCOORD4;
half4 texcoord5 : TEXCOORD5;
#endif
fixed4 color : COLOR;
};
文章大量使用了网络资源,例如candyCat的书<Unity shader 入门精要>,推荐新入门的人买来使用,有源码项目可以学习