【UnityShader入门精要学习笔记】第三章(2)Unity Shader的形式,章节答疑
本系列为作者学习UnityShader入门精要而作的笔记,内容将包括:
- 书本中句子照抄 + 个人批注
- 项目源码
- 一堆新手会犯的错误
- 潜在的太监断更,有始无终
总之适用于同样开始学习Shader的同学们进行有取舍的参考。
文章目录
(该系列笔记中大多数都会复习前文的知识,特别是前文知识非常重要的时候,这是为了巩固记忆,诸位可以直接通过目录跳转)
Unity Shader的形式
尽管Unity Shader可以做的事情非常多(例如设置渲染状态等),但最重要的任务还是指定各种着色器所需的代码。这些着色器代码可以写在SubShader语句块中(表面着色器),也可以写在Pass语句块中(顶点/片元着色器和固定函数着色器)
表面着色器
表面着色器(Surface Shader) 是Unity自己创作的一种着色器代码类型,它的本质和顶点/片元着色器(VF) 是一样的。我们定义了一个表面着色器,Unity做的工作仍然是将其转换为顶点/片元着色器再处理。因此表面着色器可以看作Unity对顶点/片元着色器的更高一层抽象。使用表面着色器,Unity帮我们处理了很多光照细节,我们就不必要去操心哪些烦人的事情。
一个简单的表面着色器示例如下:
Shader "Custom/Simple Surface Shader"{
SubShader{
Tags{ "RenderType" = "Opaque"}
// 以CGPROGRAM开头代表下列代码使用了CG编写
CGPROGRAM
// #pragma为预处理指令,声明surface表面着色器将使用函数surf作为表面函数,Lambetr作为光照函数(其中surf是自定义的,Lambert是Unity自带的光照函数)
#pragma surface surf Lambetr
// 定义结构体Input
struct Input{
float4 color: COLOR;
};
//定义Input类型的形参IN,以及SurfaceOutput类型的形参o,
// 其中inout是一个关键字
void surf (Input IN, inout SurfaceOutput o){
o.Albedo = 1;
}
// 结束CG
ENDCG
}
Fallback "Diffuse"
}
上述表面着色器定义在了SubShader语句块 (而非Pass语句块中)中的CGPROGRAM和ENDCG之间。当我们使用表面着色器,就不需要关心使用多少个Pass,这些Unity会帮我们处理好,我们要做的只是定义好使用的表面函数和光照函数即可。
CGPROGRAM和ENDCG中的代码是使用CG/HLSL编写的。语法上和CG/HLSL的语法基本一致。
拓展,CG语法中的in,out,inout关键字
既然上述代码中使用到了inout,我们可以简单讲一下这个关键字。其实类比C#中的in,out关键字以及C++的指针就方便理解了。
void surf (Input IN, inout SurfaceOutput o){
o.Albedo = 1;
}
我们定义了一个函数,这个函数有两个形参,分别是IN和o。其中IN我们没有指定关键字(限定符),而o指定了inout关键字。在CG/HLSL/GLSL中,函数形参可以通过关键字来定义:
限定符 | 说明 |
---|---|
in | 如果不使用限定符时,默认形参的限定符就是in ,传入函数的形参实际上是实参的复制值。in代表了对实参可读不可写(形参由实参的复制值得到,且修改形参不影响实参) |
out | out代表了对实参可写不可读(相当于指针,out修饰的形参指向这个实参本身,修改时会影响实参的值) |
inout | 对实参可读且可写,传入形参的是实参的复制值,但是函数结束后会根据这个值对应地修改实参 |
顶点/片元着色器
在Unity中我们可以使用CG/HLSL来编写顶点/片元着色器(Vertex/Fragment Shader,简称VF) 。它们更加复杂,但灵活度也更高。
下面是一个顶点/片元着色器的示例代码:
Shader "Custom/Simple VertexFragment Shader"{
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 , 0.0, 0.0 , 1.0);
}
ENDCG
}
}
}
与表面着色器相似,顶点/片元着色器代码也是定义在CGPROGRAM和ENDCG区块之间的。原因是,我们需要自己定义每个Pass需要使用的Shader代码,虽然我们可能需要编写更多的代码,但带来的好处是灵活性更高,更重要的是,我们可以控制渲染的实现细节。
固定函数着色器
表面着色器和顶点/片元着色器都使用了可编程的渲染管线。对于那些不支持可编程渲染管线的老式设备,我们只能使用固定函数着色器(Fixed Function Shader) 来完成渲染了,这些着色器只能完成一些简单的效果。
Shader "Tutorial/Basic" {
Properties {
_Color ("Main Color",Color) = (1, 0.5, 0.5, 1)
}
SubShader {
Pass {
Material {
Diffuse {_Color}
}
Lighting On
}
}
}
固定函数着色器是完全使用ShaderLab语法的。我们只能在Pass语句块中定义,这些代码相当于Pass中的一些渲染设置。(实际上这些固定函数着色器也会被Unity编译成对应的顶点/片元着色器)
对Unity Shader选择的建议
那么我们究竟应该选择哪一种着色器进行Unity Shader的编写?
- 如果有很明确的需求,必须在旧时设备上运行,选择固定函数着色器。否则请选择可编程的着色器,即表面着色器和顶点/片元着色器。
- 如果你想和各种光源打交道,你可能更喜欢使用表面着色器,但是小心在移动平台上的性能表现。
- 如果需要使用的光照数量少,例如只有一个平行光,那么使用顶点/片元着色器是一个更好的选择。
- 更重要的是,如果你有很多自定义的渲染效果,请使用顶点/片元着色器。
最重要的一点,不要单纯地去记忆,怎么使用,在何时使用,去理解,去掌握,知其然然后知其所以然。
(依然是作者答疑)
章节答疑
Unity Shader != Shader
在第二章学习渲染流水线的时候,我们学习到了着色器Shader。我们之前也提到了,Unity Shader和渲染流水线的Shader不是同一个东西,只是由于我们在Unity开发中常常把Unity Shader简称为Shader,因此不少人会将二者搞混。
需要区分这两个概念,传统意义上的Shader,指的是渲染流水线的着色器代码文件。而Unity Shader指的是ShaderLab编写的文件,其文件名后缀为.shader
。它们的区别如下:
- 传统Shader中,我们仅可以编写特定类型的Shader,例如顶点着色器、片元着色器等(也就是说一个Shader只能对应一种着色器)。而在Unity Shader中,我们可以在同一个文件里同时包含需要的顶点着色器和片元着色器(一个Unity Shader可以编写多个着色器)。
- 传统的Shader中,我们无法设置一些渲染设置,例如是否开启混合、深度测试等,这些需要开发者在其他代码中设置。而Unity Shader中,我们通过一行特定的指令就可以完成这些设置。(Unity Shader可以顺带进行渲染设置)
- 在传统的Shader中,我们需要编写冗长的代码来设置着色器的输入和输出,要小心地处理这些输入输出的位置对应关系等。而在Unity Shader中,我们只需要在特定语句块中声明一些属性,就可以通过Material来方便地修改这些属性。并且对于模型自带的数据(如顶点位置、纹理坐标、法线等),Unity Shader也提供了直接访问的方法,不需要开发者自行编码来传给着色器。(简化使用,对数据的访问更加灵活)
虽然Unity Shader相对于传统Shader使用上更方便了,但由于高度的抽象和封装,导致我们可以编写的Shader类型和语法都被限制了。对于一些类型的Shader,例如曲面细分着色器,几何着色器等等,Unity的支持就相对差一些。除此之外,一些高级的Shader语法Unity Shader也不支持。
Untiy Shader提供了一种让开发者同时控制渲染流水线中多个阶段的一种方式,而不仅仅是Shader代码。作为开发者而言,Unity Shader提供的抽象层使我们只需要关心Unity Shader的实现代码,而不需要关心底层渲染引擎的实现细节。
Unity Shader和CG/HLSL的关系
Unity Shader是使用ShaderLab语言编写的。但是对于表面着色器和顶点/片元着色器,我们可以在ShaderLab内部使用CG/HLSL来编写。这些CG/HLSL代码是嵌套在CGPROGRAM ENDCG
标签内部的。在Unity里CG和HLSL几乎是等价的,所以想要编写好Unity Shader,不仅需要掌握ShaderLab,还需要学习CG/HLSL语言的实现。
通常来说,CG代码位于Pass块内部:
Pass {
// Pass的标签和状态设置
CGPROGRAM
// 编译指令,例如:
#pragma vertex vert
#pragma fragment frag
// CG代码
ENDCG
// 其他一些设置
}
但是之前在编写表面着色器的示例代码中,我们发现CGPROGRAM定义在了SubShader块中,这是由于表面着色器实质上还是Unity对顶点/片元着色器的一层封装,Unity在背后会将其转化为一个包含多Pass的顶点/片元着色器,所以还是符合我们上述CG代码位于Pass块内部
的定义的。
虽然我们上文讲到Unity提供了三种着色器:表面着色器
,顶点/片元着色器
,固定函数着色器
。但实质上,表面着色器和固定函数着色器最后都会被转化为顶点/片元着色器。所以本质上讲Unity中只存在顶点/片元着色器。
Unity Shader的编译
Unity编辑器会把CG片段翻译成低级语言编译到不同渲染平台上。这些编译过程比较复杂,Unity会使用不同的编译器来把CG转化成对应平台的代码。
通过面板上的编译和生成代码, 来查看不同平台生成的Shader代码。以进行Debug和优化。
当游戏发布后,游戏数据文件会移除目标平台不需要的代码,保留平台需要的编译代码。
可以使用GLSL吗?
可以,使用GLSLPROGRAM ENDGLSL
标签。如果选择GLSL意味着不支持PC、XBOX这样的仅支持DirectX的平台。
如何在Unity中使用GLSL
Unity shader-writing文档
还有需要纠正一下,NVIDIA已经放弃CG了,所以还是学习GLSL和HLSL吧。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!