Add Unity Pipeline package support
This commit is contained in:
+405
@@ -0,0 +1,405 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Unity.Pipeline.Commands;
|
||||
using Unity.Pipeline.Editor.Authoring;
|
||||
using Unity.Pipeline.Models;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Unity.Pipeline.Editor.Commands.Materials
|
||||
{
|
||||
/// <summary>
|
||||
/// Materials authoring commands (CLI-213): read and write a material's shader, shader properties
|
||||
/// (floats, colors, vectors, textures, ints), keywords, and render queue.
|
||||
///
|
||||
/// These sit on the CLI-190 authoring foundation:
|
||||
/// - the material is addressed with an <see cref="ObjectRef"/> resolved by <see cref="ObjectResolver"/>
|
||||
/// and confined to the authoring root (a GUID/globalId handle can't edit a material outside the sandbox),
|
||||
/// - texture property values are <see cref="ObjectRef"/>s likewise confined to the root,
|
||||
/// - writes are wrapped in an <see cref="AuthoringUndoScope"/> with <see cref="Undo.RecordObject"/>
|
||||
/// (material property changes ARE covered by Undo.RecordObject, unlike AssetDatabase ops), then
|
||||
/// <see cref="EditorUtility.SetDirty"/> + <see cref="AssetDatabase.SaveAssetIfDirty"/> persist them,
|
||||
/// - unknown property names and type mismatches are reported in <c>unknown[]</c> rather than failing
|
||||
/// the whole call (as long as at least one input applied).
|
||||
///
|
||||
/// Material CREATION already exists in <c>create_asset</c> (CLI-221); this command set is the
|
||||
/// read/write surface on top of an existing material.
|
||||
/// </summary>
|
||||
public static class MaterialCommands
|
||||
{
|
||||
[CliCommand("get_material_properties",
|
||||
"Read a material's shader, render queue, enabled keywords, and all shader properties with their current values (Color as [r,g,b,a], Vector as [x,y,z,w], Texture as an object reference).")]
|
||||
public static MaterialPropertiesResult GetMaterialProperties(
|
||||
[CliArg("material", "Reference to the .mat asset (or a loaded material) to read (path / guid / globalId / instanceId).", Required = true)] ObjectRef material)
|
||||
{
|
||||
var mat = ResolveMaterial(material, out var assetPath);
|
||||
|
||||
var result = new MaterialPropertiesResult
|
||||
{
|
||||
AssetPath = assetPath,
|
||||
Shader = mat.shader != null ? mat.shader.name : null,
|
||||
// rawRenderQueue is -1 when the material inherits from the shader and a positive
|
||||
// integer when explicitly overridden — returning the raw value preserves the
|
||||
// round-trip contract with set_material_properties renderQueue:-1 (inherit).
|
||||
RenderQueue = GetRawRenderQueue(mat),
|
||||
EnabledKeywords = GetEnabledKeywords(mat),
|
||||
};
|
||||
|
||||
var shader = mat.shader;
|
||||
if (shader != null)
|
||||
{
|
||||
// Enumerate the shader's declared properties via the Shader instance APIs (single index
|
||||
// space, matching get_shader_properties), so name/type/range-limits always line up.
|
||||
int count = shader.GetPropertyCount();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var name = shader.GetPropertyName(i);
|
||||
var propType = shader.GetPropertyType(i);
|
||||
|
||||
var entry = new MaterialPropertyValue
|
||||
{
|
||||
Name = name,
|
||||
Type = PublicValueType(propType),
|
||||
Value = MaterialValueConverter.ReadValue(mat, name, propType),
|
||||
};
|
||||
|
||||
if (propType == ShaderPropertyType.Range)
|
||||
{
|
||||
var limits = shader.GetPropertyRangeLimits(i);
|
||||
entry.Range = new MaterialRange
|
||||
{
|
||||
Min = limits.x,
|
||||
Max = limits.y,
|
||||
};
|
||||
}
|
||||
|
||||
result.Properties.Add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[CliCommand("set_material_properties",
|
||||
"Set shader properties on a material (Float/Range/Int=number; Color=[r,g,b,a] or \"#RRGGBBAA\" hex; Vector=[x,y,z,w]; Texture=an object reference or null to clear), optionally reassign the shader, set the render queue, and toggle keywords. Unknown names / type mismatches are reported in unknown[].")]
|
||||
public static SetMaterialPropertiesResult SetMaterialProperties(
|
||||
[CliArg("material", "Reference to the .mat asset (or a loaded material) to edit (path / guid / globalId / instanceId).", Required = true)] ObjectRef material,
|
||||
[CliArg("shader", "Reassign the material's shader by name (e.g. \"Standard\", \"Universal Render Pipeline/Lit\", or a Shader Graph shader name). Applied before properties so new property names resolve against the new shader.")] string shader = null,
|
||||
[CliArg("properties", "JSON object of shader property name -> value. Names must include the leading underscore (e.g. _BaseColor). Float/Range/Int=number; Color=[r,g,b,a] or hex string; Vector=[x,y,z,w]; Texture=an object reference {guid/path} or null.")] JObject properties = null,
|
||||
[CliArg("renderQueue", "Explicit render queue, or -1 to inherit from the shader. Omit to leave unchanged.")] int? renderQueue = null,
|
||||
[CliArg("enableKeywords", "Shader keywords to enable (e.g. _NORMALMAP, _EMISSION).")] string[] enableKeywords = null,
|
||||
[CliArg("disableKeywords", "Shader keywords to disable.")] string[] disableKeywords = null,
|
||||
[CliArg("confirm", "Reserved for parity; editing an existing material is non-destructive and undoable, so it is not required.")] bool confirm = false,
|
||||
[CliArg("dry_run", "If true, validate the shader, resolve property names and texture refs, and report applied[]/unknown[] without writing anything.")] bool dryRun = false)
|
||||
{
|
||||
var mat = ResolveMaterial(material, out var assetPath);
|
||||
|
||||
// Resolve the (optional) replacement shader UP FRONT so dry_run validates it too, and a
|
||||
// shader_not_found writes nothing (acceptance #9). Shader.Find only finds compiled/included
|
||||
// shaders — surface that in the hint.
|
||||
Shader newShader = null;
|
||||
if (shader != null)
|
||||
{
|
||||
newShader = Shader.Find(shader);
|
||||
if (newShader == null)
|
||||
throw new ShaderNotFoundException(
|
||||
$"Shader '{shader}' not found. Shader.Find only resolves shaders that are compiled/included in the build (or referenced by a material/scene). Use list_shaders to discover valid names.");
|
||||
}
|
||||
|
||||
var result = new SetMaterialPropertiesResult();
|
||||
|
||||
// ---- dry_run: validate against the would-be shader, never mutate ----------------------
|
||||
if (dryRun)
|
||||
{
|
||||
var effectiveShader = newShader ?? mat.shader;
|
||||
ValidateProperties(effectiveShader, properties, result);
|
||||
if (renderQueue.HasValue)
|
||||
result.Applied.Add("renderQueue");
|
||||
AddKeywordTokens(result.Applied, enableKeywords, disableKeywords);
|
||||
|
||||
GuardAtLeastOneApplied(result, shaderReassigned: newShader != null);
|
||||
|
||||
CopyIdentity(mat, assetPath, result);
|
||||
result.Shader = effectiveShader != null ? effectiveShader.name : null;
|
||||
return result;
|
||||
}
|
||||
|
||||
// ---- apply --------------------------------------------------------------------------
|
||||
using (new AuthoringUndoScope("Set Material Properties"))
|
||||
{
|
||||
// Material property/shader/keyword/renderQueue changes are all covered by Undo.RecordObject.
|
||||
Undo.RecordObject(mat, "Set Material Properties");
|
||||
|
||||
if (newShader != null)
|
||||
mat.shader = newShader;
|
||||
|
||||
if (properties != null)
|
||||
{
|
||||
foreach (var pair in properties)
|
||||
{
|
||||
if (pair.Key == "token") // injected by the server; never a material property.
|
||||
continue;
|
||||
|
||||
var name = pair.Key;
|
||||
if (!mat.HasProperty(name))
|
||||
{
|
||||
result.Unknown.Add($"{name}: unknown property (not declared by shader '{(mat.shader != null ? mat.shader.name : "<none>")}')");
|
||||
continue;
|
||||
}
|
||||
|
||||
var propType = GetPropertyType(mat.shader, name);
|
||||
if (propType == null)
|
||||
{
|
||||
// HasProperty was true but the type couldn't be read off the shader (e.g. a
|
||||
// built-in property like unity_Lightmaps not declared in the shader's list).
|
||||
result.Unknown.Add($"{name}: property is not a settable shader property on '{(mat.shader != null ? mat.shader.name : "<none>")}'");
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
MaterialValueConverter.ApplyValue(mat, name, propType.Value, pair.Value);
|
||||
result.Applied.Add(name);
|
||||
}
|
||||
catch (MaterialPropertyTypeMismatch mismatch)
|
||||
{
|
||||
result.Unknown.Add(mismatch.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (renderQueue.HasValue)
|
||||
{
|
||||
mat.renderQueue = renderQueue.Value;
|
||||
result.Applied.Add("renderQueue");
|
||||
}
|
||||
|
||||
ApplyKeywords(mat, enableKeywords, disableKeywords, result.Applied);
|
||||
|
||||
GuardAtLeastOneApplied(result, shaderReassigned: newShader != null);
|
||||
|
||||
EditorUtility.SetDirty(mat);
|
||||
if (!string.IsNullOrEmpty(assetPath))
|
||||
AssetDatabase.SaveAssetIfDirty(mat);
|
||||
}
|
||||
|
||||
CopyIdentity(mat, assetPath, result);
|
||||
result.Shader = mat.shader != null ? mat.shader.name : null;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int GetRawRenderQueue(Material mat)
|
||||
{
|
||||
var serialized = new SerializedObject(mat);
|
||||
var customRenderQueue = serialized.FindProperty("m_CustomRenderQueue");
|
||||
return customRenderQueue != null ? customRenderQueue.intValue : mat.renderQueue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve an <see cref="ObjectRef"/> to a <see cref="Material"/>, confining any on-disk asset to
|
||||
/// the authoring root. Returns the project-relative asset path (null for a non-asset/in-memory
|
||||
/// material). Throws a clear <see cref="ArgumentException"/> on miss / wrong type / out-of-root.
|
||||
/// </summary>
|
||||
internal static Material ResolveMaterial(ObjectRef material, out string assetPath)
|
||||
{
|
||||
if (material == null || material.IsEmpty)
|
||||
throw new ArgumentException("'material' is required.");
|
||||
|
||||
if (!ObjectResolver.TryResolve(material, out var obj, out var error))
|
||||
throw new ArgumentException($"Could not resolve material: {error}");
|
||||
|
||||
if (!(obj is Material mat))
|
||||
throw new ArgumentException($"Reference '{material}' resolved to a {obj.GetType().Name}, not a Material.");
|
||||
|
||||
assetPath = AssetDatabase.GetAssetPath(mat);
|
||||
if (!string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
var confined = ProjectPaths.Resolve(assetPath, out var confineError);
|
||||
if (confined == null)
|
||||
throw new ArgumentException(
|
||||
$"Material '{assetPath}' is outside the authoring root '{ProjectPaths.AuthoringRoot}': {confineError}");
|
||||
assetPath = confined;
|
||||
}
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the material's enabled keyword set. Uses <see cref="Material.enabledKeywords"/>
|
||||
/// (LocalKeyword[]) which is the supported API on the package's min Unity version (Unity 6 /
|
||||
/// 6000.0); the legacy <c>shaderKeywords</c> string[] is deprecated.
|
||||
/// </summary>
|
||||
private static List<string> GetEnabledKeywords(Material mat)
|
||||
{
|
||||
var keywords = new List<string>();
|
||||
foreach (var keyword in mat.enabledKeywords)
|
||||
keywords.Add(keyword.name);
|
||||
keywords.Sort(StringComparer.Ordinal);
|
||||
return keywords;
|
||||
}
|
||||
|
||||
private static void ApplyKeywords(Material mat, string[] enable, string[] disable, List<string> applied)
|
||||
{
|
||||
// Use the LocalKeyword API (Unity 2022.1+) so that Enable/Disable and the
|
||||
// enabledKeywords reader are talking to the same keyword table. The legacy
|
||||
// string-based EnableKeyword(string) affects global keywords, whereas
|
||||
// mat.enabledKeywords returns LocalKeyword[], so they would disagree on round-trip.
|
||||
if (enable != null)
|
||||
{
|
||||
foreach (var keyword in enable)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(keyword) || mat.shader == null)
|
||||
continue;
|
||||
mat.EnableKeyword(new LocalKeyword(mat.shader, keyword));
|
||||
applied.Add($"+keyword:{keyword}");
|
||||
}
|
||||
}
|
||||
|
||||
if (disable != null)
|
||||
{
|
||||
foreach (var keyword in disable)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(keyword) || mat.shader == null)
|
||||
continue;
|
||||
mat.DisableKeyword(new LocalKeyword(mat.shader, keyword));
|
||||
applied.Add($"-keyword:{keyword}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddKeywordTokens(List<string> applied, string[] enable, string[] disable)
|
||||
{
|
||||
if (enable != null)
|
||||
foreach (var keyword in enable.Where(k => !string.IsNullOrWhiteSpace(k)))
|
||||
applied.Add($"+keyword:{keyword}");
|
||||
if (disable != null)
|
||||
foreach (var keyword in disable.Where(k => !string.IsNullOrWhiteSpace(k)))
|
||||
applied.Add($"-keyword:{keyword}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// dry_run validation: classify each supplied property as applicable (name resolves AND value
|
||||
/// shape matches the declared type) or unknown, WITHOUT mutating the material.
|
||||
/// </summary>
|
||||
private static void ValidateProperties(Shader shader, JObject properties, SetMaterialPropertiesResult result)
|
||||
{
|
||||
if (properties == null)
|
||||
return;
|
||||
|
||||
foreach (var pair in properties)
|
||||
{
|
||||
if (pair.Key == "token")
|
||||
continue;
|
||||
|
||||
var name = pair.Key;
|
||||
var propType = shader != null ? GetPropertyType(shader, name) : null;
|
||||
if (propType == null)
|
||||
{
|
||||
result.Unknown.Add($"{name}: unknown property (not declared by shader '{(shader != null ? shader.name : "<none>")}')");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate the value shape against the type by attempting a conversion on a throwaway
|
||||
// material instance — never the caller's material (dry_run must leave no trace).
|
||||
Material probe = null;
|
||||
try
|
||||
{
|
||||
probe = new Material(shader);
|
||||
MaterialValueConverter.ApplyValue(probe, name, propType.Value, pair.Value);
|
||||
result.Applied.Add(name);
|
||||
}
|
||||
catch (MaterialPropertyTypeMismatch mismatch)
|
||||
{
|
||||
result.Unknown.Add(mismatch.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (probe != null)
|
||||
Object.DestroyImmediate(probe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Look up a property's <see cref="UnityEngine.Rendering.ShaderPropertyType"/> by name on a shader.
|
||||
/// Returns null when the shader has no property with that exact name (case-sensitive; names include
|
||||
/// the leading underscore).
|
||||
/// </summary>
|
||||
internal static ShaderPropertyType? GetPropertyType(Shader shader, string name)
|
||||
{
|
||||
if (shader == null)
|
||||
return null;
|
||||
int count = shader.GetPropertyCount();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (shader.GetPropertyName(i) == name)
|
||||
return shader.GetPropertyType(i);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void GuardAtLeastOneApplied(SetMaterialPropertiesResult result, bool shaderReassigned)
|
||||
{
|
||||
// A shader reassignment is itself a successful change, so it satisfies the "at least one
|
||||
// thing applied" guard even when every supplied property is unknown for the new shader.
|
||||
if (!shaderReassigned && result.Applied.Count == 0 && result.Unknown.Count > 0)
|
||||
throw new ArgumentException(
|
||||
$"None of the supplied inputs could be applied (unknown property name or type mismatch): {string.Join("; ", result.Unknown)}.");
|
||||
}
|
||||
|
||||
private static void CopyIdentity(Material mat, string assetPath, SetMaterialPropertiesResult result)
|
||||
{
|
||||
var identity = ObjectResolver.Describe(mat);
|
||||
if (identity != null)
|
||||
{
|
||||
result.GlobalId = identity.GlobalId;
|
||||
result.Guid = identity.Guid;
|
||||
result.FileId = identity.FileId;
|
||||
result.InstanceId = identity.InstanceId;
|
||||
result.HierarchyPath = identity.HierarchyPath;
|
||||
result.Type = identity.Type;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Type = nameof(Material);
|
||||
}
|
||||
|
||||
// Prefer the confined asset path computed by ResolveMaterial.
|
||||
if (!string.IsNullOrEmpty(assetPath))
|
||||
result.AssetPath = assetPath;
|
||||
}
|
||||
|
||||
/// <summary>Map a <see cref="UnityEngine.Rendering.ShaderPropertyType"/> to the value-shape label used by get_material_properties.</summary>
|
||||
private static string PublicValueType(ShaderPropertyType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ShaderPropertyType.Color: return "Color";
|
||||
case ShaderPropertyType.Vector: return "Vector";
|
||||
case ShaderPropertyType.Float: return "Float";
|
||||
case ShaderPropertyType.Range: return "Range";
|
||||
case ShaderPropertyType.Texture: return "Texture";
|
||||
case ShaderPropertyType.Int: return "Int";
|
||||
default: return type.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Thrown when a requested shader name does not resolve via <see cref="Shader.Find"/>. Carries the
|
||||
/// stable <c>shader_not_found</c> code (also prefixed onto the message, since the server propagates
|
||||
/// only the exception message over the wire) so a client can distinguish it from other failures.
|
||||
/// </summary>
|
||||
public sealed class ShaderNotFoundException : Exception
|
||||
{
|
||||
public const string CodeValue = "shader_not_found";
|
||||
|
||||
public string Code => CodeValue;
|
||||
|
||||
public ShaderNotFoundException(string message) : base($"[{CodeValue}] {message}") { }
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a14c4a97ac9e4d61b904de30d1016d92
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Unity.Pipeline.Models;
|
||||
|
||||
namespace Unity.Pipeline.Editor.Commands.Materials
|
||||
{
|
||||
/// <summary>
|
||||
/// Result of <c>get_material_properties</c> (CLI-213): the material's shader, render queue, enabled
|
||||
/// keywords, and the full list of shader properties with their current values.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class MaterialPropertiesResult
|
||||
{
|
||||
[JsonProperty("assetPath")]
|
||||
public string AssetPath { get; set; }
|
||||
|
||||
/// <summary>Shader name, e.g. "Universal Render Pipeline/Lit".</summary>
|
||||
[JsonProperty("shader")]
|
||||
public string Shader { get; set; }
|
||||
|
||||
/// <summary>Render queue; -1 means "inherit from shader".</summary>
|
||||
[JsonProperty("renderQueue")]
|
||||
public int RenderQueue { get; set; }
|
||||
|
||||
[JsonProperty("enabledKeywords")]
|
||||
public List<string> EnabledKeywords { get; set; } = new List<string>();
|
||||
|
||||
[JsonProperty("properties")]
|
||||
public List<MaterialPropertyValue> Properties { get; set; } = new List<MaterialPropertyValue>();
|
||||
}
|
||||
|
||||
/// <summary>A single shader property of a material with its current value.</summary>
|
||||
[Serializable]
|
||||
public class MaterialPropertyValue
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>"Float" | "Range" | "Color" | "Vector" | "Texture" | "Int".</summary>
|
||||
[JsonProperty("type")]
|
||||
public string Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// number | [r,g,b,a] | [x,y,z,w] | { texture: ObjectRef } | null, per the property type.
|
||||
/// </summary>
|
||||
[JsonProperty("value")]
|
||||
public object Value { get; set; }
|
||||
|
||||
/// <summary>Min/max for a Range property; null otherwise.</summary>
|
||||
[JsonProperty("range", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public MaterialRange Range { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>Inclusive min/max bounds of a Range shader property.</summary>
|
||||
[Serializable]
|
||||
public class MaterialRange
|
||||
{
|
||||
[JsonProperty("min")]
|
||||
public float Min { get; set; }
|
||||
|
||||
[JsonProperty("max")]
|
||||
public float Max { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of <c>set_material_properties</c> (CLI-213): the canonical <see cref="AuthoringResult"/>
|
||||
/// identity of the edited material, extended with which properties applied vs. were unknown (with a
|
||||
/// reason), and the material's (possibly reassigned) shader name.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class SetMaterialPropertiesResult : AuthoringResult
|
||||
{
|
||||
/// <summary>Shader name after any reassignment.</summary>
|
||||
[JsonProperty("shader")]
|
||||
public string Shader { get; set; }
|
||||
|
||||
/// <summary>Property names (and keyword/renderQueue tokens) that were applied.</summary>
|
||||
[JsonProperty("applied")]
|
||||
public List<string> Applied { get; set; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Inputs that could not be applied, each as "name: reason" (unknown property name, or a type
|
||||
/// mismatch like "_Metallic: expected Float, got array").
|
||||
/// </summary>
|
||||
[JsonProperty("unknown")]
|
||||
public List<string> Unknown { get; set; } = new List<string>();
|
||||
}
|
||||
|
||||
/// <summary>A single discovered shader entry from <c>list_shaders</c>.</summary>
|
||||
[Serializable]
|
||||
public class ShaderInfo
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>Project asset path for a project shader; null for a built-in/engine shader.</summary>
|
||||
[JsonProperty("assetPath")]
|
||||
public string AssetPath { get; set; }
|
||||
|
||||
[JsonProperty("isBuiltin")]
|
||||
public bool IsBuiltin { get; set; }
|
||||
|
||||
/// <summary>Reflects <c>Shader.isSupported</c> for the active render pipeline.</summary>
|
||||
[JsonProperty("isSupported")]
|
||||
public bool IsSupported { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>Result of <c>get_shader_properties</c> (CLI-213): a shader's declared property list.</summary>
|
||||
[Serializable]
|
||||
public class ShaderPropertiesResult
|
||||
{
|
||||
[JsonProperty("shader")]
|
||||
public string Shader { get; set; }
|
||||
|
||||
[JsonProperty("properties")]
|
||||
public List<ShaderPropertyInfo> Properties { get; set; } = new List<ShaderPropertyInfo>();
|
||||
}
|
||||
|
||||
/// <summary>A single declared shader property, introspected via <c>ShaderUtil</c>.</summary>
|
||||
[Serializable]
|
||||
public class ShaderPropertyInfo
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>ShaderUtil property description (the UI label).</summary>
|
||||
[JsonProperty("description")]
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>"Color" | "Vector" | "Float" | "Range" | "TexEnv" | "Int".</summary>
|
||||
[JsonProperty("type")]
|
||||
public string Type { get; set; }
|
||||
|
||||
/// <summary>Min/max for a Range property; null otherwise.</summary>
|
||||
[JsonProperty("range", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public MaterialRange Range { get; set; }
|
||||
|
||||
/// <summary>"Tex2D" | "Cube" | "Tex3D" | "Tex2DArray" for a TexEnv property; null otherwise.</summary>
|
||||
[JsonProperty("textureDimension", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string TextureDimension { get; set; }
|
||||
|
||||
/// <summary>Property flags, e.g. "HideInInspector", "Normal", "HDR", "NoScaleOffset".</summary>
|
||||
[JsonProperty("flags")]
|
||||
public List<string> Flags { get; set; } = new List<string>();
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52b9d1506e9b4a95982ff554d70179b3
|
||||
+310
@@ -0,0 +1,310 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Unity.Pipeline.Editor.Authoring;
|
||||
using Unity.Pipeline.Models;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Unity.Pipeline.Editor.Commands.Materials
|
||||
{
|
||||
/// <summary>
|
||||
/// Bridges agent-supplied JSON values and a <see cref="Material"/>'s shader properties for the
|
||||
/// get_material_properties / set_material_properties commands (CLI-213).
|
||||
///
|
||||
/// Value encoding by shader property type:
|
||||
/// - Float / Range / Int : a JSON number
|
||||
/// - Color : <c>[r, g, b, a]</c> (floats 0–1) or a <c>"#RRGGBB"</c>/<c>"#RRGGBBAA"</c> hex string
|
||||
/// - Vector : <c>[x, y, z, w]</c>
|
||||
/// - Texture (TexEnv) : an <see cref="ObjectRef"/> object (resolved + confined to the authoring
|
||||
/// root), or <c>null</c> to clear
|
||||
///
|
||||
/// Reads return the same shapes (Color as <c>[r,g,b,a]</c>, Vector as <c>[x,y,z,w]</c>, Texture as
|
||||
/// <c>{ texture: ObjectRef }</c> or null) so a read round-trips back through a write.
|
||||
/// </summary>
|
||||
internal static class MaterialValueConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Read a single shader property's current value off the material into a plain object suitable
|
||||
/// for JSON serialization. The shape matches the <see cref="ShaderPropertyType"/>.
|
||||
/// </summary>
|
||||
public static object ReadValue(Material material, string propertyName, ShaderPropertyType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ShaderPropertyType.Color:
|
||||
{
|
||||
var c = material.GetColor(propertyName);
|
||||
return new[] { c.r, c.g, c.b, c.a };
|
||||
}
|
||||
case ShaderPropertyType.Vector:
|
||||
{
|
||||
var v = material.GetVector(propertyName);
|
||||
return new[] { v.x, v.y, v.z, v.w };
|
||||
}
|
||||
case ShaderPropertyType.Float:
|
||||
case ShaderPropertyType.Range:
|
||||
return material.GetFloat(propertyName);
|
||||
case ShaderPropertyType.Int:
|
||||
return material.GetInteger(propertyName);
|
||||
case ShaderPropertyType.Texture:
|
||||
{
|
||||
var tex = material.GetTexture(propertyName);
|
||||
// A re-usable handle for the assigned texture, or null when no texture is bound.
|
||||
var handle = ObjectResolver.Describe(tex);
|
||||
return handle == null ? null : new { texture = handle };
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply a JSON value to a single shader property on the material, converting by the shader's
|
||||
/// declared property type. Throws <see cref="MaterialPropertyTypeMismatch"/> when the supplied
|
||||
/// JSON shape does not match the property's type (so the caller can record it in
|
||||
/// <c>unknown[]</c> with a reason). Texture refs are resolved via <see cref="ObjectResolver"/>
|
||||
/// and confined to the authoring root; an out-of-root or unresolved ref throws
|
||||
/// <see cref="ArgumentException"/> (a hard failure — never silently dropped).
|
||||
/// </summary>
|
||||
public static void ApplyValue(Material material, string propertyName, ShaderPropertyType type, JToken value)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ShaderPropertyType.Float:
|
||||
material.SetFloat(propertyName, ToFloat(propertyName, "Float", value));
|
||||
break;
|
||||
|
||||
case ShaderPropertyType.Range:
|
||||
material.SetFloat(propertyName, ToFloat(propertyName, "Range", value));
|
||||
break;
|
||||
|
||||
case ShaderPropertyType.Int:
|
||||
// Unity 6: Material.SetInteger is the typed setter for an Int shader property.
|
||||
material.SetInteger(propertyName, ToInt(propertyName, value));
|
||||
break;
|
||||
|
||||
case ShaderPropertyType.Color:
|
||||
material.SetColor(propertyName, ToColor(propertyName, value));
|
||||
break;
|
||||
|
||||
case ShaderPropertyType.Vector:
|
||||
material.SetVector(propertyName, ToVector4(propertyName, value));
|
||||
break;
|
||||
|
||||
case ShaderPropertyType.Texture:
|
||||
material.SetTexture(propertyName, ToTexture(propertyName, value));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new MaterialPropertyTypeMismatch(
|
||||
$"{propertyName}: unsupported shader property type '{type}'.");
|
||||
}
|
||||
}
|
||||
|
||||
private static float ToFloat(string property, string typeLabel, JToken value)
|
||||
{
|
||||
if (value == null || value.Type == JTokenType.Null)
|
||||
throw new MaterialPropertyTypeMismatch($"{property}: expected {typeLabel}, got null.");
|
||||
if (value.Type != JTokenType.Integer && value.Type != JTokenType.Float)
|
||||
throw new MaterialPropertyTypeMismatch(
|
||||
$"{property}: expected {typeLabel} (a number), got {Describe(value)}.");
|
||||
return value.ToObject<float>();
|
||||
}
|
||||
|
||||
private static int ToInt(string property, JToken value)
|
||||
{
|
||||
if (value == null || value.Type == JTokenType.Null)
|
||||
throw new MaterialPropertyTypeMismatch($"{property}: expected Int, got null.");
|
||||
if (value.Type != JTokenType.Integer && value.Type != JTokenType.Float)
|
||||
throw new MaterialPropertyTypeMismatch(
|
||||
$"{property}: expected Int (a number), got {Describe(value)}.");
|
||||
// Reject fractional JSON numbers (e.g. 1.5) rather than silently rounding/truncating.
|
||||
// An Int shader property must receive a whole number.
|
||||
var asDouble = value.ToObject<double>();
|
||||
if (asDouble != Math.Truncate(asDouble))
|
||||
throw new MaterialPropertyTypeMismatch(
|
||||
$"{property}: expected Int (a whole number), got the non-integer value {value}.");
|
||||
return value.ToObject<int>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Color from either <c>[r,g,b,a]</c> (3 or 4 floats) or a hex string
|
||||
/// (<c>"#RRGGBB"</c> / <c>"#RRGGBBAA"</c>, leading '#' optional). Default alpha is 1.0.
|
||||
/// </summary>
|
||||
private static Color ToColor(string property, JToken value)
|
||||
{
|
||||
if (value is JArray arr)
|
||||
{
|
||||
if (arr.Count != 3 && arr.Count != 4)
|
||||
throw new MaterialPropertyTypeMismatch(
|
||||
$"{property}: expected Color as [r,g,b,a] (or [r,g,b]), got an array of length {arr.Count}.");
|
||||
// Route each component through ToFloat so non-numeric/null elements surface as a clean
|
||||
// MaterialPropertyTypeMismatch rather than throwing and failing the whole command.
|
||||
float r = ToFloat(property, "Color", arr[0]);
|
||||
float g = ToFloat(property, "Color", arr[1]);
|
||||
float b = ToFloat(property, "Color", arr[2]);
|
||||
float a = arr.Count == 4 ? ToFloat(property, "Color", arr[3]) : 1f;
|
||||
return new Color(r, g, b, a);
|
||||
}
|
||||
|
||||
if (value != null && value.Type == JTokenType.String)
|
||||
{
|
||||
if (TryParseHexColor(value.ToObject<string>(), out var color))
|
||||
return color;
|
||||
throw new MaterialPropertyTypeMismatch(
|
||||
$"{property}: expected Color as [r,g,b,a] or a '#RRGGBB'/'#RRGGBBAA' hex string, got '{value}'.");
|
||||
}
|
||||
|
||||
throw new MaterialPropertyTypeMismatch(
|
||||
$"{property}: expected Color as [r,g,b,a] or a hex string, got {Describe(value)}.");
|
||||
}
|
||||
|
||||
private static Vector4 ToVector4(string property, JToken value)
|
||||
{
|
||||
if (!(value is JArray arr))
|
||||
throw new MaterialPropertyTypeMismatch(
|
||||
$"{property}: expected Vector as [x,y,z,w], got {Describe(value)}.");
|
||||
if (arr.Count == 0 || arr.Count > 4)
|
||||
throw new MaterialPropertyTypeMismatch(
|
||||
$"{property}: expected Vector as [x,y,z,w] (1–4 numbers), got an array of length {arr.Count}.");
|
||||
|
||||
// Missing trailing components default to 0 (matches Unity's Vector4 component default),
|
||||
// so [x,y,z] and [x,y] are accepted as shorthand for a 4-component vector.
|
||||
// Route each present component through ToFloat so non-numeric/null elements surface as a
|
||||
// clean MaterialPropertyTypeMismatch rather than throwing and failing the whole command.
|
||||
float x = arr.Count > 0 ? ToFloat(property, "Vector", arr[0]) : 0f;
|
||||
float y = arr.Count > 1 ? ToFloat(property, "Vector", arr[1]) : 0f;
|
||||
float z = arr.Count > 2 ? ToFloat(property, "Vector", arr[2]) : 0f;
|
||||
float w = arr.Count > 3 ? ToFloat(property, "Vector", arr[3]) : 0f;
|
||||
return new Vector4(x, y, z, w);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve a texture property value: an explicit <c>null</c> clears it, otherwise the value must
|
||||
/// be an <see cref="ObjectRef"/> that resolves to a <see cref="Texture"/> confined to the
|
||||
/// authoring root. A non-texture object, an out-of-root asset, or an unresolved handle is a hard
|
||||
/// failure (never silently dropped).
|
||||
/// </summary>
|
||||
private static Texture ToTexture(string property, JToken value)
|
||||
{
|
||||
if (value == null || value.Type == JTokenType.Null)
|
||||
return null;
|
||||
|
||||
// ReadValue returns textures as { "texture": <ObjectRef> } so a get->set round-trip works.
|
||||
// Accept both that wrapper form and a bare ObjectRef supplied directly by the caller.
|
||||
JToken refToken = value;
|
||||
if (value is JObject wrapper && wrapper.ContainsKey("texture"))
|
||||
refToken = wrapper["texture"];
|
||||
|
||||
ObjectRef handle;
|
||||
try
|
||||
{
|
||||
handle = refToken.ToObject<ObjectRef>();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new MaterialPropertyTypeMismatch(
|
||||
$"{property}: expected Texture as an object reference {{guid/path/...}} or null, got {Describe(value)}.");
|
||||
}
|
||||
|
||||
if (handle == null || handle.IsEmpty)
|
||||
throw new MaterialPropertyTypeMismatch(
|
||||
$"{property}: expected Texture as an object reference {{guid/path/...}} or null, got {Describe(value)}.");
|
||||
|
||||
if (!ObjectResolver.TryResolve(handle, out var obj, out var error))
|
||||
throw new ArgumentException($"{property}: could not resolve texture reference: {error}");
|
||||
|
||||
if (!(obj is Texture texture))
|
||||
throw new MaterialPropertyTypeMismatch(
|
||||
$"{property}: expected Texture, got a {obj.GetType().Name}.");
|
||||
|
||||
// Confine to the authoring root: a GUID/globalId/path handle must bind a texture that lives
|
||||
// on disk under the sandbox. An in-memory/scene Texture has an empty asset path; allowing it
|
||||
// would bypass the root confinement, so reject any ref that doesn't resolve to a persisted
|
||||
// asset. Mirrors AssetCommands.ResolveAssetPath's confinement.
|
||||
var assetPath = AssetDatabase.GetAssetPath(texture);
|
||||
if (string.IsNullOrEmpty(assetPath))
|
||||
throw new ArgumentException(
|
||||
$"{property}: texture reference does not resolve to an on-disk asset under the authoring root '{ProjectPaths.AuthoringRoot}'; in-memory or scene textures are not allowed.");
|
||||
|
||||
var confined = ProjectPaths.Resolve(assetPath, out var confineError);
|
||||
if (confined == null)
|
||||
throw new ArgumentException(
|
||||
$"{property}: texture '{assetPath}' is outside the authoring root '{ProjectPaths.AuthoringRoot}': {confineError}");
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a hex color string ("#RRGGBB", "#RRGGBBAA", or without the leading '#'). Returns false
|
||||
/// for any malformed input. Channels are 0–255 mapped to 0–1; default alpha is 1.0.
|
||||
/// </summary>
|
||||
public static bool TryParseHexColor(string hex, out Color color)
|
||||
{
|
||||
color = Color.white;
|
||||
if (string.IsNullOrWhiteSpace(hex))
|
||||
return false;
|
||||
|
||||
var s = hex.Trim();
|
||||
if (s.StartsWith("#", StringComparison.Ordinal))
|
||||
s = s.Substring(1);
|
||||
|
||||
if (s.Length != 6 && s.Length != 8)
|
||||
return false;
|
||||
|
||||
if (!byte.TryParse(s.Substring(0, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var r) ||
|
||||
!byte.TryParse(s.Substring(2, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var g) ||
|
||||
!byte.TryParse(s.Substring(4, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var b))
|
||||
return false;
|
||||
|
||||
byte a = 255;
|
||||
if (s.Length == 8 &&
|
||||
!byte.TryParse(s.Substring(6, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out a))
|
||||
return false;
|
||||
|
||||
color = new Color(r / 255f, g / 255f, b / 255f, a / 255f);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Map a <see cref="UnityEngine.Rendering.ShaderPropertyType"/> to the public type label used in results.</summary>
|
||||
public static string TypeLabel(ShaderPropertyType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ShaderPropertyType.Color: return "Color";
|
||||
case ShaderPropertyType.Vector: return "Vector";
|
||||
case ShaderPropertyType.Float: return "Float";
|
||||
case ShaderPropertyType.Range: return "Range";
|
||||
case ShaderPropertyType.Texture: return "TexEnv";
|
||||
case ShaderPropertyType.Int: return "Int";
|
||||
default: return type.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private static string Describe(JToken value)
|
||||
{
|
||||
if (value == null)
|
||||
return "null";
|
||||
switch (value.Type)
|
||||
{
|
||||
case JTokenType.Array: return "array";
|
||||
case JTokenType.Object: return "object";
|
||||
case JTokenType.Null: return "null";
|
||||
default: return value.Type.ToString().ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Thrown when a supplied value's JSON shape does not match the shader property's declared type.
|
||||
/// The command catches this and records the property in <c>unknown[]</c> with the message as the
|
||||
/// reason, rather than failing the whole call.
|
||||
/// </summary>
|
||||
internal sealed class MaterialPropertyTypeMismatch : Exception
|
||||
{
|
||||
public MaterialPropertyTypeMismatch(string message) : base(message) { }
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fc8956be3c264ccb8096787d7146f330
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Pipeline.Commands;
|
||||
using Unity.Pipeline.Editor.Authoring;
|
||||
using Unity.Pipeline.Models;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace Unity.Pipeline.Editor.Commands.Materials
|
||||
{
|
||||
/// <summary>
|
||||
/// Shader discovery / introspection commands (CLI-213): <c>list_shaders</c> so an agent can pick a
|
||||
/// valid shader name, and <c>get_shader_properties</c> so it knows a shader's settable property
|
||||
/// names / types / ranges / texture dimensions / flags before authoring a material.
|
||||
///
|
||||
/// Property introspection uses <see cref="Shader"/> instance APIs exclusively:
|
||||
/// <c>GetPropertyCount</c>, <c>GetPropertyName</c>, <c>GetPropertyType</c>,
|
||||
/// <c>GetPropertyDescription</c>, <c>GetPropertyRangeLimits</c>,
|
||||
/// <c>GetPropertyTextureDimension</c>, and <c>GetPropertyFlags</c>. The reported type set is
|
||||
/// <see cref="UnityEngine.Rendering.ShaderPropertyType"/>: Color, Vector, Float, Range, Texture, Int.
|
||||
/// </summary>
|
||||
public static class ShaderCommands
|
||||
{
|
||||
[CliCommand("list_shaders",
|
||||
"Discover available shaders so an agent can pick a valid name for set_material_properties / create_asset. Returns [{ name, assetPath|null, isBuiltin, isSupported }].")]
|
||||
public static List<ShaderInfo> ListShaders(
|
||||
[CliArg("filter", "Case-insensitive substring matched against the shader name (e.g. \"URP\", \"Lit\").")] string filter = null,
|
||||
[CliArg("includeBuiltin", "Include built-in/engine shaders (those with no project asset path). Default true.")] bool includeBuiltin = true,
|
||||
[CliArg("limit", "Maximum number of shaders to return (default 200).")] int limit = 200)
|
||||
{
|
||||
if (limit <= 0)
|
||||
limit = 200;
|
||||
|
||||
var results = new List<ShaderInfo>();
|
||||
var seen = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
// AssetDatabase.FindAssets("t:Shader") enumerates project + package shaders (each with an
|
||||
// asset path); ShaderUtil.GetAllShaderInfo() additionally surfaces built-in engine shaders
|
||||
// that have no asset path (e.g. "Standard", "Sprites/Default"). Merge both, dedup by name.
|
||||
foreach (var guid in AssetDatabase.FindAssets("t:Shader"))
|
||||
{
|
||||
var path = AssetDatabase.GUIDToAssetPath(guid);
|
||||
if (string.IsNullOrEmpty(path))
|
||||
continue;
|
||||
var shader = AssetDatabase.LoadAssetAtPath<Shader>(path);
|
||||
if (shader == null)
|
||||
continue;
|
||||
|
||||
if (!seen.Add(shader.name))
|
||||
continue;
|
||||
|
||||
if (!PassesFilter(shader.name, filter))
|
||||
continue;
|
||||
|
||||
results.Add(new ShaderInfo
|
||||
{
|
||||
Name = shader.name,
|
||||
AssetPath = path,
|
||||
IsBuiltin = false,
|
||||
IsSupported = shader.isSupported,
|
||||
});
|
||||
|
||||
if (results.Count >= limit)
|
||||
return results;
|
||||
}
|
||||
|
||||
if (includeBuiltin)
|
||||
{
|
||||
// GetAllShaderInfo() enumerates every shader the editor knows about, including built-in
|
||||
// engine shaders that have no project/package asset path. Any name NOT already added by
|
||||
// the FindAssets("t:Shader") pass above is a built-in (assetPath null). `info.supported`
|
||||
// already reflects Shader::IsSupported(), so we don't re-run Shader.Find per entry.
|
||||
foreach (var info in ShaderUtil.GetAllShaderInfo())
|
||||
{
|
||||
if (!seen.Add(info.name))
|
||||
continue;
|
||||
|
||||
if (!PassesFilter(info.name, filter))
|
||||
continue;
|
||||
|
||||
results.Add(new ShaderInfo
|
||||
{
|
||||
Name = info.name,
|
||||
AssetPath = null,
|
||||
IsBuiltin = true,
|
||||
IsSupported = info.supported,
|
||||
});
|
||||
|
||||
if (results.Count >= limit)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
[CliCommand("get_shader_properties",
|
||||
"Introspect a shader's declared property list (name, description, type Color|Vector|Float|Range|TexEnv|Int, range, textureDimension, flags). Provide 'shader' (by name) OR 'material' (read the shader off that material).")]
|
||||
public static ShaderPropertiesResult GetShaderProperties(
|
||||
[CliArg("shader", "Shader name (e.g. \"Universal Render Pipeline/Lit\"). Provide this OR 'material'.")] string shader = null,
|
||||
[CliArg("material", "Reference to a material to read the shader from instead of naming it. Provide this OR 'shader'.")] ObjectRef material = null)
|
||||
{
|
||||
Shader resolved;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(shader))
|
||||
{
|
||||
resolved = Shader.Find(shader);
|
||||
if (resolved == null)
|
||||
throw new ShaderNotFoundException(
|
||||
$"Shader '{shader}' not found. Shader.Find only resolves shaders that are compiled/included in the build (or referenced by a material/scene). Use list_shaders to discover valid names.");
|
||||
}
|
||||
else if (material != null && !material.IsEmpty)
|
||||
{
|
||||
var mat = MaterialCommands.ResolveMaterial(material, out _);
|
||||
resolved = mat.shader;
|
||||
if (resolved == null)
|
||||
throw new ArgumentException($"Material '{material}' has no shader assigned.");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Provide either 'shader' (a shader name) or 'material' (a material reference).");
|
||||
}
|
||||
|
||||
var result = new ShaderPropertiesResult { Shader = resolved.name };
|
||||
|
||||
int count = resolved.GetPropertyCount();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var propType = resolved.GetPropertyType(i);
|
||||
var entry = new ShaderPropertyInfo
|
||||
{
|
||||
Name = resolved.GetPropertyName(i),
|
||||
Description = resolved.GetPropertyDescription(i),
|
||||
Type = MaterialValueConverter.TypeLabel(propType),
|
||||
Flags = DescribeFlags(resolved.GetPropertyFlags(i)),
|
||||
};
|
||||
|
||||
if (propType == ShaderPropertyType.Range)
|
||||
{
|
||||
var limits = resolved.GetPropertyRangeLimits(i);
|
||||
entry.Range = new MaterialRange
|
||||
{
|
||||
Min = limits.x,
|
||||
Max = limits.y,
|
||||
};
|
||||
}
|
||||
|
||||
if (propType == ShaderPropertyType.Texture)
|
||||
entry.TextureDimension = resolved.GetPropertyTextureDimension(i).ToString();
|
||||
|
||||
result.Properties.Add(entry);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool PassesFilter(string name, string filter)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filter))
|
||||
return true;
|
||||
return name.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompose a <see cref="ShaderPropertyFlags"/> bitmask into its individual flag names (the
|
||||
/// enum is a [Flags] type). <c>None</c> is reported as an empty list rather than ["None"].
|
||||
/// </summary>
|
||||
private static List<string> DescribeFlags(ShaderPropertyFlags flags)
|
||||
{
|
||||
var list = new List<string>();
|
||||
if (flags == ShaderPropertyFlags.None)
|
||||
return list;
|
||||
|
||||
foreach (ShaderPropertyFlags flag in Enum.GetValues(typeof(ShaderPropertyFlags)))
|
||||
{
|
||||
if (flag == ShaderPropertyFlags.None)
|
||||
continue;
|
||||
if ((flags & flag) == flag)
|
||||
list.Add(flag.ToString());
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1007e4472d7b4216a6a866f8aaba76c7
|
||||
Reference in New Issue
Block a user