[Custom SRP] How to use Unity features?

Update:

If you are using 2019.1+, you might notice there is a big change to the SRP APIs.
I’ve created a new repository and you can grab here. Much cleaner and minimal.

https://github.com/cinight/CustomSRP

 


 

May-14-2018 gif

Screen Shot 2018-06-02 at 22.16.24

SRPFlowScreen Shot 2018-05-12 at 18.52.43

(My playground pipeline)

Here lists out exact what codes enable the Unity feature when making our custom SRP.

*Note that my codes may not be perfectly optimised, but the concept itself won’t change.
(!) Alert: Below information might be outdated. I stopped updating this note after 2018.x releases.

Indicators:
icon_script In pipeline code
icon_shader In shader code
✅ Doesn’t need to specifically care about it in codes. Write the codes as usual.

 


Occlusion Culling

icon_script Dynamic / static culling works like usual ✅

 

Batching

icon_script Static batching works like usual ✅
icon_script Dynamic batching is disabled by default, you can turn it on with :

DrawRendererSettings drawSettingsBase =
new DrawRendererSettings(camera, passNameBase);
drawSettingsBase.flags = DrawRendererFlags.EnableDynamicBatching;

 

GPU Instancing

icon_shader  Just do instancing codes in shader as usual ✅
icon_script Should be enabled by default, if not, it can also be turned on with :

DrawRendererSettings drawSettingsBase =
new DrawRendererSettings(camera, passNameBase);
drawSettingsBase.flags = DrawRendererFlags.EnableInstancing;

 


Unlit Shader

icon_shader  ShaderLab features / HLSL snippets / other things works like usual ✅
icon_script We can draw the shader passes without pass name / LightMode tag with :

private static ShaderPassName passNameDefault =
new ShaderPassName("SRPDefaultUnlit");
//…
DrawRendererSettings drawSettingsDefault =
new DrawRendererSettings(camera, passNameDefault);
drawSettingsDefault.SetShaderPassName(1,passNameDefault);

 

Surface Shader / Standard Shader / Other legacy lit shaders

icon_script As long as you do the following things correctly :
1. Call DrawRenderers with legacy pass names e.g. ForwardBase, ForwardAdd
2. You enable the shader keywords e.g. shader keywords in standard shader
3. You set the Global Shader Properties of lights / GI data e.g. _LightColor0, _WorldSpaceLightPos0
icon_shader As long as your pipeline is doing the above things correctly, they will work ✅
⚠️  Suggested that we better not to use surface shader anymore because it’s full of hidden magic which gives limitation to advanced shader programmers.

 

Compute Shader

icon_script icon_shader Works like usual ✅

 

Multi-Pass Shader

⚠️  Remember we used to have multi-pass shader like this to achieve layered fur? It is not supported anymore. Because it creates so many draw calls (1 pass = 1 draw call). Suggested that we use alternative ways to achieve the same effect, e.g. geometry shaders, or fur quad pieces on the 3D model to imitate fur.


Built-in Shader Variables

icon_shader Use them as usual in shader ✅
icon_script Besides lighting / shadow variables, other built-in shader variables are set with :

context.SetupCameraProperties(camera);

 

_CameraDepthTexture / Projector / Decal / Motion Vector textures

icon_shader  Use in shader as usual, just pay attention to the order of rendering ✅
icon_script We have to implement them in the pipeline. For example, _CameraDepthTexture :

CommandBuffer cmdDepthOpaque = new CommandBuffer();
cmdDepthOpaque.name = "Depth Pass";

//m_DepthRT has the colour format = RenderTextureFormat.Depth
cmdDepthOpaque.SetRenderTarget(m_DepthRT);
cmdDepthOpaque.ClearRenderTarget(true, true, Color.black);
context.ExecuteCommandBuffer(cmdDepthOpaque);
cmdDepthOpaque.Clear();

//Objects with Zwrite On will generate depth data
filterSettings.renderQueueRange = RenderQueueRange.opaque;
drawSettingsDepth.sorting.flags = SortFlags.CommonOpaque;
context.DrawRenderers(
cull.visibleRenderers, ref drawSettingsDepth, filterSettings);

//m_DepthRTid’s has the name “_CameraDepthTexture”
cmdDepthOpaque.SetGlobalTexture(m_DepthRTid, m_DepthRT);
context.ExecuteCommandBuffer(cmdDepthOpaque);

cmdDepthOpaque.Release();

 

GrabPass

⚠️  In legacy pipelines, this is a very expensive operation because it triggers re-rendering of objects. Although we can implement it in SRP but better find an alternative way to fake it.
icon_shader  Use in shader as usual, just pay attention to the order of rendering ✅

icon_script The workaround is, we can “grab” any color textures at different stages in pipeline. For example, grab the color texture after rendering opaque objects so that we can use in transparent objects and achieve distortion effect. Just set the global texture with the name you want:


cmdColorOpaque.SetGlobalTexture(m_GrabOpaqueRTid, m_ColorRT);

 

CameraEvent

⚠️  In legacy pipeline, we can extend the pipeline by adding CommandBuffer to different CameraEvents. As you can see these are legacy pipeline event names so this approach is not supported in SRP. But we are always free to edit the pipeline :). Update: we can make custom passes / use the available callback function, so that we can insert commands from other scripts. See LightWeightPipeline Passes.


Baked Light and Shadow / Reflection Probe / Light Probe

icon_shader Reference to legacy shaders, use as usual ✅
icon_script Reflection probe needs content of rendered objects, so make sure you do Blit() to BuiltinRenderTexture.CameraTarget
icon_script The features needs to be enabled with : 

private static RendererConfiguration renderConfig =
RendererConfiguration.PerObjectReflectionProbes |
RendererConfiguration.PerObjectLightmaps |
RendererConfiguration.PerObjectLightProbe;
//..
DrawRendererSettings drawSettingsBase =
new DrawRendererSettings(camera, passNameBase);
drawSettingsBase.rendererConfiguration = renderConfig;

 

Realtime Light and Shadow

icon_script Culling also cull lights (directional lights are always visible). An array of visible lights will be available after culling
icon_script We need to setup / calculate each light data for shader e.g.
icon_script Shadow casting requires depth pass, shadow map pass and collect shadow pass
icon_shader Use the light data and sample shadow texture defined in the pipeline
💡 Feel free to reference to different pipelines e.g. LWRP, HDRP
💡 If you want simple ones e.g. BasicRenderPipeline (only lighting), my playground pipeline (only 1 directional light and hard shadow)

 

Fog (In LightingSettings panel)

icon_shader  Use them as usual in shader ✅


HDR

icon_scriptSet colorFormat to HDR format (e.g. RenderTextureFormat.DefaultHDR ) on the TemporaryRenderTexture for color buffer
icon_script 
Render objects onto this render texture

MSAA

icon_script Set msaasample to 1,2,4,8 on TemporaryRenderTextures depends on your settings
icon_script 
Pay attention to the resolved color textures which doesn’t have a depth buffer

RenderTextureDescriptor colorRTDesc = new RenderTextureDescriptor(
camera.pixelWidth, camera.pixelHeight);
colorRTDesc.colorFormat = m_ColorFormat; //HDR format
colorRTDesc.depthBufferBits = depthBufferBits;
colorRTDesc.sRGB = true;
colorRTDesc.msaaSamples = 1; //MSAA
colorRTDesc.enableRandomWrite = false;
cmdTempId.GetTemporaryRT(
m_ColorRTid, colorRTDesc, FilterMode.Bilinear);

 

Post-processing stack

icon_script Pass the HDR color texture as source and CameraTarget as destination when necessary e.g. bloom
icon_script Pay attention to different effect requires different texture property. For example,  DepthOfField needs _CameraDepthTexture, so make sure_CameraDepthTexture is created etc.
icon_script Some effects are done with PostProcessLayer.Render() (e.g. Bloom) and some done with PostProcessLayer.RenderOpaque() (e.g. AO). Call these functions when necessary.

CommandBuffer cmdpp = new CommandBuffer();
cmdpp.name = "Post-processing";

m_PostProcessRenderContext.Reset();
m_PostProcessRenderContext.camera = camera;
m_PostProcessRenderContext.source = m_ColorRT;
m_PostProcessRenderContext.sourceFormat = m_ColorFormat;
m_PostProcessRenderContext.destination =
BuiltinRenderTextureType.CameraTarget;
m_PostProcessRenderContext.command = cmdpp;
m_PostProcessRenderContext.flip = camera.targetTexture == null;
m_CameraPostProcessLayer.Render(m_PostProcessRenderContext);

context.ExecuteCommandBuffer(cmdpp);
cmdpp.Release();

 


Multi-Cameras

icon_script LayerMask, Camera depth order works ✅
icon_script Do the ClearRenderTarget() properly and take care of the settings on Camera component

bool clearcolor = true;
bool cleardepth = true;
if( cam.clearFlags == CameraClearFlags.Skybox ||
cam.clearFlags == CameraClearFlags.Depth ) {clearcolor = false;}
cmd.ClearRenderTarget(
cleardepth, clearcolor, camera.BackgroundColor);

 

Multi-RenderTargets

icon_script Just setup multiple RenderTargets
icon_shader Make sure shaders outputs to correct SV_Target
💡 You might want to try RenderPass approach for MRT rendering

 

Multi-Pipelines

icon_script Different camera can use different pipelines. So we can implement this with :

public override void Render(
ScriptableRenderContext renderContext, Camera[] cameras)
{
Camera[] defaultCameras;
Camera[] customCameras;
SRPDefault.FilterCameras(cameras, out defaultCameras, out customCameras);
SRPPlaygroundPipeline.Render(renderContext, customCameras);
SRPDefault.Render(renderContext, defaultCameras);
}

Scene View / Preview Camera

icon_script Make sure BuiltinRenderTextureType.CameraTarget has both Color and Depth contents so that gizmo and selection outline works
⚠️  Mind that Blit() only copies color buffer. If you want to copy depth buffer, you need a shader that outputs to SV_Depth
icon_script 
Blit() to BuiltinRenderTextureType.CameraTarget at necessary stage so that the toggle buttons on scene view functions correctly
icon_script Make UGUI
 geometry visible in scene view, do

#if UNITY_EDITOR
if (camera.cameraType == CameraType.SceneView)
ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
#endif

 

Draw Mode

icon_scriptBy default all modes are enabled ✅
icon_scriptBecause our custom SRP might not need some of the draw mode, we can hide them like this.

#if UNITY_EDITOR
ArrayList sceneViewArray = SceneView.sceneViews;
foreach (SceneView sceneView in sceneViewArray)
{
//Define which draw mode you don't want in RejectDrawMode
sceneView.onValidateCameraMode += RejectDrawMode;
}
#endif

 


Default Shader when creating objects

icon_script Have this function in our pipeline RenderPipelineAsset class : 

public override Shader GetDefaultShader()
{
Shader m_DefaultShader = Shader.Find(“MyPipeline/DefaultShader”);
return m_DefaultShader;
}

 

Pink Error Shader

icon_shader Broken shaders will still be pink ✅

 

Make other unsupported pipelines’ shader pink

icon_script Let’s say our pipeline doesn’t support Forward pipeline shaders, we replace these shaders by the error pink shader and call the DrawRenderer() with these settings :

private static ShaderPassName passNameForwardBase =
new ShaderPassName("ForwardBase");
//…
DrawRendererSettings drawSettingsForwardBase =
new DrawRendererSettings(camera, passNameForwardBase);
drawSettingsForwardBase.rendererConfiguration =
RendererConfiguration.None;
drawSettingsForwardBase.SetOverrideMaterial(
m_ErrorMaterial, 0); //Use "Hidden/InternalErrorShader"

Pipeline Asset

icon_script We can define our pipeline settings and expose the settings on the pipeline asset, ref. to BasicRenderPipeline
⚠️  Thus lots of settings in the Editor will become invalid. Some settings in Graphics Settings / Quality Settings / Player Settings will be hidden once we are using our own pipeline. e.g. Built-in Shader Settings, shadow settings, MSAA settings

 

Lighting Settings / MeshRenderer Settings

icon_script Because our custom SRP might not support all rendering features, so there is an API that can hide those Editor GUI on Lighting Settings panel and MeshRenderer component :

#if UNITY_EDITOR
SupportedRenderingFeatures.active =
new SupportedRenderingFeatures()
{
reflectionProbeSupportFlags =
SupportedRenderingFeatures.ReflectionProbeSupportFlags.None,

defaultMixedLightingMode =
SupportedRenderingFeatures.LightmapMixedBakeMode.Subtractive,

supportedMixedLightingModes =
SupportedRenderingFeatures.LightmapMixedBakeMode.Subtractive |
SupportedRenderingFeatures.LightmapMixedBakeMode.Shadowmask,

supportedLightmapBakeTypes =
LightmapBakeType.Baked |
LightmapBakeType.Mixed |
LightmapBakeType.Realtime,

supportedLightmapsModes =
LightmapsMode.CombinedDirectional |
LightmapsMode.NonDirectional,

rendererSupportsLightProbeProxyVolumes = false,
rendererSupportsMotionVectors = false,
rendererSupportsReceiveShadows = true,
rendererSupportsReflectionProbes = true
};

#endif

 

 

2 thoughts on “[Custom SRP] How to use Unity features?

  1. Excellent writeup. Thanks! I understand that everything is still experimental but this should be official documentation already 🙂
    (e.g. was looking for “SRPDefaultUnlit” for way too long)

    Like

Leave a comment