-
-
Notifications
You must be signed in to change notification settings - Fork 128
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial support for shadow volumes, generated using compute shaders.
- Loading branch information
Showing
8 changed files
with
390 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
#pragma kernel ShadowVolumeMain | ||
|
||
#pragma multi_compile __ CUBIC_INTERPOLATION_ON | ||
#pragma multi_compile __ CROSS_SECTION_ON | ||
|
||
sampler3D _VolumeTexture; | ||
sampler2D _TFTex; | ||
|
||
float _MinVal; | ||
float _MaxVal; | ||
|
||
int3 _Dimension; | ||
float3 _LightDirection; | ||
float3 _TextureSize; | ||
uint3 _DispatchOffsets; | ||
|
||
RWTexture3D<float> _ShadowVolume; | ||
|
||
#include "Assets/Shaders/Include/TricubicSampling.cginc" | ||
#include "Assets/Shaders/Include/VolumeCutout.cginc" | ||
|
||
float getDensity(float3 pos) | ||
{ | ||
return interpolateTricubicFast(_VolumeTexture, float3(pos.x, pos.y, pos.z), _TextureSize).r; | ||
} | ||
|
||
// Gets the colour from a 1D Transfer Function (x = density) | ||
float4 getTF1DColour(float density) | ||
{ | ||
return tex2Dlod(_TFTex, float4(density, 0.0f, 0.0f, 0.0f)); | ||
} | ||
|
||
float calculateShadow(float3 startPos, float3 lightDir) | ||
{ | ||
float4 col = float4(0.0f, 0.0f, 0.0f, 0.0f); | ||
int numSteps = 32; | ||
float stepSize = 0.25f / numSteps; | ||
for (int iStep = 1; iStep < numSteps; iStep++) | ||
{ | ||
const float3 currPos = startPos + lightDir * stepSize * iStep; | ||
|
||
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) | ||
break; | ||
|
||
// Perform slice culling (cross section plane) | ||
if (IsCutout(currPos)) | ||
continue; | ||
|
||
// Get the dansity/sample value of the current position | ||
const float density = getDensity(currPos); | ||
|
||
// Apply visibility window | ||
if (density < _MinVal || density > _MaxVal) continue; | ||
|
||
// Apply 1D transfer function | ||
float4 src = getTF1DColour(density); | ||
if (src.a == 0.0) | ||
continue; | ||
src.rgb *= src.a; | ||
col = (1.0f - col.a) * src + col; | ||
} | ||
return col.a; | ||
} | ||
|
||
[numthreads(8, 8, 8)] | ||
void ShadowVolumeMain(uint3 id : SV_DispatchThreadID) | ||
{ | ||
id += _DispatchOffsets; | ||
if (id.x < uint(_Dimension.x) && id.y < uint(_Dimension.y) & id.z < uint(_Dimension.z)) | ||
{ | ||
float3 rayOrigin = float3((float)id.x / uint(_Dimension.x), (float)id.y / uint(_Dimension.y), (float)id.z / uint(_Dimension.z)); | ||
float3 rayDir = _LightDirection; | ||
float shadow = calculateShadow(rayOrigin, rayDir); | ||
_ShadowVolume[id.xyz] = shadow; | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,235 @@ | ||
using System; | ||
using System.Linq; | ||
using UnityEngine; | ||
using UnityEngine.Experimental.GlobalIllumination; | ||
using UnityEngine.Rendering; | ||
using LightType = UnityEngine.LightType; | ||
|
||
namespace UnityVolumeRendering | ||
{ | ||
[ExecuteInEditMode] | ||
[RequireComponent(typeof(VolumeRenderedObject))] | ||
public class ShadowVolumeManager : MonoBehaviour | ||
{ | ||
private const int NUM_DISPATCH_CHUNKS = 5; | ||
private const int dispatchCount = NUM_DISPATCH_CHUNKS * NUM_DISPATCH_CHUNKS * NUM_DISPATCH_CHUNKS; | ||
|
||
private VolumeRenderedObject volumeRenderedObject = null; | ||
private RenderTexture shadowVolumeTexture = null; | ||
private Vector3 lightDirection; | ||
private bool initialised = false; | ||
private ComputeShader shadowVolumeShader; | ||
private int handleMain; | ||
private int currentDispatchIndex = 0; | ||
private float cooldown = 1.0f; | ||
private double lastUpdateTimeEditor = 0.0f; | ||
private bool isDirty = true; | ||
|
||
private void Awake() | ||
{ | ||
if (!SystemInfo.supportsComputeShaders) | ||
{ | ||
Debug.LogError("Shadow volumes not supported on this platform (SystemInfo.supportsComputeShaders == false)"); | ||
DestroyImmediate(this); | ||
} | ||
} | ||
|
||
private void Start() | ||
{ | ||
if (!initialised) | ||
Initialise(); | ||
} | ||
|
||
private void OnValidate() | ||
{ | ||
if (!initialised) | ||
Initialise(); | ||
} | ||
|
||
private void Update() | ||
{ | ||
HandleUpdate(); | ||
} | ||
|
||
private void OnEnable() | ||
{ | ||
#if UNITY_EDITOR | ||
UnityEditor.EditorApplication.update += OnEditorUpdate; | ||
#endif | ||
if (volumeRenderedObject != null) | ||
{ | ||
volumeRenderedObject.meshRenderer.sharedMaterial.EnableKeyword("SHADOWS_ON"); | ||
} | ||
} | ||
|
||
private void OnDisable() | ||
{ | ||
#if UNITY_EDITOR | ||
UnityEditor.EditorApplication.update -= OnEditorUpdate; | ||
#endif | ||
currentDispatchIndex = 0; | ||
if (volumeRenderedObject != null) | ||
{ | ||
volumeRenderedObject.meshRenderer.sharedMaterial.DisableKeyword("SHADOWS_ON"); | ||
} | ||
} | ||
|
||
private void OnEditorUpdate() | ||
{ | ||
#if UNITY_EDITOR | ||
if (!UnityEditor.EditorApplication.isPlaying) | ||
{ | ||
if (isDirty || (UnityEditor.EditorApplication.timeSinceStartup - lastUpdateTimeEditor > 0.02f)) | ||
{ | ||
HandleUpdate(); | ||
UnityEditor.EditorUtility.SetDirty(UnityEditor.SceneView.lastActiveSceneView); | ||
} | ||
} | ||
#endif | ||
} | ||
|
||
private void Initialise() | ||
{ | ||
Debug.Log("Initialising shadow volume buffers"); | ||
volumeRenderedObject = GetComponent<VolumeRenderedObject>(); | ||
Debug.Assert(volumeRenderedObject != null); | ||
|
||
Vector3Int shadowVolumeDimensions = new Vector3Int(512, 512, 512); | ||
|
||
shadowVolumeTexture = new RenderTexture(shadowVolumeDimensions.x, shadowVolumeDimensions.y, 0, RenderTextureFormat.RHalf, RenderTextureReadWrite.Linear); | ||
shadowVolumeTexture.dimension = TextureDimension.Tex3D; | ||
shadowVolumeTexture.volumeDepth = shadowVolumeDimensions.z; | ||
shadowVolumeTexture.enableRandomWrite = true; | ||
shadowVolumeTexture.wrapMode = TextureWrapMode.Clamp; | ||
shadowVolumeTexture.Create(); | ||
|
||
volumeRenderedObject.meshRenderer.sharedMaterial.SetTexture("_ShadowVolume", shadowVolumeTexture); | ||
volumeRenderedObject.meshRenderer.sharedMaterial.SetVector("_ShadowVolumeTextureSize", new Vector3(shadowVolumeDimensions.x, shadowVolumeDimensions.y, shadowVolumeDimensions.z)); | ||
|
||
shadowVolumeShader = Resources.Load("ShadowVolume") as ComputeShader; | ||
handleMain = shadowVolumeShader.FindKernel("ShadowVolumeMain"); | ||
if (handleMain < 0) | ||
{ | ||
Debug.LogError("Shadow volume compute shader initialization failed."); | ||
} | ||
initialised = true; | ||
} | ||
|
||
private void HandleUpdate() | ||
{ | ||
#if UNITY_EDITOR | ||
lastUpdateTimeEditor = UnityEditor.EditorApplication.timeSinceStartup; | ||
#endif | ||
// Dirty hack for broken data texture | ||
// TODO: Investigate issue with calling VolumeDataset.GetDataTexture from first update in editor after leaving play mode | ||
if (cooldown > 0.0f) | ||
{ | ||
cooldown -= Time.deltaTime; | ||
return; | ||
} | ||
|
||
if (volumeRenderedObject.GetRenderMode() != RenderMode.DirectVolumeRendering) | ||
{ | ||
return; | ||
} | ||
|
||
lightDirection = -GetLightDirection(volumeRenderedObject); | ||
|
||
if (currentDispatchIndex == 0) | ||
{ | ||
ConfigureCompute(); | ||
} | ||
if (currentDispatchIndex < dispatchCount) | ||
{ | ||
DispatchComputeChunk(); | ||
currentDispatchIndex++; | ||
} | ||
if (currentDispatchIndex == dispatchCount) | ||
{ | ||
currentDispatchIndex = 0; | ||
} | ||
isDirty = false; | ||
} | ||
|
||
private void ConfigureCompute() | ||
{ | ||
VolumeDataset dataset = volumeRenderedObject.dataset; | ||
|
||
Texture3D dataTexture = dataset.GetDataTexture(); | ||
|
||
if (volumeRenderedObject.GetCubicInterpolationEnabled()) | ||
shadowVolumeShader.EnableKeyword("CUBIC_INTERPOLATION_ON"); | ||
else | ||
shadowVolumeShader.DisableKeyword("CUBIC_INTERPOLATION_ON"); | ||
|
||
shadowVolumeShader.SetVector("_TextureSize", new Vector3(dataset.dimX, dataset.dimY, dataset.dimZ)); | ||
shadowVolumeShader.SetInts("_Dimension", new int[] { shadowVolumeTexture.width, shadowVolumeTexture.height, shadowVolumeTexture.volumeDepth }); | ||
shadowVolumeShader.SetTexture(handleMain, "_VolumeTexture", dataTexture); | ||
shadowVolumeShader.SetTexture(handleMain, "_TFTex", volumeRenderedObject.transferFunction.GetTexture()); | ||
shadowVolumeShader.SetTexture(handleMain, "_ShadowVolume", shadowVolumeTexture); | ||
shadowVolumeShader.SetVector("_LightDirection", lightDirection); | ||
|
||
Material volRendMaterial = volumeRenderedObject.meshRenderer.sharedMaterial; | ||
shadowVolumeShader.SetFloat("_MinVal", volRendMaterial.GetFloat("_MinVal")); | ||
shadowVolumeShader.SetFloat("_MaxVal", volRendMaterial.GetFloat("_MaxVal")); | ||
|
||
if (volRendMaterial.IsKeywordEnabled("CROSS_SECTION_ON")) | ||
{ | ||
shadowVolumeShader.EnableKeyword("CROSS_SECTION_ON"); | ||
shadowVolumeShader.SetMatrixArray("_CrossSectionMatrices", volRendMaterial.GetMatrixArray("_CrossSectionMatrices")); | ||
shadowVolumeShader.SetFloats("_CrossSectionTypes", volRendMaterial.GetFloatArray("_CrossSectionTypes")); | ||
shadowVolumeShader.SetInt("_NumCrossSections", 1); | ||
} | ||
else | ||
{ | ||
shadowVolumeShader.DisableKeyword("CROSS_SECTION_ON"); | ||
} | ||
if (volumeRenderedObject != null) | ||
{ | ||
volumeRenderedObject.meshRenderer.sharedMaterial.EnableKeyword("SHADOWS_ON"); | ||
} | ||
} | ||
|
||
private void DispatchComputeChunk() | ||
{ | ||
int threadGroupsX = (shadowVolumeTexture.width / NUM_DISPATCH_CHUNKS + 7) / 8; | ||
int threadGroupsY = (shadowVolumeTexture.height / NUM_DISPATCH_CHUNKS + 7) / 8; | ||
int threadGroupsZ = (shadowVolumeTexture.volumeDepth / NUM_DISPATCH_CHUNKS + 7) / 8; | ||
int dispatchChunkWidth = shadowVolumeTexture.width / NUM_DISPATCH_CHUNKS; | ||
int dispatchChunkHeight = shadowVolumeTexture.height / NUM_DISPATCH_CHUNKS; | ||
int dispatchChunkDepth = shadowVolumeTexture.volumeDepth / NUM_DISPATCH_CHUNKS; | ||
|
||
int ix = currentDispatchIndex % NUM_DISPATCH_CHUNKS; | ||
int iy = (currentDispatchIndex / NUM_DISPATCH_CHUNKS) % NUM_DISPATCH_CHUNKS; | ||
int iz = currentDispatchIndex / (NUM_DISPATCH_CHUNKS * NUM_DISPATCH_CHUNKS); | ||
shadowVolumeShader.SetInts("_DispatchOffsets", new int[] { dispatchChunkWidth * ix, dispatchChunkHeight * iy, dispatchChunkDepth * iz }); | ||
shadowVolumeShader.Dispatch(handleMain, threadGroupsX, threadGroupsY, threadGroupsZ); | ||
} | ||
|
||
private Vector3 GetLightDirection(VolumeRenderedObject targetObject) | ||
{ | ||
Transform targetTransform = targetObject.volumeContainerObject.transform; | ||
if (targetObject.GetLightSource() == LightSource.SceneMainLight) | ||
{ | ||
Light[] lights = GameObject.FindObjectsOfType(typeof(Light)) as Light[]; | ||
Light directionalLight = lights.FirstOrDefault(l => l.type == LightType.Directional); | ||
if ( directionalLight != null) | ||
{ | ||
return targetTransform.InverseTransformDirection(directionalLight.transform.forward); | ||
} | ||
|
||
if (lights.Length > 0) | ||
{ | ||
return targetTransform.InverseTransformDirection(lights[0].transform.forward); // TODO | ||
} | ||
} | ||
#if UNITY_EDITOR | ||
if (!Application.isPlaying) | ||
{ | ||
return targetTransform.InverseTransformDirection(UnityEditor.SceneView.lastActiveSceneView.camera.transform.forward); | ||
} | ||
#endif | ||
return targetTransform.InverseTransformDirection(Camera.main.transform.forward); | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.