本教程介绍了在Unity中为摄像机视图的图像后处理创建最小图像效果的基本步骤。如果你不熟悉纹理映射,你应该先阅读一下章节“纹理球体”。
Unity中的图像后处理
在虚拟摄像机渲染一张图像之后,对该图像应用一些图像后处理通常是很有用的。这里有一些艺术上的原因(比如达到某种视觉风格),但也有技术上的原因(比如在图像后处理中实现动态环境遮挡或景深通常会更有效,而不是作为渲染的一部分实现这些效果)。
在Unity中,每个图像后处理的步骤称作一个“图像效果”。标准包包含了几十种图像效果,它可以通过选择Assets > Import Package > Effects导入进来。为了在摄像机上应用一个或多个图像效果,选中摄像机然后在Component > Image Effects > …中选择图像效果,或者把Standard Assets > Effects > ImageEffects > Scripts上相应的C#脚本拖曳到你的摄像机上去。如果有多个图像效果被附到摄像机上,图像效果就会被应用于这么一个序列,其中每个图像效果被应用于之前图像效果的输出上。
创建着色器
创建一个图像效果的Cg着色器并不复杂:在Project Window中,点击Create并且选择Shader > Image Effect Shader。一个被命名为“NewImageEffectShader”的文件会显示在Project Window中。双击打开它(或右键点击并打开它)。在Cg中使用默认着色器的文本编辑器应该会出现。
以下的着色器会比默认的着色器更有用一点。你可以把它拷贝复制到着色器文件中:
Shader "tintImageEffectShader"
{
Properties
{
_MainTex ("Source", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
}
SubShader
{
Cull Off
ZWrite Off
ZTest Always
Pass
{
CGPROGRAM
#pragma vertex vertexShader
#pragma fragment fragmentShader
#include "UnityCG.cginc"
struct vertexInput
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
};
struct vertexOutput
{
float2 texcoord : TEXCOORD0;
float4 position : SV_POSITION;
};
vertexOutput vertexShader(vertexInput i)
{
vertexOutput o;
o.position = mul(UNITY_MATRIX_MVP, i.vertex);
o.texcoord = i.texcoord;
return o;
}
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _Color;
float4 fragmentShader(vertexOutput i) : COLOR
{
float4 color = tex2D(_MainTex,
UnityStereoScreenSpaceUVAdjust(
i.texcoord, _MainTex_ST));
return color * _Color;
}
ENDCG
}
}
Fallback Off
}
这个着色器有两个属性:_Color
是一个颜色,它被着色器用来对所有像素着色。_MainTex
是一张渲染纹理,它包含了被摄像机渲染的摄像机视图;或者它是之前图像效果的输出渲染纹理。渲染纹理对象可以像2D贴图一样用作纹理映射,但是摄像机也可以渲染它就像它是一个帧缓冲,然后图像被输入到下一个图像效果中就好像它是一个纹理一样。
着色器关闭了面剔除(用Cull Off
)和深度测试(用ZTest Always
),为了确保整个图像都被处理过。它也关闭了写入深度缓冲(用ZWrite Off
),为了不改变深度缓冲。
顶点着色器vertexShader()
对顶点的位置应用标准变换,并且传递了纹理坐标。对于图像效果,这些顶点通常是摄像机视口的角。纹理坐标这些角在纹理坐标空间(从0到1)的位置。
片元着色器fragmentShader()
随后会调用输出贴图的每个像素。它会使用插值的纹理坐标来访问输出贴图_MainTex
的像素。对于一些目标平台(特别是虚拟现实中的立体绘制),额外的纹理坐标变换还是有必要的;这些可以通过Unity的UnityStereoScreenSpaceUVAdjust()
函数来解决。
片元着色器会读取输入贴图中相应像素的颜色,并且用_Color
乘以它来进行着色。更高级图像效果的片元着色器会从输入贴图的不同位置读取多个像素的颜色,并且以一种复杂的方式组合起来计算每个输出像素的颜色。
创建材质并把着色器附着上去
为着色器创建材质的一种方法是在Project Window
中点击Create > Material
。你也可以在新的材质上把着色器拖拽上去。当你选择了这个新材质,在Inspector Window
中的预览窗口应该会显示该着色器的效果;如果没看到,你也许要点击Inspector Window
中下面的灰色栏使它显示出来。如果没有预览或球体是明亮的品红,错误信息会在Unity窗口的下面和Console window
中显示出来。在这种情况下你必须修复这个错误。
最后一步就是把材质和它的着色器应用到摄像机视口的图像上。这需要挂载到摄像机上的一个小型脚本;这里是一个C#中的例子,它会被保存为”tintImageEffectScript.cs”:
using System;
using UnityEngine;
[RequireComponent(typeof(Camera))]
[ExecuteInEditMode]
public class tintImageEffectScript : MonoBehaviour {
public Material material;
void Start()
{
if (!SystemInfo.supportsImageEffects || null == material ||
null == material.shader || !material.shader.isSupported)
{
enabled = false;
return;
}
}
void OnRenderImage(RenderTexture source, RenderTexture destination)
{
Graphics.Blit(source, destination, material);
}
}
这段脚本有一个公共变量material
,它必须被设置为你刚刚创建的材质。Start()
函数会检查是否一切正常;如果不是,它会使脚本和图像效果都无效。实际的工作是在OnRenderImage()
函数中进行的。Unity会用source
变量中的输入图像(作为渲染纹理)和destination变量中的期望输出图像(它也是渲染纹理)为每个图像效果调用这个函数。对source中的输入图像应用这个材质和它的着色器的标准方法是调用Graphics.Blit(source, destination, material)
,它用source
作为输入贴图_MainTex的材质中的着色器来光栅化destination渲染纹理中的所有像素。
大多数图像效果的代码会更复杂因为它们要计算多个中间图像(有时会是不同维度的)而不仅仅是调用一个Graphics.Blit()
。在这种情况下,单个图像效果会使用多个着色器。这通常是用有多个通道的单个着色器来实现的。有一个指定通道的着色器随后会像这样Graphics.Blit(source, destination, material, pass)
被调用,里面的pass
就是被使用通道的索引。
更多的效果
如前所述,Unity在Standard Assets > Effects > ImageEffects中有很多可以立即使用的图像效果。里面的C#脚本和相应的着色器文件是一个学习着色器效果编程的很好的资源。通常它们也是自己图像效果的一个很好的起点。但是,有一些脚本和着色器还是相当复杂的。为顺利开始,你应该先看一下脚本”ColorCorrectionRamp”(着色器为”ColorCorrectionEffect”),”Grayscale” (着色器为 “GrayscaleEffect”) 或”SepiaTone” (着色器为 “SepiaToneEffect”)。
总结
恭喜,你已经了解了Unity中图像效果的基础知识。你看到的一些东西是:
- 如何创建一个图像效果的着色器。
- 如何为这样的着色器创建材质。
- 如何为一个图像效果创建C#脚本。
扩展阅读
- 关于Unity中的图像效果(以及渲染纹理等),参考Unity手册中的描述。
- 关于为图像效果使用更强大的计算着色器,参考章节“计算图像效果”。