要谈Unity shader我们首先要了解下整个的渲染流水线
其实我们现在写的unityshader并等于shader只是整个渲染过程的一部分
渲染可以概念性的分为3个部分
应用阶段,几何阶段,光栅化阶段
类似这个图
每个阶段又可以分出很多小的阶段
其中 应用阶段是CPU 几何阶段和光栅化阶段载体则是GPU
它主要作用有 1.将渲染需要的数据加载至显存中, 2.设置渲染状态, 3.以及调用draw call
有场景的数据,比如摄像机位置,视锥体,场景中包含哪些模型,哪些光源等等, 网格,纹理,顶点的所携带的信息(颜色,uv坐标,法线等);
所有渲染所需的数据都需要从硬盘( Hard Disk Drive, HDD )中加载到系统内存( Random Access Memory, RAM )中。然后,网格和纹理等数据又被加载到显卡上的存储空间一一显存( Video Random Access Memory, VRAM)中。这是因为,显卡对于显存的访问速度更快,而且大多数显卡对于RAM 没有直接的访问权利
什么是渲染状态呢? 一个通俗的解释就是,这些状态定义了场景中的网格是怎样被渲染的。
使用哪个顶点着色器(Vertex Shader) /片元着色器(Fragment Shader)、光源属性、材质等
使用哪个shader,光照的模式,材质球这些
比如一个cube 一个sphere两个都受光源一样,材质球一样渲染状态就一样的.
发起方就是CPU,接收方是CPU,实现两者之间的通信.
CPU作用:发送一个命令,这个命令仅仅指向一个显存中的被渲染的图元列表,告诉GPU需要渲染这个.不需要包含材质信息,在设置渲染状态已经做了,
GPU作用:接到命令后根据渲染状态和顶点数据进行计算,这个计算过程就是重要的GPU流水线
几何阶段和光栅化阶段就是在GPU流水线上完成的;
这里就只讲uniyshader包括的
顶点着色器,裁剪,屏幕映射
顶点着色器(完全可编程)
作用:把接收的顶点数据进行一系列操作传给下一阶段
在vfshader中主要这一部分
#pragma vertex vert //定义vert函数就是代表顶点着色器
struct vertexInput{
};
struct vertexOutput{
};
vertexOutput vert(vertexInput v){
vertexOutput o;
/*----
一系列顶点上的操作
顶点动画,顶点空间转换,顶点着色等
----*/
return o;
}
首先看第一个结构体
struct vertexInput{
float4 vertex:POSITION;
};
它的作用就是把之前应用阶段传过来的顶点数据存进去
我这里先只列举其中一个数据:vertex(顶点位置)
后面跟着的一个大写的POSITION,称为语义,作用就是告诉GPU把顶点的位置数据用这个参数接收
再看这个函数
vertexOutput vert(vertexInput v){
vertexOutput o;
o.pos=UNITYObjectToClipPos(v.vertex);
//o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
return o;
}
主要必须要有中间这句代码
o.pos=UNITYObjectToClipPos(v.vertex);
它的作用就是顶点坐标转换到齐次裁剪坐标系下,接着通常再由硬件做透视除法后,最终得到归一化的设备坐标(Normalized Device Coordinates , NDC ) 然后存入第二个结构体
图2.8 给出的坐标范围是OpenGL 同时也是Unity 使用的NDC, 它的z 分量范围在[-1, 1 ]之间,而在DirectX 中, NDC 的z 分量范围是[0, 1];
最后看第二个结构体
struct vertexInput{
float4 pos:SV_POSITION;
};
它的作用就是把顶点着色器一系列操作后的数据存起来,经过光栅化阶段提供给之后的片元着色器
裁剪 (可配置)
这一阶段的目的是将那些不在摄像机视野内的顶点裁剪掉,并剔除某些三角图元的面片。
以及通过指令控制裁剪三角图元的正面还是背面
Cull Off|Back(default)|Front
屏幕映射(不可配置和编程)
负责把每个图元的坐标转换到屏幕坐标系中,屏幕坐标系是一个二维坐标系,它和我们用于显示画面的分辨率有很大关系
注意:屏幕映射不会对输入的z坐标做任何处理,它会保留并传到下一阶段
另外一个需要注意的点:
屏幕坐标系在OpenGL 和DirectX 之间的差异问题。OpenGL把屏幕的左下角当成最小的窗口坐标值,而DirectX 则定义了屏幕的左上角为最小的窗口坐标值,所有如果发现得到的图像是倒转的,那么很有可能就是这个原因造成的.
接下来就会进入光栅化阶段.
光栅化阶段有两个最重要的目标: 计算每个图元覆盖了哪些像素,以及为这些像素计算它们的颜色
三角形设置,三角形遍历,片元着色器,逐片元操作
#pragma fragment frag //定义frag函数就是代表片元着色器
fixed4 frag(vertexOutput i):SV_Target{
//fixed4 finalColor=tex2D(_MainTex,i.uv);
return fixed4(1,1,1,1);
}
片元着色器的输入是上一个阶段对顶点信息插值得到的结果, 更具体来说,是根据那些从顶点着色器中输出的数据插值得到的。而它的输出是一个或者多个颜色值。技术之一就是纹理采样。为了在片元着色器中进行纹理采样,我们通常会在顶点着色器阶段输出每个顶点对应的纹理坐标, 然后经过光栅化阶段对三角网格的3 个顶点对应的纹理坐标进行插值后,就可以得到其覆盖的片元的纹理坐标了。(只能影响单个片元)