Skip to content

Commit 25772d9

Browse files
authored
Shadow volumes (#234)
Initial support for shadow volumes, generated using compute shaders.
1 parent 87d3a1b commit 25772d9

File tree

8 files changed

+390
-42
lines changed

8 files changed

+390
-42
lines changed

Assets/Editor/VolumeRenderedObjectCustomInspector.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,13 @@ public override void OnInspectorGUI()
127127
);
128128
// Convert back to linear scale, before setting updated value.
129129
volrendObj.SetGradientLightingThreshold(new Vector2(gradLightThreshold.x * gradLightThreshold.x, gradLightThreshold.y * gradLightThreshold.y));
130+
131+
ShadowVolumeManager shadowVoumeManager = volrendObj.GetComponent<ShadowVolumeManager>();
132+
bool enableShadowVolume = GUILayout.Toggle(shadowVoumeManager != null, "Enable shadow volume (expensive)");
133+
if (enableShadowVolume && shadowVoumeManager == null)
134+
shadowVoumeManager = volrendObj.gameObject.AddComponent<ShadowVolumeManager>();
135+
else if (!enableShadowVolume && shadowVoumeManager != null)
136+
GameObject.DestroyImmediate(shadowVoumeManager);
130137
}
131138
}
132139

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#pragma kernel ShadowVolumeMain
2+
3+
#pragma multi_compile __ CUBIC_INTERPOLATION_ON
4+
#pragma multi_compile __ CROSS_SECTION_ON
5+
6+
sampler3D _VolumeTexture;
7+
sampler2D _TFTex;
8+
9+
float _MinVal;
10+
float _MaxVal;
11+
12+
int3 _Dimension;
13+
float3 _LightDirection;
14+
float3 _TextureSize;
15+
uint3 _DispatchOffsets;
16+
17+
RWTexture3D<float> _ShadowVolume;
18+
19+
#include "Assets/Shaders/Include/TricubicSampling.cginc"
20+
#include "Assets/Shaders/Include/VolumeCutout.cginc"
21+
22+
float getDensity(float3 pos)
23+
{
24+
return interpolateTricubicFast(_VolumeTexture, float3(pos.x, pos.y, pos.z), _TextureSize).r;
25+
}
26+
27+
// Gets the colour from a 1D Transfer Function (x = density)
28+
float4 getTF1DColour(float density)
29+
{
30+
return tex2Dlod(_TFTex, float4(density, 0.0f, 0.0f, 0.0f));
31+
}
32+
33+
float calculateShadow(float3 startPos, float3 lightDir)
34+
{
35+
float4 col = float4(0.0f, 0.0f, 0.0f, 0.0f);
36+
int numSteps = 32;
37+
float stepSize = 0.25f / numSteps;
38+
for (int iStep = 1; iStep < numSteps; iStep++)
39+
{
40+
const float3 currPos = startPos + lightDir * stepSize * iStep;
41+
42+
if (currPos.x < 0.0f || currPos.y < 0.0f || currPos.z < 0.0f || currPos.x > 1.0f || currPos.y > 1.0f || currPos.z > 1.0f)
43+
break;
44+
45+
// Perform slice culling (cross section plane)
46+
if (IsCutout(currPos))
47+
continue;
48+
49+
// Get the dansity/sample value of the current position
50+
const float density = getDensity(currPos);
51+
52+
// Apply visibility window
53+
if (density < _MinVal || density > _MaxVal) continue;
54+
55+
// Apply 1D transfer function
56+
float4 src = getTF1DColour(density);
57+
if (src.a == 0.0)
58+
continue;
59+
src.rgb *= src.a;
60+
col = (1.0f - col.a) * src + col;
61+
}
62+
return col.a;
63+
}
64+
65+
[numthreads(8, 8, 8)]
66+
void ShadowVolumeMain(uint3 id : SV_DispatchThreadID)
67+
{
68+
id += _DispatchOffsets;
69+
if (id.x < uint(_Dimension.x) && id.y < uint(_Dimension.y) & id.z < uint(_Dimension.z))
70+
{
71+
float3 rayOrigin = float3((float)id.x / uint(_Dimension.x), (float)id.y / uint(_Dimension.y), (float)id.z / uint(_Dimension.z));
72+
float3 rayDir = _LightDirection;
73+
float shadow = calculateShadow(rayOrigin, rayDir);
74+
_ShadowVolume[id.xyz] = shadow;
75+
}
76+
}

Assets/Resources/ShadowVolume.compute.meta

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
using System;
2+
using System.Linq;
3+
using UnityEngine;
4+
using UnityEngine.Experimental.GlobalIllumination;
5+
using UnityEngine.Rendering;
6+
using LightType = UnityEngine.LightType;
7+
8+
namespace UnityVolumeRendering
9+
{
10+
[ExecuteInEditMode]
11+
[RequireComponent(typeof(VolumeRenderedObject))]
12+
public class ShadowVolumeManager : MonoBehaviour
13+
{
14+
private const int NUM_DISPATCH_CHUNKS = 5;
15+
private const int dispatchCount = NUM_DISPATCH_CHUNKS * NUM_DISPATCH_CHUNKS * NUM_DISPATCH_CHUNKS;
16+
17+
private VolumeRenderedObject volumeRenderedObject = null;
18+
private RenderTexture shadowVolumeTexture = null;
19+
private Vector3 lightDirection;
20+
private bool initialised = false;
21+
private ComputeShader shadowVolumeShader;
22+
private int handleMain;
23+
private int currentDispatchIndex = 0;
24+
private float cooldown = 1.0f;
25+
private double lastUpdateTimeEditor = 0.0f;
26+
private bool isDirty = true;
27+
28+
private void Awake()
29+
{
30+
if (!SystemInfo.supportsComputeShaders)
31+
{
32+
Debug.LogError("Shadow volumes not supported on this platform (SystemInfo.supportsComputeShaders == false)");
33+
DestroyImmediate(this);
34+
}
35+
}
36+
37+
private void Start()
38+
{
39+
if (!initialised)
40+
Initialise();
41+
}
42+
43+
private void OnValidate()
44+
{
45+
if (!initialised)
46+
Initialise();
47+
}
48+
49+
private void Update()
50+
{
51+
HandleUpdate();
52+
}
53+
54+
private void OnEnable()
55+
{
56+
#if UNITY_EDITOR
57+
UnityEditor.EditorApplication.update += OnEditorUpdate;
58+
#endif
59+
if (volumeRenderedObject != null)
60+
{
61+
volumeRenderedObject.meshRenderer.sharedMaterial.EnableKeyword("SHADOWS_ON");
62+
}
63+
}
64+
65+
private void OnDisable()
66+
{
67+
#if UNITY_EDITOR
68+
UnityEditor.EditorApplication.update -= OnEditorUpdate;
69+
#endif
70+
currentDispatchIndex = 0;
71+
if (volumeRenderedObject != null)
72+
{
73+
volumeRenderedObject.meshRenderer.sharedMaterial.DisableKeyword("SHADOWS_ON");
74+
}
75+
}
76+
77+
private void OnEditorUpdate()
78+
{
79+
#if UNITY_EDITOR
80+
if (!UnityEditor.EditorApplication.isPlaying)
81+
{
82+
if (isDirty || (UnityEditor.EditorApplication.timeSinceStartup - lastUpdateTimeEditor > 0.02f))
83+
{
84+
HandleUpdate();
85+
UnityEditor.EditorUtility.SetDirty(UnityEditor.SceneView.lastActiveSceneView);
86+
}
87+
}
88+
#endif
89+
}
90+
91+
private void Initialise()
92+
{
93+
Debug.Log("Initialising shadow volume buffers");
94+
volumeRenderedObject = GetComponent<VolumeRenderedObject>();
95+
Debug.Assert(volumeRenderedObject != null);
96+
97+
Vector3Int shadowVolumeDimensions = new Vector3Int(512, 512, 512);
98+
99+
shadowVolumeTexture = new RenderTexture(shadowVolumeDimensions.x, shadowVolumeDimensions.y, 0, RenderTextureFormat.RHalf, RenderTextureReadWrite.Linear);
100+
shadowVolumeTexture.dimension = TextureDimension.Tex3D;
101+
shadowVolumeTexture.volumeDepth = shadowVolumeDimensions.z;
102+
shadowVolumeTexture.enableRandomWrite = true;
103+
shadowVolumeTexture.wrapMode = TextureWrapMode.Clamp;
104+
shadowVolumeTexture.Create();
105+
106+
volumeRenderedObject.meshRenderer.sharedMaterial.SetTexture("_ShadowVolume", shadowVolumeTexture);
107+
volumeRenderedObject.meshRenderer.sharedMaterial.SetVector("_ShadowVolumeTextureSize", new Vector3(shadowVolumeDimensions.x, shadowVolumeDimensions.y, shadowVolumeDimensions.z));
108+
109+
shadowVolumeShader = Resources.Load("ShadowVolume") as ComputeShader;
110+
handleMain = shadowVolumeShader.FindKernel("ShadowVolumeMain");
111+
if (handleMain < 0)
112+
{
113+
Debug.LogError("Shadow volume compute shader initialization failed.");
114+
}
115+
initialised = true;
116+
}
117+
118+
private void HandleUpdate()
119+
{
120+
#if UNITY_EDITOR
121+
lastUpdateTimeEditor = UnityEditor.EditorApplication.timeSinceStartup;
122+
#endif
123+
// Dirty hack for broken data texture
124+
// TODO: Investigate issue with calling VolumeDataset.GetDataTexture from first update in editor after leaving play mode
125+
if (cooldown > 0.0f)
126+
{
127+
cooldown -= Time.deltaTime;
128+
return;
129+
}
130+
131+
if (volumeRenderedObject.GetRenderMode() != RenderMode.DirectVolumeRendering)
132+
{
133+
return;
134+
}
135+
136+
lightDirection = -GetLightDirection(volumeRenderedObject);
137+
138+
if (currentDispatchIndex == 0)
139+
{
140+
ConfigureCompute();
141+
}
142+
if (currentDispatchIndex < dispatchCount)
143+
{
144+
DispatchComputeChunk();
145+
currentDispatchIndex++;
146+
}
147+
if (currentDispatchIndex == dispatchCount)
148+
{
149+
currentDispatchIndex = 0;
150+
}
151+
isDirty = false;
152+
}
153+
154+
private void ConfigureCompute()
155+
{
156+
VolumeDataset dataset = volumeRenderedObject.dataset;
157+
158+
Texture3D dataTexture = dataset.GetDataTexture();
159+
160+
if (volumeRenderedObject.GetCubicInterpolationEnabled())
161+
shadowVolumeShader.EnableKeyword("CUBIC_INTERPOLATION_ON");
162+
else
163+
shadowVolumeShader.DisableKeyword("CUBIC_INTERPOLATION_ON");
164+
165+
shadowVolumeShader.SetVector("_TextureSize", new Vector3(dataset.dimX, dataset.dimY, dataset.dimZ));
166+
shadowVolumeShader.SetInts("_Dimension", new int[] { shadowVolumeTexture.width, shadowVolumeTexture.height, shadowVolumeTexture.volumeDepth });
167+
shadowVolumeShader.SetTexture(handleMain, "_VolumeTexture", dataTexture);
168+
shadowVolumeShader.SetTexture(handleMain, "_TFTex", volumeRenderedObject.transferFunction.GetTexture());
169+
shadowVolumeShader.SetTexture(handleMain, "_ShadowVolume", shadowVolumeTexture);
170+
shadowVolumeShader.SetVector("_LightDirection", lightDirection);
171+
172+
Material volRendMaterial = volumeRenderedObject.meshRenderer.sharedMaterial;
173+
shadowVolumeShader.SetFloat("_MinVal", volRendMaterial.GetFloat("_MinVal"));
174+
shadowVolumeShader.SetFloat("_MaxVal", volRendMaterial.GetFloat("_MaxVal"));
175+
176+
if (volRendMaterial.IsKeywordEnabled("CROSS_SECTION_ON"))
177+
{
178+
shadowVolumeShader.EnableKeyword("CROSS_SECTION_ON");
179+
shadowVolumeShader.SetMatrixArray("_CrossSectionMatrices", volRendMaterial.GetMatrixArray("_CrossSectionMatrices"));
180+
shadowVolumeShader.SetFloats("_CrossSectionTypes", volRendMaterial.GetFloatArray("_CrossSectionTypes"));
181+
shadowVolumeShader.SetInt("_NumCrossSections", 1);
182+
}
183+
else
184+
{
185+
shadowVolumeShader.DisableKeyword("CROSS_SECTION_ON");
186+
}
187+
if (volumeRenderedObject != null)
188+
{
189+
volumeRenderedObject.meshRenderer.sharedMaterial.EnableKeyword("SHADOWS_ON");
190+
}
191+
}
192+
193+
private void DispatchComputeChunk()
194+
{
195+
int threadGroupsX = (shadowVolumeTexture.width / NUM_DISPATCH_CHUNKS + 7) / 8;
196+
int threadGroupsY = (shadowVolumeTexture.height / NUM_DISPATCH_CHUNKS + 7) / 8;
197+
int threadGroupsZ = (shadowVolumeTexture.volumeDepth / NUM_DISPATCH_CHUNKS + 7) / 8;
198+
int dispatchChunkWidth = shadowVolumeTexture.width / NUM_DISPATCH_CHUNKS;
199+
int dispatchChunkHeight = shadowVolumeTexture.height / NUM_DISPATCH_CHUNKS;
200+
int dispatchChunkDepth = shadowVolumeTexture.volumeDepth / NUM_DISPATCH_CHUNKS;
201+
202+
int ix = currentDispatchIndex % NUM_DISPATCH_CHUNKS;
203+
int iy = (currentDispatchIndex / NUM_DISPATCH_CHUNKS) % NUM_DISPATCH_CHUNKS;
204+
int iz = currentDispatchIndex / (NUM_DISPATCH_CHUNKS * NUM_DISPATCH_CHUNKS);
205+
shadowVolumeShader.SetInts("_DispatchOffsets", new int[] { dispatchChunkWidth * ix, dispatchChunkHeight * iy, dispatchChunkDepth * iz });
206+
shadowVolumeShader.Dispatch(handleMain, threadGroupsX, threadGroupsY, threadGroupsZ);
207+
}
208+
209+
private Vector3 GetLightDirection(VolumeRenderedObject targetObject)
210+
{
211+
Transform targetTransform = targetObject.volumeContainerObject.transform;
212+
if (targetObject.GetLightSource() == LightSource.SceneMainLight)
213+
{
214+
Light[] lights = GameObject.FindObjectsOfType(typeof(Light)) as Light[];
215+
Light directionalLight = lights.FirstOrDefault(l => l.type == LightType.Directional);
216+
if ( directionalLight != null)
217+
{
218+
return targetTransform.InverseTransformDirection(directionalLight.transform.forward);
219+
}
220+
221+
if (lights.Length > 0)
222+
{
223+
return targetTransform.InverseTransformDirection(lights[0].transform.forward); // TODO
224+
}
225+
}
226+
#if UNITY_EDITOR
227+
if (!Application.isPlaying)
228+
{
229+
return targetTransform.InverseTransformDirection(UnityEditor.SceneView.lastActiveSceneView.camera.transform.forward);
230+
}
231+
#endif
232+
return targetTransform.InverseTransformDirection(Camera.main.transform.forward);
233+
}
234+
}
235+
}

Assets/Scripts/Lighting/ShadowVolumeManager.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)