summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoryum <yum.food.vr@gmail.com>2024-07-13 16:33:25 -0700
committeryum <yum.food.vr@gmail.com>2024-07-13 16:33:25 -0700
commit3dcb2fd0e240f3c0141e65c32bc2c4a7f8e9fd20 (patch)
tree3972c678d7f01022a31c6eadffc1dcff50c9f83c
parentbf4457b96cd46ed2d3d61bde2eb4d58d3114730b (diff)
Integration pixellation and trochoid shaders
Trochoid is a WIP. Need to do some magic to make it properly shear. In short: it's currently implemented as a standalone Mesh Renderer object which I place on my avatar's neck bone. Since its object origin is not at the hip bone like everything else, it behaves weirdly when shearing. Solution is to implement it as a regular skinned mesh renderer. Requires some careful analysis to get right.
-rw-r--r--Editor/tooner.cs74
-rw-r--r--feature_macros.cginc2
-rw-r--r--globals.cginc17
-rw-r--r--shear_math.cginc60
-rw-r--r--tooner.shader14
-rw-r--r--tooner_lighting.cginc122
-rw-r--r--tooner_outline_pass.cginc41
-rw-r--r--trochoid_math.cginc90
8 files changed, 412 insertions, 8 deletions
diff --git a/Editor/tooner.cs b/Editor/tooner.cs
index 23a93ef..ba45a5d 100644
--- a/Editor/tooner.cs
+++ b/Editor/tooner.cs
@@ -916,6 +916,24 @@ public class ToonerGUI : ShaderGUI {
bc = FindProperty("_Gimmick_Shear_Location_Strength");
editor.VectorProperty(bc, "Strength");
+ bc = FindProperty("_Gimmick_Shear_Location_Mesh_Renderer_Fix");
+ enabled = (bc.floatValue != 0.0);
+ EditorGUI.BeginChangeCheck();
+ enabled = EditorGUILayout.Toggle("Mesh renderer fix", enabled);
+ EditorGUI.EndChangeCheck();
+ bc.floatValue = enabled ? 1.0f : 0.0f;
+
+ if (enabled) {
+ EditorGUI.indentLevel += 1;
+ bc = FindProperty("_Gimmick_Shear_Location_Mesh_Renderer_Offset");
+ editor.VectorProperty(bc, "Offset");
+ bc = FindProperty("_Gimmick_Shear_Location_Mesh_Renderer_Rotation");
+ editor.VectorProperty(bc, "Rotation");
+ bc = FindProperty("_Gimmick_Shear_Location_Mesh_Renderer_Scale");
+ editor.VectorProperty(bc, "Scale");
+ EditorGUI.indentLevel -= 1;
+ }
+
EditorGUI.indentLevel -= 1;
}
@@ -943,12 +961,68 @@ public class ToonerGUI : ShaderGUI {
EditorGUI.indentLevel -= 1;
}
+ void DoGimmickPixellate() {
+ MaterialProperty bc;
+ bc = FindProperty("_Gimmick_Pixellate_Enable_Static");
+ bool enabled = (bc.floatValue != 0.0);
+ EditorGUI.BeginChangeCheck();
+ enabled = EditorGUILayout.Toggle("Pixellate", enabled);
+ EditorGUI.EndChangeCheck();
+ bc.floatValue = enabled ? 1.0f : 0.0f;
+ SetKeyword("_PIXELLATE", enabled);
+
+ if (!enabled) {
+ return;
+ }
+
+ EditorGUI.indentLevel += 1;
+
+ bc = FindProperty("_Gimmick_Pixellate_Resolution_U");
+ editor.FloatProperty(bc, "Resolution (U)");
+ bc = FindProperty("_Gimmick_Pixellate_Resolution_V");
+ editor.FloatProperty(bc, "Resolution (V)");
+ bc = FindProperty("_Gimmick_Pixellate_Effect_Mask");
+ editor.TexturePropertySingleLine(
+ MakeLabel(bc, "Effect mask"),
+ bc);
+
+ EditorGUI.indentLevel -= 1;
+ }
+
+ void DoGimmickTrochoid() {
+ MaterialProperty bc;
+ bc = FindProperty("_Trochoid_Enable_Static");
+ bool enabled = (bc.floatValue != 0.0);
+ EditorGUI.BeginChangeCheck();
+ enabled = EditorGUILayout.Toggle("Trochoid", enabled);
+ EditorGUI.EndChangeCheck();
+ bc.floatValue = enabled ? 1.0f : 0.0f;
+ SetKeyword("_TROCHOID", enabled);
+
+ if (!enabled) {
+ return;
+ }
+
+ EditorGUI.indentLevel += 1;
+
+ bc = FindProperty("_Trochoid_R");
+ editor.FloatProperty(bc, "R");
+ bc = FindProperty("_Trochoid_r");
+ editor.FloatProperty(bc, "r");
+ bc = FindProperty("_Trochoid_d");
+ editor.FloatProperty(bc, "d");
+
+ EditorGUI.indentLevel -= 1;
+ }
+
void DoGimmicks() {
DoGimmickFlatColor();
DoGimmickQuantizeLocation();
DoGimmickShearLocation();
DoGimmickEyes00();
+ DoGimmickPixellate();
+ DoGimmickTrochoid();
}
enum RenderingMode {
diff --git a/feature_macros.cginc b/feature_macros.cginc
index 66f626f..ea9edf8 100644
--- a/feature_macros.cginc
+++ b/feature_macros.cginc
@@ -89,6 +89,8 @@
#pragma shader_feature_local _ _GIMMICK_QUANTIZE_LOCATION_AUDIOLINK
#pragma shader_feature_local _ _GIMMICK_SHEAR_LOCATION
#pragma shader_feature_local _ _GIMMICK_EYES_00
+#pragma shader_feature_local _ _PIXELLATE
+#pragma shader_feature_local _ _TROCHOID
#endif // __FEATURE_MACROS_INC
diff --git a/globals.cginc b/globals.cginc
index 6f7b434..bec4b01 100644
--- a/globals.cginc
+++ b/globals.cginc
@@ -315,6 +315,10 @@ float _Gimmick_Quantize_Location_Audiolink_Strength;
float _Gimmick_Shear_Location_Enable_Static;
float _Gimmick_Shear_Location_Enable_Dynamic;
float4 _Gimmick_Shear_Location_Strength;
+float _Gimmick_Shear_Location_Mesh_Renderer_Fix;
+float4 _Gimmick_Shear_Location_Mesh_Renderer_Offset;
+float4 _Gimmick_Shear_Location_Mesh_Renderer_Rotation;
+float4 _Gimmick_Shear_Location_Mesh_Renderer_Scale;
#endif
#if defined(_GIMMICK_EYES_00)
@@ -322,5 +326,18 @@ float _Gimmick_Eyes00_Enable_Static;
texture2D _Gimmick_Eyes00_Effect_Mask;
#endif
+#if defined(_PIXELLATE)
+float _Gimmick_Pixellate_Enable_Static;
+float _Gimmick_Pixellate_Resolution_U;
+float _Gimmick_Pixellate_Resolution_V;
+texture2D _Gimmick_Pixellate_Effect_Mask;
+#endif
+
+#if defined(_TROCHOID)
+float _Trochoid_R;
+float _Trochoid_r;
+float _Trochoid_d;
+#endif
+
#endif
diff --git a/shear_math.cginc b/shear_math.cginc
new file mode 100644
index 0000000..a2eded2
--- /dev/null
+++ b/shear_math.cginc
@@ -0,0 +1,60 @@
+#ifndef __SHEAR_MATH_INC
+#define __SHEAR_MATH_INC
+
+#if defined(_GIMMICK_SHEAR_LOCATION)
+
+void getMeshRendererMatrices(bool invert, out float3x3 rot_fix,
+ out float4x4 ts_fix) {
+ if (_Gimmick_Shear_Location_Mesh_Renderer_Fix) {
+ float3 theta = float3(
+ _Gimmick_Shear_Location_Mesh_Renderer_Rotation.x,
+ _Gimmick_Shear_Location_Mesh_Renderer_Rotation.y,
+ _Gimmick_Shear_Location_Mesh_Renderer_Rotation.z);
+ theta = invert ? -theta : theta;
+ float3x3 rotate_x = float3x3(
+ 1, 0, 0,
+ 0, cos(theta.x), -sin(theta.x),
+ 0, sin(theta.x), cos(theta.x));
+ float3x3 rotate_y = float3x3(
+ cos(theta.y), 0, sin(theta.y),
+ 0, 1, 0,
+ -sin(theta.y), 0, cos(theta.y));
+ float3x3 rotate_z = float3x3(
+ cos(theta.z), -sin(theta.z), 0,
+ sin(theta.z), cos(theta.z), 0,
+ 0, 0, 1);
+ rot_fix = invert ?
+ mul(rotate_x, mul(rotate_y, rotate_z)) :
+ mul(rotate_z, mul(rotate_y, rotate_x));
+ float3 scale = float3(
+ _Gimmick_Shear_Location_Mesh_Renderer_Scale.x,
+ _Gimmick_Shear_Location_Mesh_Renderer_Scale.y,
+ _Gimmick_Shear_Location_Mesh_Renderer_Scale.z);
+ scale = invert ? 1 / scale : scale;
+ float3 offset = float3(
+ _Gimmick_Shear_Location_Mesh_Renderer_Offset.x,
+ _Gimmick_Shear_Location_Mesh_Renderer_Offset.y,
+ _Gimmick_Shear_Location_Mesh_Renderer_Offset.z);
+ offset = invert ? -offset : offset;
+ ts_fix = float4x4(
+ scale.x, 0, 0, offset.x,
+ 0, scale.y, 0, offset.y,
+ 0, 0, scale.z, offset.z,
+ 0, 0, 0, 1);
+ } else {
+ rot_fix = float3x3(
+ 1, 0, 0,
+ 0, 1, 0,
+ 0, 0, 1);
+ ts_fix = float4x4(
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1);
+ }
+}
+
+#endif // _GIMMICK_SHEAR_LOCATION
+
+#endif // __SHEAR_MATH_INC
+
diff --git a/tooner.shader b/tooner.shader
index 61304e1..a9f5ca7 100644
--- a/tooner.shader
+++ b/tooner.shader
@@ -250,6 +250,10 @@ Shader "yum_food/tooner"
_Gimmick_Shear_Location_Enable_Static("Enable shear location gimmick", Float) = 0.0
_Gimmick_Shear_Location_Enable_Dynamic("Enable shear location gimmick", Float) = 0.0
_Gimmick_Shear_Location_Strength("Strength", Vector) = (1, 1, 1, 1)
+ _Gimmick_Shear_Location_Mesh_Renderer_Fix("Mesh renderer fix", Float) = 0.0
+ _Gimmick_Shear_Location_Mesh_Renderer_Offset("Mesh renderer offset", Vector) = (0, 0, 0, 0)
+ _Gimmick_Shear_Location_Mesh_Renderer_Rotation("Mesh renderer rotation", Vector) = (0, 0, 0, 0)
+ _Gimmick_Shear_Location_Mesh_Renderer_Scale("Mesh renderer scale", Vector) = (0, 0, 0, 0)
_Gimmick_Vertex_Normal_Slide_Enable_Static("Enable vertex normal slide", Float) = 0.0
_Gimmick_Vertex_Normal_Slide_Enable_Dynamic("Enable vertex normal slide", Float) = 0.0
@@ -257,6 +261,16 @@ Shader "yum_food/tooner"
_Gimmick_Eyes00_Enable_Static("Enable eyes 00", Float) = 0.0
_Gimmick_Eyes00_Effect_Mask("Effect mask", 2D) = "white"
+
+ _Gimmick_Pixellate_Enable_Static("Enable pixellation", Float) = 0.0
+ _Gimmick_Pixellate_Resolution_U("Resolution (U)", Float) = 64
+ _Gimmick_Pixellate_Resolution_V("Resolution (V)", Float) = 64
+ _Gimmick_Pixellate_Effect_Mask("Effect mask", 2D) = "white"
+
+ _Trochoid_Enable_Static("Enable trochoid", Float) = 0.0
+ _Trochoid_R("R", Float) = 5.0
+ _Trochoid_r("r", Float) = 3.0
+ _Trochoid_d("d", Float) = 5.0
}
SubShader
{
diff --git a/tooner_lighting.cginc b/tooner_lighting.cginc
index 8eae8ef..4288293 100644
--- a/tooner_lighting.cginc
+++ b/tooner_lighting.cginc
@@ -11,7 +11,9 @@
#include "motion.cginc"
#include "pbr.cginc"
#include "poi.cginc"
+#include "shear_math.cginc"
#include "tooner_scroll.cginc"
+#include "trochoid_math.cginc"
#include "oklab.cginc"
struct tess_data
@@ -90,14 +92,39 @@ v2f vert(appdata v)
}
#endif
-#if defined(_GIMMICK_SHEAR_LOCATION)
+#if defined(_TROCHOID)
+ {
+#define PI 3.14159265
+#define TAU PI * 2.0
+ float theta = v.uv0.x * TAU;
+ float r0 = length(v.vertex.xyz);
+
+ float x = v.vertex.x;
+ float y = v.vertex.y;
+ float z = v.vertex.z;
+
+ v.vertex.xyz = trochoid_map(theta, r0, z);
+ }
+#endif
+
+#if !defined(_SCROLL) && defined(_GIMMICK_SHEAR_LOCATION)
if (_Gimmick_Shear_Location_Enable_Dynamic) {
- v.vertex = mul(float4x4(
- _Gimmick_Shear_Location_Strength.x, 0, 0, 0,
- 0, _Gimmick_Shear_Location_Strength.y, 0, 0,
- 0, 0, _Gimmick_Shear_Location_Strength.z, 0,
- 0, 0, 0, _Gimmick_Shear_Location_Strength.w),
- v.vertex);
+ float3 p = v.vertex.xyz;
+ float3x3 shear_matrix = float3x3(
+ _Gimmick_Shear_Location_Strength.x, 0, 0,
+ 0, _Gimmick_Shear_Location_Strength.y, 0,
+ 0, 0, _Gimmick_Shear_Location_Strength.z);
+#if 0
+ float3x3 rot_fix, rot_fixi;
+ float4x4 ts_fix, ts_fixi;
+ getMeshRendererMatrices(/*invert=*/false, rot_fix, ts_fix);
+ getMeshRendererMatrices(/*invert=*/true, rot_fixi, ts_fixi);
+ if (_Gimmick_Shear_Location_Mesh_Renderer_Fix) {
+ p = mul(ts_fixi, float4(p, 1)).xyz;
+ }
+#endif
+ p = mul(shear_matrix, p);
+ v.vertex.xyz = p;
}
#endif
@@ -341,6 +368,29 @@ void geom(triangle v2f tri_in[3],
float3 v1_objPos = mul(unity_WorldToObject, float4(v1.worldPos, 1));
float3 v2_objPos = mul(unity_WorldToObject, float4(v2.worldPos, 1));
+#if defined(_GIMMICK_SHEAR_LOCATION)
+ if (_Gimmick_Shear_Location_Enable_Dynamic) {
+ v0_objPos = mul(float3x3(
+ _Gimmick_Shear_Location_Strength.x, 0, 0,
+ 0, _Gimmick_Shear_Location_Strength.y, 0,
+ 0, 0, _Gimmick_Shear_Location_Strength.z),
+ v0_objPos);
+ v1_objPos = mul(float3x3(
+ _Gimmick_Shear_Location_Strength.x, 0, 0,
+ 0, _Gimmick_Shear_Location_Strength.y, 0,
+ 0, 0, _Gimmick_Shear_Location_Strength.z),
+ v1_objPos);
+ v2_objPos = mul(float3x3(
+ _Gimmick_Shear_Location_Strength.x, 0, 0,
+ 0, _Gimmick_Shear_Location_Strength.y, 0,
+ 0, 0, _Gimmick_Shear_Location_Strength.z),
+ v2_objPos);
+ v0.worldPos.xyz = mul(unity_ObjectToWorld, v0_objPos);
+ v1.worldPos.xyz = mul(unity_ObjectToWorld, v1_objPos);
+ v2.worldPos.xyz = mul(unity_ObjectToWorld, v2_objPos);
+ }
+#endif
+
v0.vertex = UnityObjectToClipPos(v0_objPos);
v1.vertex = UnityObjectToClipPos(v1_objPos);
v2.vertex = UnityObjectToClipPos(v2_objPos);
@@ -694,12 +744,52 @@ float3 getOverlayEmission(PbrOverlay ov, v2f i, float iddx, float iddy)
return em;
}
+#if defined(_PIXELLATE)
+float2 pixellate_uv(int2 px_res, float2 uv)
+{
+ return floor(uv * px_res) / px_res;
+}
+
+float4 pixellate_color(int2 px_res, float2 uv, float4 c)
+{
+ float2 px_intra_uv = fmod(uv * px_res, 1.0);
+ float2 px_extra_uv = floor(uv * px_res) / px_res;
+
+ float2 px_uv = floor(uv * px_res) / px_res;
+ if (px_intra_uv.y > 0.1 && px_intra_uv.y < 0.9) {
+ if (px_intra_uv.x < 0.333) {
+ c.xyz = float3(1, 0, 0);
+ } else if (px_intra_uv.x < 0.666) {
+ c.yxz = float3(1, 0, 0);
+ } else {
+ c.zxy = float3(1, 0, 0);
+ }
+ c *= 3;
+ } else {
+ c = 0;
+ }
+
+ return c;
+}
+#endif
+
float4 effect(inout v2f i)
{
float iddx = ddx(i.uv.x) * _Mip_Multiplier;
float iddy = ddx(i.uv.y) * _Mip_Multiplier;
const float3 view_dir = normalize(_WorldSpaceCameraPos - i.worldPos);
+#if defined(_TROCHOID)
+ {
+ i.normal = trochoid_normal(i.objPos.xyz, i.uv);
+
+ float theta = i.uv.x * TAU;
+ float r0 = length(i.objPos.xyz);
+ float z = i.objPos.z;
+ i.objPos.xyz = trochoid_map(theta, r0, z);
+ }
+#endif
+
#if defined(_UVSCROLL)
float2 orig_uv = i.uv;
float uv_scroll_mask = round(_UVScroll_Mask.SampleGrad(linear_repeat_s, i.uv, iddx, iddy));
@@ -720,6 +810,24 @@ float4 effect(inout v2f i)
}
#endif
+#if defined(_PIXELLATE)
+ {
+ const int2 px_res = int2(
+ _Gimmick_Pixellate_Resolution_U,
+ _Gimmick_Pixellate_Resolution_V);
+
+ float2 uv = pixellate_uv(px_res, i.uv);
+ const float2 duv = float2(ddx(i.uv.x), ddy(i.uv.y)) / 16;
+ float4 color = _Gimmick_Pixellate_Effect_Mask.SampleGrad(linear_clamp_s, uv, duv.x, duv.y);
+ float2 fw = float2(fwidth(i.uv.x), fwidth(i.uv.y));
+ float fwm = max(fw.x, fw.y);
+ color.rgb *= albedo;
+ float4 px_color = pixellate_color(px_res, i.uv, color);
+ albedo = lerp(albedo, px_color, pow(0.9, fwm * 100));
+ }
+#endif
+
+
#if defined(_RENDERING_CUTOUT)
#if defined(_RENDERING_CUTOUT_STOCHASTIC)
float ar = rand2(i.uv);
diff --git a/tooner_outline_pass.cginc b/tooner_outline_pass.cginc
index c7b6ded..72deee6 100644
--- a/tooner_outline_pass.cginc
+++ b/tooner_outline_pass.cginc
@@ -8,6 +8,7 @@
#include "globals.cginc"
#include "math.cginc"
#include "pbr.cginc"
+#include "trochoid_math.cginc"
#include "tooner_scroll.cginc"
#include "UnityCG.cginc"
@@ -25,7 +26,22 @@ struct tess_factors {
v2f vert(appdata v)
{
-#if defined(_GIMMICK_SHEAR_LOCATION)
+#if defined(_TROCHOID)
+ {
+#define PI 3.14159265
+#define TAU PI * 2.0
+ float theta = v.uv0.x * TAU;
+ float r0 = length(v.vertex.xyz);
+
+ float x = v.vertex.x;
+ float y = v.vertex.y;
+ float z = v.vertex.z;
+
+ v.vertex.xyz = trochoid_map(theta, r0, z);
+ }
+#endif
+
+#if !defined(_SCROLL) && defined(_GIMMICK_SHEAR_LOCATION)
if (_Gimmick_Shear_Location_Enable_Dynamic) {
v.vertex = mul(float4x4(
_Gimmick_Shear_Location_Strength.x, 0, 0, 0,
@@ -270,6 +286,29 @@ void geom(triangle v2f tri_in[3],
float3 v1_objPos = mul(unity_WorldToObject, float4(v1.worldPos, 1));
float3 v2_objPos = mul(unity_WorldToObject, float4(v2.worldPos, 1));
+#if defined(_GIMMICK_SHEAR_LOCATION)
+ if (_Gimmick_Shear_Location_Enable_Dynamic) {
+ v0_objPos = mul(float3x3(
+ _Gimmick_Shear_Location_Strength.x, 0, 0,
+ 0, _Gimmick_Shear_Location_Strength.y, 0,
+ 0, 0, _Gimmick_Shear_Location_Strength.z),
+ v0_objPos);
+ v1_objPos = mul(float3x3(
+ _Gimmick_Shear_Location_Strength.x, 0, 0,
+ 0, _Gimmick_Shear_Location_Strength.y, 0,
+ 0, 0, _Gimmick_Shear_Location_Strength.z),
+ v1_objPos);
+ v2_objPos = mul(float3x3(
+ _Gimmick_Shear_Location_Strength.x, 0, 0,
+ 0, _Gimmick_Shear_Location_Strength.y, 0,
+ 0, 0, _Gimmick_Shear_Location_Strength.z),
+ v2_objPos);
+ v0.worldPos.xyz = mul(unity_ObjectToWorld, v0_objPos);
+ v1.worldPos.xyz = mul(unity_ObjectToWorld, v1_objPos);
+ v2.worldPos.xyz = mul(unity_ObjectToWorld, v2_objPos);
+ }
+#endif
+
v0.vertex = UnityObjectToClipPos(v0_objPos);
v1.vertex = UnityObjectToClipPos(v1_objPos);
v2.vertex = UnityObjectToClipPos(v2_objPos);
diff --git a/trochoid_math.cginc b/trochoid_math.cginc
new file mode 100644
index 0000000..f116db0
--- /dev/null
+++ b/trochoid_math.cginc
@@ -0,0 +1,90 @@
+#ifndef __TROCHOID_MATH
+#define __TROCHOID_MATH
+
+#if defined(_TROCHOID)
+
+#include "globals.cginc"
+
+#define PI 3.14159265
+#define TAU PI * 2.0
+
+float3 trochoid_map(float theta, float r0, float3 vert_z)
+{
+ r0 *= r0;
+ r0 *= 100;
+
+ float R = _Trochoid_R;
+ float r = _Trochoid_r;
+ float d = _Trochoid_d;
+
+ theta *= max(R, r);
+ float theta_t = theta + _Time[2];
+
+ float x = (R - r) * cos(theta_t) + d * cos((R - r) * theta_t / r);
+ float y = (R - r) * sin(theta_t) - d * sin((R - r) * theta_t / r);
+ float z = vert_z + cos(theta_t * 5) * .1 + theta * .0002;
+
+ float3 result = float3(x, y, z) * r0;
+ result.xy *= 0.1;
+ return result;
+}
+
+float trochoid_normal(float3 objPos, float2 uv)
+{
+ float theta = uv.x * TAU;
+ float r0 = length(objPos.xyz);
+
+ float x = objPos.x;
+ float y = objPos.y;
+ float z = objPos.z;
+
+ float e = 5E-2;
+ float small_step = 1E-2 * e;
+ float du_dt = (trochoid_map(theta + small_step, r0, z) - trochoid_map(theta - small_step, r0, z)) / small_step;
+ small_step = 1E-4 * e;
+ float du_dr = (trochoid_map(theta, r0 + small_step, z) - trochoid_map(theta, r0 - small_step, z)) / small_step;
+ small_step = 1E-5 * e;
+ float du_dz = (trochoid_map(theta, r0, z + small_step) - trochoid_map(theta, r0, z - small_step)) / small_step;
+
+ // U(T(x, y, z), R(x, y, z), Z(x, y, z))
+ // T(x, y, z) = atan2(y, x)
+ // R(x, y, z) = length(float3(x, y, z))
+ // Z(x, y, z) = z
+ // U(a, b, c) = trochoid_map(a, b, c)
+ // dU/dx = dU/dT dT/dx + dU/dR dR/dx + dU/dZ dZ/dx
+ // dU/dy = dU/dT dT/dy + dU/dR dR/dy + dU/dZ dZ/dy
+ // dU/dz = dU/dT dT/dz + dU/dR dR/dz + dU/dZ dZ/dz
+ // dT/dx = d/dx atan2(y, x) = -y / (x**2 + y**2)
+ // dT/dy = d/dx atan2(y, x) = x / (x**2 + y**2)
+ // dT/dz = d/dz atan2(y, x) = 0
+ // dR/dx = d/dx sqrt(x**2 + y**2 + z**2) = x / sqrt(x**2 + y**2 + z**2)
+ // dR/dy = d/dy sqrt(x**2 + y**2 + z**2) = y / sqrt(x**2 + y**2 + z**2)
+ // dR/dz = d/dy sqrt(x**2 + y**2 + z**2) = z / sqrt(x**2 + y**2 + z**2)
+ // dZ/dx = 0
+ // dZ/dy = 0
+ // dZ/dz = 1
+ float xy_norm = sqrt(x * x + y * y);
+ float dt_dx = -y / xy_norm;
+ float dt_dy = x / xy_norm;
+ float dt_dz = 0;
+ float xyz_norm = sqrt(x * x + y * y + z * z);
+ float dr_dx = x / xyz_norm;
+ float dr_dy = y / xyz_norm;
+ float dr_dz = z / xyz_norm;
+ float dz_dx = 0;
+ float dz_dy = 0;
+ float dz_dz = 1;
+
+ float3 normal =
+ normalize(
+ float3(
+ du_dt * dt_dx + du_dr * dr_dx + du_dz * dz_dx,
+ du_dt * dt_dy + du_dr * dr_dy + du_dz * dz_dy,
+ du_dt * dt_dz + du_dr * dr_dz + du_dz * dz_dz));
+ return UnityObjectToWorldNormal(normal);
+}
+
+#endif // _TROCHOID
+
+#endif // __TROCHOID_MATH
+