Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
125 lines (103 sloc) 4.49 KB
using UnityEngine;
using UnityEngine.Rendering;
namespace Prisma
// Shadow Slicer
// In the previous versions of the Pepper's ghost rig, I used a secondary
// camera to render shadows on the background wall. This is very costly
// because it needs generating whole shadow maps twice (Unity never shares
// shadow maps between cameras).
// This "Shadow Slicer" is designed to eliminate the cost of the secondary
// camera. It renders the shadows on the wall with reusing the shadow maps
// generated by the main camera.
// How it works: The shadow slicer renders the shadows by hijacking the
// forward-opaque pass of the main camera -- the other parts of this
// project only use the deferred shading pass, so we can safely mess it up!
// 1. (Before Forward Opaque) Make a backup of the color buffer.
// 2. (Forward Base) Clear the color buffer with a full-screen quad.
// 3. (Forward Add) Draw a full-screen quad with shadow maps.
// 4. (After Forward Opaque) Retrieve the results and restore the backup.
// One of the biggest disadvantages of this approach is that we can't use
// directional lights with it (in Unity 5.x, shadow maps with directional
// lights are flatten into screen-space shadow masks while rendering, so
// we can't "slice" these shadows in the later passes).
// There is another problem: Unity uses scissoring to exclude unlit areas
// from lighting (this reduces pixel shader invocation in forward lighting)
// but this introduces unwanted black areas on the wall rendered with
// Shadow Slicer. Unfortunately, there is no solution for this problem. I
// placed the lights very carefully as avoiding the problem.
public class ShadowSlicer : MonoBehaviour
#region Exposed attributes
[SerializeField] Camera _baseCamera;
[SerializeField] RenderTexture _targetTexture;
[SerializeField, ColorUsage(false)] Color _albedo = Color.white;
#region Built-in resources
[SerializeField, HideInInspector] Mesh _quadMesh;
[SerializeField, HideInInspector] Shader _slicerShader;
#region Private objects
Material _slicerMaterial;
RenderTexture _backupBuffer;
CommandBuffer _backupCommand;
CommandBuffer _restoreCommand;
#region MonoBehaviour functions
void OnEnable()
var width = _baseCamera.pixelWidth;
var height = _baseCamera.pixelHeight;
_backupBuffer = RenderTexture.GetTemporary(width, height, 0);
if (_backupCommand == null)
_backupCommand = new CommandBuffer(); = "Shadow Slicer (Backup)";
_backupCommand.Blit(BuiltinRenderTextureType.CurrentActive, _backupBuffer);
if (_restoreCommand == null)
_restoreCommand = new CommandBuffer(); = "Shadow Slicer (Restore)";
_restoreCommand.Blit(BuiltinRenderTextureType.CurrentActive, _targetTexture);
_restoreCommand.Blit(_backupBuffer, BuiltinRenderTextureType.CameraTarget);
_baseCamera.AddCommandBuffer(CameraEvent.BeforeForwardOpaque, _backupCommand);
_baseCamera.AddCommandBuffer(CameraEvent.AfterForwardOpaque, _restoreCommand);
void OnDisable()
if (_baseCamera != null)
_baseCamera.RemoveCommandBuffer(CameraEvent.BeforeForwardOpaque, _backupCommand);
_baseCamera.RemoveCommandBuffer(CameraEvent.AfterForwardOpaque, _restoreCommand);
void OnDestroy()
if (_slicerMaterial != null) Destroy(_slicerMaterial);
if (_backupBuffer != null) RenderTexture.ReleaseTemporary(_backupBuffer);
if (_backupCommand != null) _backupCommand.Dispose();
if (_restoreCommand != null) _restoreCommand.Dispose();
void Update()
if (_slicerMaterial == null)
_slicerMaterial = new Material(_slicerShader);
_slicerMaterial.SetColor("_Color", _albedo);
_quadMesh, transform.localToWorldMatrix, _slicerMaterial,
gameObject.layer, _baseCamera