-
Notifications
You must be signed in to change notification settings - Fork 873
Expand file tree
/
Copy pathPostProcessVolume.cs
More file actions
279 lines (251 loc) · 12.4 KB
/
Copy pathPostProcessVolume.cs
File metadata and controls
279 lines (251 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
using System.Collections.Generic;
namespace UnityEngine.Rendering.PostProcessing
{
//
// Here's a quick look at the architecture of this framework and how it's integrated into Unity
// (written between versions 5.6 and 2017.1):
//
// Users have to be able to plug in their own effects without having to modify the codebase and
// these custom effects should work out-of-the-box with all the other features we provide
// (volume blending etc). This relies on heavy use of polymorphism, but the only way to get
// the serialization system to work well with polymorphism in Unity is to use ScriptableObjects.
//
// Users can push their custom effects at different (hardcoded) injection points.
//
// Each effect consists of at least two classes (+ shaders): a POD "Settings" class which only
// stores parameters, and a "Renderer" class that holds the rendering logic. Settings are linked
// to renderers using a PostProcessAttribute. These are automatically collected at init time
// using reflection. Settings in this case are ScriptableObjects, we only need to serialize
// these.
//
// We could store these settings object straight into each volume and call it a day, but
// unfortunately there's one feature of Unity that doesn't work well with scene-stored assets:
// prefabs. So we need to store all of these settings in a disk-asset and treat them as
// sub-assets.
//
// Note: We have to use ScriptableObject for everything but these don't work with the Animator
// tool. It's unfortunate but it's the only way to make it easily extensible. On the other
// hand, users can animate post-processing effects using Volumes or straight up scripting.
//
// Volume blending leverages the physics system for distance checks to the nearest point on
// volume colliders. Each volume can have several colliders or any type (cube, mesh...), making
// it quite a powerful feature to use.
//
// Volumes & blending are handled by a singleton manager (see PostProcessManager).
//
// Rendering is handled by a PostProcessLayer component living on the camera, which mean you
// can easily toggle post-processing on & off or change the anti-aliasing type per-camera,
// which is very useful when doing multi-layered camera rendering or any other technique that
// involves multiple-camera setups. This PostProcessLayer component can also filters volumes
// by layers (as in Unity layers) so you can easily choose which volumes should affect the
// camera.
//
// All post-processing shaders MUST use the custom Standard Shader Library bundled with the
// framework. The reason for that is because the codebase is meant to work without any
// modification on the Classic Render Pipelines (Forward, Deferred...) and the upcoming
// Scriptable Render Pipelines (HDPipe, LDPipe...). But these don't have compatible shader
// libraries so instead of writing two code paths we chose to provide a minimalist, generic
// Standard Library geared toward post-processing use. An added bonus to that if users create
// their own post-processing effects using this framework, then they'll work without any
// modification on both Classic and Scriptable Render Pipelines.
//
/// <summary>
/// A post-process volume component holding a post-process profile.
/// </summary>
/// <seealso cref="RuntimeUtilities.DestroyVolume"/>
#if UNITY_2018_3_OR_NEWER
[ExecuteAlways]
#else
[ExecuteInEditMode]
#endif
[AddComponentMenu("Rendering/Post-process Volume", 1001)]
public sealed class PostProcessVolume : MonoBehaviour
{
/// <summary>
/// The shared profile of this volume.
/// Modifying <c>sharedProfile</c> will change all volumes using this profile, and change
/// profile settings that are stored in the project too.
/// </summary>
/// <remarks>
/// It is not recommended to modify profiles returned by <c>sharedProfile</c>. If you want
/// to modify the profile of a volume use <see cref="profile"/> instead.
/// </remarks>
/// <seealso cref="profile"/>
public PostProcessProfile sharedProfile;
/// <summary>
/// Should this volume be applied to the whole scene?
/// </summary>
[Tooltip("Check this box to mark this volume as global. This volume's Profile will be applied to the whole Scene.")]
public bool isGlobal = false;
/// <summary>
/// The outer distance to start blending from. A value of 0 means no blending and the volume
/// overrides will be applied immediatly upon entry.
/// </summary>
[Min(0f), Tooltip("The distance (from the attached Collider) to start blending from. A value of 0 means there will be no blending and the Volume overrides will be applied immediatly upon entry to the attached Collider.")]
public float blendDistance = 0f;
/// <summary>
/// The total weight of this volume in the scene. 0 means it won't do anything, 1 means full
/// effect.
/// </summary>
[Range(0f, 1f), Tooltip("The total weight of this Volume in the Scene. A value of 0 signifies that it will have no effect, 1 signifies full effect.")]
public float weight = 1f;
/// <summary>
/// The volume priority in the stack. Higher number means higher priority. Negative values
/// are supported.
/// </summary>
[Tooltip("The volume priority in the stack. A higher value means higher priority. Negative values are supported.")]
public float priority = 0f;
/// <summary>
/// Returns the first instantiated <see cref="PostProcessProfile"/> assigned to the volume.
/// Modifying <paramref name="profile"/> will change the profile for this volume only. If
/// the profile is used by any other volume, this will clone the shared profile and start
/// using it from now on.
/// </summary>
/// <remarks>
/// This property automatically instantiates the profile and make it unique to this volume
/// so you can safely edit it via scripting at runtime without changing the original asset
/// in the project.
/// Note that if you pass in your own profile, it is your responsibility to destroy it once
/// it's not in use anymore.
/// </remarks>
/// <seealso cref="sharedProfile"/>
/// <seealso cref="RuntimeUtilities.DestroyProfile"/>
public PostProcessProfile profile
{
get
{
if (m_InternalProfile == null)
{
m_InternalProfile = ScriptableObject.CreateInstance<PostProcessProfile>();
if (sharedProfile != null)
{
foreach (var item in sharedProfile.settings)
{
var itemCopy = Instantiate(item);
m_InternalProfile.settings.Add(itemCopy);
}
}
}
return m_InternalProfile;
}
set
{
m_InternalProfile = value;
}
}
internal PostProcessProfile profileRef
{
get
{
return m_InternalProfile == null
? sharedProfile
: m_InternalProfile;
}
}
/// <summary>
/// Checks if the volume has an intantiated profile or is using a shared profile.
/// </summary>
/// <returns><c>true</c> if the profile has been intantiated</returns>
/// <seealso cref="profile"/>
/// <seealso cref="sharedProfile"/>
public bool HasInstantiatedProfile()
{
return m_InternalProfile != null;
}
internal int previousLayer => m_PreviousLayer;
int m_PreviousLayer;
float m_PreviousPriority;
List<Collider> m_TempColliders;
PostProcessProfile m_InternalProfile;
void OnEnable()
{
PostProcessManager.instance.Register(this);
m_PreviousLayer = gameObject.layer;
m_TempColliders = new List<Collider>();
}
void OnDisable()
{
PostProcessManager.instance.Unregister(this);
}
void Update()
{
// Unfortunately we need to track the current layer to update the volume manager in
// real-time as the user could change it at any time in the editor or at runtime.
// Because no event is raised when the layer changes, we have to track it on every
// frame :/
int layer = gameObject.layer;
if (layer != m_PreviousLayer)
{
PostProcessManager.instance.UpdateVolumeLayer(this, m_PreviousLayer, layer);
m_PreviousLayer = layer;
}
// Same for `priority`. We could use a property instead, but it doesn't play nice with
// the serialization system. Using a custom Attribute/PropertyDrawer for a property is
// possible but it doesn't work with Undo/Redo in the editor, which makes it useless.
if (priority != m_PreviousPriority)
{
PostProcessManager.instance.SetLayerDirty(layer);
m_PreviousPriority = priority;
}
}
// TODO: Look into a better volume previsualization system
void OnDrawGizmos()
{
var colliders = m_TempColliders;
GetComponents(colliders);
if (isGlobal || colliders == null)
return;
#if UNITY_EDITOR
// Can't access the UnityEditor.Rendering.PostProcessing namespace from here, so
// we'll get the preferred color manually
unchecked
{
int value = UnityEditor.EditorPrefs.GetInt("PostProcessing.Volume.GizmoColor", (int)0x8033cc1a);
Gizmos.color = ColorUtilities.ToRGBA((uint)value);
}
#endif
var scale = transform.lossyScale;
var invScale = new Vector3(1f / scale.x, 1f / scale.y, 1f / scale.z);
Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, scale);
// Draw a separate gizmo for each collider
foreach (var collider in colliders)
{
if (!collider.enabled)
continue;
// We'll just use scaling as an approximation for volume skin. It's far from being
// correct (and is completely wrong in some cases). Ultimately we'd use a distance
// field or at least a tesselate + push modifier on the collider's mesh to get a
// better approximation, but the current Gizmo system is a bit limited and because
// everything is dynamic in Unity and can be changed at anytime, it's hard to keep
// track of changes in an elegant way (which we'd need to implement a nice cache
// system for generated volume meshes).
var type = collider.GetType();
if (type == typeof(BoxCollider))
{
var c = (BoxCollider)collider;
Gizmos.DrawCube(c.center, c.size);
Gizmos.DrawWireCube(c.center, c.size + 4f * blendDistance * invScale);
}
else if (type == typeof(SphereCollider))
{
var c = (SphereCollider)collider;
Gizmos.DrawSphere(c.center, c.radius);
Gizmos.DrawWireSphere(c.center, c.radius + invScale.x * blendDistance * 2f);
}
else if (type == typeof(MeshCollider))
{
var c = (MeshCollider)collider;
// Only convex mesh colliders are allowed
if (!c.convex)
c.convex = true;
// Mesh pivot should be centered or this won't work
Gizmos.DrawMesh(c.sharedMesh);
Gizmos.DrawWireMesh(c.sharedMesh, Vector3.zero, Quaternion.identity, Vector3.one + 4f * blendDistance * invScale);
}
// Nothing for capsule (DrawCapsule isn't exposed in Gizmo), terrain, wheel and
// other colliders...
}
colliders.Clear();
}
}
}