Notifications
Article
Unity《Boat Attack》Demo幕后揭秘(下)
Updated a month ago
1.9 K
18
针对Unity 发布的《Boat Attack》Demo中的植被、云朵、房屋等元素的制作过程进行了详细的介绍。今天我们将继续「通过API渲染来实现无缝的平面反射」这个话题与大家分享余下的创作经历。
作者:Andre McGrail,2020年2月10日
原文链接:https://blogs.unity3d.com/2020/02/10/achieve-beautiful-scalable-and-performant-graphics-with-the-universal-render-pipeline/

接上期,Unity《Boat Attack》Demo幕后揭秘(内附源码下载;上)

https://connect.unity.com/p/unity-boat-attack-demomu-hou-jie-mi-nei-fu-yuan-ma-xia-zai

应用平面反射的完整方法如下:

private void ExecutePlanarReflections(ScriptableRenderContext context, Camera camera) { // we dont want to render planar reflections in reflections or previews if (camera.cameraType == CameraType.Reflection || camera.cameraType == CameraType.Preview) return; UpdateReflectionCamera(camera); // create reflected camera PlanarReflectionTexture(camera); // create and assign RenderTexture var data = new PlanarReflectionSettingData(); // save quality settings and lower them for the planar reflections beginPlanarReflections?.Invoke(context, m_ReflectionCamera); // callback Action for PlanarReflection UniversalRenderPipeline.RenderSingleCamera(context, m_ReflectionCamera); // render planar reflections data.Restore(); // restore the quality settings Shader.SetGlobalTexture(planarReflectionTextureID, m_ReflectionTexture); // Assign texture to water shader }

这里我们使用了下述新的方法来渲染了平面反射镜头。

这里我们使用了新的[UniversalRenderPipeline.RenderSingleCamera()]方法来渲染了平面反射镜头。
由于使用了一个纹理(通过[Camera.targetTexture]设定)来渲染镜头,我们还取得了可用于之后水体渲染的渲染纹理RenderTexture。你可以在Github页面上查看完整的PlanarReflection脚本。
平面反射的构成。自左到右:原始平面反射镜头的输出,菲涅尔镜头调暗和法线偏移后的效果,最终水体着色,无平面反射的水体着色。
这里的回调主要是用于触发渲染,但它们也能有其他用处。比如,我们还能借其禁用平面反射镜头上的阴影。使用API可以让我们处理更加复杂的需求,有更全面的控制,若将行为硬编码进场景或预制件中,这是做不到的。

插入自定义渲染通道来实现特殊效果

在通用渲染管线中,渲染是基于ScriptableRenderPasses(可编程渲染通道)完成的,后者是设定渲染对象和方法的各种指令。许多的ScriptableRenderPasses排列起来后,便成为了ScriptableRenderer(可编程渲染器)。
另一部分是ScriptableRendererFeatures(可编程渲染器功能)。
这部分是用于储存自定义ScriptableRenderPasses数据的容器,可储存的数量不限,且支持任何类型的数据。
目前有两种ScriptableRenderer可开箱即用,ForwardRenderer(前向渲染器)和2DRenderer(2D渲染器)。ForwardRenderer支持插入不同的ScriptableRendererFeatures。
为了让ScriptableRendererFeatures的创建更加简便,我们加入了模板供用户使用,模板和C# MonoBehaviour脚本中的模板类似。你可以在项目视图中右击选择“Create→Rendering→Universal Pipeline→Renderer Feature”来创建模板。创建完成后,就可以在ForwardRendererData(前向渲染器数据)资源的Render Feature(渲染功能)列表中添加自己的ScriptableRendererFeature了。
在《Boat Attack》演示项目中,我们用ScriptableRendererFeatures为水体渲染添加了两种额外的渲染通道:一种用于焦散效果,另一种则是WaterEffects(水体效果)。

焦散效果

用于焦散的ScriptableRendererFeature为场景添加的渲染通道,可以在Opaque(不透明)和Transparent(透明)通道之间渲染一种自定义的焦散着色效果。通道会渲染一片与水面平行的大四边形,防止渲染到空中的像素。四边形随镜头移动,被固定在水面的高度上,然后着色器再叠加渲染屏幕中不透明通道的数据。
焦散渲染通道的构成。从左到右:深度纹理,基于深度信息重建的场景空间位置,根据场景空间位置贴上的焦散纹理,与不透明通道混合起来的最终效果。
你可以使用“CommandBuffer.DrawMesh”来绘制四边形,组成矩阵用于放置网格(位置由水体和镜头的坐标决定),然后设置起焦散材质。代码如下:
public class WaterCausticsPass : ScriptableRenderPass { const string k_RenderWaterCausticsTag = "Render Water Caustics"; public Material m_WaterCausticMaterial; public Mesh m_mesh; public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { var cam = renderingData.cameraData.camera; if(cam.cameraType == CameraType.Preview) // Stop the pass rendering in the preview return; // Create the matrix to position the caustics mesh. Vector3 position = cam.transform.position; position.y = 0; // TODO should read a global 'water height' variable. Matrix4x4 matrix = Matrix4x4.TRS(position, Quaternion.identity, Vector3.one); // Setup the CommandBuffer and draw the mesh with the caustic material and matrix CommandBuffer cmd = CommandBufferPool.Get(k_RenderWaterCausticsTag); cmd.DrawMesh(m_mesh, matrix , m_WaterCausticMaterial, 0, 0); context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } }

水体效果

WaterFXPass实际效果的分屏演示。左边,最终渲染效果;右边,调试视图,水体上只显示了该通道的效果。
WaterFXPass要稍微复杂一点。我们制作该效果的目的是让对象能影响到水体,制造出波浪和浮沫。为此,我们将部分对象渲染到了一个看不到的RenderTexture上,使用了一个自定义着色器将不同的渲染信息写入纹理的通道上:在红色通道中将浮沫遮到水面上,X和Z轴的法线偏移分别在绿色和蓝色通道上,最后将水体错位效果放在了不透明度通道上。
首先,我们以一半的分辨率制作了渲染纹理。接着,创建一个过滤器,来过滤出含有WaterFX着色通道的透明对象。
然后,使用“ScriptableRenderContext.DrawRenderers”
将对象渲染进场景。最终代码如下:
class WaterFXPass : ScriptableRenderPass { const string k_RenderWaterFXTag = "Render Water FX"; private readonly ShaderTagId m_WaterFXShaderTag = new ShaderTagId("WaterFX"); private readonly Color m_ClearColor = new Color(0.0f, 0.5f, 0.5f, 0.5f); //r = foam mask, g = normal.x, b = normal.z, a = displacement private FilteringSettings m_FilteringSettings; RenderTargetHandle m_WaterFX = RenderTargetHandle.CameraTarget; public WaterFXPass() { m_WaterFX.Init("_WaterFXMap"); // only wanting to render transparent objects m_FilteringSettings = new FilteringSettings(RenderQueueRange.transparent); } // Calling Configure since we are wanting to render into a RenderTexture and control cleat public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor) { // no need for a depth buffer cameraTextureDescriptor.depthBufferBits = 0; // Half resolution cameraTextureDescriptor.width /= 2; cameraTextureDescriptor.height /= 2; // default format TODO research usefulness of HDR format cameraTextureDescriptor.colorFormat = RenderTextureFormat.Default; // get a temp RT for rendering into cmd.GetTemporaryRT(m_WaterFX.id, cameraTextureDescriptor, FilterMode.Bilinear); ConfigureTarget(m_WaterFX.Identifier()); // clear the screen with a specific color for the packed data ConfigureClear(ClearFlag.Color, m_ClearColor); } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { CommandBuffer cmd = CommandBufferPool.Get(k_RenderWaterFXTag); using (new ProfilingSample(cmd, k_RenderWaterFXTag)) // makes sure we have profiling ability { context.ExecuteCommandBuffer(cmd); cmd.Clear(); // here we choose renderers based off the "WaterFX" shader pass and also sort back to front var drawSettings = CreateDrawingSettings(m_WaterFXShaderTag, ref renderingData, SortingCriteria.CommonTransparent); // draw all the renderers matching the rules we setup context.DrawRenderers(renderingData.cullResults, ref drawSettings, ref m_FilteringSettings); } context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } public override void FrameCleanup(CommandBuffer cmd) { // since the texture is used within the single cameras use we need to cleanup the RT afterwards cmd.ReleaseTemporaryRT(m_WaterFX.id); } }
这两个ScriptableRenderPasses通道全放在了一个ScriptableRendererFeature中。该功能包含了一个“Create()”函数,可用于设置资源,从UI传入设置信息。在渲染水体时,两个通道一般都一起使用,所以我们加入了将两个通道同时加进ForwardRendererData的功能。完整的代码在Github页面上。
https://github.com/Verasl/BoatAttack/blob/release/2019.3/Packages/com.verasl.water-system/Scripts/Rendering/WaterSystemFeature.cs

未来计划

在整个Unity 2019版本周期中,包括19.4LTS版,我们将持续更新项目。而自Unity 2020.1起,我们将以维护项目为主,确保其能正常运行,但不会再添加新的内容。
计划包括:
  • 进一步完善日/夜循环(在通用渲染管线中整合进更多的功能,减少自定义的需要)。
  • 打磨水体部分的UX/UI
  • 应用“Imposter”假体
  • 修整代码、调试性能

常用链接

《Boat Attack》Github代码库
完整的2019.3项目链接(不使用GitHub)
通用渲染管线手册

通用渲染管线并不会取代或包揽高清渲染管线。

通用渲染管线未来将成为Unity默认的渲染管线,“一次开发,到处部署”。它更具灵活性和可拓展性,产出比内置渲染管线性能更高,且图像质量非常出色。
HDRP则专为高端平台产出最顶级的图像。如果项目的目标是希望在高端硬件上突破图像的极限,展示性能强劲、高度清晰的图像,HDRP是最佳的选择。
请根据项目的功能特点和平台要求来选择使用两种渲染管线。

快来尝试通用渲染管线吧

快来体验管线所有成品级的功能和性能增益吧。你可以升级项目到通用渲染管线,或用Unity Hub中通用渲染管线的模板创建新项目。
开发过程中遇到问题?打开Unity Connect App或者Unity编辑器内置群聊工具,在“技术交流”群聊组中提问。
Tags:
Unity China
779
Comments
AKBOT
a month ago
客户端
完整项目百度网盘下载: 链接:https://pan.baidu.com/s/14ai2869fYP2Gq0lG6OwlRA 提取码:e4s5
1
AKBOT
a month ago
客户端
完整项目百度网盘下载:
0
AKBOT
a month ago
客户端
mark2wen能否也发我一份 48129638@qq.com
发你了
0
m
mark2wen
a month ago
AKBOT不想睡哈 发送了
能否也发我一份 48129638@qq.com
0
AKBOT
a month ago
客户端
ghost_woods收到了,谢谢你
不客气
0