From 00553b3f305d0e2217659993f237ff3da604ef85 Mon Sep 17 00:00:00 2001 From: yum Date: Tue, 17 Feb 2026 18:52:17 -0800 Subject: Fold: add plane to octahedron code --- Scripts/Fold/Editor/FoldEditorWindow.cs | 87 ++++++++++++++++++++++++++ Scripts/Fold/Editor/FoldPipelineBuilder.cs | 42 +++++++++++++ Scripts/Fold/Editor/README.md | 41 ------------- brdf.cginc | 4 -- vertex.cginc | 70 +++++++++++++++++++++ vertex_deformation.slang | 98 +++++++++++++++++++++++++++--- 6 files changed, 288 insertions(+), 54 deletions(-) diff --git a/Scripts/Fold/Editor/FoldEditorWindow.cs b/Scripts/Fold/Editor/FoldEditorWindow.cs index e2a94e1..e8faf1f 100755 --- a/Scripts/Fold/Editor/FoldEditorWindow.cs +++ b/Scripts/Fold/Editor/FoldEditorWindow.cs @@ -205,12 +205,15 @@ public class FoldEditorWindow : EditorWindow menu.AddItem(new GUIContent("Plane to Tube"), false, () => AddOperation(new PlaneToTubeOp())); menu.AddItem(new GUIContent("Plane to Hemi-Octahedron"), false, () => AddOperation(new PlaneToHemiOctahedronOp())); menu.AddItem(new GUIContent("Hemi-Octahedron to Plane"), false, () => AddOperation(new HemiOctahedronToPlaneOp())); + menu.AddItem(new GUIContent("Plane to Octahedron"), false, () => AddOperation(new PlaneToOctahedronOp())); + menu.AddItem(new GUIContent("Octahedron to Plane"), false, () => AddOperation(new OctahedronToPlaneOp())); menu.AddSeparator(""); menu.AddItem(new GUIContent("Point Align"), false, () => AddOperation(new PointAlignOp())); menu.AddItem(new GUIContent("Axis Align"), false, () => AddOperation(new AxisAlignOp())); menu.AddSeparator(""); menu.AddItem(new GUIContent("Scale"), false, () => AddOperation(new ScaleOp())); menu.AddItem(new GUIContent("Translate"), false, () => AddOperation(new TranslateOp())); + menu.AddItem(new GUIContent("Rotate"), false, () => AddOperation(new RotateOp())); menu.AddItem(new GUIContent("Norm Conversion"), false, () => AddOperation(new NormConversionOp())); menu.AddItem(new GUIContent("Seal"), false, () => AddOperation(new SealOp())); menu.AddSeparator(""); @@ -274,6 +277,9 @@ public class FoldEditorWindow : EditorWindow FoldPipelineBuilder.Opcodes.HemiOctahedronToPlane => new HemiOctahedronToPlaneOp(slot), FoldPipelineBuilder.Opcodes.Scale => new ScaleOp(slot), FoldPipelineBuilder.Opcodes.Translate => new TranslateOp(slot), + FoldPipelineBuilder.Opcodes.PlaneToOctahedron => new PlaneToOctahedronOp(slot), + FoldPipelineBuilder.Opcodes.OctahedronToPlane => new OctahedronToPlaneOp(slot), + FoldPipelineBuilder.Opcodes.Rotate => new RotateOp(slot), FoldPipelineBuilder.Opcodes.PointAlign => new PointAlignOp(slot), FoldPipelineBuilder.Opcodes.AxisAlign => new AxisAlignOp(slot), FoldPipelineBuilder.Opcodes.NormConversion => new NormConversionOp(slot), @@ -417,6 +423,37 @@ public class TranslateOp : DeformOperation builder.Translate(offset, t); } +[System.Serializable] +public class RotateOp : DeformOperation +{ + public Vector3 center = Vector3.zero; + public Vector3 axis = Vector3.up; + public float angleDeg = 90f; + public float t = 1f; + + public RotateOp() { } + public RotateOp(FoldSlot slot) + { + center = slot.vec0; + axis = slot.vec1; + angleDeg = slot.float0 * Mathf.Rad2Deg; + t = slot.float1; + } + + public override string GetDisplayName() => "Rotate"; + + public override void DrawParameters() + { + center = EditorGUILayout.Vector3Field("Center", center); + axis = EditorGUILayout.Vector3Field("Axis", axis); + angleDeg = EditorGUILayout.Slider("Angle", angleDeg, 0f, 360f); + t = EditorGUILayout.Slider("Interpolation (t)", t, 0f, 1f); + } + + public override void ApplyTo(FoldPipelineBuilder builder) => + builder.Rotate(center, axis, angleDeg * Mathf.Deg2Rad, t); +} + [System.Serializable] public class HemiOctahedronToPlaneOp : DeformOperation { @@ -442,6 +479,56 @@ public class HemiOctahedronToPlaneOp : DeformOperation builder.HemiOctahedronToPlane(p, r, s, t); } +[System.Serializable] +public class PlaneToOctahedronOp : DeformOperation +{ + public Vector3 p = Vector3.zero; + public Vector3 r = Vector3.right; + public Vector3 s = Vector3.forward; + public float t = 1f; + + public PlaneToOctahedronOp() { } + public PlaneToOctahedronOp(FoldSlot slot) { p = slot.vec0; r = slot.vec1; s = slot.vec2; t = slot.float0; } + + public override string GetDisplayName() => "Plane to Octahedron"; + + public override void DrawParameters() + { + p = EditorGUILayout.Vector3Field("Origin (p)", p); + r = EditorGUILayout.Vector3Field("R Axis", r); + s = EditorGUILayout.Vector3Field("S Axis", s); + t = EditorGUILayout.Slider("Interpolation (t)", t, 0f, 1f); + } + + public override void ApplyTo(FoldPipelineBuilder builder) => + builder.PlaneToOctahedron(p, r, s, t); +} + +[System.Serializable] +public class OctahedronToPlaneOp : DeformOperation +{ + public Vector3 p = Vector3.zero; + public Vector3 r = Vector3.right; + public Vector3 s = Vector3.forward; + public float t = 1f; + + public OctahedronToPlaneOp() { } + public OctahedronToPlaneOp(FoldSlot slot) { p = slot.vec0; r = slot.vec1; s = slot.vec2; t = slot.float0; } + + public override string GetDisplayName() => "Octahedron to Plane"; + + public override void DrawParameters() + { + p = EditorGUILayout.Vector3Field("Origin (p)", p); + r = EditorGUILayout.Vector3Field("R Axis", r); + s = EditorGUILayout.Vector3Field("S Axis", s); + t = EditorGUILayout.Slider("Interpolation (t)", t, 0f, 1f); + } + + public override void ApplyTo(FoldPipelineBuilder builder) => + builder.OctahedronToPlane(p, r, s, t); +} + [System.Serializable] public class PointAlignOp : DeformOperation { diff --git a/Scripts/Fold/Editor/FoldPipelineBuilder.cs b/Scripts/Fold/Editor/FoldPipelineBuilder.cs index 2dc2291..731ceeb 100755 --- a/Scripts/Fold/Editor/FoldPipelineBuilder.cs +++ b/Scripts/Fold/Editor/FoldPipelineBuilder.cs @@ -60,6 +60,9 @@ public class FoldPipelineBuilder public const int HemiOctahedronToPlane = 10; public const int Scale = 11; public const int Translate = 12; + public const int PlaneToOctahedron = 13; + public const int OctahedronToPlane = 14; + public const int Rotate = 15; } FoldPipelineBuilder() { } @@ -133,6 +136,45 @@ public class FoldPipelineBuilder return this; } + public FoldPipelineBuilder PlaneToOctahedron(Vector3 p, Vector3 r, Vector3 s, float t) + { + slots.Add(new FoldSlot + { + opcode = Opcodes.PlaneToOctahedron, + vec0 = p, + vec1 = r, + vec2 = s, + float0 = t + }); + return this; + } + + public FoldPipelineBuilder OctahedronToPlane(Vector3 p, Vector3 r, Vector3 s, float t) + { + slots.Add(new FoldSlot + { + opcode = Opcodes.OctahedronToPlane, + vec0 = p, + vec1 = r, + vec2 = s, + float0 = t + }); + return this; + } + + public FoldPipelineBuilder Rotate(Vector3 center, Vector3 axis, float angle, float t) + { + slots.Add(new FoldSlot + { + opcode = Opcodes.Rotate, + vec0 = center, + vec1 = axis, + float0 = angle, + float1 = t + }); + return this; + } + public FoldPipelineBuilder HemiOctahedronToPlane(Vector3 p, Vector3 r, Vector3 s, float t) { slots.Add(new FoldSlot diff --git a/Scripts/Fold/Editor/README.md b/Scripts/Fold/Editor/README.md index d2b7494..7e6224a 100755 --- a/Scripts/Fold/Editor/README.md +++ b/Scripts/Fold/Editor/README.md @@ -89,47 +89,6 @@ Applies fractal Brownian motion deformation. ### Custom(opcode, f0-f3, v0-v3) For advanced use cases or custom opcodes. -## Presets (Code) - -Use built-in presets from code: - -```csharp -FoldPresets.TubeToPlaneFull(material); -FoldPresets.NormConvL2ToL1(material); -FoldPresets.NormConvL2ToLinf(material); -``` - -## Adding Custom Presets - -Edit `FoldEditorWindow.cs` to add presets to the "Load Presets" menu: - -```csharp -// In ShowPresetsMenu(): -menu.AddItem(new GUIContent("My Custom Effect"), false, () => LoadPreset_MyEffect()); - -// Add the preset loader method: -void LoadPreset_MyEffect() -{ - operations.Clear(); - AddOperation(new TubeToPlaneOp()); - var norm = new NormConversionOp(); - norm.inputK = 2f; - norm.outputK = 1f; - AddOperation(norm); -} -``` - -Or use the fluent API in code: - -```csharp -public static void MyCustomEffect(Material mat) => - FoldPipelineBuilder.Create() - .For(mat) - .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f) - .NormConversion(2f, 1f, 0.5f) - .Apply(); -``` - ## Pipeline Chaining Operations are applied in the order they're chained: diff --git a/brdf.cginc b/brdf.cginc index d44ce14..d064823 100755 --- a/brdf.cginc +++ b/brdf.cginc @@ -250,12 +250,8 @@ float4 brdf(Pbr pbr, LightData data) { // Standard split-sum IBL float3 f0_spec = lerp(f0, pbr.albedo.xyz, pbr.metallic); - //float3 ibl_specular_reflectance = f0_spec * dfg.x + dfg.y; float3 ibl_specular_reflectance = lerp(dfg.xxx, dfg.yyy, f0_spec); - //float3 ibl_specular_reflectance = f0_spec * dfg.x; float3 indirect_specular = data.indirect.specular * ibl_specular_reflectance; - - //return float4(data.indirect.specular, 1); specular += indirect_specular; remainder = saturate(remainder - indirect_specular); diff --git a/vertex.cginc b/vertex.cginc index 7c448ab..c4da8ca 100755 --- a/vertex.cginc +++ b/vertex.cginc @@ -17,6 +17,9 @@ #define OPCODE_HEMI_OCTAHEDRON_TO_PLANE 10 #define OPCODE_SCALE 11 #define OPCODE_TRANSLATE 12 +#define OPCODE_PLANE_TO_OCTAHEDRON 13 +#define OPCODE_OCTAHEDRON_TO_PLANE 14 +#define OPCODE_ROTATE 15 #if defined(_VERTEX_DEFORMATION) @@ -210,6 +213,55 @@ void apply_translate_normal(inout float3 objPos, inout float3 objNorm, translate_normal(objPos, objNorm, objTan, offset, t); } +void apply_plane_to_octahedron(inout float3 objPos, float4 v0, float4 v1, float4 v2, float f0) { + float3 p = v0.xyz; + float3 r = v1.xyz; + float3 s = v2.xyz; + float t = f0; + objPos = plane_to_octahedron(objPos, p, r, s, t); +} + +void apply_plane_to_octahedron_normal(inout float3 objPos, inout float3 objNorm, inout float3 objTan, float4 v0, float4 v1, float4 v2, float f0) { + float3 p = v0.xyz; + float3 r = v1.xyz; + float3 s = v2.xyz; + float t = f0; + plane_to_octahedron_normal(objPos, objNorm, objTan, p, r, s, t); +} + +void apply_octahedron_to_plane(inout float3 objPos, float4 v0, float4 v1, float4 v2, float f0) { + float3 p = v0.xyz; + float3 r = v1.xyz; + float3 s = v2.xyz; + float t = f0; + objPos = octahedron_to_plane(objPos, p, r, s, t); +} + +void apply_octahedron_to_plane_normal(inout float3 objPos, inout float3 objNorm, inout float3 objTan, float4 v0, float4 v1, float4 v2, float f0) { + float3 p = v0.xyz; + float3 r = v1.xyz; + float3 s = v2.xyz; + float t = f0; + octahedron_to_plane_normal(objPos, objNorm, objTan, p, r, s, t); +} + +void apply_rotate(inout float3 objPos, float4 v0, float4 v1, float f0, float f1) { + float3 p = v0.xyz; + float3 axis = v1.xyz; + float angle = f0; + float t = f1; + objPos = rotate(objPos, p, axis, angle, t); +} + +void apply_rotate_normal(inout float3 objPos, inout float3 objNorm, + inout float3 objTan, float4 v0, float4 v1, float f0, float f1) { + float3 p = v0.xyz; + float3 axis = v1.xyz; + float angle = f0; + float t = f1; + rotate_normal(objPos, objNorm, objTan, p, axis, angle, t); +} + void deform(inout float3 objPos) { const float t = getTime(); @@ -454,6 +506,15 @@ void deform(inout float3 objPos) { case OPCODE_TRANSLATE: apply_translate(objPos, v0, f0); break; + case OPCODE_PLANE_TO_OCTAHEDRON: + apply_plane_to_octahedron(objPos, v0, v1, v2, f0); + break; + case OPCODE_OCTAHEDRON_TO_PLANE: + apply_octahedron_to_plane(objPos, v0, v1, v2, f0); + break; + case OPCODE_ROTATE: + apply_rotate(objPos, v0, v1, f0, f1); + break; } } } @@ -705,6 +766,15 @@ void deform_normal(inout float3 objPos, inout float3 objNorm, inout float3 objTa case OPCODE_TRANSLATE: apply_translate_normal(objPos, objNorm, objTan, v0, f0); break; + case OPCODE_PLANE_TO_OCTAHEDRON: + apply_plane_to_octahedron_normal(objPos, objNorm, objTan, v0, v1, v2, f0); + break; + case OPCODE_OCTAHEDRON_TO_PLANE: + apply_octahedron_to_plane_normal(objPos, objNorm, objTan, v0, v1, v2, f0); + break; + case OPCODE_ROTATE: + apply_rotate_normal(objPos, objNorm, objTan, v0, v1, f0, f1); + break; } } } diff --git a/vertex_deformation.slang b/vertex_deformation.slang index 0883c84..de53394 100755 --- a/vertex_deformation.slang +++ b/vertex_deformation.slang @@ -483,10 +483,7 @@ public void fbm_normal(inout float3 xyz, inout float3 normal, inout float3 tange tangent = mul(jac, tangent); } -// Maps a plane to a hemi-octahedron (half octahedron). -// Uses octahedral parameterization consistent with pbrt's OctahedralVector. -// Input: plane on [-1,1]² in the (r, rxs) plane -// Output: unit hemisphere with pole at +s direction +// Maps a plane on [-1, 1] on xz plane to a hemi-octahedron with radius 1. [Differentiable] public float3 plane_to_hemi_octahedron(float3 xyz, no_diff float3 p, no_diff float3 r_cart, no_diff float3 s_cart, @@ -526,7 +523,7 @@ public float3 plane_to_hemi_octahedron(float3 xyz, oct_pos = float3(x_unrot, oct_pos.y, z_unrot); // Interpolate between original position and sphere position - float3 result = dlerp(xyz0, oct_pos, dmin(t, 1.0f)); + float3 result = dlerp(xyz0, oct_pos, t); // Map back to cartesian basis xyz = mul(to_cart, result) + p; @@ -539,9 +536,7 @@ public void plane_to_hemi_octahedron_normal(inout float3 xyz, inout float3 norma R3R3_NORMALS(xyz, normal, tangent, plane_to_hemi_octahedron, p, r, s, t); } -// Maps a hemi-octahedron to a plane (inverse of plane_to_hemi_octahedron). -// Input: unit hemisphere with pole at +s direction -// Output: plane on [-1,1]² in the (r, rxs) plane +// Maps a hemi-octahedron with raidus 1 to a quad on [-1, 1] on the (r, rxs) plane. [Differentiable] public float3 hemi_octahedron_to_plane(float3 xyz, no_diff float3 p, no_diff float3 r_cart, no_diff float3 s_cart, @@ -577,7 +572,7 @@ public float3 hemi_octahedron_to_plane(float3 xyz, float3 plane_pos = float3(x_plane, 0.0f, z_plane); // Interpolate between original position and plane position - float3 result = dlerp(xyz0, plane_pos, dmin(t, 1.0f)); + float3 result = dlerp(xyz0, plane_pos, t); // Map back to cartesian basis xyz = mul(to_cart, result) + p; @@ -590,6 +585,73 @@ public void hemi_octahedron_to_plane_normal(inout float3 xyz, inout float3 norma R3R3_NORMALS(xyz, normal, tangent, hemi_octahedron_to_plane, p, r, s, t); } +// Maps [-1, 1] on (r, rxs) plane to a unit sphere using octahedral mapping. +[Differentiable] +public float3 plane_to_octahedron(float3 xyz, + no_diff float3 p, no_diff float3 r_cart, no_diff float3 s_cart, + no_diff float t) { + r_cart = normalize(r_cart); + s_cart = normalize(s_cart); + float3 rxs_cart = cross(s_cart, r_cart); + float3x3 to_rsrxs = float3x3(r_cart, s_cart, rxs_cart); + float3x3 to_cart = transpose(to_rsrxs); + + xyz = mul(to_rsrxs, xyz - p); + float3 xyz0 = xyz; + + float l1_norm = dabs(xyz.x) + dabs(xyz.z); + if (l1_norm > 1) { + xyz.x = sign(xyz0.x) * (1 - dabs(xyz0.z)); + xyz.z = sign(xyz0.z) * (1 - dabs(xyz0.x)); + } + xyz.y = 1 - l1_norm; + + xyz *= (1 + xyz0.y); + + float3 result = dlerp(xyz0, xyz, t); + + xyz = mul(to_cart, result) + p; + return xyz; +} + +public void plane_to_octahedron_normal(inout float3 xyz, inout float3 normal, + inout float3 tangent, float3 p, float3 r, float3 s, float t) { + R3R3_NORMALS(xyz, normal, tangent, plane_to_octahedron, p, r, s, t); +} + +// Maps a unit sphere to a plane using octahedral mapping. +[Differentiable] +public float3 octahedron_to_plane(float3 xyz, + no_diff float3 p, no_diff float3 r_cart, no_diff float3 s_cart, + no_diff float t) { + r_cart = normalize(r_cart); + s_cart = normalize(s_cart); + float3 rxs_cart = cross(s_cart, r_cart); + float3x3 to_rsrxs = float3x3(r_cart, s_cart, rxs_cart); + float3x3 to_cart = transpose(to_rsrxs); + + xyz = mul(to_rsrxs, xyz - p); + float3 xyz0 = xyz; + + float l1_norm = dabs(xyz.x) + dabs(xyz.y) + dabs(xyz.z); + xyz /= l1_norm; + float2 xz_tmp = xyz.xz; + if (xyz.y < 0) { + xyz.x = sign(xz_tmp[0]) * (1 - dabs(xz_tmp[1])); + xyz.z = sign(xz_tmp[1]) * (1 - dabs(xz_tmp[0])); + } + xyz.y = 0; + + float3 result = dlerp(xyz0, xyz, t); + xyz = mul(to_cart, result) + p; + return xyz; +} + +public void octahedron_to_plane_normal(inout float3 xyz, inout float3 normal, + inout float3 tangent, float3 p, float3 r, float3 s, float t) { + R3R3_NORMALS(xyz, normal, tangent, octahedron_to_plane, p, r, s, t); +} + [Differentiable] public float3 scale(float3 xyz, no_diff float3 k, no_diff float t) { @@ -612,5 +674,23 @@ public void translate_normal(inout float3 xyz, inout float3 normal, R3R3_NORMALS(xyz, normal, tangent, translate, offset, t); } +[Differentiable] +public float3 rotate(float3 xyz, + no_diff float3 p, no_diff float3 axis, no_diff float angle, no_diff float t) { + float theta = angle * t; + float3 a = normalize(axis); + float c = cos(theta); + float s = sin(theta); + float3 v = xyz - p; + // Rodrigues' rotation formula + float3 rotated = v * c + cross(a, v) * s + a * dot(a, v) * (1.0f - c); + return rotated + p; +} + +public void rotate_normal(inout float3 xyz, inout float3 normal, + inout float3 tangent, float3 p, float3 axis, float angle, float t) { + R3R3_NORMALS(xyz, normal, tangent, rotate, p, axis, angle, t); +} + #endif // __CUSTOM31_INC -- cgit v1.2.3