├── Editor ├── MeshInfoWindow.cs ├── MeshView.cs └── TableView.cs ├── README.md └── Shaders ├── Internal-Colored.shader └── MeshPreviewShader.shader /Editor/MeshInfoWindow.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEditor; 3 | using UnityEditor.IMGUI.Controls; 4 | using UnityEngine; 5 | using UnityEngine.Rendering; 6 | using UObject = UnityEngine.Object; 7 | 8 | namespace MeshExtensions.Editor 9 | { 10 | public class MeshInfoWindow : EditorWindow 11 | { 12 | [MenuItem("Window/Mesh Extensions/Mesh Info", false, 0)] 13 | public static void ShowWindow() 14 | { 15 | MeshInfoWindow window = GetWindow(); 16 | window.Show(); 17 | } 18 | 19 | protected Mesh _mesh; 20 | protected TableView _tableView; 21 | protected MeshView _meshView; 22 | 23 | protected float _splitHeight = 0.0f; 24 | protected float _splitOffset = 0.0f; 25 | protected bool _resize = false; 26 | 27 | public static IList Selected = default; 28 | 29 | private void OnEnable() 30 | { 31 | titleContent = new GUIContent("Mesh Info", EditorGUIUtility.IconContent("MainStageView").image); 32 | EditorApplication.searchChanged += Changed; 33 | 34 | _mesh = null; 35 | _tableView = null; 36 | _meshView = null; 37 | _splitHeight = EditorPrefs.GetFloat("mesh-info-split-height", 350f); 38 | 39 | Changed(); 40 | Repaint(); 41 | } 42 | 43 | private void OnDisable() 44 | { 45 | EditorApplication.searchChanged -= Changed; 46 | _meshView.Dispose(); 47 | EditorPrefs.SetFloat("mesh-info-split-height", _splitHeight); 48 | } 49 | 50 | private void OnSelectionChange() 51 | { 52 | Changed(); 53 | } 54 | 55 | private void OnGUI() 56 | { 57 | GUI.enabled = true; 58 | 59 | if (!_mesh) 60 | { 61 | EditorGUILayout.LabelField($"Select mesh to display information."); 62 | return; 63 | } 64 | 65 | ShowSplit(); 66 | ShowTableView(); 67 | ShowMeshView(); 68 | 69 | GUI.enabled = false; 70 | 71 | CheckSelection(); 72 | } 73 | 74 | private void CheckSelection() 75 | { 76 | Selected = _tableView.HasSelection() ? _tableView.GetSelection() : default; 77 | } 78 | 79 | private void Changed() 80 | { 81 | if (Selection.objects?.Length != 1 || !(Selection.activeObject is Mesh mesh)) 82 | { 83 | _mesh = null; 84 | _tableView = null; 85 | } 86 | else if (mesh != _mesh) 87 | { 88 | _mesh = mesh; 89 | _meshView?.Dispose(); 90 | CreateMeshView(); 91 | CreateTable(); 92 | } 93 | 94 | Repaint(); 95 | } 96 | 97 | private void ShowMeshView() 98 | { 99 | if (_meshView != null) 100 | { 101 | Rect r = new Rect(0, 0, position.width, position.height - (position.height - _splitHeight + 20.0f)); 102 | _meshView.OnPreviewGUI(r, GUIStyle.none); 103 | r = new Rect(0, _splitHeight - 20.0f, position.width, 20.0f); 104 | _meshView.OnPreviewSettings(r); 105 | } 106 | } 107 | 108 | private void ShowTableView() 109 | { 110 | if (_tableView != null) 111 | { 112 | Rect r = new Rect(0, _splitHeight, position.width, position.height - _splitHeight); 113 | _tableView.OnGUI(r); 114 | } 115 | } 116 | 117 | private void ShowSplit() 118 | { 119 | Rect rect = new Rect(0.0f, _splitHeight, position.width, 4.0f); 120 | EditorGUIUtility.AddCursorRect(rect, MouseCursor.SplitResizeUpDown); 121 | 122 | if (Event.current.type == EventType.MouseDown && rect.Contains(Event.current.mousePosition)) 123 | { 124 | _splitOffset = Event.current.mousePosition.y - _splitHeight; 125 | _resize = true; 126 | } 127 | 128 | if (_resize) 129 | { 130 | _splitHeight = Mathf.Clamp(Event.current.mousePosition.y - _splitOffset, 100.0f, position.height - 100.0f); 131 | Repaint(); 132 | } 133 | 134 | if (Event.current.type == EventType.MouseUp) 135 | { 136 | _resize = false; 137 | } 138 | } 139 | 140 | private void CreateTable() 141 | { 142 | TreeViewState state = new TreeViewState(); 143 | VertexAttributeDescriptor[] attributes = _mesh.GetVertexAttributes(); 144 | int vertexCount = _mesh.vertexCount; 145 | 146 | MultiColumnHeaderState.Column[] columns = CreateColumns(attributes); 147 | MultiColumnHeaderState headerstate = new MultiColumnHeaderState(columns); 148 | MultiColumnHeader header = new MultiColumnHeader(headerstate); 149 | header.canSort = false; 150 | 151 | _tableView = new TableView(state, header); 152 | _tableView.Reload(); 153 | 154 | for (int i = 0; i < vertexCount; i++) 155 | { 156 | object[] properties = CreateRow(i, attributes); 157 | _tableView.AddElement(properties); 158 | } 159 | 160 | _tableView.Repaint(); 161 | } 162 | 163 | private void CreateMeshView() 164 | { 165 | _meshView = new MeshView(_mesh); 166 | } 167 | 168 | private object[] CreateRow(int idx, VertexAttributeDescriptor[] attributes) 169 | { 170 | List rows = new List(); 171 | rows.Add(idx); 172 | 173 | List vector2s = new List(); 174 | List vector3s = new List(); 175 | List vector4s = new List(); 176 | 177 | foreach (VertexAttributeDescriptor a in attributes) 178 | { 179 | if (a.attribute == VertexAttribute.BlendIndices || a.attribute == VertexAttribute.BlendWeight) 180 | continue; 181 | 182 | int uVId = -1; 183 | 184 | switch (a.attribute) 185 | { 186 | case VertexAttribute.Position: 187 | rows.Add(_mesh.vertices[idx].x); 188 | rows.Add(_mesh.vertices[idx].y); 189 | rows.Add(_mesh.vertices[idx].z); 190 | break; 191 | case VertexAttribute.Normal: 192 | rows.Add(_mesh.normals[idx].x); 193 | rows.Add(_mesh.normals[idx].y); 194 | rows.Add(_mesh.normals[idx].z); 195 | break; 196 | case VertexAttribute.Tangent: 197 | rows.Add(_mesh.tangents[idx].x); 198 | rows.Add(_mesh.tangents[idx].y); 199 | rows.Add(_mesh.tangents[idx].z); 200 | rows.Add(_mesh.tangents[idx].w); 201 | break; 202 | case VertexAttribute.Color: 203 | if (_mesh.colors != null && _mesh.colors.Length > 0) 204 | { 205 | rows.Add(_mesh.colors[idx].r); 206 | rows.Add(_mesh.colors[idx].g); 207 | rows.Add(_mesh.colors[idx].b); 208 | rows.Add(_mesh.colors[idx].a); 209 | } 210 | else if (_mesh.colors32 != null && _mesh.colors32.Length > 0) 211 | { 212 | rows.Add(_mesh.colors32[idx].r); 213 | rows.Add(_mesh.colors32[idx].g); 214 | rows.Add(_mesh.colors32[idx].b); 215 | rows.Add(_mesh.colors32[idx].a); 216 | } 217 | break; 218 | case VertexAttribute.TexCoord0: 219 | uVId = 0; 220 | break; 221 | case VertexAttribute.TexCoord1: 222 | uVId = 1; 223 | break; 224 | case VertexAttribute.TexCoord2: 225 | uVId = 2; 226 | break; 227 | case VertexAttribute.TexCoord3: 228 | uVId = 3; 229 | break; 230 | case VertexAttribute.TexCoord4: 231 | uVId = 4; 232 | break; 233 | case VertexAttribute.TexCoord5: 234 | uVId = 5; 235 | break; 236 | case VertexAttribute.TexCoord6: 237 | uVId = 6; 238 | break; 239 | case VertexAttribute.TexCoord7: 240 | uVId = 7; 241 | break; 242 | } 243 | 244 | if (uVId == -1) 245 | continue; 246 | 247 | switch (a.dimension) 248 | { 249 | case 2: 250 | _mesh.GetUVs(uVId, vector2s); 251 | rows.Add(vector2s[idx].x); 252 | rows.Add(vector2s[idx].y); 253 | break; 254 | case 3: 255 | _mesh.GetUVs(uVId, vector3s); 256 | rows.Add(vector3s[idx].x); 257 | rows.Add(vector3s[idx].y); 258 | rows.Add(vector3s[idx].z); 259 | break; 260 | case 4: 261 | _mesh.GetUVs(uVId, vector4s); 262 | rows.Add(vector4s[idx].x); 263 | rows.Add(vector4s[idx].y); 264 | rows.Add(vector4s[idx].z); 265 | rows.Add(vector4s[idx].w); 266 | break; 267 | } 268 | } 269 | return rows.ToArray(); 270 | } 271 | 272 | private MultiColumnHeaderState.Column[] CreateColumns(VertexAttributeDescriptor[] attributes) 273 | { 274 | List columns = new List(); 275 | columns.Add(CreateColumn("", 40.0f)); 276 | 277 | foreach (VertexAttributeDescriptor a in attributes) 278 | { 279 | if (a.attribute == VertexAttribute.BlendIndices || a.attribute == VertexAttribute.BlendWeight) 280 | continue; 281 | 282 | var label = a.attribute.ToString(); 283 | if (label.Contains("TexCoord")) 284 | label = label.Replace("TexCoord", "UV"); 285 | else 286 | label = label[0].ToString(); 287 | if (a.attribute == VertexAttribute.Color) 288 | { 289 | columns.Add(CreateColumn($"{label} [R]")); 290 | columns.Add(CreateColumn($"{label} [G]")); 291 | columns.Add(CreateColumn($"{label} [B]")); 292 | columns.Add(CreateColumn($"{label} [A]")); 293 | } 294 | else 295 | { 296 | columns.Add(CreateColumn($"{label} [X]")); 297 | columns.Add(CreateColumn($"{label} [Y]")); 298 | if (a.dimension >= 3) 299 | columns.Add(CreateColumn($"{label} [Z]")); 300 | if (a.dimension == 4) 301 | columns.Add(CreateColumn($"{label} [W]")); 302 | } 303 | } 304 | 305 | return columns.ToArray(); 306 | } 307 | 308 | private MultiColumnHeaderState.Column CreateColumn(string header, float minWidth = 74.0f) 309 | { 310 | MultiColumnHeaderState.Column column = new MultiColumnHeaderState.Column(); 311 | column.headerContent = new GUIContent(header); 312 | column.minWidth = minWidth; 313 | column.width = column.minWidth; 314 | column.allowToggleVisibility = false; 315 | column.canSort = false; 316 | 317 | return column; 318 | } 319 | } 320 | } -------------------------------------------------------------------------------- /Editor/MeshView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using UnityEditor; 4 | using UnityEngine; 5 | using UnityEngine.Rendering; 6 | using UObject = UnityEngine.Object; 7 | 8 | 9 | namespace MeshExtensions.Editor 10 | { 11 | public class MeshView : IDisposable 12 | { 13 | public static class Styles 14 | { 15 | public static readonly GUIContent wireframeToggle = EditorGUIUtility.TrTextContent("Wireframe", "Show wireframe"); 16 | public static GUIContent displayModeDropdown = EditorGUIUtility.TrTextContent("", "Change display mode"); 17 | public static GUIContent uvChannelDropdown = EditorGUIUtility.TrTextContent("", "Change active UV channel"); 18 | 19 | public static GUIStyle preSlider = "preSlider"; 20 | public static GUIStyle preSliderThumb = "preSliderThumb"; 21 | } 22 | 23 | public class Settings : IDisposable 24 | { 25 | public HandleMode HandleMode 26 | { 27 | get => (HandleMode)EditorPrefs.GetInt("mesh-info-handle-mode", 0); 28 | set => EditorPrefs.SetInt("mesh-info-handle-mode", (int)value); 29 | } 30 | 31 | public float HandleScale 32 | { 33 | get => EditorPrefs.GetFloat("mesh-info-handle-scale", 0.5f); 34 | set => EditorPrefs.SetFloat("mesh-info-handle-scale", value); 35 | } 36 | 37 | public bool DrawWire 38 | { 39 | get => EditorPrefs.GetBool("mesh-info-draw-wire", true); 40 | set => EditorPrefs.SetBool("mesh-info-draw-wire", value); 41 | } 42 | 43 | public DisplayMode displayMode = DisplayMode.Shaded; 44 | public int activeUVChannel = 0; 45 | 46 | public Vector3 orthoPosition = new Vector3(0.0f, 0.0f, 0.0f); 47 | public Vector2 previewDir = new Vector2(0, 0); 48 | public Vector2 lightDir = new Vector2(0, 0); 49 | public Vector3 pivotPositionOffset = Vector3.zero; 50 | public float zoomFactor = 1.0f; 51 | public int checkerTextureMultiplier = 10; 52 | 53 | public Material shadedPreviewMaterial; 54 | public Material activeMaterial; 55 | public Material meshMultiPreviewMaterial; 56 | public Material wireMaterial; 57 | public Material lineMaterial; 58 | public Texture2D checkeredTexture; 59 | 60 | public bool[] availableDisplayModes = Enumerable.Repeat(true, 6).ToArray(); 61 | public bool[] availableUVChannels = Enumerable.Repeat(true, 8).ToArray(); 62 | public bool[] availableHandleModes = Enumerable.Repeat(true, 4).ToArray(); 63 | 64 | public Settings() 65 | { 66 | shadedPreviewMaterial = new Material(Shader.Find("Standard")); 67 | wireMaterial = CreateWireframeMaterial(); 68 | meshMultiPreviewMaterial = CreateMeshMultiPreviewMaterial(); 69 | lineMaterial = CreateLineMaterial(); 70 | checkeredTexture = EditorGUIUtility.LoadRequired("Previews/Textures/textureChecker.png") as Texture2D; 71 | activeMaterial = shadedPreviewMaterial; 72 | 73 | orthoPosition = new Vector3(0.5f, 0.5f, -1); 74 | previewDir = new Vector2(130, 0); 75 | lightDir = new Vector2(-40, -40); 76 | zoomFactor = 1.0f; 77 | } 78 | 79 | public void Dispose() 80 | { 81 | if (shadedPreviewMaterial != null) 82 | UObject.DestroyImmediate(shadedPreviewMaterial); 83 | if (wireMaterial != null) 84 | UObject.DestroyImmediate(wireMaterial); 85 | if (meshMultiPreviewMaterial != null) 86 | UObject.DestroyImmediate(meshMultiPreviewMaterial); 87 | if (lineMaterial != null) 88 | UObject.DestroyImmediate(lineMaterial); 89 | } 90 | } 91 | 92 | public static string[] m_DisplayModes = { "Shaded", "UV Checker", "UV Layout", "Vertex Color", "Normals", "Tangents" }; 93 | public static string[] m_UVChannels = { "UV0", "UV1", "UV2", "UV3", "UV4", "UV5", "UV6", "UV7" }; 94 | public static string[] m_HandleModes = { "Disabled", "Normals", "Tangents", "Binormals" }; 95 | public enum HandleMode { Disabled = 0, Normals = 1, Tangents = 2, Binormals = 3 }; 96 | public enum DisplayMode { Shaded = 0, UVChecker = 1, UVLayout = 2, VertexColor = 3, Normals = 4, Tangent = 5 } 97 | 98 | protected Mesh _mesh = default; 99 | protected PreviewRenderUtility _preview = default; 100 | protected Settings _settings; 101 | 102 | public MeshView(Mesh mesh) 103 | { 104 | _mesh = mesh; 105 | 106 | _preview = new PreviewRenderUtility(); 107 | _preview.camera.fieldOfView = 30.0f; 108 | _preview.camera.transform.position = new Vector3(5, 5, 0); 109 | 110 | _settings = new Settings(); 111 | CheckAvailableAttributes(); 112 | } 113 | 114 | public void Dispose() 115 | { 116 | _preview.Cleanup(); 117 | _settings.Dispose(); 118 | } 119 | 120 | private static Material CreateWireframeMaterial() 121 | { 122 | var shader = Shader.Find("Hidden/MeshExtension/Internal-Colored"); 123 | if (!shader) 124 | { 125 | Debug.LogWarning("Could not find the built-in Colored shader"); 126 | return null; 127 | } 128 | var mat = new Material(shader); 129 | mat.hideFlags = HideFlags.HideAndDontSave; 130 | mat.SetColor("_Color", new Color(0, 0, 0, 0.3f)); 131 | mat.SetFloat("_ZWrite", 0.0f); 132 | mat.SetFloat("_ZBias", -1.0f); 133 | return mat; 134 | } 135 | 136 | private static Material CreateMeshMultiPreviewMaterial() 137 | { 138 | var shader = Shader.Find("Hidden/MeshExtension/Mesh-MultiPreview"); 139 | if (!shader) 140 | { 141 | Debug.LogWarning("Could not find the built in Mesh preview shader"); 142 | return null; 143 | } 144 | var mat = new Material(shader); 145 | mat.hideFlags = HideFlags.HideAndDontSave; 146 | return mat; 147 | } 148 | 149 | private static Material CreateLineMaterial() 150 | { 151 | Shader shader = Shader.Find("Hidden/MeshExtension/Internal-Colored"); 152 | if (!shader) 153 | { 154 | Debug.LogWarning("Could not find the built-in Colored shader"); 155 | return null; 156 | } 157 | var mat = new Material(shader); 158 | mat.hideFlags = HideFlags.HideAndDontSave; 159 | mat.SetFloat("_SrcBlend", (float)BlendMode.SrcAlpha); 160 | mat.SetFloat("_DstBlend", (float)BlendMode.OneMinusSrcAlpha); 161 | mat.SetFloat("_Cull", (float)CullMode.Off); 162 | mat.SetFloat("_ZWrite", 0.0f); 163 | return mat; 164 | } 165 | 166 | private void ResetView() 167 | { 168 | _settings.zoomFactor = 1.0f; 169 | _settings.orthoPosition = new Vector3(0.5f, 0.5f, -1); 170 | _settings.pivotPositionOffset = Vector3.zero; 171 | 172 | _settings.activeUVChannel = 0; 173 | 174 | _settings.meshMultiPreviewMaterial.SetFloat("_UVChannel", (float)_settings.activeUVChannel); 175 | _settings.meshMultiPreviewMaterial.SetTexture("_MainTex", null); 176 | } 177 | 178 | private void FrameObject() 179 | { 180 | _settings.zoomFactor = 1.0f; 181 | _settings.orthoPosition = new Vector3(0.5f, 0.5f, -1); 182 | _settings.pivotPositionOffset = Vector3.zero; 183 | } 184 | 185 | private void CheckAvailableAttributes() 186 | { 187 | if (!_mesh.HasVertexAttribute(VertexAttribute.Color)) 188 | _settings.availableDisplayModes[(int)DisplayMode.VertexColor] = false; 189 | if (!_mesh.HasVertexAttribute(VertexAttribute.Normal)) 190 | _settings.availableDisplayModes[(int)DisplayMode.Normals] = false; 191 | if (!_mesh.HasVertexAttribute(VertexAttribute.Tangent)) 192 | _settings.availableDisplayModes[(int)DisplayMode.Tangent] = false; 193 | 194 | int index = 0; 195 | for (int i = 4; i < 12; i++) 196 | { 197 | if (!_mesh.HasVertexAttribute((VertexAttribute)i)) 198 | _settings.availableUVChannels[index] = false; 199 | index++; 200 | } 201 | } 202 | 203 | private void DoPopup(Rect popupRect, string[] elements, int selectedIndex, GenericMenu.MenuFunction2 func, bool[] disabledItems) 204 | { 205 | GenericMenu menu = new GenericMenu(); 206 | for (int i = 0; i < elements.Length; i++) 207 | { 208 | var element = elements[i]; 209 | if (Selection.count > 1) 210 | continue; 211 | 212 | if (disabledItems == null || disabledItems[i]) 213 | menu.AddItem(new GUIContent(element), i == selectedIndex, func, i); 214 | else 215 | menu.AddDisabledItem(new GUIContent(element)); 216 | } 217 | menu.DropDown(popupRect); 218 | } 219 | 220 | private void SetUVChannel(object data) 221 | { 222 | int popupIndex = (int)data; 223 | if (popupIndex < 0 || popupIndex >= _settings.availableUVChannels.Length) 224 | return; 225 | 226 | _settings.activeUVChannel = popupIndex; 227 | 228 | if (_settings.displayMode == DisplayMode.UVLayout || _settings.displayMode == DisplayMode.UVChecker) 229 | _settings.activeMaterial.SetFloat("_UVChannel", (float)popupIndex); 230 | } 231 | 232 | private void SetDisplayMode(object data) 233 | { 234 | int popupIndex = (int)data; 235 | if (popupIndex < 0 || popupIndex >= m_DisplayModes.Length) 236 | return; 237 | 238 | _settings.displayMode = (DisplayMode)popupIndex; 239 | 240 | switch (_settings.displayMode) 241 | { 242 | case DisplayMode.Shaded: 243 | OnDropDownAction(_settings.shadedPreviewMaterial, 0, false); 244 | break; 245 | case DisplayMode.UVChecker: 246 | OnDropDownAction(_settings.meshMultiPreviewMaterial, 4, false); 247 | _settings.meshMultiPreviewMaterial.SetTexture("_MainTex", _settings.checkeredTexture); 248 | _settings.meshMultiPreviewMaterial.mainTextureScale = new Vector2(_settings.checkerTextureMultiplier, _settings.checkerTextureMultiplier); 249 | break; 250 | case DisplayMode.UVLayout: 251 | OnDropDownAction(_settings.meshMultiPreviewMaterial, 0, true); 252 | break; 253 | case DisplayMode.VertexColor: 254 | OnDropDownAction(_settings.meshMultiPreviewMaterial, 1, false); 255 | break; 256 | case DisplayMode.Normals: 257 | OnDropDownAction(_settings.meshMultiPreviewMaterial, 2, false); 258 | break; 259 | case DisplayMode.Tangent: 260 | OnDropDownAction(_settings.meshMultiPreviewMaterial, 3, false); 261 | break; 262 | } 263 | } 264 | 265 | private void SetHandleMode(object data) 266 | { 267 | int popupIndex = (int)data; 268 | if (popupIndex < 0 || popupIndex >= m_HandleModes.Length) 269 | return; 270 | 271 | _settings.HandleMode = (HandleMode)popupIndex; 272 | } 273 | 274 | public static void RenderMeshPreview(Mesh mesh, PreviewRenderUtility preview, Settings settings, int meshSubset) 275 | { 276 | if (mesh == null || preview == null) 277 | return; 278 | 279 | Bounds bounds = mesh.bounds; 280 | 281 | Transform renderCamTransform = preview.camera.GetComponent(); 282 | preview.camera.nearClipPlane = 0.0001f; 283 | preview.camera.farClipPlane = 1000f; 284 | 285 | if (settings.displayMode == DisplayMode.UVLayout) 286 | { 287 | preview.camera.orthographic = true; 288 | preview.camera.orthographicSize = settings.zoomFactor; 289 | renderCamTransform.position = settings.orthoPosition; 290 | renderCamTransform.rotation = Quaternion.identity; 291 | DrawUVLayout(mesh, preview, settings); 292 | return; 293 | } 294 | 295 | float halfSize = bounds.extents.magnitude; 296 | float distance = 4.0f * halfSize; 297 | 298 | preview.camera.orthographic = false; 299 | Quaternion camRotation = Quaternion.identity; 300 | Vector3 camPosition = camRotation * Vector3.forward * (-distance * settings.zoomFactor) + settings.pivotPositionOffset; 301 | 302 | renderCamTransform.position = camPosition; 303 | renderCamTransform.rotation = camRotation; 304 | 305 | preview.lights[0].intensity = 1.1f; 306 | preview.lights[0].transform.rotation = Quaternion.Euler(-settings.lightDir.y, -settings.lightDir.x, 0); 307 | preview.lights[1].intensity = 1.1f; 308 | preview.lights[1].transform.rotation = Quaternion.Euler(settings.lightDir.y, settings.lightDir.x, 0); 309 | 310 | preview.ambientColor = new Color(.1f, .1f, .1f, 0); 311 | 312 | RenderMeshPreviewSkipCameraAndLighting(mesh, bounds, preview, settings, null, meshSubset); 313 | } 314 | 315 | private static void DrawUVLayout(Mesh mesh, PreviewRenderUtility preview, Settings settings) 316 | { 317 | GL.PushMatrix(); 318 | settings.lineMaterial.SetPass(0); 319 | 320 | GL.LoadProjectionMatrix(preview.camera.projectionMatrix); 321 | GL.MultMatrix(preview.camera.worldToCameraMatrix); 322 | 323 | GL.Begin(GL.LINES); 324 | const float step = 0.125f; 325 | for (var g = -2.0f; g <= 3.0f; g += step) 326 | { 327 | var majorLine = Mathf.Abs(g - Mathf.Round(g)) < 0.01f; 328 | if (majorLine) 329 | { 330 | GL.Color(new Color(0.6f, 0.6f, 0.7f, 1.0f)); 331 | GL.Vertex3(-2, g, 0); 332 | GL.Vertex3(+3, g, 0); 333 | GL.Vertex3(g, -2, 0); 334 | GL.Vertex3(g, +3, 0); 335 | } 336 | else if (g >= 0 && g <= 1) 337 | { 338 | GL.Color(new Color(0.6f, 0.6f, 0.7f, 0.5f)); 339 | GL.Vertex3(0, g, 0); 340 | GL.Vertex3(1, g, 0); 341 | GL.Vertex3(g, 0, 0); 342 | GL.Vertex3(g, 1, 0); 343 | } 344 | } 345 | GL.End(); 346 | 347 | GL.LoadIdentity(); 348 | settings.meshMultiPreviewMaterial.SetPass(0); 349 | GL.wireframe = true; 350 | Graphics.DrawMeshNow(mesh, preview.camera.worldToCameraMatrix); 351 | GL.wireframe = false; 352 | 353 | GL.PopMatrix(); 354 | } 355 | 356 | public static Color GetSubMeshTint(int index) 357 | { 358 | // color palette generator based on "golden ratio" idea, like in 359 | // https://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/ 360 | var hue = Mathf.Repeat(index * 0.618f, 1); 361 | var sat = index == 0 ? 0f : 0.3f; 362 | var val = 1f; 363 | return Color.HSVToRGB(hue, sat, val); 364 | } 365 | 366 | public static void RenderMeshPreviewSkipCameraAndLighting(Mesh mesh, Bounds bounds, PreviewRenderUtility preview, Settings settings, MaterialPropertyBlock customProperties, int meshSubset) 367 | { 368 | if (mesh == null || preview == null) 369 | return; 370 | 371 | Quaternion rot = Quaternion.Euler(settings.previewDir.y, 0, 0) * Quaternion.Euler(0, settings.previewDir.x, 0); 372 | Vector3 pos = rot * (-bounds.center); 373 | 374 | bool oldFog = RenderSettings.fog; 375 | Unsupported.SetRenderSettingsUseFogNoDirty(false); 376 | 377 | int submeshes = mesh.subMeshCount; 378 | var tintSubmeshes = false; 379 | var colorPropID = 0; 380 | if (submeshes > 1 && settings.displayMode == DisplayMode.Shaded && customProperties == null && meshSubset == -1) 381 | { 382 | tintSubmeshes = true; 383 | customProperties = new MaterialPropertyBlock(); 384 | colorPropID = Shader.PropertyToID("_Color"); 385 | } 386 | 387 | if (settings.activeMaterial != null) 388 | { 389 | preview.camera.clearFlags = CameraClearFlags.Nothing; 390 | if (meshSubset < 0 || meshSubset >= submeshes) 391 | { 392 | for (int i = 0; i < submeshes; ++i) 393 | { 394 | if (tintSubmeshes) 395 | customProperties.SetColor(colorPropID, GetSubMeshTint(i)); 396 | preview.DrawMesh(mesh, pos, rot, settings.activeMaterial, i, customProperties); 397 | } 398 | } 399 | else 400 | preview.DrawMesh(mesh, pos, rot, settings.activeMaterial, meshSubset, customProperties); 401 | preview.Render(); 402 | } 403 | 404 | if (settings.wireMaterial != null && settings.DrawWire) 405 | { 406 | preview.camera.clearFlags = CameraClearFlags.Nothing; 407 | GL.wireframe = true; 408 | if (tintSubmeshes) 409 | customProperties.SetColor(colorPropID, settings.wireMaterial.color); 410 | if (meshSubset < 0 || meshSubset >= submeshes) 411 | { 412 | for (int i = 0; i < submeshes; ++i) 413 | { 414 | var topology = mesh.GetTopology(i); 415 | if (topology == MeshTopology.Lines || topology == MeshTopology.LineStrip || topology == MeshTopology.Points) 416 | continue; 417 | preview.DrawMesh(mesh, pos, rot, settings.wireMaterial, i, customProperties); 418 | } 419 | } 420 | else 421 | preview.DrawMesh(mesh, pos, rot, settings.wireMaterial, meshSubset, customProperties); 422 | preview.Render(); 423 | 424 | GL.wireframe = false; 425 | } 426 | 427 | Unsupported.SetRenderSettingsUseFogNoDirty(oldFog); 428 | 429 | DrawHandles(mesh, preview, settings, pos, rot); 430 | } 431 | 432 | private static void DrawHandles(Mesh mesh, PreviewRenderUtility preview, Settings settings, Vector3 pos, Quaternion rot) 433 | { 434 | Handles.SetCamera(preview.camera); 435 | Handles.zTest = CompareFunction.LessEqual; 436 | 437 | float scale = settings.HandleScale * mesh.bounds.size.magnitude; 438 | 439 | if (MeshInfoWindow.Selected != null) 440 | { 441 | Handles.color = Color.red; 442 | foreach (int i in MeshInfoWindow.Selected) 443 | { 444 | Vector3 projPos = pos + rot * mesh.vertices[i]; 445 | Vector3 viewVec = pos + rot * (mesh.vertices[i] + mesh.normals[i] * scale); 446 | Handles.DrawSolidDisc(projPos, viewVec, 0.025f * scale); 447 | } 448 | } 449 | 450 | switch (settings.HandleMode) 451 | { 452 | case HandleMode.Normals: 453 | Handles.color = Color.green; 454 | for (int i = 0; i < mesh.vertexCount; i++) 455 | { 456 | Vector3 projPos = pos + rot * mesh.vertices[i]; 457 | Quaternion viewRot = Quaternion.LookRotation(rot * mesh.normals[i] * scale); 458 | 459 | Handles.ArrowHandleCap(i, projPos, viewRot, mesh.normals[i].magnitude * 0.1f * scale, EventType.Repaint); 460 | } 461 | break; 462 | case HandleMode.Tangents: 463 | Handles.color = Color.magenta; 464 | for (int i = 0; i < mesh.vertexCount; i++) 465 | { 466 | Vector3 projPos = pos + rot * mesh.vertices[i]; 467 | Quaternion viewRot = Quaternion.LookRotation(rot * mesh.tangents[i] * scale); 468 | 469 | Handles.ArrowHandleCap(i, projPos, viewRot, mesh.normals[i].magnitude * 0.1f * scale, EventType.Repaint); 470 | } 471 | break; 472 | case HandleMode.Binormals: 473 | Handles.color = Color.blue; 474 | for (int i = 0; i < mesh.vertexCount; i++) 475 | { 476 | Vector3 bin = Vector3.Cross(mesh.normals[i], mesh.tangents[i]); 477 | Vector3 projPos = pos + rot * mesh.vertices[i]; 478 | Quaternion viewRot = Quaternion.LookRotation(rot * bin); 479 | 480 | Handles.ArrowHandleCap(i, projPos, viewRot, mesh.normals[i].magnitude * 0.1f * scale, EventType.Repaint); 481 | } 482 | break; 483 | } 484 | } 485 | 486 | private static Vector3 SetVectorMatrix(Matrix4x4 mat) 487 | { 488 | var pos = new Vector3(mat.m03, mat.m13, mat.m23); 489 | var scale = mat.lossyScale; 490 | 491 | var invScale = new Vector3(1.0f / scale.x, 1.0f / scale.y, 1.0f / scale.z); 492 | mat.m00 *= invScale.x; mat.m10 *= invScale.x; mat.m20 *= invScale.x; 493 | mat.m01 *= invScale.y; mat.m11 *= invScale.y; mat.m21 *= invScale.y; 494 | mat.m02 *= invScale.z; mat.m12 *= invScale.z; mat.m22 *= invScale.z; 495 | 496 | var rot = mat.rotation; 497 | return pos; 498 | } 499 | 500 | public Texture2D RenderStaticPreview(int width, int height) 501 | { 502 | if (!ShaderUtil.hardwareSupportsRectRenderTexture) 503 | return null; 504 | _preview.BeginStaticPreview(new Rect(0, 0, width, height)); 505 | RenderMeshPreview(_mesh, _preview, _settings, -1); 506 | return _preview.EndStaticPreview(); 507 | } 508 | 509 | public void OnPreviewGUI(Rect rect, GUIStyle background) 510 | { 511 | var evt = Event.current; 512 | 513 | if (!ShaderUtil.hardwareSupportsRectRenderTexture) 514 | { 515 | if (evt.type == EventType.Repaint) 516 | EditorGUI.DropShadowLabel(new Rect(rect.x, rect.y, rect.width, 40), 517 | "Mesh preview requires\nrender texture support"); 518 | return; 519 | } 520 | 521 | if ((evt.type == EventType.ValidateCommand || evt.type == EventType.ExecuteCommand) && evt.commandName == "FrameSelected") 522 | { 523 | FrameObject(); 524 | evt.Use(); 525 | } 526 | 527 | if (evt.button <= 0 && _settings.displayMode != DisplayMode.UVLayout) 528 | _settings.previewDir = Drag2D(_settings.previewDir, rect); 529 | 530 | if (evt.button == 1 && _settings.displayMode != DisplayMode.UVLayout) 531 | _settings.lightDir = Drag2D(_settings.lightDir, rect); 532 | 533 | if (evt.type == EventType.ScrollWheel) 534 | MeshPreviewZoom(rect, evt); 535 | 536 | if (evt.type == EventType.MouseDrag && (_settings.displayMode == DisplayMode.UVLayout || evt.button == 2)) 537 | MeshPreviewPan(rect, evt); 538 | 539 | if (evt.type != EventType.Repaint) 540 | return; 541 | 542 | _preview.BeginPreview(rect, background); 543 | RenderMeshPreview(_mesh, _preview, _settings, -1); 544 | _preview.EndAndDrawPreview(rect); 545 | } 546 | 547 | private Vector2 Drag2D(Vector2 scrollPosition, Rect position) 548 | { 549 | int controlId = GUIUtility.GetControlID("Slider".GetHashCode(), FocusType.Passive); 550 | Event current = Event.current; 551 | switch (current.GetTypeForControl(controlId)) 552 | { 553 | case EventType.MouseDown: 554 | if (position.Contains(current.mousePosition) && (double) position.width > 50.0) 555 | { 556 | GUIUtility.hotControl = controlId; 557 | current.Use(); 558 | EditorGUIUtility.SetWantsMouseJumping(1); 559 | } 560 | break; 561 | case EventType.MouseUp: 562 | if (GUIUtility.hotControl == controlId) 563 | GUIUtility.hotControl = 0; 564 | EditorGUIUtility.SetWantsMouseJumping(0); 565 | break; 566 | case EventType.MouseDrag: 567 | if (GUIUtility.hotControl == controlId) 568 | { 569 | scrollPosition -= current.delta * (current.shift ? 3f : 1f) / Mathf.Min(position.width, position.height) * 140f; 570 | current.Use(); 571 | GUI.changed = true; 572 | } 573 | break; 574 | } 575 | return scrollPosition; 576 | } 577 | 578 | public void OnPreviewSettings(Rect r) 579 | { 580 | if (!ShaderUtil.hardwareSupportsRectRenderTexture) 581 | return; 582 | 583 | GUI.Box(r, "", EditorStyles.inspectorDefaultMargins); 584 | 585 | GUI.enabled = true; 586 | EditorGUILayout.BeginHorizontal(); 587 | 588 | // calculate width based on the longest value in display modes 589 | float displayModeDropDownWidth = EditorStyles.toolbarDropDown.CalcSize(new GUIContent(m_DisplayModes[(int)DisplayMode.VertexColor])).x; 590 | Rect displayModeDropdownRect = EditorGUILayout.GetControlRect(GUILayout.Width(displayModeDropDownWidth)); 591 | displayModeDropdownRect.y = r.y + 1; 592 | displayModeDropdownRect.x += 0; //2 593 | GUIContent displayModeDropdownContent = new GUIContent(m_DisplayModes[(int)_settings.displayMode], Styles.displayModeDropdown.tooltip); 594 | 595 | if (EditorGUI.DropdownButton(displayModeDropdownRect, displayModeDropdownContent, FocusType.Passive, EditorStyles.toolbarDropDown)) 596 | DoPopup(displayModeDropdownRect, m_DisplayModes, (int)_settings.displayMode, SetDisplayMode, _settings.availableDisplayModes); 597 | 598 | if (_settings.displayMode == DisplayMode.UVLayout || _settings.displayMode == DisplayMode.UVChecker) 599 | { 600 | float channelDropDownWidth = EditorStyles.toolbarDropDown.CalcSize(new GUIContent("Channel 6")).x; 601 | Rect channelDropdownRect = EditorGUILayout.GetControlRect(GUILayout.Width(channelDropDownWidth)); 602 | channelDropdownRect.y = r.y + 1; 603 | channelDropdownRect.x += 5; 604 | GUIContent channel = new GUIContent("UV " + _settings.activeUVChannel, Styles.uvChannelDropdown.tooltip); 605 | 606 | if (EditorGUI.DropdownButton(channelDropdownRect, channel, FocusType.Passive, EditorStyles.toolbarDropDown)) 607 | DoPopup(channelDropdownRect, m_UVChannels, 608 | _settings.activeUVChannel, SetUVChannel, _settings.availableUVChannels); 609 | } 610 | 611 | if (_settings.displayMode == DisplayMode.UVChecker) 612 | { 613 | int oldVal = _settings.checkerTextureMultiplier; 614 | 615 | float sliderWidth = EditorStyles.label.CalcSize(new GUIContent("--------")).x; 616 | Rect sliderRect = EditorGUILayout.GetControlRect(GUILayout.Width(sliderWidth)); 617 | sliderRect.y = r.y; 618 | sliderRect.x += 6; 619 | 620 | _settings.checkerTextureMultiplier = (int)GUI.HorizontalSlider(sliderRect, _settings.checkerTextureMultiplier, 30, 1, Styles.preSlider, Styles.preSliderThumb); 621 | if (oldVal != _settings.checkerTextureMultiplier) 622 | _settings.activeMaterial.mainTextureScale = new Vector2(_settings.checkerTextureMultiplier, _settings.checkerTextureMultiplier); 623 | } 624 | 625 | using (new EditorGUI.DisabledScope(_settings.displayMode == DisplayMode.UVLayout)) 626 | { 627 | float wireWidth = EditorStyles.toolbarDropDown.CalcSize(Styles.wireframeToggle).x; 628 | Rect wireRect = EditorGUILayout.GetControlRect(GUILayout.Width(wireWidth)); 629 | wireRect.y = r.y; 630 | wireRect.x = r.width - wireRect.width - 0; //2 631 | _settings.DrawWire = GUI.Toggle(wireRect, _settings.DrawWire, Styles.wireframeToggle, EditorStyles.toolbarButton); 632 | 633 | float handleWidth = EditorStyles.toolbarDropDown.CalcSize(new GUIContent(m_DisplayModes[(int)HandleMode.Binormals])).x; 634 | Rect handleRect = EditorGUILayout.GetControlRect(GUILayout.Width(handleWidth)); 635 | handleRect.y = r.y; 636 | handleRect.x = r.width - (wireRect.width + handleRect.width); //2 637 | GUIContent handleContent = new GUIContent(m_HandleModes[(int)_settings.HandleMode]); 638 | 639 | if (EditorGUI.DropdownButton(handleRect, handleContent, FocusType.Passive, EditorStyles.toolbarDropDown)) 640 | DoPopup(handleRect, m_HandleModes, (int)_settings.HandleMode, SetHandleMode, _settings.availableHandleModes); 641 | 642 | float scaleWidth = EditorStyles.label.CalcSize(new GUIContent("--------")).x; 643 | Rect scaleRect = EditorGUILayout.GetControlRect(GUILayout.Width(scaleWidth)); 644 | scaleRect.y = r.y; 645 | scaleRect.x = r.width - (wireRect.width + handleRect.width + scaleRect.width + 6); 646 | 647 | _settings.HandleScale = GUI.HorizontalSlider(scaleRect, _settings.HandleScale, 0.01f, 1.0f, Styles.preSlider, Styles.preSliderThumb); 648 | } 649 | 650 | EditorGUILayout.EndHorizontal(); 651 | } 652 | 653 | private void OnDropDownAction(Material mat, int mode, bool flatUVs) 654 | { 655 | ResetView(); 656 | 657 | _settings.activeMaterial = mat; 658 | _settings.activeMaterial.SetFloat("_Mode", (float)mode); 659 | _settings.activeMaterial.SetFloat("_UVChannel", 0.0f); 660 | _settings.activeMaterial.SetFloat("_Cull", flatUVs ? (float)CullMode.Off : (float)CullMode.Back); 661 | } 662 | 663 | private void MeshPreviewZoom(Rect rect, Event evt) 664 | { 665 | float zoomDelta = -(HandleUtility.niceMouseDeltaZoom * 0.5f) * 0.05f; 666 | var newZoom = _settings.zoomFactor + _settings.zoomFactor * zoomDelta; 667 | newZoom = Mathf.Clamp(newZoom, 0.1f, 10.0f); 668 | 669 | // we want to zoom around current mouse position 670 | var mouseViewPos = new Vector2( 671 | evt.mousePosition.x / rect.width, 672 | 1 - evt.mousePosition.y / rect.height); 673 | var mouseWorldPos = _preview.camera.ViewportToWorldPoint(mouseViewPos); 674 | var mouseToCamPos = _settings.orthoPosition - mouseWorldPos; 675 | var newCamPos = mouseWorldPos + mouseToCamPos * (newZoom / _settings.zoomFactor); 676 | 677 | if (_settings.displayMode != DisplayMode.UVLayout) 678 | { 679 | _preview.camera.transform.position = new Vector3(newCamPos.x, newCamPos.y, newCamPos.z); 680 | } 681 | else 682 | { 683 | _settings.orthoPosition.x = newCamPos.x; 684 | _settings.orthoPosition.y = newCamPos.y; 685 | } 686 | 687 | _settings.zoomFactor = newZoom; 688 | evt.Use(); 689 | } 690 | 691 | private void MeshPreviewPan(Rect rect, Event evt) 692 | { 693 | var cam = _preview.camera; 694 | 695 | var delta = new Vector3(-evt.delta.x * cam.pixelWidth / rect.width, evt.delta.y * cam.pixelHeight / rect.height, 0); 696 | 697 | Vector3 screenPos; 698 | Vector3 worldPos; 699 | if (_settings.displayMode == DisplayMode.UVLayout) 700 | { 701 | screenPos = cam.WorldToScreenPoint(_settings.orthoPosition); 702 | screenPos += delta; 703 | worldPos = cam.ScreenToWorldPoint(screenPos); 704 | _settings.orthoPosition.x = worldPos.x; 705 | _settings.orthoPosition.y = worldPos.y; 706 | } 707 | else 708 | { 709 | screenPos = cam.WorldToScreenPoint(_settings.pivotPositionOffset); 710 | screenPos += delta; 711 | worldPos = cam.ScreenToWorldPoint(screenPos) - _settings.pivotPositionOffset; 712 | _settings.pivotPositionOffset += worldPos; 713 | } 714 | 715 | evt.Use(); 716 | } 717 | } 718 | } -------------------------------------------------------------------------------- /Editor/TableView.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEditor; 3 | using UnityEditor.IMGUI.Controls; 4 | using UnityEngine; 5 | using UObject = UnityEngine.Object; 6 | 7 | namespace MeshExtensions.Editor 8 | { 9 | public class TableView : TreeView 10 | { 11 | protected int freeID = 0; 12 | 13 | public TableView(TreeViewState state, MultiColumnHeader multiColumnHeader) : base(state, multiColumnHeader) 14 | { 15 | freeID = 0; 16 | 17 | showAlternatingRowBackgrounds = true; 18 | showBorder = true; 19 | cellMargin = 1; 20 | 21 | multiColumnHeader.canSort = false; 22 | multiColumnHeader.ResizeToFit(); 23 | } 24 | 25 | public int GetNewID() 26 | { 27 | int id = freeID; 28 | freeID += 1; 29 | return id; 30 | } 31 | 32 | public void AddElement(object[] properties) 33 | { 34 | var rows = GetRows(); 35 | var item = TableViewItem.Create(properties, this); 36 | 37 | rootItem.AddChild(item); 38 | rows.Add(item); 39 | } 40 | 41 | protected override void RowGUI(RowGUIArgs args) 42 | { 43 | GUI.enabled = false; 44 | var item = (TableViewItem)args.item; 45 | 46 | for (int i = 0; i < args.GetNumVisibleColumns(); ++i) 47 | { 48 | Rect r = args.GetCellRect(i); 49 | int column = args.GetColumn(i); 50 | int idx = column; 51 | 52 | switch (item.properties[idx]) 53 | { 54 | case int n: 55 | EditorGUI.DelayedIntField(r, GUIContent.none, n, new GUIStyle("label")); 56 | DrawDivider(r, -1.0f); 57 | break; 58 | case float f: 59 | EditorGUI.DelayedFloatField(r, GUIContent.none, f, new GUIStyle("label")); 60 | DrawDivider(r); 61 | break; 62 | case string s: 63 | EditorGUI.DelayedTextField(r, GUIContent.none, s, new GUIStyle("label")); 64 | DrawDivider(r); 65 | break; 66 | } 67 | } 68 | 69 | GUI.enabled = true; 70 | } 71 | 72 | protected void DrawDivider(Rect r, float xMaxOffset = 0.0f) 73 | { 74 | Rect dividerRect = new Rect(r.xMax + xMaxOffset, r.y, 1f, r.height); 75 | EditorGUI.DrawRect(dividerRect, new Color(0.5f, 0.5f, 0.5f, 0.5f)); 76 | } 77 | 78 | protected override TreeViewItem BuildRoot() 79 | { 80 | TreeViewItem root = new TreeViewItem(); 81 | root.depth = -1; 82 | root.id = -1; 83 | root.parent = null; 84 | root.children = new List(); 85 | 86 | return root; 87 | } 88 | } 89 | 90 | public class TableViewItem : TreeViewItem 91 | { 92 | public object[] properties; 93 | 94 | public static TableViewItem Create(object[] properties, TableView tableView) 95 | { 96 | TableViewItem item = new TableViewItem(); 97 | item.children = new List(); 98 | item.depth = 0; 99 | item.id = tableView.GetNewID(); 100 | item.properties = properties; 101 | 102 | return item; 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity-Mesh-Info 2 | 3 | A simple mesh data viewer for Unity. 4 | 5 | How to preview 6 | ----------- 7 | * Open a mesh info window (Window -> Mesh Extensions -> Mesh Info) and select mesh. 8 | 9 | 10 | -------------------------------------------------------------------------------- /Shaders/Internal-Colored.shader: -------------------------------------------------------------------------------- 1 | Shader "Hidden/MeshExtension/Internal-Colored" 2 | { 3 | Properties 4 | { 5 | _Color ("Color", Color) = (1,1,1,1) 6 | _SrcBlend ("SrcBlend", Int) = 5.0 // SrcAlpha 7 | _DstBlend ("DstBlend", Int) = 10.0 // OneMinusSrcAlpha 8 | _ZWrite ("ZWrite", Int) = 1.0 // On 9 | _ZTest ("ZTest", Int) = 4.0 // LEqual 10 | _Cull ("Cull", Int) = 0.0 // Off 11 | _ZBias ("ZBias", Float) = 0.0 12 | } 13 | 14 | SubShader 15 | { 16 | Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" } 17 | Pass 18 | { 19 | Blend [_SrcBlend] [_DstBlend] 20 | ZWrite [_ZWrite] 21 | ZTest [_ZTest] 22 | Cull [_Cull] 23 | Offset [_ZBias], [_ZBias] 24 | 25 | CGPROGRAM 26 | #pragma vertex vert 27 | #pragma fragment frag 28 | #include "UnityCG.cginc" 29 | struct appdata_t { 30 | float4 vertex : POSITION; 31 | float4 color : COLOR; 32 | }; 33 | struct v2f { 34 | fixed4 color : COLOR; 35 | float4 vertex : SV_POSITION; 36 | }; 37 | float4 _Color; 38 | v2f vert (appdata_t v) 39 | { 40 | v2f o; 41 | o.vertex = UnityObjectToClipPos(v.vertex); 42 | o.color = v.color * _Color; 43 | return o; 44 | } 45 | fixed4 frag (v2f i) : SV_Target 46 | { 47 | return i.color; 48 | } 49 | ENDCG 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /Shaders/MeshPreviewShader.shader: -------------------------------------------------------------------------------- 1 | Shader "Hidden/MeshExtension/Mesh-MultiPreview" 2 | { 3 | Properties 4 | { 5 | _MainTex ("Texture", 2D) = "white" {} 6 | _UVChannel ("UV Channel", Int) = 0 7 | 8 | // this goes up to 4 9 | // 0 - flat UVs 10 | // 1 - vertex color 11 | // 2 - normals as color 12 | // 3 - tangents as color 13 | // 4 - checkerboard UVs 14 | _Mode ("Draw mode", Int) = 1 15 | } 16 | SubShader 17 | { 18 | Tags { "RenderType"="Opaque" } 19 | LOD 100 20 | 21 | Pass 22 | { 23 | CGPROGRAM 24 | #pragma vertex vertShader 25 | #pragma fragment fragShader 26 | 27 | #include "UnityCG.cginc" 28 | 29 | float _WireThickness; 30 | int _UVChannel; 31 | int _Mode; 32 | 33 | struct appdata 34 | { 35 | float4 vertex : POSITION; 36 | float2 uv0 : TEXCOORD0; 37 | float2 uv1 : TEXCOORD1; 38 | float2 uv2 : TEXCOORD2; 39 | float2 uv3 : TEXCOORD3; 40 | float2 uv4 : TEXCOORD4; 41 | float2 uv5 : TEXCOORD5; 42 | float2 uv6 : TEXCOORD6; 43 | float2 uv7 : TEXCOORD7; 44 | float4 tangent : TANGENT; 45 | float3 normal : NORMAL; 46 | fixed4 color : COLOR; 47 | }; 48 | 49 | struct v2f 50 | { 51 | float4 vertex : SV_POSITION; 52 | float2 uv : TEXCOORD0; 53 | fixed4 color : COLOR; 54 | }; 55 | 56 | sampler2D _MainTex; 57 | float4 _MainTex_ST; 58 | 59 | float2 GetUV(appdata v) 60 | { 61 | if(_UVChannel == 0) 62 | return v.uv0.xy; 63 | if(_UVChannel == 1) 64 | return v.uv1.xy; 65 | if(_UVChannel == 2) 66 | return v.uv2.xy; 67 | if(_UVChannel == 3) 68 | return v.uv3.xy; 69 | if(_UVChannel == 4) 70 | return v.uv4.xy; 71 | if(_UVChannel == 5) 72 | return v.uv5.xy; 73 | if(_UVChannel == 6) 74 | return v.uv6.xy; 75 | if(_UVChannel == 7) 76 | return v.uv7.xy; 77 | return v.uv0.xy; 78 | } 79 | 80 | v2f vertShader (appdata v) 81 | { 82 | v2f o; 83 | 84 | if(_Mode == 0) 85 | { 86 | o.uv = GetUV(v); 87 | o.vertex = UnityObjectToClipPos(float4(o.uv.x, o.uv.y, 0, 1)); 88 | o.color = fixed4(1.0, 0.9, 0.6, 1.0); 89 | } 90 | 91 | if(_Mode > 0) 92 | { 93 | o.vertex = UnityObjectToClipPos(v.vertex); 94 | } 95 | 96 | if(_Mode == 1) 97 | { 98 | o.color = v.color; 99 | } 100 | 101 | if(_Mode == 2) 102 | { 103 | o.color = float4(normalize(v.normal.xyz) * 0.5 + 0.5, 1); 104 | } 105 | 106 | if(_Mode == 3) 107 | { 108 | o.color = float4(normalize(v.tangent.xyz) * 0.5 + 0.5, 1); 109 | } 110 | 111 | if(_Mode == 4) 112 | { 113 | o.uv = TRANSFORM_TEX(GetUV(v), _MainTex); 114 | // a little bit of shading based on object space normal 115 | half3 skyColor = half3(0.212, 0.227, 0.259)*4; 116 | half3 groundColor = half3(0.047, 0.043, 0.035)*4; 117 | float a = v.normal.y * 0.5 + 0.5; 118 | o.color.rgb = lerp(groundColor, skyColor, a); 119 | o.color.a = 1; 120 | } 121 | 122 | return o; 123 | } 124 | 125 | fixed4 fragShader (v2f i) : SV_Target 126 | { 127 | if(_Mode == 4) 128 | { 129 | half4 checker = tex2D(_MainTex, i.uv); 130 | i.color *= checker; 131 | } 132 | return i.color; 133 | } 134 | ENDCG 135 | } 136 | } 137 | } --------------------------------------------------------------------------------