前言
Unity2018中引入了可编程渲染管线(Scriptable Render Pipeline,简称SRP),是一种在Unity中通过C#脚本配置和执行渲染的方式。至2018.1版本,Unity中除了默认渲染管线,还提供了轻量级渲染管线(Lightweight Pipeline)和高清晰渲染管线(HD Pipleline)二个渲染管线。当然也支持自定义渲染管线。与高清晰渲染管线相比,轻量级渲染管线的开发已经比较成熟。
这篇文章主要是分析轻量级渲染管线的C#代码都做了哪些工作。
可编程渲染管线能做什么
为了解决仅有一个默认渲染管线,造成的可配置型、可发现性、灵活性等问题。Unity在管线设计的概念上做了转移,决定在C++端保留一个非常小的渲染内核,让C#端可以通过API暴露出更多的选择性,也就是说,Unity会提供一系列的C# API以及内置渲染管线的C#实现;这样一来,一方面可以保证C++端的代码都能严格通过各种白盒测试,另一方面C#端代码就可以在实际项目中调整,有任何问题也可以方便地进行调试。
新的管线对用户而言主要是C# 端的API以及由这些API编写的一系列定制化的内置渲染管线。而在内部实现上,引擎C++端会负责多线程实现性能关键的部分,如上图所示,而C#端负责更高层的渲染指令调度。[1]
可编程渲染管线的使用层设计
用户可以直接使用开源的内置管线,或者在内置管线的基础上进行修改,甚至直接编写定制化的管线。具体使用上渲染管线在工程中会生成特定的Asset,如下图所示,这个Asset序列化了这条管线的一些公共设置变量,并负责在运行时创建实际的渲染上下文;当这个Asset的设置变量在运行时发生变化,引擎会销毁当前上下文然后重新创建管线(这个操作在现有固定管线中无法做到)。
简单的渲染管线示例
可编程渲染管线的使用层设计中,最少需要两个类,一个是渲染管线资源,一个是渲染管线实例。
下面是一个不透明渲染管线的C#代码查看详细:
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
[ExecuteInEditMode]
public class OpaqueAssetPipe : RenderPipelineAsset
{
#if UNITY_EDITOR
[UnityEditor.MenuItem("SRP-Demo/02 - Create Opaque Asset Pipeline")]
static void CreateBasicAssetPipeline()
{
var instance = ScriptableObject.CreateInstance<OpaqueAssetPipe>();
UnityEditor.AssetDatabase.CreateAsset(instance, "Assets/SRP-Demo/2-OpaqueAssetPipe/OpaqueAssetPipe.asset");
}
#endif
protected override IRenderPipeline InternalCreatePipeline()
{
return new OpaqueAssetPipeInstance();
}
}
public class OpaqueAssetPipeInstance : RenderPipeline
{
public override void Render(ScriptableRenderContext context, Camera[] cameras)
{
base.Render(context, cameras);
foreach (var camera in cameras)
{
ScriptableCullingParameters cullingParams;
if (!CullResults.GetCullingParameters(camera, out cullingParams))
continue;
CullResults cull = CullResults.Cull(ref cullingParams, context);
context.SetupCameraProperties(camera);
var cmd = new CommandBuffer();
cmd.ClearRenderTarget(true, false, Color.black);
context.ExecuteCommandBuffer(cmd);
cmd.Release();
var settings = new DrawRendererSettings(camera, new ShaderPassName("BasicPass"));
settings.sorting.flags = SortFlags.CommonOpaque;
var filterSettings = new FilterRenderersSettings(true) { renderQueueRange = RenderQueueRange.opaque };
context.DrawRenderers(cull.visibleRenderers, ref settings, filterSettings);
context.DrawSkybox(camera);
context.Submit();
}
}
}
这里定义了一个继承自RenderPipelineAsset的类,通过这个类创建一个渲染管线实例:继承自RenderPipeline的类。
渲染管线实例中的Render方法是渲染入口点,它需要两个参数,渲染上下文以及一个需要渲染的摄像机列表。
在Render方法中依次进行了剔除、绘制、过滤等操作,通过渲染命令缓冲向渲染上下文发送命令来实现绘制。
轻量级渲染管线
以下内容来自Unity官方在GitHub中的项目,本文将在代码中通过注释来解释渲染管线的功能。
渲染管线资源
渲染管线资源是项目中实际使用的资源,它包含一系列的可调整设置,并通过这些设置创建渲染管线实例,在设置发生变更时重新创建。
#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEditor.ProjectWindowCallback;
#endif
namespace UnityEngine.Experimental.Rendering.LightweightPipeline
{
/// <summary>
/// 阴影级联
/// </summary>
public enum ShadowCascades
{
NO_CASCADES = 0,
TWO_CASCADES,
FOUR_CASCADES,
}
/// <summary>
/// 阴影类型
/// </summary>
public enum ShadowType
{
NO_SHADOW = 0,
HARD_SHADOWS,
SOFT_SHADOWS,
}
/// <summary>
/// 阴影分辨率
/// </summary>
public enum ShadowResolution
{
_256 = 256,
_512 = 512,
_1024 = 1024,
_2048 = 2048,
_4096 = 4096
}
/// <summary>
/// MSAA质量
/// MSAA是“多重采样抗锯齿”,可以使画面更加平滑。
/// 超级采样抗锯齿(Super Sampling Anti-Aliasing)的原理是把当前分辨率成倍提高,然后再把画缩放到当前的显示器上。
/// 这样的做法实际上就是在显示尺寸不变的情况提高分辨率,让单个像素变得极小,这样就能够大幅减轻画面的锯齿感了。
/// 不过是由于对整个显示画面的放大,因此它消耗的显示资源也是非常大的。
/// 不过MSAA是寻找出物体边缘部分的像素,然后对它们进行缩放处理。
/// 由于只是物体的外层像素进行缩放处理,忽略掉了不会产生锯齿的内部像素,
/// 所以显卡不会像处理SSAA(超级采样抗锯齿)那样需要庞大的计算量,因此MSAA比起SSAA来更有效。
/// </summary>
public enum MSAAQuality
{
Disabled = 1,
_2x = 2,
_4x = 4,
_8x = 8
}
/// <summary>
/// 降采样,在ForwardLitPass中使用
/// </summary>
public enum Downsampling
{
None = 0,
_2xBilinear,
_4xBox,
_4xBilinear
}
/// <summary>
/// 默认材质类型
/// LightweightPipelineEditorResources的资源中获得
/// </summary>
public enum DefaultMaterialType
{
Standard = 0,
Particle,
Terrain,
UnityBuiltinDefault
}
public class LightweightPipelineAsset : RenderPipelineAsset, ISerializationCallbackReceiver
{
// 这两个路径用于查找ScriptableObject,优先在"Assets"中查找,若不存在直接取默认
public static readonly string s_SearchPathProject = "Assets";
public static readonly string s_SearchPathPackage = "Packages/com.unity.render-pipelines.lightweight";
/// <summary>
/// GetDefaultShader()方法调用默认shader的私有变量
/// </summary>
Shader m_DefaultShader;
// Default values set when a new LightweightPipeli ne asset is created
/// <summary>
/// 版本号,用于开发过程的迭代,在ISerializationCallbackReceiver接口的OnAfterDeserialize()中可以将旧版数据转换为新版使用的数据
/// </summary>
[SerializeField] int k_AssetVersion = 3;
#region Editor中包含的元素
/// <summary>
/// 最大逐像素光源个数
/// </summary>
[SerializeField] int m_MaxPixelLights = 4;
/// <summary>
/// 支持顶点光照
/// </summary>
[SerializeField] bool m_SupportsVertexLight = false;
/// <summary>
/// 需要深度纹理
/// </summary>
[SerializeField] bool m_RequireDepthTexture = false;
/// <summary>
/// 需要软粒子
/// </summary>
[SerializeField] bool m_RequireSoftParticles = false;
/// <summary>
/// 需要不透明贴图
/// </summary>
[SerializeField] bool m_RequireOpaqueTexture = false;
/// <summary>
/// 不透明降采样
/// </summary>
[SerializeField] Downsampling m_OpaqueDownsampling = Downsampling._2xBilinear;
/// <summary>
/// 支持HDR
/// </summary>
[SerializeField] bool m_SupportsHDR = false;
/// <summary>
/// MSAA
/// </summary>
[SerializeField] MSAAQuality m_MSAA = MSAAQuality._4x;
/// <summary>
/// 渲染比例
/// </summary>
[SerializeField] float m_RenderScale = 1.0f;
/// <summary>
/// 支持动态批处理
/// </summary>
[SerializeField] bool m_SupportsDynamicBatching = true;
/// <summary>
/// 支持定向阴影
/// </summary>
[SerializeField] bool m_DirectionalShadowsSupported = true;
/// <summary>
/// 阴影图集分辨率
/// </summary>
[SerializeField] ShadowResolution m_ShadowAtlasResolution = ShadowResolution._2048;
/// <summary>
/// 阴影距离
/// </summary>
[SerializeField] float m_ShadowDistance = 50.0f;
/// <summary>
/// 阴影级联
/// </summary>
[SerializeField] ShadowCascades m_ShadowCascades = ShadowCascades.FOUR_CASCADES;
/// <summary>
/// 二级级联分界
/// </summary>
[SerializeField] float m_Cascade2Split = 0.25f;
/// <summary>
/// 四级级联分界
/// </summary>
[SerializeField] Vector3 m_Cascade4Split = new Vector3(0.067f, 0.2f, 0.467f);
/// <summary>
/// 支持非平行光阴影
/// </summary>
[SerializeField] bool m_LocalShadowsSupported = true;
/// <summary>
/// 非平行光阴影图集分辨率
/// </summary>
[SerializeField] ShadowResolution m_LocalShadowsAtlasResolution = ShadowResolution._512;
/// <summary>
/// 支持软阴影
/// </summary>
[SerializeField] bool m_SoftShadowsSupported = false;
#endregion
//以下内容,关系到LightweightPipelineCore中的 static PipelineCapabilities s_PipelineCapabilities
[SerializeField] bool m_KeepAdditionalLightVariants = true;
[SerializeField] bool m_KeepVertexLightVariants = true;
[SerializeField] bool m_KeepDirectionalShadowVariants = true;
[SerializeField] bool m_KeepLocalShadowVariants = true;
[SerializeField] bool m_KeepSoftShadowVariants = true;
/// <summary>
/// 4个shader :BlitShader;CopyDepthShader;ScreenSpaceShadowShader;SamplingShader;
/// </summary>
[SerializeField] LightweightPipelineResources m_ResourcesAsset;
// Deprecated
[SerializeField] ShadowType m_ShadowType = ShadowType.HARD_SHADOWS;
#if UNITY_EDITOR
/// <summary>
/// 三个材质
/// </summary>
[NonSerialized]
LightweightPipelineEditorResources m_EditorResourcesAsset;
[MenuItem("Assets/Create/Rendering/Lightweight Pipeline Asset", priority = CoreUtils.assetCreateMenuPriority1)]
static void CreateLightweightPipeline()
{
ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, CreateInstance<CreateLightweightPipelineAsset>(),
"LightweightAsset.asset", null, null);
}
//[MenuItem("Assets/Create/Rendering/Lightweight Pipeline Resources", priority = CoreUtils.assetCreateMenuPriority1)]
static void CreateLightweightPipelineResources()
{
var instance = CreateInstance<LightweightPipelineResources>();
AssetDatabase.CreateAsset(instance, string.Format("Assets/{0}.asset", typeof(LightweightPipelineResources).Name));
}
//[MenuItem("Assets/Create/Rendering/Lightweight Pipeline Editor Resources", priority = CoreUtils.assetCreateMenuPriority1)]
static void CreateLightweightPipelineEditorResources()
{
var instance = CreateInstance<LightweightPipelineEditorResources>();
AssetDatabase.CreateAsset(instance, string.Format("Assets/{0}.asset", typeof(LightweightPipelineEditorResources).Name));
}
/// <summary>
/// 创建带初始化的窗口
/// </summary>
class CreateLightweightPipelineAsset : EndNameEditAction
{
public override void Action(int instanceId, string pathName, string resourceFile)
{
var instance = CreateInstance<LightweightPipelineAsset>();
instance.m_EditorResourcesAsset = LoadResourceFile<LightweightPipelineEditorResources>();
instance.m_ResourcesAsset = LoadResourceFile<LightweightPipelineResources>();
AssetDatabase.CreateAsset(instance, pathName);
}
}
/// <summary>
/// 加载ScriptableObject资源,先查找Asset文件夹,若不存在使用默认
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
static T LoadResourceFile<T>() where T : ScriptableObject
{
T resourceAsset = null;
var guids = AssetDatabase.FindAssets(typeof(T).Name + " t:scriptableobject", new[] {s_SearchPathProject});
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
resourceAsset = AssetDatabase.LoadAssetAtPath<T>(path);
if (resourceAsset != null)
break;
}
// There's currently an issue that prevents FindAssets from find resources withing the package folder.
if (resourceAsset == null)
{
string path = s_SearchPathPackage + "/LWRP/Data/" + typeof(T).Name + ".asset";
resourceAsset = AssetDatabase.LoadAssetAtPath<T>(path);
}
return resourceAsset;
}
LightweightPipelineEditorResources editorResources
{
get
{
if (m_EditorResourcesAsset == null)
m_EditorResourcesAsset = LoadResourceFile<LightweightPipelineEditorResources>();
return m_EditorResourcesAsset;
}
}
#endif
LightweightPipelineResources resources
{
get
{
#if UNITY_EDITOR
if (m_ResourcesAsset == null)
m_ResourcesAsset = LoadResourceFile<LightweightPipelineResources>();
#endif
return m_ResourcesAsset;
}
}
/// <summary>
/// 创建渲染管线的方法
/// </summary>
/// <returns></returns>
protected override IRenderPipeline InternalCreatePipeline()
{
return new LightweightPipeline(this);
}
/// <summary>
/// 获得ScriptableObject中的材质资源
/// </summary>
/// <param name="materialType"></param>
/// <returns></returns>
Material GetMaterial(DefaultMaterialType materialType)
{
#if UNITY_EDITOR
if (editorResources == null)
return null;
switch (materialType)
{
case DefaultMaterialType.Standard:
return editorResources.DefaultMaterial;
case DefaultMaterialType.Particle:
return editorResources.DefaultParticleMaterial;
case DefaultMaterialType.Terrain:
return editorResources.DefaultTerrainMaterial;
// Unity Builtin Default
default:
return null;
}
#else
return null;
#endif
}
/// <summary>
/// 获得版本号
/// </summary>
/// <returns></returns>
public int GetAssetVersion()
{
return k_AssetVersion;
}
/// <summary>
/// 最大逐像素光源数量
/// </summary>
public int maxPixelLights
{
get { return m_MaxPixelLights; }
}
/// <summary>
/// 支持顶点光照
/// </summary>
public bool supportsVertexLight
{
get { return m_SupportsVertexLight; }
}
/// <summary>
/// 支持深度纹理
/// </summary>
public bool supportsCameraDepthTexture
{
get { return m_RequireDepthTexture; }
}
/// <summary>
/// 支持软粒子
/// </summary>
public bool supportsSoftParticles
{
get { return m_RequireSoftParticles; }
}
/// <summary>
/// 支持不透明贴图
/// </summary>
public bool supportsCameraOpaqueTexture
{
get { return m_RequireOpaqueTexture; }
}
/// <summary>
/// 不透明降采样 Downsampling._2xBilinear
/// </summary>
public Downsampling opaqueDownsampling
{
get { return m_OpaqueDownsampling; }
}
/// <summary>
/// 支持HDR
/// </summary>
public bool supportsHDR
{
get { return m_SupportsHDR; }
}
/// <summary>
/// MSAA的程度
/// </summary>
public int msaaSampleCount
{
get { return (int)m_MSAA; }
set { m_MSAA = (MSAAQuality)value; }
}
/// <summary>
/// 渲染比例
/// </summary>
public float renderScale
{
get { return m_RenderScale; }
set { m_RenderScale = value; }
}
/// <summary>
/// 支持动态批处理
/// </summary>
public bool supportsDynamicBatching
{
get { return m_SupportsDynamicBatching; }
}
/// <summary>
/// 支持定向阴影
/// </summary>
public bool supportsDirectionalShadows
{
get { return m_DirectionalShadowsSupported; }
}
/// <summary>
/// 定向阴影图集分辨率
/// </summary>
public int directionalShadowAtlasResolution
{
get { return (int)m_ShadowAtlasResolution; }
}
/// <summary>
/// 阴影距离
/// </summary>
public float shadowDistance
{
get { return m_ShadowDistance; }
set { m_ShadowDistance = value; }
}
/// <summary>
/// 阴影级联(1、2、4)
/// </summary>
public int cascadeCount
{
get
{
switch (m_ShadowCascades)
{
case ShadowCascades.TWO_CASCADES:
return 2;
case ShadowCascades.FOUR_CASCADES:
return 4;
default:
return 1;
}
}
}
/// <summary>
/// 阴影级联2级
/// </summary>
public float cascade2Split
{
get { return m_Cascade2Split; }
}
/// <summary>
/// 阴影级联4级
/// </summary>
public Vector3 cascade4Split
{
get { return m_Cascade4Split; }
}
/// <summary>
/// 支持非平行光阴影
/// </summary>
public bool supportsLocalShadows
{
get { return m_LocalShadowsSupported; }
}
/// <summary>
/// 非平行光阴影分辨率
/// </summary>
public int localShadowAtlasResolution
{
get { return (int)m_LocalShadowsAtlasResolution; }
}
/// <summary>
/// 支持软阴影
/// </summary>
public bool supportsSoftShadows
{
get { return m_SoftShadowsSupported; }
}
/// <summary>
/// 自定义Shader变量分离,False
/// </summary>
public bool customShaderVariantStripping
{
get { return false; }
}
//以下内容,关系到LightweightPipelineCore中的 static PipelineCapabilities s_PipelineCapabilities
public bool keepAdditionalLightVariants
{
get { return m_KeepAdditionalLightVariants; }
}
public bool keepVertexLightVariants
{
get { return m_KeepVertexLightVariants; }
}
public bool keepDirectionalShadowVariants
{
get { return m_KeepDirectionalShadowVariants; }
}
public bool keepLocalShadowVariants
{
get { return m_KeepLocalShadowVariants; }
}
public bool keepSoftShadowVariants
{
get { return m_KeepSoftShadowVariants; }
}
/// <summary>
/// Lightweight-Default.mat
/// </summary>
/// <returns></returns>
public override Material GetDefaultMaterial()
{
return GetMaterial(DefaultMaterialType.Standard);
}
/// <summary>
/// Lightweight-DefaultParticle.mat
/// </summary>
/// <returns></returns>
public override Material GetDefaultParticleMaterial()
{
return GetMaterial(DefaultMaterialType.Particle);
}
/// <summary>
/// null
/// </summary>
/// <returns></returns>
public override Material GetDefaultLineMaterial()
{
return GetMaterial(DefaultMaterialType.UnityBuiltinDefault);
}
/// <summary>
/// Lightweight-DefaultTerrain.mat
/// </summary>
/// <returns></returns>
public override Material GetDefaultTerrainMaterial()
{
return GetMaterial(DefaultMaterialType.Terrain);
}
/// <summary>
/// null
/// </summary>
/// <returns></returns>
public override Material GetDefaultUIMaterial()
{
return GetMaterial(DefaultMaterialType.UnityBuiltinDefault);
}
/// <summary>
/// null
/// </summary>
/// <returns></returns>
public override Material GetDefaultUIOverdrawMaterial()
{
return GetMaterial(DefaultMaterialType.UnityBuiltinDefault);
}
/// <summary>
/// null
/// </summary>
/// <returns></returns>
public override Material GetDefaultUIETC1SupportedMaterial()
{
return GetMaterial(DefaultMaterialType.UnityBuiltinDefault);
}
/// <summary>
/// null
/// </summary>
/// <returns></returns>
public override Material GetDefault2DMaterial()
{
return GetMaterial(DefaultMaterialType.UnityBuiltinDefault);
}
/// <summary>
/// 获取默认shader
/// LightweightShaderUtils内有静态数据
/// 实际字符串为"LightweightPipeline/Standard (Physically Based)"
/// Lightweight-Default.mat也使用该Shader
/// </summary>
/// <returns></returns>
public override Shader GetDefaultShader()
{
if (m_DefaultShader == null)
m_DefaultShader = Shader.Find(LightweightShaderUtils.GetShaderPath(ShaderPathID.STANDARD_PBS));
return m_DefaultShader;
}
/// <summary>
/// "Hidden/LightweightPipeline/BlitShader"
/// </summary>
public Shader blitShader
{
get { return resources != null ? resources.BlitShader : null; }
}
/// <summary>
/// "Hidden/LightweightPipeline/CopyDepthShader"
/// </summary>
public Shader copyDepthShader
{
get { return resources != null ? resources.CopyDepthShader : null; }
}
/// <summary>
/// "Hidden/LightweightPipeline/ScreenSpaceShadows"
/// </summary>
public Shader screenSpaceShadowShader
{
get { return resources != null ? resources.ScreenSpaceShadowShader : null; }
}
/// <summary>
/// "Hidden/LightweightPipeline/SamplingShader"
/// </summary>
public Shader samplingShader
{
get { return resources != null ? resources.SamplingShader : null; }
}
public void OnBeforeSerialize()
{
}
/// <summary>
/// m_ShadowType已弃用,将旧版资源的m_ShadowType转为m_SoftShadowsSupported字段
/// </summary>
public void OnAfterDeserialize()
{
if (k_AssetVersion < 3)
{
k_AssetVersion = 3;
m_SoftShadowsSupported = (m_ShadowType == ShadowType.SOFT_SHADOWS);
}
}
}
}
下面是两个上文引用到的,用来存储资源的脚本:
using UnityEngine;
public class LightweightPipelineResources : ScriptableObject
{
public Shader BlitShader;
public Shader CopyDepthShader;
public Shader ScreenSpaceShadowShader;
public Shader SamplingShader;
}
using System;
using UnityEngine;
public class LightweightPipelineEditorResources : ScriptableObject
{
public Material DefaultMaterial;
public Material DefaultParticleMaterial;
public Material DefaultTerrainMaterial;
}
渲染管线实例
渲染管线实例包含运行时逻辑、静态信息(RenderTexture、Buffer等),是渲染指令的设置处。
using System;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor.Experimental.Rendering.LightweightPipeline;
#endif
using UnityEngine.Rendering;
using UnityEngine.Rendering.PostProcessing;
using UnityEngine.XR;
namespace UnityEngine.Experimental.Rendering.LightweightPipeline
{
public partial class LightweightPipeline : RenderPipeline
{
/// <summary>
/// 渲染管线资源
/// </summary>
public LightweightPipelineAsset pipelineAsset { get; private set; }
/// <summary>
/// 摄像机按深度排序
/// </summary>
CameraComparer m_CameraComparer = new CameraComparer();
/// <summary>
/// 前向渲染类
/// </summary>
LightweightForwardRenderer m_Renderer;
/// <summary>
/// 剔除结果
/// </summary>
CullResults m_CullResults;
/// <summary>
/// 非平行光源编号索引,用于阴影生成
/// </summary>
List<int> m_LocalLightIndices = new List<int>();
/// <summary>
/// 标记逐相机渲染进行过程
/// </summary>
bool m_IsCameraRendering;
public LightweightPipeline(LightweightPipelineAsset asset)
{
pipelineAsset = asset;
SetSupportedRenderingFeatures();
SetPipelineCapabilities(asset);
PerFrameBuffer._GlossyEnvironmentColor = Shader.PropertyToID("_GlossyEnvironmentColor");
PerFrameBuffer._SubtractiveShadowColor = Shader.PropertyToID("_SubtractiveShadowColor");
PerCameraBuffer._ScaledScreenParams = Shader.PropertyToID("_ScaledScreenParams");
m_Renderer = new LightweightForwardRenderer(asset);
// Let engine know we have MSAA on for cases where we support MSAA backbuffer
if (QualitySettings.antiAliasing != pipelineAsset.msaaSampleCount)
QualitySettings.antiAliasing = pipelineAsset.msaaSampleCount;
Shader.globalRenderPipeline = "LightweightPipeline";
m_IsCameraRendering = false;
}
public override void Dispose()
{
base.Dispose();
Shader.globalRenderPipeline = "";
SupportedRenderingFeatures.active = new SupportedRenderingFeatures();
#if UNITY_EDITOR
SceneViewDrawMode.ResetDrawMode();
#endif
m_Renderer.Dispose();
}
public override void Render(ScriptableRenderContext context, Camera[] cameras)
{
if (m_IsCameraRendering)
{
Debug.LogWarning("Nested camera rendering is forbidden. If you are calling camera.Render inside OnWillRenderObject callback, use BeginCameraRender callback instead.");
return;
}
//初始化每帧数据
base.Render(context, cameras);
BeginFrameRendering(cameras);
GraphicsSettings.lightsUseLinearIntensity = true;
SetupPerFrameShaderConstants();
// Sort cameras array by camera depth
Array.Sort(cameras, m_CameraComparer);
foreach (Camera camera in cameras)
{
BeginCameraRendering(camera);
string renderCameraTag = "Render " + camera.name;
CommandBuffer cmd = CommandBufferPool.Get(renderCameraTag);
//using 结束或中途中断会调用()中类的Dispose()方法
using (new ProfilingSample(cmd, renderCameraTag))
{
//初始化逐相机的数据
CameraData cameraData;
InitializeCameraData(camera, out cameraData);
SetupPerCameraShaderConstants(cameraData);
//剔除
ScriptableCullingParameters cullingParameters;
if (!CullResults.GetCullingParameters(camera, cameraData.isStereoEnabled, out cullingParameters))
{
CommandBufferPool.Release(cmd);
continue;
}
cullingParameters.shadowDistance = Mathf.Min(cameraData.maxShadowDistance, camera.farClipPlane);
//这里执行的new ProfilingSample(cmd, renderCameraTag)中设置的cmd.BeginSample(name);命令
context.ExecuteCommandBuffer(cmd);
cmd.Clear();
#if UNITY_EDITOR
try
#endif
{
m_IsCameraRendering = true;
#if UNITY_EDITOR
// Emit scene view UI
if (cameraData.isSceneViewCamera)
ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
#endif
//剔除结果
CullResults.Cull(ref cullingParameters, context, ref m_CullResults);
List<VisibleLight> visibleLights = m_CullResults.visibleLights;
RenderingData renderingData;
InitializeRenderingData(ref cameraData, visibleLights,
m_Renderer.maxSupportedLocalLightsPerPass, m_Renderer.maxSupportedVertexLights,
out renderingData);
m_Renderer.Setup(ref context, ref m_CullResults, ref renderingData);
m_Renderer.Execute(ref context, ref m_CullResults, ref renderingData);
}
#if UNITY_EDITOR
catch (Exception)
{
CommandBufferPool.Release(cmd);
throw;
}
finally
#endif
{
m_IsCameraRendering = false;
}
}
//这里执行的ProfilingSample.Dispose()中设置的m_Cmd.EndSample(m_Name);命令
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
context.Submit();
}
}
public static void RenderPostProcess(CommandBuffer cmd, PostProcessRenderContext context, ref CameraData cameraData, RenderTextureFormat colorFormat, RenderTargetIdentifier source, RenderTargetIdentifier dest, bool opaqueOnly)
{
context.Reset();
context.camera = cameraData.camera;
context.source = source;
context.sourceFormat = colorFormat;
context.destination = dest;
context.command = cmd;
context.flip = cameraData.camera.targetTexture == null;
if (opaqueOnly)
cameraData.postProcessLayer.RenderOpaqueOnly(context);
else
cameraData.postProcessLayer.Render(context);
}
/// <summary>
/// 设置支持的渲染特征,active是静态的
/// </summary>
void SetSupportedRenderingFeatures()
{
#if UNITY_EDITOR
SupportedRenderingFeatures.active = new SupportedRenderingFeatures()
{
//反射探针
reflectionProbeSupportFlags = SupportedRenderingFeatures.ReflectionProbeSupportFlags.None,
//默认的光照贴图的混合烘焙模式
defaultMixedLightingMode = SupportedRenderingFeatures.LightmapMixedBakeMode.Subtractive,
//支持的光照贴图的混合烘焙模式
supportedMixedLightingModes = SupportedRenderingFeatures.LightmapMixedBakeMode.Subtractive,
//支持的光照贴图烘焙模式
supportedLightmapBakeTypes = LightmapBakeType.Baked | LightmapBakeType.Mixed,
//支持的光照贴图类型
supportedLightmapsModes = LightmapsMode.CombinedDirectional | LightmapsMode.NonDirectional,
//支持光照探针代理
rendererSupportsLightProbeProxyVolumes = false,
//支持运动矢量
rendererSupportsMotionVectors = false,
//接受阴影
rendererSupportsReceiveShadows = true,
//反射探针
rendererSupportsReflectionProbes = true
};
SceneViewDrawMode.SetupDrawMode();
#endif
}
/// <summary>
/// 初始化摄像机数据
/// </summary>
/// <param name="camera"></param>
/// <param name="cameraData"></param>
void InitializeCameraData(Camera camera, out CameraData cameraData)
{
//接近1的pipelineAsset.renderScale(或者XRSettings.eyeTextureResolutionScale),置为1
const float kRenderScaleThreshold = 0.05f;
cameraData.camera = camera;
bool msaaEnabled = camera.allowMSAA && pipelineAsset.msaaSampleCount > 1;
if (msaaEnabled)
cameraData.msaaSamples = (camera.targetTexture != null) ? camera.targetTexture.antiAliasing : pipelineAsset.msaaSampleCount;
else
cameraData.msaaSamples = 1;
//场景相机
cameraData.isSceneViewCamera = camera.cameraType == CameraType.SceneView;
//存在RT且不是场景相机
cameraData.isOffscreenRender = camera.targetTexture != null && !cameraData.isSceneViewCamera;
cameraData.isStereoEnabled = IsStereoEnabled(camera);
cameraData.isHdrEnabled = camera.allowHDR && pipelineAsset.supportsHDR;
cameraData.postProcessLayer = camera.GetComponent<PostProcessLayer>();
cameraData.postProcessEnabled = cameraData.postProcessLayer != null && cameraData.postProcessLayer.isActiveAndEnabled;
// PostProcess for VR is not working atm. Disable it for now.
cameraData.postProcessEnabled &= !cameraData.isStereoEnabled;
Rect cameraRect = camera.rect;
cameraData.isDefaultViewport = (!(Math.Abs(cameraRect.x) > 0.0f || Math.Abs(cameraRect.y) > 0.0f ||
Math.Abs(cameraRect.width) < 1.0f || Math.Abs(cameraRect.height) < 1.0f));
// Discard variations lesser than kRenderScaleThreshold.
// Scale is only enabled for gameview.
// In XR mode, grab renderScale from XRSettings instead of SRP asset for now.
// This is just a temporary change pending full integration of XR with SRP
if (camera.cameraType == CameraType.Game)
{
#if !UNITY_SWITCH
if (cameraData.isStereoEnabled)
{
cameraData.renderScale = XRSettings.eyeTextureResolutionScale;
} else
#endif
{
cameraData.renderScale = pipelineAsset.renderScale;
}
}
else
{
cameraData.renderScale = 1.0f;
}
cameraData.renderScale = (Mathf.Abs(1.0f - cameraData.renderScale) < kRenderScaleThreshold) ? 1.0f : cameraData.renderScale;
cameraData.requiresDepthTexture = pipelineAsset.supportsCameraDepthTexture || cameraData.isSceneViewCamera;
cameraData.requiresSoftParticles = pipelineAsset.supportsSoftParticles;
cameraData.requiresOpaqueTexture = pipelineAsset.supportsCameraOpaqueTexture;
cameraData.opaqueTextureDownsampling = pipelineAsset.opaqueDownsampling;
bool anyShadowsEnabled = pipelineAsset.supportsDirectionalShadows || pipelineAsset.supportsLocalShadows;
cameraData.maxShadowDistance = (anyShadowsEnabled) ? pipelineAsset.shadowDistance : 0.0f;
//这是一个额外添加的脚本
LightweightAdditionalCameraData additionalCameraData = camera.gameObject.GetComponent<LightweightAdditionalCameraData>();
if (additionalCameraData != null)
{
cameraData.maxShadowDistance = (additionalCameraData.renderShadows) ? cameraData.maxShadowDistance : 0.0f;
cameraData.requiresDepthTexture &= additionalCameraData.requiresDepthTexture;
cameraData.requiresOpaqueTexture &= additionalCameraData.requiresColorTexture;
}
else if (!cameraData.isSceneViewCamera && camera.cameraType != CameraType.Reflection && camera.cameraType != CameraType.Preview)
{
cameraData.requiresDepthTexture = false;
cameraData.requiresOpaqueTexture = false;
}
cameraData.requiresDepthTexture |= cameraData.postProcessEnabled;
}
/// <summary>
/// 初始化渲染数据
/// </summary>
/// <param name="cameraData"></param>
/// <param name="visibleLights"></param>
/// <param name="maxSupportedLocalLightsPerPass"></param>
/// <param name="maxSupportedVertexLights"></param>
/// <param name="renderingData"></param>
void InitializeRenderingData(ref CameraData cameraData, List<VisibleLight> visibleLights, int maxSupportedLocalLightsPerPass, int maxSupportedVertexLights, out RenderingData renderingData)
{
//用于生成阴影的非平行光源
m_LocalLightIndices.Clear();
//有阴影投射平行光源
bool hasDirectionalShadowCastingLight = false;
//有阴影投射非平行光源
bool hasLocalShadowCastingLight = false;
//初始化阴影的相关变量
if (cameraData.maxShadowDistance > 0.0f)
{
for (int i = 0; i < visibleLights.Count; ++i)
{
Light light = visibleLights[i].light;
bool castShadows = light != null && light.shadows != LightShadows.None;
if (visibleLights[i].lightType == LightType.Directional)
{
hasDirectionalShadowCastingLight |= castShadows;
}
else
{
hasLocalShadowCastingLight |= castShadows;
m_LocalLightIndices.Add(i);
}
}
}
renderingData.cameraData = cameraData;
InitializeLightData(visibleLights, maxSupportedLocalLightsPerPass, maxSupportedVertexLights, out renderingData.lightData);
InitializeShadowData(hasDirectionalShadowCastingLight, hasLocalShadowCastingLight, out renderingData.shadowData);
renderingData.supportsDynamicBatching = pipelineAsset.supportsDynamicBatching;
}
/// <summary>
/// 初始化阴影数据
/// </summary>
/// <param name="hasDirectionalShadowCastingLight"></param>
/// <param name="hasLocalShadowCastingLight"></param>
/// <param name="shadowData"></param>
void InitializeShadowData(bool hasDirectionalShadowCastingLight, bool hasLocalShadowCastingLight, out ShadowData shadowData)
{
// Until we can have keyword stripping forcing single cascade hard shadows on gles2
bool supportsScreenSpaceShadows = SystemInfo.graphicsDeviceType != GraphicsDeviceType.OpenGLES2;
//渲染平行光阴影
shadowData.renderDirectionalShadows = pipelineAsset.supportsDirectionalShadows && hasDirectionalShadowCastingLight;
// we resolve shadows in screenspace when cascades are enabled to save ALU as computing cascade index + shadowCoord on fragment is expensive
shadowData.requiresScreenSpaceShadowResolve = shadowData.renderDirectionalShadows && supportsScreenSpaceShadows && pipelineAsset.cascadeCount > 1;
//阴影级联
shadowData.directionalLightCascadeCount = (shadowData.requiresScreenSpaceShadowResolve) ? pipelineAsset.cascadeCount : 1;
shadowData.directionalShadowAtlasWidth = pipelineAsset.directionalShadowAtlasResolution;
shadowData.directionalShadowAtlasHeight = pipelineAsset.directionalShadowAtlasResolution;
//阴影级联统一为Vector3表达
switch (shadowData.directionalLightCascadeCount)
{
case 1:
shadowData.directionalLightCascades = new Vector3(1.0f, 0.0f, 0.0f);
break;
case 2:
shadowData.directionalLightCascades = new Vector3(pipelineAsset.cascade2Split, 1.0f, 0.0f);
break;
default:
shadowData.directionalLightCascades = pipelineAsset.cascade4Split;
break;
}
//渲染非平行光阴影
shadowData.renderLocalShadows = pipelineAsset.supportsLocalShadows && hasLocalShadowCastingLight;
shadowData.localShadowAtlasWidth = shadowData.localShadowAtlasHeight = pipelineAsset.localShadowAtlasResolution;
shadowData.supportsSoftShadows = pipelineAsset.supportsSoftShadows;
shadowData.bufferBitCount = 16;
shadowData.renderedDirectionalShadowQuality = LightShadows.None;
shadowData.renderedLocalShadowQuality = LightShadows.None;
}
/// <summary>
/// 初始化光源数据
/// </summary>
/// <param name="visibleLights"></param>
/// <param name="maxSupportedLocalLightsPerPass"></param>
/// <param name="maxSupportedVertexLights"></param>
/// <param name="lightData"></param>
void InitializeLightData(List<VisibleLight> visibleLights, int maxSupportedLocalLightsPerPass, int maxSupportedVertexLights, out LightData lightData)
{
//控制最大可见光源数量<=pipelineAsset.maxPixelLights
int visibleLightsCount = Math.Min(visibleLights.Count, pipelineAsset.maxPixelLights);
lightData.mainLightIndex = GetMainLight(visibleLights);
// If we have a main light we don't shade it in the per-object light loop. We also remove it from the per-object cull list
int mainLightPresent = (lightData.mainLightIndex >= 0) ? 1 : 0;
//计算附加光数量,maxSupportedLocalLightsPerPasss是4或16
int additionalPixelLightsCount = Math.Min(visibleLightsCount - mainLightPresent, maxSupportedLocalLightsPerPass);
int vertexLightCount = (pipelineAsset.supportsVertexLight) ? Math.Min(visibleLights.Count, maxSupportedLocalLightsPerPass) - additionalPixelLightsCount : 0;
//计算逐顶点光数量,maxSupportedVertexLights是4
vertexLightCount = Math.Min(vertexLightCount, maxSupportedVertexLights);
//附加光数量
lightData.pixelAdditionalLightsCount = additionalPixelLightsCount;
//不支持顶点光时vertexLightCount是0,最后是additionalPixelLightsCount;
//支持时最后是 Math.Min(visibleLights.Count, maxSupportedLocalLightsPerPass)
lightData.totalAdditionalLightsCount = additionalPixelLightsCount + vertexLightCount;
lightData.visibleLights = visibleLights;
lightData.visibleLocalLightIndices = m_LocalLightIndices;
}
/// <summary>
/// 查找第一个Direction光源,返回数组中编号,若无返回-1
/// </summary>
/// <param name="visibleLights"></param>
/// <returns></returns>
// Main Light is always a directional light
int GetMainLight(List<VisibleLight> visibleLights)
{
int totalVisibleLights = visibleLights.Count;
if (totalVisibleLights == 0 || pipelineAsset.maxPixelLights == 0)
return -1;
for (int i = 0; i < totalVisibleLights; ++i)
{
VisibleLight currLight = visibleLights[i];
// Particle system lights have the light property as null. We sort lights so all particles lights
// come last. Therefore, if first light is particle light then all lights are particle lights.
// In this case we either have no main light or already found it.
if (currLight.light == null)
break;
// In case no shadow light is present we will return the brightest directional light
if (currLight.lightType == LightType.Directional)
return i;
}
return -1;
}
/// <summary>
/// 设置每帧的Shader常量_GlossyEnvironmentColor、_SubtractiveShadowColor
/// </summary>
void SetupPerFrameShaderConstants()
{
// When glossy reflections are OFF in the shader we set a constant color to use as indirect specular
SphericalHarmonicsL2 ambientSH = RenderSettings.ambientProbe;
Color linearGlossyEnvColor = new Color(ambientSH[0, 0], ambientSH[1, 0], ambientSH[2, 0]) * RenderSettings.reflectionIntensity;
Color glossyEnvColor = CoreUtils.ConvertLinearToActiveColorSpace(linearGlossyEnvColor);
Shader.SetGlobalVector(PerFrameBuffer._GlossyEnvironmentColor, glossyEnvColor);
// Used when subtractive mode is selected
Shader.SetGlobalVector(PerFrameBuffer._SubtractiveShadowColor, CoreUtils.ConvertSRGBToActiveColorSpace(RenderSettings.subtractiveShadowColor));
}
/// <summary>
/// 设置每个相机的Shader常量_ScaledScreenParams
/// </summary>
/// <param name="cameraData"></param>
void SetupPerCameraShaderConstants(CameraData cameraData)
{
float cameraWidth = (float)cameraData.camera.pixelWidth * cameraData.renderScale;
float cameraHeight = (float)cameraData.camera.pixelWidth * cameraData.renderScale;
Shader.SetGlobalVector(PerCameraBuffer._ScaledScreenParams, new Vector4(cameraWidth, cameraHeight, 1.0f + 1.0f / cameraWidth, 1.0f + 1.0f / cameraHeight));
}
bool IsStereoEnabled(Camera camera)
{
#if !UNITY_SWITCH
bool isSceneViewCamera = camera.cameraType == CameraType.SceneView;
return XRSettings.isDeviceActive && !isSceneViewCamera && (camera.stereoTargetEye == StereoTargetEyeMask.Both);
#else
return false;
#endif
}
}
}
参考资料
[1] Unite 2017 | Unity可编程渲染管线剖析.