/// /// Shiny SSR - Screen Space Reflections - (c) 2021-2024 Kronnect /// using System; using System.Collections.Generic; using UnityEngine; namespace ShinySSRR { public enum Scope { OnlyThisObject = 0, IncludeChildren = 10, AllObjectsInLayer = 20 } [Serializable] public struct SubMeshSettingsData { [Range(0, 1)] public float metallic; [Range(0, 1)] public float smoothness; } public enum SmoothnessSource { Custom, Material } public enum NormalSource { ScreenSpaceNormals, Material, Custom } public enum OcclusionSource { Material, Custom } [ExecuteInEditMode] public partial class Reflections : MonoBehaviour { [Tooltip("Prevent this object from receiving reflections")] public bool ignore; [Tooltip("On which objects should reflections apply")] public Scope scope; [Tooltip("Include only objects matching this layer mask")] public LayerMask layerMask = -1; [Tooltip("Include only objects including this text")] public string nameFilter; [Tooltip("Optionally specify which submeshes can receive reflections. Set this to 0 to apply reflections to all submeshes.")] public int subMeshMask; [Header("Material Features")] [Tooltip("Source for the smoothness map")] public SmoothnessSource smoothnessSource = SmoothnessSource.Material; public Texture customSmoothnessMap; [Tooltip("Custom material property name for the smoothness map")] public string materialSmoothnessMapPropertyName = "_MetallicGlossMap"; [Tooltip("Custom material property name for the metallic intensity")] public string materialReflectivityPropertyName = "_Metallic"; [Tooltip("Custom material property name for the smoothness intensity")] public string materialSmoothnessIntensityPropertyName = "_Smoothness"; [Tooltip("Smoothness value used if material has no smoothness property.")] [Range(0, 1)] public float smoothness = 0.75f; [Tooltip("Metallic value used if material has no metallic property.")] [Range(0, 1)] public float metallic = 1f; [Tooltip("Apply a different smoothness multiplier per submesh")] public bool perSubMeshSmoothness; [Tooltip("Smoothness values per each submesh")] public SubMeshSettingsData[] subMeshSettings; public NormalSource normalSource = NormalSource.Material; [Tooltip("The normal/bump map")] public Texture normalMap; [Tooltip("The normal map strength")] public float normalMapStrength = 1f; [Tooltip("Custom material property name for the normal map")] public string materialNormalMapPropertyName = "_BumpMap"; [Tooltip("Custom material property name for the normal map strength")] public string materialNormalMapScalePropertyName = "_BumpScale"; public OcclusionSource occlusionSource = OcclusionSource.Material; [Tooltip("The occlusion map")] public Texture occlusionMap; [Tooltip("The occlusion strength")] [Range(0, 1)] public float occlusionStrength = 1f; [Tooltip("Custom material property name for the occlusion map")] public string materialOcclusionMapPropertyName = "_OcclusionMap"; [Tooltip("Custom material property name for the occlusion strength")] public string materialOcclusionStrengthPropertyName = "_OcclusionStrength"; [Header("Raytracing Settings")] public bool overrideGlobalSettings; [Tooltip("Max number of samples used during the raymarch loop")] [Range(4, 128)] public int sampleCount = 16; [HideInInspector] public float stepSize; // no longer used; kept for backward compatibility during upgrade [Tooltip("Maximum reflection distance")] public float maxRayLength; [Tooltip("Assumed thickness of geometry in the depth buffer before binary search")] public float thickness = 0.3f; [Tooltip("Number of refinements steps when a reflection hit is found")] [Range(0, 16)] public int binarySearchIterations = 6; [Tooltip("Increase accuracy of reflection hit after binary search by discarding points farther than a reduced thickness.")] public bool refineThickness; [Tooltip("Assumed thickness of geometry in the depth buffer after binary search")] [Range(0.005f, 1f)] public float thicknessFine = 0.05f; [Tooltip("Reflection decay with distance to reflective point")] public float decay = 2f; [Range(0, 1)] [Tooltip("Reduces reflection based on view angle")] public float fresnel = 0.75f; [Min(0)] [Tooltip("Ray dispersion with distance")] public float fuzzyness; [Tooltip("Makes sharpen reflections near objects")] public float contactHardening; [Tooltip("Jitter helps smoothing edges")] [Range(0, 1f)] public float jitter = 0.3f; [Header("Special Effects")] public Vector2 uvDistortionSpeed; [Tooltip("Skybox reflection intensity multiplier for this object")] [Range(0, 1)] public float skyboxMultiplier = 1f; [NonSerialized] public readonly List ssrRenderers = new List(); [NonSerialized] public readonly List renderers = new List(); public readonly static List instances = new List(); public static bool needUpdateMaterials; public static Reflections currentEditingReflections; void OnEnable () { if ((int)smoothnessSource == 2) // migration from old Custom value (2 -> 0) { smoothnessSource = SmoothnessSource.Custom; } if (maxRayLength == 0) { maxRayLength = Mathf.Max(0.1f, stepSize * sampleCount); } Refresh(); instances.Add(this); } void OnDisable () { if (instances != null) { int k = instances.IndexOf(this); if (k >= 0) { instances.RemoveAt(k); } } } private void OnValidate () { normalMapStrength = Mathf.Max(0, normalMapStrength); fuzzyness = Mathf.Max(0, fuzzyness); thickness = Mathf.Max(0.01f, thickness); thicknessFine = Mathf.Max(0.01f, thicknessFine); contactHardening = Mathf.Max(0, contactHardening); decay = Mathf.Max(1f, decay); if (maxRayLength == 0) { maxRayLength = stepSize * sampleCount; } maxRayLength = Mathf.Max(0.1f, maxRayLength); Refresh(); } private void OnDestroy () { if (ssrRenderers == null) return; for (int k = 0; k < ssrRenderers.Count; k++) { Material[] mats = ssrRenderers[k].ssrMaterials; if (mats != null) { for (int j = 0; j < mats.Length; j++) { if (mats[j] != null) DestroyImmediate(mats[j]); } } } } public void UpdateMaterialPropertes () { int ssrCount = ssrRenderers.Count; for (int k = 0; k < ssrCount; k++) { SSR_Renderer ssr = ssrRenderers[k]; if (ssr != null) { ssr.UpdateMaterialProperties(); } } } /// /// Updates the list of included renderers to receive reflections /// public void Refresh () { switch (scope) { case Scope.OnlyThisObject: { renderers.Clear(); Renderer r = GetComponent(); if (r != null) { renderers.Add(r); } } break; case Scope.IncludeChildren: { GetComponentsInChildren(true, renderers); // Check if some children has an exclusive reflections component int rCount = renderers.Count; bool usesNameFilter = !string.IsNullOrEmpty(nameFilter); for (int k = 0; k < rCount; k++) { Renderer r = renderers[k]; if (((1 << r.gameObject.layer) & layerMask) == 0) { renderers[k] = null; continue; } if (usesNameFilter) { if (!r.name.Contains(nameFilter)) { renderers[k] = null; continue; } } Reflections refl = r.GetComponent(); if (refl != null && refl != this) { renderers[k] = null; } } break; } case Scope.AllObjectsInLayer: { Renderer[] objects = Misc.FindObjectsOfType(); renderers.Clear(); int rCount = objects.Length; bool usesNameFilter = !string.IsNullOrEmpty(nameFilter); for (int k = 0; k < rCount; k++) { Renderer r = objects[k]; if (((1 << r.gameObject.layer) & layerMask) == 0) { continue; } if (usesNameFilter) { if (!r.name.Contains(nameFilter)) { continue; } } renderers.Add(r); } break; } } // Prepare required ssr materials slots for (int k = 0; k < ssrRenderers.Count; k++) { if (ssrRenderers[k] != null) { ssrRenderers[k].DestroyMaterials(); } } ssrRenderers.Clear(); if (ignore) return; int renderersCount = renderers.Count; for (int k = 0; k < renderersCount; k++) { Renderer r = renderers[k]; if (r == null) continue; Reflections refl = r.GetComponent(); if (refl != null && refl.ignore) { continue; } SSR_Renderer ssr = new SSR_Renderer { renderer = r }; ssrRenderers.Add(ssr); } } public class SSR_Renderer { public Renderer renderer; public Material[] ssrMaterials; readonly public List originalMaterials = new List(); readonly List tempMaterials = new List(); public bool isInitialized; public bool exclude; public Collider collider; public bool hasStaticBounds; public Bounds staticBounds; public void Init (Material ssrMat) { isInitialized = true; renderer.GetSharedMaterials(originalMaterials); collider = renderer.GetComponent(); hasStaticBounds = false; if (renderer.isPartOfStaticBatch && collider == null) { MeshFilter mf = renderer.GetComponent(); if (mf != null) { Mesh mesh = mf.sharedMesh; if (mesh != null) { int subMeshStartIndex = ((MeshRenderer)renderer).subMeshStartIndex; staticBounds = mesh.GetSubMesh(subMeshStartIndex).bounds; int subMeshesCount = originalMaterials.Count; for (int k = 1; k < subMeshesCount; k++) { staticBounds.Encapsulate(mesh.GetSubMesh(subMeshStartIndex + k).bounds); } hasStaticBounds = true; } } } if (ssrMaterials == null || ssrMaterials.Length != originalMaterials.Count) { if (ssrMaterials != null) { for (int i = 0; i < ssrMaterials.Length; i++) { if (ssrMaterials[i] != null) DestroyImmediate(ssrMaterials[i]); } } ssrMaterials = new Material[originalMaterials.Count]; } for (int k = 0; k < ssrMaterials.Length; k++) { if (ssrMaterials[k] != null) { DestroyImmediate(ssrMaterials[k]); } ssrMaterials[k] = Instantiate(ssrMat); } } public void DestroyMaterials () { if (ssrMaterials == null) return; for (int k = 0; k < ssrMaterials.Length; k++) { if (ssrMaterials[k] != null) { DestroyImmediate(ssrMaterials[k]); } } ssrMaterials = null; isInitialized = false; } public void CheckMaterialChanges (Material shinyMat) { if (!isInitialized || renderer == null) return; renderer.GetSharedMaterials(tempMaterials); int tempMaterialsCount = tempMaterials.Count; if (tempMaterialsCount != originalMaterials.Count) { Init(shinyMat); return; } for (int k = 0; k < tempMaterialsCount; k++) { if (tempMaterials[k] != originalMaterials[k]) { Init(shinyMat); return; }; } } /// /// Forces a material update in next render frame /// public void UpdateMaterialProperties () { isInitialized = false; } public void UpdateMaterialProperties (Reflections go, ShinyScreenSpaceRaytracedReflections globalSettings) { if (go.subMeshSettings == null) { go.subMeshSettings = new SubMeshSettingsData[0]; } float totalReflectivity = 0; int ssrMaterialsCount = ssrMaterials != null ? ssrMaterials.Length : 0; NormalSource normalSource = go.normalSource; if (ShinySSRR.isDeferredActive) normalSource = NormalSource.Material; for (int s = 0; s < ssrMaterialsCount; s++) { Material ssrMat = ssrMaterials[s]; Material originalMaterial = originalMaterials[s]; Texture metallicGlossMap = go.customSmoothnessMap; float smoothness = go.smoothness; float reflectivity = go.metallic; if (go.perSubMeshSmoothness) { if (s < go.subMeshSettings.Length) { reflectivity = go.subMeshSettings[s].metallic; smoothness = go.subMeshSettings[s].smoothness; } } Texture normalMap = go.normalMap; float normalMapScale = go.normalMapStrength; Texture occlusionMap = go.occlusionMap; float occlusionStrength = go.occlusionStrength; bool hasSmoothnessMap = false; bool hasNormalMap = false; bool hasOcclusionMap = false; if (originalMaterial != null) { if (go.smoothnessSource == SmoothnessSource.Material) { if (!string.IsNullOrEmpty(go.materialReflectivityPropertyName) && originalMaterial.HasProperty(go.materialReflectivityPropertyName)) { reflectivity = originalMaterial.GetFloat(go.materialReflectivityPropertyName); } else if (originalMaterial.HasProperty(ShaderParams.Reflectivity)) { reflectivity = originalMaterial.GetFloat(ShaderParams.Reflectivity); } if (!string.IsNullOrEmpty(go.materialSmoothnessIntensityPropertyName) && originalMaterial.HasProperty(go.materialSmoothnessIntensityPropertyName)) { smoothness = originalMaterial.GetFloat(go.materialSmoothnessIntensityPropertyName); } else if (originalMaterial.HasProperty(ShaderParams.Smoothness)) { smoothness = originalMaterial.GetFloat(ShaderParams.Smoothness); } if (!string.IsNullOrEmpty(go.materialSmoothnessMapPropertyName) && originalMaterial.HasProperty(go.materialSmoothnessMapPropertyName)) { metallicGlossMap = originalMaterial.GetTexture(go.materialSmoothnessMapPropertyName); } else if (originalMaterial.HasProperty(ShaderParams.MetallicGlossMap)) { metallicGlossMap = originalMaterial.GetTexture(ShaderParams.MetallicGlossMap); } } else { // color alpha can also influence smoothness if (originalMaterial.HasProperty(ShaderParams.Color)) { smoothness *= originalMaterial.GetColor(ShaderParams.Color).a; } else if (originalMaterial.HasProperty(ShaderParams.BaseColor)) { smoothness *= originalMaterial.GetColor(ShaderParams.BaseColor).a; } } if (metallicGlossMap != null) { ssrMat.SetTexture(ShaderParams.SmoothnessMap, metallicGlossMap); ssrMat.EnableKeyword(ShaderParams.SKW_SMOOTHNESSMAP); hasSmoothnessMap = true; } if (normalSource == NormalSource.Material) { if (!string.IsNullOrEmpty(go.materialNormalMapPropertyName) && originalMaterial.HasProperty(go.materialNormalMapPropertyName)) { normalMap = originalMaterial.GetTexture(go.materialNormalMapPropertyName); } else if (originalMaterial.HasProperty(ShaderParams.BumpMap)) { normalMap = originalMaterial.GetTexture(ShaderParams.BumpMap); } if (!string.IsNullOrEmpty(go.materialNormalMapScalePropertyName) && originalMaterial.HasProperty(go.materialNormalMapScalePropertyName)) { normalMapScale = originalMaterial.GetFloat(go.materialNormalMapScalePropertyName); } else if (originalMaterial.HasProperty(ShaderParams.BumpMap_Scale)) { normalMapScale = originalMaterial.GetFloat(ShaderParams.BumpMap_Scale); } } if (normalSource != NormalSource.ScreenSpaceNormals && normalMap != null) { ssrMat.SetTexture(ShaderParams.BumpMap, normalMap); if (originalMaterial.HasProperty(ShaderParams.BaseMap_ST)) { ssrMat.SetVector(ShaderParams.BumpMap_ST, originalMaterial.GetVector(ShaderParams.BaseMap_ST)); } ssrMat.SetFloat(ShaderParams.BumpMap_Scale, normalMapScale); ssrMat.EnableKeyword(ShaderParams.SKW_NORMALMAP); hasNormalMap = true; } if (go.occlusionSource == OcclusionSource.Material) { if (!string.IsNullOrEmpty(go.materialOcclusionMapPropertyName) && originalMaterial.HasProperty(go.materialOcclusionMapPropertyName)) { occlusionMap = originalMaterial.GetTexture(go.materialOcclusionMapPropertyName); } else if (originalMaterial.HasProperty(ShaderParams.OcclusionMap)) { occlusionMap = originalMaterial.GetTexture(ShaderParams.OcclusionMap); } if (!string.IsNullOrEmpty(go.materialOcclusionStrengthPropertyName) && originalMaterial.HasProperty(go.materialOcclusionStrengthPropertyName)) { occlusionStrength = originalMaterial.GetFloat(go.materialOcclusionStrengthPropertyName); } else if (originalMaterial.HasProperty(ShaderParams.OcclusionStrength)) { occlusionStrength = originalMaterial.GetFloat(ShaderParams.OcclusionStrength); } } if (occlusionMap != null) { ssrMat.SetTexture(ShaderParams.OcclusionMap, occlusionMap); if (originalMaterial.HasProperty(ShaderParams.BaseMap_ST)) { ssrMat.SetVector(ShaderParams.BumpMap_ST, originalMaterial.GetVector(ShaderParams.BaseMap_ST)); } ssrMat.SetFloat(ShaderParams.OcclusionStrength, occlusionStrength); ssrMat.EnableKeyword(ShaderParams.SKW_OCCLUSIONMAP); hasOcclusionMap = true; } } if (!hasSmoothnessMap) { ssrMat.DisableKeyword(ShaderParams.SKW_SMOOTHNESSMAP); } if (!hasNormalMap) { ssrMat.DisableKeyword(ShaderParams.SKW_NORMALMAP); } if (!hasOcclusionMap) { ssrMat.DisableKeyword(ShaderParams.SKW_OCCLUSIONMAP); } if (normalSource == NormalSource.ScreenSpaceNormals && ShinySSRR.isUsingScreenSpaceNormals) { ssrMat.EnableKeyword(ShaderParams.SKW_SCREEN_SPACE_NORMALS); } if (globalSettings.reflectionsWorkflow == ReflectionsWorkflow.MetallicAndSmoothness) { totalReflectivity += reflectivity; ssrMat.EnableKeyword(ShaderParams.SKW_METALLIC_WORKFLOW); } else { totalReflectivity += smoothness; ssrMat.DisableKeyword(ShaderParams.SKW_METALLIC_WORKFLOW); } if (go.overrideGlobalSettings) { if (go.jitter > 0) { ssrMat.EnableKeyword(ShaderParams.SKW_JITTER); } else { ssrMat.DisableKeyword(ShaderParams.SKW_JITTER); } if (go.refineThickness) { ssrMat.EnableKeyword(ShaderParams.SKW_REFINE_THICKNESS); } else { ssrMat.DisableKeyword(ShaderParams.SKW_REFINE_THICKNESS); } ssrMat.SetVector(ShaderParams.SSRSettings5, new Vector4(go.thicknessFine * go.thickness, globalSettings.smoothnessThreshold.value, globalSettings.skyboxIntensity.value, globalSettings.opaqueReflectionsBlending.value)); ssrMat.SetVector(ShaderParams.SSRSettings2, new Vector4(go.jitter, go.contactHardening + 0.0001f, globalSettings.reflectionsMultiplier.value, reflectivity)); ssrMat.SetVector(ShaderParams.SSRSettings, new Vector4(go.thickness, go.sampleCount, go.binarySearchIterations, go.maxRayLength)); ssrMat.SetVector(ShaderParams.MaterialData, new Vector4(smoothness, go.fresnel, go.fuzzyness + 1f, go.decay)); } else { if (globalSettings.jitter.value > 0) { ssrMat.EnableKeyword(ShaderParams.SKW_JITTER); } else { ssrMat.DisableKeyword(ShaderParams.SKW_JITTER); } if (globalSettings.refineThickness.value) { ssrMat.EnableKeyword(ShaderParams.SKW_REFINE_THICKNESS); } else { ssrMat.DisableKeyword(ShaderParams.SKW_REFINE_THICKNESS); } ssrMat.SetVector(ShaderParams.SSRSettings5, new Vector4(globalSettings.thicknessFine.value * globalSettings.thickness.value, globalSettings.smoothnessThreshold.value, globalSettings.skyboxIntensity.value, globalSettings.opaqueReflectionsBlending.value)); ssrMat.SetVector(ShaderParams.SSRSettings2, new Vector4(globalSettings.jitter.value, globalSettings.contactHardening.value + 0.0001f, globalSettings.reflectionsMultiplier.value, reflectivity)); ssrMat.SetVector(ShaderParams.SSRSettings, new Vector4(globalSettings.thickness.value, globalSettings.sampleCount.value, globalSettings.binarySearchIterations.value, globalSettings.maxRayLength.value)); ssrMat.SetVector(ShaderParams.MaterialData, new Vector4(smoothness, globalSettings.fresnel.value, globalSettings.fuzzyness.value + 1f, globalSettings.decay.value)); } ssrMat.SetVector(ShaderParams.DistortionData, new Vector4(go.uvDistortionSpeed.x, go.uvDistortionSpeed.y, go.skyboxMultiplier, 0)); } exclude = (totalReflectivity == 0); } } private void OnDrawGizmos () { for (int k = 0; k < ssrRenderers.Count; k++) { Bounds bounds = ssrRenderers[k].staticBounds; Gizmos.DrawWireCube(bounds.center, bounds.size); } } public void ApplyRaytracingPreset (RaytracingPreset preset) { switch (preset) { case RaytracingPreset.Fast: sampleCount = 16; maxRayLength = 6; binarySearchIterations = 4; thickness = Mathf.Max(0.5f, thickness); refineThickness = false; jitter = 0.3f; break; case RaytracingPreset.Medium: sampleCount = 24; maxRayLength = 12; thickness = Mathf.Max(0.5f, thickness); binarySearchIterations = 5; refineThickness = false; break; case RaytracingPreset.High: sampleCount = 48; maxRayLength = 24; binarySearchIterations = 6; thickness = Mathf.Max(0.5f, thickness); refineThickness = false; thicknessFine = 0.05f; break; case RaytracingPreset.Superb: sampleCount = 128; maxRayLength = 48; binarySearchIterations = 7; thickness = Mathf.Max(0.5f, thickness); refineThickness = false; thicknessFine = 0.05f; break; case RaytracingPreset.Ultra: sampleCount = 200; maxRayLength = 64; binarySearchIterations = 8; thickness = Mathf.Max(0.5f, thickness); refineThickness = false; thicknessFine = 0.05f; break; } Refresh(); } } }