using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEngine.Rendering; using Object = UnityEngine.Object; namespace GPUInstancer { public class GPUInstancerPreviewCache { private readonly Dictionary _previewCache = new Dictionary(); public void AddPreview(GPUInstancerPrototype prototype, Texture2D preview) { if (_previewCache.ContainsKey(prototype)) _previewCache[prototype] = preview; else _previewCache.Add(prototype, preview); } public void RemovePreview(GPUInstancerPrototype prototype) { if (_previewCache.ContainsKey(prototype)) _previewCache.Remove(prototype); } public Texture2D GetPreview(GPUInstancerPrototype prototype) { if (_previewCache.ContainsKey(prototype)) return _previewCache[prototype]; return null; } public void ClearEmptyPreviews() { GPUInstancerPrototype[] prototypes = new GPUInstancerPrototype[_previewCache.Count]; _previewCache.Keys.CopyTo(prototypes, 0); foreach (GPUInstancerPrototype prototype in prototypes) { if (!_previewCache[prototype]) _previewCache.Remove(prototype); } } public void ClearPreviews() { foreach (GPUInstancerPrototype prototype in _previewCache.Keys) { if (_previewCache[prototype]) Object.DestroyImmediate(_previewCache[prototype]); } _previewCache.Clear(); } } public class GPUInstancerPreviewDrawer { private Texture2D _defaultBackgroundTexture; private Texture2D _additionalTexture; #if UNITY_PRO_LICENSE private Color _defaultColor = new Color(0.45f, 0.45f, 0.45f, 1.0f); #else private Color _defaultColor = new Color(0.7f, 0.7f, 0.7f, 1.0f); #endif private readonly Color _colorMultiplier = new Color(0.5f, 0.5f, 0.5f, 1.0f); private Camera _camera; private readonly List _gameObjects = new List(); private Light[] lights = new Light[2]; private readonly int _sampleLayer = 31; public bool isVertexBased = true; public GPUInstancerPreviewDrawer(Texture2D defaultBackgroundTexture) { _defaultBackgroundTexture = defaultBackgroundTexture; InitializeCameraAndLights(); } public void InitializeCameraAndLights() { var camGO = EditorUtility.CreateGameObjectWithHideFlags("Preview Scene Camera", HideFlags.HideAndDontSave, typeof(Camera)); AddGameObject(camGO); camGO.transform.rotation = Quaternion.Euler(30, -135, 0); _camera = camGO.GetComponent(); _camera.cameraType = CameraType.Preview; _camera.enabled = false; _camera.clearFlags = CameraClearFlags.Depth; _camera.fieldOfView = 15; _camera.farClipPlane = 100; _camera.nearClipPlane = -100; _camera.renderingPath = RenderingPath.Forward; _camera.useOcclusionCulling = false; _camera.orthographic = true; _camera.backgroundColor = Color.clear; _camera.cullingMask = 1 << _sampleLayer; _camera.allowHDR = false; var l0 = CreateLight(); AddGameObject(l0); lights[0] = l0.GetComponent(); var l1 = CreateLight(); AddGameObject(l1); lights[1] = l1.GetComponent(); foreach (Light l in lights) { l.cullingMask = 1 << _sampleLayer; } lights[0].color = new Color(1f, 0.9568627f, 0.8392157f, 0f); lights[0].transform.rotation = Quaternion.Euler(30, -135, 0); lights[0].intensity = 1f; lights[1].color = new Color(.4f, .4f, .45f, 0f) * .7f; lights[1].transform.rotation = Quaternion.identity; lights[1].intensity = 0.5f; if (QualitySettings.activeColorSpace == ColorSpace.Linear) { _defaultColor = _defaultColor.linear; lights[0].color = lights[0].color.linear; lights[1].color = lights[1].color.linear; } } public void AddGameObject(GameObject go) { go.layer = _sampleLayer; if (_gameObjects.Contains(go)) return; _gameObjects.Add(go); } public void SetAdditionalTexture(Texture2D additionalTexture) { _additionalTexture = additionalTexture; } public void Cleanup() { foreach (var go in _gameObjects) Object.DestroyImmediate(go); _gameObjects.Clear(); lights = null; } public Texture2D GetPreviewForGameObject(GameObject gameObject, Rect prevRect, Color backgroundColor, GPUInstancerRuntimeData runtimeData = null) { #if UNITY_2022_2_OR_NEWER if (GPUInstancerConstants.gpuiSettings.isURP && gameObject != null) return AssetPreview.GetAssetPreview(gameObject); #endif if (!_camera || lights == null) { Cleanup(); InitializeCameraAndLights(); } #region Initialize float scaleFac = GetScaleFactor(prevRect.width, prevRect.height); int rtWidth = (int)(prevRect.width * scaleFac); int rtHeight = (int)(prevRect.height * scaleFac); RenderTexture _renderTexture = RenderTexture.GetTemporary(rtWidth, rtHeight, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default); _renderTexture.Create(); _camera.targetTexture = _renderTexture; RenderTexture previousRenderTexture = RenderTexture.active; RenderTexture.active = _renderTexture; GL.Clear(true, true, Color.clear); foreach (var light in lights) light.enabled = true; if (QualitySettings.activeColorSpace == ColorSpace.Linear) { if (backgroundColor != Color.clear) backgroundColor = backgroundColor.linear; } if (GPUInstancerConstants.gpuiSettings.isHDRP && gameObject != null) { _camera.backgroundColor = backgroundColor == Color.clear ? _defaultColor.gamma : backgroundColor.gamma; } else if (_defaultBackgroundTexture != null) { GL.PushMatrix(); GL.LoadOrtho(); GL.LoadPixelMatrix(0, _renderTexture.width, _renderTexture.height, 0); Graphics.DrawTexture( new Rect(0, 0, _renderTexture.width, _renderTexture.height), _defaultBackgroundTexture, new Rect(0, 0, 1, 1), 1, 1, 1, 1, backgroundColor == Color.clear ? _defaultColor * _colorMultiplier : backgroundColor * _colorMultiplier); GL.PopMatrix(); } if (_additionalTexture != null) { GL.PushMatrix(); GL.LoadOrtho(); GL.LoadPixelMatrix(0, _renderTexture.width - 2, _renderTexture.height - 2, 0); Graphics.DrawTexture( new Rect(1, 1, _renderTexture.width - 1, _renderTexture.height - 1), _additionalTexture, new Rect(0, 0, 1, 1), 1, 1, 1, 1); GL.PopMatrix(); } #endregion Initialize if (gameObject != null) { Renderer[] renderers; if (gameObject.GetComponent() != null) { renderers = gameObject.GetComponent().GetLODs()[0].renderers; } else { renderers = gameObject.GetComponentsInChildren(); } Bounds bounds = new Bounds(); bool isBoundsInitialized = false; foreach (Renderer renderer in renderers) { if (renderer == null) continue; Mesh mesh = null; if (renderer.GetComponent()) { mesh = renderer.GetComponent().sharedMesh; } else if (renderer is SkinnedMeshRenderer) { mesh = ((SkinnedMeshRenderer)renderer).sharedMesh; } if (mesh != null) { if (isVertexBased) { Vector3[] verts = mesh.vertices; for (var v = 0; v < verts.Length; v++) { if (!isBoundsInitialized) { isBoundsInitialized = true; bounds = new Bounds(renderer.transform.localToWorldMatrix.MultiplyPoint3x4(verts[v]), Vector3.zero); } else bounds.Encapsulate(renderer.transform.localToWorldMatrix.MultiplyPoint3x4(verts[v])); } } else { Bounds rendererBounds = renderer.bounds; if (!isBoundsInitialized) { isBoundsInitialized = true; bounds = new Bounds(rendererBounds.center, rendererBounds.size); } else { bounds.Encapsulate(rendererBounds); } } } } float maxBounds = Mathf.Max(Mathf.Max(bounds.extents.x, bounds.extents.y), bounds.extents.z); _camera.transform.position = bounds.center; _camera.orthographicSize = maxBounds * 1.3f; UnityEditorInternal.InternalEditorUtility.SetCustomLighting(lights, Color.gray); foreach (Renderer renderer in renderers) { if (renderer == null) continue; Matrix4x4 matrix = renderer.transform.localToWorldMatrix; Mesh mesh = null; if (renderer.GetComponent()) { mesh = renderer.GetComponent().sharedMesh; } else if (renderer is SkinnedMeshRenderer) { mesh = ((SkinnedMeshRenderer)renderer).sharedMesh; } if (mesh != null && renderer.sharedMaterials != null) { int submeshIndex = 0; foreach (Material mat in renderer.sharedMaterials) { Graphics.DrawMesh(mesh, matrix, mat, _sampleLayer, _camera, Math.Min(submeshIndex, mesh.subMeshCount - 1), null, ShadowCastingMode.Off, false, null, false); submeshIndex++; } } } _camera.Render(); UnityEditorInternal.InternalEditorUtility.RemoveCustomLighting(); } else if (runtimeData != null && runtimeData.instanceLODs != null && runtimeData.instanceLODs.Count > 0 && runtimeData.instanceLODs[0].renderers != null #if UNITY_2022_2_OR_NEWER && !GPUInstancerConstants.gpuiSettings.isURP #endif ) { float maxBounds = Mathf.Max(Mathf.Max(runtimeData.instanceBounds.extents.x, runtimeData.instanceBounds.extents.y), runtimeData.instanceBounds.extents.z); _camera.transform.position = runtimeData.instanceBounds.center; _camera.orthographicSize = maxBounds * 1.3f; UnityEditorInternal.InternalEditorUtility.SetCustomLighting(lights, Color.gray); for (int i = 0; i < runtimeData.instanceLODs[0].renderers.Count; i++) { GPUInstancerRenderer renderer = runtimeData.instanceLODs[0].renderers[i]; if (renderer.materials != null) { int submeshIndex = 0; foreach (Material mat in renderer.materials) { Graphics.DrawMesh(renderer.mesh, renderer.transformOffset, mat, _sampleLayer, _camera, Math.Min(submeshIndex, renderer.mesh.subMeshCount - 1), null, ShadowCastingMode.Off, false, null, false); submeshIndex++; } } } _camera.Render(); UnityEditorInternal.InternalEditorUtility.RemoveCustomLighting(); } #region Generate Texture Texture2D newTx = new Texture2D(_renderTexture.width, _renderTexture.height, TextureFormat.RGBA32, true); newTx.ReadPixels(new Rect(0, 0, _renderTexture.width, _renderTexture.height), 0, 0); newTx.Apply(); #endregion Generate Texture #region End Preview RenderTexture.active = previousRenderTexture; foreach (var light in lights) light.enabled = false; RenderTexture.ReleaseTemporary(_renderTexture); #endregion End Preview return newTx; } public float GetScaleFactor(float width, float height) { float scaleFacX = Mathf.Max(Mathf.Min(width * 2, 1024), width) / width; float scaleFacY = Mathf.Max(Mathf.Min(height * 2, 1024), height) / height; float result = Mathf.Min(scaleFacX, scaleFacY) * EditorGUIUtility.pixelsPerPoint; return result; } protected static GameObject CreateLight() { GameObject lightGO = EditorUtility.CreateGameObjectWithHideFlags("PreRenderLight", HideFlags.HideAndDontSave, typeof(Light)); var light = lightGO.GetComponent(); light.type = LightType.Directional; light.intensity = 1.0f; light.enabled = false; return lightGO; } } }