Rendering Depth
First thing we'll need is a depth buffer. By rendering to a separate render texture we can no longer take advantage of the GPU's built-in z-testing. Having a depth buffer will allow us to do our own z-testing. Unity made it stupid easy to get a depth buffer of the scene with version 2.6.0. On the main camera run this line somewhere in Awake:
this.camera.depthTextureMode = DepthTextureMode.Depth;
However, for our purposes the depth buffer given to us by Unity is a bit overkill. Unity will render everything that the main camera can see. We only have dust-like particles near the camera, so there's no reason for Unity to render depth information at far distances. So instead of taking advantage of Unity's one-liner solution, we render our own depth buffer with a different far-clip plane. This isn't as straight forward as setting the camera's far-clip plane and is slightly outside of this article's scope. We'll address this in a future article.
Rendering the Particles
Here comes the hard part. First we'll outline the steps we need to take to render the particles. All of this happens in the camera's OnRenderImage function.
- Create/Setup the render texture that we will render our particles into
- Create/Setup the camera that will render our particles
- Render the particles with a replacement shader
- Blend the particles back into the screen with a composite shader
First we'll create and setup the render texture that will hold our particles.
// get the downsample factor
var downsampleFactor:int = this.offScreenParticlesOptions.downsampleFactor;
// create the off-screen particles texture
var particlesRT:RenderTexture = RenderTexture.GetTemporary(Screen.width / downsampleFactor, Screen.height / downsampleFactor, 0);
The downsampleFactor determines the quality of the off-screen particles. Higher numbers will give worse quality, but better performance.
Next, we'll create and setup the camera.
var ppCamera:Camera = PostProcessingHelper.GetPPCamera();
ppCamera.depthTextureMode = DepthTextureMode.None;
ppCamera.cullingMask = this.offScreenParticlesOptions.layerMask.value;
ppCamera.targetTexture = particlesRT;
ppCamera.clearFlags = CameraClearFlags.SolidColor;
ppCamera.backgroundColor = Color.black;
And the PostProcessingHelper.GetPPCamera() function:
private static var ppCameraGO:GameObject;
static function GetPPCamera():Camera
{
// Create the shader camera if it doesn't exist yet
if(!ppCameraGO) {
ppCameraGO = new GameObject("Post Processing Camera", Camera);
ppCameraGO.camera.enabled = false;
ppCameraGO.hideFlags = HideFlags.HideAndDontSave;
}
return ppCameraGO.camera;
}
Notice how we are setting the layer that the particles are on. This is how the camera determines which renderers in the scene are the particles that we wish to render off-screen.
Next we render the actual particles. Telling the camera to render is easy enough.
Shader.SetGlobalVector("_CameraDepthTexture_Size", Vector4(this.camera.pixelWidth, this.camera.pixelHeight, 0.0, 0.0)); // some data about the depth buffer we need to send the shaders
depthCamera.RenderWithShader(Shader.Find("Hidden/Off-Screen Particles Replace"), "RenderType");
The replacement shader is a bit unwieldy, so here it is as a file. Don't worry too much about what's going on in the replacement shader. Just make sure to place this shader in a Resources folder.
Lastly, we blend the particles back into the scene.
var blendMaterial:Material = PostProcessingHelper.GetMaterial(Shader.Find("Hidden/Off-Screen Particles Composite"));
var texelOffset:Vector2 = Vector2.Scale(source.GetTexelOffset(), Vector2(source.width, source.height));
Graphics.BlitMultiTap(particlesRT, source, blendMaterial, texelOffset);
And the Composite shader (again, place this in a Resources folder):
Shader "Hidden/Off-Screen Particles Composite" {
Properties {
_MainTex ("Base (RGB)", RECT) = "white" {}
}
SubShader {
Pass {
ZTest Always Cull Off ZWrite Off Fog { Mode Off }
Blend One SrcAlpha
SetTexture[_MainTex] {combine texture}
}
}
Fallback Off
}
Don't forget to release the particles render texture!
RenderTexture.ReleaseTemporary(particlesRT);
And after you finish doing any other post processing effects you may be doing, output to the destination RenderTexture:
Graphics.Blit(source, destination);
PRETTY PICTURES!
Click for bigger images. These all should be pixel perfect if you want to flip through or diff them.
Notes
Mixed Resolution: We decided that mixed resolution particles wasn't necessary for us. The scene is too fast moving to notice the depth sampling artifacts. Plus the performance overhead from needing a second pass for the alpha channel made mixed resolution rendering just too expensive.
Soft Particles: Soft Particles are extremely easy to implement with the off-screen particles, but we didn't see much of a difference in the final render. We decided to just use discard instead of soft particles in the end.
Anti-aliasing: This is untested with Anti-aliasing on directx. I really doubt it'll work correctly as is. Shouldn't be too difficult to get it to work though.
No comments:
Post a Comment