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
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:
In pipeline code
In shader code
✅ Doesn’t need to specifically care about it in codes. Write the codes as usual.
Occlusion Culling
Dynamic / static culling works like usual ✅
Batching
Static batching works like usual ✅
Dynamic batching
is disabled by default, you can turn it on with :
DrawRendererSettings drawSettingsBase = new DrawRendererSettings(camera, passNameBase); drawSettingsBase.flags = DrawRendererFlags.EnableDynamicBatching;
GPU Instancing
Just do instancing codes in shader as usual ✅
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
ShaderLab features / HLSL snippets / other things works like usual ✅
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
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
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
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
Use them as usual in shader ✅
Besides lighting / shadow variables, other built-in shader variables are set with :
context.SetupCameraProperties(camera);
_CameraDepthTexture / Projector / Decal / Motion Vector textures
Use in shader as usual, just pay attention to the order of rendering ✅
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.
Use in shader as usual, just pay attention to the order of rendering ✅
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
Reference to legacy shaders, use as usual ✅
Reflection probe needs content of rendered objects, so make sure you do Blit() to BuiltinRenderTexture.CameraTarget
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
Culling also cull lights (directional lights are always visible). An array of visible lights will be available after culling
We need to setup / calculate each light data for shader e.g.
Shadow casting requires depth pass, shadow map pass and collect shadow pass
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)
Use them as usual in shader ✅
HDR
Set colorFormat to HDR format (e.g. RenderTextureFormat.DefaultHDR ) on the TemporaryRenderTexture for color buffer
Render objects onto this render texture
MSAA
Set msaasample to 1,2,4,8 on TemporaryRenderTextures depends on your settings
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
Pass the HDR color texture as source and CameraTarget as destination when necessary e.g. bloom
Pay attention to different effect requires different texture property. For example, DepthOfField needs _CameraDepthTexture, so make sure_CameraDepthTexture is created etc.
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
LayerMask, Camera depth order works ✅
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
Just setup multiple RenderTargets
Make sure shaders outputs to correct SV_Target
💡 You might want to try RenderPass approach for MRT rendering
Multi-Pipelines
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
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
Blit() to BuiltinRenderTextureType.CameraTarget at necessary stage so that the toggle buttons on scene view functions correctly
Make UGUI geometry visible in scene view, do
#if UNITY_EDITOR if (camera.cameraType == CameraType.SceneView) ScriptableRenderContext.EmitWorldGeometryForSceneView(camera); #endif
Draw Mode
By default all modes are enabled ✅
Because 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
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
Broken shaders will still be pink ✅
Make other unsupported pipelines’ shader pink
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
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
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
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)
LikeLike
Thanks! 😊 I’ll see if I can make these into docs… but there are still many things missing, e.g. the docs for Core RP library
LikeLike