1. 多轴向视图支持 Front/Back: 前/后视图(±Z轴) Left/Right: 左/右视图(±X轴) Top/Bottom: 顶/底视图(±Y轴) Isometric: 等轴测视图(-X, -Y, -Z方向) IsometricOpposite: 相反方向的等轴测视图 2. 相机控制 Auto Center: 自动根据轴向定位相机 Custom Rotation: 手动设置旋转角度 3. 预览功能 在场景中创建临时实例 自动定位场景相机 方便调整角度后导出 4. 自动灯光 如果没有光源,自动创建基本的三点光源 确保物体被正确照亮 使用说明: 选择轴向:从下拉菜单中选择需要的视图方向 调整设置:分辨率、背景色、边距等 预览:点击"Preview in Scene"查看效果 导出:选择单个或多个Prefab导出 轴向后缀: 导出的文件名会自动添加轴向信息,如:
EmperorWalkingOverThere_Front_thumbnail.png EmperorWalkingOverThere_Top_thumbnail.png EmperorWalkingOverThere_Isometric_thumbnail.png 脚本代码 using UnityEditor; using UnityEngine; using System.IO; public class ThumbnailExportSettings : EditorWindow { public int width = 256; public int height = 256; public bool transparentBackground = true; public Color backgroundColor = Color.clear; public int padding = 10; // 轴向选择 public enum ViewAxis { Front, Back, Left, Right, Top, Bottom, Isometric, IsometricOpposite } public ViewAxis selectedAxis = ViewAxis.Front; public bool autoCenter = true; public Vector3 customRotation = Vector3.zero; [MenuItem("Tools/Prefab Thumbnail Exporter")] static void ShowWindow() { GetWindow<ThumbnailExportSettings>("Thumbnail Exporter"); } void OnGUI() { GUILayout.Label("Thumbnail Settings", EditorStyles.boldLabel); width = EditorGUILayout.IntField("Width", Mathf.Max(1, width)); height = EditorGUILayout.IntField("Height", Mathf.Max(1, height)); transparentBackground = EditorGUILayout.Toggle("Transparent Background", transparentBackground); if (!transparentBackground) { backgroundColor = EditorGUILayout.ColorField("Background Color", backgroundColor); } padding = EditorGUILayout.IntField("Padding", Mathf.Max(0, padding)); GUILayout.Space(10); GUILayout.Label("Camera Settings", EditorStyles.boldLabel); selectedAxis = (ViewAxis)EditorGUILayout.EnumPopup("View Axis", selectedAxis); if (selectedAxis == ViewAxis.Isometric || selectedAxis == ViewAxis.IsometricOpposite) { EditorGUILayout.HelpBox("Isometric view combines multiple axes", MessageType.Info); } autoCenter = EditorGUILayout.Toggle("Auto Center Camera", autoCenter); if (!autoCenter) { customRotation = EditorGUILayout.Vector3Field("Custom Rotation", customRotation); } GUILayout.Space(10); if (GUILayout.Button("Export Selected Prefab", GUILayout.Height(30))) { ExportSelectedPrefab(); } if (GUILayout.Button("Export All Selected Prefabs", GUILayout.Height(30))) { ExportAllSelectedPrefabs(); } if (GUILayout.Button("Preview in Scene", GUILayout.Height(25))) { PreviewSelectedPrefab(); } GUILayout.Space(10); GUILayout.Label("Tips:", EditorStyles.boldLabel); GUILayout.Label("• Front: +Z direction", EditorStyles.miniLabel); GUILayout.Label("• Back: -Z direction", EditorStyles.miniLabel); GUILayout.Label("• Left: -X direction", EditorStyles.miniLabel); GUILayout.Label("• Right: +X direction", EditorStyles.miniLabel); GUILayout.Label("• Top: +Y direction", EditorStyles.miniLabel); GUILayout.Label("• Bottom: -Y direction", EditorStyles.miniLabel); } void ExportSelectedPrefab() { GameObject selectedPrefab = Selection.activeObject as GameObject; if (selectedPrefab == null) { Debug.LogError("Please select a Prefab in the Project window!"); return; } string savePath = EditorUtility.SaveFilePanel( "Save Thumbnail", "", $"{selectedPrefab.name}_{selectedAxis}_thumbnail", "png"); if (!string.IsNullOrEmpty(savePath)) { Texture2D thumbnail = CapturePrefabThumbnail(selectedPrefab, width, height); if (thumbnail != null) { byte[] bytes = thumbnail.EncodeToPNG(); File.WriteAllBytes(savePath, bytes); DestroyImmediate(thumbnail); Debug.Log($"Thumbnail saved to: {savePath}"); } } } void ExportAllSelectedPrefabs() { string outputFolder = EditorUtility.SaveFolderPanel("Select Output Folder", "", ""); if (string.IsNullOrEmpty(outputFolder)) return; int processed = 0; int total = Selection.objects.Length; for (int i = 0; i < total; i++) { var obj = Selection.objects[i]; if (obj is GameObject) { EditorUtility.DisplayProgressBar("Exporting Thumbnails", $"Processing: {obj.name} ({i + 1}/{total})", (float)i / total); Texture2D thumbnail = CapturePrefabThumbnail(obj as GameObject, width, height); if (thumbnail != null) { byte[] bytes = thumbnail.EncodeToPNG(); string fileName = GetValidFileName($"{obj.name}_{selectedAxis}") + ".png"; File.WriteAllBytes(Path.Combine(outputFolder, fileName), bytes); DestroyImmediate(thumbnail); processed++; } if (i % 5 == 0) System.Threading.Thread.Sleep(10); } } EditorUtility.ClearProgressBar(); Debug.Log($"Finished exporting {processed} out of {total} thumbnails"); } void PreviewSelectedPrefab() { GameObject selectedPrefab = Selection.activeObject as GameObject; if (selectedPrefab == null) { Debug.LogError("Please select a Prefab in the Project window!"); return; } // 在场景中创建临时实例用于预览 GameObject previewInstance = PrefabUtility.InstantiatePrefab(selectedPrefab) as GameObject; if (previewInstance == null) { previewInstance = Object.Instantiate(selectedPrefab); } // 定位场景相机 SceneView.lastActiveSceneView.Frame(previewInstance.GetBounds(), true); Debug.Log($"Preview instance created for {selectedPrefab.name}. Press Play to capture or delete the instance manually."); } Texture2D CapturePrefabThumbnail(GameObject prefab, int width, int height) { if (prefab == null) return null; GameObject tempInstance = null; RenderTexture rt = null; Camera camera = null; GameObject cameraGO = null; try { // 实例化Prefab tempInstance = PrefabUtility.InstantiatePrefab(prefab) as GameObject; if (tempInstance == null) { tempInstance = Object.Instantiate(prefab); } // 计算边界 Bounds bounds = CalculateBounds(tempInstance); if (bounds.size == Vector3.zero) { Debug.LogWarning($"Prefab {prefab.name} has no renderable bounds"); return null; } // 计算相机距离和位置 float maxSize = Mathf.Max(bounds.size.x, bounds.size.y, bounds.size.z); float distance = maxSize * 0.5f + padding * 0.01f; // 设置渲染纹理 rt = RenderTexture.GetTemporary(width, height, 24, RenderTextureFormat.ARGB32); // 创建相机 cameraGO = new GameObject("ThumbnailCamera"); camera = cameraGO.AddComponent<Camera>(); camera.clearFlags = transparentBackground ? CameraClearFlags.SolidColor : CameraClearFlags.Color; camera.backgroundColor = transparentBackground ? Color.clear : backgroundColor; camera.orthographic = true; camera.orthographicSize = distance; camera.targetTexture = rt; camera.nearClipPlane = 0.01f; camera.farClipPlane = 1000f; // 设置相机位置和旋转 Vector3 cameraPosition = bounds.center; Quaternion cameraRotation = GetCameraRotation(selectedAxis); if (autoCenter) { // 根据轴向计算相机位置 Vector3 direction = cameraRotation * Vector3.back; cameraPosition += direction * (distance * 2f); } else { cameraGO.transform.rotation = Quaternion.Euler(customRotation); cameraPosition += cameraGO.transform.forward * -(distance * 2f); } cameraGO.transform.position = cameraPosition; cameraGO.transform.LookAt(bounds.center); // 可选:添加简单的灯光 SetupBasicLighting(tempInstance); // 渲染 camera.Render(); // 读取像素 Texture2D result = new Texture2D(width, height, TextureFormat.RGBA32, false); RenderTexture.active = rt; result.ReadPixels(new Rect(0, 0, width, height), 0, 0); result.Apply(); RenderTexture.active = null; return result; } catch (System.Exception e) { Debug.LogError($"Error capturing thumbnail for {prefab.name}: {e.Message}"); return null; } finally { // 清理资源 if (tempInstance != null) DestroyImmediate(tempInstance); if (cameraGO != null) DestroyImmediate(cameraGO); if (rt != null) RenderTexture.ReleaseTemporary(rt); } } Quaternion GetCameraRotation(ViewAxis axis) { switch (axis) { case ViewAxis.Front: return Quaternion.LookRotation(Vector3.forward, Vector3.up); case ViewAxis.Back: return Quaternion.LookRotation(Vector3.back, Vector3.up); case ViewAxis.Left: return Quaternion.LookRotation(Vector3.left, Vector3.up); case ViewAxis.Right: return Quaternion.LookRotation(Vector3.right, Vector3.up); case ViewAxis.Top: return Quaternion.LookRotation(Vector3.up, Vector3.forward); case ViewAxis.Bottom: return Quaternion.LookRotation(Vector3.down, Vector3.back); case ViewAxis.Isometric: return Quaternion.LookRotation(new Vector3(-1, -0.5f, -1).normalized, Vector3.up); case ViewAxis.IsometricOpposite: return Quaternion.LookRotation(new Vector3(1, -0.5f, 1).normalized, Vector3.up); default: return Quaternion.identity; } } void SetupBasicLighting(GameObject target) { // 检查是否已有光源 Light[] existingLights = Object.FindObjectsOfType<Light>(); if (existingLights.Length == 0) { // 创建基本的三点光源 CreateLight(new Vector3(-5, 5, 5), 1.0f, Color.white); // 主光 CreateLight(new Vector3(5, 3, 5), 0.5f, new Color(0.8f, 0.8f, 1.0f)); // 辅光 CreateLight(new Vector3(0, 10, 0), 0.3f, Color.white); // 顶光 } } void CreateLight(Vector3 position, float intensity, Color color) { GameObject lightGO = new GameObject("ThumbnailLight"); Light light = lightGO.AddComponent<Light>(); light.type = LightType.Directional; lightGO.transform.position = position; lightGO.transform.LookAt(Vector3.zero); light.intensity = intensity; light.color = color; light.shadows = LightShadows.None; // 为了性能,禁用阴影 } Bounds CalculateBounds(GameObject obj) { Bounds bounds = new Bounds(obj.transform.position, Vector3.zero); bool hasBounds = false; // 获取所有Renderer组件 Renderer[] renderers = obj.GetComponentsInChildren<Renderer>(); if (renderers.Length > 0) { bounds = renderers[0].bounds; hasBounds = true; foreach (Renderer renderer in renderers) { bounds.Encapsulate(renderer.bounds); } } // 如果没有Renderer,使用Collider if (!hasBounds) { Collider[] colliders = obj.GetComponentsInChildren<Collider>(); if (colliders.Length > 0) { bounds = colliders[0].bounds; hasBounds = true; foreach (Collider collider in colliders) { bounds.Encapsulate(collider.bounds); } } } // 如果仍然没有边界,使用MeshFilter if (!hasBounds) { MeshFilter[] meshFilters = obj.GetComponentsInChildren<MeshFilter>(); if (meshFilters.Length > 0 && meshFilters[0].sharedMesh != null) { bounds = meshFilters[0].sharedMesh.bounds; bounds.center = obj.transform.position; hasBounds = true; foreach (MeshFilter meshFilter in meshFilters) { if (meshFilter.sharedMesh != null) { bounds.Encapsulate(meshFilter.sharedMesh.bounds); } } } } return bounds; } string GetValidFileName(string fileName) { char[] invalidChars = Path.GetInvalidFileNameChars(); foreach (char c in invalidChars) { fileName = fileName.Replace(c.ToString(), ""); } return fileName; } } // 扩展方法用于计算GameObject的边界 public static class GameObjectExtensions { public static Bounds GetBounds(this GameObject obj) { Bounds bounds = new Bounds(obj.transform.position, Vector3.zero); Renderer[] renderers = obj.GetComponentsInChildren<Renderer>(); if (renderers.Length > 0) { bounds = renderers[0].bounds; foreach (Renderer renderer in renderers) { bounds.Encapsulate(renderer.bounds); } } return bounds; } }