本文翻译自catlikecoding
1 默认场景
当你在Unity中创建一个新场景时,你会得到一个默认的camera和directional light.通过GameObject/ 3D Object/ Sphere创建一个简单的球体,把它放在原点,然后把camera放在它前面。
虽然这是很简单的场景,但已经可以进行复杂的渲染操作。为了更好地了解渲染的过程,删掉一些东西会很有帮助。
1删除
通过 Window / Lighting /Setting, 我们来看一下场景的光照设置。
我们可以在Environtment lighting中选择skybox。skybox现在被当做场景的背景使用。把skybox设为none来关闭它。
当你这么做了的时候,你也可以关闭Precomputed Realtime GI和 Baked GI,我们这次不会用到它们。
取消skybox之后,背景自动被设置成了灰蓝色。
你可能会猜测背景的颜色来自哪里。
每个camera都会定义背景,camera默认渲染skybox,但是没有skybox也会渲染默认的颜色。
为了进一步简化渲染,删掉directional light object.这样场景中不再存在光线。球体会显示阴影。
2从Object到Image
这个简单的场景通过两步渲染。首先,图片填充了camera的背景颜色。接下来球体的阴影被画在背景颜色上。
Unity是怎么知道它需要画一个球体的呢?我们有一个球体对象,并这个对象有一个mesh renderer组件。如果这个对象在camera的视野里,那么它就会被渲染。Unity通过检查对象的包围盒是否和相机视野的平截锥体相交来确认是否需要渲染。
1第一个着色器
通过Assets/ Cteate / Shader / Unlit Shader并命名,比如你可以命名My First Shader
打开着色器文件,删掉它的内容,这样我们可以从0开始。
一个着色器通过Shader关键字定义。紧跟Shader关键字的时描述着色器的字符串,你可以在着色器选择菜单选择这个着色器。命名不需要和文件名相同。
Shader "Custom/My First Shader" {
}
保存文件,再把着色器赋值给一个material.使用我们自己的material改变我们的球体。球体会变成紫红色。这发生在Unity使用了一个有错误的着色器的时候,Unity使用这个颜色来告诉我们这里出问题了。
我们需要添加sub-shaders来消除这个问题,我们可以使用不同的sub-shader渲染不同的平来或者是不同等级的细节。举个例子,你可以为pc使用一个sub-shader,为手机使用另一个。这里我们只需要一个sub-shader.
Shader "Custom/My First Shader" {
SubShader {
}
}
一个sub-shader至少有一个pass.一个shader pass 是一个对象被渲染的地方。我们使用一个pass,但使用多个pass是可能的。使用多个pass意味着这个对象被渲染多次。
Shader "Custom/My First Shader" {
SubShader {
Pass {
}
}
}
这时球体会变成白色,因为我们使用了空白的empty的默认行为。这时我们也不再有任何的错误。
2着色器程序
是时候编写我们自己的着色器程序了。我们使用Unity的着色语言来编写,Unity的着色语言是HLSL和CG渲染语言的变种。我们必须用CGPROGRAM关键字开始我们的代码,用关键字ENDCG结束。
Pass {
CGPROGRAM
ENDCG
}
着色器现在抱怨没有vertex program和fragment programs。每个Shader都由两个programs组成。vertex program负责处理mesh的顶点。这包括从object space到display space的转换。fragment program负责每个位于mesh三角形中像素的染色。
我们需要通过pragma 指令告诉编译器使用哪个程序。
CGPROGRAM
#pragma vertex MyVertexProgram
#pragma fragment MyFragmentProgram
void MyVertexProgram () {
}
void MyFragmentProgram () {
}
ENDCG
3着色器合集
Unity着色器编译器根据目标平台,将我们的代码转化成其他的程序。不同的平台需要不同的解决方案。举个例子,Windows下是Direct3D,,Macs 是OpenGL,手机是OpenGL ES。
在编辑器中选择着色器,在inspector窗口中观察。它展示了一些着色器的信息,包括编译错误。你也可以点击Compile and show code 按钮,Uity会编译着色器,并在你的编辑器中输出结果,这样你可以观察生成的代码。
4包含其他文件
为了生成有用的着色器,你需要很多模版代码, 那些定义了常用变量,函数还有其他东西的代码。如果这是C#语言,我们可以把代码放到其他类里面,但是着色器没有类。那些代码就全部扔在一个大文件里,没有类或者命名空间。
幸运的是,我们可以把代码分成多个文件。你可以使用#include指令加载不同文件的内容。一个需要包含的经典文件是 UnityCG.cginc
CGPROGRAM
#pragma vertex MyVertexProgram
#pragma fragment MyFragmentProgram
#include "UnityCG.cginc"
void MyVertexProgram () {
}
void MyFragmentProgram () {
}
ENDCG
UnityCG.cginc是绑定到Unity我们会经常使用的包含文件。它包含了一些其他的重要文件,和一些通用的函数。
UnityShaderVariables.cgnic定义了大量在渲染中必须的着色器变量,比如transformation, camera, 还有light data.如果需要的话,这些全部由Unity设置。
HLSLSupport.cginc让你的代码能够跨平台,这样你就无需担心平台指定的数据类型。
UnityInstancing.cginc专门用来instancing support。instancing support是用来降低draw调用的渲染技术。虽然UnityCG没有直接包含这个文件,但它被包含在UnityShaderVariables中。
5产生输出
为了渲染一些东西,我们的着色器程序需要产生一些输出。Vertex Program 必须返回一个顶点最后的坐标。坐标有几个参数?4个。因为我们使用的是4 X 4变换矩阵。
将函数的返回值从void改成float4,float4就是4个浮点数的集合。现在我们只需要返回0。
float4 MyVertexProgram () {
return 0;
}
现在我们会得到一个“语义缺失(missing semantics)”的错误。编译器知道了我们返回float4,但它不知道这个返回值代表什么。所以它不知道GPU会怎么处理它。我们程序的输出必须变得具