Update 151: 02/25/2020SCRIPTABLE RENDER PIPELINE, PART IIIMost of the time between January and February 2020 was spent on further implementation of the scriptable render pipeline.
Fortunately, it's almost done!
Below is a step through of how everything in the game is rendered so far.
---
SHADOW MAPSRendering shadows in games can be fairly complex, but fortunately I had ample amount of
reference.Desolus is currently using an 8k resolution shadowmap, with four cascades.
Additionally, I am using a 7x7 Percentage Closer Filtering algorithm to drastically smooth shadows.
You can read more about implementing shadows in the reference link.
Inspecting the shadow maps in Unity's frame debugger is pretty neat, they're almost like architecture prints
---
DRAWING GEOMETRYAfter the screen is cleared from the shadow pass, I draw normal geometry to the screen.
Drawing geometry in Scriptable Render Pipeline is considerably more efficient with draw calls, due to Unity's
'SRP Batcher.'As you can see from this screenshot, I am drawing a considerable amount of modular geometry on screen.
Normally, this would result in a great deal of draw calls.
However, because of Unity's SRP batching system, the draw calls are highly optimized to reduce CPU load.
You can see the lighting of my BRDF model at work here. I gave the architecture in Desolus a slightly metallic property.
Additionally, I opted instead of using Unity's fog model, to use
Inigo Quilez's fog.
This model is significantly better, as Unity's fog is not based in world-space distance and instead based in view-space distance.
Using this fog model, rotating the camera doesn't distort the fog color.
---
DRAWING INSTANCED GEOMETRY (PARTICLES)Desolus uses my own custom implementation of TC Particles, which is written by my friend
Arthur Brussee whom I worked with on Manifold Garden.
I have been using the particle system since the very first builds of Desolus.
TC Particles is highly performant and allows for GPU particles which run in a compute shader.
Although the system is a bit old, as it was created in 2014, it was considerably ahead of its time in that Unity was still using CPU particles.
I briefly contemplated migrating to Unity's VFX graph (as it also supports GPU particles).
However, the VFX graph is DEEPLY coupled with Universal Render Pipeline and High Definition Render Pipeline, and I would rather not deal with porting it.
Instead, I opted to port TC Particles from Unity's old pipeline to my own custom SRP.
For anyone else porting similar systems, there are a few critical differences between Unity's old API and the new SRP API.
- Functions like OnRenderImage() and Camera.OnPreCull() no longer work.
If you're designing your own SRP, you must manually place when to call your functions.
Honestly this is a good thing, because the order of events is considerably less ambiguous and you have manual control.
- Unity's 'Graphics' functions no longer work.
For example, instead of using Graphics.DrawMeshInstancedIndirect, you MUST use the Command Buffer version instead.
For each camera, pass in your Scriptable Render Pipeline's command buffer, and have it call this function if the object isn't culled.
- All shaders must be written in HLSL, instead of CG, but you probably already knew this.
TC Particles processes everything in a compute shader, draws instanced geometry with C#, and then renders the result in a shader.
Fortunately, the existing code was modular enough in that it was primarily only changing over the API as described above.
Overall, the port took several days. However the code was written well enough that it was a clean and successful.
---
POST PROCESSING: COLOR CORRECTIONAnother challenge with Scriptable Render Pipeline, is getting post-processing working.
To begin, I looked into Unity's own
Post-Processing Stack. However, I opted to implement my own stack as the Desolus post-processing is fairly minimal.
After all of the normal and procedural geometry is drawn, I begin rendering my post-processing stack for Desolus.
Currently, there are only two effects (Color Correction and Bloom), as the Fog is done within the fragment shader.
Despite the relative simplicity of these effects, there were a few challenges.
As I mentioned previously, in SRP the Unity graphics functions such as
OnRenderImage(), no longer work. Additionally, there is no
Graphics.Blit function.
A reasonable substitute for
Graphics.Blit is
CommandBuffer.Blit.
However, due to the restrictions with Unity's Blit (specifically with the stencil buffer,
as discussed previously, I created my own 'Manual' Blit function.
This function simply binds the camera's color/depth textures to a material, sets the render target, and draws a static triangle mesh with the image effect material.
You can see the Blit in action in the frame debugger, under 'Draw Mesh.'
One draw is used to copy the current camera's color/depth buffer to a temporary texture with the image effect, and another to copy back to the render target.
After creating this, porting the color correction script was fairly simple. I've been using
ColorCorrection.cs for ages, and the Desolus colors are graded very precisely.
Porting this involved transferring the old C# code to my new SRP post-processing stack, and copying over the animation curve vales.
Additionally, I translated the old Unity CG shader into HLSL.
It worked well! The port ended up being super clean, with a very small amount of code difference.
---
POST PROCESSING: BLUR AND BLOOMA bit more challenging to port than the Color Correction effect, was the Bloom in Desolus.
After attempting a direct port of
Unity's 'Optimized Bloom' and having it be considerably bugged, I almost gave up.
I began implementing my own bloom shader,
after a bit of research. I successfully implemented this with my SRP, but the results were sub-par compared to my old bloom.
Additionally, from a production standpoint, I knew that *yet again* would I have to spend a considerably amount of time on color-grading.
The Desolus color grading process in total takes me about 10-15 hours to do, as it's very meticulously adjusting color curves and bloom values.
Knowing this, I got a bit of rest and tackled the problem again.
I ended up successfully porting over both the
old CG shader, to HLSL and the
old C# code to the SRP API with a bit of focus.
The bloom shader itself is a 'fast'
Gaussian Blur which progressively down samples the image based on a certain number of iterations.
I have my bloom set to 8 iterations, with a
billinear downsample for each texture.
This results in a large amount of 'Draw Mesh' calls from the custom Blit, but since Desolus is meant for desktop PCs, the performance impact is negligible.
Overall, this was also a clean port, albeit a bit more difficult.
---
FINAL IMAGE (SO FAR)Although the basics of my SRP seems to be complete, I am still lacking a few critical features.
From a visual standpoint, there is no volumetric lighting.
My next set of work will be porting my existing volumetric lighting system over to my SRP, which could be challenging.
Also, most importantly, there is no rendering set up for portals between universes yet.
Although I (theoretically) perfected my approach in the old Unity pipeline, it's going to take a bit of work to implement in SRP.
Fortunately, I have total control over the rendering process so I won't have to fight Unity.
---