589 lines
23 KiB
C#
589 lines
23 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using JetBrains.Annotations;
|
|
using UnityEngine;
|
|
using UnityEditor.Graphing;
|
|
using UnityEditor.ShaderGraph.Internal;
|
|
|
|
namespace UnityEditor.ShaderGraph
|
|
{
|
|
abstract class CodeFunctionNode : AbstractMaterialNode
|
|
, IGeneratesBodyCode
|
|
, IGeneratesFunction
|
|
, IMayRequireNormal
|
|
, IMayRequireTangent
|
|
, IMayRequireBitangent
|
|
, IMayRequireMeshUV
|
|
, IMayRequireScreenPosition
|
|
, IMayRequireViewDirection
|
|
, IMayRequirePosition
|
|
, IMayRequirePositionPredisplacement
|
|
, IMayRequireVertexColor
|
|
{
|
|
[NonSerialized]
|
|
private List<SlotAttribute> m_Slots = new List<SlotAttribute>();
|
|
|
|
public override bool hasPreview
|
|
{
|
|
get { return true; }
|
|
}
|
|
|
|
protected CodeFunctionNode()
|
|
{
|
|
UpdateNodeAfterDeserialization();
|
|
}
|
|
|
|
protected struct Boolean
|
|
{ }
|
|
|
|
protected struct Vector1
|
|
{ }
|
|
|
|
protected struct Texture2D
|
|
{ }
|
|
|
|
protected struct Texture2DArray
|
|
{ }
|
|
|
|
protected struct Texture3D
|
|
{ }
|
|
|
|
protected struct SamplerState
|
|
{ }
|
|
|
|
protected struct Gradient
|
|
{ }
|
|
|
|
protected struct DynamicDimensionVector
|
|
{ }
|
|
|
|
protected struct ColorRGBA
|
|
{ }
|
|
|
|
protected struct ColorRGB
|
|
{ }
|
|
|
|
protected struct Matrix3x3
|
|
{ }
|
|
|
|
protected struct Matrix2x2
|
|
{ }
|
|
|
|
protected struct DynamicDimensionMatrix
|
|
{ }
|
|
|
|
protected struct PropertyConnectionState
|
|
{ }
|
|
|
|
protected enum Binding
|
|
{
|
|
None,
|
|
ObjectSpaceNormal,
|
|
ObjectSpaceTangent,
|
|
ObjectSpaceBitangent,
|
|
ObjectSpacePosition,
|
|
ViewSpaceNormal,
|
|
ViewSpaceTangent,
|
|
ViewSpaceBitangent,
|
|
ViewSpacePosition,
|
|
WorldSpaceNormal,
|
|
WorldSpaceTangent,
|
|
WorldSpaceBitangent,
|
|
WorldSpacePosition,
|
|
TangentSpaceNormal,
|
|
TangentSpaceTangent,
|
|
TangentSpaceBitangent,
|
|
TangentSpacePosition,
|
|
MeshUV0,
|
|
MeshUV1,
|
|
MeshUV2,
|
|
MeshUV3,
|
|
ScreenPosition,
|
|
ObjectSpaceViewDirection,
|
|
ViewSpaceViewDirection,
|
|
WorldSpaceViewDirection,
|
|
TangentSpaceViewDirection,
|
|
VertexColor,
|
|
}
|
|
|
|
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
|
|
protected class SlotAttribute : Attribute
|
|
{
|
|
public int slotId { get; private set; }
|
|
public Binding binding { get; private set; }
|
|
public bool hidden { get; private set; }
|
|
public Vector4? defaultValue { get; private set; }
|
|
public ShaderStageCapability stageCapability { get; private set; }
|
|
|
|
public SlotAttribute(int mSlotId, Binding mImplicitBinding, ShaderStageCapability mStageCapability = ShaderStageCapability.All)
|
|
{
|
|
slotId = mSlotId;
|
|
binding = mImplicitBinding;
|
|
defaultValue = null;
|
|
stageCapability = mStageCapability;
|
|
}
|
|
|
|
public SlotAttribute(int mSlotId, Binding mImplicitBinding, bool mHidden, ShaderStageCapability mStageCapability = ShaderStageCapability.All)
|
|
{
|
|
slotId = mSlotId;
|
|
binding = mImplicitBinding;
|
|
hidden = mHidden;
|
|
defaultValue = null;
|
|
stageCapability = mStageCapability;
|
|
}
|
|
|
|
public SlotAttribute(int mSlotId, Binding mImplicitBinding, float defaultX, float defaultY, float defaultZ, float defaultW, ShaderStageCapability mStageCapability = ShaderStageCapability.All)
|
|
{
|
|
slotId = mSlotId;
|
|
binding = mImplicitBinding;
|
|
defaultValue = new Vector4(defaultX, defaultY, defaultZ, defaultW);
|
|
stageCapability = mStageCapability;
|
|
}
|
|
}
|
|
|
|
protected abstract MethodInfo GetFunctionToConvert();
|
|
|
|
private static SlotValueType ConvertTypeToSlotValueType(ParameterInfo p)
|
|
{
|
|
Type t = p.ParameterType;
|
|
if (p.ParameterType.IsByRef)
|
|
t = p.ParameterType.GetElementType();
|
|
|
|
if (t == typeof(Boolean))
|
|
{
|
|
return SlotValueType.Boolean;
|
|
}
|
|
if (t == typeof(Vector1))
|
|
{
|
|
return SlotValueType.Vector1;
|
|
}
|
|
if (t == typeof(Vector2))
|
|
{
|
|
return SlotValueType.Vector2;
|
|
}
|
|
if (t == typeof(Vector3))
|
|
{
|
|
return SlotValueType.Vector3;
|
|
}
|
|
if (t == typeof(Vector4))
|
|
{
|
|
return SlotValueType.Vector4;
|
|
}
|
|
if (t == typeof(Color))
|
|
{
|
|
return SlotValueType.Vector4;
|
|
}
|
|
if (t == typeof(ColorRGBA))
|
|
{
|
|
return SlotValueType.Vector4;
|
|
}
|
|
if (t == typeof(ColorRGB))
|
|
{
|
|
return SlotValueType.Vector3;
|
|
}
|
|
if (t == typeof(Texture2D))
|
|
{
|
|
return SlotValueType.Texture2D;
|
|
}
|
|
if (t == typeof(Texture2DArray))
|
|
{
|
|
return SlotValueType.Texture2DArray;
|
|
}
|
|
if (t == typeof(Texture3D))
|
|
{
|
|
return SlotValueType.Texture3D;
|
|
}
|
|
if (t == typeof(Cubemap))
|
|
{
|
|
return SlotValueType.Cubemap;
|
|
}
|
|
if (t == typeof(Gradient))
|
|
{
|
|
return SlotValueType.Gradient;
|
|
}
|
|
if (t == typeof(SamplerState))
|
|
{
|
|
return SlotValueType.SamplerState;
|
|
}
|
|
if (t == typeof(DynamicDimensionVector))
|
|
{
|
|
return SlotValueType.DynamicVector;
|
|
}
|
|
if (t == typeof(Matrix4x4))
|
|
{
|
|
return SlotValueType.Matrix4;
|
|
}
|
|
if (t == typeof(Matrix3x3))
|
|
{
|
|
return SlotValueType.Matrix3;
|
|
}
|
|
if (t == typeof(Matrix2x2))
|
|
{
|
|
return SlotValueType.Matrix2;
|
|
}
|
|
if (t == typeof(DynamicDimensionMatrix))
|
|
{
|
|
return SlotValueType.DynamicMatrix;
|
|
}
|
|
if (t == typeof(PropertyConnectionState))
|
|
{
|
|
return SlotValueType.PropertyConnectionState;
|
|
}
|
|
|
|
throw new ArgumentException("Unsupported type " + t);
|
|
}
|
|
|
|
public sealed override void UpdateNodeAfterDeserialization()
|
|
{
|
|
var method = GetFunctionToConvert();
|
|
|
|
if (method == null)
|
|
throw new ArgumentException("Mapped method is null on node" + this);
|
|
|
|
if (method.ReturnType != typeof(string))
|
|
throw new ArgumentException("Mapped function should return string");
|
|
|
|
// validate no duplicates
|
|
var slotAtributes = method.GetParameters().Select(GetSlotAttribute).ToList();
|
|
if (slotAtributes.Any(x => x == null))
|
|
throw new ArgumentException("Missing SlotAttribute on " + method.Name);
|
|
|
|
if (slotAtributes.GroupBy(x => x.slotId).Any(x => x.Count() > 1))
|
|
throw new ArgumentException("Duplicate SlotAttribute on " + method.Name);
|
|
|
|
List<MaterialSlot> slots = new List<MaterialSlot>();
|
|
foreach (var par in method.GetParameters())
|
|
{
|
|
var attribute = GetSlotAttribute(par);
|
|
var name = GraphUtil.ConvertCamelCase(par.Name, true);
|
|
|
|
MaterialSlot s;
|
|
if (attribute.binding == Binding.None && !par.IsOut && par.ParameterType == typeof(Color))
|
|
s = new ColorRGBAMaterialSlot(attribute.slotId, name, par.Name, SlotType.Input, attribute.defaultValue ?? Vector4.zero, stageCapability: attribute.stageCapability, hidden: attribute.hidden);
|
|
else if (attribute.binding == Binding.None && !par.IsOut && par.ParameterType == typeof(ColorRGBA))
|
|
s = new ColorRGBAMaterialSlot(attribute.slotId, name, par.Name, SlotType.Input, attribute.defaultValue ?? Vector4.zero, stageCapability: attribute.stageCapability, hidden: attribute.hidden);
|
|
else if (attribute.binding == Binding.None && !par.IsOut && par.ParameterType == typeof(ColorRGB))
|
|
s = new ColorRGBMaterialSlot(attribute.slotId, name, par.Name, SlotType.Input, attribute.defaultValue ?? Vector4.zero, ColorMode.Default, stageCapability: attribute.stageCapability, hidden: attribute.hidden);
|
|
else if (attribute.binding == Binding.None || par.IsOut)
|
|
s = MaterialSlot.CreateMaterialSlot(
|
|
ConvertTypeToSlotValueType(par),
|
|
attribute.slotId,
|
|
name,
|
|
par.Name,
|
|
par.IsOut ? SlotType.Output : SlotType.Input,
|
|
attribute.defaultValue ?? Vector4.zero,
|
|
shaderStageCapability: attribute.stageCapability,
|
|
hidden: attribute.hidden);
|
|
else
|
|
s = CreateBoundSlot(attribute.binding, attribute.slotId, name, par.Name, attribute.stageCapability, attribute.hidden);
|
|
slots.Add(s);
|
|
|
|
m_Slots.Add(attribute);
|
|
}
|
|
foreach (var slot in slots)
|
|
{
|
|
AddSlot(slot);
|
|
}
|
|
RemoveSlotsNameNotMatching(slots.Select(x => x.id), true);
|
|
}
|
|
|
|
private static MaterialSlot CreateBoundSlot(Binding attributeBinding, int slotId, string displayName, string shaderOutputName, ShaderStageCapability shaderStageCapability, bool hidden = false)
|
|
{
|
|
switch (attributeBinding)
|
|
{
|
|
case Binding.ObjectSpaceNormal:
|
|
return new NormalMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Object, shaderStageCapability, hidden);
|
|
case Binding.ObjectSpaceTangent:
|
|
return new TangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Object, shaderStageCapability, hidden);
|
|
case Binding.ObjectSpaceBitangent:
|
|
return new BitangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Object, shaderStageCapability, hidden);
|
|
case Binding.ObjectSpacePosition:
|
|
return new PositionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Object, shaderStageCapability, hidden);
|
|
case Binding.ViewSpaceNormal:
|
|
return new NormalMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.View, shaderStageCapability, hidden);
|
|
case Binding.ViewSpaceTangent:
|
|
return new TangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.View, shaderStageCapability, hidden);
|
|
case Binding.ViewSpaceBitangent:
|
|
return new BitangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.View, shaderStageCapability, hidden);
|
|
case Binding.ViewSpacePosition:
|
|
return new PositionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.View, shaderStageCapability, hidden);
|
|
case Binding.WorldSpaceNormal:
|
|
return new NormalMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.World, shaderStageCapability, hidden);
|
|
case Binding.WorldSpaceTangent:
|
|
return new TangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.World, shaderStageCapability, hidden);
|
|
case Binding.WorldSpaceBitangent:
|
|
return new BitangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.World, shaderStageCapability, hidden);
|
|
case Binding.WorldSpacePosition:
|
|
return new PositionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.World, shaderStageCapability, hidden);
|
|
case Binding.TangentSpaceNormal:
|
|
return new NormalMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Tangent, shaderStageCapability, hidden);
|
|
case Binding.TangentSpaceTangent:
|
|
return new TangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Tangent, shaderStageCapability, hidden);
|
|
case Binding.TangentSpaceBitangent:
|
|
return new BitangentMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Tangent, shaderStageCapability, hidden);
|
|
case Binding.TangentSpacePosition:
|
|
return new PositionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Tangent, shaderStageCapability, hidden);
|
|
case Binding.MeshUV0:
|
|
return new UVMaterialSlot(slotId, displayName, shaderOutputName, UVChannel.UV0, shaderStageCapability, hidden);
|
|
case Binding.MeshUV1:
|
|
return new UVMaterialSlot(slotId, displayName, shaderOutputName, UVChannel.UV1, shaderStageCapability, hidden);
|
|
case Binding.MeshUV2:
|
|
return new UVMaterialSlot(slotId, displayName, shaderOutputName, UVChannel.UV2, shaderStageCapability, hidden);
|
|
case Binding.MeshUV3:
|
|
return new UVMaterialSlot(slotId, displayName, shaderOutputName, UVChannel.UV3, shaderStageCapability, hidden);
|
|
case Binding.ScreenPosition:
|
|
return new ScreenPositionMaterialSlot(slotId, displayName, shaderOutputName, ScreenSpaceType.Default, shaderStageCapability, hidden);
|
|
case Binding.ObjectSpaceViewDirection:
|
|
return new ViewDirectionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Object, shaderStageCapability, hidden);
|
|
case Binding.ViewSpaceViewDirection:
|
|
return new ViewDirectionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.View, shaderStageCapability, hidden);
|
|
case Binding.WorldSpaceViewDirection:
|
|
return new ViewDirectionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.World, shaderStageCapability, hidden);
|
|
case Binding.TangentSpaceViewDirection:
|
|
return new ViewDirectionMaterialSlot(slotId, displayName, shaderOutputName, CoordinateSpace.Tangent, shaderStageCapability, hidden);
|
|
case Binding.VertexColor:
|
|
return new VertexColorMaterialSlot(slotId, displayName, shaderOutputName, shaderStageCapability, hidden);
|
|
default:
|
|
throw new ArgumentOutOfRangeException("attributeBinding", attributeBinding, null);
|
|
}
|
|
}
|
|
|
|
public void GenerateNodeCode(ShaderStringBuilder sb, GenerationMode generationMode)
|
|
{
|
|
using (var tempSlots = PooledList<MaterialSlot>.Get())
|
|
{
|
|
GetOutputSlots(tempSlots);
|
|
foreach (var outSlot in tempSlots)
|
|
{
|
|
sb.AppendLine(outSlot.concreteValueType.ToShaderString(PrecisionUtil.Token) + " " + GetVariableNameForSlot(outSlot.id) + ";");
|
|
}
|
|
|
|
string call = GetFunctionName() + "(";
|
|
bool first = true;
|
|
tempSlots.Clear();
|
|
GetSlots(tempSlots);
|
|
tempSlots.Sort((slot1, slot2) => slot1.id.CompareTo(slot2.id));
|
|
foreach (var slot in tempSlots)
|
|
{
|
|
if (!first)
|
|
{
|
|
call += ", ";
|
|
}
|
|
first = false;
|
|
|
|
if (slot.isInputSlot)
|
|
call += GetSlotValue(slot.id, generationMode);
|
|
else
|
|
call += GetVariableNameForSlot(slot.id);
|
|
}
|
|
call += ");";
|
|
|
|
sb.AppendLine(call);
|
|
}
|
|
}
|
|
|
|
private string GetFunctionName()
|
|
{
|
|
var function = GetFunctionToConvert();
|
|
return function.Name + (function.IsStatic ? string.Empty : "_" + objectId) + "_$precision"
|
|
+ (this.GetSlots<DynamicVectorMaterialSlot>().Select(s => NodeUtils.GetSlotDimension(s.concreteValueType)).FirstOrDefault() ?? "")
|
|
+ (this.GetSlots<DynamicMatrixMaterialSlot>().Select(s => NodeUtils.GetSlotDimension(s.concreteValueType)).FirstOrDefault() ?? "");
|
|
}
|
|
|
|
private string GetFunctionHeader()
|
|
{
|
|
string header = "void " + GetFunctionName() + "(";
|
|
|
|
using (var tempSlots = PooledList<MaterialSlot>.Get())
|
|
{
|
|
GetSlots(tempSlots);
|
|
tempSlots.Sort((slot1, slot2) => slot1.id.CompareTo(slot2.id));
|
|
var first = true;
|
|
foreach (var slot in tempSlots)
|
|
{
|
|
if (!first)
|
|
header += ", ";
|
|
|
|
first = false;
|
|
|
|
if (slot.isOutputSlot)
|
|
header += "out ";
|
|
|
|
// always use generic precisions for parameters, they will get concretized by the system
|
|
header += slot.concreteValueType.ToShaderString(PrecisionUtil.Token) + " " + slot.shaderOutputName;
|
|
}
|
|
|
|
header += ")";
|
|
}
|
|
|
|
return header;
|
|
}
|
|
|
|
private static object GetDefault(Type type)
|
|
{
|
|
return type.IsValueType ? Activator.CreateInstance(type) : null;
|
|
}
|
|
|
|
private string GetFunctionBody(MethodInfo info)
|
|
{
|
|
var args = new List<object>();
|
|
foreach (var param in info.GetParameters())
|
|
args.Add(GetDefault(param.ParameterType));
|
|
|
|
var result = info.Invoke(this, args.ToArray()) as string;
|
|
|
|
if (string.IsNullOrEmpty(result))
|
|
return string.Empty;
|
|
|
|
using (var tempSlots = PooledList<MaterialSlot>.Get())
|
|
{
|
|
GetSlots(tempSlots);
|
|
foreach (var slot in tempSlots)
|
|
{
|
|
var toReplace = string.Format("{{slot{0}dimension}}", slot.id);
|
|
var replacement = NodeUtils.GetSlotDimension(slot.concreteValueType);
|
|
result = result.Replace(toReplace, replacement);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public virtual void GenerateNodeFunction(FunctionRegistry registry, GenerationMode generationMode)
|
|
{
|
|
registry.ProvideFunction(GetFunctionName(), s =>
|
|
{
|
|
s.AppendLine(GetFunctionHeader());
|
|
var functionBody = GetFunctionBody(GetFunctionToConvert());
|
|
var lines = functionBody.Trim('\r', '\n', '\t', ' ');
|
|
s.AppendLines(lines);
|
|
});
|
|
}
|
|
|
|
private static SlotAttribute GetSlotAttribute([NotNull] ParameterInfo info)
|
|
{
|
|
var attrs = info.GetCustomAttributes(typeof(SlotAttribute), false).OfType<SlotAttribute>().ToList();
|
|
return attrs.FirstOrDefault();
|
|
}
|
|
|
|
public NeededCoordinateSpace RequiresNormal(ShaderStageCapability stageCapability)
|
|
{
|
|
var binding = NeededCoordinateSpace.None;
|
|
using (var tempSlots = PooledList<MaterialSlot>.Get())
|
|
{
|
|
GetInputSlots(tempSlots);
|
|
foreach (var slot in tempSlots)
|
|
binding |= slot.RequiresNormal();
|
|
return binding;
|
|
}
|
|
}
|
|
|
|
public NeededCoordinateSpace RequiresViewDirection(ShaderStageCapability stageCapability)
|
|
{
|
|
var binding = NeededCoordinateSpace.None;
|
|
using (var tempSlots = PooledList<MaterialSlot>.Get())
|
|
{
|
|
GetInputSlots(tempSlots);
|
|
foreach (var slot in tempSlots)
|
|
binding |= slot.RequiresViewDirection();
|
|
return binding;
|
|
}
|
|
}
|
|
|
|
public NeededCoordinateSpace RequiresPosition(ShaderStageCapability stageCapability)
|
|
{
|
|
using (var tempSlots = PooledList<MaterialSlot>.Get())
|
|
{
|
|
GetInputSlots(tempSlots);
|
|
var binding = NeededCoordinateSpace.None;
|
|
foreach (var slot in tempSlots)
|
|
binding |= slot.RequiresPosition();
|
|
return binding;
|
|
}
|
|
}
|
|
|
|
public NeededCoordinateSpace RequiresPositionPredisplacement(ShaderStageCapability stageCapability)
|
|
{
|
|
using (var tempSlots = PooledList<MaterialSlot>.Get())
|
|
{
|
|
GetInputSlots(tempSlots);
|
|
var binding = NeededCoordinateSpace.None;
|
|
foreach (var slot in tempSlots)
|
|
binding |= slot.RequiresPositionPredisplacement();
|
|
return binding;
|
|
}
|
|
}
|
|
|
|
public NeededCoordinateSpace RequiresTangent(ShaderStageCapability stageCapability)
|
|
{
|
|
using (var tempSlots = PooledList<MaterialSlot>.Get())
|
|
{
|
|
GetInputSlots(tempSlots);
|
|
var binding = NeededCoordinateSpace.None;
|
|
foreach (var slot in tempSlots)
|
|
binding |= slot.RequiresTangent();
|
|
return binding;
|
|
}
|
|
}
|
|
|
|
public NeededCoordinateSpace RequiresBitangent(ShaderStageCapability stageCapability)
|
|
{
|
|
using (var tempSlots = PooledList<MaterialSlot>.Get())
|
|
{
|
|
GetInputSlots(tempSlots);
|
|
var binding = NeededCoordinateSpace.None;
|
|
foreach (var slot in tempSlots)
|
|
binding |= slot.RequiresBitangent();
|
|
return binding;
|
|
}
|
|
}
|
|
|
|
public bool RequiresMeshUV(UVChannel channel, ShaderStageCapability stageCapability)
|
|
{
|
|
using (var tempSlots = PooledList<MaterialSlot>.Get())
|
|
{
|
|
GetInputSlots(tempSlots);
|
|
foreach (var slot in tempSlots)
|
|
{
|
|
if (slot.RequiresMeshUV(channel))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public bool RequiresScreenPosition(ShaderStageCapability stageCapability)
|
|
{
|
|
using (var tempSlots = PooledList<MaterialSlot>.Get())
|
|
{
|
|
GetInputSlots(tempSlots);
|
|
foreach (var slot in tempSlots)
|
|
{
|
|
if (slot.RequiresScreenPosition(stageCapability))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public bool RequiresVertexColor(ShaderStageCapability stageCapability)
|
|
{
|
|
using (var tempSlots = PooledList<MaterialSlot>.Get())
|
|
{
|
|
GetInputSlots(tempSlots);
|
|
foreach (var slot in tempSlots)
|
|
{
|
|
if (slot.RequiresVertexColor())
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|