屏幕后处理效果:渲染完整个场景后得到屏幕图像后再对图像进行操作
在Unity中实现需要通过效果处理的脚本挂载在摄像机中,书中该脚本继承一个基类PostEffectBase。
该脚本被希望在编辑器模式下也能正常使用,且规定只能挂载在Camera上。
[ExecuteInEditMode]
[RequireComponent (typeof(Camera))]
CheckResources函数用于检查平台是否支持该函数。
CheckShaderAndCreateMaterial函数则用于子类对材质进行操作前先检查是否存在该材质如果没有的话会新建一个,检查shader是否可以使用
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
[RequireComponent (typeof(Camera))]
public class PostEffectsBase : MonoBehaviour {
// Called when start
protected void CheckResources() {
bool isSupported = CheckSupport();
if (isSupported == false) {
NotSupported();
}
}
// Called in CheckResources to check support on this platform
protected bool CheckSupport() {
if (SystemInfo.supportsImageEffects == false || SystemInfo.supportsRenderTextures == false) {
Debug.LogWarning("This platform does not support image effects or render textures.");
return false;
}
return true;
}
// Called when the platform doesn't support this effect
protected void NotSupported() {
enabled = false;
}
protected void Start() {
CheckResources();
}
// Called when need to create the material used by this effect
protected Material CheckShaderAndCreateMaterial(Shader shader, Material material) {
if (shader == null) {
return null;
}
if (shader.isSupported && material && material.shader == shader)
return material;
if (!shader.isSupported) {
return null;
}
else {
material = new Material(shader);
material.hideFlags = HideFlags.DontSave;
if (material)
return material;
else
return null;
}
}
}
子类脚本
获取shader,声明三个变量可以提供到外面进行更改。
OnRenderImage函数可以将变量值传递给材质,通过材质来使用屏幕特效
Graphics.Blit(src, dest, material);
如果没有材质则直接使用原图不做任何处理。
Graphics.Blit(src, destl);
using UnityEngine;
using System.Collections;
public class BrightnessSaturationAndContrast : PostEffectsBase {
public Shader briSatConShader;
private Material briSatConMaterial;
//检查材质和shader是否存在、支持
//返回材质
public Material material {
get {
briSatConMaterial = CheckShaderAndCreateMaterial(briSatConShader, briSatConMaterial);
return briSatConMaterial;
}
}
[Range(0.0f, 3.0f)]
public float brightness = 1.0f;
[Range(0.0f, 3.0f)]
public float saturation = 1.0f;
[Range(0.0f, 3.0f)]
public float contrast = 1.0f;
//把参数传给材质
void OnRenderImage(RenderTexture src, RenderTexture dest) {
if (material != null) {
material.SetFloat("_Brightness", brightness);
material.SetFloat("_Saturation", saturation);
material.SetFloat("_Contrast", contrast);
Graphics.Blit(src, dest, material);
} else {
Graphics.Blit(src, dest);
}
}
}
调节对比度、亮度、饱和度shader
属性必须声明一个名为_MainTex的纹理属性,Graphics.Blit(src, dest, material)会将第一个参数传递给名为_MainTex的纹理属性。然后还要声明脚本中色彩调节三个的参数属性。
关闭深度写入,防止挡住在其后渲染的物体。OnRenderImage函数在所有不透明的Pass执行完后会立即被调用,会影响到后面透明的Pass渲染。
顶点着色器直接调用unity内置的appdata_img结构体作为顶点着色器输入
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 12/Brightness Saturation And Contrast" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_Brightness ("Brightness", Float) = 1
_Saturation("Saturation", Float) = 1
_Contrast("Contrast", Float) = 1
}
SubShader {
Pass {
ZTest Always Cull Off ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
half _Brightness;
half _Saturation;
half _Contrast;
struct v2f {
float4 pos : SV_POSITION;
half2 uv: TEXCOORD0;
};
v2f vert(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed4 renderTex = tex2D(_MainTex, i.uv);
// Apply brightness
fixed3 finalColor = renderTex.rgb * _Brightness;
// Apply saturation
fixed luminance = 0.2125 * renderTex.r + 0.7154 * renderTex.g + 0.0721 * renderTex.b;
fixed3 luminanceColor = fixed3(luminance, luminance, luminance);
finalColor = lerp(luminanceColor, finalColor, _Saturation);
// Apply contrast
fixed3 avgColor = fixed3(0.5, 0.5, 0.5);
finalColor = lerp(avgColor, finalColor, _Contrast);
return fixed4(finalColor, renderTex.a);
}
ENDCG
}
}
Fallback Off
}
边缘检测:
卷积核是一个四方形网格结构,每个方格内都有一个权重值,卷积时需要将卷积核放置于像素上,反转核之后一次计算核中每个元素和其覆盖的像素值的乘积并求和。
Image Kernels explained visually (setosa.io)
描边效果
下面两张图是利用四方格对图像进行分别卷积的结果,纵向和横向分别用不同的四方格去计算,计算出纵向和横向的边缘值,综合计算出边缘值(1 - edgeX - edgeY),再利用边缘值对边缘颜色和贴图颜色进行线性插值。
脚本代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EdgeDetectionCS : PostEffectsBase
{
public Shader edgeDetectShader;
private Material edgeDetectMaterial = null;
//获取材质
public Material material
{
get
{
edgeDetectMaterial = CheckShaderAndCreateMaterial(edgeDetectShader,edgeDetectMaterial);
return edgeDetectMaterial;
}
}
//从外面调节颜色和边缘
[Range(0.0f, 1.0f)]
public float edgesOnly = 0.0f;
public Color edgeColor = Color.black;
public Color backgroundColor = Color.white;
//直接应用或者赋予给材质参数
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if(material != null)
{
material.SetFloat("_EdgeOnly", edgesOnly);
material.SetColor("_EdgeColor", edgeColor);
material.SetColor("_BackgroundColor", backgroundColor);
Graphics.Blit(source, destination, material);
}
else
{
Graphics.Blit(source, destination);
}
}
}
shader代码
Shader "Custom/EdgeDetectionShader"
{
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_EdgeOnly ("Edge Only", Float) = 1.0
_EdgeColor ("Edge Color", Color) = (0, 0, 0, 1)
_BackgroundColor ("Background Color", Color) = (1, 1, 1, 1)
}
SubShader
{
Pass
{
ZTest Always Cull Off ZWrite Off
CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert
#pragma fragment fragSobel
sampler2D _MainTex;
//unity提供的可以用于访问纹理每个纹素大小
//512x512的纹理纹素为1/512
uniform half4 _MainTex_TexelSize;
fixed _EdgeOnly;
fixed4 _EdgeColor;
fixed4 _BackgroundColor;
struct v2f
{
float4 pos :SV_POSITION;
//定义一个维数为9的纹理数组
half2 uv[9] : TEXCOORD0;
};
v2f vert(appdata_img v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//临时变量,存储纹理值
half2 uv = v.texcoord;
//数组元素 = 纹理坐标为(0,0) + 纹素值 * (-1,-1)
//就是该坐标相邻左上角的像素点
//该数组就是取周围的包括自己九个像素点的坐标
o.uv[0] = uv + _MainTex_TexelSize.xy * half2(-1, -1);
o.uv[1] = uv + _MainTex_TexelSize.xy * half2(0, -1);
o.uv[2] = uv + _MainTex_TexelSize.xy * half2(1, -1);
o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 0);
o.uv[4] = uv + _MainTex_TexelSize.xy * half2(0, 0);
o.uv[5] = uv + _MainTex_TexelSize.xy * half2(1, 0);
o.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1, 1);
o.uv[7] = uv + _MainTex_TexelSize.xy * half2(0, 1);
o.uv[8] = uv + _MainTex_TexelSize.xy * half2(1, 1);
return o;
}
fixed luminance(fixed4 color)
{
//返回的是颜色的亮度
return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
}
half Sobel(v2f i)
{
const half Gx[9] = {-1, 0, 1,
-2, 0, 2,
-1, 0, 1};
const half Gy[9] = {-1, -2, -1,
0, 0, 0,
1, 2, 1};
half texColor;
half edgeX = 0;
half edgeY = 0;
for(int it = 0;it < 9;it++)
{
//计算出来亮度
texColor = luminance(tex2D(_MainTex,i.uv[it]));
//亮度和每个方格内元素相乘再相加
edgeX += texColor * Gx[it];
edgeY += texColor * Gy[it];
}
//edge值越小越有可能是边界
half edge = 1- abs(edgeX) - abs(edgeY);
return edge;
}
fixed4 fragSobel(v2f i) : SV_Target
{
half edge = Sobel(i);
//线性插值(边缘颜色,贴图颜色,edge值)越是边界edge值越小,就越偏向边缘颜色
fixed4 withEdgeColor = lerp(_EdgeColor,tex2D(_MainTex,i.uv[4]),edge);
fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor,edge);
return lerp(withEdgeColor,onlyEdgeColor,_EdgeOnly);
}
ENDCG
}
}
FallBack Off
}
效果: