Boat Attack使用了Universal RP的许多新图形功能,可以用于探索 Universal RP 的使用方式和技巧。
您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 

789 行
29 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEditor.Graphing;
using UnityEditor.Graphing.Util;
using UnityEditor.ShaderGraph.Drawing.Controls;
using UnityEngine.Rendering;
using UnityEditor.Experimental.GraphView;
using UnityEditor.Rendering;
using UnityEditor.ShaderGraph.Drawing.Colors;
using UnityEditor.ShaderGraph.Internal;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using Node = UnityEditor.Experimental.GraphView.Node;
namespace UnityEditor.ShaderGraph.Drawing
{
sealed class MaterialNodeView : Node, IShaderNodeView
{
PreviewRenderData m_PreviewRenderData;
Image m_PreviewImage;
// Remove this after updated to the correct API call has landed in trunk. ------------
VisualElement m_TitleContainer;
new VisualElement m_ButtonContainer;
VisualElement m_PreviewContainer;
VisualElement m_ControlItems;
VisualElement m_PreviewFiller;
VisualElement m_ControlsDivider;
IEdgeConnectorListener m_ConnectorListener;
VisualElement m_PortInputContainer;
VisualElement m_SettingsContainer;
bool m_ShowSettings = false;
VisualElement m_SettingsButton;
VisualElement m_Settings;
VisualElement m_NodeSettingsView;
GraphView m_GraphView;
public void Initialize(AbstractMaterialNode inNode, PreviewManager previewManager, IEdgeConnectorListener connectorListener, GraphView graphView)
{
styleSheets.Add(Resources.Load<StyleSheet>("Styles/MaterialNodeView"));
styleSheets.Add(Resources.Load<StyleSheet>($"Styles/ColorMode"));
AddToClassList("MaterialNode");
if (inNode == null)
return;
var contents = this.Q("contents");
m_GraphView = graphView;
m_ConnectorListener = connectorListener;
node = inNode;
viewDataKey = node.guid.ToString();
UpdateTitle();
// Add controls container
var controlsContainer = new VisualElement { name = "controls" };
{
m_ControlsDivider = new VisualElement { name = "divider" };
m_ControlsDivider.AddToClassList("horizontal");
controlsContainer.Add(m_ControlsDivider);
m_ControlItems = new VisualElement { name = "items" };
controlsContainer.Add(m_ControlItems);
// Instantiate control views from node
foreach (var propertyInfo in node.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
foreach (IControlAttribute attribute in propertyInfo.GetCustomAttributes(typeof(IControlAttribute), false))
m_ControlItems.Add(attribute.InstantiateControl(node, propertyInfo));
}
if (m_ControlItems.childCount > 0)
contents.Add(controlsContainer);
// Node Base class toggles the 'expanded' variable already, this is on top of that call
m_CollapseButton.RegisterCallback<MouseUpEvent>(SetNodeExpandedStateOnSelection);
if (node.hasPreview)
{
// Add actual preview which floats on top of the node
m_PreviewContainer = new VisualElement
{
name = "previewContainer",
style = { overflow = Overflow.Hidden },
pickingMode = PickingMode.Ignore
};
m_PreviewImage = new Image
{
name = "preview",
pickingMode = PickingMode.Ignore,
image = Texture2D.whiteTexture,
};
{
// Add preview collapse button on top of preview
var collapsePreviewButton = new VisualElement { name = "collapse" };
collapsePreviewButton.Add(new VisualElement { name = "icon" });
collapsePreviewButton.AddManipulator(new Clickable(() =>
{
node.owner.owner.RegisterCompleteObjectUndo("Collapse Preview");
SetPreviewExpandedStateOnSelection(false);
}));
m_PreviewImage.Add(collapsePreviewButton);
}
m_PreviewContainer.Add(m_PreviewImage);
// Hook up preview image to preview manager
m_PreviewRenderData = previewManager.GetPreview(inNode);
m_PreviewRenderData.onPreviewChanged += UpdatePreviewTexture;
UpdatePreviewTexture();
// Add fake preview which pads out the node to provide space for the floating preview
m_PreviewFiller = new VisualElement { name = "previewFiller" };
m_PreviewFiller.AddToClassList("expanded");
{
var previewDivider = new VisualElement { name = "divider" };
previewDivider.AddToClassList("horizontal");
m_PreviewFiller.Add(previewDivider);
var expandPreviewButton = new VisualElement { name = "expand" };
expandPreviewButton.Add(new VisualElement { name = "icon" });
expandPreviewButton.AddManipulator(new Clickable(() =>
{
node.owner.owner.RegisterCompleteObjectUndo("Expand Preview");
SetPreviewExpandedStateOnSelection(true);
}));
m_PreviewFiller.Add(expandPreviewButton);
}
contents.Add(m_PreviewFiller);
SetPreviewExpandedStateOnSelection(node.previewExpanded);
}
// Add port input container, which acts as a pixel cache for all port inputs
m_PortInputContainer = new VisualElement
{
name = "portInputContainer",
style = { overflow = Overflow.Hidden },
pickingMode = PickingMode.Ignore
};
Add(m_PortInputContainer);
AddSlots(node.GetSlots<MaterialSlot>());
UpdatePortInputs();
base.expanded = node.drawState.expanded;
RefreshExpandedState(); //This should not be needed. GraphView needs to improve the extension api here
UpdatePortInputVisibilities();
SetPosition(new Rect(node.drawState.position.x, node.drawState.position.y, 0, 0));
if (node is SubGraphNode)
{
RegisterCallback<MouseDownEvent>(OnSubGraphDoubleClick);
}
m_PortInputContainer.SendToBack();
m_TitleContainer = this.Q("title");
var masterNode = node as IMasterNode;
if (masterNode != null)
{
AddToClassList("master");
if (!masterNode.IsPipelineCompatible(GraphicsSettings.renderPipelineAsset))
{
AttachMessage("The current render pipeline is not compatible with this master node.", ShaderCompilerMessageSeverity.Error);
}
}
m_NodeSettingsView = new NodeSettingsView();
m_NodeSettingsView.visible = false;
Add(m_NodeSettingsView);
m_SettingsButton = new VisualElement {name = "settings-button"};
m_SettingsButton.Add(new VisualElement { name = "icon" });
m_Settings = new VisualElement();
AddDefaultSettings();
// Add Node type specific settings
var nodeTypeSettings = node as IHasSettings;
if (nodeTypeSettings != null)
m_Settings.Add(nodeTypeSettings.CreateSettingsElement());
// Add manipulators
m_SettingsButton.AddManipulator(new Clickable(() =>
{
UpdateSettingsExpandedState();
}));
if(m_Settings.childCount > 0)
{
m_ButtonContainer = new VisualElement { name = "button-container" };
m_ButtonContainer.style.flexDirection = FlexDirection.Row;
m_ButtonContainer.Add(m_SettingsButton);
m_ButtonContainer.Add(m_CollapseButton);
m_TitleContainer.Add(m_ButtonContainer);
}
// Register OnMouseHover callbacks for node highlighting
RegisterCallback<MouseEnterEvent>(OnMouseHover);
RegisterCallback<MouseLeaveEvent>(OnMouseHover);
}
public void AttachMessage(string errString, ShaderCompilerMessageSeverity severity)
{
ClearMessage();
IconBadge badge;
if (severity == ShaderCompilerMessageSeverity.Error)
{
badge = IconBadge.CreateError(errString);
}
else
{
badge = IconBadge.CreateComment(errString);
}
Add(badge);
badge.AttachTo(m_TitleContainer, SpriteAlignment.RightCenter);
}
public void ClearMessage()
{
var badge = this.Q<IconBadge>();
if(badge != null)
{
badge.Detach();
badge.RemoveFromHierarchy();
}
}
public VisualElement colorElement
{
get { return this; }
}
static readonly StyleColor noColor = new StyleColor(StyleKeyword.Null);
public void SetColor(Color color)
{
m_TitleContainer.style.borderBottomColor = color;
}
public void ResetColor()
{
m_TitleContainer.style.borderBottomColor = noColor;
}
public Color GetColor()
{
return m_TitleContainer.resolvedStyle.borderBottomColor;
}
void OnGeometryChanged(GeometryChangedEvent evt)
{
// style.positionTop and style.positionLeft are in relation to the parent,
// so we translate the layout of the settings button to be in the coordinate
// space of the settings view's parent.
var settingsButtonLayout = m_SettingsButton.ChangeCoordinatesTo(m_NodeSettingsView.parent, m_SettingsButton.layout);
m_NodeSettingsView.style.top = settingsButtonLayout.yMax - 18f;
m_NodeSettingsView.style.left = settingsButtonLayout.xMin - 16f;
}
void OnSubGraphDoubleClick(MouseDownEvent evt)
{
if (evt.clickCount == 2 && evt.button == 0)
{
SubGraphNode subgraphNode = node as SubGraphNode;
var path = AssetDatabase.GUIDToAssetPath(subgraphNode.subGraphGuid);
ShaderGraphImporterEditor.ShowGraphEditWindow(path);
}
}
public Node gvNode => this;
public AbstractMaterialNode node { get; private set; }
public override bool expanded
{
get { return base.expanded; }
set
{
if (base.expanded != value)
base.expanded = value;
if (node.drawState.expanded != value)
{
var ds = node.drawState;
ds.expanded = value;
node.drawState = ds;
}
RefreshExpandedState(); //This should not be needed. GraphView needs to improve the extension api here
UpdatePortInputVisibilities();
}
}
public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
{
if (evt.target is Node)
{
var isMaster = node is IMasterNode;
var isActive = node.guid == node.owner.activeOutputNodeGuid;
if (isMaster)
{
evt.menu.AppendAction("Set Active", SetMasterAsActive,
_ => isActive ? DropdownMenuAction.Status.Checked : DropdownMenuAction.Status.Normal);
}
var canViewShader = node.hasPreview || node is IMasterNode || node is SubGraphOutputNode;
evt.menu.AppendAction("Copy Shader", CopyToClipboard,
_ => canViewShader ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Hidden,
GenerationMode.ForReals);
evt.menu.AppendAction("Show Generated Code", ShowGeneratedCode,
_ => canViewShader ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Hidden,
GenerationMode.ForReals);
if (Unsupported.IsDeveloperMode())
{
evt.menu.AppendAction("Show Preview Code", ShowGeneratedCode,
_ => canViewShader ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Hidden,
GenerationMode.Preview);
}
}
base.BuildContextualMenu(evt);
}
void SetMasterAsActive(DropdownMenuAction action)
{
node.owner.activeOutputNodeGuid = node.guid;
}
void CopyToClipboard(DropdownMenuAction action)
{
GUIUtility.systemCopyBuffer = ConvertToShader((GenerationMode) action.userData);
}
public string SanitizeName(string name)
{
return new string(name.Where(c => !Char.IsWhiteSpace(c)).ToArray());
}
public void ShowGeneratedCode(DropdownMenuAction action)
{
string name = GetFirstAncestorOfType<GraphEditorView>().assetName;
var mode = (GenerationMode)action.userData;
string path = String.Format("Temp/GeneratedFromGraph-{0}-{1}-{2}{3}.shader", SanitizeName(name),
SanitizeName(node.name), node.guid, mode == GenerationMode.Preview ? "-Preview" : "");
if (GraphUtil.WriteToFile(path, ConvertToShader(mode)))
GraphUtil.OpenFile(path);
}
string ConvertToShader(GenerationMode mode)
{
List<PropertyCollector.TextureInfo> textureInfo;
if (node is IMasterNode masterNode)
return masterNode.GetShader(mode, node.name, out textureInfo);
return node.owner.GetShader(node, mode, node.name).shader;
}
void AddDefaultSettings()
{
PropertySheet ps = new PropertySheet();
bool hasDefaultSettings = false;
if(node.canSetPrecision)
{
hasDefaultSettings = true;
ps.Add(new PropertyRow(new Label("Precision")), (row) =>
{
row.Add(new EnumField(node.precision), (field) =>
{
field.RegisterValueChangedCallback(evt =>
{
if (evt.newValue.Equals(node.precision))
return;
var editorView = GetFirstAncestorOfType<GraphEditorView>();
var nodeList = m_GraphView.Query<MaterialNodeView>().ToList();
editorView.colorManager.SetNodesDirty(nodeList);
node.owner.owner.RegisterCompleteObjectUndo("Change precision");
node.precision = (Precision)evt.newValue;
node.owner.ValidateGraph();
editorView.colorManager.UpdateNodeViews(nodeList);
node.Dirty(ModificationScope.Graph);
});
});
});
}
if(hasDefaultSettings)
m_Settings.Add(ps);
}
void RecreateSettings()
{
m_Settings.RemoveFromHierarchy();
m_Settings = new PropertySheet();
// Add default settings
AddDefaultSettings();
// Add Node type specific settings
var nodeTypeSettings = node as IHasSettings;
if (nodeTypeSettings != null)
m_Settings.Add(nodeTypeSettings.CreateSettingsElement());
m_NodeSettingsView.Add(m_Settings);
}
void UpdateSettingsExpandedState()
{
m_ShowSettings = !m_ShowSettings;
if (m_ShowSettings)
{
m_NodeSettingsView.Add(m_Settings);
m_NodeSettingsView.visible = true;
SetSelfSelected();
m_SettingsButton.AddToClassList("clicked");
RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
OnGeometryChanged(null);
}
else
{
m_Settings.RemoveFromHierarchy();
SetSelfSelected();
m_NodeSettingsView.visible = false;
m_SettingsButton.RemoveFromClassList("clicked");
UnregisterCallback<GeometryChangedEvent>(OnGeometryChanged);
}
}
private void SetSelfSelected()
{
m_GraphView.ClearSelection();
m_GraphView.AddToSelection(this);
}
void SetNodeExpandedStateOnSelection(MouseUpEvent evt)
{
if (!selected)
SetSelfSelected();
else
{
if (m_GraphView is MaterialGraphView)
{
var matGraphView = m_GraphView as MaterialGraphView;
matGraphView.SetNodeExpandedOnSelection(expanded);
}
}
}
void SetPreviewExpandedStateOnSelection(bool state)
{
if (!selected)
{
SetSelfSelected();
UpdatePreviewExpandedState(state);
}
else
{
if(m_GraphView is MaterialGraphView)
{
var matGraphView = m_GraphView as MaterialGraphView;
matGraphView.SetPreviewExpandedOnSelection(state);
}
}
}
public bool CanToggleExpanded()
{
return m_CollapseButton.enabledInHierarchy;
}
void UpdatePreviewExpandedState(bool expanded)
{
node.previewExpanded = expanded;
if (m_PreviewFiller == null)
return;
if (expanded)
{
if (m_PreviewContainer.parent != this)
{
Add(m_PreviewContainer);
m_PreviewContainer.PlaceBehind(this.Q("selection-border"));
}
m_PreviewFiller.AddToClassList("expanded");
m_PreviewFiller.RemoveFromClassList("collapsed");
}
else
{
if (m_PreviewContainer.parent == m_PreviewFiller)
{
m_PreviewContainer.RemoveFromHierarchy();
}
m_PreviewFiller.RemoveFromClassList("expanded");
m_PreviewFiller.AddToClassList("collapsed");
}
UpdatePreviewTexture();
}
void UpdateTitle()
{
if (node is SubGraphNode subGraphNode && subGraphNode.asset != null)
title = subGraphNode.asset.name;
else
title = node.name;
}
public void OnModified(ModificationScope scope)
{
UpdateTitle();
if (node.hasPreview)
UpdatePreviewExpandedState(node.previewExpanded);
base.expanded = node.drawState.expanded;
// Update slots to match node modification
if (scope == ModificationScope.Topological)
{
RecreateSettings();
var slots = node.GetSlots<MaterialSlot>().ToList();
var inputPorts = inputContainer.Children().OfType<ShaderPort>().ToList();
foreach (var port in inputPorts)
{
var currentSlot = port.slot;
var newSlot = slots.FirstOrDefault(s => s.id == currentSlot.id);
if (newSlot == null)
{
// Slot doesn't exist anymore, remove it
inputContainer.Remove(port);
// We also need to remove the inline input
var portInputView = m_PortInputContainer.Children().OfType<PortInputView>().FirstOrDefault(v => Equals(v.slot, port.slot));
if (portInputView != null)
portInputView.RemoveFromHierarchy();
}
else
{
port.slot = newSlot;
var portInputView = m_PortInputContainer.Children().OfType<PortInputView>().FirstOrDefault(x => x.slot.id == currentSlot.id);
if (newSlot.isConnected)
{
portInputView?.RemoveFromHierarchy();
}
else
{
portInputView?.UpdateSlot(newSlot);
}
slots.Remove(newSlot);
}
}
var outputPorts = outputContainer.Children().OfType<ShaderPort>().ToList();
foreach (var port in outputPorts)
{
var currentSlot = port.slot;
var newSlot = slots.FirstOrDefault(s => s.id == currentSlot.id);
if (newSlot == null)
{
outputContainer.Remove(port);
}
else
{
port.slot = newSlot;
slots.Remove(newSlot);
}
}
AddSlots(slots);
slots.Clear();
slots.AddRange(node.GetSlots<MaterialSlot>());
if (inputContainer.childCount > 0)
inputContainer.Sort((x, y) => slots.IndexOf(((ShaderPort)x).slot) - slots.IndexOf(((ShaderPort)y).slot));
if (outputContainer.childCount > 0)
outputContainer.Sort((x, y) => slots.IndexOf(((ShaderPort)x).slot) - slots.IndexOf(((ShaderPort)y).slot));
UpdatePortInputs();
UpdatePortInputVisibilities();
}
RefreshExpandedState(); //This should not be needed. GraphView needs to improve the extension api here
foreach (var listener in m_ControlItems.Children().OfType<AbstractMaterialNodeModificationListener>())
{
if (listener != null)
listener.OnNodeModified(scope);
}
}
void AddSlots(IEnumerable<MaterialSlot> slots)
{
foreach (var slot in slots)
{
if (slot.hidden)
continue;
var port = ShaderPort.Create(slot, m_ConnectorListener);
if (slot.isOutputSlot)
outputContainer.Add(port);
else
inputContainer.Add(port);
}
}
void UpdatePortInputs()
{
foreach (var port in inputContainer.Children().OfType<ShaderPort>())
{
if (port.slot.isConnected)
{
continue;
}
var portInputView = m_PortInputContainer.Children().OfType<PortInputView>().FirstOrDefault(a => Equals(a.slot, port.slot));
if (portInputView == null)
{
portInputView = new PortInputView(port.slot) { style = { position = Position.Absolute } };
m_PortInputContainer.Add(portInputView);
SetPortInputPosition(port, portInputView);
}
port.RegisterCallback<GeometryChangedEvent>(UpdatePortInput);
}
}
void UpdatePortInput(GeometryChangedEvent evt)
{
var port = (ShaderPort)evt.target;
var inputViews = m_PortInputContainer.Children().OfType<PortInputView>().Where(x => Equals(x.slot, port.slot));
// Ensure PortInputViews are initialized correctly
// Dynamic port lists require one update to validate before init
if(inputViews.Count() != 0)
{
var inputView = inputViews.First();
SetPortInputPosition(port, inputView);
}
port.UnregisterCallback<GeometryChangedEvent>(UpdatePortInput);
}
void SetPortInputPosition(ShaderPort port, PortInputView inputView)
{
inputView.style.top = port.layout.y;
inputView.parent.style.height = inputContainer.layout.height;
}
void UpdatePortInputVisibilities()
{
if (expanded)
{
m_PortInputContainer.style.display = StyleKeyword.Null;
}
else
{
m_PortInputContainer.style.display = DisplayStyle.None;
}
}
public void UpdatePortInputTypes()
{
foreach (var anchor in inputContainer.Children().Concat(outputContainer.Children()).OfType<ShaderPort>())
{
var slot = anchor.slot;
anchor.portName = slot.displayName;
anchor.visualClass = slot.concreteValueType.ToClassName();
}
foreach (var portInputView in m_PortInputContainer.Children().OfType<PortInputView>())
portInputView.UpdateSlotType();
foreach (var control in m_ControlItems.Children())
{
var listener = control as AbstractMaterialNodeModificationListener;
if (listener != null)
listener.OnNodeModified(ModificationScope.Graph);
}
}
void OnResize(Vector2 deltaSize)
{
var updatedWidth = topContainer.layout.width + deltaSize.x;
var updatedHeight = m_PreviewImage.layout.height + deltaSize.y;
var previewNode = node as PreviewNode;
if (previewNode != null)
{
previewNode.SetDimensions(updatedWidth, updatedHeight);
UpdateSize();
}
}
void OnMouseHover(EventBase evt)
{
var graphView = GetFirstAncestorOfType<GraphEditorView>();
if (graphView == null)
return;
var blackboardProvider = graphView.blackboardProvider;
if (blackboardProvider == null)
return;
// Keyword nodes should be highlighted when Blackboard entry is hovered
// TODO: Move to new NodeView type when keyword node has unique style
if(node is KeywordNode keywordNode)
{
var keywordRow = blackboardProvider.GetBlackboardRow(keywordNode.keywordGuid);
if (keywordRow != null)
{
if (evt.eventTypeId == MouseEnterEvent.TypeId())
{
keywordRow.AddToClassList("hovered");
}
else
{
keywordRow.RemoveFromClassList("hovered");
}
}
}
}
void UpdatePreviewTexture()
{
if (m_PreviewRenderData.texture == null || !node.previewExpanded)
{
m_PreviewImage.visible = false;
m_PreviewImage.image = Texture2D.blackTexture;
}
else
{
m_PreviewImage.visible = true;
m_PreviewImage.AddToClassList("visible");
m_PreviewImage.RemoveFromClassList("hidden");
if (m_PreviewImage.image != m_PreviewRenderData.texture)
m_PreviewImage.image = m_PreviewRenderData.texture;
else
m_PreviewImage.MarkDirtyRepaint();
if (m_PreviewRenderData.shaderData.isCompiling)
m_PreviewImage.tintColor = new Color(1.0f, 1.0f, 1.0f, 0.3f);
else
m_PreviewImage.tintColor = Color.white;
}
}
void UpdateSize()
{
var previewNode = node as PreviewNode;
if (previewNode == null)
return;
var width = previewNode.width;
var height = previewNode.height;
m_PreviewImage.style.height = height;
m_PreviewImage.style.width = width;
}
public void Dispose()
{
foreach (var portInputView in m_PortInputContainer.Children().OfType<PortInputView>())
portInputView.Dispose();
node = null;
((VisualElement)this).userData = null;
if (m_PreviewRenderData != null)
{
m_PreviewRenderData.onPreviewChanged -= UpdatePreviewTexture;
m_PreviewRenderData = null;
}
}
}
}