|
《Unity Shader入门精要》学习笔记(7)-Shader基础(1)
这个系列将会分享我在学习《Unity Shader入门精要》这本书的时候学习到的重点内容和一些心得体会,在书的基础上如果有能力的话会进行一定程度上的创新,以下是本系列的第七篇.这一篇中,我们会对Unity Shader进行更为详细的介绍.
时间:2021.11.22
在阅读这篇文章之前,读者最好已经阅读了前置的关于渲染管线和Shader基本介绍,以及关于数学部分介绍的文章,详细可以点击专栏阅读:
技美学习 - 知乎 (zhihu.com)
这篇文章对应的书的位置:
基础篇 第5章
1.软件版本与相关环境
在Unity Shader入门精要更新系列中,我使用的Unity版本是Unity2021.1.5f1c1版本,理论上大于等于Unity5.x版本应该暂时都是可以的,使用的编译器是Visual Studio 2017,操作系统为Windows系统.关于OpenGL还是DirectX图形接口带来的差异会在后文出现时进行说明.
2.一个简单的顶点/片元着色器
(1)在Unity中新建Shader
这里面我们选择新建一个Unlit Shader(关于如何创建Shader,参考前置文章《Unity Shader入门精要》阅读笔记(2)-Shader初探 - 知乎 (zhihu.com)),取名为ShaderLearnChap5_1.双击打开Shader的源代码,可以看到基础结构如下:
Shader "Unlit/ShaderLearnChap5_1"
{
Properties
{
//这里面放置相关的属性
}
//针对显卡1的SubShader
SubShader
{
//设置渲染状态与标签,如:
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
//设置渲染状态与标签
CGPROGRAM
//该代码片段的编译指令
#pragma vertex vert
#pragma fragment frag
//以下来写CG代码,直至ENDCG部分
//...
ENDCG
//其他设置写在这里
}
//其他需要Pass代码写在这里
}
//针对显卡2的SubShader
SubShader
{
//...
}
// 上述SubShader都失败后用于回调的Unity Shader
Fallback "VertexLit"
}
其中,最重要的部分是Pass语义块,因为我们绝大多数的代码是写在这里的.
(2)用实例来体验Shader
将刚才的Shader中的代码替换为下述的代码:
Shader "ShaderLearnChap5_1"
{
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
}
}
}
将这个Shader赋予一个材质TestMaterial1,将Material赋予一个物体,这里用一个Plane来表示,处理结果如下:
关掉SkyBox以更好地看到Shader效果
有的时候SkyBox会对我们看到的结果造成干扰,可以用如下的方式关掉:
(a)在Unity的菜单中,选择Window -> Rendering -> Lighting
(b)将SkyBox这一栏的材质设置为空
此时关闭了天空盒子,呈现的效果如下:
(3)代码介绍
重新进入到我们的Shader,可能会发现提示行尾不一致等信息,点击确定之后发现代码被升级了,不用担心,我们将升级后的代码分析一下:
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' //Unity的提示信息
//这是因为在unity5.6以上版本中,shader中的UNITY_MATRIX_MVP将会被UnityObjectToClipPos替代,参考https://blog.csdn.net/weixin_30631587/article/details/99771586
Shader <span class="s">&#34;ShaderLearnChap5_1&#34; //这句话定义了Shader的名字
{
//这里没有定义Properties语句块,事实上这不是必须定义的
SubShader{
//SubShader中没有进行任何自定义的渲染设置和标签设置
Pass {
//Pass中没有进行任何自定义的渲染设置和标签设置
CGPROGRAM
//由CGPROGRAM和ENDCG包围的是CG代码片段,很重要
#pragma vertex vert
#pragma fragment frag //见下方注释1
float4 vert(float4 v : POSITION) : SV_POSITION {
return UnityObjectToClipPos(v);
}//见下方注释2
fixed4 frag() : SV_Target {
return fixed4(1.0, 1.0, 1.0, 1.0);
}//见下方注释3
ENDCG
}
}
}
(a)注释1:pragma
#pragma vertex vert
#pragma fragment frag
//这两句告诉Unity,哪个函数包含了顶点着色器的代码,哪个函数包含了片元着色器的代码。更通用的编译指令表示如下:
#pragma vertex name1
#pragma fragment name2
虽然我们可以自己指定这两个name,但我们通常使用vert和frag,因为他们比较直观.
(b)注释2:vert函数
float4 vert(float4 v : POSITION) : SV_POSITION
{
return UnityObjectToClipPos(v);
}
这就是本例中的顶点着色器代码,它是逐顶点进行的.vert函数的输入v包含了这个顶点的位置,这是通过POSITION 语义指定的。它的返回值是一个float4类型的变量,它是该顶点在裁剪空间中的位置(通过函数名可以很明显地看到),POSITION 和SV_POSITION 都是Cg/HLSL中的语义 (semantics) ,它们是不可省略的,这些语义将告诉系统用户需要哪些输入值,以及用户的输出是什么。
在这里,POSITION告诉Unity把模型的顶点坐标填充到输入参数v中;
SV_POSITION告诉Unity顶点着色器的输出是裁剪空间中的顶点坐标.
关于各种语义的介绍将在后文中进行总结.
(c)注释3:frag函数
fixed4 frag() : SV_Target
{
return fixed4(1.0, 1.0, 1.0, 1.0);
}
类比vert函数,我们可以知道frag函数没有任何输入。它的输出是一个fixed4类型的变量,并且使用了SV_Target 语义进行限定。该语义告诉Unity把用户的输出颜色存储到一个渲染目标(render target)中,这里将输出到默认的帧缓存中。
片元着色器中的代码很简单,返回了一个表示白色的fixed4类型的变量。片元着色器输出的颜色的每个分量范围在[0, 1],其中(0, 0, 0)表示黑色,而(1, 1, 1)表示白色。
(4)模型数据从哪里来?
在上述例子中,我们在顶点着色器中通过POSITION语义得到了模型的顶点位置.以下的代码会告诉我们当要获取更多信息的时候要如何做:
举个例子:得到模型上每个顶点的纹理坐标和法线方向.
在这个例子里,输入的语义不再是一个简单的顶点位置,而应该是一个结构体,新增结构体及修改顶点着色器之后的相关代码如下:
//使用一个结构体来定义顶点着色器的输入
struct a2v
{
//POSITION语义告诉Unity,用模型空间的顶点坐标来填充vertex变量
float4 vertex:POSITION;
//MORMAL语义告诉Unity,用模型空间的法线方向填充normal变量
float3 normal:NORMAL;
//TEXCOORD0语义告诉Unity,用模型的第一套纹理坐标填充texcoord
float4 texcoord:TEXCOORD0;
};
float4 vert(a2v v) : SV_POSITION //注意,这里输入变量去掉了POSITION语义,而是改成了刚才定义的a2v
{
return UnityObjectToClipPos(v.vertex);//注意这里的v对应也要改为v.vertex
}
Unity支持的顶点着色器语义
POSITION,TANGENT,NORMAL,TEXCOORD0,TEXCOORD1,TEXCOORD2,TEXCOORD3,COLOR等.
新建结构体的格式要求
struct StructName
{
Type Name:Semantic;
Type Name:Semantic;
...
};
其中,语义部分Semantic不能够省略.
通过上面的程序,可以看到我们将结构体应用于顶点着色器.通过这种自定义结构体的方法,可以在顶点着色器中访问各种模型数据.
a2v的含义
虽然这个结构体的名字不是固定的,但为了方便理解,取名为a2v会方便记忆(application to vertex shader),也就是说,这个结构体a2v的意思是把数据从应用阶段传递到顶点着色器中.
语义是如何产生的?
这些语义由材质的Mesh Renderer组件所提供.每帧调Draw Call的时候,Mesh Renderer组件会把它负责渲染的模型数据发送给Unity Shader,而由于模型往往是由三角面片组成的,且面由点组成,点会有很多数据,比如法线,切线,位置,纹理坐标等,所以顶点着色器中自然也可以访问这些模型数据.
(5)顶点着色器与片元着色器之间的通信
实际应用中,往往会需要顶点着色器输出一些如模型法线,纹理坐标等信息到片元着色器,这就涉及到通信问题.为此.需要定义一个新的结构体.
Shader &#34;ShaderLearnChap5_1&#34;
{
SubShader{ |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|