Unity3D使用RenderCommand渲染外轮廓
发表于2018-07-24
外轮廓渲染方式
我这里所要介绍的外轮廓是使用模糊后处理实现的,不涉及到边缘查找或是顶点扩展这些,简单的说这种方式渲染外轮廓总共分三步:

1.用单色渲染目标物体到RT1上

2.对该RT1进行模糊处理得到RT2

3.将RT2中与RT1重合的像素抠掉,形成的外轮廓与原始图叠加,最终在原图上绘制出了目标物体的外轮廓。

这样绘制出的外轮廓形状比较均匀,薄厚易于调整(通过调整模糊程度),不会被遮挡住也可以同时作为被遮挡人形的渲染效果,之前玩儿风暴英雄感觉他的那个角色轮廓就是这么搞的。另外如果说不希望外轮廓透过遮挡物的话可以在第一步的时候使用Camera目标缓冲区的深度缓存,添加一点深度位移就可以保证RT1得到的只是目标物体未被遮挡的部分。
第二步与第三步都是简单的图像处理,在OnRenderImage消息响应方法中使用Unity3D提供的非常简便的Graphic.Blit接口就好了,我主要讲讲使用Unity5新提供的CommandBuffer对第一步的实现方法。
原来的做法
原来我做第一步的时候就是创建一个Camera,调用主摄像机的CopyFrom,修改一些设置再使用replacementShader来对目标物体进行单独的额外渲染,这有两点要求,一是目标物体有单独的layer,这样复制相机可以通过layermask对其进行筛选,第二就是针对该物体原有shader的RenderType写一个replacementShader。Camera的脚本如下:
[DisallowMultipleComponent] [RequireComponent(typeof(Camera))] public class CopyCamera : MonoBehaviour { public LayerMask cullingMask; public Shader replacementShader; public Camera mainCamera; protected virtual void Start() { if (!mainCamera) mainCamera = Camera.main; if (!mainCamera) return; Camera camera = GetComponent<Camera>(); transform.parent = mainCamera.transform; transform.localPosition = Vector3.zero; transform.localRotation = Quaternion.identity; camera.CopyFrom(mainCamera); camera.clearFlags = CameraClearFlags.SolidColor; camera.backgroundColor = Color.clear ; camera.cullingMask = cullingMask.value; camera.SetReplacementShader(replacementShader, null); } }
创建一个摄像机挂上这个脚步,运行时那个摄像机就会与原相机保持一致并使用replacementShader渲染指定layer的物体。可以直接在该摄像机下写后处理脚本,直接使用该摄像机的颜色缓存,或者为该摄像机指定一个renderTarget,将结果保存在一张RenderTexture上并在主摄像机的后处理逻辑中进行后面的步骤。
这种做法有几个问题,首先是需要额外创建出一个摄像机并对其进行管理,Camera本身属于Unity3D场景管理中比较重的对象,他的背后应该还涉及视锥切割,排序等一系列复杂的操作,对于仅需要绘制几个简单物体的操作来说太浪费计算资源了。另外需要绘制的对象需要有单独的层,如果本身已经由其他需要跟其他同类物体指定一个layer的话就不太方便操作了。最后渲染的第一步与后几步分开了,由于最终需要将结果输出到主摄像机上,这意味着两个摄像机上都有一些需要维护的脚本。
使用CommandBuffer
CommandBuffer算是一个渲染任务组,包含了完整的绘制指令,数据传输以及状态设定等,使用他可以让我们更灵活的控制渲染过程。直接上代码:
[RequireComponent(typeof(Camera))] public class RenderBlurOutline : MonoBehaviour { public int blurIterCount = 1; public float blurScale = 1.0f; public Shader outlineShader; public Shader silhouetteShader; public Renderer[] silhouettes; public Color outlineColor = Color.red; public Color OutlineColor { get { return outlineColor; } set { outlineColor = value; } } Material outlineMaterial; Material silhouetteMaterial; Camera mCamera; CommandBuffer renderCommand; void Awake() { outlineMaterial = new Material(outlineShader); silhouetteMaterial = new Material(silhouetteShader); renderCommand = new CommandBuffer(); renderCommand.name = "Render Solid Color Silhouette"; mCamera = GetComponent<Camera>(); } void OnEnable() { //顺序将渲染任务加入renderCommand中 renderCommand.ClearRenderTarget(true, true, Color.clear); for (int i = 0; i < silhouettes.Length; ++i) { renderCommand.DrawRenderer(silhouettes[i], silhouetteMaterial); } } void OnDisable() { renderCommand.Clear(); } void OnDestroy() { renderCommand.Clear(); } void OnRenderImage(RenderTexture src, RenderTexture dest) { //1. Draw Solid Color Silhouette silhouetteMaterial.SetColor("_Color", outlineColor); RenderTexture mSolidSilhouette = RenderTexture.GetTemporary(Screen.width, Screen.height); Graphics.SetRenderTarget(mSolidSilhouette); Graphics.ExecuteCommandBuffer(renderCommand); //2. Downscale 4x RenderTexture mBlurSilhouette = RenderTexture.G
CommandBuffer提供了一系列高级渲染接口,包括参数设置SetXXX和绘制DrawRenderer/DrawMesh/Blit。在代码中,我在OnEnable中定义了CommandBuffer需要执行的渲染任务,在OnRenderImage中调用Graphic.ExcuteCommandBuffer来执行这一组定义好的渲染任务,将目标物体渲染到指定的RenderTarget上。查看一下FrameDebug(这个东西应该是新Unity里最好用的新功能了!尤其是对图像后处理这一块,定位渲染问题方便多了)

在RenderBlurOutline后处理渲染组中,首先执行了我定义的CommandBuffer“Render Solid Color Silhouette”,这个名字在CommandBuffer.name中定义。这组任务中执行了Clear,Draw Mesh,对应了我在创建CommandBuffer时的设置。然后执行了4步Draw GL分别对应代码OnRenderImage中执行的Graphic.Blit。
在定义渲染任务时,使用到的接口是DrawRenderer(Renderer, Material),这就相当于是使用replacementShader,使用一个新的材质去替代Renderer上原有的材质对其进行渲染,不过由于提供的是Material,这也就意味着可以通过Material去设置一些渲染参数,而无须使用全局的ShaderVariable。不过代码中我仍然是使用了RenderCommand.SetGlobalColor来进行颜色设置,这等同于正常的渲染中使用Shader.SetXX。
另外一个渲染接口是DrawMesh,与DrawRenderer不同的是你还要额外提供一个旋转矩阵,这个矩阵对应的是Unity shader中_Object2World变量,而DrawRenderer则无须设置,使用的就是该物体正常渲染时的转换矩阵。下面给出对应使用的shader代码:
Shader "Custom/SolidColor" { SubShader { Tags { "RenderType"="Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct v2f { float4 pos : SV_POSITION; }; v2f vert( appdata_base v ) { v2f o; float4 vec = mul(_Object2World, v.vertex); o.pos = mul(UNITY_MATRIX_VP, vec); return o; } fixed4 g_SolidColor; fixed4 frag(v2f i) : SV_Target { return g_SolidColor; } ENDCG } } }
这个shader很简单就是单色渲染,需要注意的就是vs中先乘了 _Object2World再乘以 UNITY_MATRIX_VP,我测试的时候发现MVP是不能直接用的,在OnPostRender或OnRenderImage中执行CommandBuffer均可保证UNITY_MATRIX_VP是有效的,但是在OnPreRender就不行了,Unity提供CommandBuffer接口主要目的应该是方便在Unity渲染流程的各个阶段方便的插入用户自定义的渲染任务,其渲染操作本身的执行在Graphic接口中均有对应的实现,除了OnPostRender、OnPreRender和OnRenderImage这几个摄像机渲染阶段外,还可以在OnWillRenderObject中调用。另外除了Graphics.ExcuteCommandBuffer外,Camera.AddCommandBuffer也可以对其进行执行。
最后给出Unity官方博客中对CommandBuffer的介绍,里面还给了示例工程,提供了另外几个CommandBuffer的使用方式。extending unity 5 render pipeline - command buffers
原文地址:http://blog.csdn.net/sparrowfc/article/details/44975287