// Amplify Impostors // Copyright (c) Amplify Creations, Lda //#define AI_DEBUG_MODE using System; using System.Runtime.InteropServices; using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Experimental.Rendering; using Unity.Collections; using System.IO; #if UNITY_EDITOR using UnityEditor; using Unity.Collections.LowLevel.Unsafe; #endif namespace AmplifyImpostors { public enum LODReplacement { DoNothing = 0, ReplaceCulled = 1, ReplaceLast = 2, ReplaceAllExceptFirst = 3, ReplaceSpecific = 4, ReplaceAfterSpecific = 5, InsertAfter = 6 } public enum CutMode { Automatic = 0, Manual = 1 } public enum FolderMode { RelativeToPrefab = 0, Global = 1 } public enum RenderPipelineInUse { None = 0, HDRP = 1, URP = 2, Custom = 3 } //[ExecuteInEditMode] [HelpURL( "https://wiki.amplify.pt/index.php?title=Unity_Products:Amplify_Impostors/Manual" )] public class AmplifyImpostor : MonoBehaviour { public const string DefaultPreset = "e4786beb7716da54dbb02a632681cc37"; public const string ShaderBiRP = "e82933f4c0eb9ba42aab0739f48efe21"; public const string ShaderOctaBiRP = "572f9be5706148142b8da6e9de53acdb"; public const string ShaderHDRP = "175c951fec709c44fa2f26b8ab78b8dd"; public const string ShaderOctaHDRP = "56236dc63ad9b7949b63a27f0ad180b3"; public const string ShaderURP = "da79d698f4bf0164e910ad798d07efdf"; public const string ShaderOctaURP = "83dd8de9a5c14874884f9012def4fdcc"; public const string DilateGUID = "57c23892d43bc9f458360024c5985405"; public const string PackerGUID = "31bd3cd74692f384a916d9d7ea87710d"; public const string GBufferToOutputGUID = "9587d58ea8f1dac478d1adbf2a63d31f"; private const string GlobalShaderVariablesQualifiedNameHDRP = "UnityEngine.Rendering.HighDefinition.ShaderVariablesGlobal, Unity.RenderPipelines.HighDefinition.Runtime, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"; public readonly static int _DetailNormalMap_PID = Shader.PropertyToID( "_DetailNormalMap" ); [SerializeField] private AmplifyImpostorAsset m_data; public AmplifyImpostorAsset Data { get { return m_data; } set { m_data = value; } } [SerializeField] private Transform m_rootTransform; public Transform RootTransform { get { return m_rootTransform; } set { m_rootTransform = value; } } [SerializeField] private LODGroup m_lodGroup; public LODGroup LodGroup { get { return m_lodGroup; } set { m_lodGroup = value; } } [SerializeField] private Renderer[] m_renderers; public Renderer[] Renderers { get { return m_renderers; } set { m_renderers = value; } } public LODReplacement m_lodReplacement = LODReplacement.ReplaceLast; [SerializeField] public RenderPipelineInUse m_renderPipelineInUse = RenderPipelineInUse.None; public int m_insertIndex = 1; [SerializeField] public GameObject m_lastImpostor; [SerializeField] public string m_folderPath; [NonSerialized] public string m_impostorName = string.Empty; [SerializeField] public CutMode m_cutMode = CutMode.Automatic; [NonSerialized] private const float StartXRotation = -90; [NonSerialized] private const float StartYRotation = 90; [NonSerialized] private const int MinAlphaResolution = 256; [NonSerialized] private RenderTexture[] m_rtGBuffers; [NonSerialized] private RenderTexture[] m_outBuffers; [NonSerialized] private RenderTexture[] m_alphaGBuffers; [NonSerialized] private RenderTexture m_trueDepth; [NonSerialized] public Texture2D m_alphaTex; [NonSerialized] private float m_xyFitSize = 0; [NonSerialized] private float m_depthFitSize = 0; [NonSerialized] private Vector2 m_pixelOffset = Vector2.zero; [NonSerialized] private Bounds m_originalBound = new Bounds(); [NonSerialized] private Vector3 m_oriPos = Vector3.zero; [NonSerialized] private Quaternion m_oriRot = Quaternion.identity; [NonSerialized] private Vector3 m_oriSca = Vector3.one; [NonSerialized] private const int BlockSize = 65536; #if UNITY_EDITOR [NonSerialized] private readonly string[] m_propertyNames = { "_Albedo", "_Normals", "_Specular", "_Occlusion", "_Emission", "_Position" }; [NonSerialized] private string[] m_standardFileNamesOld = { string.Empty, string.Empty, string.Empty, string.Empty, string.Empty, string.Empty }; [NonSerialized] private string[] m_standardFileNames = { string.Empty, string.Empty, string.Empty, string.Empty, string.Empty, string.Empty }; [NonSerialized] private string[] m_fileNames = { string.Empty, string.Empty, string.Empty, string.Empty, string.Empty, string.Empty }; [Serializable] private class TextureData { public bool SRGB = true; public bool Alpha = true; public TextureCompression Compression = TextureCompression.Normal; public int MaxSize = -1; public TextureData() { } public TextureData( bool sRGB, TextureCompression compression, bool alpha, int maxSize ) { SRGB = sRGB; Compression = compression; Alpha = alpha; MaxSize = maxSize; } } #endif [NonSerialized] private Matrix4x4[] m_cameraInvViewProjPerFrame = null; #if AI_DEBUG_MODE [SerializeField] private string m_renderInfo = string.Empty; public string RenderInfo { get { return m_renderInfo; } set { m_renderInfo = value; } } public bool m_createGameobject = true; public bool m_generateQuad = true; #endif private enum RenderImpostorMode { Alpha = 0, Normal = 1 } private void GenerateTextures( List outputList, bool standardRendering ) { m_outBuffers = new RenderTexture[ outputList.Count ]; for ( int i = 0; i < outputList.Count; i++ ) { m_outBuffers[ i ] = new RenderTexture( ( int )m_data.TexSize.x, ( int )m_data.TexSize.y, 16, outputList[ i ].SRGB ? RenderTextureFormat.ARGB32 : RenderTextureFormat.ARGBHalf ); m_outBuffers[ i ].Create(); } if ( standardRendering ) { m_rtGBuffers = new RenderTexture[ 4 ]; if ( m_renderPipelineInUse == RenderPipelineInUse.HDRP ) { // Albedo (RGB) Occlusion (A) [A => occlusion(7) / isLightmap(1)] m_rtGBuffers[ 0 ] = new RenderTexture( ( int )m_data.TexSize.x, ( int )m_data.TexSize.y, 16, GraphicsFormat.R8G8B8A8_SRGB ); m_rtGBuffers[ 0 ].Create(); // Normals (RGB) PerceptualRoughness (A) [RGB => X:Y / 12:12 / Octa] m_rtGBuffers[ 1 ] = new RenderTexture( ( int )m_data.TexSize.x, ( int )m_data.TexSize.y, 16, GraphicsFormat.R8G8B8A8_UNorm ); m_rtGBuffers[ 1 ].Create(); // Specular (RGB) m_rtGBuffers[ 2 ] = new RenderTexture( ( int )m_data.TexSize.x, ( int )m_data.TexSize.y, 16, GraphicsFormat.R8G8B8A8_UNorm ); m_rtGBuffers[ 2 ].Create(); // Emission (RGB) Alpha (A) m_rtGBuffers[ 3 ] = new RenderTexture( ( int )m_data.TexSize.x, ( int )m_data.TexSize.y, 16, GraphicsFormat.R16G16B16A16_SFloat ); m_rtGBuffers[ 3 ].Create(); } else if ( m_renderPipelineInUse == RenderPipelineInUse.URP ) { // Albedo (RGB) MaterialFlags (A) [A => miscFlags(7) / specularSetup(1)] m_rtGBuffers[ 0 ] = new RenderTexture( ( int )m_data.TexSize.x, ( int )m_data.TexSize.y, 16, GraphicsFormat.R8G8B8A8_SRGB ); m_rtGBuffers[ 0 ].Create(); // Specular (RGB) Occlusion (A) [specularSetup ? R => Metallic : RGB => Specular ] m_rtGBuffers[ 1 ] = new RenderTexture( ( int )m_data.TexSize.x, ( int )m_data.TexSize.y, 16, GraphicsFormat.R8G8B8A8_UNorm ); m_rtGBuffers[ 1 ].Create(); // Normals (RGB) Smoothness (A) m_rtGBuffers[ 2 ] = new RenderTexture( ( int )m_data.TexSize.x, ( int )m_data.TexSize.y, 16, GraphicsFormat.R8G8B8A8_SNorm ); m_rtGBuffers[ 2 ].Create(); // Emission (RGB) Alpha (A) m_rtGBuffers[ 3 ] = new RenderTexture( ( int )m_data.TexSize.x, ( int )m_data.TexSize.y, 16, GraphicsFormat.R16G16B16A16_SFloat ); m_rtGBuffers[ 3 ].Create(); } else { // Albedo (RGB) Occlusion (A) m_rtGBuffers[ 0 ] = new RenderTexture( ( int )m_data.TexSize.x, ( int )m_data.TexSize.y, 16, GraphicsFormat.R8G8B8A8_SRGB ); m_rtGBuffers[ 0 ].Create(); // Specular (RGB) Smoothness (A) m_rtGBuffers[ 1 ] = new RenderTexture( ( int )m_data.TexSize.x, ( int )m_data.TexSize.y, 16, GraphicsFormat.R8G8B8A8_SRGB ); m_rtGBuffers[ 1 ].Create(); // Normals (RGB) m_rtGBuffers[ 2 ] = new RenderTexture( ( int )m_data.TexSize.x, ( int )m_data.TexSize.y, 16, GraphicsFormat.A2B10G10R10_UNormPack32 ); m_rtGBuffers[ 2 ].Create(); // Emission (RGB) Alpha (A) m_rtGBuffers[ 3 ] = new RenderTexture( ( int )m_data.TexSize.x, ( int )m_data.TexSize.y, 16, GraphicsFormat.R16G16B16A16_SFloat ); m_rtGBuffers[ 3 ].Create(); } } else { m_rtGBuffers = m_outBuffers; } m_trueDepth = new RenderTexture( (int)m_data.TexSize.x, (int)m_data.TexSize.y, 16, RenderTextureFormat.Depth ); m_trueDepth.Create(); } private void GenerateAlphaTextures( int targetAmount ) { m_alphaGBuffers = new RenderTexture[ targetAmount ]; for( int i = 0; i < m_alphaGBuffers.Length; ++i ) { m_alphaGBuffers[ i ] = new RenderTexture( MinAlphaResolution, MinAlphaResolution, 16, RenderTextureFormat.ARGB32 ); m_alphaGBuffers[ i ].Create(); } m_trueDepth = new RenderTexture( MinAlphaResolution, MinAlphaResolution, 16, RenderTextureFormat.Depth ); m_trueDepth.Create(); } private void ClearBuffers() { RenderTexture.active = null; foreach( var rt in m_rtGBuffers ) { rt.Release(); } m_rtGBuffers = null; foreach ( var rt in m_outBuffers ) { rt.Release(); } m_outBuffers = null; } private void ClearAlphaBuffers( ) { RenderTexture.active = null; foreach( var rt in m_alphaGBuffers ) { rt.Release(); } m_alphaGBuffers = null; } public static void GetFrameInfo( AmplifyImpostorAsset data, out int hframes, out int vframes ) { hframes = data.HorizontalFrames; vframes = ( data.ImpostorType == ImpostorType.Spherical && data.DecoupleAxisFrames ) ? data.VerticalFrames : data.HorizontalFrames; } #if UNITY_EDITOR private static readonly List> m_hdrpStencilCheck = new List> { new KeyValuePair( "_StencilForwardRef", 0 ), new KeyValuePair( "_StencilForwardMask", 6 ), new KeyValuePair( "_StencilMotionRef", 40 ), new KeyValuePair( "_StencilMotionMask", 40 ), new KeyValuePair( "_StencilDepthRef", 8 ), new KeyValuePair( "_StencilDepthMask", 8 ), new KeyValuePair( "_StencilGBufferRef", 10 ), new KeyValuePair( "_StencilGBufferMask", 14 ) }; public void CheckHDRPMaterial() { if( m_renderPipelineInUse != RenderPipelineInUse.HDRP ) return; if( m_data == null || m_data.Preset == null || m_data.Material == null ) return; foreach ( var pair in m_hdrpStencilCheck ) { if ( m_data.Material.HasProperty( pair.Key ) && m_data.Material.GetInt( pair.Key ) != pair.Value ) { m_data.Material.SetInt( pair.Key, pair.Value ); } } } public void SaveDebugRenderTexturePNG( ref RenderTexture rtex, string path ) { SaveTexture( ref rtex, path, ImageFormat.PNG, 1, TextureChannels.RGBA, rtex.width, rtex.height ); } public void SaveTexture( ref RenderTexture tex, string path, ImageFormat imageFormat, int resizeScale, TextureChannels channels, int width = 0, int height = 0 ) { width = ( width != 0 ) ? width : ( int )m_data.TexSize.x; height = ( height != 0 ) ? height : ( int )m_data.TexSize.y; Texture2D outfile = AssetDatabase.LoadAssetAtPath( path ); if( imageFormat == ImageFormat.EXR ) outfile = new Texture2D( (int)width / resizeScale, (int)height / resizeScale, TextureFormat.RGBAFloat, false ); else outfile = new Texture2D( (int)width / resizeScale, (int)height / resizeScale, channels == TextureChannels.RGB ? TextureFormat.RGB24 : TextureFormat.RGBA32, true ); outfile.name = Path.GetFileNameWithoutExtension( path ); RenderTexture temp = RenderTexture.active; RenderTexture.active = tex; outfile.ReadPixels( new Rect( 0, 0, (int)width / resizeScale, (int)height / resizeScale ), 0, 0 ); RenderTexture.active = temp; outfile.Apply(); byte[] bytes; switch( imageFormat ) { case ImageFormat.PNG: bytes = outfile.EncodeToPNG(); break; default: case ImageFormat.TGA: bytes = outfile.EncodeToTGA( Texture2DEx.Compression.RLE ); break; case ImageFormat.EXR: bytes = ImageConversion.EncodeToEXR( outfile, Texture2D.EXRFlags.CompressZIP ); break; } if( imageFormat == ImageFormat.EXR ) { File.WriteAllBytes( path, bytes ); DestroyImmediate( outfile ); } else { int BytesToWrite, BufIndex; int bytesLength = bytes.Length; FileStream FSFile = new FileStream( path, FileMode.Create, FileAccess.Write, FileShare.None, BlockSize, false ); BufIndex = 0; do { BytesToWrite = Math.Min( BlockSize, bytesLength - BufIndex ); FSFile.Write( bytes, BufIndex, BytesToWrite ); BufIndex += BytesToWrite; } while( BufIndex < bytesLength ); FSFile.Close(); FSFile.Dispose(); DestroyImmediate( outfile ); } } public void ChangeTextureImporter( ref RenderTexture tex, string path, bool sRGB = true, bool changeResolution = false, TextureCompression compression = TextureCompression.Normal, bool alpha = true ) { Texture2D outfile = AssetDatabase.LoadAssetAtPath( path ); TextureImporter tImporter = AssetImporter.GetAtPath( path ) as TextureImporter; if( tImporter != null ) { if( (tImporter.alphaSource == TextureImporterAlphaSource.FromInput && !alpha) || ( tImporter.textureCompression != (TextureImporterCompression)compression ) || tImporter.sRGBTexture != sRGB || ( changeResolution && tImporter.maxTextureSize != (int)m_data.TexSize.x ) ) { tImporter.sRGBTexture = sRGB; tImporter.alphaSource = alpha ? TextureImporterAlphaSource.FromInput : TextureImporterAlphaSource.None; tImporter.textureCompression = (TextureImporterCompression)compression; if( changeResolution ) tImporter.maxTextureSize = (int)m_data.TexSize.x; EditorUtility.SetDirty( tImporter ); EditorUtility.SetDirty( outfile ); tImporter.SaveAndReimport(); } } } public void CalculateSheetBounds() { m_xyFitSize = 0; m_depthFitSize = 0; GetFrameInfo( m_data, out int hframes, out int vframes ); for( int x = 0; x < hframes; x++ ) { for( int y = 0; y < vframes; y++ ) { Bounds frameBounds = new Bounds(); Matrix4x4 camMatrixRot = GetCameraRotationMatrix( m_data.ImpostorType, hframes, vframes, x, y ); for( int i = 0; i < Renderers.Length; i++ ) { if( Renderers[ i ] == null || !Renderers[ i ].enabled || Renderers[ i ].shadowCastingMode == ShadowCastingMode.ShadowsOnly ) continue; MeshFilter mf = Renderers[ i ].GetComponent(); if( mf == null || mf.sharedMesh == null ) continue; if( frameBounds.size == Vector3.zero ) frameBounds = mf.sharedMesh.bounds.Transform( m_rootTransform.worldToLocalMatrix * Renderers[ i ].localToWorldMatrix ); else frameBounds.Encapsulate( mf.sharedMesh.bounds.Transform( m_rootTransform.worldToLocalMatrix * Renderers[ i ].localToWorldMatrix ) ); } if( x == 0 && y == 0 ) m_originalBound = frameBounds; frameBounds = frameBounds.Transform( camMatrixRot ); m_xyFitSize = Mathf.Max( m_xyFitSize, frameBounds.size.x, frameBounds.size.y ); const float depthFitCorrection = 1.0f; // @diogo: cube with a smaller cube on top was intersecting near plane m_depthFitSize = Mathf.Max( m_depthFitSize, frameBounds.size.z * depthFitCorrection ); } } #if AI_DEBUG_MODE m_renderInfo = ""; m_renderInfo += "\nXY fit:\t" + m_trueFitsize; m_renderInfo += "\nDepth:\t" + m_depthFitsize; #endif } public void DilateRenderTextureUsingMask( ref RenderTexture mainTex, ref RenderTexture maskTex, int pixelBleed, bool alpha, Material dilateMat = null ) { if( pixelBleed == 0 ) return; bool destroyMaterial = false; if( dilateMat == null ) { destroyMaterial = true; Shader dilateShader = AssetDatabase.LoadAssetAtPath( AssetDatabase.GUIDToAssetPath( DilateGUID ) ); dilateMat = new Material( dilateShader ); } RenderTexture tempTex = RenderTexture.GetTemporary( mainTex.width, mainTex.height, mainTex.depth, mainTex.format ); RenderTexture tempMask = RenderTexture.GetTemporary( maskTex.width, maskTex.height, maskTex.depth, maskTex.format ); RenderTexture dilatedMask = RenderTexture.GetTemporary( maskTex.width, maskTex.height, maskTex.depth, maskTex.format ); Graphics.Blit( maskTex, dilatedMask ); for( int i = 0; i < pixelBleed; i++ ) { dilateMat.SetTexture( "_MaskTex", dilatedMask ); Graphics.Blit( mainTex, tempTex, dilateMat, alpha ? 1 : 0 ); Graphics.Blit( tempTex, mainTex ); Graphics.Blit( dilatedMask, tempMask, dilateMat, 1 ); Graphics.Blit( tempMask, dilatedMask ); } RenderTexture.ReleaseTemporary( tempTex ); RenderTexture.ReleaseTemporary( tempMask ); RenderTexture.ReleaseTemporary( dilatedMask ); if( destroyMaterial ) { DestroyImmediate( dilateMat ); dilateMat = null; } } public void PackingRemapping( ref RenderTexture src, ref RenderTexture dst, int passIndex, Material packerMat = null, Texture extraTex = null, string texName = null ) { bool destroyMaterial = false; if( packerMat == null ) { destroyMaterial = true; Shader packerShader = AssetDatabase.LoadAssetAtPath( AssetDatabase.GUIDToAssetPath( PackerGUID ) ); packerMat = new Material( packerShader ); } if( extraTex != null ) { if( string.IsNullOrEmpty( texName ) ) packerMat.SetTexture( "_A", extraTex ); else packerMat.SetTexture( texName, extraTex ); } if( src == dst ) { int width = src.width; int height = src.height; int depth = src.depth; RenderTextureFormat format = src.format; RenderTexture tempTex = RenderTexture.GetTemporary( width, height, depth, format ); Graphics.Blit( src, tempTex, packerMat, passIndex ); Graphics.Blit( tempTex, dst ); RenderTexture.ReleaseTemporary( tempTex ); } else { Graphics.Blit( src, dst, packerMat, passIndex ); } if( destroyMaterial ) { DestroyImmediate( packerMat ); packerMat = null; } } private void CopyTransform() { m_oriPos = RootTransform.position; m_oriRot = RootTransform.rotation; m_oriSca = RootTransform.localScale; RootTransform.position = Vector3.zero; RootTransform.rotation = Quaternion.identity; RootTransform.localScale = Vector3.one; } private void PasteTransform() { RootTransform.position = m_oriPos; RootTransform.rotation = m_oriRot; RootTransform.localScale = m_oriSca; } public void CalculatePixelBounds( int targetAmount ) { bool sRGBcache = GL.sRGBWrite; CalculateSheetBounds(); GenerateAlphaTextures( targetAmount ); GL.sRGBWrite = true; m_pixelOffset = Vector2.zero; // TODO: remove this temporary solution CopyTransform(); try { RenderImpostor( targetAmount, RenderImpostorMode.Alpha, true, m_data.Preset.BakeShader ); PasteTransform(); } finally { PasteTransform(); EditorUtility.ClearProgressBar(); } GL.sRGBWrite = sRGBcache; bool standardRendering = ( m_data.Preset.BakeShader == null ); int alphaIndex = standardRendering ? 3 : m_data.Preset.AlphaIndex; Shader packerShader = AssetDatabase.LoadAssetAtPath( AssetDatabase.GUIDToAssetPath( PackerGUID ) ); Material packerMat = new Material( packerShader ); // Generate Alpha using Depth RenderTexture tempTex2 = RenderTextureEx.GetTemporary( m_alphaGBuffers[ alphaIndex ] ); Graphics.Blit( m_alphaGBuffers[ alphaIndex ], tempTex2 ); packerMat.SetTexture( "_A", tempTex2 ); Graphics.Blit( m_trueDepth, m_alphaGBuffers[ alphaIndex ], packerMat, 11 ); RenderTexture.ReleaseTemporary( tempTex2 ); m_trueDepth.Release(); m_trueDepth = null; // Render just alpha RenderTexture combinedAlphaTexture = RenderTexture.GetTemporary( MinAlphaResolution, MinAlphaResolution, m_alphaGBuffers[ alphaIndex ].depth, m_alphaGBuffers[ alphaIndex ].format ); PackingRemapping( ref m_alphaGBuffers[ alphaIndex ], ref combinedAlphaTexture, 8, packerMat ); DestroyImmediate( packerMat ); packerMat = null; ClearAlphaBuffers(); RenderTexture.active = combinedAlphaTexture; Texture2D tempTex = new Texture2D( combinedAlphaTexture.width, combinedAlphaTexture.height, TextureFormat.RGBA32, false ); tempTex.ReadPixels( new Rect( 0, 0, combinedAlphaTexture.width, combinedAlphaTexture.height ), 0, 0 ); tempTex.Apply(); RenderTexture.active = null; RenderTexture.ReleaseTemporary( combinedAlphaTexture ); Rect testRect = new Rect( 0, 0, tempTex.width, tempTex.height ); Vector2[][] paths; SpriteUtilityEx.GenerateOutline( tempTex, testRect, 0.2f, 0, false, out paths ); int sum = 0; for( int i = 0; i < paths.Length; i++ ) { sum += paths[ i ].Length; } Vector2[] minMaxPoints = new Vector2[ sum ]; int index = 0; for( int i = 0; i < paths.Length; i++ ) { for( int j = 0; j < paths[ i ].Length; j++ ) { minMaxPoints[ index ] = (Vector2)( paths[ i ][ j ] ) + ( new Vector2( tempTex.width * 0.5f, tempTex.height * 0.5f ) ); minMaxPoints[ index ] = Vector2.Scale( minMaxPoints[ index ], new Vector2( 1.0f / tempTex.width, 1.0f / tempTex.height ) ); index++; } } Vector2 mins = Vector2.one; Vector2 maxs = Vector2.zero; for( int i = 0; i < minMaxPoints.Length; i++ ) { mins.x = Mathf.Min( minMaxPoints[ i ].x, mins.x ); mins.y = Mathf.Min( minMaxPoints[ i ].y, mins.y ); maxs.x = Mathf.Max( minMaxPoints[ i ].x, maxs.x ); maxs.y = Mathf.Max( minMaxPoints[ i ].y, maxs.y ); } Vector2 height = ( maxs - mins ); float maxBound = Mathf.Max( height.x, height.y ); Vector2 center = mins + ( height * 0.5f ); m_pixelOffset = ( center - ( Vector2.one * 0.5f ) ) * m_xyFitSize; //Debug.Log( m_pixelOffset.ToString( "N5" ) ); //Debug.Log( height.ToString( "N5" ) ); m_xyFitSize *= maxBound; m_depthFitSize *= maxBound; } // For inspector public void RenderCombinedAlpha( AmplifyImpostorAsset data = null ) { AmplifyImpostorAsset tempData = m_data; if( data != null ) m_data = data; bool standardRendering = ( m_data.Preset.BakeShader == null ); int targetAmount = standardRendering ? 4 : m_data.Preset.Output.Count; CalculatePixelBounds( targetAmount ); GenerateAlphaTextures( targetAmount ); bool sRGBcache = GL.sRGBWrite; GL.sRGBWrite = true; // TODO: remove this temporary solution CopyTransform(); try { RenderImpostor( targetAmount, RenderImpostorMode.Alpha, false, m_data.Preset.BakeShader ); PasteTransform(); } finally { PasteTransform(); EditorUtility.ClearProgressBar(); } GL.sRGBWrite = sRGBcache; int alphaIndex = standardRendering ? 3 : m_data.Preset.AlphaIndex; Shader packerShader = AssetDatabase.LoadAssetAtPath( AssetDatabase.GUIDToAssetPath( PackerGUID ) ); Material packerMat = new Material( packerShader ); // Generate Alpha using Depth RenderTexture tempTex = RenderTextureEx.GetTemporary( m_alphaGBuffers[ alphaIndex ] ); Graphics.Blit( m_alphaGBuffers[ alphaIndex ], tempTex ); packerMat.SetTexture( "_A", tempTex ); Graphics.Blit( m_trueDepth, m_alphaGBuffers[ alphaIndex ], packerMat, 11 ); RenderTexture.ReleaseTemporary( tempTex ); m_trueDepth.Release(); m_trueDepth = null; // Render just alpha RenderTexture combinedAlphaTexture = RenderTexture.GetTemporary( MinAlphaResolution, MinAlphaResolution, m_alphaGBuffers[ alphaIndex ].depth, m_alphaGBuffers[ alphaIndex ].format ); PackingRemapping( ref m_alphaGBuffers[ alphaIndex ], ref combinedAlphaTexture, 8, packerMat ); DestroyImmediate( packerMat ); packerMat = null; ClearAlphaBuffers(); RenderTexture.active = combinedAlphaTexture; m_alphaTex = new Texture2D( combinedAlphaTexture.width, combinedAlphaTexture.height, TextureFormat.RGBAFloat, false ); m_alphaTex.ReadPixels( new Rect( 0, 0, combinedAlphaTexture.width, combinedAlphaTexture.height ), 0, 0 ); m_alphaTex.Apply(); RenderTexture.active = null; RenderTexture.ReleaseTemporary( combinedAlphaTexture ); m_data = tempData; } public void CreateAssetFile( AmplifyImpostorAsset data = null ) { string folderPath = this.OpenFolderForImpostor(); if( string.IsNullOrEmpty( folderPath ) ) return; string fileName = m_impostorName; if( string.IsNullOrEmpty( fileName ) ) fileName = m_rootTransform.name + "_Impostor"; folderPath = folderPath.TrimEnd( new char[] { '/', '*', '.', ' ' } ); folderPath += "/"; folderPath = folderPath.TrimStart( new char[] { '/', '*', '.', ' ' } ); if( m_data == null ) { Undo.RegisterCompleteObjectUndo( this, "Create Impostor Asset" ); AmplifyImpostorAsset existingAsset = AssetDatabase.LoadAssetAtPath( folderPath + fileName + ".asset" ); if( existingAsset != null ) { m_data = existingAsset; } else { m_data = ScriptableObject.CreateInstance(); AssetDatabase.CreateAsset( m_data, folderPath + fileName + ".asset" ); } } } private void DisplayProgress( float progress, string message ) { #if UNITY_EDITOR if( !Application.isPlaying ) { EditorUtility.DisplayProgressBar( "Baking Impostor", message, progress ); if( progress >= 1.0f ) EditorUtility.ClearProgressBar(); } #endif } public void DetectRenderPipeline() { string pipelineName = string.Empty; try { pipelineName = UnityEngine.Rendering.RenderPipelineManager.currentPipeline.ToString(); } catch( Exception ) { pipelineName = ""; } if( pipelineName.Contains( "UniversalRenderPipeline" ) ) { m_renderPipelineInUse = RenderPipelineInUse.URP; } else if( pipelineName.Contains( "HDRenderPipeline" ) ) { m_renderPipelineInUse = RenderPipelineInUse.HDRP; } else if( pipelineName.Equals( "" ) ) { m_renderPipelineInUse = RenderPipelineInUse.None; } else { m_renderPipelineInUse = RenderPipelineInUse.Custom; } } public static void UpdateKeywords( Material material, AmplifyImpostorBakePreset preset ) { foreach ( var outout in preset.Output ) { if ( material.HasProperty( outout.Name ) ) { if ( !outout.Active ) { material.SetTexture( outout.Name, null ); } material.EnsureKeywordState( outout.Name.ToUpper() + "MAP", outout.Active ); } } } private void PostProcessTextures( Dictionary textureImportData ) { foreach ( var pair in textureImportData ) { TextureData desc = pair.Value; TextureImporter textureImporter = ( TextureImporter )AssetImporter.GetAtPath( pair.Key ); var desiredSRGB = desc.SRGB; var desiredAlphaSource = desc.Alpha ? TextureImporterAlphaSource.FromInput : TextureImporterAlphaSource.None; var desiredCompression = ( TextureImporterCompression )desc.Compression; var desiredMaxSize = ( desc.MaxSize > -1 ) ? desc.MaxSize : textureImporter.maxTextureSize; if ( textureImporter.sRGBTexture != desiredSRGB || textureImporter.alphaSource != desiredAlphaSource || textureImporter.textureCompression != desiredCompression || textureImporter.maxTextureSize != desiredMaxSize ) { textureImporter.sRGBTexture = desiredSRGB; textureImporter.alphaSource = desiredAlphaSource; textureImporter.textureCompression = desiredCompression; textureImporter.maxTextureSize = desiredMaxSize; textureImporter.SaveAndReimport(); } } } public void RenderAllDeferredGroups( AmplifyImpostorAsset data = null ) { string folderPath = m_folderPath; if( m_data == null ) { folderPath = this.OpenFolderForImpostor(); } else { m_impostorName = m_data.name; folderPath = Path.GetDirectoryName( AssetDatabase.GetAssetPath( m_data ) ).Replace( "\\", "/" ) + "/"; } if( string.IsNullOrEmpty( folderPath ) ) return; DisplayProgress( 0, "Please Wait... Setting up" ); string fileName = m_impostorName; if( string.IsNullOrEmpty( fileName ) ) fileName = m_rootTransform.name + "_Impostor"; m_folderPath = folderPath; folderPath = folderPath.TrimEnd( new char[] { '/', '*', '.', ' ' } ); folderPath += "/"; folderPath = folderPath.TrimStart( new char[] { '/', '*', '.', ' ' } ); m_impostorName = fileName; Undo.RegisterCompleteObjectUndo( this, "Create Impostor" ); DetectRenderPipeline(); if( m_data == null ) { AmplifyImpostorAsset existingAsset = AssetDatabase.LoadAssetAtPath( folderPath + fileName + ".asset" ); m_data = ScriptableObject.CreateInstance(); AssetDatabase.CreateAsset( m_data, folderPath + fileName + ".asset" ); if( data != null ) { m_data.ShapePoints = data.ShapePoints; } } else if( data != null ) { m_data = data; } bool chache = GL.sRGBWrite; GL.sRGBWrite = true; if( !m_data.DecoupleAxisFrames ) m_data.HorizontalFrames = m_data.VerticalFrames; if( m_data.Preset == null ) { m_data.Preset = AssetDatabase.LoadAssetAtPath( AssetDatabase.GUIDToAssetPath( DefaultPreset ) ); } bool standardRendering = ( m_data.Preset.BakeShader == null ); if ( standardRendering && m_data.Preset.Output.Count != AmplifyImpostorBakePreset.DefaultOutputCount ) { Debug.LogError( "[AmplifyImpostor] Detected an error in Bake Preset " + m_data.Preset.name + ". It uses a default/null baking shader but the outputs are non-default." ); return; } List outputList = new List(); for( int i = 0; i < m_data.Preset.Output.Count; i++ ) outputList.Add( m_data.Preset.Output[ i ].Clone() ); for( int i = 0; i < m_data.OverrideOutput.Count && i < m_data.Preset.Output.Count; i++ ) { if( ( m_data.OverrideOutput[ i ].OverrideMask & OverrideMask.OutputToggle ) == OverrideMask.OutputToggle ) outputList[ m_data.OverrideOutput[ i ].Index ].Active = m_data.OverrideOutput[ i ].Active; if( ( m_data.OverrideOutput[ i ].OverrideMask & OverrideMask.NameSuffix ) == OverrideMask.NameSuffix ) outputList[ m_data.OverrideOutput[ i ].Index ].Name = m_data.OverrideOutput[ i ].Name; if( ( m_data.OverrideOutput[ i ].OverrideMask & OverrideMask.RelativeScale ) == OverrideMask.RelativeScale ) outputList[ m_data.OverrideOutput[ i ].Index ].Scale = m_data.OverrideOutput[ i ].Scale; if( ( m_data.OverrideOutput[ i ].OverrideMask & OverrideMask.ColorSpace ) == OverrideMask.ColorSpace ) outputList[ m_data.OverrideOutput[ i ].Index ].SRGB = m_data.OverrideOutput[ i ].SRGB; if( ( m_data.OverrideOutput[ i ].OverrideMask & OverrideMask.QualityCompression ) == OverrideMask.QualityCompression ) outputList[ m_data.OverrideOutput[ i ].Index ].Compression = m_data.OverrideOutput[ i ].Compression; if( ( m_data.OverrideOutput[ i ].OverrideMask & OverrideMask.FileFormat ) == OverrideMask.FileFormat ) outputList[ m_data.OverrideOutput[ i ].Index ].ImageFormat = m_data.OverrideOutput[ i ].ImageFormat; } m_fileNames = new string[ outputList.Count ]; string guid = string.Empty; if( m_renderPipelineInUse == RenderPipelineInUse.HDRP ) guid = m_data.ImpostorType == ImpostorType.Spherical ? ShaderHDRP : ShaderOctaHDRP; else if( m_renderPipelineInUse == RenderPipelineInUse.URP ) guid = m_data.ImpostorType == ImpostorType.Spherical ? ShaderURP : ShaderOctaURP; else guid = m_data.ImpostorType == ImpostorType.Spherical ? ShaderBiRP : ShaderOctaBiRP; int targetAmount = standardRendering ? 4 : outputList.Count; CalculatePixelBounds( targetAmount ); DisplayProgress( 0.1f, "Please Wait... Allocating Resources" ); GenerateTextures( outputList, standardRendering ); DisplayProgress( 0.2f, "Please Wait... Baking" ); // TODO: remove this temporary solution CopyTransform(); GetFrameInfo( m_data, out int hframes, out int vframes ); m_cameraInvViewProjPerFrame = new Matrix4x4[ hframes * vframes ]; try { RenderImpostor( targetAmount, RenderImpostorMode.Normal, true, m_data.Preset.BakeShader ); PasteTransform(); } finally { PasteTransform(); EditorUtility.ClearProgressBar(); } DisplayProgress( 0.5f, "Please Wait... Remapping" ); Shader packerShader = AssetDatabase.LoadAssetAtPath( AssetDatabase.GUIDToAssetPath( PackerGUID ) ); Material packerMat = new Material( packerShader ); Shader gbufferToOutputShader = AssetDatabase.LoadAssetAtPath( AssetDatabase.GUIDToAssetPath( GBufferToOutputGUID ) ); Material gbufferToOutputMat = new Material( gbufferToOutputShader ); int alphaIndex = m_data.Preset.AlphaIndex; if ( standardRendering ) { gbufferToOutputMat.SetInt( "_RenderPipeline", ( int )m_renderPipelineInUse ); gbufferToOutputMat.SetTexture( "_GBuffer0", m_rtGBuffers[ 0 ] ); gbufferToOutputMat.SetTexture( "_GBuffer1", m_rtGBuffers[ 1 ] ); gbufferToOutputMat.SetTexture( "_GBuffer2", m_rtGBuffers[ 2 ] ); gbufferToOutputMat.SetTexture( "_GBuffer3", m_rtGBuffers[ 3 ] ); gbufferToOutputMat.SetTexture( "_Depth", m_trueDepth ); var invViewProjBuffer = new ComputeBuffer( m_cameraInvViewProjPerFrame.Length, sizeof( float ) * 16, ComputeBufferType.Structured ); invViewProjBuffer.SetData( m_cameraInvViewProjPerFrame ); gbufferToOutputMat.SetBuffer( "_CameraInvViewProjPerFrame", invViewProjBuffer ); gbufferToOutputMat.SetVector( "_FrameCount", new Vector2( hframes, vframes ) ); gbufferToOutputMat.SetFloat( "_DepthSize", m_depthFitSize ); gbufferToOutputMat.SetVector( "_BoundsMin", m_originalBound.min ); gbufferToOutputMat.SetVector( "_BoundsSize", m_originalBound.size ); for ( int i = 0; i < m_outBuffers.Length; i++ ) { Graphics.Blit( null, m_outBuffers[ i ], gbufferToOutputMat, i ); } invViewProjBuffer.Release(); m_trueDepth.Release(); m_trueDepth = null; } m_cameraInvViewProjPerFrame = null; // TGA for ( int i = 0; i < outputList.Count; i++ ) { if( outputList[ i ].ImageFormat == ImageFormat.TGA ) PackingRemapping( ref m_outBuffers[ i ], ref m_outBuffers[ i ], 6, packerMat ); } if( m_data.PixelPadding > 0 ) DisplayProgress( 0.55f, "Please Wait... Dilating" ); Shader dilateShader = AssetDatabase.LoadAssetAtPath( AssetDatabase.GUIDToAssetPath( DilateGUID ) ); Material dilateMat = new Material( dilateShader ); // Dilation for( int i = 0; i < outputList.Count; i++ ) { if( outputList[ i ].Active ) DilateRenderTextureUsingMask( ref m_outBuffers[ i ], ref m_outBuffers[ alphaIndex ], m_data.PixelPadding, alphaIndex != i, dilateMat ); } DestroyImmediate( dilateMat ); dilateMat = null; DisplayProgress( 0.575f, "Please Wait... Resizing" ); // Resize Final Textures for( int i = 0; i < outputList.Count; i++ ) { if( outputList[ i ].Scale != TextureScale.Full ) { RenderTexture resTex = RenderTexture.GetTemporary( m_outBuffers[ i ].width / (int)outputList[ i ].Scale, m_outBuffers[ i ].height / (int)outputList[ i ].Scale, m_outBuffers[ i ].depth, m_outBuffers[ i ].graphicsFormat ); Graphics.Blit( m_outBuffers[ i ], resTex ); m_outBuffers[ i ].Release(); m_outBuffers[ i ] = new RenderTexture( resTex.width, resTex.height, m_outBuffers[ i ].depth, m_outBuffers[ i ].graphicsFormat ); m_outBuffers[ i ].Create(); Graphics.Blit( resTex, m_outBuffers[ i ] ); RenderTexture.ReleaseTemporary( resTex ); } } DestroyImmediate( packerMat ); packerMat = null; DestroyImmediate( gbufferToOutputMat ); gbufferToOutputMat = null; DisplayProgress( 0.6f, "Please Wait... Creating Asset and Textures" ); bool isPrefab = false; if( PrefabUtility.GetPrefabAssetType( this.gameObject ) == PrefabAssetType.Regular && PrefabUtility.GetPrefabInstanceHandle( this.gameObject ) == null ) isPrefab = true; // Create billboard Shader defaultShader = null; if( m_data.Preset.RuntimeShader != null ) { defaultShader = m_data.Preset.RuntimeShader; } else { defaultShader = AssetDatabase.LoadAssetAtPath( AssetDatabase.GUIDToAssetPath( guid ) ); } Material material = m_data.Material; if( material == null ) { material = new Material( defaultShader ); material.name = fileName; material.enableInstancing = true; AssetDatabase.AddObjectToAsset( material, m_data ); m_data.Material = material; EditorUtility.SetDirty( material ); } else { material.shader = defaultShader; material.name = fileName; EditorUtility.SetDirty( material ); } Texture2D tex = null; bool hasDifferentResolution = false; // Construct file names m_standardFileNamesOld[ 0 ] = "_AlbedoAlpha"; m_standardFileNamesOld[ 1 ] = "_NormalDepth"; m_standardFileNamesOld[ 2 ] = "_SpecularSmoothness"; m_standardFileNamesOld[ 3 ] = "_EmissionOcclusion"; m_standardFileNames[ 0 ] = Preferences.GlobalAlbedo; m_standardFileNames[ 1 ] = Preferences.GlobalNormals; m_standardFileNames[ 2 ] = Preferences.GlobalSpecular; m_standardFileNames[ 3 ] = Preferences.GlobalOcclusion; m_standardFileNames[ 4 ] = Preferences.GlobalEmission; for( int i = 0; i < outputList.Count; i++ ) { tex = null; m_fileNames[ i ] = string.Empty; if( material.HasProperty( outputList[ i ].Name ) ) tex = material.GetTexture( outputList[ i ].Name ) as Texture2D; if( tex != null ) { m_fileNames[ i ] = AssetDatabase.GetAssetPath( tex ); //m_fileNames[ i ] = Path.GetDirectoryName( AssetDatabase.GetAssetPath( tex ) ).Replace( "\\", "/" ) + "/"; if( tex.width != (int)m_data.TexSize.x / (int)outputList[ i ].Scale ) hasDifferentResolution = true; } else { m_fileNames[ i ] = folderPath; m_fileNames[ i ] += fileName + outputList[ i ].Name + "." + outputList[ i ].ImageFormat.ToString().ToLower(); } } for( int i = 0; i < m_propertyNames.Length; i++ ) { tex = null; if( material.HasProperty( m_propertyNames[ i ] ) ) { tex = material.GetTexture( m_propertyNames[ i ] ) as Texture2D; if( tex != null ) { int indexFound = outputList.FindIndex( x => x.Name == m_standardFileNames[ i ] ); if( indexFound > -1 ) { m_fileNames[ indexFound ] = AssetDatabase.GetAssetPath( tex ); //m_fileNames[ indexFound ] = Path.GetDirectoryName( AssetDatabase.GetAssetPath( tex ) ).Replace( "\\", "/" ) + "/"; //m_fileNames[ indexFound ] += fileName + outputList[ indexFound ].Name + "." + outputList[ indexFound ].ImageFormat.ToString().ToLower(); if( tex.width != (int)m_data.TexSize.x / (int)outputList[ indexFound ].Scale ) hasDifferentResolution = true; } } } } int activeCount = 0; int missingCount = 0; for ( int i = 0; i < outputList.Count; i++ ) { activeCount += outputList[ i ].Active ? 1 : 0; missingCount += string.IsNullOrEmpty( AssetDatabase.AssetPathToGUID( m_fileNames[ i ], AssetPathToGUIDOptions.OnlyExistingAssets ) ) ? 1 : 0; } bool resizeTextures = false; if ( missingCount == activeCount ) { // @diogo: they're all missing; always resize in this case. resizeTextures = true; } else { if ( hasDifferentResolution && EditorPrefs.GetInt( Preferences.PrefGlobalTexImport, 0 ) == 0 ) resizeTextures = EditorUtility.DisplayDialog( "Resize Textures?", "Do you wish to override the Texture Import settings to match the provided Impostor Texture Size?", "Yes", "No" ); else if ( EditorPrefs.GetInt( Preferences.PrefGlobalTexImport, 0 ) == 1 ) resizeTextures = true; else resizeTextures = false; } // save to texture files var textureImportData = new Dictionary(); if( !Application.isPlaying ) { for( int i = 0; i < outputList.Count; i++ ) { if( outputList[ i ].Active ) { SaveTexture( ref m_outBuffers[ i ], m_fileNames[ i ], outputList[ i ].ImageFormat, (int)outputList[ i ].Scale, outputList[ i ].Channels ); textureImportData.Add( m_fileNames[ i ], new TextureData( outputList[ i ].SRGB, outputList[ i ].Compression, outputList[ i ].Channels == TextureChannels.RGBA, resizeTextures ? (int)m_data.TexSize.x / (int)outputList[ i ].Scale : -1 ) ); } } } GL.sRGBWrite = chache; GameObject impostorObject = null; DisplayProgress( 0.65f, "Please Wait... Generating Mesh and Material" ); //RenderCombinedAlpha(); Vector4 offsetCalc = /*transform.worldToLocalMatrix **/ new Vector4( m_originalBound.center.x, m_originalBound.center.y, m_originalBound.center.z, 1 ); Vector4 offset = new Vector4( offsetCalc.x, offsetCalc.y, offsetCalc.z, -m_pixelOffset.y / m_xyFitSize/*(-pixelOffset.y / m_data.VerticalFrames) * ( m_trueFitsize / m_data.VerticalFrames )*/ ); Vector4 sizeOffset = new Vector4( m_xyFitSize, m_depthFitSize, (m_pixelOffset.x / m_xyFitSize) / (float)m_data.HorizontalFrames, (m_pixelOffset.y / m_xyFitSize) / (float)m_data.VerticalFrames ); //offset.y += pixelOffset.y; bool justCreated = false; UnityEngine.Object targetPrefab = null; GameObject tempGO = null; Mesh mesh = m_data.Mesh; if( mesh == null ) { mesh = GenerateMesh( m_data.ShapePoints, offset, m_xyFitSize, m_xyFitSize, true ); mesh.name = fileName; AssetDatabase.AddObjectToAsset( mesh, m_data ); m_data.Mesh = mesh; EditorUtility.SetDirty( mesh ); } else { Mesh tempmesh = GenerateMesh( m_data.ShapePoints, offset, m_xyFitSize, m_xyFitSize, true ); EditorUtility.CopySerialized( tempmesh, mesh ); mesh.vertices = tempmesh.vertices; mesh.triangles = tempmesh.triangles; mesh.uv = tempmesh.uv; mesh.normals = tempmesh.normals; mesh.bounds = tempmesh.bounds; mesh.name = fileName; EditorUtility.SetDirty( mesh ); } if( isPrefab ) { if( m_lastImpostor != null && PrefabUtility.GetPrefabAssetType( m_lastImpostor ) == PrefabAssetType.Regular ) { impostorObject = m_lastImpostor; } else { GameObject mainGO = new GameObject( "Impostor", new Type[] { typeof( MeshFilter ), typeof( MeshRenderer ) } ); impostorObject = mainGO; justCreated = true; } } else { if( m_lastImpostor != null ) { impostorObject = m_lastImpostor; //impostorObject.transform.position = m_rootTransform.position; //impostorObject.transform.rotation = m_rootTransform.rotation; } else { impostorObject = new GameObject( "Impostor", new Type[] { typeof( MeshFilter ), typeof( MeshRenderer ) } ); Undo.RegisterCreatedObjectUndo( impostorObject, "Create Impostor" ); impostorObject.transform.position = m_rootTransform.position; impostorObject.transform.rotation = m_rootTransform.rotation; justCreated = true; } } m_lastImpostor = impostorObject; impostorObject.transform.localScale = Vector3.one; impostorObject.GetComponent().sharedMesh = mesh; if( justCreated ) { if( LodGroup != null ) { if( isPrefab ) { targetPrefab = PrefabUtility.GetPrefabInstanceHandle( ( Selection.activeObject as GameObject ).transform.root.gameObject ); GameObject targetGO = AssetDatabase.LoadAssetAtPath( folderPath + ( Selection.activeObject as GameObject ).transform.root.gameObject.name + ".prefab", typeof( GameObject ) ) as GameObject; UnityEngine.Object inst = PrefabUtility.InstantiatePrefab( targetGO ); tempGO = inst as GameObject; AmplifyImpostor ai = tempGO.GetComponentInChildren(); impostorObject.transform.SetParent( ai.LodGroup.transform ); ai.m_lastImpostor = impostorObject; PrefabUtility.SaveAsPrefabAssetAndConnect( tempGO, AssetDatabase.GetAssetPath( targetPrefab ), InteractionMode.AutomatedAction ); ai = targetGO.GetComponentInChildren(); impostorObject = ai.m_lastImpostor; DestroyImmediate( tempGO ); } else { impostorObject.transform.SetParent( LodGroup.transform, true ); impostorObject.transform.localScale = Vector3.one; } switch( m_lodReplacement ) { default: case LODReplacement.DoNothing: break; case LODReplacement.ReplaceCulled: { LOD[] lods = LodGroup.GetLODs(); Array.Resize( ref lods, lods.Length + 1 ); LOD lastLOD = new LOD(); lastLOD.screenRelativeTransitionHeight = 0; lastLOD.renderers = impostorObject.GetComponents(); lods[ lods.Length - 1 ] = lastLOD; LodGroup.SetLODs( lods ); } break; case LODReplacement.ReplaceLast: { LOD[] lods = LodGroup.GetLODs(); foreach( Renderer item in lods[ lods.Length - 1 ].renderers ) if( item ) item.enabled = false; lods[ lods.Length - 1 ].renderers = impostorObject.GetComponents(); LodGroup.SetLODs( lods ); } break; case LODReplacement.ReplaceAllExceptFirst: { LOD[] lods = LodGroup.GetLODs(); for( int i = lods.Length - 1; i > 0; i-- ) { foreach( Renderer item in lods[ i ].renderers ) if( item ) item.enabled = false; } float lastTransition = lods[ lods.Length - 1 ].screenRelativeTransitionHeight; Array.Resize( ref lods, 2 ); lods[ lods.Length - 1 ].screenRelativeTransitionHeight = lastTransition; lods[ lods.Length - 1 ].renderers = impostorObject.GetComponents(); LodGroup.SetLODs( lods ); } break; case LODReplacement.ReplaceSpecific: { LOD[] lods = LodGroup.GetLODs(); foreach( Renderer item in lods[ m_insertIndex ].renderers ) if( item ) item.enabled = false; lods[ m_insertIndex ].renderers = impostorObject.GetComponents(); LodGroup.SetLODs( lods ); } break; case LODReplacement.ReplaceAfterSpecific: { LOD[] lods = LodGroup.GetLODs(); for( int i = lods.Length - 1; i > m_insertIndex; i-- ) { foreach( Renderer item in lods[ i ].renderers ) if( item ) item.enabled = false; } float lastTransition = lods[ lods.Length - 1 ].screenRelativeTransitionHeight; if( m_insertIndex == lods.Length - 1 ) lastTransition = 0; Array.Resize( ref lods, 2 + m_insertIndex ); lods[ lods.Length - 1 ].screenRelativeTransitionHeight = lastTransition; lods[ lods.Length - 1 ].renderers = impostorObject.GetComponents(); LodGroup.SetLODs( lods ); } break; case LODReplacement.InsertAfter: { LOD[] lods = LodGroup.GetLODs(); Array.Resize( ref lods, lods.Length + 1 ); for( int i = lods.Length - 1; i > m_insertIndex; i-- ) { lods[ i ].screenRelativeTransitionHeight = lods[ i - 1 ].screenRelativeTransitionHeight; lods[ i ].fadeTransitionWidth = lods[ i - 1 ].fadeTransitionWidth; lods[ i ].renderers = lods[ i - 1 ].renderers; } float firstTransition = 1; if( m_insertIndex > 0 ) firstTransition = lods[ m_insertIndex - 1 ].screenRelativeTransitionHeight; lods[ m_insertIndex + 1 ].renderers = impostorObject.GetComponents(); lods[ m_insertIndex ].screenRelativeTransitionHeight = ( lods[ m_insertIndex + 1 ].screenRelativeTransitionHeight + firstTransition ) * 0.5f; LodGroup.SetLODs( lods ); } break; } Undo.RegisterCompleteObjectUndo( LodGroup, "Create Impostor" ); } else if( !isPrefab ) { impostorObject.transform.SetParent( m_rootTransform.parent ); int sibIndex = m_rootTransform.GetSiblingIndex(); impostorObject.transform.SetSiblingIndex( sibIndex + 1 ); m_rootTransform.SetSiblingIndex( sibIndex ); impostorObject.transform.localScale = Vector3.one; } } if( LodGroup == null ) { Transform par = impostorObject.transform.parent; int sibIndex = impostorObject.transform.GetSiblingIndex(); if ( !isPrefab ) { impostorObject.transform.SetParent( m_rootTransform, true ); } impostorObject.transform.localScale = Vector3.one; impostorObject.transform.SetParent( par, true ); impostorObject.transform.SetSiblingIndex( sibIndex ); } EditorUtility.SetDirty( m_data ); if( m_lastImpostor == null ) impostorObject.name = fileName; impostorObject.GetComponent().sharedMaterial = material; EditorUtility.SetDirty( impostorObject ); DisplayProgress( 0.7f, "Please Wait... Saving and Importing" ); // saving and refreshing to make sure textures can be set properly into the material AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); PostProcessTextures( textureImportData ); DisplayProgress( 0.8f, "Please Wait... Changing Texture Import Settings" ); hasDifferentResolution = false; tex = null; if( standardRendering ) { for( int i = 0; i < outputList.Count; i++ ) { tex = null; if( outputList[ i ].Active ) { if( material.HasProperty( m_propertyNames[ i ] ) ) tex = material.GetTexture( m_propertyNames[ i ] ) as Texture2D; if( tex == null ) tex = AssetDatabase.LoadAssetAtPath( m_fileNames[ i ] ); if( tex != null ) material.SetTexture( m_propertyNames[ i ], tex ); if( tex != null && tex.width != m_data.TexSize.x / (int)outputList[ i ].Scale ) hasDifferentResolution = true; } } } else { for( int i = 0; i < outputList.Count; i++ ) { tex = null; if( outputList[ i ].Active ) { if( material.HasProperty( outputList[ i ].Name ) ) tex = material.GetTexture( outputList[ i ].Name ) as Texture2D; if( tex == null ) tex = AssetDatabase.LoadAssetAtPath( m_fileNames[ i ] ); if( tex != null ) material.SetTexture( outputList[ i ].Name, tex ); if( tex != null && tex.width != m_data.TexSize.x / (int)outputList[ i ].Scale ) hasDifferentResolution = true; } } for( int i = 0; i < m_propertyNames.Length; i++ ) { tex = null; if( material.HasProperty( m_propertyNames[ i ] ) ) { tex = material.GetTexture( m_propertyNames[ i ] ) as Texture2D; if ( tex == null ) { string filen = folderPath + fileName + m_standardFileNames[ i ] + ".tga"; tex = AssetDatabase.LoadAssetAtPath( filen ); } if ( tex == null ) { string filen = folderPath + fileName + m_standardFileNames[ i ] + ".png"; tex = AssetDatabase.LoadAssetAtPath( filen ); } if ( tex == null ) { string filen = folderPath + fileName + m_standardFileNames[ i ] + ".exr"; tex = AssetDatabase.LoadAssetAtPath( filen ); } if ( tex == null ) { string filen = folderPath + fileName + m_standardFileNamesOld[ i ] + ".tga"; tex = AssetDatabase.LoadAssetAtPath( filen ); } if ( tex == null ) { string filen = folderPath + fileName + m_standardFileNamesOld[ i ] + ".png"; tex = AssetDatabase.LoadAssetAtPath( filen ); } if ( tex == null ) { string filen = folderPath + fileName + m_standardFileNamesOld[ i ] + ".exr"; tex = AssetDatabase.LoadAssetAtPath( filen ); } if ( tex != null ) material.SetTexture( m_propertyNames[ i ], tex ); if ( tex != null ) { int indexFound = outputList.FindIndex( x => x.Name == m_standardFileNames[ i ] ); if ( indexFound > -1 && tex.width != ( ( int )m_data.TexSize.x / ( int )outputList[ indexFound ].Scale ) ) hasDifferentResolution = true; } } } } if( m_data.ImpostorType == ImpostorType.HemiOctahedron ) { material.SetFloat( "_Hemi", 1 ); material.SetFloat( "_AI_Hemi", 1 ); material.EnableKeyword( "_HEMI_ON" ); } else { material.SetFloat( "_Hemi", 0 ); material.SetFloat( "_AI_Hemi", 0 ); material.DisableKeyword( "_HEMI_ON" ); } material.SetFloat( "_Frames", m_data.HorizontalFrames ); material.SetFloat( "_ImpostorSize", m_xyFitSize ); material.SetVector( "_Offset", offset ); material.SetFloat( "_DepthSize", m_depthFitSize ); material.SetFloat( "_FramesX", m_data.HorizontalFrames ); material.SetFloat( "_FramesY", m_data.VerticalFrames ); material.SetVector( "_BoundsMin", m_originalBound.min ); material.SetVector( "_BoundsSize", m_originalBound.size ); material.SetFloat( "_AI_Frames", m_data.HorizontalFrames ); material.SetFloat( "_AI_ImpostorSize", m_xyFitSize ); material.SetVector( "_AI_Offset", offset ); material.SetVector( "_AI_SizeOffset", sizeOffset ); material.SetFloat( "_AI_DepthSize", m_depthFitSize ); material.SetFloat( "_AI_FramesX", m_data.HorizontalFrames ); material.SetFloat( "_AI_FramesY", m_data.VerticalFrames ); material.SetVector( "_AI_BoundsMin", m_originalBound.min ); material.SetVector( "_AI_BoundsSize", m_originalBound.size ); UpdateKeywords( material, m_data.Preset ); CheckHDRPMaterial(); //if( standardRendering && m_renderPipelineInUse == RenderPipelineInUse.HDRP ) // material.SetShaderPassEnabled( "MotionVectors", true ); EditorUtility.SetDirty( material ); if( hasDifferentResolution && resizeTextures ) resizeTextures = true; else resizeTextures = false; DisplayProgress( 1f, "Complete!" ); for( int i = 0; i < outputList.Count; i++ ) { if( outputList[ i ].Active ) ChangeTextureImporter( ref m_outBuffers[ i ], m_fileNames[ i ], outputList[i].SRGB, resizeTextures, outputList[ i ].Compression, outputList[ i ].Channels == TextureChannels.RGBA ); } ClearBuffers(); Data.Version = VersionInfo.FullNumber; } #endif private Cubemap CreateBlackCubemap() { var blackCubemap = new Cubemap( 1, TextureFormat.RGBA32, false ); blackCubemap.name = "BlackCube"; blackCubemap.SetPixel( CubemapFace.PositiveX, 1, 1, Color.black ); blackCubemap.SetPixel( CubemapFace.NegativeX, 1, 1, Color.black ); blackCubemap.SetPixel( CubemapFace.PositiveY, 1, 1, Color.black ); blackCubemap.SetPixel( CubemapFace.NegativeY, 1, 1, Color.black ); blackCubemap.SetPixel( CubemapFace.PositiveZ, 1, 1, Color.black ); blackCubemap.SetPixel( CubemapFace.NegativeZ, 1, 1, Color.black ); blackCubemap.Apply( false, true ); return blackCubemap; } private void CopyConstantStructToArray( object constants, Vector4[] array, int stride ) { // @diogo: I know this looks dirty but couldn't find another way to do it as HighDefinition.ShaderVariablesGlobal // is internal. Should be safe, however. This avoids having to duplicate the struct on our side and all // the different versions/sizes for different versions of HDRP, while saving us any kind of maintenance. GCHandle handle = GCHandle.Alloc( constants, GCHandleType.Pinned ); try { IntPtr src = handle.AddrOfPinnedObject(); unsafe { fixed ( Vector4* dst = array ) { Buffer.MemoryCopy( ( void* )src, dst, stride, stride ); } } } finally { // Always free the handle handle.Free(); } } /// /// Renders Impostors maps to render textures /// /// /// set to true to render all selected maps /// set to true to render the combined alpha map which is used to generate the mesh private void RenderImpostor( int targetAmount, RenderImpostorMode mode, bool useMinResolution = false, Shader customShader = null ) { if( targetAmount <= 0 ) return; bool standardRendering = customShader == null; Dictionary bakeMats = new Dictionary(); CommandBuffer commandBuffer = new CommandBuffer(); if ( mode == RenderImpostorMode.Normal ) { commandBuffer.name = "GBufferCatcher"; RenderTargetIdentifier[] rtIDs = new RenderTargetIdentifier[ targetAmount ]; for( int i = 0; i < targetAmount; i++ ) { rtIDs[ i ] = m_rtGBuffers[ i ]; } commandBuffer.SetRenderTarget( rtIDs, m_trueDepth ); commandBuffer.ClearRenderTarget( true, true, Color.clear, 1 ); } if ( mode == RenderImpostorMode.Alpha ) { commandBuffer.name = "DepthAlphaCatcher"; RenderTargetIdentifier[] rtIDsAlpha = new RenderTargetIdentifier[ targetAmount ]; for( int i = 0; i < targetAmount; i++ ) { rtIDsAlpha[ i ] = m_alphaGBuffers[ i ]; } commandBuffer.SetRenderTarget( rtIDsAlpha, m_trueDepth ); commandBuffer.ClearRenderTarget( true, true, Color.clear, 1 ); } GetFrameInfo( m_data, out int hframes, out int vframes ); List validMeshes = new List(); for( int i = 0; i < Renderers.Length; i++ ) { // only allow for renderers that are enabled and not marked as shadow only if( Renderers[ i ] == null || !Renderers[ i ].enabled || Renderers[ i ].shadowCastingMode == ShadowCastingMode.ShadowsOnly ) { validMeshes.Add( null ); continue; } // skip non-meshes, for now MeshFilter mf = Renderers[ i ].GetComponent(); if( mf == null || mf.sharedMesh == null ) { validMeshes.Add( null ); continue; } validMeshes.Add( mf ); } int validMeshesCount = validMeshes.Count; Type constantsType = null; ComputeBuffer constantsBuffer = null; object constants = null; Vector4[] constantsArray = null; if ( m_renderPipelineInUse == RenderPipelineInUse.HDRP ) { constantsType = Type.GetType( GlobalShaderVariablesQualifiedNameHDRP ); if ( constantsType != null ) { constantsBuffer = new ComputeBuffer( 1, Marshal.SizeOf( constantsType ), ComputeBufferType.Constant ); constantsArray = new Vector4 [ constantsBuffer.stride / Marshal.SizeOf() ]; constants = Activator.CreateInstance( constantsType, nonPublic: true ); } } var blackCubemap = CreateBlackCubemap(); Bounds rendererBounds = new Bounds(); for ( int i = 0; i < validMeshesCount; i++ ) { if ( validMeshes[ i ] == null ) continue; if ( rendererBounds.size == Vector3.zero ) rendererBounds = validMeshes[ i ].sharedMesh.bounds.Transform( m_rootTransform.worldToLocalMatrix * Renderers[ i ].localToWorldMatrix ); else rendererBounds.Encapsulate( validMeshes[ i ].sharedMesh.bounds.Transform( m_rootTransform.worldToLocalMatrix * Renderers[ i ].localToWorldMatrix ) ); } m_originalBound = rendererBounds; for ( int x = 0; x < hframes; x++ ) { for( int y = 0; y < vframes; y++ ) { int frameIndex = y * hframes + x; float fitSize = m_xyFitSize * 0.5f; Matrix4x4 camMatrixRot = GetCameraRotationMatrix( m_data.ImpostorType, hframes, vframes, x, y ); Bounds frameBounds = rendererBounds.Transform( camMatrixRot ); Matrix4x4 matrixP = Matrix4x4.Ortho( -fitSize + m_pixelOffset.x, fitSize + m_pixelOffset.x, -fitSize + m_pixelOffset.y, fitSize + m_pixelOffset.y, 0, -m_depthFitSize ); Matrix4x4 matrixV = Matrix4x4.Inverse( camMatrixRot ) * Matrix4x4.LookAt( frameBounds.center - new Vector3( 0, 0, m_depthFitSize * 0.5f ), frameBounds.center, Vector3.up ); matrixV = Matrix4x4.Inverse( matrixV ) * m_rootTransform.worldToLocalMatrix; commandBuffer.SetViewProjectionMatrices( matrixV, matrixP ); matrixP = GL.GetGPUProjectionMatrix( matrixP, true ); Matrix4x4 matrixVP = matrixP * matrixV; Matrix4x4 matrixInvVP = Matrix4x4.Inverse( matrixVP ); if ( m_renderPipelineInUse == RenderPipelineInUse.HDRP ) { if ( constants != null ) { constantsType.GetField( "_ViewMatrix" ).SetValue( constants, matrixV ); constantsType.GetField( "_CameraViewMatrix" ).SetValue( constants, matrixV ); constantsType.GetField( "_InvViewMatrix" ).SetValue( constants, Matrix4x4.Inverse( matrixV ) ); constantsType.GetField( "_ProjMatrix" ).SetValue( constants, matrixP ); constantsType.GetField( "_InvProjMatrix" ).SetValue( constants, Matrix4x4.Inverse( matrixP ) ); constantsType.GetField( "_ViewProjMatrix" ).SetValue( constants, matrixVP ); constantsType.GetField( "_CameraViewProjMatrix" ).SetValue( constants, matrixVP ); constantsType.GetField( "_InvViewProjMatrix" ).SetValue( constants, matrixInvVP ); constantsType.GetField( "_ProbeExposureScale" ).SetValue( constants, 1 ); } CopyConstantStructToArray( constants, constantsArray, constantsBuffer.stride ); commandBuffer.SetBufferData( constantsBuffer, constantsArray ); commandBuffer.SetGlobalConstantBuffer( constantsBuffer, "ShaderVariablesGlobal", 0, constantsBuffer.stride ); } else if ( m_renderPipelineInUse == RenderPipelineInUse.URP ) { commandBuffer.SetGlobalTexture( "_GlossyEnvironmentCubeMap", blackCubemap ); } if ( mode == RenderImpostorMode.Normal && m_cameraInvViewProjPerFrame != null ) { m_cameraInvViewProjPerFrame[ y * hframes + x ] = Matrix4x4.Transpose( matrixInvVP ); } commandBuffer.SetGlobalVector( "unity_SHAr", Vector4.zero ); commandBuffer.SetGlobalVector( "unity_SHAg", Vector4.zero ); commandBuffer.SetGlobalVector( "unity_SHAb", Vector4.zero ); commandBuffer.SetGlobalVector( "unity_SHBr", Vector4.zero ); commandBuffer.SetGlobalVector( "unity_SHBg", Vector4.zero ); commandBuffer.SetGlobalVector( "unity_SHBb", Vector4.zero ); commandBuffer.SetGlobalVector( "unity_SHC", Vector4.zero ); commandBuffer.SetGlobalTexture( "unity_SpecCube0", blackCubemap ); commandBuffer.SetGlobalTexture( "unity_SpecCube1", blackCubemap ); commandBuffer.SetGlobalVector( "unity_SpecCube0_HDR", Vector4.zero ); commandBuffer.SetGlobalVector( "unity_SpecCube1_HDR", Vector4.zero ); commandBuffer.SetGlobalVector( "_AI_BoundsMin", m_originalBound.min ); commandBuffer.SetGlobalVector( "_AI_BoundsSize", m_originalBound.size ); if ( mode == RenderImpostorMode.Normal ) { float targetWidth = m_data.TexSize.x / m_data.HorizontalFrames; float targetHeight = m_data.TexSize.y / m_data.VerticalFrames; commandBuffer.SetViewport( new Rect( ( m_data.TexSize.x / hframes ) * x, ( m_data.TexSize.y / vframes ) * y, targetWidth, targetHeight ) ); } else if ( mode == RenderImpostorMode.Alpha ) { float targetWidth = MinAlphaResolution; float targetHeight = MinAlphaResolution; commandBuffer.SetViewport( new Rect( 0, 0, targetWidth, targetHeight ) ); } for ( int j = 0; j < validMeshesCount; j++ ) { if( validMeshes[ j ] == null ) continue; // Renderer shares array position with validMesh Material[] meshMaterials = Renderers[ j ].sharedMaterials; // Draw Mesh for( int k = 0; k < meshMaterials.Length; k++ ) { Material renderMaterial = null; Mesh mesh = validMeshes[ j ].sharedMesh; int pass = 0; int prePass = 0; if( standardRendering ) { renderMaterial = meshMaterials[ k ]; pass = renderMaterial.FindPass( "DEFERRED" ); if( pass == -1 ) pass = renderMaterial.FindPass( "Deferred" ); if( pass == -1 ) pass = renderMaterial.FindPass( "GBuffer" ); prePass = renderMaterial.FindPass( "DepthOnly" ); if( pass == -1 ) // last resort fallback { pass = 0; for( int sp = 0; sp < renderMaterial.passCount; sp++ ) { string lightmode = renderMaterial.GetTag( "LightMode", true ); if( lightmode.Equals( "Deferred" ) ) { pass = sp; break; } } } // Only useful for 2017.1 and 2017.2 commandBuffer.EnableShaderKeyword( "UNITY_HDR_ON" ); } else { prePass = -1; if( !bakeMats.TryGetValue( meshMaterials[ k ], out renderMaterial ) ) { renderMaterial = new Material( customShader ) { hideFlags = HideFlags.HideAndDontSave }; renderMaterial.CopyPropertiesFromMaterial( meshMaterials[ k ] ); // @diogo: workaround for a Unity bug; not assigning default detail normal map if ( m_renderPipelineInUse == RenderPipelineInUse.URP && meshMaterials[ k ].HasProperty( _DetailNormalMap_PID ) ) { if ( meshMaterials[ k ].GetTexture( _DetailNormalMap_PID ) == null ) { renderMaterial.SetTexture( _DetailNormalMap_PID, Texture2D.normalTexture ); } } bakeMats.Add( meshMaterials[ k ], renderMaterial ); } } // Setup Lightmap keywords and values bool isUsingBakedGI = Renderers[ j ].lightmapIndex > -1; bool isUsingRealtimeGI = Renderers[ j ].realtimeLightmapIndex > -1; if(( isUsingBakedGI || isUsingRealtimeGI) && !standardRendering ) { commandBuffer.EnableShaderKeyword( "LIGHTMAP_ON" ); if( isUsingBakedGI ) { commandBuffer.SetGlobalVector( "unity_LightmapST", Renderers[ j ].lightmapScaleOffset ); } if( isUsingRealtimeGI ) { commandBuffer.EnableShaderKeyword( "DYNAMICLIGHTMAP_ON" ); commandBuffer.SetGlobalVector( "unity_DynamicLightmapST", Renderers[ j ].realtimeLightmapScaleOffset ); } else { commandBuffer.DisableShaderKeyword( "DYNAMICLIGHTMAP_ON" ); } if( isUsingBakedGI && isUsingRealtimeGI ) { commandBuffer.EnableShaderKeyword( "DIRLIGHTMAP_COMBINED" ); } else { commandBuffer.DisableShaderKeyword( "DIRLIGHTMAP_COMBINED" ); } } else { commandBuffer.DisableShaderKeyword( "LIGHTMAP_ON" ); commandBuffer.DisableShaderKeyword( "DYNAMICLIGHTMAP_ON" ); commandBuffer.DisableShaderKeyword( "DIRLIGHTMAP_COMBINED" ); } commandBuffer.DisableShaderKeyword( "LIGHTPROBE_SH" ); commandBuffer.DisableShaderKeyword( "USING_STEREO_MATRICES" ); commandBuffer.DisableShaderKeyword( "SHADEROPTIONS_CAMERA_RELATIVE_RENDERING" ); commandBuffer.DisableShaderKeyword( "WRITE_DECAL_BUFFER" ); if ( prePass > -1 ) { commandBuffer.DrawRenderer( Renderers[ j ], renderMaterial, k, prePass ); } commandBuffer.DrawRenderer( Renderers[ j ], renderMaterial, k, pass ); } } } } Graphics.ExecuteCommandBuffer( commandBuffer ); if ( constantsBuffer != null ) { constantsBuffer.Release(); } validMeshes.Clear(); Cubemap.DestroyImmediate( blackCubemap ); foreach ( var pair in bakeMats ) { Material bakeMat = pair.Value; if( bakeMat != null ) { if( !Application.isPlaying ) DestroyImmediate( bakeMat ); bakeMat = null; } } bakeMats.Clear(); commandBuffer.Release(); commandBuffer = null; } //public void Update() //{ // RenderAllDeferredGroups( m_data ); //} private static Matrix4x4 GetCameraRotationMatrix( ImpostorType impostorType, int hframes, int vframes, int x, int y ) { Matrix4x4 camMatrixRot = Matrix4x4.identity; if( impostorType == ImpostorType.Spherical ) //SPHERICAL { float fractionY = 0; if( vframes > 0 ) fractionY = -( 180.0f / ( ( float )vframes - 1 ) ); Quaternion hRot = Quaternion.Euler( fractionY * y + StartYRotation, 0, 0 ); Quaternion vRot = Quaternion.Euler( 0, ( 360.0f / hframes ) * x + StartXRotation, 0 ); camMatrixRot = Matrix4x4.Rotate( hRot * vRot ); } else if( impostorType == ImpostorType.Octahedron ) //OCTAHEDRON { Vector3 forw = OctahedronToVector( ( (float)( x ) / ( (float)hframes - 1 ) ) * 2f - 1f, ( (float)( y ) / ( (float)vframes - 1 ) ) * 2f - 1f ); Quaternion octa = Quaternion.LookRotation( new Vector3( forw.x * -1, forw.z * -1, forw.y * -1 ), Vector3.up ); camMatrixRot = Matrix4x4.Rotate( octa ).inverse; } else if( impostorType == ImpostorType.HemiOctahedron ) //HEMIOCTAHEDRON { Vector3 forw = HemiOctahedronToVector( ( (float)( x ) / ( (float)hframes - 1 ) ) * 2f - 1f, ( (float)( y ) / ( (float)vframes - 1 ) ) * 2f - 1f ); Quaternion octa = Quaternion.LookRotation( new Vector3( forw.x * -1, forw.z * -1, forw.y * -1 ), Vector3.up ); camMatrixRot = Matrix4x4.Rotate( octa ).inverse; } return camMatrixRot; } private static Vector3 OctahedronToVector( Vector2 oct ) { Vector3 N = new Vector3( oct.x, oct.y, 1.0f - Mathf.Abs( oct.x ) - Mathf.Abs( oct.y ) ); float t = Mathf.Clamp01( -N.z ); N.Set( N.x + ( N.x >= 0.0f ? -t : t ), N.y + ( N.y >= 0.0f ? -t : t ), N.z ); N = Vector3.Normalize( N ); return N; } private static Vector3 OctahedronToVector( float x, float y ) { Vector3 N = new Vector3( x, y, 1.0f - Mathf.Abs( x ) - Mathf.Abs( y ) ); float t = Mathf.Clamp01( -N.z ); N.Set( N.x + ( N.x >= 0.0f ? -t : t ), N.y + ( N.y >= 0.0f ? -t : t ), N.z ); N = Vector3.Normalize( N ); return N; } private static Vector3 HemiOctahedronToVector( float x, float y ) { float tempx = x; float tempy = y; x = ( tempx + tempy ) * 0.5f; y = ( tempx - tempy ) * 0.5f; Vector3 N = new Vector3( x, y, 1.0f - Mathf.Abs( x ) - Mathf.Abs( y ) ); N = Vector3.Normalize( N ); return N; } public void GenerateAutomaticMesh( AmplifyImpostorAsset data ) { // create a 2d texture for calculations Rect testRect = new Rect( 0, 0, m_alphaTex.width, m_alphaTex.height ); Vector2[][] paths; SpriteUtilityEx.GenerateOutline( m_alphaTex, testRect, data.Tolerance, 254, false, out paths ); int sum = 0; for( int i = 0; i < paths.Length; i++ ) { sum += paths[ i ].Length; } data.ShapePoints = new Vector2[ sum ]; int index = 0; for( int i = 0; i < paths.Length; i++ ) { for( int j = 0; j < paths[ i ].Length; j++ ) { data.ShapePoints[ index ] = (Vector2)( paths[ i ][ j ] ) + ( new Vector2( m_alphaTex.width * 0.5f, m_alphaTex.height * 0.5f ) ); data.ShapePoints[ index ] = Vector2.Scale( data.ShapePoints[ index ], new Vector2( 1.0f / m_alphaTex.width, 1.0f / m_alphaTex.height ) ); index++; } } // make it convex hull data.ShapePoints = Vector2Ex.ConvexHull( data.ShapePoints ); // reduce vertices data.ShapePoints = Vector2Ex.ReduceVertices( data.ShapePoints, data.MaxVertices ); // Resize the mesh using calculated normals data.ShapePoints = Vector2Ex.ScaleAlongNormals( data.ShapePoints, data.NormalScale ); // clamp to box (needs a cut algorithm) for( int i = 0; i < data.ShapePoints.Length; i++ ) { data.ShapePoints[ i ].x = Mathf.Clamp01( data.ShapePoints[ i ].x ); data.ShapePoints[ i ].y = Mathf.Clamp01( data.ShapePoints[ i ].y ); } // make it convex hull gain to clean edges data.ShapePoints = Vector2Ex.ConvexHull( data.ShapePoints ); // invert Y for( int i = 0; i < data.ShapePoints.Length; i++ ) { data.ShapePoints[ i ] = new Vector2( data.ShapePoints[ i ].x, 1 - data.ShapePoints[ i ].y ); } } public Mesh GenerateMesh( Vector2[] points, Vector3 offset, float width = 1, float height = 1, bool invertY = true ) { Vector2[] newPoints = new Vector2[ points.Length ]; Vector2[] UVs = new Vector2[ points.Length ]; Array.Copy( points, newPoints, points.Length ); float halfWidth = width * 0.5f; float halfHeight = height * 0.5f; if( invertY ) { for( int i = 0; i < newPoints.Length; i++ ) { newPoints[ i ] = new Vector2( newPoints[ i ].x, 1 - newPoints[ i ].y ); } } Array.Copy( newPoints, UVs, newPoints.Length ); for( int i = 0; i < newPoints.Length; i++ ) { newPoints[ i ] = new Vector2( newPoints[ i ].x * width - halfWidth + m_pixelOffset.x, newPoints[ i ].y * height - halfHeight + m_pixelOffset.y ); } Triangulator tr = new Triangulator( newPoints ); int[] indices = tr.Triangulate(); Vector3[] vertices = new Vector3[ tr.Points.Count ]; for( int i = 0; i < vertices.Length; i++ ) { vertices[ i ] = new Vector3( tr.Points[ i ].x, tr.Points[ i ].y, 0 ); } //Vector4[] tangents = new Vector4[ tr.Points.Count ]; //for( int i = 0; i < vertices.Length; i++ ) //{ // tangents[ i ] = new Vector4( 1, 0, 0, 1 ); //} Mesh mesh = new Mesh(); mesh.vertices = vertices; mesh.uv = UVs; //mesh.tangents = tangents; mesh.triangles = indices; mesh.RecalculateNormals(); mesh.bounds = new Bounds( offset, m_originalBound.size ); return mesh; } } }