// using System; using System.Collections.Generic; using UnityEditor.ShaderGraph.Internal; namespace UnityEditor.ShaderGraph { enum ConversionType { Position, Direction, Normal } internal struct SpaceTransform { public CoordinateSpace from; public CoordinateSpace to; public ConversionType type; public bool normalize; public int version; public const int kLatestVersion = 2; public SpaceTransform(CoordinateSpace from, CoordinateSpace to, ConversionType type, bool normalize = false, int version = kLatestVersion) { this.from = from; this.to = to; this.type = type; this.normalize = normalize; this.version = version; } internal string NormalizeString() { return normalize ? "true" : "false"; } // non-identity transforms involving tangent space require the full tangent frame public NeededCoordinateSpace RequiresNormal => (((from == CoordinateSpace.Tangent) || (to == CoordinateSpace.Tangent)) && (from != to)) ? NeededCoordinateSpace.World : NeededCoordinateSpace.None; public NeededCoordinateSpace RequiresTangent => (((from == CoordinateSpace.Tangent) || (to == CoordinateSpace.Tangent)) && (from != to)) ? NeededCoordinateSpace.World : NeededCoordinateSpace.None; public NeededCoordinateSpace RequiresBitangent => (((from == CoordinateSpace.Tangent) || (to == CoordinateSpace.Tangent)) && (from != to)) ? NeededCoordinateSpace.World : NeededCoordinateSpace.None; // non-identity position transforms involving tangent space require the world position public NeededCoordinateSpace RequiresPosition => ((type == ConversionType.Position) && ((from == CoordinateSpace.Tangent) || (to == CoordinateSpace.Tangent)) && (from != to)) ? NeededCoordinateSpace.World : NeededCoordinateSpace.None; public IEnumerable RequiresTransform => SpaceTransformUtil.ComputeTransformRequirement(this); }; static class SpaceTransformUtil { delegate void TransformFunction(SpaceTransform xform, string inputValue, string outputVariable, ShaderStringBuilder sb); public static string GenerateTangentTransform(ShaderStringBuilder sb, CoordinateSpace tangentTransformSpace) { sb.AddLine("$precision3x3 tangentTransform = $precision3x3(IN.", tangentTransformSpace.ToString(), "SpaceTangent, IN.", tangentTransformSpace.ToString(), "SpaceBiTangent, IN.", tangentTransformSpace.ToString(), "SpaceNormal);"); return "tangentTransform"; } public static void Identity(SpaceTransform xform, string inputValue, string outputVariable, ShaderStringBuilder sb) { // identity didn't normalize before version 2 if ((xform.version > 1) && xform.normalize && (xform.type != ConversionType.Position)) sb.AddLine(outputVariable, " = SafeNormalize(", inputValue, ");"); else sb.AddLine(outputVariable, " = ", inputValue, ";"); } private static void ViaWorld(SpaceTransform xform, string inputValue, string outputVariable, ShaderStringBuilder sb) { // should never be calling this if one of the spaces is already world space (silly, and could lead to infinite recursions) if ((xform.from == CoordinateSpace.World) || (xform.to == CoordinateSpace.World)) return; // this breaks the transform into two parts: (from->world) and (world->to) var toWorld = new SpaceTransform() { from = xform.from, to = CoordinateSpace.World, type = xform.type, normalize = false, version = xform.version }; var fromWorld = new SpaceTransform() { from = CoordinateSpace.World, to = xform.to, type = xform.type, normalize = xform.normalize, version = xform.version }; // Apply Versioning Hacks to match old (incorrect) versions if (xform.version <= 1) { if (xform.type == ConversionType.Direction) { switch (xform.from) { case CoordinateSpace.AbsoluteWorld: if ((xform.to == CoordinateSpace.Object) || (xform.to == CoordinateSpace.View)) { // these transforms were wrong in v0, but correct in v1, so here we // pretend it is a later version to disable the v1 versioning in the AbsWorldToWorld transform if (xform.version == 1) toWorld.version = 2; } break; case CoordinateSpace.View: if ((xform.to == CoordinateSpace.Tangent) || (xform.to == CoordinateSpace.AbsoluteWorld)) { // these transforms erroneously used the position view-to-world transform toWorld.type = ConversionType.Position; } break; case CoordinateSpace.Tangent: if ((xform.to == CoordinateSpace.Object) || (xform.to == CoordinateSpace.View) || (xform.to == CoordinateSpace.AbsoluteWorld)) { // manually version to 2, to remove normalization (while keeping Normal type) toWorld.type = ConversionType.Normal; toWorld.version = 2; } break; } } } using (sb.BlockScope()) { sb.AddLine("// Converting ", xform.type.ToString(), " from ", xform.from.ToString(), " to ", xform.to.ToString(), " via world space"); sb.AddLine("float3 world;"); GenerateTransformCodeStatement(toWorld, inputValue, "world", sb); GenerateTransformCodeStatement(fromWorld, "world", outputVariable, sb); } } public static void WorldToObject(SpaceTransform xform, string inputValue, string outputVariable, ShaderStringBuilder sb) { switch (xform.type) { case ConversionType.Position: sb.AddLine(outputVariable, " = TransformWorldToObject(", inputValue, ");"); break; case ConversionType.Direction: if (xform.version <= 1) xform.normalize = true; sb.AddLine(outputVariable, " = TransformWorldToObjectDir(", inputValue, ", ", xform.NormalizeString(), ");"); break; case ConversionType.Normal: sb.AddLine(outputVariable, " = TransformWorldToObjectNormal(", inputValue, ", ", xform.NormalizeString(), ");"); break; } } public static void WorldToTangent(SpaceTransform xform, string inputValue, string outputVariable, ShaderStringBuilder sb) { if (xform.version <= 1) { // prior to version 2, all transform were normalized, and all transforms were Normal transforms xform.normalize = true; xform.type = ConversionType.Normal; } using (sb.BlockScope()) { string tangentTransform = GenerateTangentTransform(sb, xform.from); switch (xform.type) { case ConversionType.Position: sb.AddLine(outputVariable, " = TransformWorldToTangentDir(", inputValue, " - IN.WorldSpacePosition, ", tangentTransform, ", false);"); break; case ConversionType.Direction: sb.AddLine(outputVariable, " = TransformWorldToTangentDir(", inputValue, ", ", tangentTransform, ", ", xform.NormalizeString(), ");"); break; case ConversionType.Normal: sb.AddLine(outputVariable, " = TransformWorldToTangent(", inputValue, ", ", tangentTransform, ", ", xform.NormalizeString(), ");"); break; } } } public static void WorldToView(SpaceTransform xform, string inputValue, string outputVariable, ShaderStringBuilder sb) { switch (xform.type) { case ConversionType.Position: sb.AddLine(outputVariable, " = TransformWorldToView(", inputValue, ");"); break; case ConversionType.Direction: if (xform.version <= 1) xform.normalize = false; sb.AddLine(outputVariable, " = TransformWorldToViewDir(", inputValue, ", ", xform.NormalizeString(), ");"); break; case ConversionType.Normal: sb.AddLine(outputVariable, " = TransformWorldToViewNormal(", inputValue, ", ", xform.NormalizeString(), ");"); break; } } public static void WorldToAbsoluteWorld(SpaceTransform xform, string inputValue, string outputVariable, ShaderStringBuilder sb) { // prior to version 2 always used Position transform if (xform.version <= 1) xform.type = ConversionType.Position; switch (xform.type) { case ConversionType.Position: sb.AddLine(outputVariable, " = GetAbsolutePositionWS(", inputValue, ");"); break; case ConversionType.Direction: case ConversionType.Normal: // both normal and direction are unchanged if (xform.normalize) sb.AddLine(outputVariable, " = SafeNormalize(", inputValue, ");"); else sb.AddLine(outputVariable, " = ", inputValue, ";"); break; } } public static void ObjectToWorld(SpaceTransform xform, string inputValue, string outputVariable, ShaderStringBuilder sb) { switch (xform.type) { case ConversionType.Position: sb.AddLine(outputVariable, " = TransformObjectToWorld(", inputValue, ");"); break; case ConversionType.Direction: if (xform.version <= 1) xform.normalize = true; sb.AddLine(outputVariable, " = TransformObjectToWorldDir(", inputValue, ", ", xform.NormalizeString(), ");"); break; case ConversionType.Normal: sb.AddLine(outputVariable, " = TransformObjectToWorldNormal(", inputValue, ", ", xform.NormalizeString(), ");"); break; } } public static void ObjectToAbsoluteWorld(SpaceTransform xform, string inputValue, string outputVariable, ShaderStringBuilder sb) { switch (xform.type) { case ConversionType.Position: ViaWorld(xform, inputValue, outputVariable, sb); break; case ConversionType.Direction: if (xform.version <= 1) xform.normalize = true; sb.AddLine(outputVariable, " = TransformObjectToWorldDir(", inputValue, ", ", xform.NormalizeString(), ");"); break; case ConversionType.Normal: sb.AddLine(outputVariable, " = TransformObjectToWorldNormal(", inputValue, ", ", xform.NormalizeString(), ");"); break; } } public static void TangentToWorld(SpaceTransform xform, string inputValue, string outputVariable, ShaderStringBuilder sb) { // prior to version 2 all transforms were Normal, and directional transforms were normalized if (xform.version <= 1) { if (xform.type != ConversionType.Position) xform.normalize = true; xform.type = ConversionType.Normal; } using (sb.BlockScope()) { string tangentTransform = GenerateTangentTransform(sb, CoordinateSpace.World); switch (xform.type) { case ConversionType.Position: sb.AddLine(outputVariable, " = TransformTangentToWorldDir(", inputValue, ", ", tangentTransform, ", false).xyz + IN.WorldSpacePosition;"); break; case ConversionType.Direction: sb.AddLine(outputVariable, " = TransformTangentToWorldDir(", inputValue, ", ", tangentTransform, ", ", xform.NormalizeString(), ").xyz;"); break; case ConversionType.Normal: sb.AddLine(outputVariable, " = TransformTangentToWorld(", inputValue, ", ", tangentTransform, ", ", xform.NormalizeString(), ");"); break; } } } public static void ViewToWorld(SpaceTransform xform, string inputValue, string outputVariable, ShaderStringBuilder sb) { switch (xform.type) { case ConversionType.Position: sb.AddLine(outputVariable, " = TransformViewToWorld(", inputValue, ");"); break; case ConversionType.Direction: if (xform.version <= 1) xform.normalize = false; sb.AddLine(outputVariable, " = TransformViewToWorldDir(", inputValue, ", ", xform.NormalizeString(), ");"); break; case ConversionType.Normal: sb.AddLine(outputVariable, " = TransformViewToWorldNormal(", inputValue, ", ", xform.NormalizeString(), ");"); break; } } public static void AbsoluteWorldToWorld(SpaceTransform xform, string inputValue, string outputVariable, ShaderStringBuilder sb) { // prior to version 2, always used position transform if (xform.version <= 1) xform.type = ConversionType.Position; switch (xform.type) { case ConversionType.Position: sb.AddLine(outputVariable, " = GetCameraRelativePositionWS(", inputValue, ");"); break; case ConversionType.Direction: case ConversionType.Normal: // both normal and direction are unchanged if (xform.normalize) sb.AddLine(outputVariable, " = SafeNormalize(", inputValue, ");"); else sb.AddLine(outputVariable, " = ", inputValue, ";"); break; } } public static void ObjectToHScreen(SpaceTransform xform, string inputValue, string outputVariable, ShaderStringBuilder sb) { switch (xform.type) { case ConversionType.Position: using (sb.BlockScope()) { sb.AddLine("$precision4 ", outputVariable, "_value = TransformObjectToHClip(", inputValue, ");"); sb.AddLine("$precision3 ", outputVariable, "_uv = ", outputVariable, "_value.xyz / ", outputVariable, "_value.w;"); sb.AddLine("#if UNITY_UV_STARTS_AT_TOP"); sb.AddLine(outputVariable, "_uv.y = -", outputVariable, "_uv.y;"); sb.AddLine("#endif"); sb.AddLine(outputVariable, "_uv.xy = ", outputVariable, "_uv.xy * 0.5 + 0.5;"); sb.AddLine(outputVariable, " = ", outputVariable, "_uv;"); } break; case ConversionType.Direction: case ConversionType.Normal: ViaWorld(xform, inputValue, outputVariable, sb); break; } } public static void ViewToHScreen(SpaceTransform xform, string inputValue, string outputVariable, ShaderStringBuilder sb) { switch (xform.type) { case ConversionType.Position: sb.AddLine("$precision4 ", outputVariable, "_value = TransformWViewToHClip(", inputValue, ");"); sb.AddLine("$precision3 ", outputVariable, "_uv = ", outputVariable, "_value.xyz / ", outputVariable, "_value.w;"); sb.AddLine("#if UNITY_UV_STARTS_AT_TOP"); sb.AddLine(outputVariable, "_uv.y = -", outputVariable, "_uv.y;"); sb.AddLine("#endif"); sb.AddLine(outputVariable, "_uv.xy = ", outputVariable, "_uv.xy * 0.5 + 0.5;"); sb.AddLine(outputVariable, " = ", outputVariable, "_uv;"); break; case ConversionType.Direction: case ConversionType.Normal: ViaWorld(xform, inputValue, outputVariable, sb); break; } } public static void WorldToHScreen(SpaceTransform xform, string inputValue, string outputVariable, ShaderStringBuilder sb) { switch (xform.type) { case ConversionType.Position: sb.AddLine("$precision4 ", outputVariable, "_value = TransformWorldToHClip(", inputValue, ");"); break; case ConversionType.Direction: if (xform.version <= 1) xform.normalize = true; sb.AddLine("$precision4 ", outputVariable, "_value = $precision4(TransformWorldToHClipDir(", inputValue, ", ", xform.NormalizeString(), "), 1.0f);"); break; case ConversionType.Normal: sb.AddLine("$precision4 ", outputVariable, "_value = $precision4(TransformWorldToHClipDir(", inputValue, ", ", xform.NormalizeString(), "), 1.0f);"); break; } sb.AddLine("$precision3 ", outputVariable, "_uv = ", outputVariable, "_value.xyz / ", outputVariable, "_value.w;"); sb.AddLine("#if UNITY_UV_STARTS_AT_TOP"); sb.AddLine(outputVariable, "_uv.y = -", outputVariable, "_uv.y;"); sb.AddLine("#endif"); sb.AddLine(outputVariable, "_uv.xy = ", outputVariable, "_uv.xy * 0.5 + 0.5;"); sb.AddLine(outputVariable, " = ", outputVariable, "_uv;"); } public static void HScreenToWorld(SpaceTransform xform, string inputValue, string outputVariable, ShaderStringBuilder sb) { switch (xform.type) { case ConversionType.Position: sb.AddLine(outputVariable, " = ComputeWorldSpacePosition(", inputValue, ".xy, ", inputValue, ".z, UNITY_MATRIX_I_VP);"); break; case ConversionType.Direction: case ConversionType.Normal: sb.AddLine("$precision4 ", outputVariable, "_CS = ComputeClipSpacePosition(", inputValue, ".xy, ", inputValue, ".z);"); sb.AddLine(outputVariable, " = mul(UNITY_MATRIX_I_VP, $precision4(", outputVariable, "_CS.xyz, 0.0f)).xyz;"); break; } } static readonly TransformFunction[,] k_TransformFunctions = new TransformFunction[6, 6] // [from, to] { { // from CoordinateSpace.Object Identity, // to CoordinateSpace.Object ViaWorld, // to CoordinateSpace.View ObjectToWorld, // to CoordinateSpace.World ViaWorld, // to CoordinateSpace.Tangent ObjectToAbsoluteWorld, // to CoordinateSpace.AbsoluteWorld ObjectToHScreen // to CoordinateSpace.Screen }, { // from CoordinateSpace.View ViaWorld, // to CoordinateSpace.Object Identity, // to CoordinateSpace.View ViewToWorld, // to CoordinateSpace.World ViaWorld, // to CoordinateSpace.Tangent ViaWorld, // to CoordinateSpace.AbsoluteWorld ViewToHScreen // to CoordinateSpace.Screen }, { // from CoordinateSpace.World WorldToObject, // to CoordinateSpace.Object WorldToView, // to CoordinateSpace.View Identity, // to CoordinateSpace.World WorldToTangent, // to CoordinateSpace.Tangent WorldToAbsoluteWorld, // to CoordinateSpace.AbsoluteWorld WorldToHScreen // to CoordinateSpace.Screen }, { // from CoordinateSpace.Tangent ViaWorld, // to CoordinateSpace.Object ViaWorld, // to CoordinateSpace.View TangentToWorld, // to CoordinateSpace.World Identity, // to CoordinateSpace.Tangent ViaWorld, // to CoordinateSpace.AbsoluteWorld ViaWorld // to CoordinateSpace.Screen }, { // from CoordinateSpace.AbsoluteWorld ViaWorld, // to CoordinateSpace.Object ViaWorld, // to CoordinateSpace.View AbsoluteWorldToWorld, // to CoordinateSpace.World ViaWorld, // to CoordinateSpace.Tangent Identity, // to CoordinateSpace.AbsoluteWorld ViaWorld // to CoordinateSpace.Screen }, { // from CoordinateSpace.Screen ViaWorld, // to CoordinateSpace.Object ViaWorld, // to CoordinateSpace.View HScreenToWorld, // to CoordinateSpace.World ViaWorld, // to CoordinateSpace.Tangent ViaWorld, // to CoordinateSpace.AbsoluteWorld Identity, // to CoordinateSpace.Screen } }; internal static IEnumerable ComputeTransformRequirement(SpaceTransform xform) { var func = k_TransformFunctions[(int)xform.from, (int)xform.to]; if (func == ViaWorld || func == ObjectToAbsoluteWorld) { yield return new NeededTransform(xform.from.ToNeededCoordinateSpace(), NeededCoordinateSpace.World); yield return new NeededTransform(NeededCoordinateSpace.World, xform.to.ToNeededCoordinateSpace()); } else { yield return new NeededTransform(xform.from.ToNeededCoordinateSpace(), xform.to.ToNeededCoordinateSpace()); } } public static void GenerateTransformCodeStatement(SpaceTransform xform, string inputValue, string outputVariable, ShaderStringBuilder sb) { var func = k_TransformFunctions[(int)xform.from, (int)xform.to]; func(xform, inputValue, outputVariable, sb); } } }