本书版本为“占红来 译”版,笔记会持续更新,有错误的地方欢迎指正,谢谢!
引言
屏幕特效,又称后期特效。大多数现代的游戏都运用了屏幕特效,比如:景深(DOF)效果、光晕效果或颜色矫正效果等。
创建屏幕特效的脚本系统
通过屏幕特效,可以将屏幕作为一个整体进行修改,需要创建一个C#脚本来抓取当前游戏的渲染纹理,并将其传给着色器,着色器处理(像素级别的操作)该渲染纹理后,再将修改后的纹理(也就是每个像素的颜色)传给Unity的渲染器中~
ImageEffect.shader代码:
Shader "Custom/ImageEffect" {
Properties {
_MainTex("Base(RGB)",2D) = "white"{}
_LuminosityAmount("GrayScale Amount",Range(0,1)) = 1
}
//此着色器将使用纯CG代码,而不是表面着色器代码,可更加优化,只需对渲染纹理的像素进行操作。
SubShader {
Pass
{
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
half _LuminosityAmount;//声明该值是为了接受Unity编辑器传过来的数据并使用之。
//建立像素函数,这就是屏幕特效实现的核心内容了。这个函数将会对渲染纹理sourceTexture
//的每个像素进行操作,并把新的图像返回给TestRenderImage.cs脚本的destTexture。
half4 frag(v2f_img i):COLOR
{
//从渲染纹理中得到颜色,从v2f_img结构中得到UV值。
half4 renderTex = tex2D(_MainTex,i.uv);
//对渲染纹理应用_LuminosityAmount。
float luminosity = 0.299*renderTex.r + 0.587*renderTex.g + 0.114*renderTex.b;
half4 finalColor = lerp(renderTex, luminosity, _LuminosityAmount);
return finalColor;
}
ENDCG
}
}
FallBack "Diffuse"
}
TestRenderImage.cs代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode]//添加这个的原因:为了在Unity没有处于运行状态下也能运行该脚本。
public class TestRenderImage : MonoBehaviour {
//添加一些变量来存储重要的数据以及对象。
//#region和 #endregion只是把代码包起来,使代码看起来简洁清晰。
#region Variables
public Shader curShader;
public float grayScaleAmount = 1.0f;
private Material curMaterial;
#endregion
//C#中为什么要用属性,1、在给一个对象附值时,可以通过属性SET方法,对这个值
//进行一个过滤,看这个值服不服合属性的要求。2、有的对象的属性,需要只读的,
//就可以省去SET方法,有的是只写的,就可以省去GET方法。
//屏幕特效是使用着色器对屏幕图像进行逐像素操作。因此,需要一个着色器运行
//的载体:材质。那就新建一个C#属性用来检查材质是否存在,即读之前检查一下。
#region Properties
Material material
{
get
{
if(curMaterial==null)
{
curMaterial = new Material(curShader);
curMaterial.hideFlags = HideFlags.HideAndDontSave;
}
return curMaterial;
}
}
#endregion
//在脚本开始就检查当前的目标平台能否支持屏幕特效,不能则禁用该脚本。
void Start ()
{
if (!SystemInfo.supportsImageEffects)
{
enabled = false;
return;
}
if (!curShader && !curShader.isSupported)
enabled = false;
}
//使用Unity内置的函数OnRenderImage()来抓取渲染纹理,并传给了着色器。
void OnRenderImage(RenderTexture sourceTexture, RenderTexture destTexture)
{
if (curShader != null)
{
material.SetFloat("_LuminosityAmount", grayScaleAmount);
Graphics.Blit(sourceTexture, destTexture, material);
}
else
Graphics.Blit(sourceTexture, destTexture);
}
void Update () {
//grayScaleAmount表示灰度值,限制value的值在0和1之间。
grayScaleAmount = Mathf.Clamp(grayScaleAmount, 0.0f, 1.0f);
}
//脚本结束之后做清理。
private void OnDisable()
{
if (curMaterial)
DestroyImmediate(curMaterial);
}
}
灰度值=0.0:
灰度值=0.5:
灰度值=1.0:
就像PS的图层一样,特效也可以一层一层地添加,并会以一定的顺序呈现出来。
场景深度(景深)
通过开启Unity内置的深度模式可获取当前游戏中所有对象的深度,利用这些深度信息可实现很多不同的效果。
还是分为Shader和C#脚本两部分。
1.Shader中通过Unity内置函数UNITY_SAMPLE_DEPTH()来得到深度信息并为每一个像素生成一个浮点值;Linear01Depth()确保深度值在0到1之间。
2.C#脚本中,不就是把上面的C#脚本的灰度值(grayScaleAmount)改成了深度值(depthPower)嘛,其他几乎一样。
使用屏幕特效实现亮度、饱和度以及对比度
亮度,饱和度,对比度,灰度的概念:
亮度
图像中RGB值的大小,RGB各个值越大,那么亮度越亮,越小,亮度越暗。比如我们要增加亮度,那么直接增加RGB值即可。饱和度
指的是颜色的纯度。一般用彩度除以明度,表征彩色偏离同亮度灰色的程度。简单来说,当颜色越偏向某个值,即越偏离灰度,饱和度越大;当颜色越偏向灰度,饱和度越小。
下面是百度百科关于饱和度的一段定义:
饱和度是指色彩的鲜艳程度,也称色彩的纯度。饱和度取决于该色中含色成分和消色成分(灰色)的比例。含色成分越大,饱和度越大;消色成分越大,饱和度越小。纯的颜色都是高度饱和的,如鲜红,鲜绿。混杂上白色,灰色或其他色调的颜色,是不饱和的颜色,如绛紫,粉红,黄褐等。完全不饱和的颜色根本没有色调,如黑白之间的各种灰色对比度
指的是一幅图像中明暗区域最亮的白和最暗的黑之间不同亮度层级的测量,差异范围越大代表对比越大,差异范围越小代表对比越小。一般来说对比度越大,图像越清晰醒目,色彩也越鲜明艳丽;而对比度小,则会让整个画面都灰蒙蒙的。灰度
灰度使用黑色调表示物体,即用黑色为基准色,不同的饱和度的黑色来显示图像。 每个灰度对象都具有从 0%(白色)到100%(黑色)的亮度值。
了解了一些基本的色彩概念,我们就可以开始进行处理了。首先我们看一下Unity后处理效果的原理:
所谓屏幕后处理,简单来说就是渲染流水线的最后阶段,对由整个场景生成的一张图片进行处理,比如HDR,运动模糊等等效果,通过屏幕空间的后处理,可以整体改变整个游戏的风格或者效果。所以,要制作屏幕后处理,我们需要两样东西,一个是用于渲染后处理效果的shader,而另一个是我们需要调用这个渲染的脚本,好在Unity为我们提供了相关的功能。
可在C#脚本中使用的两个函数:
1.OnRenderImage函数
该函数在MonoBehaviour中提供,该函数在所有渲染完成后才进行调用,也就是我们上文所说的生成了一张场景图片,函数的原型如下:
void OnRenderImage(RenderTexture sourceTexture,RenderTexture destTexture);
RenderTexture表示的是渲染纹理,我们渲染物体并不是仅仅渲染在屏幕空间,也可以将物体渲染到特定纹理上,也就是RenderTexture。sourceTexture就是我们渲染的场景图片,而destTexture是目标渲染纹理。我们可以在这个函数中进行相关的后处理效果,使用带有后处理效果shader的材质将场景内容重新渲染。
2.Graphics.Blit函数
该函数是Graphics的函数,用于将源纹理拷贝到目标纹理,函数原型如下:
public static void Blit(Texture source,RenderTexture dest);
public static void Blit(Texture source,RenderTexture dest, Material mat, int pass = -1);
public static void Blit(Texture source,Material mat, int pass = -1);
source是源纹理,dest是目标纹理,当dest为null时,直接将源纹理拷贝到屏幕;mat是拷贝时使用的材质,也就是我们后处理时使用的材质,Unity会使用该材质将源纹理进行处理拷贝给目标纹理,pass是使用的材质shader所使用的pass,我们知道,一个shader中可能有多个pass,使用哪个pass进行处理就可以从该参数传入,当然,默认为-1时表示所有的pass都会执行。
在了解了Untiy的后处理流程后,我们就可以着手写我们的亮度对比度饱和度调整后处理了。
话不多说,直接放代码:
BSC_ImageEffect.shader代码:
Shader "Custom/BSC_ImageEffect" {
Properties {
_MainTex("Base(RGB)",2D) = "white"{}
_BrightnessAmount("Brightness Amount",Range(0,2)) = 1
_satAmount("Saturation Amount",Range(0,2)) = 1
_conAmount("Contrast Amount",Range(0,3)) = 1
}
SubShader {
Pass
{
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
half _BrightnessAmount;
half _satAmount;
half _conAmount;
//color是当前的渲染纹理。
float3 ConSatBrt(float3 color, float brt, float sat, float con)
{
//增加或减少下面三个值来调整颜色。
float AvgLumR = 0.5, AvgLumG = 0.5, AvgLumB = 0.5;
//LuminanceCoeff是图像的亮度系数。
float3 LuminanceCoeff = float3(0.2125, 0.7154, 0.0721);
//亮度的操作
float3 AvgLumin = float3(AvgLumR, AvgLumG, AvgLumB);
float3 brtColor = color*brt;
//把当前颜色值和亮度系数进行点积得到图像的全局亮度。
float intensityf = dot(brtColor, LuminanceCoeff);
float3 intensity = float3(intensityf, intensityf, intensityf);
//混合运算
float3 satColor = lerp(intensity, brtColor, sat);
float3 conColor = lerp(AvgLumin, satColor, con);
return conColor;
}
half4 frag(v2f_img i):COLOR
{
half4 renderTex = tex2D(_MainTex,i.uv);
renderTex.rgb = ConSatBrt(renderTex.rgb, _BrightnessAmount, _satAmount, _conAmount);
return renderTex;
}
ENDCG
}
}
FallBack "Diffuse"
}
BSC_ImageEffect代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode]
public class BSC_ImageEffect : MonoBehaviour {
#region Variables
public Shader curShader;
//增加三个用于调节的滑块。
public float brightnessAmount = 1.0f;
public float saturationAmount = 1.0f;
public float contrastAmount = 1.0f;
private Material curMaterial;
#endregion
#region Properties
Material material
{
get
{
if(curMaterial==null)
{
curMaterial = new Material(curShader);
curMaterial.hideFlags = HideFlags.HideAndDontSave;
}
return curMaterial;
}
}
#endregion
void Start ()
{
if (!SystemInfo.supportsImageEffects)
{
enabled = false;
return;
}
if (!curShader && !curShader.isSupported)
enabled = false;
}
void OnRenderImage(RenderTexture sourceTexture, RenderTexture destTexture)
{
if (curShader != null)
{
material.SetFloat("_BrightnessAmount", brightnessAmount);
material.SetFloat("_satAmount", saturationAmount);
material.SetFloat("_conAmount", contrastAmount);
Graphics.Blit(sourceTexture, destTexture, material);
}
else
Graphics.Blit(sourceTexture, destTexture);
}
void Update () {
brightnessAmount = Mathf.Clamp(brightnessAmount, 0.0f, 2.0f);
saturationAmount = Mathf.Clamp(saturationAmount, 0.0f, 2.0f);
contrastAmount = Mathf.Clamp(contrastAmount, 0.0f, 3.0f);
}
private void OnDisable()
{
if (curMaterial)
DestroyImmediate(curMaterial);
}
}
使用屏幕特效实现类似PS的基本混合模式
屏幕特效不仅限于渲染画面的颜色,还可以实现其他图像和渲染纹理之间的混合效果。最常见的混合模式:乘法(Multiply)、叠加(Add)以及覆盖(Overlay),在你的游戏中拥有PS的混合模式是多么的方便。但还是算了,不学这了,还是交给美工的PS去搞这吧。。。
还是说一下吧,实现很简单,和上面的代码很类似,自己去搞咯~