一些渲染流水线的图形学知识 与 UnityShader
编写实例。
笔记都是照着“Unity Shader入门精要”摘抄的,为以后想要复习实现方便。
渲染流水线流程
从虚拟的世界渲染到屏幕上的像素。
也就是:
1.从虚拟世界定义的各种几何图形——主要是三角形面,
2.转换到数字世界裁剪空间的片元——是一个二维平面光栅化后的每一个"像素",
但不是真正意义上的显示在屏幕的像素点,而是用于插值设置颜色的最小单位
3.最后在输出到屏幕的一个个像素上。
最主要的图形学内容是各种坐标变换。
缩放、平移、旋转。
涉及到很多矩阵内容,当然不是很难了。
学习如何编写Unity Shader
。
第一个UnityShader与应用方法
定义一个最简单的顶点/片元着色器。
其中顶点着色器是逐顶点调用,片元着色器是逐片元调用
新建一个Standard Surface Shader
(虽然说名字叫表面着色器,但是本身表面着色器也会被编译成顶点/片元着色器,所以本质是一样的)。
打开并修改代码为,这里实现的效果就是设定所有点和片元颜色为纯白色。
Shader "Custom/SimpleShader"
{
SubShader
{
PASS {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//这两句话告诉Unity 哪个函数包含了顶点着色器代码,或是片元着色器代码
//POSITION和SV_POSITION告诉Unity这个函数的输入和输出各是什么,从而自动注入
//POSITION就是模型的各个顶点,SV_POSITION就是模型在裁剪空间对应的顶点
float4 vert(float4 v : POSITION) : SV_POSITION{
return UnityObjectToClipPos (v);
//这句话同 return mul(UNITY_MATRIX_MVP,*);等效
}
//SV_Target即是指定输出颜色到帧缓存中,也就是所有的片元都取这个颜色
fixed4 frag() : SV_Target{
return fixed4(1.0, 1.0, 1.0, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
其中顶点着色器负责将顶点从模型空间一路转化到裁剪空间,这之后的工作(从裁剪到屏幕空间由Unity自动完成)。
片元着色器就是使得每一个像素都为纯白色。
着色器要赋给材质才能作用到物体上,因此新建一个材质Material
。
在材质的Inspector
面板里选择到编译好的Shader。
创建一个新的物体,这里用一个正方体演示,默认的材质能在光线的作用下产生明暗面。
将刚刚写的附着了纯白着色器的材质附着到物体上,就能看到明暗也不存在了,只是纯纯的白,就更___一样纯。
这里默认的材质可能是灰的,不给改,但是可以直接将材质托到场景物件列表Hierarchy
的物件上。
语义
SV_POSTION
、POSITION
、COLOR0
等等都称作“语义”。
用于让Shader明确从哪里读取数据并将数据输出到哪里。
其中SV_
开头的指系统数值语义(System-Value semantics)
,这些语义有着特殊的含义,其中的变量不可以随便赋值。
例如渲染引擎会把用SV_POSITION修饰的变量经过光栅化后显示在屏幕上。
SV_POSITION和POSITION在大多数平台上等价,但在某些平台如PS4必须使用SV_POSITION来修饰顶点着色器的输出
一些常用的语义
传递于处理更多数据
上面的例子只传递了模型的顶点坐标数据。
可以使用结构体一次传递多个数据,如模型各个顶点的坐标、法向量、切向量、各层纹理坐标等。
下面一个更复杂点的例子
Shader "Custom/SimpleShader2"{
Properties{
//声明一个Color类型
_Color ("Color Tint", Color) = (1.0, 1.0, 1.0, 1.0)
}
SubShader{
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//同名同类型
fixed4 _Color;
struct a2v{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f{
float4 pos : SV_POSITION;
fixed3 color : COLOR0;
};
v2f vert(a2v v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.color = v.normal;
return o;
}
fixed4 frag(v2f i) : SV_Target{
return fixed4(i.color * _Color.rgb, 1.0);
}
ENDCG
}
}
}
运行结果是这样的:
可以看到放在一个立方体中,有3个面是黑色,3个面个是红蓝绿。
这是因为在顶点着色器中,o.color = v.normal;
这句话的含义就是,将顶点的法向量(一个三维向量)直接设定为颜色值(rgb数组,本质也是一个三维向量)。
但是颜色值只能是[0, 1.0]的区间范围,
法向量的每个元素会在[-1.0, 1.0]之间。
因此直接赋值,显而易见只有三个向上的面(法向量各个元素为非负的)有有效的值,所有只有三个面有效颜色,另外的会转化为(0, 0, 0)
也就是黑色。
此外上述代码在片元着色器中,用i.color * _Color.rgb
,将每个顶点的颜色值
与在属性面板中手动设置的颜色值
做了乘积。
这里两个三维向量直接乘,得出另一个三维向量,应该是每个分量依次乘,而不是叉乘。
因为从结果上看,是一种过滤效果,图中只有本来是蓝色的那一面依然是蓝色,其他面均为黑色。
ShaderLab
中的封装的属性和可以在CG代码
中使用的能够互相转换的属性对应表如下
上面的颜色输出,也可以作为Debug时需要的数据可视化方案
再复杂一点的Shader编写
上面的Shader都很简单。再复杂一点可以利用Unity内置的许多文件中的函数和变量做更高级的库调用。
需要在CG代码中使用#include
引用。
DEBUG
使用帧调试器可以看到渲染本帧时所有的渲染事件event
。
可以用这个滑动条重放事件。