Added Dohya

This commit is contained in:
2026-08-29 13:34:57 +03:00
parent f6a7547e84
commit 9f87122a34
1478 changed files with 963649 additions and 35753 deletions
@@ -0,0 +1,146 @@
#if UNITY_5_6_OR_NEWER
#define VLB_GPU_INSTANCING_SUPPORT
#endif
// Force isDepthBlendEnabled at true when GPU Instancing is enabled, to prevent from breaking the batch if 1 beam has it at 0 and 1 has it at > 0
#define FORCE_ENABLE_DEPTHBLEND_FOR_BATCHING
using UnityEngine;
namespace VLB
{
public static class BatchingHelper
{
/// <summary>
/// Returns if GPU Instancing feature is supported on this Unity version or not.
/// </summary>
#if VLB_GPU_INSTANCING_SUPPORT
public const bool isGpuInstancingSupported = true;
#else
public static bool isGpuInstancingSupported { get; private set; }
#endif
#if FORCE_ENABLE_DEPTHBLEND_FOR_BATCHING
public static bool forceEnableDepthBlend { get { return Config.Instance.actualRenderingMode == RenderingMode.GPUInstancing || Config.Instance.actualRenderingMode == RenderingMode.SRPBatcher; } }
#else
public const bool forceEnableDepthBlend = false;
#endif
public static bool IsGpuInstancingEnabled(Material material)
{
#if VLB_GPU_INSTANCING_SUPPORT
return material.enableInstancing;
#else
return false;
#endif
}
public static void SetMaterialProperties(Material material, bool enableGpuInstancing)
{
#if VLB_GPU_INSTANCING_SUPPORT
Debug.Assert(material != null);
material.enableInstancing = enableGpuInstancing;
#endif
}
public static bool CanBeBatched(VolumetricLightBeam beamA, VolumetricLightBeam beamB, ref string reasons)
{
#pragma warning disable 0162 // warning CS0162: Unreachable code detected
if (Config.Instance.renderPipeline == RenderPipeline.BuiltIn && !isGpuInstancingSupported)
{
reasons = "'GPU Instancing' is not supported on your setup.";
return false;
}
#pragma warning restore 0162
if (Config.Instance.actualRenderingMode != RenderingMode.GPUInstancing && Config.Instance.actualRenderingMode != RenderingMode.SRPBatcher)
{
reasons = string.Format("Current Render Pipeline is '{0}'. To enable batching, use 'GPU Instancing'", Config.Instance.renderPipeline);
if(Config.Instance.renderPipeline != RenderPipeline.BuiltIn)
reasons += " or 'SRP Batcher'";
return false;
}
bool ret = true;
if (!CanBeBatched(beamA, ref reasons))
ret = false;
if (!CanBeBatched(beamB, ref reasons))
ret = false;
if (Config.Instance.featureEnabledDynamicOcclusion)
{
if ((beamA.GetComponent<DynamicOcclusionAbstractBase>() == null) != (beamB.GetComponent<DynamicOcclusionAbstractBase>() == null))
{
AppendErrorMessage(ref reasons, string.Format("{0}/{1}: dynamically occluded and non occluded beams cannot be batched together", beamA.name, beamB.name));
ret = false;
}
}
if (Config.Instance.featureEnabledColorGradient != FeatureEnabledColorGradient.Off && beamA.colorMode != beamB.colorMode)
{
AppendErrorMessage(ref reasons, string.Format("'Color Mode' mismatch: {0} / {1}", beamA.colorMode, beamB.colorMode));
ret = false;
}
if (beamA.blendingMode != beamB.blendingMode)
{
AppendErrorMessage(ref reasons, string.Format("'Blending Mode' mismatch: {0} / {1}", beamA.blendingMode, beamB.blendingMode));
ret = false;
}
if (Config.Instance.featureEnabledNoise3D && beamA.isNoiseEnabled != beamB.isNoiseEnabled)
{
AppendErrorMessage(ref reasons, string.Format("'3D Noise' enabled mismatch: {0} / {1}", beamA.noiseMode, beamB.noiseMode));
ret = false;
}
if (Config.Instance.featureEnabledDepthBlend && !forceEnableDepthBlend)
{
#pragma warning disable 0162
if ((beamA.depthBlendDistance > 0) != (beamB.depthBlendDistance > 0))
{
AppendErrorMessage(ref reasons, string.Format("'Opaque Geometry Blending' mismatch: {0} / {1}", beamA.depthBlendDistance, beamB.depthBlendDistance));
ret = false;
}
#pragma warning restore 0162
}
if (Config.Instance.featureEnabledShaderAccuracyHigh && beamA.shaderAccuracy != beamB.shaderAccuracy)
{
AppendErrorMessage(ref reasons, string.Format("'Shader Accuracy' mismatch: {0} / {1}", beamA.shaderAccuracy, beamB.shaderAccuracy));
ret = false;
}
return ret;
}
public static bool CanBeBatched(VolumetricLightBeam beam, ref string reasons)
{
bool ret = true;
if (Config.Instance.actualRenderingMode == RenderingMode.GPUInstancing)
{
if (beam.geomMeshType != MeshType.Shared)
{
AppendErrorMessage(ref reasons, string.Format("{0} is not using shared mesh", beam.name));
ret = false;
}
}
if (Config.Instance.featureEnabledDynamicOcclusion && beam.GetComponent<DynamicOcclusionDepthBuffer>() != null)
{
AppendErrorMessage(ref reasons, string.Format("{0} is using the DynamicOcclusion DepthBuffer feature", beam.name));
ret = false;
}
return ret;
}
static void AppendErrorMessage(ref string message, string toAppend)
{
if (message != "") message += "\n";
message += "- " + toAppend;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: eb22530640d12f542baf4bb5bf6306ff
timeCreated: 1538426592
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,613 @@
#if DEBUG
//#define DEBUG_SHOW_MESH_NORMALS
#endif
#define FORCE_CURRENT_CAMERA_DEPTH_TEXTURE_MODE
#if UNITY_2018_1_OR_NEWER
#define VLB_SRP_SUPPORT // Comment this to disable SRP support
#endif
using UnityEngine;
using System.Collections;
#pragma warning disable 0429, 0162 // Unreachable expression code detected (because of Noise3D.isSupported on mobile)
namespace VLB
{
[AddComponentMenu("")] // hide it from Component search
[ExecuteInEditMode]
[HelpURL(Consts.Help.UrlBeam)]
public class BeamGeometry : MonoBehaviour, MaterialModifier.Interface
{
VolumetricLightBeam m_Master = null;
Matrix4x4 m_ColorGradientMatrix;
MeshType m_CurrentMeshType = MeshType.Shared;
Material m_CustomMaterial = null;
MaterialModifier.Callback m_MaterialModifierCallback = null;
Coroutine m_CoFadeOut = null;
public MeshRenderer meshRenderer { get; private set; }
public MeshFilter meshFilter { get; private set; }
public Mesh coneMesh { get; private set; }
public bool visible
{
get { return meshRenderer.enabled; }
set { meshRenderer.enabled = value; }
}
public int sortingLayerID
{
get { return meshRenderer.sortingLayerID; }
set { meshRenderer.sortingLayerID = value; }
}
public int sortingOrder
{
get { return meshRenderer.sortingOrder; }
set { meshRenderer.sortingOrder = value; }
}
public bool _INTERNAL_IsFadeOutCoroutineRunning { get { return m_CoFadeOut != null; } }
float ComputeFadeOutFactor(Transform camTransform)
{
if (m_Master.isFadeOutEnabled)
{
float distanceCamToBeam = Vector3.SqrMagnitude(meshRenderer.bounds.center - camTransform.position);
return Mathf.InverseLerp(m_Master.fadeOutEnd * m_Master.fadeOutEnd, m_Master.fadeOutBegin * m_Master.fadeOutBegin, distanceCamToBeam);
}
else
{
return 1.0f;
}
}
IEnumerator CoUpdateFadeOut()
{
while (m_Master.isFadeOutEnabled)
{
ComputeFadeOutFactor();
yield return null;
}
SetFadeOutFactorProp(1.0f);
m_CoFadeOut = null;
}
void ComputeFadeOutFactor()
{
var camTransform = Config.Instance.fadeOutCameraTransform;
if (camTransform)
{
float fadeOutFactor = ComputeFadeOutFactor(camTransform);
SetFadeOutFactorProp(fadeOutFactor);
}
else
{
SetFadeOutFactorProp(1.0f);
}
}
void SetFadeOutFactorProp(float value)
{
if (value > 0)
{
meshRenderer.enabled = true;
MaterialChangeStart();
SetMaterialProp(ShaderProperties.FadeOutFactor, value);
MaterialChangeStop();
}
else
{
meshRenderer.enabled = false;
}
}
public void RestartFadeOutCoroutine()
{
#if UNITY_EDITOR
if (Application.isPlaying)
#endif
{
if (m_CoFadeOut != null)
{
StopCoroutine(m_CoFadeOut);
m_CoFadeOut = null;
}
if (m_Master && m_Master.isFadeOutEnabled)
{
m_CoFadeOut = StartCoroutine(CoUpdateFadeOut());
}
}
}
void Start()
{
// Handle copy / paste the LightBeam in Editor
if (!m_Master)
DestroyImmediate(gameObject);
}
void OnDestroy()
{
if (m_CustomMaterial)
{
DestroyImmediate(m_CustomMaterial);
m_CustomMaterial = null;
}
}
#if VLB_SRP_SUPPORT
Camera m_CurrentCameraRenderingSRP = null;
void OnDisable()
{
SRPHelper.UnregisterOnBeginCameraRendering(OnBeginCameraRenderingSRP);
m_CurrentCameraRenderingSRP = null;
}
public static bool isCustomRenderPipelineSupported { get { return true; } }
#else
public static bool isCustomRenderPipelineSupported { get { return false; } }
#endif
bool shouldUseGPUInstancedMaterial
{ get {
return m_Master._INTERNAL_DynamicOcclusionMode != MaterialManager.DynamicOcclusion.DepthTexture // sampler cannot be passed to shader as instanced property
&& Config.Instance.actualRenderingMode == RenderingMode.GPUInstancing;
}}
void OnEnable()
{
// When a GAO is disabled, all its coroutines are killed, so renable them on OnEnable.
RestartFadeOutCoroutine();
#if VLB_SRP_SUPPORT
SRPHelper.RegisterOnBeginCameraRendering(OnBeginCameraRenderingSRP);
#endif
}
public void Initialize(VolumetricLightBeam master)
{
Debug.Assert(master != null);
var customHideFlags = Consts.Internal.ProceduralObjectsHideFlags;
m_Master = master;
transform.SetParent(master.transform, false);
meshRenderer = gameObject.GetOrAddComponent<MeshRenderer>();
meshRenderer.hideFlags = customHideFlags;
meshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
meshRenderer.receiveShadows = false;
meshRenderer.reflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Off; // different reflection probes could break batching with GPU Instancing
meshRenderer.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off;
if (!shouldUseGPUInstancedMaterial)
{
m_CustomMaterial = MaterialManager.NewMaterialTransient(gpuInstanced:false);
ApplyMaterial();
}
if (SortingLayer.IsValid(m_Master.sortingLayerID))
sortingLayerID = m_Master.sortingLayerID;
else
Debug.LogError(string.Format("Beam '{0}' has an invalid sortingLayerID ({1}). Please fix it by setting a valid layer.", Utils.GetPath(m_Master.transform), m_Master.sortingLayerID));
sortingOrder = m_Master.sortingOrder;
meshFilter = gameObject.GetOrAddComponent<MeshFilter>();
meshFilter.hideFlags = customHideFlags;
gameObject.hideFlags = customHideFlags;
#if UNITY_EDITOR
UnityEditor.GameObjectUtility.SetStaticEditorFlags(gameObject, master.GetStaticEditorFlagsForSubObjects());
gameObject.SetSameSceneVisibilityStatesThan(master.gameObject);
#endif
RestartFadeOutCoroutine();
}
/// <summary>
/// Generate the cone mesh and calls UpdateMaterialAndBounds.
/// Since this process involves recreating a new mesh, make sure to not call it at every frame during playtime.
/// </summary>
public void RegenerateMesh()
{
Debug.Assert(m_Master);
if (Config.Instance.geometryOverrideLayer)
gameObject.layer = Config.Instance.geometryLayerID;
else
gameObject.layer = m_Master.gameObject.layer;
gameObject.tag = Config.Instance.geometryTag;
if (coneMesh && m_CurrentMeshType == MeshType.Custom)
{
DestroyImmediate(coneMesh);
}
m_CurrentMeshType = m_Master.geomMeshType;
switch (m_Master.geomMeshType)
{
case MeshType.Custom:
{
coneMesh = MeshGenerator.GenerateConeZ_Radius(1f, 1f, 1f, m_Master.geomCustomSides, m_Master.geomCustomSegments, m_Master.geomCap, Config.Instance.requiresDoubleSidedMesh);
coneMesh.hideFlags = Consts.Internal.ProceduralObjectsHideFlags;
meshFilter.mesh = coneMesh;
break;
}
case MeshType.Shared:
{
coneMesh = GlobalMesh.Get();
meshFilter.sharedMesh = coneMesh;
break;
}
default:
{
Debug.LogError("Unsupported MeshType");
break;
}
}
UpdateMaterialAndBounds();
}
Vector3 ComputeLocalMatrix()
{
// In the VS, we compute the vertices so the whole beam fits into a fixed 2x2x1 box.
// We have to apply some scaling to get the proper beam size.
// This way we have the proper bounds without having to recompute specific bounds foreach beam.
var maxRadius = Mathf.Max(m_Master.coneRadiusStart, m_Master.coneRadiusEnd);
transform.localScale = new Vector3(maxRadius, maxRadius, m_Master.maxGeometryDistance);
transform.localRotation = m_Master.beamInternalLocalRotation;
return transform.localScale;
}
bool isNoiseEnabled { get { return m_Master.isNoiseEnabled && m_Master.noiseIntensity > 0f && Noise3D.isSupported; } } // test Noise3D.isSupported the last
#pragma warning disable 0162
bool isDepthBlendEnabled { get { return BatchingHelper.forceEnableDepthBlend || m_Master.depthBlendDistance > 0f; } }
#pragma warning restore 0162
bool ApplyMaterial()
{
var colorGradient = MaterialManager.ColorGradient.Off;
if (m_Master.colorMode == ColorMode.Gradient)
{
var precision = Utils.GetFloatPackingPrecision();
colorGradient = precision == Utils.FloatPackingPrecision.High ? MaterialManager.ColorGradient.MatrixHigh : MaterialManager.ColorGradient.MatrixLow;
}
Debug.Assert((int)BlendingMode.Additive == (int)MaterialManager.BlendingMode.Additive);
Debug.Assert((int)BlendingMode.SoftAdditive == (int)MaterialManager.BlendingMode.SoftAdditive);
Debug.Assert((int)BlendingMode.TraditionalTransparency == (int)MaterialManager.BlendingMode.TraditionalTransparency);
var staticProps = new MaterialManager.StaticProperties
{
blendingMode = (MaterialManager.BlendingMode)m_Master.blendingMode,
noise3D = isNoiseEnabled ? MaterialManager.Noise3D.On : MaterialManager.Noise3D.Off,
depthBlend = isDepthBlendEnabled ? MaterialManager.DepthBlend.On : MaterialManager.DepthBlend.Off,
colorGradient = colorGradient,
dynamicOcclusion = m_Master._INTERNAL_DynamicOcclusionMode_Runtime,
meshSkewing = m_Master.hasMeshSkewing ? MaterialManager.MeshSkewing.On : MaterialManager.MeshSkewing.Off,
shaderAccuracy = (m_Master.shaderAccuracy == ShaderAccuracy.Fast) ? MaterialManager.ShaderAccuracy.Fast : MaterialManager.ShaderAccuracy.High
};
Material mat = null;
if (!shouldUseGPUInstancedMaterial)
{
mat = m_CustomMaterial;
if(mat)
staticProps.ApplyToMaterial(mat);
}
else
{
mat = MaterialManager.GetInstancedMaterial(m_Master._INTERNAL_InstancedMaterialGroupID, ref staticProps);
}
meshRenderer.material = mat;
return mat != null;
}
public void SetMaterialProp(int nameID, float value)
{
if (m_CustomMaterial)
m_CustomMaterial.SetFloat(nameID, value);
else
MaterialManager.materialPropertyBlock.SetFloat(nameID, value);
}
public void SetMaterialProp(int nameID, Vector4 value)
{
if (m_CustomMaterial)
m_CustomMaterial.SetVector(nameID, value);
else
MaterialManager.materialPropertyBlock.SetVector(nameID, value);
}
public void SetMaterialProp(int nameID, Color value)
{
if (m_CustomMaterial)
m_CustomMaterial.SetColor(nameID, value);
else
MaterialManager.materialPropertyBlock.SetColor(nameID, value);
}
public void SetMaterialProp(int nameID, Matrix4x4 value)
{
if (m_CustomMaterial)
m_CustomMaterial.SetMatrix(nameID, value);
else
MaterialManager.materialPropertyBlock.SetMatrix(nameID, value);
}
public void SetMaterialProp(int nameID, Texture value)
{
if (m_CustomMaterial)
m_CustomMaterial.SetTexture(nameID, value);
else
Debug.LogError("Setting a Texture property to a GPU instanced material is not supported");
}
void MaterialChangeStart()
{
if (m_CustomMaterial == null)
meshRenderer.GetPropertyBlock(MaterialManager.materialPropertyBlock);
}
void MaterialChangeStop()
{
if (m_CustomMaterial == null)
meshRenderer.SetPropertyBlock(MaterialManager.materialPropertyBlock);
}
public void SetDynamicOcclusionCallback(string shaderKeyword, MaterialModifier.Callback cb)
{
m_MaterialModifierCallback = cb;
if (m_CustomMaterial)
{
m_CustomMaterial.SetKeywordEnabled(shaderKeyword, cb != null);
if (cb != null)
cb(this);
}
else
UpdateMaterialAndBounds();
}
public void UpdateMaterialAndBounds()
{
Debug.Assert(m_Master);
if (ApplyMaterial() == false)
{
return;
}
MaterialChangeStart();
{
if (m_CustomMaterial == null)
{
if(m_MaterialModifierCallback != null)
m_MaterialModifierCallback(this);
}
float slopeRad = (m_Master.coneAngle * Mathf.Deg2Rad) / 2; // use coneAngle (instead of spotAngle) which is more correct with the geometry
SetMaterialProp(ShaderProperties.ConeSlopeCosSin, new Vector2(Mathf.Cos(slopeRad), Mathf.Sin(slopeRad)));
// kMinRadius and kMinApexOffset prevents artifacts when fresnel computation is done in the vertex shader
const float kMinRadius = 0.0001f;
var coneRadius = new Vector2(Mathf.Max(m_Master.coneRadiusStart, kMinRadius), Mathf.Max(m_Master.coneRadiusEnd, kMinRadius));
SetMaterialProp(ShaderProperties.ConeRadius, coneRadius);
const float kMinApexOffset = 0.0001f;
float nonNullApex = Mathf.Sign(m_Master.coneApexOffsetZ) * Mathf.Max(Mathf.Abs(m_Master.coneApexOffsetZ), kMinApexOffset);
SetMaterialProp(ShaderProperties.ConeApexOffsetZ, nonNullApex);
if (m_Master.usedColorMode == ColorMode.Flat)
{
SetMaterialProp(ShaderProperties.ColorFlat, m_Master.color);
}
else
{
var precision = Utils.GetFloatPackingPrecision();
m_ColorGradientMatrix = m_Master.colorGradient.SampleInMatrix((int)precision);
// pass the gradient matrix in OnWillRenderObject()
}
float intensityInside, intensityOutside;
m_Master.GetInsideAndOutsideIntensity(out intensityInside, out intensityOutside);
SetMaterialProp(ShaderProperties.AlphaInside, intensityInside);
SetMaterialProp(ShaderProperties.AlphaOutside, intensityOutside);
SetMaterialProp(ShaderProperties.AttenuationLerpLinearQuad, m_Master.attenuationLerpLinearQuad);
SetMaterialProp(ShaderProperties.DistanceFallOff, new Vector3(m_Master.fallOffStart, m_Master.fallOffEnd, m_Master.maxGeometryDistance));
SetMaterialProp(ShaderProperties.DistanceCamClipping, m_Master.cameraClippingDistance);
SetMaterialProp(ShaderProperties.FresnelPow, Mathf.Max(0.001f, m_Master.fresnelPow)); // no pow 0, otherwise will generate inf fresnel and issues on iOS
SetMaterialProp(ShaderProperties.GlareBehind, m_Master.glareBehind);
SetMaterialProp(ShaderProperties.GlareFrontal, m_Master.glareFrontal);
SetMaterialProp(ShaderProperties.DrawCap, m_Master.geomCap ? 1 : 0);
SetMaterialProp(ShaderProperties.TiltVector, m_Master.tiltFactor);
SetMaterialProp(ShaderProperties.AdditionalClippingPlaneWS, m_Master.additionalClippingPlane);
if (isDepthBlendEnabled)
{
SetMaterialProp(ShaderProperties.DepthBlendDistance, m_Master.depthBlendDistance);
}
if (isNoiseEnabled)
{
Noise3D.LoadIfNeeded();
var noiseVelocity = m_Master.noiseVelocityUseGlobal ? Config.Instance.globalNoiseVelocity : m_Master.noiseVelocityLocal;
var noiseScale = m_Master.noiseScaleUseGlobal ? Config.Instance.globalNoiseScale : m_Master.noiseScaleLocal;
SetMaterialProp(ShaderProperties.NoiseVelocityAndScale, new Vector4(
noiseVelocity.x,
noiseVelocity.y,
noiseVelocity.z,
noiseScale));
SetMaterialProp(ShaderProperties.NoiseParam, new Vector2(
m_Master.noiseIntensity,
m_Master.noiseMode == NoiseMode.WorldSpace ? 0f : 1f));
}
var localScale = ComputeLocalMatrix(); // compute matrix before sending it to the shader
if (m_Master.hasMeshSkewing)
{
var localForwardDirectionNormalized = m_Master.skewingLocalForwardDirectionNormalized;
SetMaterialProp(ShaderProperties.LocalForwardDirection, localForwardDirectionNormalized);
if (coneMesh != null) // coneMesh can be null few frames with Dynamic Occlusion & GPU Instancing
{
var localForwardDirectionN = localForwardDirectionNormalized;
localForwardDirectionN /= localForwardDirectionN.z;
localForwardDirectionN *= m_Master.fallOffEnd;
localForwardDirectionN.x /= localScale.x;
localForwardDirectionN.y /= localScale.y;
var bounds = MeshGenerator.ComputeBounds(1f, 1f, 1f);
var min = bounds.min;
var max = bounds.max;
if (localForwardDirectionN.x > 0.0f) max.x += localForwardDirectionN.x;
else min.x += localForwardDirectionN.x;
if (localForwardDirectionN.y > 0.0f) max.y += localForwardDirectionN.y;
else min.y += localForwardDirectionN.y;
bounds.min = min;
bounds.max = max;
coneMesh.bounds = bounds;
}
}
#if VLB_SRP_SUPPORT
// This update is to make QA test 'ReflectionObliqueProjection' pass
UpdateMatricesPropertiesForGPUInstancingSRP();
#endif
}
MaterialChangeStop();
#if DEBUG_SHOW_MESH_NORMALS
for (int vertexInd = 0; vertexInd < coneMesh.vertexCount; vertexInd++)
{
var vertex = coneMesh.vertices[vertexInd];
// apply modification done inside VS
vertex.x *= Mathf.Lerp(coneRadius.x, coneRadius.y, vertex.z);
vertex.y *= Mathf.Lerp(coneRadius.x, coneRadius.y, vertex.z);
vertex.z *= m_Master.fallOffEnd;
var cosSinFlat = new Vector2(vertex.x, vertex.y).normalized;
var normal = new Vector3(cosSinFlat.x * Mathf.Cos(slopeRad), cosSinFlat.y * Mathf.Cos(slopeRad), -Mathf.Sin(slopeRad)).normalized;
vertex = transform.TransformPoint(vertex);
normal = transform.TransformDirection(normal);
Debug.DrawRay(vertex, normal * 0.25f);
}
#endif
}
#if VLB_SRP_SUPPORT
void UpdateMatricesPropertiesForGPUInstancingSRP()
{
if (SRPHelper.IsUsingCustomRenderPipeline() && Config.Instance.actualRenderingMode == RenderingMode.GPUInstancing)
{
SetMaterialProp(ShaderProperties.LocalToWorldMatrix, transform.localToWorldMatrix);
SetMaterialProp(ShaderProperties.WorldToLocalMatrix, transform.worldToLocalMatrix);
}
}
#if UNITY_2019_1_OR_NEWER
void OnBeginCameraRenderingSRP(UnityEngine.Rendering.ScriptableRenderContext context, Camera cam)
#else
void OnBeginCameraRenderingSRP(Camera cam)
#endif
{
m_CurrentCameraRenderingSRP = cam;
}
#endif
void OnWillRenderObject()
{
Camera currentCam = null;
#if VLB_SRP_SUPPORT
if (SRPHelper.IsUsingCustomRenderPipeline())
{
currentCam = m_CurrentCameraRenderingSRP;
}
else
#endif
{
currentCam = Camera.current;
}
OnWillCameraRenderThisBeam(currentCam);
}
void OnWillCameraRenderThisBeam(Camera cam)
{
if (m_Master && cam)
{
if (
#if UNITY_EDITOR
Utils.IsEditorCamera(cam) || // make sure to call UpdateCameraRelatedProperties for editor scene camera
#endif
cam.enabled) // prevent from doing stuff when we render from a previous DynamicOcclusionDepthBuffer's DepthCamera, because the DepthCamera are disabled
{
Debug.Assert(cam.GetComponentInParent<VolumetricLightBeam>() == null);
UpdateCameraRelatedProperties(cam);
m_Master._INTERNAL_OnWillCameraRenderThisBeam(cam);
}
}
}
void UpdateCameraRelatedProperties(Camera cam)
{
if (cam && m_Master)
{
MaterialChangeStart();
{
var camPosOS = m_Master.transform.InverseTransformPoint(cam.transform.position);
var camForwardVectorOSN = transform.InverseTransformDirection(cam.transform.forward).normalized;
float camIsInsideBeamFactor = cam.orthographic ? -1f : m_Master.GetInsideBeamFactorFromObjectSpacePos(camPosOS);
SetMaterialProp(ShaderProperties.CameraParams, new Vector4(camForwardVectorOSN.x, camForwardVectorOSN.y, camForwardVectorOSN.z, camIsInsideBeamFactor));
#if VLB_SRP_SUPPORT
// This update is to be able to move beams without trackChangesDuringPlaytime enabled with SRP & GPU Instancing
UpdateMatricesPropertiesForGPUInstancingSRP();
#endif
if (m_Master.usedColorMode == ColorMode.Gradient)
{
// Send the gradient matrix every frame since it's not a shader's property
SetMaterialProp(ShaderProperties.ColorGradientMatrix, m_ColorGradientMatrix);
}
}
MaterialChangeStop();
#if FORCE_CURRENT_CAMERA_DEPTH_TEXTURE_MODE
if (m_Master.depthBlendDistance > 0f)
cam.depthTextureMode |= DepthTextureMode.Depth;
#endif
}
}
#if UNITY_EDITOR
public bool _EDITOR_IsUsingCustomMaterial { get { return m_CustomMaterial != null; } }
#endif
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 50a48a2b69575db4c8b5bf5aa8186d89
timeCreated: 1504793414
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,40 @@
#if UNITY_EDITOR
using UnityEngine;
namespace VLB
{
struct CachedLightProperties
{
Light light;
float range;
float spotAngle;
Color color;
public CachedLightProperties(Light lightParam)
{
light = lightParam;
range = -1f;
spotAngle = -1f;
color = Color.white;
if (light && light.type == LightType.Spot)
{
range = light.range;
spotAngle = light.spotAngle;
color = light.color;
}
}
public override int GetHashCode() { return light ? light.GetHashCode() : 0; }
public bool Equals(CachedLightProperties other) { return Equals(other, this); }
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
return false;
var other = (CachedLightProperties)obj;
return other.range == range && other.spotAngle == spotAngle && other.color == color;
}
}
}
#endif
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: b455c84cf6c7ad74e84e15749caaccc3
timeCreated: 1510650372
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,535 @@
using UnityEngine;
using UnityEngine.Serialization;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace VLB
{
[HelpURL(Consts.Help.UrlConfig)]
public class Config : ScriptableObject
{
public const string ClassName = "Config";
/// <summary>
/// Override the layer on which the procedural geometry is created or not
/// </summary>
public bool geometryOverrideLayer = Consts.Config.GeometryOverrideLayerDefault;
/// <summary>
/// The layer the procedural geometry gameObject is in (only if geometryOverrideLayer is enabled)
/// </summary>
public int geometryLayerID = Consts.Config.GeometryLayerIDDefault;
/// <summary>
/// The tag applied on the procedural geometry gameObject
/// </summary>
public string geometryTag = Consts.Config.GeometryTagDefault;
/// <summary>
/// Determine in which order beams are rendered compared to other objects.
/// This way for example transparent objects are rendered after opaque objects, and so on.
/// </summary>
public int geometryRenderQueue = (int)Consts.Config.GeometryRenderQueueDefault;
/// <summary>
/// Select the Render Pipeline (Built-In or SRP) in use.
/// </summary>
public RenderPipeline renderPipeline
{
get { return _RenderPipeline; }
set
{
#if UNITY_EDITOR
_RenderPipeline = value;
#else
Debug.LogError("Modifying the RenderPipeline in standalone builds is not permitted");
#endif
}
}
[FormerlySerializedAs("renderPipeline")]
[SerializeField] RenderPipeline _RenderPipeline = Consts.Config.GeometryRenderPipelineDefault;
/// <summary>
/// MultiPass: Use the 2 pass shader. Will generate 2 drawcalls per beam.
/// SinglePass: Use the 1 pass shader. Will generate 1 drawcall per beam.
/// GPUInstancing: Dynamically batch multiple beams to combine and reduce draw calls (Feature only supported in Unity 5.6 or above). More info: https://docs.unity3d.com/Manual/GPUInstancing.html
/// SRPBatcher: Use the SRP Batcher to automatically batch multiple beams and reduce draw calls. Only available when using SRP.
/// </summary>
public RenderingMode renderingMode
{
get { return _RenderingMode; }
set
{
#if UNITY_EDITOR
_RenderingMode = value;
#else
Debug.LogError("Modifying the RenderingMode in standalone builds is not permitted");
#endif
}
}
[FormerlySerializedAs("renderingMode")]
[SerializeField] RenderingMode _RenderingMode = Consts.Config.GeometryRenderingModeDefault;
public bool IsSRPBatcherSupported()
{
// The SRP Batcher Rendering Mode is only compatible when using a SRP
if (renderPipeline == RenderPipeline.BuiltIn) return false;
// SRP Batcher only works with URP and HDRP
var rp = SRPHelper.renderPipelineType;
return rp == SRPHelper.RenderPipeline.URP || rp == SRPHelper.RenderPipeline.HDRP;
}
/// <summary>
/// Actual Rendering Mode used on the current platform
/// </summary>
public RenderingMode actualRenderingMode
{
get
{
#pragma warning disable 0162 // warning CS0162: Unreachable code detected
if (renderingMode == RenderingMode.GPUInstancing && !BatchingHelper.isGpuInstancingSupported) return RenderingMode.SinglePass;
#pragma warning restore 0162
if (renderingMode == RenderingMode.SRPBatcher && !IsSRPBatcherSupported()) return RenderingMode.SinglePass;
if (renderPipeline != RenderPipeline.BuiltIn)
{
// Using a Scriptable Render Pipeline with 'Multi-Pass' Rendering Mode is not supported
if (renderingMode == RenderingMode.MultiPass) return RenderingMode.SinglePass;
}
return renderingMode;
}
}
/// <summary>
/// Depending on the actual Rendering Mode used, returns true if the single pass shader will be used, false otherwise.
/// </summary>
public bool useSinglePassShader { get { return actualRenderingMode != RenderingMode.MultiPass; } }
public bool requiresDoubleSidedMesh { get { return useSinglePassShader; } }
/// <summary>
/// Main shader applied to the cone beam geometry
/// </summary>
public Shader beamShader
{
get
{
#if UNITY_EDITOR
if(_BeamShader == null)
RefreshShader(RefreshShaderFlags.All);
#endif
return _BeamShader;
}
}
/// <summary>
/// Depending on the quality of your screen, you might see some artifacts with high contrast visual (like a white beam over a black background).
/// These is a very common problem known as color banding.
/// To help with this issue, the plugin offers a Dithering factor: it smooths the banding by introducing a subtle pattern of noise.
/// </summary>
public float ditheringFactor = Consts.Config.DitheringFactor;
/// <summary>
/// Number of Sides of the shared cone mesh
/// </summary>
public int sharedMeshSides = Consts.Config.SharedMeshSides;
/// <summary>
/// Number of Segments of the shared cone mesh
/// </summary>
public int sharedMeshSegments = Consts.Config.SharedMeshSegments;
/// <summary>
/// Global 3D Noise texture scaling: higher scale make the noise more visible, but potentially less realistic.
/// </summary>
[Range(Consts.Beam.NoiseScaleMin, Consts.Beam.NoiseScaleMax)]
public float globalNoiseScale = Consts.Beam.NoiseScaleDefault;
/// <summary>
/// Global World Space direction and speed of the noise scrolling, simulating the fog/smoke movement
/// </summary>
public Vector3 globalNoiseVelocity = Consts.Beam.NoiseVelocityDefault;
/// <summary>
/// Tag used to retrieve the camera used to compute the fade out factor on beams
/// </summary>
public string fadeOutCameraTag = Consts.Config.FadeOutCameraTagDefault;
public Transform fadeOutCameraTransform
{
get
{
if (m_CachedFadeOutCamera == null)
{
ForceUpdateFadeOutCamera();
}
return m_CachedFadeOutCamera;
}
}
/// <summary>
/// Call this function if you want to manually change the fadeOutCameraTag property at runtime
/// </summary>
public void ForceUpdateFadeOutCamera()
{
var gao = GameObject.FindGameObjectWithTag(fadeOutCameraTag);
if (gao)
m_CachedFadeOutCamera = gao.transform;
}
/// <summary>
/// 3D Texture storing noise data.
/// </summary>
[HighlightNull]
public Texture3D noiseTexture3D = null;
/// <summary>
/// ParticleSystem prefab instantiated for the Volumetric Dust Particles feature (Unity 5.5 or above)
/// </summary>
[HighlightNull]
public ParticleSystem dustParticlesPrefab = null;
/// <summary>
/// Noise texture for dithering feature
/// </summary>
public Texture2D ditheringNoiseTexture = null;
/// <summary>
/// Off: do not support having a gradient as color.
/// High Only: support gradient color only for devices with Shader Level = 35 or higher.
/// High and Low: support gradient color for all devices.
/// </summary>
public FeatureEnabledColorGradient featureEnabledColorGradient = Consts.Config.FeatureEnabledColorGradientDefault;
/// <summary>
/// Support 'Soft Intersection with Opaque Geometry' feature or not.
/// </summary>
public bool featureEnabledDepthBlend = Consts.Config.FeatureEnabledDefault;
/// <summary>
/// Support 'Noise 3D' feature or not.
/// </summary>
public bool featureEnabledNoise3D = Consts.Config.FeatureEnabledDefault;
/// <summary>
/// Support 'Dynamic Occlusion' features or not.
/// </summary>
public bool featureEnabledDynamicOcclusion = Consts.Config.FeatureEnabledDefault;
/// <summary>
/// Support 'Mesh Skewing' feature or not.
/// </summary>
public bool featureEnabledMeshSkewing = Consts.Config.FeatureEnabledDefault;
/// <summary>
/// Support 'Shader Accuracy' property set to 'High' or not.
/// </summary>
public bool featureEnabledShaderAccuracyHigh = Consts.Config.FeatureEnabledDefault;
// INTERNAL
#pragma warning disable 0414
[SerializeField] int pluginVersion = -1;
[SerializeField] Material _DummyMaterial = null;
#pragma warning restore 0414
[SerializeField] Shader _BeamShader = null;
Transform m_CachedFadeOutCamera = null;
public bool hasRenderPipelineMismatch { get { return (SRPHelper.renderPipelineType == SRPHelper.RenderPipeline.BuiltIn) != (_RenderPipeline == RenderPipeline.BuiltIn); } }
[RuntimeInitializeOnLoadMethod]
static void OnStartup()
{
Instance.m_CachedFadeOutCamera = null;
Instance.RefreshGlobalShaderProperties();
#if UNITY_EDITOR
Instance.RefreshShader(RefreshShaderFlags.All);
#endif
if(Instance.hasRenderPipelineMismatch)
Debug.LogError("It looks like the 'Render Pipeline' is not correctly set in the config. Please make sure to select the proper value depending on your pipeline in use.", Instance);
}
public void Reset()
{
geometryOverrideLayer = Consts.Config.GeometryOverrideLayerDefault;
geometryLayerID = Consts.Config.GeometryLayerIDDefault;
geometryTag = Consts.Config.GeometryTagDefault;
geometryRenderQueue = (int)Consts.Config.GeometryRenderQueueDefault;
sharedMeshSides = Consts.Config.SharedMeshSides;
sharedMeshSegments = Consts.Config.SharedMeshSegments;
globalNoiseScale = Consts.Beam.NoiseScaleDefault;
globalNoiseVelocity = Consts.Beam.NoiseVelocityDefault;
renderPipeline = Consts.Config.GeometryRenderPipelineDefault;
renderingMode = Consts.Config.GeometryRenderingModeDefault;
ditheringFactor = Consts.Config.DitheringFactor;
fadeOutCameraTag = Consts.Config.FadeOutCameraTagDefault;
featureEnabledColorGradient = Consts.Config.FeatureEnabledColorGradientDefault;
featureEnabledDepthBlend = Consts.Config.FeatureEnabledDefault;
featureEnabledNoise3D = Consts.Config.FeatureEnabledDefault;
featureEnabledDynamicOcclusion = Consts.Config.FeatureEnabledDefault;
featureEnabledMeshSkewing = Consts.Config.FeatureEnabledDefault;
featureEnabledShaderAccuracyHigh = Consts.Config.FeatureEnabledDefault;
ResetInternalData();
#if UNITY_EDITOR
GlobalMesh.Destroy();
VolumetricLightBeam._EditorSetAllMeshesDirty();
#endif
}
void RefreshGlobalShaderProperties()
{
Shader.SetGlobalFloat(ShaderProperties.GlobalUsesReversedZBuffer, SystemInfo.usesReversedZBuffer ? 1.0f : 0.0f);
Shader.SetGlobalFloat(ShaderProperties.GlobalDitheringFactor, ditheringFactor);
Shader.SetGlobalTexture(ShaderProperties.GlobalDitheringNoiseTex, ditheringNoiseTexture);
}
#if UNITY_EDITOR
public void _EditorSetRenderingModeAndRefreshShader(RenderingMode mode)
{
renderingMode = mode;
RefreshShader(RefreshShaderFlags.All);
}
void OnValidate()
{
sharedMeshSides = Mathf.Clamp(sharedMeshSides, Consts.Beam.GeomSidesMin, Consts.Beam.GeomSidesMax);
sharedMeshSegments = Mathf.Clamp(sharedMeshSegments, Consts.Beam.GeomSegmentsMin, Consts.Beam.GeomSegmentsMax);
ditheringFactor = Mathf.Clamp01(ditheringFactor);
}
void AutoSelectRenderPipeline()
{
var newPipeline = renderPipeline;
switch (SRPHelper.renderPipelineType)
{
case SRPHelper.RenderPipeline.BuiltIn:
newPipeline = RenderPipeline.BuiltIn;
break;
case SRPHelper.RenderPipeline.HDRP:
newPipeline = RenderPipeline.HDRP;
break;
case SRPHelper.RenderPipeline.URP:
case SRPHelper.RenderPipeline.LWRP:
newPipeline = RenderPipeline.URP;
break;
}
if (newPipeline != renderPipeline)
{
renderPipeline = newPipeline;
EditorUtility.SetDirty(this); // make sure to save this property change
RefreshShader(RefreshShaderFlags.All);
}
}
public static void EditorSelectInstance()
{
Selection.activeObject = Config.Instance; // this will create the instance if it doesn't exist
if (Selection.activeObject == null)
Debug.LogError("Cannot find any Config resource");
}
[System.Flags]
public enum RefreshShaderFlags
{
Reference = 1 << 1,
Dummy = 1 << 2,
All = Reference | Dummy,
}
public void RefreshShader(RefreshShaderFlags flags)
{
if (flags.HasFlag(RefreshShaderFlags.Reference))
{
var prevShader = _BeamShader;
var enabledFeatures = new ShaderGenerator.EnabledFeatures
{
dithering = ditheringFactor > 0.0f,
depthBlend = featureEnabledDepthBlend,
noise3D = featureEnabledNoise3D,
colorGradient = featureEnabledColorGradient,
dynamicOcclusion = featureEnabledDynamicOcclusion,
meshSkewing = featureEnabledMeshSkewing,
shaderAccuracyHigh = featureEnabledShaderAccuracyHigh
};
_BeamShader = ShaderGenerator.Generate(_RenderPipeline, actualRenderingMode, enabledFeatures);
if (_BeamShader != prevShader)
{
EditorUtility.SetDirty(this);
}
}
if (flags.HasFlag(RefreshShaderFlags.Dummy) && _BeamShader != null)
{
bool gpuInstanced = actualRenderingMode == RenderingMode.GPUInstancing;
_DummyMaterial = DummyMaterial.Create(_BeamShader, gpuInstanced);
}
if (_DummyMaterial == null)
{
Debug.LogError("No dummy material referenced to VLB config, please try to reset this asset.", this);
}
RefreshGlobalShaderProperties();
}
#endif // UNITY_EDITOR
public void ResetInternalData()
{
noiseTexture3D = Resources.Load("Noise3D_64x64x64") as Texture3D;
dustParticlesPrefab = Resources.Load("DustParticles", typeof(ParticleSystem)) as ParticleSystem;
ditheringNoiseTexture = Resources.Load("VLBDitheringNoise", typeof(Texture2D)) as Texture2D;
#if UNITY_EDITOR
RefreshShader(RefreshShaderFlags.All);
#endif
}
public ParticleSystem NewVolumetricDustParticles()
{
if (!dustParticlesPrefab)
{
if (Application.isPlaying)
{
Debug.LogError("Failed to instantiate VolumetricDustParticles prefab.");
}
return null;
}
var instance = Instantiate(dustParticlesPrefab);
instance.useAutoRandomSeed = false;
instance.name = "Dust Particles";
instance.gameObject.hideFlags = Consts.Internal.ProceduralObjectsHideFlags;
instance.gameObject.SetActive(true);
return instance;
}
void OnEnable()
{
HandleBackwardCompatibility(pluginVersion, Version.Current);
pluginVersion = Version.Current;
}
void HandleBackwardCompatibility(int serializedVersion, int newVersion)
{
if (serializedVersion == -1) return; // freshly new spawned config: nothing to do
if (serializedVersion == newVersion) return; // same version: nothing to do
#if UNITY_EDITOR
if (serializedVersion < 1830)
{
AutoSelectRenderPipeline();
}
if (serializedVersion < 1950)
{
ResetInternalData(); // retrieve Noise3D texture converted from binary data to texture 3D asset in 1950
EditorUtility.SetDirty(this); // make sure to save this property change
}
if (newVersion > serializedVersion)
{
// Import to keep, we have to regenerate the shader each time the plugin is updated
RefreshShader(RefreshShaderFlags.All);
}
#endif
}
// Singleton management
static Config ms_Instance = null;
public static Config Instance { get { return GetInstance(true); } }
#if UNITY_EDITOR
static bool ms_IsCreatingInstance = false;
public bool IsCurrentlyUsedInstance() { return Instance == this; }
public bool HasValidAssetName()
{
if (name.IndexOf(ConfigOverride.kAssetName) != 0)
return false;
return PlatformHelper.IsValidPlatformSuffix(GetAssetSuffix());
}
public string GetAssetSuffix()
{
var fullname = name;
var strToFind = ConfigOverride.kAssetName;
if (fullname.IndexOf(strToFind) == 0) return fullname.Substring(strToFind.Length);
else return "";
}
#endif
private static Config GetInstance(bool assertIfNotFound)
{
#if UNITY_EDITOR
// Do not cache the instance during editing in order to handle new asset created or moved.
if (!Application.isPlaying || ms_Instance == null)
#else
if (ms_Instance == null)
#endif
{
#if UNITY_EDITOR
if (ms_IsCreatingInstance)
{
Debug.LogError(string.Format("Trying to access Config.Instance while creating it. Breaking before infinite loop."));
return null;
}
#endif
{
var newInstance = Resources.Load<ConfigOverride>(ConfigOverride.kAssetName + PlatformHelper.GetCurrentPlatformSuffix());
if (newInstance == null)
newInstance = Resources.Load<ConfigOverride>(ConfigOverride.kAssetName);
#if UNITY_EDITOR
if (newInstance && newInstance != ms_Instance)
{
ms_Instance = newInstance;
newInstance.RefreshGlobalShaderProperties(); // make sure noise textures are properly loaded as soon as the editor is started
}
#endif
ms_Instance = newInstance;
}
if (ms_Instance == null)
{
#if UNITY_EDITOR
ms_IsCreatingInstance = true;
ms_Instance = ConfigOverride.CreateInstanceOverride();
ms_IsCreatingInstance = false;
ms_Instance.AutoSelectRenderPipeline();
if (Application.isPlaying)
ms_Instance.Reset(); // Reset is not automatically when instancing a ScriptableObject when in playmode
#endif
Debug.Assert(!(assertIfNotFound && ms_Instance == null), string.Format("Can't find any resource of type '{0}'. Make sure you have a ScriptableObject of this type in a 'Resources' folder.", typeof(Config)));
}
}
return ms_Instance;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: fed3f3d91454db34f894abc9ea1d8ec4
timeCreated: 1508325517
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,37 @@
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace VLB
{
[HelpURL(Consts.Help.UrlConfig)]
public class ConfigOverride : Config // useless override, only useful for backward compatibility
{
public const string kAssetName = "VLBConfigOverride";
#if UNITY_EDITOR
public static void CreateFolderAndAsset(Object obj, string folderParent, string folderResources, string assetName)
{
if (!AssetDatabase.IsValidFolder(string.Format("{0}/{1}", folderParent, folderResources)))
AssetDatabase.CreateFolder(folderParent, folderResources);
CreateAsset(obj, string.Format("{0}/{1}/{2}", folderParent, folderResources, assetName));
}
public static void CreateAsset(Object obj, string fullPath)
{
AssetDatabase.CreateAsset(obj, fullPath);
AssetDatabase.SaveAssets();
}
public static ConfigOverride CreateInstanceOverride()
{
var asset = CreateInstance<ConfigOverride>();
Debug.Assert(asset != null);
CreateFolderAndAsset(asset, "Assets", "Resources", kAssetName + ".asset");
return asset;
}
#endif
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: a9536b6488d9e024db1c753e8e90657e
timeCreated: 1556445627
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,153 @@
using UnityEngine;
namespace VLB
{
public static class Consts
{
public const string PluginFolder = "Plugins/VolumetricLightBeam";
public static class Help
{
const string UrlBase = "http://saladgamer.com/vlb-doc/";
const string UrlSuffix = ".html";
public const string UrlBeam = UrlBase + "comp-lightbeam" + UrlSuffix;
public const string UrlDustParticles = UrlBase + "comp-dustparticles" + UrlSuffix;
public const string UrlDynamicOcclusionRaycasting = UrlBase + "comp-dynocclusion-raycasting" + UrlSuffix;
public const string UrlDynamicOcclusionDepthBuffer = UrlBase + "comp-dynocclusion-depthbuffer" + UrlSuffix;
public const string UrlTriggerZone = UrlBase + "comp-triggerzone" + UrlSuffix;
public const string UrlSkewingHandle = UrlBase + "comp-skewinghandle" + UrlSuffix;
public const string UrlEffectFlicker = UrlBase + "comp-effect-flicker" + UrlSuffix;
public const string UrlEffectPulse = UrlBase + "comp-effect-pulse" + UrlSuffix;
public const string UrlConfig = UrlBase + "config" + UrlSuffix;
}
public static class Internal
{
public static readonly bool ProceduralObjectsVisibleInEditor = true;
public static HideFlags ProceduralObjectsHideFlags { get { return ProceduralObjectsVisibleInEditor ? (HideFlags.NotEditable | HideFlags.DontSave) : (HideFlags.HideAndDontSave); } }
}
public static class Beam
{
public static readonly Color FlatColor = Color.white;
public const ColorMode ColorModeDefault = ColorMode.Flat;
public const float IntensityDefault = 1f;
public const float IntensityMin = 0f;
public const float SpotAngleDefault = 35f;
public const float SpotAngleMin = 0.1f;
public const float SpotAngleMax = 179.9f;
public const float ConeRadiusStart = 0.1f;
public const MeshType GeomMeshType = MeshType.Shared;
public const int GeomSidesDefault = 18;
public const int GeomSidesMin = 3;
public const int GeomSidesMax = 256;
public const int GeomSegmentsDefault = 5;
public const int GeomSegmentsMin = 0;
public const int GeomSegmentsMax = 64;
public const bool GeomCap = false;
public const AttenuationEquation AttenuationEquationDefault = AttenuationEquation.Quadratic;
public const float AttenuationCustomBlending = 0.5f;
public const float FallOffStart = 0f;
public const float FallOffEnd = 3f;
public const float FallOffDistancesMinThreshold = 0.01f;
public const float DepthBlendDistance = 2f;
public const float CameraClippingDistance = 0.5f;
public const float FresnelPowMaxValue = 10f;
public const float FresnelPow = 8f;
public const float GlareFrontal = 0.5f;
public const float GlareBehind = 0.5f;
public const NoiseMode NoiseModeDefault = NoiseMode.Disabled;
public const float NoiseIntensityMin = 0.0f;
public const float NoiseIntensityMax = 1.0f;
public const float NoiseIntensityDefault = 0.5f;
public const float NoiseScaleMin = 0.01f;
public const float NoiseScaleMax = 2f;
public const float NoiseScaleDefault = 0.5f;
public static readonly Vector3 NoiseVelocityDefault = new Vector3(0.07f, 0.18f, 0.05f);
public const BlendingMode BlendingModeDefault = BlendingMode.Additive;
public const ShaderAccuracy ShaderAccuracyDefault = ShaderAccuracy.Fast;
public const float FadeOutBeginDefault = -150;
public const float FadeOutEndDefault = -200;
public const Dimensions DimensionsDefault = Dimensions.Dim3D;
public static readonly Vector2 TiltDefault = Vector2.zero;
public static readonly Vector3 SkewingLocalForwardDirectionDefault = Vector3.forward;
public const Transform ClippingPlaneTransformDefault = null;
}
public static class DustParticles
{
public const float AlphaDefault = 0.5f;
public const float SizeDefault = 0.01f;
public const ParticlesDirection DirectionDefault = ParticlesDirection.Random;
public static readonly Vector3 VelocityDefault = new Vector3(0.0f, 0.0f, 0.03f);
public const float DensityDefault = 5f;
public const float DensityMin = 0f;
public const float DensityMax = 1000f;
public static readonly MinMaxRangeFloat SpawnDistanceRangeDefault = new MinMaxRangeFloat(0.0f, 0.7f);
public const bool CullingEnabledDefault = false;
public const float CullingMaxDistanceDefault = 10f;
public const float CullingMaxDistanceMin = 1f;
}
public static class DynOcclusion
{
public static readonly LayerMask LayerMaskDefault = 1; // Default layer
public const float FadeDistanceToSurfaceDefault = 0.25f;
public const DynamicOcclusionUpdateRate UpdateRateDefault = DynamicOcclusionUpdateRate.EveryXFrames;
public const int WaitFramesCountDefault = 3;
public const Dimensions RaycastingDimensionsDefault = Dimensions.Dim3D;
public const bool RaycastingConsiderTriggersDefault = false;
public const float RaycastingMinOccluderAreaDefault = 0.0f;
public const float RaycastingMinSurfaceRatioDefault = 0.5f;
public const float RaycastingMinSurfaceRatioMin = 50f;
public const float RaycastingMinSurfaceRatioMax = 100f;
public const float RaycastingMaxSurfaceDotDefault = 0.25f; // around 75 degrees
public const float RaycastingMaxSurfaceAngleMin = 45f;
public const float RaycastingMaxSurfaceAngleMax = 90f;
public const PlaneAlignment RaycastingPlaneAlignmentDefault = PlaneAlignment.Surface;
public const float RaycastingPlaneOffsetDefault = 0.1f;
public const int DepthBufferDepthMapResolutionDefault = 32;
public const bool DepthBufferOcclusionCullingDefault = true;
}
public static class Effects
{
public const EffectAbstractBase.ComponentsToChange ComponentsToChangeDefault = (EffectAbstractBase.ComponentsToChange)int.MaxValue;
public const bool RestoreBaseIntensityDefault = true;
public const float FrequencyDefault = 10.0f;
public const bool PerformPausesDefault = false;
public static readonly MinMaxRangeFloat FlickeringDurationDefault = new MinMaxRangeFloat(1.0f, 4.0f);
public static readonly MinMaxRangeFloat PauseDurationDefault = new MinMaxRangeFloat(0.0f, 1.0f);
public static readonly MinMaxRangeFloat IntensityAmplitudeDefault = new MinMaxRangeFloat(-1.0f, 1.0f);
public const float SmoothingDefault = 0.05f;
}
public static class Config
{
public const bool GeometryOverrideLayerDefault = true;
public const int GeometryLayerIDDefault = 1;
public const string GeometryTagDefault = "Untagged";
public const string FadeOutCameraTagDefault = "MainCamera";
public const RenderQueue GeometryRenderQueueDefault = RenderQueue.Transparent;
public const RenderPipeline GeometryRenderPipelineDefault = RenderPipeline.BuiltIn;
public const RenderingMode GeometryRenderingModeDefault = RenderingMode.SinglePass;
public const int Noise3DSizeDefault = 64;
public const int SharedMeshSides = 24;
public const int SharedMeshSegments = 5;
public const float DitheringFactor = 0.0f;
public const bool FeatureEnabledDefault = true;
public const FeatureEnabledColorGradient FeatureEnabledColorGradientDefault = FeatureEnabledColorGradient.HighOnly;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: aa337c7dd20e18b49b4b0b5b42e59e96
timeCreated: 1508860935
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,50 @@
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace VLB
{
public static class DummyMaterial
{
#if UNITY_EDITOR
static string GetPath(Shader shader)
{
const string kDummyFilename = "VLBDummyMaterial.mat";
const string kDummyPathFallback = "Assets/" + Consts.PluginFolder + "/Shaders/" + kDummyFilename;
if (shader == null)
return kDummyPathFallback;
var shaderPath = AssetDatabase.GetAssetPath(shader);
if (string.IsNullOrEmpty(shaderPath))
return kDummyPathFallback;
var shaderFolder = System.IO.Path.GetDirectoryName(shaderPath);
return System.IO.Path.Combine(shaderFolder, kDummyFilename);
}
/// <summary>
/// Create a dummy material with the proper instancing flag to prevent from stripping away needed shader variants when exporting build
/// </summary>
public static Material Create(Shader shader, bool gpuInstanced)
{
if (shader == null)
return null;
string path = GetPath(shader);
var dummyMat = AssetDatabase.LoadAssetAtPath<Material>(path);
if (dummyMat == null
|| dummyMat.shader != shader
|| BatchingHelper.IsGpuInstancingEnabled(dummyMat) != gpuInstanced)
{
dummyMat = MaterialManager.NewMaterialPersistent(shader, gpuInstanced);
if (dummyMat)
AssetDatabase.CreateAsset(dummyMat, path);
}
return dummyMat;
}
#endif
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 1cb6a728dad16224884d567a0a1d618d
timeCreated: 1546757296
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,182 @@
using UnityEngine;
using UnityEngine.Serialization;
namespace VLB
{
[AddComponentMenu("")] // hide it from Component search
[DisallowMultipleComponent]
[RequireComponent(typeof(VolumetricLightBeam))]
public abstract class DynamicOcclusionAbstractBase : MonoBehaviour
{
public const string ClassName = "DynamicOcclusionAbstractBase";
/// <summary>
/// How often will the occlusion be processed?
/// Try to update the occlusion as rarely as possible to keep good performance.
/// </summary>
public DynamicOcclusionUpdateRate updateRate = Consts.DynOcclusion.UpdateRateDefault;
/// <summary>
/// How many frames we wait between 2 occlusion tests?
/// If you want your beam to be super responsive to the changes of your environment, update it every frame by setting 1.
/// If you want to save on performance, we recommend to wait few frames between each update by setting a higher value.
/// </summary>
[FormerlySerializedAs("waitFrameCount")]
public int waitXFrames = Consts.DynOcclusion.WaitFramesCountDefault;
/// <summary>
/// Manually process the occlusion.
/// You have to call this function in order to update the occlusion when using DynamicOcclusionUpdateRate.Never.
/// </summary>
public void ProcessOcclusionManually() { ProcessOcclusion(ProcessOcclusionSource.User); }
public event System.Action onOcclusionProcessed;
public static bool _INTERNAL_ApplyRandomFrameOffset = true;
protected enum ProcessOcclusionSource
{
RenderLoop,
OnEnable,
EditorUpdate,
User,
}
protected void ProcessOcclusion(ProcessOcclusionSource source)
{
if (!Config.Instance.featureEnabledDynamicOcclusion)
return;
Debug.Assert(!Application.isPlaying || m_LastFrameRendered != Time.frameCount, "ProcessOcclusion has been called twice on the same frame, which is forbidden");
Debug.Assert(m_Master);
bool occlusionSuccess = OnProcessOcclusion(source);
if(onOcclusionProcessed != null)
onOcclusionProcessed();
if (m_Master)
{
Debug.Assert(m_MaterialModifierCallbackCached != null);
m_Master._INTERNAL_SetDynamicOcclusionCallback(GetShaderKeyword(), occlusionSuccess ? m_MaterialModifierCallbackCached : (MaterialModifier.Callback)(null));
}
if (updateRate.HasFlag(DynamicOcclusionUpdateRate.OnBeamMove))
m_TransformPacked = transform.GetWorldPacked();
bool firstTime = m_LastFrameRendered < 0;
m_LastFrameRendered = Time.frameCount;
if (firstTime && _INTERNAL_ApplyRandomFrameOffset)
{
m_LastFrameRendered += Random.Range(0, waitXFrames); // add a random offset to prevent from updating texture for all beams having the same wait value
}
}
TransformUtils.Packed m_TransformPacked;
int m_LastFrameRendered = int.MinValue;
public int _INTERNAL_LastFrameRendered { get { return m_LastFrameRendered; } } // for unit tests
protected VolumetricLightBeam m_Master = null;
protected MaterialModifier.Callback m_MaterialModifierCallbackCached = null;
protected abstract string GetShaderKeyword();
protected abstract MaterialManager.DynamicOcclusion GetDynamicOcclusionMode();
protected abstract bool OnProcessOcclusion(ProcessOcclusionSource source);
protected abstract void OnModifyMaterialCallback(MaterialModifier.Interface owner);
protected abstract void OnEnablePostValidate();
protected virtual void OnValidateProperties()
{
waitXFrames = Mathf.Clamp(waitXFrames, 1, 60);
}
protected virtual void Awake()
{
m_Master = GetComponent<VolumetricLightBeam>();
Debug.Assert(m_Master);
m_Master._INTERNAL_DynamicOcclusionMode = GetDynamicOcclusionMode();
}
protected virtual void OnDestroy()
{
m_Master._INTERNAL_DynamicOcclusionMode = MaterialManager.DynamicOcclusion.Off;
DisableOcclusion();
}
protected virtual void OnEnable()
{
// cache the delegate to prevent from being inlined as '() => OnModifyMaterialCallback' when calling _INTERNAL_SetDynamicOcclusionCallback and from generating GC garbage
m_MaterialModifierCallbackCached = OnModifyMaterialCallback;
OnValidateProperties();
OnEnablePostValidate();
#if UNITY_EDITOR
if (Application.isPlaying)
#endif
{
m_Master.onWillCameraRenderThisBeam += OnWillCameraRender;
if (!updateRate.HasFlag(DynamicOcclusionUpdateRate.Never))
m_Master.RegisterOnBeamGeometryInitializedCallback(() => ProcessOcclusion(ProcessOcclusionSource.OnEnable));
}
}
protected virtual void OnDisable()
{
#if UNITY_EDITOR
if (Application.isPlaying)
#endif
{
m_Master.onWillCameraRenderThisBeam -= OnWillCameraRender;
}
DisableOcclusion();
}
#if UNITY_EDITOR
protected virtual void OnValidate()
{
OnValidateProperties();
}
#endif
void OnWillCameraRender(Camera cam)
{
Debug.Assert(Application.isPlaying);
if (cam != null && cam.enabled
&& Time.frameCount != m_LastFrameRendered) // prevent from updating multiple times if there are more than 1 camera
{
bool shouldUpdate = false;
if (!shouldUpdate && updateRate.HasFlag(DynamicOcclusionUpdateRate.OnBeamMove))
{
if (!m_TransformPacked.IsSame(transform))
shouldUpdate = true;
}
if (!shouldUpdate && updateRate.HasFlag(DynamicOcclusionUpdateRate.EveryXFrames))
{
if (Time.frameCount >= m_LastFrameRendered + waitXFrames)
shouldUpdate = true;
}
if (shouldUpdate)
ProcessOcclusion(ProcessOcclusionSource.RenderLoop);
}
}
void DisableOcclusion()
{
m_Master._INTERNAL_SetDynamicOcclusionCallback(GetShaderKeyword(), null);
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: c87e171f8942d184e958eba76fd39108
timeCreated: 1510650372
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,229 @@
using UnityEngine;
using System.Collections;
namespace VLB
{
[ExecuteInEditMode]
[HelpURL(Consts.Help.UrlDynamicOcclusionDepthBuffer)]
public class DynamicOcclusionDepthBuffer : DynamicOcclusionAbstractBase
{
public new const string ClassName = "DynamicOcclusionDepthBuffer";
/// <summary>
/// The beam can only be occluded by objects located on the layers matching this mask.
/// It's very important to set it as restrictive as possible (checking only the layers which are necessary)
/// to perform a more efficient process in order to increase the performance.
/// It should NOT include the layer on which the beams are generated.
/// </summary>
public LayerMask layerMask = Consts.DynOcclusion.LayerMaskDefault;
/// <summary>
/// Whether or not the virtual camera will use occlusion culling during rendering from the beam's POV.
/// </summary>
public bool useOcclusionCulling = Consts.DynOcclusion.DepthBufferOcclusionCullingDefault;
/// <summary>
/// Controls how large the depth texture captured by the virtual camera is.
/// The lower the resolution, the better the performance, but the less accurate the rendering.
/// </summary>
public int depthMapResolution = Consts.DynOcclusion.DepthBufferDepthMapResolutionDefault;
/// <summary>
/// Fade out the beam before the occlusion surface in order to soften the transition.
/// </summary>
public float fadeDistanceToSurface = Consts.DynOcclusion.FadeDistanceToSurfaceDefault;
protected override string GetShaderKeyword() { return "VLB_OCCLUSION_DEPTH_TEXTURE"; }
protected override MaterialManager.DynamicOcclusion GetDynamicOcclusionMode() { return MaterialManager.DynamicOcclusion.DepthTexture; }
Camera m_DepthCamera = null;
bool m_NeedToUpdateOcclusionNextFrame = false;
void ProcessOcclusionInternal()
{
UpdateDepthCameraPropertiesAccordingToBeam();
m_DepthCamera.Render();
}
protected override bool OnProcessOcclusion(ProcessOcclusionSource source)
{
Debug.Assert(m_Master && m_DepthCamera);
if (source == ProcessOcclusionSource.RenderLoop && SRPHelper.IsUsingCustomRenderPipeline()) // Recursive rendering is not supported on SRP
m_NeedToUpdateOcclusionNextFrame = true;
else
ProcessOcclusionInternal();
return true;
}
void Update()
{
if (m_NeedToUpdateOcclusionNextFrame && m_Master && m_DepthCamera)
{
ProcessOcclusionInternal();
m_NeedToUpdateOcclusionNextFrame = false;
}
}
void UpdateDepthCameraPropertiesAccordingToBeam()
{
Debug.Assert(m_Master);
Debug.Assert(m_DepthCamera);
float apexDist = m_Master.coneApexOffsetZ;
m_DepthCamera.transform.localPosition = m_Master.beamLocalForward * (-apexDist);
m_DepthCamera.transform.localRotation = m_Master.beamInternalLocalRotation;
var scale = m_Master.lossyScale;
if (!Mathf.Approximately(scale.y * scale.z, 0))
{
const float kMinNearClipPlane = 0.1f; // should be the same than in shader
m_DepthCamera.nearClipPlane = Mathf.Max(apexDist, kMinNearClipPlane) * scale.z;
m_DepthCamera.farClipPlane = (m_Master.maxGeometryDistance + apexDist) * scale.z;
m_DepthCamera.aspect = scale.x / scale.y;
m_DepthCamera.fieldOfView = scale.y * m_Master.coneAngle / scale.z;
}
}
public bool HasLayerMaskIssues()
{
if(Config.Instance.geometryOverrideLayer)
{
int layerBit = 1 << Config.Instance.geometryLayerID;
return ((layerMask.value & layerBit) == layerBit);
}
return false;
}
protected override void OnValidateProperties()
{
base.OnValidateProperties();
depthMapResolution = Mathf.Clamp(Mathf.NextPowerOfTwo(depthMapResolution), 8, 2048);
fadeDistanceToSurface = Mathf.Max(fadeDistanceToSurface, 0f);
}
void InstantiateOrActivateDepthCamera()
{
if (m_DepthCamera != null)
{
m_DepthCamera.gameObject.SetActive(true); // active it in case it has been disabled by OnDisable()
}
else
{
m_DepthCamera = new GameObject("Depth Camera").AddComponent<Camera>();
if (m_DepthCamera && m_Master)
{
m_DepthCamera.enabled = false;
m_DepthCamera.cullingMask = layerMask;
m_DepthCamera.clearFlags = CameraClearFlags.Depth;
m_DepthCamera.depthTextureMode = DepthTextureMode.Depth;
m_DepthCamera.renderingPath = RenderingPath.VertexLit; // faster
m_DepthCamera.useOcclusionCulling = useOcclusionCulling;
m_DepthCamera.gameObject.hideFlags = Consts.Internal.ProceduralObjectsHideFlags;
m_DepthCamera.transform.SetParent(transform, false);
var rt = new RenderTexture(depthMapResolution, depthMapResolution, 16, RenderTextureFormat.Depth);
m_DepthCamera.targetTexture = rt;
UpdateDepthCameraPropertiesAccordingToBeam();
#if UNITY_EDITOR
UnityEditor.GameObjectUtility.SetStaticEditorFlags(m_DepthCamera.gameObject, m_Master.GetStaticEditorFlagsForSubObjects());
m_DepthCamera.gameObject.SetSameSceneVisibilityStatesThan(m_Master.gameObject);
#endif
}
}
}
protected override void OnEnablePostValidate()
{
InstantiateOrActivateDepthCamera();
}
protected override void OnDisable()
{
base.OnDisable();
if (m_DepthCamera) m_DepthCamera.gameObject.SetActive(false);
}
protected override void Awake()
{
base.Awake();
#if UNITY_EDITOR
MarkMaterialAsDirty();
#endif
}
protected override void OnDestroy()
{
base.OnDestroy();
DestroyDepthCamera();
#if UNITY_EDITOR
MarkMaterialAsDirty();
#endif
}
void DestroyDepthCamera()
{
if (m_DepthCamera)
{
if (m_DepthCamera.targetTexture)
{
m_DepthCamera.targetTexture.Release();
DestroyImmediate(m_DepthCamera.targetTexture);
m_DepthCamera.targetTexture = null;
}
DestroyImmediate(m_DepthCamera.gameObject); // Make sure to delete the GAO
m_DepthCamera = null;
}
}
protected override void OnModifyMaterialCallback(MaterialModifier.Interface owner)
{
Debug.Assert(owner != null);
owner.SetMaterialProp(ShaderProperties.DynamicOcclusionDepthTexture, m_DepthCamera.targetTexture);
owner.SetMaterialProp(ShaderProperties.DynamicOcclusionDepthProps, fadeDistanceToSurface);
}
#if UNITY_EDITOR
bool m_NeedToReinstantiateDepthCamera = false;
void MarkMaterialAsDirty()
{
// when adding/removing this component in editor, we might need to switch from a GPU Instanced material to a custom one,
// since this feature doesn't support GPU Instancing
if (!Application.isPlaying)
m_Master._EditorSetBeamGeomDirty();
}
protected override void OnValidate()
{
base.OnValidate();
m_NeedToReinstantiateDepthCamera = true;
}
void LateUpdate()
{
if (!Application.isPlaying)
{
if (m_NeedToReinstantiateDepthCamera)
{
DestroyDepthCamera();
InstantiateOrActivateDepthCamera();
m_NeedToReinstantiateDepthCamera = false;
}
if(m_Master && m_Master.enabled)
ProcessOcclusion(ProcessOcclusionSource.EditorUpdate);
}
}
#endif // UNITY_EDITOR
}
}
@@ -0,0 +1,28 @@
fileFormatVersion: 2
guid: fdb43fff075aa0b4b995dcfaed06dc44
labels:
- volumetric
- light
- lighting
- ray
- shaft
- beam
- density
- vr
- dynamic
- spot
- spotlight
- fog
- noise
- occlusion
- procedural
timeCreated: 1577531941
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: f4140fedf3f72d7448a4e55ea9db44ab, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,472 @@
#if DEBUG
//#define DEBUG_SHOW_RAYCAST_LINES
#endif
using UnityEngine;
using UnityEngine.Serialization;
namespace VLB
{
[ExecuteInEditMode]
[HelpURL(Consts.Help.UrlDynamicOcclusionRaycasting)]
public class DynamicOcclusionRaycasting : DynamicOcclusionAbstractBase
{
public new const string ClassName = "DynamicOcclusionRaycasting";
/// <summary>
/// Should it interact with 2D or 3D occluders?
/// </summary>
public Dimensions dimensions = Consts.DynOcclusion.RaycastingDimensionsDefault;
/// <summary>
/// The beam can only be occluded by objects located on the layers matching this mask.
/// It's very important to set it as restrictive as possible (checking only the layers which are necessary)
/// to perform a more efficient process in order to increase the performance.
/// </summary>
public LayerMask layerMask = Consts.DynOcclusion.LayerMaskDefault;
/// <summary>
/// Should this beam be occluded by triggers or not?
/// </summary>
public bool considerTriggers = Consts.DynOcclusion.RaycastingConsiderTriggersDefault;
/// <summary>
/// Minimum 'area' of the collider to become an occluder.
/// Colliders smaller than this value will not block the beam.
/// </summary>
public float minOccluderArea = Consts.DynOcclusion.RaycastingMinOccluderAreaDefault;
/// <summary>
/// Approximated percentage of the beam to collide with the surface in order to be considered as occluder
/// </summary>
public float minSurfaceRatio = Consts.DynOcclusion.RaycastingMinSurfaceRatioDefault;
/// <summary>
/// Max angle (in degrees) between the beam and the surface in order to be considered as occluder
/// </summary>
public float maxSurfaceDot = Consts.DynOcclusion.RaycastingMaxSurfaceDotDefault;
/// <summary>
/// Alignment of the computed clipping plane:
/// </summary>
public PlaneAlignment planeAlignment = Consts.DynOcclusion.RaycastingPlaneAlignmentDefault;
/// <summary>
/// Translate the plane. We recommend to set a small positive offset in order to handle non-flat surface better.
/// </summary>
public float planeOffset = Consts.DynOcclusion.RaycastingPlaneOffsetDefault;
/// <summary>
/// Fade out the beam before the computed clipping plane in order to soften the transition.
/// </summary>
[FormerlySerializedAs("fadeDistanceToPlane")]
public float fadeDistanceToSurface = Consts.DynOcclusion.FadeDistanceToSurfaceDefault;
[System.Obsolete("Use 'fadeDistanceToSurface' instead")]
public float fadeDistanceToPlane { get { return fadeDistanceToSurface; } set { fadeDistanceToSurface = value; } }
public bool IsColliderHiddenByDynamicOccluder(Collider collider)
{
Debug.Assert(collider, "You should pass a valid Collider to VLB.DynamicOcclusion.IsColliderHiddenByDynamicOccluder");
if (!planeEquationWS.IsValid())
return false;
var isInside = GeometryUtility.TestPlanesAABB(new Plane[] { planeEquationWS }, collider.bounds);
return !isInside;
}
public struct HitResult
{
public HitResult(ref RaycastHit hit3D)
{
point = hit3D.point;
normal = hit3D.normal;
distance = hit3D.distance;
collider3D = hit3D.collider;
collider2D = null;
}
public HitResult(ref RaycastHit2D hit2D)
{
point = hit2D.point;
normal = hit2D.normal;
distance = hit2D.distance;
collider2D = hit2D.collider;
collider3D = null;
}
public Vector3 point;
public Vector3 normal;
public float distance;
Collider2D collider2D;
Collider collider3D;
public bool hasCollider { get { return collider2D || collider3D; } }
public string name
{
get
{
if (collider3D) return collider3D.name;
else if (collider2D) return collider2D.name;
else return "null collider";
}
}
public Bounds bounds
{
get
{
if (collider3D) return collider3D.bounds;
else if (collider2D) return collider2D.bounds;
else return new Bounds();
}
}
public void SetNull() { collider2D = null; collider3D = null; }
}
/// <summary>
/// Get information about the current occluder hit by the beam.
/// Can be null if the beam is not occluded.
/// </summary>
HitResult m_CurrentHit;
protected override string GetShaderKeyword() { return "VLB_OCCLUSION_CLIPPING_PLANE"; }
protected override MaterialManager.DynamicOcclusion GetDynamicOcclusionMode() { return MaterialManager.DynamicOcclusion.ClippingPlane; }
float m_RangeMultiplier = 1f;
public Plane planeEquationWS { get; private set; }
#if UNITY_EDITOR
public HitResult editorCurrentHitResult { get { return m_CurrentHit; } }
public struct EditorDebugData
{
public int lastFrameUpdate;
}
public EditorDebugData editorDebugData;
public static bool editorShowDebugPlane = true;
public static bool editorRaycastAtEachFrame = true;
private static bool editorPrefsLoaded = false;
public static void EditorLoadPrefs()
{
if (!editorPrefsLoaded)
{
editorShowDebugPlane = UnityEditor.EditorPrefs.GetBool(EditorPrefsStrings.DynOcclusion.PrefShowDebugPlane, true);
editorRaycastAtEachFrame = UnityEditor.EditorPrefs.GetBool(EditorPrefsStrings.DynOcclusion.PrefRaycastingEditor, true);
editorPrefsLoaded = true;
}
}
#endif
protected override void OnValidateProperties()
{
base.OnValidateProperties();
minOccluderArea = Mathf.Max(minOccluderArea, 0f);
fadeDistanceToSurface = Mathf.Max(fadeDistanceToSurface, 0f);
}
protected override void OnEnablePostValidate()
{
m_CurrentHit.SetNull();
#if UNITY_EDITOR
EditorLoadPrefs();
editorDebugData.lastFrameUpdate = 0;
#endif
}
protected override void OnDisable()
{
base.OnDisable();
SetHitNull();
}
void Start()
{
if (Application.isPlaying)
{
var triggerZone = GetComponent<TriggerZone>();
if (triggerZone)
{
m_RangeMultiplier = Mathf.Max(1f, triggerZone.rangeMultiplier);
}
}
}
Vector3 GetRandomVectorAround(Vector3 direction, float angleDiff)
{
var halfAngle = angleDiff * 0.5f;
return Quaternion.Euler(Random.Range(-halfAngle, halfAngle), Random.Range(-halfAngle, halfAngle), Random.Range(-halfAngle, halfAngle)) * direction;
}
QueryTriggerInteraction queryTriggerInteraction { get { return considerTriggers ? QueryTriggerInteraction.Collide : QueryTriggerInteraction.Ignore; } }
float raycastMaxDistance { get { return m_Master.raycastDistance * m_RangeMultiplier * m_Master.lossyScale.z; } }
HitResult GetBestHit(Vector3 rayPos, Vector3 rayDir)
{
return dimensions == Dimensions.Dim2D ? GetBestHit2D(rayPos, rayDir) : GetBestHit3D(rayPos, rayDir);
}
HitResult GetBestHit3D(Vector3 rayPos, Vector3 rayDir)
{
var hits = Physics.RaycastAll(rayPos, rayDir, raycastMaxDistance, layerMask.value, queryTriggerInteraction);
int bestHit = -1;
float bestLength = float.MaxValue;
for (int i = 0; i < hits.Length; ++i)
{
if (hits[i].collider.gameObject != m_Master.gameObject) // skip collider from TriggerZone
{
if (hits[i].collider.bounds.GetMaxArea2D() >= minOccluderArea)
{
if (hits[i].distance < bestLength)
{
bestLength = hits[i].distance;
bestHit = i;
}
}
}
}
#if DEBUG_SHOW_RAYCAST_LINES
Debug.DrawLine(rayPos, rayPos + rayDir * raycastMaxDistance, bestHit != -1 ? Color.green : Color.red);
#endif
if (bestHit != -1)
return new HitResult(ref hits[bestHit]);
else
return new HitResult();
}
HitResult GetBestHit2D(Vector3 rayPos, Vector3 rayDir)
{
var hits = Physics2D.RaycastAll(new Vector2(rayPos.x, rayPos.y), new Vector2(rayDir.x, rayDir.y), raycastMaxDistance, layerMask.value);
int bestHit = -1;
float bestLength = float.MaxValue;
for (int i = 0; i < hits.Length; ++i)
{
if (!considerTriggers && hits[i].collider.isTrigger) // do not query triggers if considerTriggers is disabled
continue;
if (hits[i].collider.gameObject != m_Master.gameObject) // skip collider from TriggerZone
{
if (hits[i].collider.bounds.GetMaxArea2D() >= minOccluderArea)
{
if (hits[i].distance < bestLength)
{
bestLength = hits[i].distance;
bestHit = i;
}
}
}
}
#if DEBUG_SHOW_RAYCAST_LINES
Debug.DrawLine(rayPos, rayPos + rayDir * raycastMaxDistance, bestHit != -1 ? Color.green : Color.red);
#endif
if (bestHit != -1)
return new HitResult(ref hits[bestHit]);
else
return new HitResult();
}
enum Direction {
Up,
Down,
Left,
Right,
Max2D = Down,
Max3D = Right,
};
uint m_PrevNonSubHitDirectionId = 0;
uint GetDirectionCount() { return dimensions == Dimensions.Dim2D ? ((uint)Direction.Max2D + 1) : ((uint)Direction.Max3D + 1); }
Vector3 GetDirection(uint dirInt)
{
dirInt = dirInt % GetDirectionCount();
switch (dirInt)
{
case (uint)Direction.Up: return m_Master.raycastGlobalUp;
case (uint)Direction.Right: return m_Master.raycastGlobalRight;
case (uint)Direction.Down: return -m_Master.raycastGlobalUp;
case (uint)Direction.Left: return -m_Master.raycastGlobalRight;
}
return Vector3.zero;
}
bool IsHitValid(ref HitResult hit, Vector3 forwardVec)
{
if (hit.hasCollider)
{
float dot = Vector3.Dot(hit.normal, -forwardVec);
return dot >= maxSurfaceDot;
}
return false;
}
protected override bool OnProcessOcclusion(ProcessOcclusionSource source)
{
#if UNITY_EDITOR
editorDebugData.lastFrameUpdate = Time.frameCount;
#endif
var raycastGlobalForward = m_Master.raycastGlobalForward;
var bestHit = GetBestHit(transform.position, raycastGlobalForward);
if (IsHitValid(ref bestHit, raycastGlobalForward))
{
if (minSurfaceRatio > 0.5f)
{
var raycastDistance = m_Master.raycastDistance;
for (uint i = 0; i < GetDirectionCount(); i++)
{
var dir3 = GetDirection(i + m_PrevNonSubHitDirectionId) * (minSurfaceRatio * 2 - 1);
dir3.Scale(transform.localScale);
var startPt = transform.position + dir3 * m_Master.coneRadiusStart;
var newPt = transform.position + dir3 * m_Master.coneRadiusEnd + raycastGlobalForward * raycastDistance;
var bestHitSub = GetBestHit(startPt, (newPt - startPt).normalized);
if (IsHitValid(ref bestHitSub, raycastGlobalForward))
{
if (bestHitSub.distance > bestHit.distance)
{
bestHit = bestHitSub;
}
}
else
{
m_PrevNonSubHitDirectionId = i;
bestHit.SetNull();
break;
}
}
}
}
else
{
bestHit.SetNull();
}
SetHit(ref bestHit);
return bestHit.hasCollider;
}
void SetHit(ref HitResult hit)
{
if (!hit.hasCollider)
{
SetHitNull();
}
else
{
switch (planeAlignment)
{
case PlaneAlignment.Beam:
SetClippingPlane(new Plane(-m_Master.raycastGlobalForward, hit.point));
break;
case PlaneAlignment.Surface:
default:
SetClippingPlane(new Plane(hit.normal, hit.point));
break;
}
m_CurrentHit = hit;
}
}
void SetHitNull()
{
SetClippingPlaneOff();
m_CurrentHit.SetNull();
}
protected override void OnModifyMaterialCallback(MaterialModifier.Interface owner)
{
Debug.Assert(owner != null);
var planeWS = planeEquationWS;
owner.SetMaterialProp(ShaderProperties.DynamicOcclusionClippingPlaneWS, new Vector4(planeWS.normal.x, planeWS.normal.y, planeWS.normal.z, planeWS.distance));
owner.SetMaterialProp(ShaderProperties.DynamicOcclusionClippingPlaneProps, fadeDistanceToSurface);
}
void SetClippingPlane(Plane planeWS)
{
planeWS = planeWS.TranslateCustom(planeWS.normal * planeOffset);
SetPlaneWS(planeWS);
Debug.Assert(m_MaterialModifierCallbackCached != null);
m_Master._INTERNAL_SetDynamicOcclusionCallback(GetShaderKeyword(), m_MaterialModifierCallbackCached);
}
void SetClippingPlaneOff()
{
SetPlaneWS(new Plane());
m_Master._INTERNAL_SetDynamicOcclusionCallback(GetShaderKeyword(), null);
}
void SetPlaneWS(Plane planeWS)
{
planeEquationWS = planeWS;
#if UNITY_EDITOR
m_DebugPlaneLocal = planeWS;
if (m_DebugPlaneLocal.IsValid())
{
float dist;
if (m_DebugPlaneLocal.Raycast(new Ray(transform.position, m_Master.raycastGlobalForward), out dist))
m_DebugPlaneLocal.distance = dist; // compute local distance
}
#endif
}
#if UNITY_EDITOR
void LateUpdate()
{
if (!Application.isPlaying)
{
// In Editor, process raycasts at each frame update
if (!editorRaycastAtEachFrame)
SetHitNull();
else
ProcessOcclusion(ProcessOcclusionSource.EditorUpdate);
}
}
Plane m_DebugPlaneLocal;
void OnDrawGizmos()
{
if (!editorShowDebugPlane)
return;
if (m_DebugPlaneLocal.IsValid())
{
var planePos = transform.position + m_DebugPlaneLocal.distance * m_Master.raycastGlobalForward;
float planeDistNormalized = Mathf.Clamp01(Mathf.InverseLerp(0f, m_Master.raycastDistance, m_DebugPlaneLocal.distance));
float planeSize = Mathf.Lerp(m_Master.coneRadiusStart, m_Master.coneRadiusEnd, planeDistNormalized);
var color = m_Master.ComputeColorAtDepth(planeDistNormalized).ComputeComplementaryColor(false);
Utils.GizmosDrawPlane(
m_DebugPlaneLocal.normal,
planePos,
color,
Matrix4x4.identity,
planeSize,
planeSize * 0.5f);
UnityEditor.Handles.color = color;
UnityEditor.Handles.DrawWireDisc(planePos,
m_DebugPlaneLocal.normal,
planeSize * (minSurfaceRatio * 2 - 1));
}
}
#endif
}
}
@@ -0,0 +1,28 @@
fileFormatVersion: 2
guid: 558dd6d156642974780bc97cd2ab1bd2
labels:
- volumetric
- light
- lighting
- ray
- shaft
- beam
- density
- vr
- dynamic
- spot
- spotlight
- fog
- noise
- occlusion
- procedural
timeCreated: 1513615359
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: f4140fedf3f72d7448a4e55ea9db44ab, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
public static class EditorPrefsStrings
{
#if UNITY_EDITOR
public static class Beam
{
public const string PrefShowTiltDir = "VLB_BEAM_SHOWTILTDIR";
}
public static class DynOcclusion
{
public const string PrefShowDebugPlane = "VLB_DYNOCCLUSION_SHOWDEBUGPLANE";
public const string PrefRaycastingEditor = "VLB_DYNOCCLUSION_RAYCASTINGEDITOR";
}
#endif
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a26cf2b22b6b7cd47ab72ea8f98b0a73
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,80 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace VLB
{
[AddComponentMenu("")] // hide it from Component search
[DisallowMultipleComponent]
public class EffectAbstractBase : MonoBehaviour
{
public const string ClassName = "EffectAbstractBase";
[System.Flags]
public enum ComponentsToChange
{
UnityLight = 1 << 0,
VolumetricLightBeam = 1 << 1,
VolumetricDustParticles = 1 << 2,
}
/// <summary>
/// Decide which component to change among:
/// - Unity's Light
/// - Volumetric Light Beam
/// - Volumetric Dust Particles
/// </summary>
public ComponentsToChange componentsToChange = Consts.Effects.ComponentsToChangeDefault;
/// <summary>
/// Restore the default intensity when this component is disabled.
/// </summary>
public bool restoreBaseIntensity = Consts.Effects.RestoreBaseIntensityDefault;
protected VolumetricLightBeam m_Beam = null;
protected Light m_Light = null;
protected VolumetricDustParticles m_Particles = null;
protected float m_BaseIntensityBeamInside = 0.0f;
protected float m_BaseIntensityBeamOutside = 0.0f;
protected float m_BaseIntensityLight = 0.0f;
protected void SetAdditiveIntensity(float additive)
{
if (componentsToChange.HasFlag(ComponentsToChange.VolumetricLightBeam) && m_Beam)
{
m_Beam.intensityInside = Mathf.Max(0.0f, m_BaseIntensityBeamInside + additive);
m_Beam.intensityOutside = Mathf.Max(0.0f, m_BaseIntensityBeamOutside + additive);
}
if (componentsToChange.HasFlag(ComponentsToChange.UnityLight) && m_Light)
m_Light.intensity = Mathf.Max(0.0f, m_BaseIntensityLight + additive);
if (componentsToChange.HasFlag(ComponentsToChange.VolumetricDustParticles) && m_Particles)
m_Particles.alphaAdditionalRuntime = 1.0f + additive;
}
void Awake()
{
m_Beam = GetComponent<VolumetricLightBeam>();
m_Light = GetComponent<Light>();
m_Particles = GetComponent<VolumetricDustParticles>();
m_BaseIntensityBeamInside = m_Beam ? m_Beam.intensityInside : 0.0f;
m_BaseIntensityBeamOutside = m_Beam ? m_Beam.intensityOutside : 0.0f;
m_BaseIntensityLight = m_Light ? m_Light.intensity : 0.0f;
}
protected virtual void OnEnable()
{
StopAllCoroutines();
}
void OnDisable()
{
StopAllCoroutines();
if (restoreBaseIntensity)
SetAdditiveIntensity(0.0f);
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 2a25e62b0a3d171468d83a4db6928379
timeCreated: 1617641637
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,106 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace VLB
{
[HelpURL(Consts.Help.UrlEffectFlicker)]
public class EffectFlicker : EffectAbstractBase
{
public new const string ClassName = "EffectFlicker";
/// <summary>
/// Frequency of flickering.
/// Higher value means the flickering will occur faster.
/// </summary>
[Range(1.0f, 60.0f)]
public float frequency = Consts.Effects.FrequencyDefault;
/// <summary>
/// If enabled, pauses will be added between 2 flickering sequences.
/// </summary>
public bool performPauses = Consts.Effects.PerformPausesDefault;
/// <summary>
/// The duration of a flickering sequence.
/// A random value will be picked each time inside that range.
/// </summary>
[MinMaxRange(0.0f, 10.0f)]
public MinMaxRangeFloat flickeringDuration = Consts.Effects.FlickeringDurationDefault;
/// <summary>
/// The duration of a pause sequence.
/// A random value will be picked each time inside that range.
/// </summary>
[MinMaxRange(0.0f, 10.0f)]
public MinMaxRangeFloat pauseDuration = Consts.Effects.PauseDurationDefault;
/// <summary>
/// The amplitude of intensity change which will be applied to the Light and/or Beam.
/// A random value will be picked each time inside that range.
/// </summary>
[MinMaxRange(-5.0f, 5.0f)]
public MinMaxRangeFloat intensityAmplitude = Consts.Effects.IntensityAmplitudeDefault;
/// <summary>
/// How much intensity change will be smoothed.
/// Higher value means the more smoothing.
/// </summary>
[Range(0.0f, 0.25f)]
public float smoothing = Consts.Effects.SmoothingDefault;
float m_CurrentAdditiveIntensity = 0.0f;
protected override void OnEnable()
{
base.OnEnable();
StartCoroutine(CoUpdate());
}
IEnumerator CoUpdate()
{
while(true)
{
yield return CoFlicker();
float remaining = pauseDuration.randomValue;
do
{
remaining -= Time.deltaTime;
yield return null;
}
while (performPauses && remaining > 0.0f);
}
}
IEnumerator CoFlicker()
{
float remainingDuration = flickeringDuration.randomValue;
float lastTime = Time.deltaTime;
while (!performPauses || remainingDuration > 0.0f)
{
Debug.Assert(frequency > 0.0f);
float freqDuration = 1.0f / frequency;
yield return CoChangeIntensity(freqDuration, intensityAmplitude.randomValue);
remainingDuration -= freqDuration;
}
}
IEnumerator CoChangeIntensity(float expectedDuration, float nextIntensity)
{
float velocity = 0.0f;
float t = 0.0f;
while (t < expectedDuration)
{
m_CurrentAdditiveIntensity = Mathf.SmoothDamp(m_CurrentAdditiveIntensity, nextIntensity, ref velocity, smoothing);
SetAdditiveIntensity(m_CurrentAdditiveIntensity);
t += Time.deltaTime;
yield return null;
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: de6c986ea168387458edac98bcd43dfd
timeCreated: 1617798048
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 6dfec383bc9ad504891027fdab8062b1, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,47 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace VLB
{
[HelpURL(Consts.Help.UrlEffectPulse)]
public class EffectPulse : EffectAbstractBase
{
public new const string ClassName = "EffectPulse";
/// <summary>
/// Frequency of pulsing.
/// Higher value means the pulsing will occur faster.
/// </summary>
[Range(0.1f, 60.0f)]
public float frequency = Consts.Effects.FrequencyDefault;
/// <summary>
/// The amplitude of intensity change which will be applied to the Light and/or Beam.
/// A random value will be picked each time inside that range.
/// </summary>
[MinMaxRange(-5.0f, 5.0f)]
public MinMaxRangeFloat intensityAmplitude = Consts.Effects.IntensityAmplitudeDefault;
protected override void OnEnable()
{
base.OnEnable();
StartCoroutine(CoUpdate());
}
IEnumerator CoUpdate()
{
var t = 0.0f;
while (true)
{
var cos = Mathf.Sin(frequency * t);
var value = intensityAmplitude.GetLerpedValue(cos * 0.5f + 0.5f);
SetAdditiveIntensity(value);
yield return null;
t += Time.deltaTime;
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: d4d69db65d6d47040b7bd02d847008da
timeCreated: 1617798061
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 6dfec383bc9ad504891027fdab8062b1, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,136 @@
namespace VLB
{
public enum FeatureEnabledColorGradient
{
Off, // Do not support having a gradient as color
HighOnly, // Support gradient color only for devices with Shader Level = 35 or higher
HighAndLow // Support gradient color for all devices
};
public enum ColorMode
{
Flat, // Apply a flat/plain/single color
Gradient // Apply a gradient
}
public enum AttenuationEquation
{
Linear = 0, // Simple linear attenuation.
Quadratic = 1, // Quadratic attenuation, which usually gives more realistic results.
Blend = 2 // Custom blending mix between linear and quadratic attenuation formulas. Use attenuationEquation property to tweak the mix.
}
public enum BlendingMode
{
Additive,
SoftAdditive,
TraditionalTransparency,
}
public enum ShaderAccuracy
{
/// <summary> Default accuracy: a lot of computation are done on the vertex shader to maximize performance. </summary>
Fast,
/// <summary> Higher accuracy: most of the computation are done on the pixel shader to maximize graphical quality at some performance cost. </summary>
High,
}
public enum NoiseMode
{
/// <summary> 3D Noise is disabled </summary>
Disabled,
/// <summary> 3D Noise is enabled: noise will look static compared to the world </summary>
WorldSpace,
/// <summary> 3D Noise is enabled: noise will look static compared to the beam position </summary>
LocalSpace,
}
public enum MeshType
{
Shared, // Use the global shared mesh (recommended setting, since it will save a lot on memory). Will use the geometry properties set on Config.
Custom, // Use a custom mesh instead. Will use the geometry properties set on the beam.
}
public enum RenderPipeline
{
/// <summary> Unity's built-in Render Pipeline. </summary>
BuiltIn,
/// <summary> Use the Universal Render Pipeline. </summary>
URP,
/// <summary> Use the High Definition Render Pipeline. </summary>
HDRP,
}
public enum RenderingMode
{
/// <summary> Use the 2 pass shader. Will generate 2 drawcalls per beam (Not compatible with custom Render Pipeline such as HDRP and LWRP).</summary>
MultiPass,
/// <summary> Use the 1 pass shader. Will generate 1 drawcall per beam. </summary>
SinglePass,
/// <summary> Dynamically batch multiple beams to combine and reduce draw calls. </summary>
GPUInstancing,
/// <summary> Use the SRP Batcher to automatically batch multiple beams and reduce draw calls. Only available when using SRP. </summary>
SRPBatcher,
}
public enum RenderQueue
{
/// Specify a custom render queue.
Custom = 0,
/// This render queue is rendered before any others.
Background = 1000,
/// Opaque geometry uses this queue.
Geometry = 2000,
/// Alpha tested geometry uses this queue.
AlphaTest = 2450,
/// Last render queue that is considered "opaque".
GeometryLast = 2500,
/// This render queue is rendered after Geometry and AlphaTest, in back-to-front order.
Transparent = 3000,
/// This render queue is meant for overlay effects.
Overlay = 4000,
}
public enum Dimensions
{
/// <summary> 3D </summary>
Dim3D,
/// <summary> 2D </summary>
Dim2D
}
public enum PlaneAlignment
{
/// <summary>Align the plane to the surface normal which blocks the beam. Works better for large occluders such as floors and walls.</summary>
Surface,
/// <summary>Keep the plane aligned with the beam direction. Works better with more complex occluders or with corners.</summary>
Beam
}
[System.Flags]
public enum DynamicOcclusionUpdateRate
{
Never = 1 << 0,
OnEnable = 1 << 1,
OnBeamMove = 1 << 2,
EveryXFrames = 1 << 3,
OnBeamMoveAndEveryXFrames = OnBeamMove | EveryXFrames,
}
public enum ParticlesDirection
{
/// <summary> Random direction. </summary>
Random,
/// <summary> Particles follow the velicity direction in local space (Z is along the beam). </summary>
LocalSpace,
/// <summary> Particles follow the velicity direction in world space. </summary>
WorldSpace
};
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 368667f41a4ca5a48bb73bc39c67ab69
timeCreated: 1521635218
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,45 @@
using UnityEngine;
using System.Collections;
namespace VLB
{
public static class GlobalMesh
{
public static Mesh Get()
{
var needDoubleSided = Config.Instance.requiresDoubleSidedMesh;
if (ms_Mesh == null
|| ms_DoubleSided != needDoubleSided)
{
Destroy();
ms_Mesh = MeshGenerator.GenerateConeZ_Radius(
lengthZ: 1f,
radiusStart: 1f,
radiusEnd: 1f,
numSides: Config.Instance.sharedMeshSides,
numSegments: Config.Instance.sharedMeshSegments,
cap: true,
doubleSided: needDoubleSided);
ms_Mesh.hideFlags = Consts.Internal.ProceduralObjectsHideFlags;
ms_DoubleSided = needDoubleSided;
}
return ms_Mesh;
}
public static void Destroy()
{
if (ms_Mesh != null)
{
GameObject.DestroyImmediate(ms_Mesh);
ms_Mesh = null;
}
}
static Mesh ms_Mesh = null;
static bool ms_DoubleSided = false;
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 0e4bae9c762e9004296c04beb33798ed
timeCreated: 1529559345
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace VLB
{
/// <summary>
/// Highlight in red in inspector in not set
/// </summary>
public sealed class HighlightNullAttribute : PropertyAttribute { }
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(HighlightNullAttribute))]
public class HighlightNullDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (property.propertyType != SerializedPropertyType.ObjectReference)
{
EditorGUI.LabelField(position, label.text, "Only valid for object references");
return;
}
if (property.objectReferenceValue == null)
EditorGUI.DrawRect(position, Color.red);
EditorGUI.ObjectField(position, property, label);
}
}
#endif
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: e7b5213963066994eaa8a96b953deb97
timeCreated: 1511460052
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,191 @@
using UnityEngine;
using System.Collections;
namespace VLB
{
public static class MaterialManager
{
public static MaterialPropertyBlock materialPropertyBlock = new MaterialPropertyBlock();
public enum BlendingMode
{
Additive,
SoftAdditive,
TraditionalTransparency,
Count
}
public enum Noise3D
{
Off,
On,
Count
}
public enum DepthBlend
{
Off,
On,
Count
}
public enum ColorGradient
{
Off,
MatrixLow,
MatrixHigh,
Count
}
public enum DynamicOcclusion
{
Off,
ClippingPlane,
DepthTexture,
Count
}
public enum MeshSkewing
{
Off,
On,
Count
}
public enum ShaderAccuracy
{
Fast,
High,
Count
}
static readonly UnityEngine.Rendering.BlendMode[] BlendingMode_SrcFactor = new UnityEngine.Rendering.BlendMode[(int)BlendingMode.Count]
{
UnityEngine.Rendering.BlendMode.One, // Additive
UnityEngine.Rendering.BlendMode.OneMinusDstColor, // SoftAdditive
UnityEngine.Rendering.BlendMode.SrcAlpha, // TraditionalTransparency
};
static readonly UnityEngine.Rendering.BlendMode[] BlendingMode_DstFactor = new UnityEngine.Rendering.BlendMode[(int)BlendingMode.Count]
{
UnityEngine.Rendering.BlendMode.One, // Additive
UnityEngine.Rendering.BlendMode.One, // SoftAdditive
UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha, // TraditionalTransparency
};
static readonly bool[] BlendingMode_AlphaAsBlack = new bool[(int)BlendingMode.Count]
{
true, // Additive
true, // SoftAdditive
false, // TraditionalTransparency
};
static int kStaticPropertiesCount = (int)BlendingMode.Count * (int)Noise3D.Count * (int)DepthBlend.Count * (int)ColorGradient.Count * (int)DynamicOcclusion.Count * (int)MeshSkewing.Count * (int)ShaderAccuracy.Count;
public struct StaticProperties
{
public BlendingMode blendingMode;
public Noise3D noise3D;
public DepthBlend depthBlend;
public ColorGradient colorGradient;
public DynamicOcclusion dynamicOcclusion;
public MeshSkewing meshSkewing;
public ShaderAccuracy shaderAccuracy;
int blendingModeID { get { return (int)blendingMode; } }
int noise3DID { get { return Config.Instance.featureEnabledNoise3D ? (int)noise3D : 0; } }
int depthBlendID { get { return Config.Instance.featureEnabledDepthBlend ? (int)depthBlend : 0; } }
int colorGradientID { get { return Config.Instance.featureEnabledColorGradient != FeatureEnabledColorGradient.Off ? (int)colorGradient : 0; } }
int dynamicOcclusionID { get { return Config.Instance.featureEnabledDynamicOcclusion ? (int)dynamicOcclusion : 0; } }
int meshSkewingID { get { return Config.Instance.featureEnabledMeshSkewing ? (int)meshSkewing : 0; } }
int shaderAccuracyID { get { return Config.Instance.featureEnabledShaderAccuracyHigh ? (int)shaderAccuracy : 0; } }
public int materialID
{
get
{
return (((((((blendingModeID)
* (int)Noise3D.Count + noise3DID)
* (int)DepthBlend.Count + depthBlendID)
* (int)ColorGradient.Count + colorGradientID)
* (int)DynamicOcclusion.Count + dynamicOcclusionID)
* (int)MeshSkewing.Count + meshSkewingID)
* (int)ShaderAccuracy.Count + shaderAccuracyID)
;
}
}
public void ApplyToMaterial(Material mat)
{
mat.SetKeywordEnabled(ShaderKeywords.AlphaAsBlack, BlendingMode_AlphaAsBlack[(int)blendingMode]);
mat.SetKeywordEnabled(ShaderKeywords.ColorGradientMatrixLow, colorGradient == ColorGradient.MatrixLow);
mat.SetKeywordEnabled(ShaderKeywords.ColorGradientMatrixHigh, colorGradient == ColorGradient.MatrixHigh);
mat.SetKeywordEnabled(ShaderKeywords.DepthBlend, depthBlend == DepthBlend.On);
mat.SetKeywordEnabled(ShaderKeywords.Noise3D, noise3D == Noise3D.On);
mat.SetKeywordEnabled(ShaderKeywords.OcclusionClippingPlane, dynamicOcclusion == DynamicOcclusion.ClippingPlane);
mat.SetKeywordEnabled(ShaderKeywords.OcclusionDepthTexture, dynamicOcclusion == DynamicOcclusion.DepthTexture);
mat.SetKeywordEnabled(ShaderKeywords.MeshSkewing, meshSkewing == MeshSkewing.On);
mat.SetKeywordEnabled(ShaderKeywords.ShaderAccuracyHigh, shaderAccuracy == ShaderAccuracy.High);
mat.SetInt(ShaderProperties.BlendSrcFactor, (int)BlendingMode_SrcFactor[(int)blendingMode]);
mat.SetInt(ShaderProperties.BlendDstFactor, (int)BlendingMode_DstFactor[(int)blendingMode]);
}
}
public static Material NewMaterialTransient(bool gpuInstanced)
{
var material = NewMaterialPersistent(Config.Instance.beamShader, gpuInstanced);
if (material)
{
material.hideFlags = Consts.Internal.ProceduralObjectsHideFlags;
material.renderQueue = Config.Instance.geometryRenderQueue;
}
return material;
}
public static Material NewMaterialPersistent(Shader shader, bool gpuInstanced)
{
if (!shader)
{
Debug.LogError("Invalid VLB Shader. Please try to reset the VLB Config asset or reinstall the plugin.");
return null;
}
var material = new Material(shader);
BatchingHelper.SetMaterialProperties(material, gpuInstanced);
return material;
}
class MaterialsGroup
{
public Material[] materials = new Material[kStaticPropertiesCount];
}
static Hashtable ms_MaterialsGroup = new Hashtable(1);
public static Material GetInstancedMaterial(uint groupID, ref StaticProperties staticProps) // pass StaticProperties by ref to avoid per value arg copy
{
MaterialsGroup group = (MaterialsGroup)ms_MaterialsGroup[groupID];
if (group == null)
{
group = new MaterialsGroup();
ms_MaterialsGroup[groupID] = group;
}
int matID = staticProps.materialID;
Debug.Assert(matID < kStaticPropertiesCount);
var mat = group.materials[matID];
if (mat == null)
{
mat = NewMaterialTransient(gpuInstanced:true);
if(mat)
{
group.materials[matID] = mat;
staticProps.ApplyToMaterial(mat);
}
}
return mat;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 6f3b53f8b071fa74997d838b723bcfa7
timeCreated: 1537852951
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,18 @@
using UnityEngine;
namespace VLB
{
public static class MaterialModifier
{
public interface Interface
{
void SetMaterialProp(int nameID, float value);
void SetMaterialProp(int nameID, Vector4 value);
void SetMaterialProp(int nameID, Color value);
void SetMaterialProp(int nameID, Matrix4x4 value);
void SetMaterialProp(int nameID, Texture value);
}
public delegate void Callback(Interface owner);
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 51c15cec95cca374a92f3e0c0d7b7fe8
timeCreated: 1577644734
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,258 @@
using UnityEngine;
namespace VLB
{
public static class MeshGenerator
{
const float kMinTruncatedRadius = 0.001f;
static float GetAngleOffset(int numSides)
{
// rotate square beams so they are properly oriented to scale them more easily
return numSides == 4 ? (Mathf.PI * 0.25f) : 0f;
}
public static Mesh GenerateConeZ_RadiusAndAngle(float lengthZ, float radiusStart, float coneAngle, int numSides, int numSegments, bool cap, bool doubleSided)
{
Debug.Assert(lengthZ > 0f);
Debug.Assert(coneAngle > 0f && coneAngle < 180f);
var radiusEnd = lengthZ * Mathf.Tan(coneAngle * Mathf.Deg2Rad * 0.5f);
return GenerateConeZ_Radius(lengthZ, radiusStart, radiusEnd, numSides, numSegments, cap, doubleSided);
}
public static Mesh GenerateConeZ_Angle(float lengthZ, float coneAngle, int numSides, int numSegments, bool cap, bool doubleSided)
{
return GenerateConeZ_RadiusAndAngle(lengthZ, 0f, coneAngle, numSides, numSegments, cap, doubleSided);
}
public static Mesh GenerateConeZ_Radius(float lengthZ, float radiusStart, float radiusEnd, int numSides, int numSegments, bool cap, bool doubleSided)
{
Debug.Assert(lengthZ > 0f);
Debug.Assert(radiusStart >= 0f);
Debug.Assert(numSides >= 3);
Debug.Assert(numSegments >= 0);
var mesh = new Mesh();
bool genCap = cap && radiusStart > 0f;
// We use the XY position of the vertices to compute the cone normal in the shader.
// With a perfectly sharp cone, we couldn't compute accurate normals at its top.
radiusStart = Mathf.Max(radiusStart, kMinTruncatedRadius);
int vertCountSides = numSides * (numSegments + 2);
int vertCountTotal = vertCountSides;
if (genCap)
vertCountTotal += numSides + 1;
// VERTICES
{
float angleOffset = GetAngleOffset(numSides);
var vertices = new Vector3[vertCountTotal];
for (int i = 0; i < numSides; i++)
{
float angle = angleOffset + 2 * Mathf.PI * i / numSides;
float angleCos = Mathf.Cos(angle);
float angleSin = Mathf.Sin(angle);
for (int seg = 0; seg < numSegments + 2; seg++)
{
float tseg = (float)seg / (numSegments + 1);
Debug.Assert(tseg >= 0f && tseg <= 1f);
float radius = Mathf.Lerp(radiusStart, radiusEnd, tseg);
vertices[i + seg * numSides] = new Vector3(radius * angleCos, radius * angleSin, tseg * lengthZ);
}
}
if (genCap)
{
int ind = vertCountSides;
vertices[ind] = Vector3.zero;
ind++;
for (int i = 0; i < numSides; i++)
{
float angle = angleOffset + 2 * Mathf.PI * i / numSides;
float angleCos = Mathf.Cos(angle);
float angleSin = Mathf.Sin(angle);
vertices[ind] = new Vector3(radiusStart * angleCos, radiusStart * angleSin, 0f);
ind++;
}
Debug.Assert(ind == vertices.Length);
}
if (!doubleSided)
{
mesh.vertices = vertices;
}
else
{
var vertices2 = new Vector3[vertices.Length * 2];
vertices.CopyTo(vertices2, 0);
vertices.CopyTo(vertices2, vertices.Length);
mesh.vertices = vertices2;
}
}
// UV (used to flags vertices as sides or cap)
// X: 0 = sides ; 1 = cap
// Y: 0 = front face ; 1 = back face (doubleSided only)
{
var uv = new Vector2[vertCountTotal];
int ind = 0;
for (int i = 0; i < vertCountSides; i++)
uv[ind++] = Vector2.zero;
if (genCap)
{
for (int i = 0; i < numSides + 1; i++)
uv[ind++] = new Vector2(1, 0);
}
Debug.Assert(ind == uv.Length);
if (!doubleSided)
{
mesh.uv = uv;
}
else
{
var uv2 = new Vector2[uv.Length * 2];
uv.CopyTo(uv2, 0);
uv.CopyTo(uv2, uv.Length);
for (int i = 0; i < uv.Length; i++)
{
var value = uv2[i + uv.Length];
uv2[i + uv.Length] = new Vector2(value.x, 1);
}
mesh.uv = uv2;
}
}
// INDICES
{
int triCountSides = numSides * 2 * Mathf.Max(numSegments + 1, 1);
int indCountSides = triCountSides * 3;
int indCountTotal = indCountSides;
if (genCap)
indCountTotal += numSides * 3;
var indices = new int[indCountTotal];
int ind = 0;
for (int i = 0; i < numSides; i++)
{
int ip1 = i + 1;
if (ip1 == numSides)
ip1 = 0;
for (int k = 0; k < numSegments + 1; ++k)
{
var offset = k * numSides;
indices[ind++] = offset + i;
indices[ind++] = offset + ip1;
indices[ind++] = offset + i + numSides;
indices[ind++] = offset + ip1 + numSides;
indices[ind++] = offset + i + numSides;
indices[ind++] = offset + ip1;
}
}
if (genCap)
{
for (int i = 0; i < numSides - 1; i++)
{
indices[ind++] = vertCountSides;
indices[ind++] = vertCountSides + i + 2;
indices[ind++] = vertCountSides + i + 1;
}
indices[ind++] = vertCountSides;
indices[ind++] = vertCountSides + 1;
indices[ind++] = vertCountSides + numSides;
}
Debug.Assert(ind == indices.Length);
if (!doubleSided)
{
mesh.triangles = indices;
}
else
{
var indices2 = new int[indices.Length * 2];
indices.CopyTo(indices2, 0);
for (int i = 0; i < indices.Length; i += 3)
{
indices2[indices.Length + i + 0] = indices[i + 0] + vertCountTotal;
indices2[indices.Length + i + 1] = indices[i + 2] + vertCountTotal;
indices2[indices.Length + i + 2] = indices[i + 1] + vertCountTotal;
}
mesh.triangles = indices2;
}
}
mesh.bounds = ComputeBounds(lengthZ, radiusStart, radiusEnd);
Debug.Assert(mesh.vertexCount == GetVertexCount(numSides, numSegments, genCap, doubleSided));
Debug.Assert(mesh.triangles.Length == GetIndicesCount(numSides, numSegments, genCap, doubleSided));
return mesh;
}
public static Bounds ComputeBounds(float lengthZ, float radiusStart, float radiusEnd)
{
float maxDiameter = Mathf.Max(radiusStart, radiusEnd) * 2;
return new Bounds(
new Vector3(0, 0, lengthZ * 0.5f),
new Vector3(maxDiameter, maxDiameter, lengthZ)
);
}
public static int GetVertexCount(int numSides, int numSegments, bool geomCap, bool doubleSided)
{
Debug.Assert(numSides >= 2);
Debug.Assert(numSegments >= 0);
int count = numSides * (numSegments + 2);
if (geomCap) count += numSides + 1;
if (doubleSided) count *= 2;
return count;
}
public static int GetIndicesCount(int numSides, int numSegments, bool geomCap, bool doubleSided)
{
Debug.Assert(numSides >= 2);
Debug.Assert(numSegments >= 0);
int count = numSides * (numSegments + 1) * 2 * 3;
if (geomCap) count += numSides * 3;
if (doubleSided) count *= 2;
return count;
}
public static int GetSharedMeshVertexCount()
{
return GetVertexCount(Config.Instance.sharedMeshSides, Config.Instance.sharedMeshSegments, true, Config.Instance.requiresDoubleSidedMesh);
}
public static int GetSharedMeshIndicesCount()
{
return GetIndicesCount(Config.Instance.sharedMeshSides, Config.Instance.sharedMeshSegments, true, Config.Instance.requiresDoubleSidedMesh);
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: bc0059e5d7876fb4b97e542dcc0e8350
timeCreated: 1504627701
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,96 @@
using System;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace VLB
{
[Serializable]
public struct MinMaxRangeFloat
{
public float minValue { get { return m_MinValue; } }
public float maxValue { get { return m_MaxValue; } }
public float randomValue { get { return UnityEngine.Random.Range(minValue, maxValue); } }
public Vector2 asVector2 { get { return new Vector2(minValue, maxValue); } }
public float GetLerpedValue(float lerp01) { return Mathf.Lerp(minValue, maxValue, lerp01); }
public MinMaxRangeFloat(float min, float max) { m_MinValue = min; m_MaxValue = max; Debug.Assert(min <= max); }
[SerializeField] float m_MinValue;
[SerializeField] float m_MaxValue;
}
public class MinMaxRangeAttribute : System.Attribute
{
public float minValue { get; private set; }
public float maxValue { get; private set; }
public MinMaxRangeAttribute(float min, float max) { minValue = min; maxValue = max; Debug.Assert(min <= max); }
}
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(MinMaxRangeFloat), true)]
public class MinMaxRangeFloatPropertyDrawer : PropertyDrawer
{
static float RoundFloat(float f) { return (float)Math.Round(f * 100f) / 100f; }
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
label = EditorGUI.BeginProperty(position, label, property);
{
position = EditorGUI.PrefixLabel(position, label);
var propMin = property.FindPropertyRelative("m_MinValue");
Debug.AssertFormat(propMin != null, "Failed to find property 'MinMaxRangeFloat.m_MinValue'");
var propMax = property.FindPropertyRelative("m_MaxValue");
Debug.AssertFormat(propMax != null, "Failed to find property 'MinMaxRangeFloat.m_MaxValue'");
float valueMin = RoundFloat(propMin.floatValue);
float valueMax = RoundFloat(propMax.floatValue);
float rangeMin = 0.0f, rangeMax = 1.0f;
var ranges = (MinMaxRangeAttribute[])fieldInfo.GetCustomAttributes(typeof(MinMaxRangeAttribute), true);
if (ranges.Length > 0)
{
rangeMin = ranges[0].minValue;
rangeMax = ranges[0].maxValue;
}
const float kBoundsFieldWidth = 40.0f;
const float kWidthOffset = 5.0f;
EditorGUI.showMixedValue = propMin.hasMultipleDifferentValues || propMax.hasMultipleDifferentValues;
{
EditorGUI.BeginChangeCheck();
{
var rectMinValue = new Rect(position);
rectMinValue.width = kBoundsFieldWidth;
valueMin = EditorGUI.FloatField(rectMinValue, valueMin);
position.xMin += kBoundsFieldWidth + kWidthOffset;
var reactMaxValue = new Rect(position);
reactMaxValue.xMin = reactMaxValue.xMax - kBoundsFieldWidth;
valueMax = EditorGUI.FloatField(reactMaxValue, valueMax);
position.xMax -= kBoundsFieldWidth + kWidthOffset;
EditorGUI.MinMaxSlider(position, ref valueMin, ref valueMax, rangeMin, rangeMax);
}
if (EditorGUI.EndChangeCheck())
{
propMin.floatValue = valueMin;
propMax.floatValue = valueMax;
}
}
EditorGUI.showMixedValue = false;
}
EditorGUI.EndProperty();
}
}
#endif
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: dbd77861b0969d4469f82aed94865349
timeCreated: 1617383958
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,77 @@
using UnityEngine;
#pragma warning disable 0429, 0162 // Unreachable expression code detected (because of Noise3D.isSupported on mobile)
namespace VLB
{
public static class Noise3D
{
/// <summary>
/// Returns if the 3D Noise feature is supported on the current platform or not.
/// 3D Noise feature requires a graphicsShaderLevel 35 or higher (which is basically Shader Model 3.5 / OpenGL ES 3.0 or above)
/// If not supported, the beams will look like the 3D Noise has been disabled.
/// </summary>
public static bool isSupported {
get {
if (!ms_IsSupportedChecked)
{
ms_IsSupported = SystemInfo.graphicsShaderLevel >= kMinShaderLevel;
if (!ms_IsSupported)
Debug.LogWarning(isNotSupportedString);
ms_IsSupportedChecked = true;
}
return ms_IsSupported;
}
}
/// <summary>
/// Returns if the 3D Noise Texture has been successfully loaded or not.
/// If the feature is not supported (isSupported == false), isProperlyLoaded is also false.
/// </summary>
public static bool isProperlyLoaded { get { return ms_NoiseTexture != null; } }
public static string isNotSupportedString { get {
var str = string.Format("3D Noise requires higher shader capabilities (Shader Model 3.5 / OpenGL ES 3.0), which are not available on the current platform: graphicsShaderLevel (current/required) = {0} / {1}",
SystemInfo.graphicsShaderLevel,
kMinShaderLevel);
#if UNITY_EDITOR
str += "\nPlease change the editor's graphics emulation for a more capable one via \"Edit/Graphics Emulation\" and press Play to force the light beams to be recomputed.";
#endif
return str;
}
}
static bool ms_IsSupportedChecked = false;
static bool ms_IsSupported = false;
static Texture3D ms_NoiseTexture = null;
const int kMinShaderLevel = 35; // Shader Model 3.5 / OpenGL ES 3.0 to handle sampler3D -> https://docs.unity3d.com/ScriptReference/SystemInfo-graphicsShaderLevel.html
[RuntimeInitializeOnLoadMethod]
static void OnStartUp()
{
LoadIfNeeded();
}
#if UNITY_EDITOR
public static void _EditorForceReloadData()
{
ms_NoiseTexture = null;
LoadIfNeeded();
}
#endif
public static void LoadIfNeeded()
{
if (!isSupported) return;
if (ms_NoiseTexture == null)
{
ms_NoiseTexture = Config.Instance.noiseTexture3D;
Shader.SetGlobalTexture(ShaderProperties.GlobalNoiseTex3D, ms_NoiseTexture);
Shader.SetGlobalFloat(ShaderProperties.GlobalNoiseCustomTime, -1.0f);
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 57abb2ea789b7604eae964ef5c2fdcbb
timeCreated: 1508397352
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,107 @@
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace VLB
{
public class PlatformHelper
{
#if UNITY_EDITOR
static BuildTarget ms_BuildTargetOverride = BuildTarget.NoTarget;
public static void SetBuildTargetOverride(BuildTarget target)
{
ms_BuildTargetOverride = target;
Config.Instance.RefreshShader(Config.RefreshShaderFlags.All);
}
static BuildTarget GetCurrentBuildTarget()
{
if (BuildPipeline.isBuildingPlayer && ms_BuildTargetOverride != BuildTarget.NoTarget) return ms_BuildTargetOverride;
return EditorUserBuildSettings.activeBuildTarget;
}
static RuntimePlatform BuildTargetToRuntimePlatform(BuildTarget buildTarget)
{
#pragma warning disable 0618 // obsolete BuildTargets
switch (buildTarget)
{
case BuildTarget.Android: return RuntimePlatform.Android;
case BuildTarget.PS4: return RuntimePlatform.PS4;
case BuildTarget.StandaloneLinux64: return RuntimePlatform.LinuxPlayer;
case BuildTarget.StandaloneWindows: return RuntimePlatform.WindowsPlayer;
case BuildTarget.StandaloneWindows64: return RuntimePlatform.WindowsPlayer;
case BuildTarget.WSAPlayer: return RuntimePlatform.WSAPlayerARM;
case BuildTarget.XboxOne: return RuntimePlatform.XboxOne;
case BuildTarget.iOS: return RuntimePlatform.IPhonePlayer;
case BuildTarget.tvOS: return RuntimePlatform.tvOS;
case BuildTarget.WebGL: return RuntimePlatform.WebGLPlayer;
#if UNITY_2017_1_OR_NEWER
case BuildTarget.Switch: return RuntimePlatform.Switch;
#endif
#if UNITY_2017_3_OR_NEWER
case BuildTarget.StandaloneOSX: return RuntimePlatform.OSXPlayer;
#else
case BuildTarget.StandaloneOSXUniversal:return RuntimePlatform.OSXPlayer;
#endif
#if UNITY_2019_3_OR_NEWER
case BuildTarget.Stadia: return RuntimePlatform.Stadia;
#endif
#if UNITY_2020_2_OR_NEWER
case BuildTarget.CloudRendering: return RuntimePlatform.CloudRendering;
#endif
#if UNITY_2021_1_OR_NEWER
case BuildTarget.GameCoreScarlett: return RuntimePlatform.GameCoreScarlett;
case BuildTarget.GameCoreXboxOne: return RuntimePlatform.GameCoreXboxOne;
case BuildTarget.PS5: return RuntimePlatform.PS5;
#endif
// obsolete
case BuildTarget.StandaloneOSXIntel: return RuntimePlatform.OSXPlayer;
case BuildTarget.StandaloneOSXIntel64: return RuntimePlatform.OSXPlayer;
case BuildTarget.StandaloneLinuxUniversal:return RuntimePlatform.LinuxPlayer;
case BuildTarget.WiiU: return RuntimePlatform.WiiU;
case BuildTarget.PSP2: return RuntimePlatform.PSP2;
case BuildTarget.PS3: return RuntimePlatform.PS3;
case BuildTarget.XBOX360: return RuntimePlatform.XBOX360;
default: return (RuntimePlatform)(-1);
}
#pragma warning restore 0618
}
public static bool IsValidPlatformSuffix(string suffix)
{
if (string.IsNullOrEmpty(suffix))
return true;
foreach (var platform in System.Enum.GetNames(typeof(RuntimePlatform)))
{
if (suffix == platform)
return true;
}
return false;
}
#endif // UNITY_EDITOR
public static string GetCurrentPlatformSuffix()
{
#if UNITY_EDITOR
return GetPlatformSuffix(BuildTargetToRuntimePlatform(GetCurrentBuildTarget()));
#else
return GetPlatformSuffix(Application.platform);
#endif
}
static string GetPlatformSuffix(RuntimePlatform platform)
{
return platform.ToString();
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 30abcef3cbaaee1408d5966265dc731b
timeCreated: 1612121684
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,84 @@
using System.Collections.Generic;
using UnityEngine;
namespace VLB
{
public class PolygonHelper : MonoBehaviour
{
public struct Plane2D
{
public Vector2 normal;
public float distance;
public float Distance(Vector2 point) { return Vector2.Dot(normal, point) + distance; }
public Vector2 ClosestPoint(Vector2 pt) { return pt - normal * Distance(pt); }
public Vector2 Intersect(Vector2 p1, Vector2 p2)
{
float denominator = Vector2.Dot(normal, p1 - p2);
if (Utils.IsAlmostZero(denominator))
return (p1 + p2) * 0.5f;
float u = (normal.x * p1.x + normal.y * p1.y + distance) / denominator;
return (p1 + u * (p2 - p1));
}
public bool GetSide(Vector2 point) { return Distance(point) > 0.0f; }
public static Plane2D FromPoints(Vector3 p1, Vector3 p2)
{
var v = (p2 - p1).normalized;
return new Plane2D
{
normal = new Vector2(v.y, -v.x),
distance = (-v.y * p1.x + v.x * p1.y)
};
}
public static Plane2D FromNormalAndPoint(Vector3 normalizedNormal, Vector3 p1)
{
return new Plane2D
{
normal = normalizedNormal,
distance = (-normalizedNormal.x * p1.x - normalizedNormal.y * p1.y)
};
}
public void Flip() { normal = -normal; distance = -distance; }
public Vector2[] CutConvex(Vector2[] poly)
{
Debug.Assert(poly.Length >= 3);
var polyOut = new List<Vector2>(poly.Length);
Vector2 startingPoint = poly[poly.Length - 1];
foreach (var endPoint in poly)
{
var startingSide = GetSide(startingPoint);
var endSide = GetSide(endPoint);
if (startingSide && endSide)
{
polyOut.Add(endPoint);
}
else if (startingSide && !endSide)
{
polyOut.Add(Intersect(startingPoint, endPoint));
}
else if (!startingSide && endSide)
{
polyOut.Add(Intersect(startingPoint, endPoint));
polyOut.Add(endPoint);
}
startingPoint = endPoint;
}
return polyOut.ToArray();
}
public override string ToString() { return string.Format("{0} x {1} + {2}", normal.x, normal.y, distance); }
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 7b55955c734a6a04db43faaa6390235a
timeCreated: 1596631375
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,86 @@
#if UNITY_2018_1_OR_NEWER
#define VLB_SRP_SUPPORT // Comment this to disable SRP support
#endif
#if VLB_SRP_SUPPORT
#if UNITY_2019_1_OR_NEWER
using AliasCurrentPipeline = UnityEngine.Rendering.RenderPipelineManager;
using AliasCameraEvents = UnityEngine.Rendering.RenderPipelineManager;
using CallbackType = System.Action<UnityEngine.Rendering.ScriptableRenderContext, UnityEngine.Camera>;
#else
using AliasCurrentPipeline = UnityEngine.Experimental.Rendering.RenderPipelineManager;
using AliasCameraEvents = UnityEngine.Experimental.Rendering.RenderPipeline;
using CallbackType = System.Action<UnityEngine.Camera>;
#endif // UNITY_2019_1_OR_NEWER
#endif // VLB_SRP_SUPPORT
namespace VLB
{
public static class SRPHelper
{
public enum RenderPipeline
{
Undefined,
BuiltIn,
URP,
LWRP,
HDRP,
}
public static RenderPipeline renderPipelineType
{
get
{
// cache the value to prevent from comparing strings (in ComputeRenderPipeline) each frame when SRPBatcher is enabled
if (m_RenderPipelineCached == RenderPipeline.Undefined)
m_RenderPipelineCached = ComputeRenderPipeline();
return m_RenderPipelineCached;
}
}
static RenderPipeline m_RenderPipelineCached = RenderPipeline.Undefined;
static RenderPipeline ComputeRenderPipeline()
{
#if VLB_SRP_SUPPORT
var rp = UnityEngine.Rendering.GraphicsSettings.defaultRenderPipeline;
if (rp)
{
var name = rp.GetType().ToString();
if (name.Contains("Universal")) return RenderPipeline.URP;
if (name.Contains("Lightweight")) return RenderPipeline.LWRP;
if (name.Contains("HD")) return RenderPipeline.HDRP;
}
#endif
return RenderPipeline.BuiltIn;
}
#if VLB_SRP_SUPPORT
public static bool IsUsingCustomRenderPipeline()
{
// TODO: optimize and use renderPipelineType
return AliasCurrentPipeline.currentPipeline != null || UnityEngine.Rendering.GraphicsSettings.defaultRenderPipeline != null;
}
public static void RegisterOnBeginCameraRendering(CallbackType cb)
{
if (IsUsingCustomRenderPipeline())
{
AliasCameraEvents.beginCameraRendering -= cb;
AliasCameraEvents.beginCameraRendering += cb;
}
}
public static void UnregisterOnBeginCameraRendering(CallbackType cb)
{
if (IsUsingCustomRenderPipeline())
{
AliasCameraEvents.beginCameraRendering -= cb;
}
}
#else
public static bool IsUsingCustomRenderPipeline() { return false; }
#endif
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 8086b200893fe0c4e88663a09fdc78a3
timeCreated: 1585382401
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,318 @@
#if UNITY_EDITOR
using UnityEngine;
using UnityEngine.Rendering;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
namespace VLB
{
public class ShaderGenerator : ScriptableObject
{
[SerializeField] TextAsset m_Base = null;
TextAsset textAssetBase { get { return m_Base; } }
[SerializeField] TextAsset m_Pass = null;
TextAsset textAssetPass { get { return m_Pass; } }
[SerializeField] TextAsset m_IncludesBuiltin = null;
TextAsset textAssetIncludesBuiltin { get { return m_IncludesBuiltin; } }
[SerializeField] TextAsset m_IncludesURP = null;
TextAsset textAssetIncludesURP { get { return m_IncludesURP; } }
[SerializeField] TextAsset m_IncludesHDRP = null;
TextAsset textAssetIncludesHDRP { get { return m_IncludesHDRP; } }
const string kShaderAssetName = "VLBGeneratedShader";
static string GetOutputPath()
{
var path = Path.GetDirectoryName(AssetDatabase.GetAssetPath(Instance));
if (string.IsNullOrEmpty(path)) path = Consts.PluginFolder + "/Shaders/";
path = path.Replace("\\", "/");
const string kAssetsFolder = "Assets/";
if (path.IndexOf(kAssetsFolder) == 0) path = path.Remove(0, kAssetsFolder.Length);
return path;
}
public class EnabledFeatures
{
public bool dithering;
public bool depthBlend;
public FeatureEnabledColorGradient colorGradient;
public bool noise3D;
public bool dynamicOcclusion;
public bool meshSkewing;
public bool shaderAccuracyHigh;
}
public static Shader Generate(RenderPipeline rp, RenderingMode rm, EnabledFeatures enabledFeatures)
{
// The instance might not be accessible yet, when called from Config.OnEnable for instance if the Config is enabled before the ShaderGenerator.
// In this case we don't generate the shader right away, we store the parameters instead and we'll generate the shader in ShaderGenerator.OnEnable.
if (Instance == null)
{
ms_GenerationParamOnEnable = new GenerationParam { renderPipeline = rp, renderingMode = rm, enabledFeatures = enabledFeatures };
return null;
}
return new GenShader(rp, rm, enabledFeatures).Generate();
}
static string LoadText(TextAsset textAsset)
{
Debug.Assert(textAsset != null, "Fail to load a TextAsset, please try to reinstall the VolumetricLightBeam plugin");
return textAsset.text;
}
TextAsset GetTextAssetIncludes(SRPHelper.RenderPipeline rp)
{
switch (rp)
{
case SRPHelper.RenderPipeline.BuiltIn:
return textAssetIncludesBuiltin;
case SRPHelper.RenderPipeline.LWRP:
case SRPHelper.RenderPipeline.URP:
return textAssetIncludesURP;
case SRPHelper.RenderPipeline.HDRP:
return textAssetIncludesHDRP;
}
return null;
}
static bool IsFogSupported(SRPHelper.RenderPipeline rp) { return rp != SRPHelper.RenderPipeline.HDRP; }
enum ShaderLangage { CG, HLSL }
static ShaderLangage GetShaderLangage(SRPHelper.RenderPipeline rp)
{
switch (rp)
{
case SRPHelper.RenderPipeline.BuiltIn:
case SRPHelper.RenderPipeline.LWRP:
return ShaderLangage.CG;
case SRPHelper.RenderPipeline.URP:
case SRPHelper.RenderPipeline.HDRP:
return ShaderLangage.HLSL;
}
return ShaderLangage.CG;
}
static string GetShaderLangagePre (ShaderLangage lang) { return lang == ShaderLangage.CG ? "CGPROGRAM" : "HLSLPROGRAM"; }
static string GetShaderLangagePost(ShaderLangage lang) { return lang == ShaderLangage.CG ? "ENDCG" : "ENDHLSL"; }
public class GenPass
{
CullMode m_CullMode;
public GenPass(CullMode cullMode)
{
m_CullMode = cullMode;
}
static void AppendMultiCompile(ref string str, bool genDefaultVariant, params string[] options)
{
#if UNITY_2019_1_OR_NEWER
const string kPrefix = " #pragma multi_compile_local";
#else
const string kPrefix = " #pragma multi_compile";
#endif
str += kPrefix;
if(genDefaultVariant) str += " __";
foreach (string opt in options) str += " " + opt;
str += System.Environment.NewLine;
}
public string Generate(SRPHelper.RenderPipeline rp, RenderingMode rm, EnabledFeatures enabledFeatures, int passID, int passCount)
{
var code = LoadText(Instance.textAssetPass);
code = code.Replace("{VLB_GEN_CULLING}", m_CullMode.ToString());
code = code.Replace("{VLB_GEN_PRAGMA_INSTANCING}", rm == RenderingMode.GPUInstancing ? "#pragma multi_compile_instancing" : "");
code = code.Replace("{VLB_GEN_PRAGMA_FOG}", IsFogSupported(rp) ? "#pragma multi_compile_fog" : "");
string multiCompileVariants = "";
AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.AlphaAsBlack);
if (enabledFeatures.noise3D) AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.Noise3D);
if (enabledFeatures.depthBlend) AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.DepthBlend);
switch(enabledFeatures.colorGradient)
{
case FeatureEnabledColorGradient.HighOnly: AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.ColorGradientMatrixHigh); break;
case FeatureEnabledColorGradient.HighAndLow: AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.ColorGradientMatrixHigh, ShaderKeywords.ColorGradientMatrixLow); break;
}
if (enabledFeatures.dynamicOcclusion) AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.OcclusionClippingPlane, ShaderKeywords.OcclusionDepthTexture);
if (enabledFeatures.meshSkewing) AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.MeshSkewing);
if (enabledFeatures.shaderAccuracyHigh) AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.ShaderAccuracyHigh);
code = code.Replace("{VLB_GEN_PRAGMA_MULTI_COMPILE_VARIANTS}", multiCompileVariants);
var lang = GetShaderLangage(rp);
code = code.Replace("{VLB_GEN_PROGRAM_PRE}", GetShaderLangagePre(lang));
code = code.Replace("{VLB_GEN_PROGRAM_POST}", GetShaderLangagePost(lang));
var passPre = "";
if (passCount > 1)
{
code = code.Replace("{VLB_GEN_INPUT_VS}", string.Format("{0}", passID));
code = code.Replace("{VLB_GEN_INPUT_FS}", string.Format("{0}", passID));
}
else
{
code = code.Replace("{VLB_GEN_INPUT_VS}", string.Format("{0}", "v.texcoord.y"));
code = code.Replace("{VLB_GEN_INPUT_FS}", string.Format("{0}", "i.cameraPosObjectSpace_outsideBeam.w"));
passPre += " #define VLB_PASS_OUTSIDEBEAM_FROM_VS_TO_FS 1" + System.Environment.NewLine;
}
if (rp != SRPHelper.RenderPipeline.BuiltIn)
{
passPre += " #define VLB_SRP_API 1" + System.Environment.NewLine;
if (rm == RenderingMode.SRPBatcher)
{
passPre += " #define VLB_SRP_BATCHER 1" + System.Environment.NewLine;
if (rp == SRPHelper.RenderPipeline.URP)
{
// force enable constant buffers to fix SRP Batcher support on Android
passPre += " #pragma enable_cbuffer" + System.Environment.NewLine;
}
}
}
if (enabledFeatures.dithering)
{
passPre += " #define VLB_DITHERING 1" + System.Environment.NewLine;
}
passPre += LoadText(Instance.GetTextAssetIncludes(rp));
code = code.Replace("{VLB_GEN_PRE}", passPre);
return code;
}
}
class GenShader
{
SRPHelper.RenderPipeline m_RenderPipeline;
RenderingMode m_RenderingMode;
EnabledFeatures m_EnabledFeatures;
List<GenPass> m_Passes = new List<GenPass>();
public GenShader(RenderPipeline rp, RenderingMode rm, EnabledFeatures enabledFeatures)
{
m_RenderingMode = rm;
m_EnabledFeatures = enabledFeatures;
switch (rp)
{
case RenderPipeline.BuiltIn:
AddPass(CullMode.Front);
if (rm == RenderingMode.MultiPass) AddPass(CullMode.Back);
m_RenderPipeline = SRPHelper.RenderPipeline.BuiltIn;
break;
case RenderPipeline.URP:
AddPass(CullMode.Front);
m_RenderPipeline = SRPHelper.RenderPipeline.URP;
break;
case RenderPipeline.HDRP:
AddPass(CullMode.Front);
m_RenderPipeline = SRPHelper.RenderPipeline.HDRP;
break;
}
}
GenShader AddPass(CullMode cullMode)
{
m_Passes.Add(new GenPass(cullMode));
return this;
}
public Shader Generate()
{
var shaderName = string.Format("Hidden/VLB_{0}_{1}", m_RenderPipeline, m_RenderingMode);
var code = LoadText(Instance.textAssetBase);
code = code.Replace("{VLB_GEN_SHADERNAME}", shaderName);
var passes = "";
for (int i = 0; i < m_Passes.Count; ++i)
passes += m_Passes[i].Generate(m_RenderPipeline, m_RenderingMode, m_EnabledFeatures, i, m_Passes.Count);
code = code.Replace("{VLB_GEN_PASSES}", passes);
code = code.Replace("{VLB_GEN_SPECIFIC_INCLUDE}", GetRenderPipelineInclude(m_RenderPipeline));
var outputFolderPath = ShaderGenerator.GetOutputPath();
var outputFilePath = Path.Combine(outputFolderPath, kShaderAssetName);
var outputFullPath = System.IO.Path.Combine(Application.dataPath, outputFilePath + ".shader");
File.WriteAllText(outputFullPath, code);
AssetDatabase.Refresh();
var shader = Shader.Find(shaderName);
Debug.Assert(shader != null, string.Format("Failed to generate shader '{0}' at '{1}'", shaderName, outputFullPath));
return shader;
}
string GetRenderPipelineInclude(SRPHelper.RenderPipeline rp)
{
switch(rp)
{
case SRPHelper.RenderPipeline.BuiltIn: return "ShaderSpecificBuiltin.cginc";
case SRPHelper.RenderPipeline.HDRP: return "ShaderSpecificHDRP.hlsl";
case SRPHelper.RenderPipeline.LWRP:
case SRPHelper.RenderPipeline.URP: return "ShaderSpecificURP.cginc";
}
return null;
}
}
static ShaderGenerator FindInstance()
{
var assetGUIDs = AssetDatabase.FindAssets("ShaderGenerator");
foreach (var guid in assetGUIDs)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var asset = AssetDatabase.LoadAssetAtPath<ShaderGenerator>(path);
if (asset)
return asset;
}
return null;
}
// Store data to generate the shader on OnEnable
class GenerationParam
{
public RenderPipeline renderPipeline;
public RenderingMode renderingMode;
public EnabledFeatures enabledFeatures;
}
static GenerationParam ms_GenerationParamOnEnable = null;
void OnEnable()
{
if(ms_GenerationParamOnEnable != null && Instance != null)
{
Generate(ms_GenerationParamOnEnable.renderPipeline, ms_GenerationParamOnEnable.renderingMode, ms_GenerationParamOnEnable.enabledFeatures);
ms_GenerationParamOnEnable = null;
}
}
// Singleton management
static ShaderGenerator m_Instance = null;
static ShaderGenerator Instance
{
get
{
if (m_Instance == null)
m_Instance = FindInstance();
return m_Instance;
}
}
}
}
#endif
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 5a0074f2031380c488da5e38151a7a47
timeCreated: 1592812245
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
namespace VLB
{
public static class ShaderKeywords
{
public const string AlphaAsBlack = "VLB_ALPHA_AS_BLACK";
public const string ColorGradientMatrixLow = "VLB_COLOR_GRADIENT_MATRIX_LOW";
public const string ColorGradientMatrixHigh = "VLB_COLOR_GRADIENT_MATRIX_HIGH";
public const string DepthBlend = "VLB_DEPTH_BLEND";
public const string Noise3D = "VLB_NOISE_3D";
public const string OcclusionClippingPlane = "VLB_OCCLUSION_CLIPPING_PLANE";
public const string OcclusionDepthTexture = "VLB_OCCLUSION_DEPTH_TEXTURE";
public const string MeshSkewing = "VLB_MESH_SKEWING";
public const string ShaderAccuracyHigh = "VLB_SHADER_ACCURACY_HIGH";
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: d03404178af782e419e82d8a3b973df5
timeCreated: 1607620118
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,47 @@
using UnityEngine;
namespace VLB
{
public static class ShaderProperties
{
public static readonly int FadeOutFactor = Shader.PropertyToID("_FadeOutFactor");
public static readonly int ConeSlopeCosSin = Shader.PropertyToID("_ConeSlopeCosSin");
public static readonly int ConeRadius = Shader.PropertyToID("_ConeRadius");
public static readonly int ConeApexOffsetZ = Shader.PropertyToID("_ConeApexOffsetZ");
public static readonly int ColorFlat = Shader.PropertyToID("_ColorFlat");
public static readonly int AlphaInside = Shader.PropertyToID("_AlphaInside");
public static readonly int AlphaOutside = Shader.PropertyToID("_AlphaOutside");
public static readonly int AttenuationLerpLinearQuad = Shader.PropertyToID("_AttenuationLerpLinearQuad");
public static readonly int DistanceFallOff = Shader.PropertyToID("_DistanceFallOff");
public static readonly int DistanceCamClipping = Shader.PropertyToID("_DistanceCamClipping");
public static readonly int FresnelPow = Shader.PropertyToID("_FresnelPow");
public static readonly int GlareBehind = Shader.PropertyToID("_GlareBehind");
public static readonly int GlareFrontal = Shader.PropertyToID("_GlareFrontal");
public static readonly int DrawCap = Shader.PropertyToID("_DrawCap");
public static readonly int DepthBlendDistance = Shader.PropertyToID("_DepthBlendDistance");
public static readonly int NoiseVelocityAndScale = Shader.PropertyToID("_NoiseVelocityAndScale");
public static readonly int NoiseParam = Shader.PropertyToID("_NoiseParam");
public static readonly int CameraParams = Shader.PropertyToID("_CameraParams");
public static readonly int ColorGradientMatrix = Shader.PropertyToID("_ColorGradientMatrix");
public static readonly int LocalToWorldMatrix = Shader.PropertyToID("_LocalToWorldMatrix");
public static readonly int WorldToLocalMatrix = Shader.PropertyToID("_WorldToLocalMatrix");
public static readonly int BlendSrcFactor = Shader.PropertyToID("_BlendSrcFactor");
public static readonly int BlendDstFactor = Shader.PropertyToID("_BlendDstFactor");
public static readonly int DynamicOcclusionClippingPlaneWS = Shader.PropertyToID("_DynamicOcclusionClippingPlaneWS");
public static readonly int DynamicOcclusionClippingPlaneProps = Shader.PropertyToID("_DynamicOcclusionClippingPlaneProps");
public static readonly int DynamicOcclusionDepthTexture = Shader.PropertyToID("_DynamicOcclusionDepthTexture");
public static readonly int DynamicOcclusionDepthProps = Shader.PropertyToID("_DynamicOcclusionDepthProps");
public static readonly int LocalForwardDirection = Shader.PropertyToID("_LocalForwardDirection");
public static readonly int TiltVector = Shader.PropertyToID("_TiltVector");
public static readonly int AdditionalClippingPlaneWS = Shader.PropertyToID("_AdditionalClippingPlaneWS");
public static readonly int ParticlesTintColor = Shader.PropertyToID("_TintColor");
public static readonly int GlobalUsesReversedZBuffer = Shader.PropertyToID("_VLB_UsesReversedZBuffer");
public static readonly int GlobalNoiseTex3D = Shader.PropertyToID("_VLB_NoiseTex3D");
public static readonly int GlobalNoiseCustomTime = Shader.PropertyToID("_VLB_NoiseCustomTime");
public static readonly int GlobalDitheringFactor = Shader.PropertyToID("_VLB_DitheringFactor");
public static readonly int GlobalDitheringNoiseTex = Shader.PropertyToID("_VLB_DitheringNoiseTex");
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: f7c111997c90d464c94acc87b1711d59
timeCreated: 1560540688
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,57 @@
using UnityEngine;
using System.Collections;
namespace VLB
{
[ExecuteInEditMode]
[HelpURL(Consts.Help.UrlSkewingHandle)]
public class SkewingHandle : MonoBehaviour
{
public VolumetricLightBeam volumetricLightBeam = null;
public bool shouldUpdateEachFrame = false;
#if UNITY_EDITOR
void Update()
{
if (!Application.isPlaying && CanSetSkewingVector())
SetSkewingVector();
}
#endif
public bool IsAttachedToSelf() { return volumetricLightBeam != null && volumetricLightBeam.gameObject == this.gameObject; }
public bool CanSetSkewingVector() { return volumetricLightBeam != null && volumetricLightBeam.canHaveMeshSkewing; }
public bool CanUpdateEachFrame() { return CanSetSkewingVector() && volumetricLightBeam.trackChangesDuringPlaytime; }
bool ShouldUpdateEachFrame() { return shouldUpdateEachFrame && CanUpdateEachFrame(); }
void OnEnable()
{
if(CanSetSkewingVector())
SetSkewingVector();
}
void Start()
{
if (Application.isPlaying && ShouldUpdateEachFrame())
{
StartCoroutine(CoUpdate());
}
}
IEnumerator CoUpdate()
{
while(ShouldUpdateEachFrame())
{
SetSkewingVector();
yield return null;
}
}
void SetSkewingVector()
{
Debug.Assert(CanSetSkewingVector());
var vec = volumetricLightBeam.transform.InverseTransformPoint(transform.position);
volumetricLightBeam.skewingLocalForwardDirection = vec;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 39d38d9261901ee459f4c704884f0ad3
timeCreated: 1617808264
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 96c382e4368041b4aad93aed0a833e2b, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,29 @@
using UnityEngine;
namespace VLB
{
public static class TransformUtils
{
public struct Packed
{
public Vector3 position;
public Quaternion rotation;
public Vector3 lossyScale;
public bool IsSame(Transform transf)
{
return transf.position == position && transf.rotation == rotation && transf.lossyScale == lossyScale;
}
}
public static Packed GetWorldPacked(this Transform self)
{
return new Packed()
{
position = self.position,
rotation = self.rotation,
lossyScale = self.lossyScale,
};
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: db47de7f89a659b49982236fc59b4e04
timeCreated: 1578089892
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,153 @@
using UnityEngine;
namespace VLB
{
[DisallowMultipleComponent]
[RequireComponent(typeof(VolumetricLightBeam))]
[HelpURL(Consts.Help.UrlTriggerZone)]
public class TriggerZone : MonoBehaviour
{
public const string ClassName = "TriggerZone";
/// <summary>
/// Define if the Collider will be created as a convex trigger (not physical, most common behavior) or as a regular collider (physical).
/// </summary>
public bool setIsTrigger = true;
/// <summary>
/// Change the length of the Collider. For example, set 2.0 to make the Collider 2x longer than the beam. Default value is 1.0.
/// </summary>
public float rangeMultiplier = 1.0f;
enum TriggerZoneUpdateRate
{
/// <summary>Compute the Trigger Zone only once at startup</summary>
OnEnable,
/// <summary>Compute the Trigger Zone each time the dynamic occlusion has changed</summary>
OnOcclusionChange,
}
/// <summary>
/// How often will the Trigger Zone be computed?
/// </summary>
TriggerZoneUpdateRate updateRate
{
get
{
Debug.Assert(m_Beam != null);
if(m_Beam.dimensions == Dimensions.Dim3D) return TriggerZoneUpdateRate.OnEnable; // for 3D meshes, do it only once because it's too performance heavy
return m_DynamicOcclusionRaycasting != null ? TriggerZoneUpdateRate.OnOcclusionChange : TriggerZoneUpdateRate.OnEnable;
}
}
const int kMeshColliderNumSides = 8;
VolumetricLightBeam m_Beam = null;
DynamicOcclusionRaycasting m_DynamicOcclusionRaycasting = null;
PolygonCollider2D m_PolygonCollider2D = null;
void OnEnable()
{
m_Beam = GetComponent<VolumetricLightBeam>();
Debug.Assert(m_Beam != null);
m_DynamicOcclusionRaycasting = GetComponent<DynamicOcclusionRaycasting>();
switch(updateRate)
{
case TriggerZoneUpdateRate.OnEnable:
{
ComputeZone();
enabled = false;
break;
}
case TriggerZoneUpdateRate.OnOcclusionChange:
{
if(m_DynamicOcclusionRaycasting)
m_DynamicOcclusionRaycasting.onOcclusionProcessed += OnOcclusionProcessed;
break;
}
}
}
void OnOcclusionProcessed()
{
ComputeZone();
}
void ComputeZone()
{
if (m_Beam)
{
var rangeEnd = m_Beam.fallOffEnd * rangeMultiplier;
var lerpedRadiusEnd = Mathf.LerpUnclamped(m_Beam.coneRadiusStart, m_Beam.coneRadiusEnd, rangeMultiplier);
if (m_Beam.dimensions == Dimensions.Dim3D)
{
var meshCollider = gameObject.GetOrAddComponent<MeshCollider>();
Debug.Assert(meshCollider);
int sides = Mathf.Min(m_Beam.geomSides, kMeshColliderNumSides);
var mesh = MeshGenerator.GenerateConeZ_Radius(rangeEnd, m_Beam.coneRadiusStart, lerpedRadiusEnd, sides, 0, false, false);
mesh.hideFlags = Consts.Internal.ProceduralObjectsHideFlags;
meshCollider.sharedMesh = mesh;
meshCollider.convex = setIsTrigger;
meshCollider.isTrigger = setIsTrigger;
}
else
{
if (m_PolygonCollider2D == null)
{
m_PolygonCollider2D = gameObject.GetOrAddComponent<PolygonCollider2D>();
Debug.Assert(m_PolygonCollider2D);
}
var polyCoordsLS = new Vector2[] // polygon coord in local space
{
new Vector2(0.0f, -m_Beam.coneRadiusStart),
new Vector2(rangeEnd, -lerpedRadiusEnd),
new Vector2(rangeEnd, lerpedRadiusEnd),
new Vector2(0.0f, m_Beam.coneRadiusStart)
};
if (m_DynamicOcclusionRaycasting && m_DynamicOcclusionRaycasting.planeEquationWS.IsValid())
{
var plane3dWS = m_DynamicOcclusionRaycasting.planeEquationWS;
if (Utils.IsAlmostZero(plane3dWS.normal.z))
{
// Compute 2 points on the plane in world space
// Use this technique instead of transforming the plane's normal to fully support scaling
var ptOnPlane1 = plane3dWS.ClosestPointOnPlaneCustom(Vector3.zero);
var ptOnPlane2 = plane3dWS.ClosestPointOnPlaneCustom(Vector3.up);
if(Utils.IsAlmostZero(Vector3.SqrMagnitude(ptOnPlane1 - ptOnPlane2)))
ptOnPlane1 = plane3dWS.ClosestPointOnPlaneCustom(Vector3.right);
// Compute 2 points on the plane in local space
ptOnPlane1 = transform.InverseTransformPoint(ptOnPlane1);
ptOnPlane2 = transform.InverseTransformPoint(ptOnPlane2);
// Compute plane equation in local space
var plane2dLS = PolygonHelper.Plane2D.FromPoints(ptOnPlane1, ptOnPlane2);
if (plane2dLS.normal.x > 0.0f) plane2dLS.Flip();
polyCoordsLS = plane2dLS.CutConvex(polyCoordsLS);
}
}
m_PolygonCollider2D.points = polyCoordsLS;
m_PolygonCollider2D.isTrigger = setIsTrigger;
}
}
}
#if UNITY_EDITOR
void OnValidate()
{
rangeMultiplier = Mathf.Max(rangeMultiplier, 0.001f);
}
#endif
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 5b5217c9f8723a447824a7530d14c03d
timeCreated: 1531459125
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 568401c2857b94943af27dc14afca0ae, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,249 @@
using System;
using UnityEngine;
namespace VLB
{
public static class Utils
{
public static float ComputeConeRadiusEnd(float fallOffEnd, float spotAngle)
{
return fallOffEnd * Mathf.Tan(spotAngle * Mathf.Deg2Rad * 0.5f);
}
public static float ComputeSpotAngle(float fallOffEnd, float coneRadiusEnd)
{
return Mathf.Atan2(coneRadiusEnd, fallOffEnd) * Mathf.Rad2Deg * 2.0f;
}
public static void Swap<T>(ref T a, ref T b)
{
var temp = a;
a = b;
b = temp;
}
public static string GetPath(Transform current)
{
if (current.parent == null)
return "/" + current.name;
return GetPath(current.parent) + "/" + current.name;
}
public static T NewWithComponent<T>(string name) where T : Component
{
return (new GameObject(name, typeof(T))).GetComponent<T>();
}
public static T GetOrAddComponent<T>(this GameObject self) where T : Component
{
var component = self.GetComponent<T>();
if (component == null)
component = self.AddComponent<T>();
return component;
}
public static T GetOrAddComponent<T>(this MonoBehaviour self) where T : Component
{
return self.gameObject.GetOrAddComponent<T>();
}
/// <summary>
/// true if the bit field or bit fields that are set in flags are also set in the current instance; otherwise, false.
/// </summary>
public static bool HasFlag(this Enum mask, Enum flags) // Same behavior than Enum.HasFlag is .NET 4
{
#if DEBUG
if (mask.GetType() != flags.GetType())
throw new System.ArgumentException(string.Format("The argument type, '{0}', is not the same as the enum type '{1}'.", flags.GetType(), mask.GetType()));
#endif
return ((int)(IConvertible)mask & (int)(IConvertible)flags) == (int)(IConvertible)flags;
}
public static Vector2 xy(this Vector3 aVector) { return new Vector2(aVector.x, aVector.y); }
public static Vector2 xz(this Vector3 aVector) { return new Vector2(aVector.x, aVector.z); }
public static Vector2 yz(this Vector3 aVector) { return new Vector2(aVector.y, aVector.z); }
public static Vector2 yx(this Vector3 aVector) { return new Vector2(aVector.y, aVector.x); }
public static Vector2 zx(this Vector3 aVector) { return new Vector2(aVector.z, aVector.x); }
public static Vector2 zy(this Vector3 aVector) { return new Vector2(aVector.z, aVector.y); }
const float kEpsilon = 0.00001f;
public static bool Approximately(this float a, float b, float epsilon = kEpsilon) { return Mathf.Abs(a - b) < epsilon; }
public static bool Approximately(this Vector2 a, Vector2 b, float epsilon = kEpsilon) { return Vector2.SqrMagnitude(a - b) < epsilon; }
public static bool Approximately(this Vector3 a, Vector3 b, float epsilon = kEpsilon) { return Vector3.SqrMagnitude(a - b) < epsilon; }
public static bool Approximately(this Vector4 a, Vector4 b, float epsilon = kEpsilon) { return Vector4.SqrMagnitude(a - b) < epsilon; }
public static Vector4 AsVector4(this Vector3 vec3, float w) { return new Vector4(vec3.x, vec3.y, vec3.z, w); }
public static Vector4 PlaneEquation(Vector3 normalizedNormal, Vector3 pt) { return normalizedNormal.AsVector4(-Vector3.Dot(normalizedNormal, pt)); }
public static float GetVolumeCubic(this Bounds self) { return self.size.x * self.size.y * self.size.z; }
public static float GetMaxArea2D(this Bounds self) { return Mathf.Max(Mathf.Max(self.size.x * self.size.y, self.size.y * self.size.z), self.size.x * self.size.z); }
public static Color Opaque(this Color self) { return new Color(self.r, self.g, self.b, 1f); }
public static Color ComputeComplementaryColor(this Color self, bool blackAndWhite)
{
if (blackAndWhite)
{
// http://stackoverflow.com/a/3943023/112731
return (self.r * 0.299 + self.g * 0.587 + self.b * 0.114) > (186.0f / 255) ? Color.black : Color.white;
}
return new Color(1.0f - self.r, 1.0f - self.g, 1.0f - self.b);
}
#if UNITY_EDITOR
public static void GizmosDrawPlane(Vector3 normal, Vector3 position, Color color, Matrix4x4 mat, float size = 1f, float normalLength = 0.0f)
{
normal = normal.normalized;
var prevMat = UnityEditor.Handles.matrix;
var prevColor = UnityEditor.Handles.color;
UnityEditor.Handles.matrix = mat;
UnityEditor.Handles.color = color;
UnityEditor.Handles.RectangleHandleCap(0, position, Quaternion.LookRotation(normal), size, EventType.Repaint);
if (normalLength > 0.0f)
{
UnityEditor.Handles.DrawLine(position, position + normal * normalLength);
UnityEditor.Handles.ConeHandleCap(0, position + normal * normalLength, Quaternion.LookRotation(normal), normalLength * 0.25f, EventType.Repaint);
}
UnityEditor.Handles.matrix = prevMat;
UnityEditor.Handles.color = prevColor;
}
#endif // UNITY_EDITOR
// Plane.Translate is not available in Unity 5
public static Plane TranslateCustom(this Plane plane, Vector3 translation)
{
plane.distance += Vector3.Dot(translation.normalized, plane.normal) * translation.magnitude;
return plane;
}
// Plane.ClosestPointOnPlaneCustom is not available in Unity 5
public static Vector3 ClosestPointOnPlaneCustom(this Plane plane, Vector3 point)
{
return point - plane.GetDistanceToPoint(point) * plane.normal;
}
public static bool IsAlmostZero(float f) { return Mathf.Abs(f) < 0.001f; }
public static bool IsValid(this Plane plane)
{
return plane.normal.sqrMagnitude > 0.5f;
}
public static void SetKeywordEnabled(this Material mat, string name, bool enabled)
{
if(enabled) mat.EnableKeyword(name);
else mat.DisableKeyword(name);
}
public static void SetShaderKeywordEnabled(string name, bool enabled)
{
if (enabled) Shader.EnableKeyword(name);
else Shader.DisableKeyword(name);
}
public static Matrix4x4 SampleInMatrix(this Gradient self, int floatPackingPrecision)
{
const int kSamplesCount = 16;
var mat = new Matrix4x4();
for (int i = 0; i < kSamplesCount; ++i)
{
var color = self.Evaluate(Mathf.Clamp01((float)(i) / (kSamplesCount - 1)));
mat[i] = color.PackToFloat(floatPackingPrecision);
}
return mat;
}
public static Color[] SampleInArray(this Gradient self, int samplesCount)
{
var array = new Color[samplesCount];
for (int i = 0; i < samplesCount; ++i)
array[i] = self.Evaluate(Mathf.Clamp01((float)(i) / (samplesCount - 1)));
return array;
}
static Vector4 Vector4_Floor(Vector4 vec) { return new Vector4(Mathf.Floor(vec.x), Mathf.Floor(vec.y), Mathf.Floor(vec.z), Mathf.Floor(vec.w)); }
public static float PackToFloat(this Color color, int floatPackingPrecision)
{
Vector4 iVal = Vector4_Floor(color * (floatPackingPrecision - 1));
float output = 0;
output += iVal.x * floatPackingPrecision * floatPackingPrecision * floatPackingPrecision;
output += iVal.y * floatPackingPrecision * floatPackingPrecision;
output += iVal.z * floatPackingPrecision;
output += iVal.w;
return output;
}
public enum FloatPackingPrecision { High = 64, Low = 8, Undef = 0 }
static FloatPackingPrecision ms_FloatPackingPrecision = FloatPackingPrecision.Undef;
// OpenGL ES 2.0 GPU (graphicsShaderLevel = 30) usually have low float precision (16 bits on fragments)
// So we lower the float packing precision on them (8 seems fine on Adreno (TM) 220, NVIDIA Tegra 3 and on Mali-450 MP)
// https://docs.unity3d.com/Manual/SL-DataTypesAndPrecision.html
const int kFloatPackingHighMinShaderLevel = 35;
public static FloatPackingPrecision GetFloatPackingPrecision()
{
if (ms_FloatPackingPrecision == FloatPackingPrecision.Undef)
{
ms_FloatPackingPrecision = SystemInfo.graphicsShaderLevel >= kFloatPackingHighMinShaderLevel ? FloatPackingPrecision.High : FloatPackingPrecision.Low;
}
return ms_FloatPackingPrecision;
}
public static void MarkCurrentSceneDirty()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(UnityEngine.SceneManagement.SceneManager.GetActiveScene());
}
#endif
}
public static void MarkObjectDirty(UnityEngine.Object obj)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
UnityEditor.EditorUtility.SetDirty(obj);
}
#endif
}
#if UNITY_EDITOR
public static bool IsEditorCamera(Camera cam)
{
var sceneView = UnityEditor.SceneView.currentDrawingSceneView;
if (sceneView)
{
return cam == sceneView.camera;
}
return false;
}
public static void SetSameSceneVisibilityStatesThan(this GameObject self, GameObject model)
{
// SceneVisibilityManager is a feature available from 2019.2, but fixed for transient objects only from 2019.3.14f1
// https://issuetracker.unity3d.com/issues/toggling-of-picking-and-visibility-flags-of-a-gameobject-is-ignored-when-gameobject-dot-hideflags-is-set-to-hideflags-dot-dontsave
#if UNITY_2019_3_OR_NEWER
bool pickingDisabled = UnityEditor.SceneVisibilityManager.instance.IsPickingDisabled(model);
if (pickingDisabled) UnityEditor.SceneVisibilityManager.instance.DisablePicking(self, true);
else UnityEditor.SceneVisibilityManager.instance.EnablePicking(self, true);
bool hidden = UnityEditor.SceneVisibilityManager.instance.IsHidden(model);
if (hidden) UnityEditor.SceneVisibilityManager.instance.Hide(self, true);
else UnityEditor.SceneVisibilityManager.instance.Show(self, true);
#endif
}
#endif
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 8a3c52d4dd2a02a4499d1c94fcc9c542
timeCreated: 1504688222
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,7 @@
namespace VLB
{
public static class Version
{
public const int Current = 1960;
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: c62ea6a423ab5824c9d344b83ee9706e
timeCreated: 1507996747
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,404 @@
using UnityEngine;
namespace VLB
{
[ExecuteInEditMode]
[DisallowMultipleComponent]
[RequireComponent(typeof(VolumetricLightBeam))]
[HelpURL(Consts.Help.UrlDustParticles)]
public class VolumetricDustParticles : MonoBehaviour
{
public const string ClassName = "VolumetricDustParticles";
/// <summary>
/// Max alpha of the particles
/// </summary>
[Range(0f, 1f)]
public float alpha = Consts.DustParticles.AlphaDefault;
/// <summary>
/// Max size of the particles
/// </summary>
[Range(0.0001f, 0.1f)]
public float size = Consts.DustParticles.SizeDefault;
/// <summary>
/// Direction of the particles.
/// </summary>
public ParticlesDirection direction = Consts.DustParticles.DirectionDefault;
/// <summary>
/// Movement speed of the particles.
/// </summary>
public Vector3 velocity = Consts.DustParticles.VelocityDefault;
[System.Obsolete("Use 'velocity' instead")]
public float speed = 0.03f;
/// <summary>
/// Control how many particles are spawned. The higher the density, the more particles are spawned, the higher the performance cost is.
/// </summary>
public float density = Consts.DustParticles.DensityDefault;
/// <summary>
/// The distance range (from the light source) where the particles are spawned.
/// - Min bound: the higher it is, the more the particles are spawned away from the light source.
/// - Max bound: the lower it is, the more the particles are gathered near the light source.
/// </summary>
[MinMaxRange(0.0f, 1.0f)]
public MinMaxRangeFloat spawnDistanceRange = Consts.DustParticles.SpawnDistanceRangeDefault;
[System.Obsolete("Use 'spawnDistanceRange' instead")]
public float spawnMinDistance = 0f;
[System.Obsolete("Use 'spawnDistanceRange' instead")]
public float spawnMaxDistance = 0.7f;
/// <summary>
/// Enable particles culling based on the distance to the Main Camera.
/// We highly recommend to enable this feature to keep good runtime performances.
/// </summary>
public bool cullingEnabled = Consts.DustParticles.CullingEnabledDefault;
/// <summary>
/// If culling is enabled, the particles will not be rendered if they are further than cullingMaxDistance to the Main Camera.
/// </summary>
public float cullingMaxDistance = Consts.DustParticles.CullingMaxDistanceDefault;
/// <summary>
/// Is the particle system currently culled (no visible) because too far from the main camera?
/// </summary>
public bool isCulled { get; private set; }
[SerializeField] float m_AlphaAdditionalRuntime = 1.0f;
public float alphaAdditionalRuntime
{
get { return m_AlphaAdditionalRuntime; }
set { if (m_AlphaAdditionalRuntime != value) { m_AlphaAdditionalRuntime = value; m_RuntimePropertiesDirty = true; } }
}
public bool particlesAreInstantiated { get { return m_Particles; } }
public int particlesCurrentCount { get { return m_Particles ? m_Particles.particleCount : 0; } }
public int particlesMaxCount { get { return m_Particles ? m_Particles.main.maxParticles : 0; } }
ParticleSystem m_Particles = null;
ParticleSystemRenderer m_Renderer = null;
Material m_Material = null;
Gradient m_GradientCached = new Gradient();
bool m_RuntimePropertiesDirty = true;
/// <summary>Cached version of Camera.main, for performance reasons</summary>
public Camera mainCamera
{
get
{
if (!ms_MainCamera)
{
ms_MainCamera = Camera.main;
if (!ms_MainCamera && !ms_NoMainCameraLogged)
{
Debug.LogErrorFormat(gameObject, "In order to use '" + VolumetricDustParticles.ClassName + "' culling, you must have a MainCamera defined in your scene.");
ms_NoMainCameraLogged = true;
}
}
return ms_MainCamera;
}
}
// Cache the main camera, because accessing Camera.main at each frame is very bad performance-wise
static bool ms_NoMainCameraLogged = false;
static Camera ms_MainCamera = null;
#if UNITY_EDITOR
void OnValidate()
{
density = Mathf.Clamp(density, Consts.DustParticles.DensityMin, Consts.DustParticles.DensityMax);
cullingMaxDistance = Mathf.Max(cullingMaxDistance, Consts.DustParticles.CullingMaxDistanceMin);
Play(); // support instant refresh when modifying properties from inspector
}
#endif // UNITY_EDITOR
VolumetricLightBeam m_Master = null;
void Start()
{
isCulled = false;
m_Master = GetComponent<VolumetricLightBeam>();
Debug.Assert(m_Master);
HandleBackwardCompatibility(m_Master._INTERNAL_pluginVersion, Version.Current);
InstantiateParticleSystem();
SetActiveAndPlay();
}
void InstantiateParticleSystem()
{
// If we duplicate (from Editor and Playmode) the VLB, the children are also duplicated (like the dust particles)
// we have to make sure to properly destroy them before creating our proper procedural particle instance.
var slaves = GetComponentsInChildren<ParticleSystem>(true);
for (int i = slaves.Length - 1; i >= 0; --i)
DestroyImmediate(slaves[i].gameObject);
m_Particles = Config.Instance.NewVolumetricDustParticles();
if (m_Particles)
{
#if UNITY_EDITOR
if (m_Master)
{
UnityEditor.GameObjectUtility.SetStaticEditorFlags(m_Particles.gameObject, m_Master.GetStaticEditorFlagsForSubObjects());
m_Particles.gameObject.SetSameSceneVisibilityStatesThan(m_Master.gameObject);
}
#endif // UNITY_EDITOR
m_Particles.transform.SetParent(transform, false);
m_Particles.transform.localRotation = m_Master.beamInternalLocalRotation;
m_Renderer = m_Particles.GetComponent<ParticleSystemRenderer>();
Debug.Assert(m_Renderer);
m_Material = new Material(m_Renderer.sharedMaterial);
Debug.Assert(m_Material);
m_Renderer.material = m_Material;
}
}
void OnEnable()
{
SetActiveAndPlay();
}
void SetActive(bool active)
{
if (m_Particles) m_Particles.gameObject.SetActive(active);
}
void SetActiveAndPlay()
{
SetActive(true);
Play();
}
void Play()
{
if (m_Particles)
{
SetParticleProperties();
m_Particles.Simulate(0f);
m_Particles.Play(true);
}
}
void OnDisable()
{
SetActive(false);
}
void OnDestroy()
{
if (m_Particles)
{
DestroyImmediate(m_Particles.gameObject); // Make sure to delete the GAO
m_Particles = null;
}
if (m_Material)
{
DestroyImmediate(m_Material);
m_Material = null;
}
}
void Update()
{
#if UNITY_EDITOR
if(!Application.isPlaying)
{
if (m_Particles == null)
InstantiateParticleSystem();
Play();
}
else
#endif // UNITY_EDITOR
{
UpdateCulling();
Debug.Assert(m_Master);
if (m_Master.trackChangesDuringPlaytime)
SetParticleProperties();
}
if (m_RuntimePropertiesDirty && m_Material != null)
{
m_Material.SetColor(ShaderProperties.ParticlesTintColor, new Color(1.0f, 1.0f, 1.0f, alphaAdditionalRuntime));
m_RuntimePropertiesDirty = false;
}
}
void SetParticleProperties()
{
if (m_Particles && m_Particles.gameObject.activeSelf)
{
var thickness = Mathf.Clamp01(1 - (m_Master.fresnelPow / Consts.Beam.FresnelPowMaxValue));
var coneLength = m_Master.fallOffEnd * (spawnDistanceRange.maxValue - spawnDistanceRange.minValue);
var ratePerSec = coneLength * density;
int maxParticles = (int)(ratePerSec * 4);
var main = m_Particles.main;
var startLifetime = main.startLifetime;
startLifetime.mode = ParticleSystemCurveMode.TwoConstants;
startLifetime.constantMin = 4f;
startLifetime.constantMax = 6f;
main.startLifetime = startLifetime;
var startSize = main.startSize;
startSize.mode = ParticleSystemCurveMode.TwoConstants;
startSize.constantMin = size * 0.9f;
startSize.constantMax = size * 1.1f;
main.startSize = startSize;
var startColor = main.startColor;
if (m_Master.usedColorMode == ColorMode.Flat)
{
startColor.mode = ParticleSystemGradientMode.Color;
var colorMax = m_Master.color;
colorMax.a *= alpha;
startColor.color = colorMax;
}
else
{
startColor.mode = ParticleSystemGradientMode.Gradient;
// Duplicate gradient and apply alpha
var gradientRef = m_Master.colorGradient;
var colorKeys = gradientRef.colorKeys;
var alphaKeys = gradientRef.alphaKeys;
for(int i=0; i< alphaKeys.Length; ++i)
alphaKeys[i].alpha *= alpha;
Debug.Assert(m_GradientCached != null);
m_GradientCached.SetKeys(colorKeys, alphaKeys);
startColor.gradient = m_GradientCached;
}
main.startColor = startColor;
{
var startSpeed = main.startSpeed;
startSpeed.constant = (direction == ParticlesDirection.Random) ? Mathf.Abs(velocity.z) : 0.0f;
main.startSpeed = startSpeed;
}
{
var velocityOverLifetime = m_Particles.velocityOverLifetime;
velocityOverLifetime.enabled = (direction != ParticlesDirection.Random);
velocityOverLifetime.space = (direction == ParticlesDirection.LocalSpace) ? ParticleSystemSimulationSpace.Local : ParticleSystemSimulationSpace.World;
velocityOverLifetime.xMultiplier = velocity.x;
velocityOverLifetime.yMultiplier = velocity.y;
velocityOverLifetime.zMultiplier = velocity.z;
}
main.maxParticles = maxParticles;
{
var shape = m_Particles.shape;
shape.shapeType = ParticleSystemShapeType.ConeVolume;
float coneAngle = m_Master.coneAngle * Mathf.Lerp(0.7f, 1f, thickness);
shape.angle = coneAngle * 0.5f;
float radiusStart = m_Master.coneRadiusStart * Mathf.Lerp(0.3f, 1.0f, thickness);
float radiusEnd = Utils.ComputeConeRadiusEnd(m_Master.fallOffEnd, coneAngle);
shape.radius = Mathf.Lerp(radiusStart, radiusEnd, spawnDistanceRange.minValue);
shape.length = coneLength;
var localOffset = m_Master.fallOffEnd * spawnDistanceRange.minValue;
#if UNITY_2017_1_OR_NEWER
shape.position = new Vector3(0f, 0f, localOffset);
#else
m_Particles.transform.localPosition = m_Master.beamLocalForward * localOffset;
#endif
shape.arc = 360f;
shape.randomDirectionAmount = (direction == ParticlesDirection.Random) ? 1f : 0f;
}
var emission = m_Particles.emission;
var rate = emission.rateOverTime;
rate.constant = ratePerSec;
emission.rateOverTime = rate;
if(m_Renderer)
{
m_Renderer.sortingLayerID = m_Master.sortingLayerID;
m_Renderer.sortingOrder = m_Master.sortingOrder;
}
}
}
void HandleBackwardCompatibility(int serializedVersion, int newVersion)
{
if (serializedVersion == -1) return; // freshly new spawned entity: nothing to do
if (serializedVersion == newVersion) return; // same version: nothing to do
#pragma warning disable 0618
if (serializedVersion < 1880)
{
// Version 1880 changed the order of ParticlesDirection enum and add WorldSpace option
if ((int)direction == 0) direction = (ParticlesDirection)1;
else direction = (ParticlesDirection)0;
// Version 1880 changed from single float speed to 3D velocity vector
velocity = new Vector3(0.0f, 0.0f, speed);
}
if (serializedVersion < 1940)
{
spawnDistanceRange = new MinMaxRangeFloat(spawnMinDistance, spawnMaxDistance);
}
#pragma warning restore 0618
Utils.MarkCurrentSceneDirty();
}
#region Culling
void UpdateCulling()
{
if (m_Particles)
{
bool visible = true;
if ((cullingEnabled || m_Master.isFadeOutEnabled) && m_Master.hasGeometry)
{
if (mainCamera)
{
var maxDist = cullingMaxDistance;
if (m_Master.isFadeOutEnabled) maxDist = Mathf.Min(maxDist, m_Master.fadeOutEnd);
var maxDistSqr = maxDist * maxDist;
var distSqr = m_Master.bounds.SqrDistance(mainCamera.transform.position);
visible = distSqr <= maxDistSqr;
}
else
cullingEnabled = false;
}
if (m_Particles.gameObject.activeSelf != visible)
{
SetActive(visible);
isCulled = !visible;
}
if (visible && !m_Particles.isPlaying)
m_Particles.Play();
}
}
#endregion
}
}
@@ -0,0 +1,28 @@
fileFormatVersion: 2
guid: e39f0f7e17f11f2448932da58e9b63d4
labels:
- volumetric
- light
- lighting
- ray
- shaft
- beam
- density
- vr
- dynamic
- spot
- spotlight
- fog
- noise
- occlusion
- procedural
timeCreated: 1511563236
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 54d50eb83191723439a591eeeb0eab84, type: 3}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,29 @@
fileFormatVersion: 2
guid: 7b69c542c9a8f4f44a3123e3fa103d13
labels:
- volumetric
- light
- lighting
- ray
- shaft
- beam
- density
- vr
- dynamic
- spot
- spotlight
- fog
- noise
- occlusion
- procedural
timeCreated: 1507404056
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences:
- m_Shader: {fileID: 4800000, guid: 936bd00b0f168d949b0c27d2be40615a, type: 3}
executionOrder: 0
icon: {fileID: 2800000, guid: 837747a8b63291a48838c340da834873, type: 3}
userData:
assetBundleName:
assetBundleVariant: