Unity 项目中查找仅具有单一颜色的纹理

news2024/9/21 12:31:04

如何使用

只需在“项目”窗口中创建一个名为“编辑器”的文件夹,然后在其中添加此脚本即可。然后,打开“窗口-单色纹理检测器”并点击“刷新”。

你可能会问,为什么我需要这个?某些纹理可以是 1024x1024 或更大,并且仅包含单一颜色(Asset Store 上的许多艺术资源将此类纹理用于其材质的金属/发射/镜面/等通道)。为什么你想要一个 1k 纹理?您可以简单地将其最大尺寸设置为 32,在显着优化纹理尺寸的同时,它也会产生相同的效果。该插件可用于快速找到此类纹理。

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;

public class SingleColorTextureDetector : EditorWindow, IHasCustomMenu
{
	[Serializable]
	private class SaveData
	{
		public List<string> paths = new List<string>();
	}

	private const string LOW_RES_DUMMY_TEXTURE_PATH = "Assets/low_dummyy_texturee.png";
	private const string HIGH_RES_DUMMY_TEXTURE_PATH = "Assets/high_dummyy_texturee.png";
	private const string SAVE_FILE_PATH = "Library/SingleColorAssetDetector.json";
	private const float BUTTON_DRAG_THRESHOLD_SQR = 600f;

	private readonly MethodInfo instanceIDFromGUID = typeof( AssetDatabase ).GetMethod( "GetInstanceIDFromGUID", BindingFlags.NonPublic | BindingFlags.Static );

	private List<string> results = new List<string>(); // This is not readonly so that it can be serialized

	private double lastClickTime;
	private string lastClickedPath;

	private readonly GUIContent buttonGUIContent = new GUIContent();
	private Vector2 buttonPressPosition;
	private Vector2 scrollPos;

	[MenuItem( "Window/Single Color Texture Detector" )]
	private static void Init()
	{
		SingleColorTextureDetector window = GetWindow<SingleColorTextureDetector>();
		window.titleContent = new GUIContent( "Solid Textures" );
		window.minSize = new Vector2( 200f, 150f );
		window.Show();
	}

	private void Awake()
	{
		LoadSession( null );
	}

	// Show additional options in the window's context menu
	public void AddItemsToMenu( GenericMenu menu )
	{
		if( results.Count > 0 )
			menu.AddItem( new GUIContent( "Save To Clipboard" ), false, () => GUIUtility.systemCopyBuffer = JsonUtility.ToJson( new SaveData() { paths = results }, true ) );
		else
			menu.AddDisabledItem( new GUIContent( "Save To Clipboard" ) );

		if( string.IsNullOrEmpty( GUIUtility.systemCopyBuffer ) )
			menu.AddDisabledItem( new GUIContent( "Load From Clipboard" ) );
		else
		{
			menu.AddItem( new GUIContent( "Load From Clipboard" ), false, () =>
			{
				string json = GUIUtility.systemCopyBuffer;
				LoadSession( json );
				SaveSession( json ); // If load succeeds, overwrite the saved session
			} );
		}
	}

	private void OnGUI()
	{
		Event ev = Event.current;
		scrollPos = GUILayout.BeginScrollView( scrollPos );

		// Calculate single color Textures
		if( GUILayout.Button( "Refresh" ) )
		{
			try
			{
				double startTime = EditorApplication.timeSinceStartup;

				CalculateSingleColorTextures();
				SaveSession( null );

				Debug.Log( "Refreshed in " + ( EditorApplication.timeSinceStartup - startTime ) + " seconds." );
			}
			catch( Exception e )
			{
				Debug.LogException( e );
			}
			finally
			{
				EditorUtility.ClearProgressBar();

				if( File.Exists( LOW_RES_DUMMY_TEXTURE_PATH ) )
					AssetDatabase.DeleteAsset( LOW_RES_DUMMY_TEXTURE_PATH );
				if( File.Exists( HIGH_RES_DUMMY_TEXTURE_PATH ) )
					AssetDatabase.DeleteAsset( HIGH_RES_DUMMY_TEXTURE_PATH );
			}

			GUIUtility.ExitGUI();
		}

		// Draw found results
		if( results.Count > 0 )
		{
			EditorGUILayout.HelpBox( "- Double click a path to select the Texture asset\n- Right/middle click a path to hide it from the list", MessageType.Info );

			for( int i = 0; i < results.Count; i++ )
			{
				Rect rect = EditorGUILayout.GetControlRect( false, EditorGUIUtility.singleLineHeight + 2f );
				rect.xMin += 3f;
				rect.xMax -= 3f;

				Rect iconRect = new Rect( rect.x, rect.y, rect.height, rect.height );
				rect.xMin += iconRect.width + 2f;

				Texture icon = AssetDatabase.GetCachedIcon( results[i] );
				if( icon )
					EditorGUI.DrawTextureTransparent( iconRect, icon );

				// Buttons must support 1) click and 2) drag & drop. The most reliable way is to simulate GUI.Button from scratch
				buttonGUIContent.text = results[i];
				int buttonControlID = GUIUtility.GetControlID( FocusType.Passive );
				switch( ev.GetTypeForControl( buttonControlID ) )
				{
					case EventType.MouseDown:
						if( rect.Contains( ev.mousePosition ) )
						{
							GUIUtility.hotControl = buttonControlID;
							buttonPressPosition = ev.mousePosition;
						}

						break;
					case EventType.MouseDrag:
						if( GUIUtility.hotControl == buttonControlID && ev.button == 0 && ( ev.mousePosition - buttonPressPosition ).sqrMagnitude >= BUTTON_DRAG_THRESHOLD_SQR )
						{
							GUIUtility.hotControl = 0;

							Object asset = AssetDatabase.LoadMainAssetAtPath( results[i] );
							if( asset )
							{
								// Credit: https://forum.unity.com/threads/editor-draganddrop-bug-system-needs-to-be-initialized-by-unity.219342/#post-1464056
								DragAndDrop.PrepareStartDrag();
								DragAndDrop.objectReferences = new Object[] { asset };
								DragAndDrop.StartDrag( "DuplicateAssetDetector" );
							}

							ev.Use();
						}

						break;
					case EventType.MouseUp:
						if( GUIUtility.hotControl == buttonControlID )
						{
							GUIUtility.hotControl = 0;

							if( rect.Contains( ev.mousePosition ) )
							{
								if( ev.button == 0 && File.Exists( results[i] ) )
								{
									// Ping clicked Texture
									double clickTime = EditorApplication.timeSinceStartup;
									if( clickTime - lastClickTime < 0.5f && lastClickedPath == results[i] )
									{
										if( !ev.control && !ev.command && !ev.shift )
											Selection.objects = new Object[] { AssetDatabase.LoadMainAssetAtPath( results[i] ) };
										else
										{
											// While holding CTRL, either add clicked asset to current selection or remove it from current selection
											Object asset = AssetDatabase.LoadMainAssetAtPath( results[i] );
											List<Object> selection = new List<Object>( Selection.objects );
											if( !selection.Remove( asset ) )
												selection.Add( asset );

											Selection.objects = selection.ToArray();
										}
									}
									else if( instanceIDFromGUID != null )
										EditorGUIUtility.PingObject( (int) instanceIDFromGUID.Invoke( null, new object[] { AssetDatabase.AssetPathToGUID( results[i] ) } ) );
									else
										EditorGUIUtility.PingObject( AssetDatabase.LoadMainAssetAtPath( results[i] ) );

									lastClickTime = clickTime;
									lastClickedPath = results[i];
								}
								else if( ev.button == 1 )
								{
									// Show an option to hide that Texture from the list
									int _i = i;
									GenericMenu menu = new GenericMenu();
									menu.AddItem( new GUIContent( "Hide" ), false, () => HideTexture( _i ) );
									menu.ShowAsContext();
								}
								else if( ev.button == 2 )
									HideTexture( i );
							}
						}
						break;
					case EventType.Repaint:
						EditorStyles.textField.Draw( rect, buttonGUIContent, buttonControlID );
						break;
				}

				if( ev.isMouse && GUIUtility.hotControl == buttonControlID )
					ev.Use();
			}

			GUILayout.Space( 1f );
		}

		GUILayout.EndScrollView();
	}

	private void CalculateSingleColorTextures()
	{
		// Dummy Texture is used to read Textures' pixels
		CreateDummyTexture( LOW_RES_DUMMY_TEXTURE_PATH, 32 );
		CreateDummyTexture( HIGH_RES_DUMMY_TEXTURE_PATH, 1024 );

		results.Clear();

		string[] textureGUIDs = AssetDatabase.FindAssets( "t:Texture" );
		if( textureGUIDs.Length == 0 )
			return;

		string pathsLengthStr = "/" + textureGUIDs.Length.ToString();
		float progressMultiplier = 1f / textureGUIDs.Length;

		for( int i = 0; i < textureGUIDs.Length; i++ )
		{
			if( i % 15 == 0 && EditorUtility.DisplayCancelableProgressBar( "Please wait...", string.Concat( "Searching: ", ( i + 1 ).ToString(), pathsLengthStr ), ( i + 1 ) * progressMultiplier ) )
				throw new Exception( "Search aborted" );

			if( string.IsNullOrEmpty( textureGUIDs[i] ) )
				continue;

			string path = AssetDatabase.GUIDToAssetPath( textureGUIDs[i] );
			if( string.IsNullOrEmpty( path ) || !path.StartsWith( "Assets/" ) || path == LOW_RES_DUMMY_TEXTURE_PATH || path == HIGH_RES_DUMMY_TEXTURE_PATH )
				continue;

			// Happens for Font Assets' Textures, for example
			if( !typeof( Texture ).IsAssignableFrom( AssetDatabase.GetMainAssetTypeAtPath( path ) ) )
				continue;

			// First downscale the Texture to 32 pixels for performance reasons, then downscale it to 1024 pixels to verify the result
			if( CheckTextureAtPath( path, LOW_RES_DUMMY_TEXTURE_PATH ) && CheckTextureAtPath( path, HIGH_RES_DUMMY_TEXTURE_PATH ) )
				results.Add( path );
		}
	}

	// Creates dummy Texture asset that will be used to read Textures' pixels
	private void CreateDummyTexture( string path, int maxSize )
	{
		if( !File.Exists( path ) )
		{
			File.WriteAllBytes( path, new Texture2D( 2, 2 ).EncodeToPNG() );
			AssetDatabase.ImportAsset( path, ImportAssetOptions.ForceUpdate );
		}

		TextureImporter textureImporter = AssetImporter.GetAtPath( path ) as TextureImporter;
		textureImporter.maxTextureSize = maxSize;
		textureImporter.isReadable = true;
		textureImporter.filterMode = FilterMode.Point;
		textureImporter.mipmapEnabled = false;
		textureImporter.alphaSource = TextureImporterAlphaSource.FromInput;
		textureImporter.alphaIsTransparency = true;
		textureImporter.textureCompression = TextureImporterCompression.Uncompressed;
		textureImporter.SaveAndReimport();
	}

	// Checks if downsized Texture's pixels are all same
	private bool CheckTextureAtPath( string texturePath, string dummyTexturePath )
	{
		File.Copy( texturePath, dummyTexturePath, true );
		AssetDatabase.ImportAsset( dummyTexturePath, ImportAssetOptions.ForceUpdate );

		Texture2D texture = AssetDatabase.LoadAssetAtPath<Texture2D>( dummyTexturePath );
		if( !texture ) // RenderTextures, for example, are also Textures but not Texture2Ds
			return false;

		Color32[] colors = texture.GetPixels32();
		Color32 color = colors[0];
		for( int i = 1; i < colors.Length; i++ )
		{
			Color32 color2 = colors[i];
			if( color2.r != color.r || color2.g != color.g || color2.b != color.b || color2.a != color.a )
				return false;
		}

		return true;
	}

	// Hides the Texture at the specified index from the results
	private void HideTexture( int textureIndex )
	{
		results.RemoveAt( textureIndex );

		SaveSession( null );
		Repaint();
	}

	// Saves current session to file
	private void SaveSession( string json )
	{
		if( string.IsNullOrEmpty( json ) )
			json = JsonUtility.ToJson( new SaveData() { paths = results }, false );

		File.WriteAllText( SAVE_FILE_PATH, json );
	}

	// Restores previous session
	private void LoadSession( string json )
	{
		if( string.IsNullOrEmpty( json ) )
		{
			if( !File.Exists( SAVE_FILE_PATH ) )
				return;

			json = File.ReadAllText( SAVE_FILE_PATH );
		}

		SaveData saveData = JsonUtility.FromJson<SaveData>( json );

		// Remove non-existent paths
		for( int i = saveData.paths.Count - 1; i >= 0; i-- )
		{
			if( !File.Exists( saveData.paths[i] ) )
				saveData.paths.RemoveAt( i );
		}

		results = saveData.paths;
		Repaint();
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1132550.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PHPstorm 安装汉化包失败解决方法

出错的原因是官方的包和软件的版本不对应&#xff0c;下载对应的汉化包就行了 官网汉化包下载地址https://plugins.jetbrains.com/plugin/13710-chinese-simplified-language-pack----/versions 下载对应ide版本的包&#xff0c;我下载了一个2020版的 放到安装软件的plugins文…

纺织辅料经营小程序商城的作用是什么

数字化时代&#xff0c;各个行业都在通过线上转型实现新增长&#xff0c;纺织辅料厂商也一样&#xff0c;需要通过线上化实现与客户的面对面交流有利于打造品牌及构建私有化流量池&#xff0c;但无论入驻哪家电商平台及垂直行业平台&#xff0c;都有不少的佣金及入驻费。 可通过…

h750错误记录 卡死 ,18b20数据读取失败 解决办法

1.程序运行没反应 &#xff0c;debug发现卡死到 b. **> 解决办法&#xff0c;中断函数缺失&#xff0c;添加即可 2.stm32h750对18b20 读取数据失败 由于hal库没有 微秒延时&#xff0c;故采用nop&#xff08; &#xff09;函数来进行延时&#xff0c;死活读不到温度数据&…

UG\NX二次开发 分享“一键清除高亮工具”的源代码

文章作者:里海 来源网站:王牌飞行员_里海_里海NX二次开发3000例,里海BlockUI专栏,C\C++-CSDN博客 感谢粉丝订阅 感谢 Trust_MJ 订阅本专栏,非常感谢。 简介 一键清除高亮工具,这个工具怎么开发呢?遍历对象使用UF_DISP_set_highlight()吗?下面分享一种最简单的方法: 效果…

跨境电商必备指南:教你轻松玩转Facebook聊单

Facebook作为全球最大的社交媒体平台之一&#xff0c;拥有数十亿的用户。这样庞大的客户群体为出海企业营销提供了巨大的潜力&#xff0c;无论是中小型的企业还是大型的跨国公司&#xff0c;Facebook都是一个有助于建立品牌形象的平台。企业可以在上面与用户互动、推广产品和服…

抄表系统是如何抄到电表水表的数据的?

抄表系统是一种利用无线通信技术&#xff0c;实现远程读取电表水表数据的系统。抄表系统主要由三部分组成&#xff1a;电表水表、集中器和后台管理平台。接下来&#xff0c;小编来为大家详细的介绍下抄表系统是如何抄到电表水表的数据的&#xff0c;一起来看下吧! 电表水表是抄…

餐饮外卖小程序商城的作用是什么

随着互联网及线上餐饮的发展趋势&#xff0c;行业洗牌正在加速&#xff0c;并且对餐饮连锁门店提出更高要求&#xff0c;餐饮数字化转型加快&#xff0c;积极发展线上经营是不少餐饮商家的首选。这其中&#xff0c;餐饮外卖商城成为很多餐饮品牌的线上经营品牌&#xff0c;也是…

k8s客户端配置

K8s客户端安装 前提 K8s服务部署成功&#xff0c;如下 角色 IP地址 操作系统 主机名 Kubernetes版本 master节点 172.16.4.167 CentOS 7.9 k8s-master01 v1.28.2 工作节点1 172.16.4.168 CentOS 7.9 k8s-worker01 v1.28.2 工作节点2 172.16.4.169 CentOS 7.9…

【蓝桥】通关

1、题目 问题描述 游戏“蓝桥争霸”由很多关卡和副本组成&#xff0c;每一关可以抽象为一个节点&#xff0c;整个游戏的关卡可以抽象为一棵树形图&#xff0c;每一关会有一道算法题&#xff0c;只有当经验值不低于第 i i i 关的要求 k i k_i ki​ 时&#xff0c;小蓝才能挑…

SAP-怎么查一个工艺路线抬头特性值用在了哪个工艺路线里

起因 在DEV里写了个报表&#xff0c;用生产订单关联工艺路线&#xff0c;关联特性值的时候发现特性值维护的和PRD里不一样&#xff0c;PRD里&#xff0c;这个特性值在“值”标签里定义了选项&#xff0c;而DEV里却没有&#xff0c;于是乎就想把它补过来。然而保存存的时候报错…

Echarts 实现 设备运行状态图(甘特图) 工业大数据展示

let option{tooltip: {formatter: function (params) {let startTime new Date(params.value[1])let endTime new Date(params.value[2]);//北京时间/时间戳转成日常时间function convert(date){var y date.getFullYear();var m date.getMonth() 1;m m < 10 ? "0…

基于边缘智能网关的储能系统安全监测管理方案

“储能系统充电”是配套新能源汽车产业发展的重要应用之一。得益于电池技术的发展&#xff0c;新能源汽车正逐步迈入快充时代&#xff0c;由于在使用快速充电桩时&#xff0c;可能导致用电峰值负荷超过电网的承载能力&#xff0c;对于电网的稳定性和持续性会有较大影响&#xf…

【智能大数据分析】实验1 MapReduce实验:单词计数

【智能大数据分析】实验1 MapReduce实验&#xff1a;单词计数 文章目录 【智能大数据分析】实验1 MapReduce实验&#xff1a;单词计数一、实验目的二、实验要求三、实验原理1 MapReduce编程2 Java API解析 四、实验步骤1 启动Hadoop2 验证HDFS上没有wordcount的文件夹3 上传数据…

云安全-对象存储安全(配置错误,域名接管,AK泄露)

0x00 云安全-对象存储 云安全厂商分类&#xff1a;阿里云&#xff0c;腾讯云&#xff0c;华为云&#xff0c;亚马逊云等 云厂商所对应的云服务包括&#xff1a;对象存储&#xff0c;云数据库&#xff0c;弹性计算服务&#xff08;云主机&#xff09;&#xff0c;云控制台 各厂…

桃花峪滑雪场租赁系统 JAVA开源项目 毕业设计

目录 项目介绍 项目下载 项目截图 项目介绍 基于JAVAVueSpringBootMySQL的桃花峪滑雪场租赁系统&#xff0c;包含了滑雪场、门票预定、滑雪教练聘请、器材租赁归还、规章制度等模块&#xff0c;分为管理后台和微信小程序端&#xff0c;还包含系统自带的用户管理、部门管理、…

加上boot程序,FreeRTOS就跑不起来了

一、问题描述 bootloader跳转到APP时&#xff0c;app执行完初始化程序后死机 二、分析问题 第一步&#xff0c;执行app时死机死到哪里&#xff1f;通过DEBUG调试发现死到hardfault_handler()函数中&#xff0c;硬件错误&#xff0c;导致硬件错误的原因一般都是中断异常引起的。…

上云容灾如何实现碳中和-万博智云受邀参加1024程序员节数据技术论坛并发表演讲

近日&#xff0c;2023长沙中国1024程序员节在长沙召开。 长沙中国1024程序员节继2020年后已成功连续举办三届&#xff0c;逐步成为 IT 行业引领技术前沿、推动应用创新发展的高影响力年度盛会。是 IT 领域新技术、新产品、新服务的重要发布平台。 万博智云CEO Michael受邀参加…

信息检索与数据挖掘 | 【实验】排名检索模型

文章目录 &#x1f4da;实验内容&#x1f4da;相关概念&#x1f4da;实验步骤&#x1f407;分词预处理&#x1f407;构建倒排索引表&#x1f407;计算query和各个文档的相似度&#x1f407;queries预处理及检索函数&#x1f525;对输入的文本进行词法分析和标准化处理&#x1f…

微信小程序设置 wx.showModal 提示框中 确定和取消按钮的颜色

wx官方提供的 showModal 无疑是个非常优秀的选择提示工具 但是 我们还可以让他的颜色更贴近整体的小程序风格 cancelColor 可以改变取消按钮的颜色 confirmColor 则可以控制确定按钮的颜色 参考代码如下 wx.showModal({cancelColor: #0000FF,confirmColor: #45B250,content:…