Permalink
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;
#endregion
#region Built-in resources
[SerializeField, HideInInspector] Mesh _quadMesh;
[SerializeField, HideInInspector] Shader _slicerShader;
#endregion
#region Private objects
Material _slicerMaterial;
RenderTexture _backupBuffer;
CommandBuffer _backupCommand;
CommandBuffer _restoreCommand;
#endregion
#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();
_backupCommand.name = "Shadow Slicer (Backup)";
_backupCommand.Blit(BuiltinRenderTextureType.CurrentActive, _backupBuffer);
}
if (_restoreCommand == null)
{
_restoreCommand = new CommandBuffer();
_restoreCommand.name = "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);
Graphics.DrawMesh(
_quadMesh, transform.localToWorldMatrix, _slicerMaterial,
gameObject.layer, _baseCamera
);
}
#endregion
}
}