1.command buffer具有很高的灵活性,它的作用是预定义一些渲染指令,然后在我们想要执行的时候去执行这些指令(见图1),绿点表示可以在“Forward Rendering Path”和“Deferred Path”中执行这些命令的阶段。

  查看Unity文档,我们有以下事件可以在其中添加CommandBuffer的指令。

  https://docs.unity3d.com/ScriptReference/Rendering.CameraEvent.html

图1.1 渲染管线

  通常,在实现某些屏幕后处理时(比如Bloom效果、屏幕灰化、物体的外轮廓等),场景的所有物体都会受到处理,用command buffer的话,就能选择性地处理物体的效果。command buffer还有一个重要的功能,就是它能代替GrabPass{},去截取在它身后的图像,然后去实现水面的扰动、火焰的热空气扰动、玻璃的伪折射等的效果,在移动端,它所消耗的性能比GrabPass{}的低很多。

2. 用command buffer实现物体发光轮廓

  

   为了减少代码的重复书写,首先我们将创建一个Common.cginc文件,以便各个shader都能调用。

  我们还需要一些GaussianBlur以达到我们要处理的效果。关于高斯模糊可以看这一点。 Gaussian Kernel Calculator.

   现在,给出原图的图片。

  图2.1

  

在初始状态下,我们想要中间的正方体是一个被选中的状态,粗略分解一下该操作步骤

1) 我们使用CommandBuffer中的DrawRenderer方法,将要实现效果的物体绘制出来,该(见图2.2)。

图2.2

2) 对于先前获得的纹理,我们将使用高斯模糊对其进行处理。 (见图2.3)。

图2.3

3) 用模糊过的纹理减去原始的纹理,乘以颜色和亮度,就可以得到发光外轮廓。(见图2.4)

 原始图像的正方体是一个白色物体,颜色值为1,而模糊过的图像的颜色值是小于1的,模糊图像减去原始图片的话是<0的,所以中间部分显示的是黑色。因为高斯模糊是一个加权平均操作,每个像素的颜色值都是由其本身和相邻像素的颜色值进行加权平均得到,越靠近像素本身,权值越高,越偏离像素,权值越低。所以,正方体周围的像素也进行加权平均操作,得到淡一点的颜色。

图2.4

为此,我们将首先创建负责处理所需效果GlowOutlineEffect.cs的类(此脚本附加到主相机),还包括要渲染的每个纹理所必需的着色器。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering; namespace FI
{
public class GlowOutlineEffect : MonoBehaviour
{
#region ENUMS
private enum Pass
{
PrePass,
BlurredPass,
FinalPass,
Result
} [SerializeField]
private Pass passToShow;
#endregion #region PRIVATE_VARS
[SerializeField, Range(0, 3)]
private int iterations; [SerializeField, Range(0, 10)]
private float intensity; [SerializeField]
private Color glowColor; [SerializeField]
private Renderer[] toRender; private Material unlitMat;
private Material gaussianBlurMat;
private Material finalPassMat;
private Material glowOutlineMat; private Material showPrePassMat;
private Material showBlurredMat;
private Material showFinalPassMat; private CommandBuffer buffer; private int prePassTexID;
private int blurredTexID;
private int finalTexID; private int tempText1ID;
#endregion #region UNITY_API void Start()
{ //初始化
InitializeBuffer();
InitializeMaterials();
InitializeProperties();
} // Update is called once per frame
void Update()
{
UpdateBuffer();
UpdateGlowProperties();
MouseButtonInput();
}
#endregion #region PRIVATE_METHODS private void MouseButtonInput()
{
if(Input.GetMouseButtonDown(0))
{
RaycastHit hit;
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if(Physics.Raycast(ray,out hit))
{
if(hit.transform)
{
toRender[0] = hit.transform.GetComponentInChildren<Renderer>();
}
}
}
}
private void InitializeBuffer()
{
buffer = new CommandBuffer();
Camera.main.AddCommandBuffer(CameraEvent.BeforeImageEffects, buffer);
} private void InitializeMaterials()
{
unlitMat = new Material(Shader.Find("Unlit/Color"));
gaussianBlurMat = new Material(Shader.Find("FI/GaussianBlur"));
finalPassMat = new Material(Shader.Find("FI/FinalPass"));
glowOutlineMat = new Material(Shader.Find("FI/GlowOutline")); showPrePassMat = new Material(Shader.Find("FI/ShowPrePass"));
showBlurredMat = new Material(Shader.Find("FI/ShowBlurred"));
showFinalPassMat = new Material(Shader.Find("FI/ShowFinalPass"));
} private void InitializeProperties()
{
prePassTexID = Shader.PropertyToID("_PrePassTex");
blurredTexID = Shader.PropertyToID("_BlurredTex");
tempText1ID = Shader.PropertyToID("_TempTex1");
finalTexID = Shader.PropertyToID("_FinalTex");
} private void UpdateGlowProperties()
{
Shader.SetGlobalFloat("_Intensity", intensity);
Shader.SetGlobalColor("_GlowColor", glowColor);
} private void UpdateBuffer()
{
buffer.Clear(); UpdatePrePassTexture();
UpdateBlurredTexture();
UpdateFinalTexture();
} private void UpdatePrePassTexture()
{
//创建一个临时纹理,prePassTexID是纹理标识,
//第二和第三个参数为-1,表示该纹理的大小跟屏幕大小一样大。
//添加双线性过滤和抗锯齿的,以确保基本纹理尽可能清晰
buffer.GetTemporaryRT(prePassTexID, -1, -1,
0, FilterMode.Bilinear, RenderTextureFormat.ARGB32,
RenderTextureReadWrite.Default, QualitySettings.antiAliasing); buffer.SetRenderTarget(prePassTexID);
buffer.ClearRenderTarget(true, true, Color.clear); for (int i = 0; i < toRender.Length; i++)
{
if (toRender[i].gameObject.activeSelf)
{
buffer.DrawRenderer(toRender[i], unlitMat);
}
} } private void UpdateBlurredTexture()
{
/*创建临时纹理,第二和第三个参数为-2,代表该纹理的大小为屏幕大小的1/2
* 这样可以降低纹理的分辨率以获得更模糊的结果,并降低成本
*/
buffer.GetTemporaryRT(blurredTexID, -2, -2, 0, FilterMode.Bilinear);
buffer.GetTemporaryRT(tempText1ID, -2, -2, 0, FilterMode.Bilinear); //prePassTexID的纹理经过gaussianBlurMat材质的处理后,复制给blurredTexID
buffer.Blit(prePassTexID, blurredTexID, gaussianBlurMat); //循环迭代,迭代次数越多越模糊
for (int i = 0; i < iterations; i++)
{
//最后一个参数代表执行材质Shader里的哪一个Pass
//0代表执行该Shader的第一个Pass,即在水平方向进行模糊
//1代表执行该Shader的第二个Pass,即在垂直方向上进行模糊
buffer.Blit(blurredTexID, tempText1ID, gaussianBlurMat,0);
buffer.Blit(tempText1ID, blurredTexID, gaussianBlurMat,1);
}
} private void UpdateFinalTexture()
{
buffer.GetTemporaryRT(finalTexID, -1, -1, 0, FilterMode.Bilinear); buffer.Blit(null, finalTexID, finalPassMat);
} /// <summary>
/// 在所有的对象都渲染完后执行的方法,source是全屏幕的初始图像,
/// Blit()函数把source传给材质处理,在材质的Shader中用_MainTex
/// 去获取source的图像。
/// </summary>
/// <param name="source"></param>
/// <param name="destination"></param>
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
switch (passToShow)
{
case Pass.PrePass:
Graphics.Blit(null, destination, showPrePassMat);
break;
case Pass.BlurredPass:
Graphics.Blit(null, destination, showBlurredMat);
break;
case Pass.FinalPass:
Graphics.Blit(null, destination, showFinalPassMat);
break;
default:
Graphics.Blit(source, destination, glowOutlineMat);
break;
} }
#endregion
}
}

有了外发光轮廓的纹理后,剩下的就是将其添加到屏幕上渲染的纹理。

Shader "FI/GlowOutline"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
} SubShader
{
Pass
{
CGPROGRAM #pragma vertex vert
#pragma fragment frag #include "Common.cginc" sampler2D _FinalTex; fixed4 frag(v2f i) : SV_Target
{
return tex2D(_MainTex, i.uv) + tex2D(_FinalTex, i.uv);
} ENDCG
} }
}

Unity CommandBuffer物体轮廓的更多相关文章

  1. Unity 实现物体破碎效果(转)

    感谢网友分享,原文地址(How to Make an Object Shatter Into Smaller Fragments in Unity),中文翻译地址(Unity实现物体破碎效果) In ...

  2. Unity查找物体的子物体、孙物体

    Unity查找物体下的所有物体 本文提供全流程,中文翻译. Chinar 坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 -- 高分辨率用户请根据需求调整网页缩放比例) Chinar -- 心分 ...

  3. opengl学习-利用模板测试勾画物体轮廓中出现的一个问题

    我在学习OpenGL模板测试勾画物体轮廓的时候,出现了这个问题: 这个出现的原因就是,改变摄像机的时候,每次绘制,上次绘制中模板缓冲区的数据没有清除的原因.也就是在while循环开始的时候,glCle ...

  4. Unity CommandBuffer的一些学习整理

    1.前言 近期在整理CommandBuffer这块资料,之前的了解一直较为混乱. 算不上新东西了,但个人觉得有些时候要比加一个摄像机再转RT廉价一些,至少省了深度排序这些操作. 本文使用两个例子讲解C ...

  5. unity 实现物体破碎效果的一些方法 - 细雨淅淅

    游戏越来越接近现实的感觉,如果有一个真是的 虚拟现实设备,可能我们真的会感觉是在真实世界.场景的逼真是在渲染效果.角色AI.游戏逻辑.物理效果等等一起导致的结果.现在游戏越来越大,除了渲染,物理估计是 ...

  6. unity 实现物体破碎效果的一些方法

    游戏越来越接近现实的感觉,如果有一个真是的 虚拟现实设备,可能我们真的会感觉是在真实世界.场景的逼真是在渲染效果.角色AI.游戏逻辑.物理效果等等一起导致的结果.现在游戏越来越大,除了渲染,物理估计是 ...

  7. 关于Unity动态物体无法向使用使用custom shader和lightmap的物体投射阴影

    最近在做unity shader forge和marmoset的优化,TA那边遇到了一个阴影显示的问题,具体如下:   在Forward Rendering状态下,静态场景使用了是shader for ...

  8. Unity 父物体与子物体位置

         酷跑片段本来想做三条轨道,然后通过切换轨道来做,后面发现一种巧妙的方法,利用物体的父级偏移来实现轨道的切换. 比如上图,实际运动的是Car对象,通过修改MineControler的左右位置( ...

  9. Unity 3D物体的点击事件响应以及NGUI坐标和世界坐标的互相转换

    Unity 版本:4.5 NGUI版本:3.6.5 参考链接:http://game.ceeger.com/Script/Camera/Camera.ScreenPointToRay.html,Uni ...

随机推荐

  1. Linux运维学习第六周记

    四月上夏渐热 善疗也须调摄 文殊眼裹抽筋 金刚脑后拔楔 网络的世界让人变得不那么真实! 第六周学记 用了一周的时间学习了计算机网络基础知识,说是基础,更应该说是必备的常识! 网络的协议和管理 TCP/ ...

  2. pip安装与使用

    介绍 pip是python包管理工具,提供了对python包的查找,下载,安装,卸载功能. 安装 检查是否安装 pip --version 安装 curl https://bootstrap.pypa ...

  3. 枚举 switch case 标签必须为枚举常量的非限定名称

    枚举 switch case 标签必须为枚举常量的非限定名称 错误描述: Error:(63, 24) 错误: 枚举 switch case 标签必须为枚举常量的非限定名称. 解决思路: switch ...

  4. F. Make It Connected 解析(思維、MST)

    Codeforce 1095 F. Make It Connected 解析(思維.MST) 今天我們來看看CF1095F 題目連結 題目 給你\(n\)個點,每個點\(u\)還有一個值\(a[u]\ ...

  5. 卸载联软UniAccess,删除UniAccess Agent记录

    UniAccess 卸载 事情起因: 公司假以安全上网为由,让公司员工安装所谓的"XX上网助手",实则是内嵌了联软的UniAccess监控系统. 有关这个软件的用途就不用多介绍了, ...

  6. SpringBoot原理发现(一)

    说明: 本系列基于SpringBoot 2.2.9.RELEASE 版本,对SpringBoot的原理进行分析,一共分为四节: SpringBoot原理发现(一):创建Hello World,对pom ...

  7. eyou升级弹窗、云插件库、接口配置、功能开关【按需显示插件】

    分享一个实用三方插件,如插件描述所言,很多时候我们不希望客户乱搞. hbh.cool/find/146.html

  8. MIPS汇编及模拟器下载

    1. 简述汇编语言发展 在计算机发展初期,人们用0-1序列来表示每一条语言,亦即二进制的机器指令 由于机器指令过于繁琐,程序员们开发出了一种新的语言,这种用符号表示的计算机语言被称为汇编语言 计算机继 ...

  9. Hash 算法与 Manacher 算法

    目录 前言 简单介绍 简述 Hash 冲突 离散化 基本结构 普通 Hash 简述 例题 字符串 Hash 简单介绍 核心思想 基本运算 二维字符串 Hash 例题 兔子与兔子 回文子串的最大长度 后 ...

  10. Runnable接口和Callable接口的区别

    Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已:Callable接口中的call()方法是有返回值的,是一个泛型,和Future.Fut ...