通过Universal Render Pipeline获得精美,可扩展和高性能的图形

Universal Render Pipeline is a powerful, ready-to-use solution with a full suite of artist tools for content creation. You should use this rendering pipeline if you want to make a game that has full Unity platform reach with best-in-class visual quality and performance. We covered the benefits of the Universal Render pipeline in this blog post. In this blog post, we’ll dive into how the Universal Render Pipeline was used to create the vertical slice Boat Attack demo.  

Universal Render Pipeline是一种功能强大且易于使用的解决方案,带有一整套用于内容创建的艺术家工具。 如果您想制作一款具有一流的视觉质量和性能的,具有完整Unity平台功能的游戏,则应使用此渲染管道。 我们在此 博客文章 中介绍了通用渲染管道的好处 。 在此博客文章中,我们将深入探讨如何使用Universal Render Pipeline创建垂直切片Boat Attack演示。

We first created the Boat Attack demo to help us validate and test the Universal Render Pipeline (which, at the time, was known as the Lightweight Render Pipeline). Producing a vertical slice as part of our development process was also an exercise in using real-world production processes in our feature development.

我们首先创建了 Boat Attack演示, 以帮助我们验证和测试 Universal Render Pipeline (在当时被称为“轻量级渲染管道”)。 在我们的功能开发中使用真实的生产过程也是在我们的开发过程中生产垂直切片的一种练习。

We have upgraded the Boat Attack demo considerably since we first created it. It now uses many of the Universal Render Pipeline’s new graphical features, along with recent Unity features such as the C# Job System, Burst compiler, Shader Graph, Input System, and more.

自从我们首次创建以来,我们已经对Boat Attack演示进行了相当大的升级。 现在,它使用了Universal Render Pipeline的许多新图形功能,以及最新的Unity功能,例如 C#作业系统Burst编译器Shader GraphInput System等。

You can download the Boat Attack demo now, and start using it today with Unity 2019.3.

您可以立即下载 Boat Attack演示 ,并立即从Unity 2019.3。开始使用。

演示 (The Demo)

The Boat Attack demo is a small vertical slice of a boat-racing game. It is playable and we are continually adjusting it to take full advantage of the latest Unity features.

划船比赛演示是划船比赛的垂直小片段。 它是可播放的,我们会不断对其进行调整,以充分利用Unity的最新功能。

The demo is designed to work well on a wide variety of platforms: mid- to high-range mobile devices, all current consoles and standalone apps. We demonstrated Boat Attack live at Unite Copenhagen 2019 on a range of devices, from the iPhone 7 to the PlayStation 4.

该演示旨在在各种平台上良好运行:中高端移动设备,所有当前控制台和独立应用程序。 我们在Unite Copenhagen 2019上使用从iPhone 7到PlayStation 4。

To use the demo, we suggest you install the latest version of Unity 2019.3, and then grab the project from GitHub (make sure to read the readme for usage instructions).

要使用该演示,我们建议您安装最新版本的Unity 2019.3,然后从 GitHub 获取项目 (请务必阅读自述文件以获取使用说明)。

着色器图 (Shader Graph)

Shader Graph is an artist-friendly interface for creating shaders. It’s a powerful prototyping tool for technical artists. We used Shader Graph to create some of the unique shading effects in the Boat Attack demo.

Shader Graph 是用于创建着色器的艺术家友好界面。 对于技术美术师来说,这是一个强大的原型制作工具。 我们使用着色器图在Boat Attack演示中创建了一些独特的着色效果。

Using Shader Graph allowed us to create great shading effects, and then painlessly maintain them across many versions of the Lightweight Render Pipeline and the Universal Render Pipeline.

使用“ Shader Graph”使我们能够创建出色的着色效果,然后在轻量级渲染管线和通用渲染管线的许多版本中轻松维护它们。

The cliff shader in Boat Attack demonstrates the effects you can achieve using mesh data – it’s easy to get data from a mesh in Shader Graph. We use the normal vector of the mesh to draw grass on the parts of the cliff face that are flat and facing upwards, and we use the world space height of the mesh to ensure that cliffs and rocks close to the water level will not have grass.

Boat Attack中的悬崖着色器演示了使用网格数据可以实现的效果–在Shader Graph中很容易从网格中获取数据。 我们使用网格的法线矢量在平坦且朝上的悬崖面上的部分上绘制草,并使用网格的世界空间高度来确保靠近水位的悬崖和岩石不会草。

通过Universal Render Pipeline获得精美,可扩展和高性能的图形

From left to right: Y height mask, Y normal mask, height + normal mask remapped, final shader.

从左到右:Y高度蒙版,Y常规蒙版,高度+常规蒙版重新映射,最终着色器。

植被遮荫 (Vegetation shading)

The vegetation in Boat Attack was initially a custom vertex/fragment shader, but this was painful to maintain when the render pipeline was in early development and code was changing frequently. Recreating the shader in Shader Graph let us take advantage of Shader Graph’s easy upgradeability.

Boat Attack中的植被最初是一个自定义的顶点/片段着色器,但是当渲染管道处于早期开发阶段并且代码频繁更改时,维护起来很痛苦。 在Shader Graph中重新创建着色器使我们可以利用Shader Graph的易升级性。

This Shader Graph effect is based on an implementation from Tiago Sousa of Crytek, which makes great use of vertex colors to control wind animation via vertex displacement. In Boat Attack, we created a Sub-graph to house all the required nodes needed for calculating the wind effect. The Sub-graph contains nested Sub-graphs, which are a collection of utility graphs that perform repeating math calculations.

此着色器图效果基于 Crytek的Tiago Sousa 的实现 ,该实现充分利用了顶点颜色来通过顶点位移控制风的动画。 在Boat Attack中,我们创建了一个子图来容纳计算风效应所需的所有必需节点。 子图包含嵌套的子图,这些子图是执行重复数学计算的实用程序图的集合。

Individual vertex animations and their masks. From left to right: main bending from distance to origin, leaf edge from vertex color Red channel, and branches from vertex color Blue using vertex color Green channel for phase offset.

各个顶点动画及其蒙版。 从左到右:从距离到原点的主弯曲,从顶点颜色红色通道的叶边缘,从顶点颜色蓝色的分支,使用顶点颜色绿色的通道进行相位偏移。

Another big part of creating believable vegetation is subsurface scattering (SSS), which is currently not available with the Universal Render Pipeline. However, to create an SSS-like effect, you can use Shader Graph’s custom function node to retrieve lighting information from Universal Render Pipeline to create your own SSS-like effect.

创建可信植被的另一个重要部分是地下散射(SSS),这在Universal Render Pipeline中目前不可用。 但是,要创建类似SSS的效果,可以使用Shader Graph的自定义功能节点从Universal Render Pipeline中检索照明信息,以创建自己的类似SSS的效果。

通过Universal Render Pipeline获得精美,可扩展和高性能的图形

Node layout. The SSS Mask is made from the vertex color Green (leaf phase) and the albedo texture map.

节点布局。 SSS遮罩由顶点颜色绿色(叶相)和反照率纹理贴图制成。

The custom function node gives you a lot of creative freedom. You can read up on custom rendering techniques here, or simply grab the code for the node in the Boat Attack repository to try out your own custom lighting ideas.

自定义功能节点为您提供了很多创作自由。 您可以在 此处 阅读自定义渲染技术 ,或者直接在Boat Attack存储库中获取该节点的代码,以尝试自己的自定义照明想法。

通过Universal Render Pipeline获得精美,可扩展和高性能的图形

From left to right: without SSS, SSS only, final shader.

从左到右:没有SSS,仅SSS,最终着色器。

船定制 (Boat customization)

The boats needed to have multiple variations of colors. In Substance Painter, two livery masks were painted and stored in a packed texture containing Metallic (red), Smoothness (green), Livery 1 (blue) and Livery 2 (alpha). Using the masks via Shader Graph, we can selectively apply coloring to these masked areas.

船需要具有多种颜色。 在Substance Painter中,绘制了两个涂装口罩并将其存储在包含金属(红色),平滑度(绿色),Livery 1(蓝色)和Livery 2(alpha)的填充纹理中。 通过“明暗器图形”(Shader Graph)使用遮罩,我们可以选择性地将颜色应用于这些遮罩区域。

通过Universal Render Pipeline获得精美,可扩展和高性能的图形

An overview of how the boats are colored. Using overlay blending allows subtle coloring to come through the base albedo map.

船只颜色的概述。 使用叠加混合可以使基础反照率图产生微妙的着色。

通过Universal Render Pipeline获得精美,可扩展和高性能的图形

The node layout in Shader Graph, wrapped into a Sub-graph for easy use in the parent RaceBoats graph.

Shader Graph中的节点布局被包装到Sub-graph中,以便在父RaceBoats图中轻松使用。

房屋 (Houses)

Boat Attack covers a full day/night cycle. To enhance this illusion, we created a Shader Graph for the windows of the buildings throughout the level. The Shader Graph lights up the windows at dusk and switches them off at dawn.

泛舟攻击涵盖了一个完整的昼/夜周期。 为了增强这种错觉,我们为整个关卡中建筑物的窗户创建了一个着色器图。 着色器图在黄昏时照亮窗户,在黎明时将其关闭。

We achieved this using a simple emission texture that was mapped to a day/night value. We added an effect to slightly randomize the order, using the objects’ positions, so that the houses would light up at different times.

我们使用映射到白天/夜晚值的简单发射纹理实现了这一点。 我们添加了一种效果,可以使用对象的位置稍微随机化顺序,以便房屋在不同的时间亮起。

通过Universal Render Pipeline获得精美,可扩展和高性能的图形

The node map that enables random emissions.

启用随机发射的节点图。

乌云 (Clouds)

Now that we have added changing lighting to Boat Attack, a simple high-dynamic-range imaging (HDRI) skybox is no longer sufficient. The clouds should be dynamically lit by the lighting in the Scene.

现在,我们已经在Boat Attack中增加了更改照明的功能,简单的高动态范围成像(HDRI)天空盒已不再足够。 云应该由场景中的照明动态地照亮。

But rendering big puffy clouds in real-time is demanding, especially with the need to run on mobile hardware. Because we don’t need to see the clouds from many angles, we decided to use cards with textures to save on performance.

但是,实时渲染大型浮云的要求很高,尤其是需要在移动硬件上运行时。 因为我们不需要从多个角度看云,所以我们决定使用具有纹理的卡来节省性能。

通过Universal Render Pipeline获得精美,可扩展和高性能的图形

The whole current graph for rendering the clouds.

用于渲染云的整个当前图形。

Shader Graph was crucial in prototyping the look. We baked out some volumetric cloud data from Houdini, and created fully custom lighting in Shader Graph. These clouds are still a work in progress, but they prove that a wide range of surfaces can be created with the node-based editor.

Shader Graph对原型外观至关重要。 我们烘焙了来自Houdini的一些体积云数据,并在Shader Graph中创建了完全自定义的照明。 这些云仍在开发中,但是它们证明可以使用基于节点的编辑器来创建各种曲面。

从API渲染以实现无缝的平面反射 (Rendering from API for seamless Planar Reflections)

Unity’s goal with Scriptable Render Pipelines was to allow users to customize rendering code, instead of hiding it in a black box. Rather than simply opening up our existing rendering code, we pushed our rendering tech with new APIs and hardware in mind. 

Unity使用脚本化渲染管道的目标是允许用户自定义渲染代码,而不是将其隐藏在黑盒中。 我们不仅开放现有的渲染代码,还考虑了新的API和硬件来推动渲染技术。

The Universal Render Pipeline lets you extend its out-of-the-box rendering capabilities with your own C#. It exposes 4 hooks:

通用渲染管线允许您使用自己的C#扩展其即用的渲染功能。 它暴露了4个钩子:

  • RenderPipelineManager.beginFrameRendering

    RenderPipelineManager.beginFrameRendering

  • RenderPipelineManager.beginCameraRendering

    RenderPipelineManager.beginCameraRendering

  • RenderPipelineManager.endCameraRendering

    RenderPipelineManager.endCameraRendering

  • RenderPipelineManager.endFrameRendering

    RenderPipelineManager.endFrameRendering

These hooks let you easily run your own code before rendering the Scene or before rendering certain Cameras. In Boat Attack, we used these hooks to implement Planar Reflections by rendering the Scene into a texture before the main frame is rendered.

这些挂钩可让您在渲染场景之前或在渲染某些摄像机之前轻松地运行自己的代码。 在《快艇攻击》中,我们使用这些钩子在渲染主框架之前通过将场景渲染为纹理来实现“平面反射”。

1
2
3
4
private void OnEnable()
{
   RenderPipelineManager.beginCameraRendering += ExecutePlanarReflections;
}
1
2
3
4
private void OnEnable ( )
{
   RenderPipelineManager . beginCameraRendering += ExecutePlanarReflections ;
}

Because this is a callback we subscribe to, we also unsubscribe from it in OnDisable.

因为这是我们订阅的回调,所以我们也可以在OnDisable中取消订阅它。

Here we can see the entry point in the Planar Reflection script. This code lets us call a custom method every time Universal Render Pipeline goes to render a camera. The method we call is our ExecutePlanarReflections method:

在这里,我们可以看到“平面反射”脚本中的入口点。 每次Universal Render Pipeline渲染照相机时,此代码都使我们可以调用自定义方法。 我们调用的方法是ExecutePlanarReflections方法:

1
2
3
4
public void ExecutePlanarReflections(ScriptableRenderContext context, Camera camera)
{
    //rendering code....
}
1
2
3
4
public void ExecutePlanarReflections ( ScriptableRenderContext context , Camera camera )
{
     //rendering code....
}

Because we are using the [beginCameraRendering] callback, our method must take a [ScriptableRenderContext] and a [Camera] as its parameters. This data is piped through with the callback, and it will let us know which Camera is about to render.

因为我们使用[beginCameraRendering]回调,所以我们的方法必须采用[ScriptableRenderContext]和[Camera]作为其参数。 此数据通过回调传递给我们,它将使我们知道要渲染的Camera。

For the most part, the code here is the same code as you would normally use to implement planar reflections: you are dealing with cameras and matrices. The only difference is that Universal Render Pipeline provides a new API for rendering a camera. 

在大多数情况下,这里的代码与通常用于实现平面反射的代码相同:正在处理相机和矩阵。 唯一的区别是Universal Render Pipeline提供了用于渲染摄影机的新API。

The full method for implementing planar reflections is as follows:

实现平面反射的完整方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
}

Here we use the new [UniversalRenderPipeline.RenderSingleCamera()] method to render a given camera. In this case, the camera is our Planar Reflection Camera.

在这里,我们使用新的[UniversalRenderPipeline.RenderSingleCamera()]方法来渲染给定的相机。 在这种情况下,相机就是我们的平面反射相机。

Since this camera renders to a texture (which we set using [Camera.targetTexture]), we now get a RenderTexture we can use in our water shading later in the rendering. Check out the whole PlanarReflection script on the GitHub page.

由于此摄像机渲染为纹理(我们使用[Camera.targetTexture]进行了设置),因此我们现在获得了RenderTexture,可以在渲染的后期进行水阴影处理。 在 GitHub页面 上检查整个PlanarReflection脚本 。

通过Universal Render Pipeline获得精美,可扩展和高性能的图形

Planar reflection composition. From left to right: raw planar reflection camera output, fresnel darkening and normal offsetting, final water shader, water shader without planar reflections.

平面反射组成。 从左到右:原始平面反射摄像机输出,菲涅耳变暗和法线偏移,最终的水着色器,无平面反射的水着色器。

These callbacks are used here to invoke some rendering, but they can be used for several things. For example, we also use them to disable shadows on the Planar Reflection Camera, or choose which Renderer to use for a camera. Rather than hard coding the behavior in the Scene or a Prefab, using an API allows you to handle more complexity with greater control.

这些回调在这里用于调用某些呈现,但是它们可以用于几件事。 例如,我们还使用它们来禁用“平面反射相机”上的阴影,或选择用于相机的渲染器。 使用API​​无需对场景或预制件中的行为进行硬编码,而是可以使用更强大的控制来处理更多的复杂性。

注入自定义渲染通道以产生特殊效果 (Injecting Custom Render Passes for specialized effects)

In the Universal Render Pipeline, rendering is based upon ScriptableRenderPasses. These are instruction sets on what and how to render. Many ScriptableRenderPasses are queued together to create what we call a ScriptableRenderer.

在“通用渲染管道”中,渲染基于ScriptableRenderPasses。 这些是有关如何渲染以及如何渲染的指令集。 许多ScriptableRenderPasses排队在一起以创建我们所谓的ScriptableRenderer。

Another part of Universal Render Pipeline is ScriptableRendererFeatures. These are essentially data containers for custom ScriptableRenderPasses and can contain any number of passes inside along with any type of data attached.

通用渲染管道的另一部分是ScriptableRendererFeatures。 这些本质上是用于自定义ScriptableRenderPasses的数据容器,并且可以在内部包含任意数量的传递以及附加的任何类型的数据。

Out of the box we have two ScriptableRenderers, the ForwardRenderer and the 2DRenderer. ForwardRenderer supports injecting ScriptableRendererFeatures.

开箱即用,我们有两个ScriptableRenderer,即ForwardRenderer和2DRenderer。 ForwardRenderer支持注入ScriptableRendererFeatures。

To make it easier to create ScriptableRendererFeatures, we added the ability to start with a template file, much like we do for C# MonoBehaviour scripts. You can simply right-click in the Project view and choose [Create/Rendering/Universal Pipeline/Renderer Feature]. This creates a template to help you get started. Once created, you can add your ScriptableRendererFeature to the Render Feature list on the ForwardRendererData assets.

为了使创建ScriptableRendererFeatures更加容易,我们添加了从模板文件开始的功能,就像我们对C#MonoBehaviour脚本所做的一样。 您只需在“项目”视图中右键单击并选择[创建/渲染/通用管道/渲染器功能]。 这将创建一个模板来帮助您入门。 创建完成后,您可以将ScriptableRendererFeature添加到ForwardRendererData资产的“渲染功能”列表中。

In the Boat Attack demo, we used ScriptableRendererFeatures to add two extra rendering passes for the water rendering: one for caustics and one called WaterEffects.

在Boat Attack演示中,我们使用ScriptableRendererFeatures为水渲染添加了两个额外的渲染通道:一个用于腐蚀,另一个称为WaterEffects。

焦散 (Caustics)

The Caustics ScriptableRendererFeature adds a pass that renders a custom caustics shader over the scene between the Opaque and Transparent passes. This is done by rendering a large quad aligned with the water to avoid rendering all the pixels that might be in the sky. The quad follows the camera but is snapped to the water height, and the shader is additively rendered over what’s on screen from the opaque pass.

Caustics ScriptableRendererFeature添加了一个通道,该通道在“不透明”和“透明”通道之间的场景上渲染自定义的碱度着色器。 这是通过渲染与水对齐的大四边形来完成的,以避免渲染天空中可能存在的所有像素。 四边形跟随摄像机,但被捕捉到水的高度,并且从不透明的通道开始,将着色器叠加渲染到屏幕上的内容上。

通过Universal Render Pipeline获得精美,可扩展和高性能的图形

Caustic Render Pass compositing. From left to right: depth texture, world space position reconstruction from depth, caustics texture mapped with world space position, and final blending with Opaque pass.

苛刻的渲染过程合成。 从左到右:深度纹理,从深度开始的世界空间位置重构,使用世界空间位置映射的焦散纹理以及与不透明通道的最终混合。

Using [CommandBuffer.DrawMesh], you can draw the quad, supply a matrix to position the mesh (based on water and camera coordinates), and set up the caustics material. The code looks like this:

使用[CommandBuffer.DrawMesh],可以绘制四边形,提供矩阵以定位网格(基于水和相机坐标),并设置焦散材料。 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
   }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 ) ;
   }
}

水效应 (Water effects)

Split view of the WaterFXPass in action. Left, the final render; right, a debug view showing only the result of the pass on the water.

实际使用中的WaterFXPass的拆分视图。 左为最终渲染; 右边是一个调试视图,仅显示水上传递的结果。

The WaterFXPass is a bit more complex. The goal for this effect was to have objects affect the water, such as making waves and foam. To achieve this, we render certain objects to an offscreen RenderTexture, using a custom shader that is able to write different information into each channel of the texture: a foam mask into red channel, normal offset X and Z into green and blue, and finally water displacement in the alpha channel.

WaterFXPass比较复杂。 该效果的目标是使物体影响水,例如产生波浪和泡沫。 为此,我们使用自定义着色器将某些对象渲染到屏幕外的RenderTexture,该着色器能够将不同的信息写入纹理的每个通道:将泡沫遮罩写入红色通道,将法线偏移X和Z写入绿色和蓝色,最后alpha通道中的水置换。

通过Universal Render Pipeline获得精美,可扩展和高性能的图形

WaterFXPass compositing. From left to right: final output, the green and blue channels used to create world space normals, the red channel used for a foam mask, and the alpha channel used for creating water displacement (red positive, black no change, blue negative).

WaterFXPass合成。 从左到右:最终输出,用于创建世界空间法线的绿色和蓝色通道,用于泡沫面罩的红色通道,用于创建水置换的alpha通道(红色正,黑色不变,蓝色负)。

First, we need a texture to render into, which we create at half resolution. Next, we create a filter for any transparent objects that have the shader pass called WaterFX. After this, we use [ScriptableRenderContext.DrawRenderers] to render the objects into the scene. The final code looks like this:

首先,我们需要渲染一个纹理,并以一半的分辨率创建它。 接下来,我们为具有着色器通行证的所有透明对象(称为WaterFX)创建一个过滤器。 此后,我们使用[ScriptableRenderContext.DrawRenderers]将对象渲染到场景中。 最终代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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);
   }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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 ) ;
   }
}

Both of these ScriptableRenderPasses live in a single ScriptableRendererFeature. This feature contains a [Create()] function that you can use to set up resources and also pass along settings from the UI. Since they are always used together when rendering water, a single feature can add them to the ForwardRendererData. You can see the full code on Github.

这两个ScriptableRenderPasses都生活在一个ScriptableRendererFeature中。 此功能包含[Create()]函数,可用于设置资源以及​​从UI传递设置。 由于它们在渲染水时总是一起使用,因此单个功能可以将它们添加到ForwardRendererData中。 您可以 在Github上 查看 完整的代码

Future plans

未来的计划

We will continue to update this project throughout the Unity 2019 cycle including 19.4LTS. As of Unity 2020.1, we intend to maintain the project to make sure it runs, but we will not add any new features.

我们将在Unity 2019周期(包括19.4LTS)中继续更新此项目。 从Unity 2020.1开始,我们打算维护项目以确保其运行,但是我们不会添加任何新功能。

Some of the planned improvements include:

计划中的一些改进包括:

  • Finish day/night cycle (this requires more features to be integrated into Universal Render Pipeline to reduce the need for customization)

    完成昼/夜周期(这需要将更多功能集成到Universal Render Pipeline中,以减少定制需求)

  • Refine Water UX/UI 

    精制水UX / UI

  • Implement Imposters

    实施冒名顶替者

  • Continue code cleanup and performance tweaks

    继续代码清理和性能调整

Useful links

有用的链接

Boat Attack GitHub repository 

Boat Attack GitHub存储库  

Full 2019.3 project link (if you don’t want to use GitHub)

完整的2019.3项目链接 (如果您不想使用GitHub)

Universal Render Pipeline manual

通用渲染管线手册

通用渲染管线和高清渲染管线 (Universal Render Pipeline and High Definition Render Pipeline)

The Universal Render Pipeline does not replace or encompass the High Definition Render Pipeline (HDRP). The Universal Render Pipeline aims to be the future default render pipeline for Unity. Develop once, deploy everywhere. It is more flexible and extensible, it delivers higher performance than the built-in render pipeline, and it is scalable across platforms. It also has fantastic graphics quality. 

通用渲染管道不替代或包含 高清渲染管道 (HDRP)。 通用渲染管线 旨在成为Unity未来的默认渲染管线。 一次开发,随处部署。 它具有更高的灵活性和可扩展性,与内置的渲染管道相比,具有更高的性能,并且可以跨平台进行扩展。 它还具有出色的图形质量。

HDRP delivers state-of-the-art graphics on high-end platforms. HDRP is best to use if your goal is more targeted – pushing graphics on high-end hardware, delivering performant powerful high-fidelity visuals. 

HDRP在高端平台上提供最先进的图形。 如果您的目标更有针对性,则HDRP是最佳的选择-将图形推入高端硬件,提供性能强大的高保真视觉效果。

You should choose which render pipeline to use based on the feature and platform requirements of your project.

您应该根据项目的功能和平台要求选择要使用的渲染管道。

开始使用通用渲染管道 (Start using the Universal Render Pipeline)

You can start taking advantage of all the production-ready features and performance benefits today. Upgrade your projects using the upgrade tooling, or start a new project using our Universal Project Template from the Unity Hub.

您现在就可以开始利用所有生产就绪的功能和性能优势。 使用升级工具升级项目,或使用Unity Hub中的通用项目模板开始新项目。

Please send us feedback in the Universal Render Pipeline forum!

请在“通用渲染管线” 论坛中 向我们发送反馈

翻译自: https://blogs.unity3d.com/2020/02/10/achieve-beautiful-scalable-and-performant-graphics-with-the-universal-render-pipeline/