Shader笔记_001编写最简单的顶点/片元着色器
< 返回列表时间: 2020-05-14来源:OSCHINA
今天正式开始学习shader, 准确的说是学习如何编写顶点/片元着色器
一、顶点/片元着色器的基本结构
shader包含subshader、shader、property、fallback等语句块  PS:每个UnityShader文件可以包含多个SubShader语义块,但至少要有一个。 当Unity需要加载这个UnityShader时, Unity会扫描所有的SubShader语义块, 然后选择一个能够在目标平台上运行的SubShader。 如果都不支持的话,Unity 就会使用FallBack语义指定的UnityShader。 同样子着色器中的Pass也是可以有很多个, 当执行子着色器内的渲染方案时,所有的Pass会被依次执行 Shader "MyShader"{ Properties{//属性} SubShader{//针对显卡A的SubShader Pass{ //设置渲染状态和标签 //开始CG代码片段 CGPROGRAM //该代码片段编译指令,例如 #pragma vertex vert #pragma fragment frag //CG代码 ENDCG //其他设置 } //其他需要的Pass } SubShader{//针对显卡B的SubShader} //上面的subshader全都失败之后用于回调的unity shader Fallback "VertexLit" }
其中最重要的是Pass语义块,绝大部分代码都是在Pass里实现的
EXAMPLE: Shader "Customer/SimpleShader"{ SubShader{ Pass{ CGPROGRAM #pragma vertex vert #pragma fragment frag float4 vert(float4 v :POSITION) :SV_POSITION{ return mul(UNITY_MATRIX_MVP,v) } fixed4 frag() : SV_Target{ return fixed4(1.0,1.0,1.0,1.0) } ENDCG } } }
上面的代码里没有用到Properties语义
Properties语义并不是必须的 可以不声明任何材质属性
subshader里没有进行标签设置,所以将使用默认的渲染和标签设置
在SubShader语义块中,我们定义了一个Pass, 这个pass里也没有设置任何自定义的渲染和标签
接着就是CGPROGRAM和ENDGC所包围的CG代码片段
#pragma vertex vert
#pragma fragment frag
这两个指令将告诉UNITY哪个函数包含了顶点着色器的代码
哪个包含了片元着色器的代码
float4介绍 参考 https://www.cnblogs.com/jiahuafu/p/6136871.html
GPU是以四维向量为基本单位来计算的。4个浮点数所组成的float4向量是GPU内置的最基本类型。使用GPU对两个float4向量进行计算,与CPU对两个整数或两个浮点数进行计算一样简单,都是只需要一个指令就可以完成。
HLSH的基本数据类型定义了float、int和bool等非向量类型,但是它们实际上都会被Complier转换成float4的向量, 只要把float4向量的其中3个数值忽略,就可以把float4类型作为标量使用 。
使用贴图坐标时,只需要二维向量,HLSL定义了float2类型作为二维向量使用。
Shader经常会用到矩阵,HLSL有一个内置类型float4x4,它可以用来表示一个4*4矩阵。float4x4并不是GPU的内置类型,float4x4实际上是由4个float4所组成的数组。其他的还有float3x3、float2x2,分表代表3*3矩阵、2*2矩阵。
Shader也可以声明数组,4*4矩阵实际上就是一个float4 m[4]的数组。注意,Shader中的所有的变量都使用寄存器,没有其他内存空间可以使用,所以越大的数组会占用越多的寄存器,甚至会超出寄存器的数量限制。
在使用float4向量中的个别数值时,可以用xyzw或rgba,都可以用来表示四维向量中的数值。但不能把它们混用,例如不能用xyba,把它视为颜色时就用rgba,否则就是用xyzw,不能把这二者混合使用。

rgba : 前三个值(红绿蓝)的范围为0到255之间的整数或者0%到100%之间的百分数。这些值描述了红绿蓝三原色在预期色彩中的量。
第四个值,alpha值,制订了色彩的透明度/不透明度,它的范围为0.0到1.0之间,0.5为半透明。
rgba(255,255,255,0)则表示完全透明的白色;
rgba(0,0,0,1)则表示完全不透明的黑色;
rgba(0,0,0,0)则表示完全不透明的白色,也即是无色;
二、顶点着色器结构
具体看一下vert函数的定义
float4 vert(float4 v: POSITION):SV_POSITION{
return mul(UNITY_MATRIX_MVP,v);
}
这个就是本例所使用的顶点着色器的代码,他是逐顶点执行的。mul(UNITY_MATRIX_MVP,v)的意思是把顶点坐标从模型空间转换为裁剪空间
vert函数输入的v包含顶点的位置 这个是通过POSITION语义指定的
他的返回值是一个float4类型的变量
POSITION/SV_POSITION都是CG/HLSL里的语义,不可省略
这些语义将要告诉系统用户需要哪些输入值以及用户输出的是什么 。 简单地说POSITION语意用于顶点着色器,用来指定这些个位置坐标值,是在变换前的顶点的object space坐标。SV_POSITION语意则用于像素着色器,用来标识经过顶点着色器变换之后的顶点坐标
本例中POSITION 将告诉UNITY 把模型的顶点坐标填充到输入参数v中
SV_POSITION将告诉UNITY顶点着色器输出的是裁剪空间中的顶点坐标
三、片元着色器结构
现在看一下frag函数
fixed4 frag() :SV_Target{
return fixed4(1.0,1.0,1.0,1.0);
}
上面的代码输出的上一个fixed4类型的变量,并且使用了SV_Target语义进行限定,SV_Target也是一个系统语义,它告诉渲染器,把用户输出的颜色存储到一个渲染目标里,这里将输出到默认的帧缓存中,片元着色器返回的fixed4类型的变量,片元着色器输出的颜色在[0,1]中,其中(0,0,0)是黑色,(1,1,1)是白色.
PS:FIXED类型数据 参考 https://www.cnblogs.com/jiahuafu/p/6237859.html
在处理图形运算,特别是3D图形生成运算时,往往要定义一个Fixed数据类型,我称它为定点数,定点数其时就是一个整形数据类型,他的作用就是把所有数 进行转换,从而得到相应类型的整型表达,然后使用定点数进行整行运算,取到最终值并将其转换回实际的基本数据类型。因此它是通过避免大量的浮点运算来加快 图形处理的一个方式。
Fixed类型说了一堆,究竟来做什么的?
比如上例中,Y轴每次都要偏移0.4,而这个数是个浮点,严重影响了运算速度。比如,我们后台有一个数,用来计量Y轴本次的坐标,就叫做变量YY吧。X每 次都加1,也就是XX++,Y每次加0.4,也就是YY+=0.4。为了提高速度,我们将YY升级到Fixed类型,YY每次加Fixed的0.4,也就 是0.4*65536=26214,然后再四舍五入到整数类型,即YY+=26214,Y=(YY+32768)>>16。这样,就得到了每 次的整数Y,并且都是整数的加减和位运算,速度非常快。
SV_Target介绍 参考 https://zhuanlan.zhihu.com/p/113237579
SV_Target SV_POSITION 这两个都是 语义绑定 (semantics binding)。什么是语义绑定呢?语义绑定可以理解为关键字,我们都知道图形渲染是按照一步一步地进行的,又叫做渲染管线。那么为什么是要一步一步地进行呢?因为GPU的架构与CPU非常不同,cpu更像是人的大脑,可以同时思考不同的问题,而GPU则相当于计算机,通过有特定的输入,通过计算得到最终的输出。GPU没有像CPU一样的堆栈来存取变量与值,所以通过语义绑定将一个计算好的值放在一个物理存储位置,再用的话就利用语义绑定去特定物理位置取出,这也是GPU不能像CPU一样工作的原因之一吧。
SV_前缀的变量代表system value的意思,在DX10+的语义绑定中被使用代表特殊的意义,SV_POSITION在用法上和POSITION是一样的,区别是 SV_POSTION一旦被作为vertex函数的输出语义,那么这个最终的顶点位置就被固定了,不得改变。

四、效果
把写好的shader文件赋值给material文件,然后新建一个3DObject
修改mesh render里面的material为我们自己创建的
效果如下

可以尝试修改shader,参考 https://zhuanlan.zhihu.com/p/85594617 Shader "Custom/SimpleShader02" { SubShader { Pass { Blend SrcAlpha OneMinusSrcAlpha CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; float2 uv : TEXCOORD1; }; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } float4 frag(v2f i) : SV_Target { return float4(i.uv.r, i.uv.g,0,1); } ENDCG } }

先在两个结构体里面加入一些东西
struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; float2 uv : TEXCOORD1; };
在vertex shader做的事情:将拿到的uv直接通过v2f传给frag函数
v2f vert(appdata v) { v2f o; o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); o.uv = v.uv return o; }
接下来把uv的值填入颜色的返回值中,得到下图
float4 frag(v2f i) : SV_Target { float4 color = float4(i.uv.r, i.uv.g, 0, 1); return color; }

PS:TEXCOORD0 TEXCOORD1里的uv是什么?
纹理坐标,也就是俗称的UV,是顶点数据的一部分。
在建模软件比如3D Max中,建模时有个过程俗称展开UV,其实就是指定模型每个顶点的UV。因为纹理可以有不止一张,所以UV也可以有不止一组。
建模软件导出的模型数据自然也包含顶点的UV数据,在引擎中渲染模型之前,创建顶点缓冲时,包含了UV信息的顶点数组被复制到顶点缓冲里用于接下来的渲染。
在渲染过程中,vs阶段的输入就是(当前)顶点数据,包括各组UV。当然你可以选择性使用,比如说你只需要一组UV,就可以只选择绑定一个TEXCOORD0
texcoord0和texcoord1分别表示两层UV,有时候我们模型上的贴图需要多个图片一起贴在一处,那么贴两层就会有两层UV。


参考<<SHADER入门精要>>第五章
参考 https://zhuanlan.zhihu.com/p/113237579 SV_Target介绍
参考 https://zhuanlan.zhihu.com/p/85594617 shader简单着色编写
参考 https://docs.unity3d.com/Manual/SL-VertexProgramInputs.html 官方实例及API介绍
参考 https://www.zhihu.com/question/353273260/answer/876813032 TEXCOORD0 的uv是什么
参考 https://www.cnblogs.com/leeplogs/p/7339097.html TEXCOORD0
参考 https://www.cnblogs.com/jiahuafu/p/6237859.html FIXED类型的数据
热门排行