summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoryum <yum.food.vr@gmail.com>2026-01-24 15:03:30 -0800
committeryum <yum.food.vr@gmail.com>2026-01-24 15:03:30 -0800
commit4885fb42bd42f5dfb7fdc198bd0cc42be7959aaf (patch)
tree1e8f9eea89eda456244690654608eaca39c6e760
parent97ee1dcf26b62f48e351b6392c11a30775619442 (diff)
Remove worldPos interpolator
-rw-r--r--2ner.cginc61
-rw-r--r--2ner.shader6
-rw-r--r--glitter.cginc5
-rw-r--r--globals.cginc4
-rw-r--r--interpolators.cginc16
-rw-r--r--math.cginc678
-rw-r--r--tessellation.cginc1
-rw-r--r--yum_brdf.cginc6
-rw-r--r--yum_lighting.cginc47
-rw-r--r--yum_pbr.cginc8
10 files changed, 433 insertions, 399 deletions
diff --git a/2ner.cginc b/2ner.cginc
index 3835002..c886fe6 100644
--- a/2ner.cginc
+++ b/2ner.cginc
@@ -185,7 +185,6 @@ v2f vert(appdata v) {
o.uv23.z = 1.0 - o.uv23.z;
}
#endif
- o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.objPos = v.vertex;
// These are used to convert normals from tangent space to world space.
@@ -206,16 +205,17 @@ v2f vert(appdata v) {
// Calculate vertex lights
#ifdef VERTEXLIGHT_ON
+ float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
#if defined(_WRAPPED_LIGHTING)
o.vertexLight = Shade4PointLightsWrapped(
unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,
- unity_4LightAtten0, o.worldPos, o.normal, _Wrap_NoL_Diffuse_Strength);
+ unity_4LightAtten0, worldPos, o.normal, _Wrap_NoL_Diffuse_Strength);
#else
o.vertexLight = Shade4PointLights(
unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,
- unity_4LightAtten0, o.worldPos, o.normal);
+ unity_4LightAtten0, worldPos, o.normal);
#endif
#else
o.vertexLight = 0;
@@ -225,7 +225,7 @@ v2f vert(appdata v) {
}
//ifex _Fur_Enabled==0
-[maxvertexcount(3 * 10)]
+[maxvertexcount(30)]
void geom(triangle v2f input[3], inout TriangleStream<v2f> stream) {
#if defined(_FUR)
#if defined(_FUR_MASK)
@@ -233,31 +233,44 @@ void geom(triangle v2f input[3], inout TriangleStream<v2f> stream) {
#else
float fur_mask = 1;
#endif
+
+ stream.Append(input[0]);
+ stream.Append(input[1]);
+ stream.Append(input[2]);
+ stream.RestartStrip();
+
[branch]
if (fur_mask < 0.5) {
- stream.Append(input[0]);
- stream.Append(input[1]);
- stream.Append(input[2]);
- stream.RestartStrip();
return;
}
+ float3 gravDirObj = normalize(mul(unity_WorldToObject, float3(0, -1, 0)));
+
+ // Compute gravity direction minus component that would compress this shell.
+ // Could compute average normal but this is probably good enough.
+ float3 gravDirNoRegress = gravDirObj - min(0, input[0].normal * dot(gravDirObj, input[0].normal));
+
+ float3 worldPos = mul(unity_ObjectToWorld, input[0].objPos).xyz;
+ float radius = length(_WorldSpaceCameraPos - worldPos);
+ uint fur_layers = lerp(_Fur_Layers - 3, 5, saturate((radius - _Fur_Min_Dist) / (_Fur_Max_Dist - _Fur_Min_Dist)));
+
[loop]
- for (int layer = 0; layer < _Fur_Layers; layer++) {
- float t = (float) layer / (float) max(_Fur_Layers - 1, 1);
+ for (uint layer = 0; layer < fur_layers; layer++) {
+ float t = (float) layer / (float) max(fur_layers - 4, 1);
+
float offset = t * _Fur_Thickness;
- [unroll]
- for (int i = 0; i < 3; i++) {
- v2f o = input[i];
- float3 normal_ws = UnityObjectToWorldNormal(o.normal);
- o.worldPos.xyz += normal_ws * offset;
- o.objPos.xyz += o.normal * offset;
- o.pos = UnityWorldToClipPos(o.worldPos);
- o.vertexLight.w = t;
- stream.Append(o);
- }
- // We do not restart strips. Looks a little nicer.
+ v2f o = input[layer % 3];
+ float3 normal_ws = UnityObjectToWorldNormal(o.normal);
+
+ float3 dir = lerp(o.normal, gravDirNoRegress, t * t * _Fur_Gravity_Strength);
+
+ o.objPos.xyz += dir * offset;
+
+ float3 worldPos = mul(unity_ObjectToWorld, o.objPos).xyz;
+ o.pos = UnityWorldToClipPos(worldPos);
+ o.vertexLight.w = t;
+ stream.Append(o);
}
#else
stream.Append(input[0]);
@@ -291,8 +304,9 @@ float4 frag(v2f i, uint facing : SV_IsFrontFace
i.tangent = normalize(i.tangent);
f2f f = (f2f) 0;
+ f.worldPos = mul(unity_ObjectToWorld, i.objPos);
f.binormal = cross(i.tangent, i.normal);
- f.eyeVec = i.worldPos - _WorldSpaceCameraPos;
+ f.eyeVec = f.worldPos - _WorldSpaceCameraPos;
f.viewDir = normalize(f.eyeVec);
f.tbn = float3x3(
i.tangent,
@@ -424,7 +438,6 @@ float4 frag(v2f i, uint facing : SV_IsFrontFace
Custom30Output c30_out = (Custom30Output) 0;
#endif
i.normal = c30_out.normal;
- i.worldPos = mul(unity_ObjectToWorld, float4(c30_out.objPos, 1));
float4 c30_clipPos = UnityObjectToClipPos(i.objPos);
float4 c30_screenPos = ComputeScreenPos(c30_clipPos);
i.pos = c30_screenPos;
@@ -535,7 +548,7 @@ float4 frag(v2f i, uint facing : SV_IsFrontFace
#if defined(_UNLIT)
float4 lit = pbr.albedo;
#else
- float4 lit = YumBRDF(i, l, pbr);
+ float4 lit = YumBRDF(i, f, l, pbr);
#endif
#if defined(_HARNACK_TRACING)
diff --git a/2ner.shader b/2ner.shader
index 6cac4f9..ae966d1 100644
--- a/2ner.shader
+++ b/2ner.shader
@@ -111,10 +111,14 @@ Shader "yum_food/2ner"
[HideInInspector] m_start_Fur("Fur", Float) = 0
[ThryToggle(_FUR)]_Fur_Enabled("Enable", Float) = 0
_Fur_Thickness("Thickness", Float) = 1
- [IntRange] _Fur_Layers("Layers", Range(1, 12)) = 1
+ [IntRange] _Fur_Layers("Layers", Range(1, 30)) = 1
_Fur_Heightmap("Heightmap", 2D) = "black" {}
_Fur_Heightmap_Mip_Bias("Heightmap mip bias", Range(-4, 4)) = 0
_Fur_AO_Strength("Ambient occlusion strength", Range(0, 1)) = 1
+ _Fur_Gravity_Strength("Gravity strength", Range(0, 1)) = 1
+ _Fur_Thickness_Power("Thickness power", Range(-1, 5)) = 0
+ _Fur_Min_Dist("Min distance", Float) = 3
+ _Fur_Max_Dist("Max distance", Float) = 10
[HideInInspector] m_start_Fur_Mask("Mask", Float) = 0
[ThryToggle(_FUR_MASK)]_Fur_Mask_Enabled("Enable", Float) = 0
diff --git a/glitter.cginc b/glitter.cginc
index dc353df..637a324 100644
--- a/glitter.cginc
+++ b/glitter.cginc
@@ -16,6 +16,7 @@ struct GlitterParams {
float center_randomization_range;
float size_randomization_range;
float existence_chance;
+ float seed;
#if defined(_GLITTER_ANGLE_LIMIT)
float angle_limit;
float angle_limit_transition_width;
@@ -43,8 +44,8 @@ float4 getGlitter(v2f i, f2f f, GlitterParams params, float3 normal) {
float2 p = uv + glitter_offset_vectors[layer_i] * params.cell_size * 0.5;
float3 cell_id = float3(floor(p / params.cell_size), layer_i);
- float cell_rand = rand3(cell_id*.0001);
- float cell_rand2 = rand3((cell_id+1)*.0001);
+ float cell_rand = rand3(cell_id*.0001+params.seed);
+ float cell_rand2 = rand3((cell_id+1)*.0001+params.seed);
p = glsl_mod(p, params.cell_size);
p -= params.cell_size * 0.5;
// Apply center randomization
diff --git a/globals.cginc b/globals.cginc
index 00f9a31..d374c2d 100644
--- a/globals.cginc
+++ b/globals.cginc
@@ -150,6 +150,10 @@ texture2D _Fur_Heightmap;
float4 _Fur_Heightmap_ST;
float _Fur_Heightmap_Mip_Bias;
float _Fur_AO_Strength;
+float _Fur_Gravity_Strength;
+float _Fur_Thickness_Power;
+float _Fur_Min_Dist;
+float _Fur_Max_Dist;
#endif
#if defined(_FUR_MASK)
diff --git a/interpolators.cginc b/interpolators.cginc
index 020c80f..6a632d1 100644
--- a/interpolators.cginc
+++ b/interpolators.cginc
@@ -24,26 +24,26 @@ struct v2f {
float4 uv01 : TEXCOORD0;
float4 uv23 : TEXCOORD1; // just one more uv slot bro please
float4 objPos : TEXCOORD2;
- float3 worldPos : TEXCOORD3;
- float3 normal : TEXCOORD4;
- float3 tangent : TEXCOORD5;
- float4 vertexLight : TEXCOORD6; // vertexLight.xyz | furLayer
- UNITY_LIGHTING_COORDS(7,8)
+ float3 normal : TEXCOORD3;
+ float3 tangent : TEXCOORD4;
+ float4 vertexLight : TEXCOORD5; // vertexLight.xyz | furLayer
+ UNITY_LIGHTING_COORDS(6,7)
#if defined(V2F_ORIG_POS)
- float3 orig_pos : TEXCOORD9;
+ float3 orig_pos : TEXCOORD8;
#endif
#if defined(V2F_COLOR)
- float4 color : TEXCOORD10;
+ float4 color : TEXCOORD9;
#endif
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};
-// Fragment shader common data (fragment 2 fragment)
+// Fragment shader common data. f2f = fragment 2 fragment
struct f2f {
+ float3 worldPos;
float3 binormal;
float3 eyeVec;
float3 viewDir;
diff --git a/math.cginc b/math.cginc
index cbf162c..b0fa129 100644
--- a/math.cginc
+++ b/math.cginc
@@ -1,312 +1,312 @@
-#ifndef __MATH_INC
-#define __MATH_INC
-
-#include "pema99.cginc"
-
-#define PI 3.14159265358979323846264f
-#define TAU (2.0f * PI)
-#define HALF_PI (PI * 0.5f)
-#define RCP_PI (1.0f / PI)
-#define RCP_TAU (1.0f / TAU)
-#define PHI 1.618033989f
-#define RCP_PHI 0.618033989f
-#define SQRT_2 1.414213562f
-#define SQRT_2_RCP 0.707106781f
-#define RCP_SQRT_2 0.707106781f
-#define RCP_SQRT_3 0.577350269f
-#define TWO_OVER_THREE 0.6666666666666666f
-#define SQRT_3_OVER_2 0.8660254037844386f
-#define EULERS_CONSTANT 2.718281828f
-
-
-float pow5(float x)
-{
- float tmp = x * x;
- return (tmp * tmp) * x;
-}
-
-// Wrap NoL. Assume it's already clamped.
-// At k=0, you get standard lambertian shading.
-// At k=0.5, you get half-lambertian shading.
-// At k=1.0, you get flat shading.
-// k must be on [0, 1].
-// Energy preserving, within some small bound.
-float wrapNoL(float NoL, float k) {
- float lambertian = NoL;
- float half_lambertian = pow(max(1e-4, (NoL + 0.5f) / (1.0f + 0.5f)), 2);
- float flat = RCP_PI;
-
- if (k < 0.5) {
- return lerp(lambertian, half_lambertian, k * 2.0f);
- } else {
- return lerp(half_lambertian, flat, k * 2.0f - 1.0f);
- }
-}
-
-float halfLambertianNoL(float NoL) {
- // https://www.iro.umontreal.ca/~derek/files/jgt_wrap_final.pdf
- float tmp = (NoL + 1) * 0.5;
- return tmp * tmp;
-}
-
-float rand1(float p)
-{
- return frac(sin(p) * 43758.5453123);
-}
-
-float rand2(float2 p)
-{
- return frac(sin(dot(p, float2(12.9898, 78.233))) * 43758.5453123);
-}
-
-inline float rand3_dot(float3 p)
-{
- return dot(p, float3(151.0, 157.0, 163.0));
-}
-
-float3 rand3_hash(float3 p)
-{
- // Improved Murmurhash3 by Squirrel Eiserloh (GDC 2017)
- p = float3(dot(p, float3(127.1, 311.7, 74.7)),
- dot(p, float3(269.5, 183.3, 246.1)),
- dot(p, float3(113.5, 271.9, 124.6)));
- return -1.0 + 2.0 * frac(sin(p) * 43758.5453123);
-}
-
-float rand3(float3 p)
-{
- return frac(rand3_hash(p).x);
-}
-
-float2 domainWarp1(float x, uint octaves, float strength, float scale, float speed)
-{
- [loop]
- for (uint i = 0; i < octaves; i++) {
- x += strength * frac(sin(float2(
- dot(x * scale, float2(12.9898, 78.233)),
- dot(x * scale + 1, float2(12.9898, 78.233))) * 43758.5453123));
- }
- return x;
-}
-
-float2 domainWarp2(float2 uv, uint octaves, float strength, float scale, float speed)
-{
- uv *= 0.001;
- [loop]
- for (uint i = 0; i < octaves; i++) {
- uv += strength * frac(sin(float2(
- dot(uv * scale, float2(12.9898, 78.233)),
- dot(uv * scale, float2(36.7539, 50.3658)))) * 43758.5453123);
- }
- uv *= 1000;
- return uv;
-}
-
-float determinant(float3x3 m)
-{
- return (m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1])
- - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]))
- + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]);
-}
-
-float3x3 inverse(float3x3 m)
-{
- float det = determinant(m);
-
- float3x3 adj;
- adj[0][0] = (m[1][1] * m[2][2] - m[1][2] * m[2][1]);
- adj[0][1] = -(m[0][1] * m[2][2] - m[0][2] * m[2][1]);
- adj[0][2] = (m[0][1] * m[1][2] - m[0][2] * m[1][1]);
-
- adj[1][0] = -(m[1][0] * m[2][2] - m[1][2] * m[2][0]);
- adj[1][1] = (m[0][0] * m[2][2] - m[0][2] * m[2][0]);
- adj[1][2] = -(m[0][0] * m[1][2] - m[0][2] * m[1][0]);
-
- adj[2][0] = (m[1][0] * m[2][1] - m[1][1] * m[2][0]);
- adj[2][1] = -(m[0][0] * m[2][1] - m[0][1] * m[2][0]);
- adj[2][2] = (m[0][0] * m[1][1] - m[0][1] * m[1][0]);
-
- return adj * (1.0 / det);
-}
-
-float3 domainWarp3(float3 pos, uint octaves, float strength, float scale, float offset)
-{
- [loop]
- for (uint i = 0; i < octaves; i++) {
- pos += strength * frac(sin(float3(
- rand3_dot(pos * scale + offset),
- rand3_dot(pos * scale + offset + 1),
- rand3_dot(pos * scale + offset + 2)) * 43758.5453123));
- }
- return pos;
-}
-
-void domainWarp3Normals(inout float3 normal, inout float3 tangent, float3 basePos, uint octaves, float strength, float scale, float offset)
-{
- // Use the actual vertex position for correct derivative evaluation.
- float3 p = basePos;
-
- // Start with the identity matrix for the total Jacobian.
- float3x3 J = float3x3(
- 1.0, 0.0, 0.0,
- 0.0, 1.0, 0.0,
- 0.0, 0.0, 1.0
- );
-
- const float k = 43758.5453123;
- // Updated constant vector to match that of rand3_dot (used in domainWarp3)
- const float3 c = float3(151.0, 157.0, 163.0);
-
- for (uint i = 0; i < octaves; i++)
- {
- // Compute the vector v using the same offsetting as in domainWarp3.
- float3 v = float3(
- dot(p * scale + float3(offset, offset, offset), c),
- dot(p * scale + float3(offset + 1.0, offset + 1.0, offset + 1.0), c),
- dot(p * scale + float3(offset + 2.0, offset + 2.0, offset + 2.0), c)
- );
-
- // Compute the warp offset with frac.
- float3 f_val = frac(sin(v) * k);
- float3 warpOffset = strength * f_val;
-
- // Compute the derivative (Jacobian) of the offset.
- float3 cos_v = cos(v);
- float3x3 D = float3x3(
- strength * k * scale * cos_v.x * c.x, strength * k * scale * cos_v.x * c.y, strength * k * scale * cos_v.x * c.z,
- strength * k * scale * cos_v.y * c.x, strength * k * scale * cos_v.y * c.y, strength * k * scale * cos_v.y * c.z,
- strength * k * scale * cos_v.z * c.x, strength * k * scale * cos_v.z * c.y, strength * k * scale * cos_v.z * c.z
- );
-
- // The per–octave Jacobian is I + D.
- float3x3 iterJacobian = float3x3(
- 1.0 + D[0][0], D[0][1], D[0][2],
- D[1][0], 1.0 + D[1][1], D[1][2],
- D[2][0], D[2][1], 1.0 + D[2][2]
- );
-
- // Chain this iteration's Jacobian.
- J = mul(iterJacobian, J);
-
- // Update p for the next iteration.
- p += warpOffset;
- }
-
- // Transform the normal via the inverse-transpose of the total Jacobian.
- float3x3 invTransJ = transpose(inverse(J));
- normal = normalize(mul(invTransJ, normal));
-
- // Transform the tangent via the forward total Jacobian.
- tangent = normalize(mul(J, tangent));
-}
-
-// Alpha blend `dst` onto `src`.
-// Imagine two transparent planes. We're rendering a situation where you're
-// looking through `front` at `behind`.
-float4 alphaBlend(float4 behind, float4 front) {
- return float4(front.rgb * front.a + behind.rgb * (1 - front.a), front.a + behind.a * (1 - front.a));
-}
-
-// Reoriented normal mapping
-// https://blog.selfshadow.com/publications/blending-in-detail/
-// Inputs are in tangent space.
-float3 blendNormalsHill12(float3 n0, float3 n1) {
- n0.z += 1.0;
- n1.xy = -n1.xy;
-
- return normalize(n0 * dot(n0, n1) - n1 * n0.z);
-}
-
-float luminance(float3 color) {
- return dot(color, float3(0.2126, 0.7152, 0.0722));
-}
-
-float median(float3 x) {
- // Get the min and max.
- float x_min= min(min(x.r, x.g), x.b);
- float x_max = max(max(x.r, x.g), x.b);
-
- // Compute (x.r + x.g + x.b) - (x_min + x_max). This gives us the median.
- return (x.r + x.g + x.b) - (x_min + x_max);
-}
-
-// Quaternions
-float4 qmul(float4 q1, float4 q2)
-{
- return float4(
- q2.xyz * q1.w + q1.xyz * q2.w + cross(q1.xyz, q2.xyz),
- q1.w * q2.w - dot(q1.xyz, q2.xyz));
-}
-
-// Vector rotation with a quaternion
-// https://blog.molecular-matters.com/2013/05/24/a-faster-quaternion-vector-multiplication/
-float3 rotate_vector(float3 v, float4 q)
-{
- float3 t = 2.0 * cross(q.xyz, v);
- return v + q.w * t + cross(q.xyz, t);
-}
-
-float4 get_quaternion(float3 axis_normal, float theta) {
- return float4(axis_normal * sin(theta / 2), cos(theta / 2));
-}
-
-void calcNormalInScreenSpace(inout float3 normal, float3 objPos) {
- normal = normalize(cross(ddy(objPos), ddx(objPos)));
-}
-
-// Formulae from here: https://www.rapidtables.com/convert/color/rgb-to-cmyk.html
-float4 rgbToCmyk(float3 rgb) {
- float4 cmyk;
- cmyk[3] = 1 - max(rgb.r, max(rgb.g, rgb.b));
- cmyk[0] = (1 - rgb.r - cmyk[3]) / (1 - cmyk[3]);
- cmyk[1] = (1 - rgb.g - cmyk[3]) / (1 - cmyk[3]);
- cmyk[2] = (1 - rgb.b - cmyk[3]) / (1 - cmyk[3]);
- return cmyk;
-}
-
-float rgbToCmyk_C(float3 rgb) {
- float k = 1 - max(rgb.r, max(rgb.g, rgb.b));
- float c = (1 - rgb.r - k) / (1 - k);
- return c;
-}
-float rgbToCmyk_M(float3 rgb) {
- float k = 1 - max(rgb.r, max(rgb.g, rgb.b));
- float m = (1 - rgb.g - k) / (1 - k);
- return m;
-}
-float rgbToCmyk_Y(float3 rgb) {
- float k = 1 - max(rgb.r, max(rgb.g, rgb.b));
- float y = (1 - rgb.b - k) / (1 - k);
- return y;
-}
-float rgbToCmyk_K(float3 rgb) {
- float k = 1 - max(rgb.r, max(rgb.g, rgb.b));
- return k;
-}
-
-float3 cmykToRgb(float4 cmyk) {
- return float3(
- (1 - cmyk[0]) * (1 - cmyk[3]),
- (1 - cmyk[1]) * (1 - cmyk[3]),
- (1 - cmyk[2]) * (1 - cmyk[3]));
-}
-
-// Cartesian to cube hexagonal coordinates.
-// Based on this: https://backdrifting.net/post/064_hex_grids
-float3 cart_to_hex(float2 cart) {
- float p = cart.x;
- float q = dot(cart, float2(0.5f, SQRT_3_OVER_2));
- float r = dot(cart, float2(0.5f, -SQRT_3_OVER_2));
-
- return float3(p, q, r) * TWO_OVER_THREE;
-}
-
-float2 hex_to_cart(float3 cart) {
- return float2(
- cart[0] + (cart[1] + cart[2]) * 0.5f,
- (cart[1] - cart[2]) * SQRT_3_OVER_2);
-}
-
+#ifndef __MATH_INC
+#define __MATH_INC
+
+#include "pema99.cginc"
+
+#define PI 3.14159265358979323846264f
+#define TAU (2.0f * PI)
+#define HALF_PI (PI * 0.5f)
+#define RCP_PI (1.0f / PI)
+#define RCP_TAU (1.0f / TAU)
+#define PHI 1.618033989f
+#define RCP_PHI 0.618033989f
+#define SQRT_2 1.414213562f
+#define SQRT_2_RCP 0.707106781f
+#define RCP_SQRT_2 0.707106781f
+#define RCP_SQRT_3 0.577350269f
+#define TWO_OVER_THREE 0.6666666666666666f
+#define SQRT_3_OVER_2 0.8660254037844386f
+#define EULERS_CONSTANT 2.718281828f
+
+
+float pow5(float x)
+{
+ float tmp = x * x;
+ return (tmp * tmp) * x;
+}
+
+// Wrap NoL. Assume it's already clamped.
+// At k=0, you get standard lambertian shading.
+// At k=0.5, you get half-lambertian shading.
+// At k=1.0, you get flat shading.
+// k must be on [0, 1].
+// Energy preserving, within some small bound.
+float wrapNoL(float NoL, float k) {
+ float lambertian = NoL;
+ float half_lambertian = pow(max(1e-4, (NoL + 0.5f) / (1.0f + 0.5f)), 2);
+ float flat = RCP_PI;
+
+ if (k < 0.5) {
+ return lerp(lambertian, half_lambertian, k * 2.0f);
+ } else {
+ return lerp(half_lambertian, flat, k * 2.0f - 1.0f);
+ }
+}
+
+float halfLambertianNoL(float NoL) {
+ // https://www.iro.umontreal.ca/~derek/files/jgt_wrap_final.pdf
+ float tmp = (NoL + 1) * 0.5;
+ return tmp * tmp;
+}
+
+float rand1(float p)
+{
+ return frac(sin(p) * 43758.5453123);
+}
+
+float rand2(float2 p)
+{
+ return frac(sin(dot(p, float2(12.9898, 78.233))) * 43758.5453123);
+}
+
+inline float rand3_dot(float3 p)
+{
+ return dot(p, float3(151.0, 157.0, 163.0));
+}
+
+float3 rand3_hash(float3 p)
+{
+ // Improved Murmurhash3 by Squirrel Eiserloh (GDC 2017)
+ p = float3(dot(p, float3(127.1, 311.7, 74.7)),
+ dot(p, float3(269.5, 183.3, 246.1)),
+ dot(p, float3(113.5, 271.9, 124.6)));
+ return -1.0 + 2.0 * frac(sin(p) * 43758.5453123);
+}
+
+float rand3(float3 p)
+{
+ return frac(rand3_hash(p).x);
+}
+
+float2 domainWarp1(float x, uint octaves, float strength, float scale, float speed)
+{
+ [loop]
+ for (uint i = 0; i < octaves; i++) {
+ x += strength * frac(sin(float2(
+ dot(x * scale, float2(12.9898, 78.233)),
+ dot(x * scale + 1, float2(12.9898, 78.233))) * 43758.5453123));
+ }
+ return x;
+}
+
+float2 domainWarp2(float2 uv, uint octaves, float strength, float scale, float speed)
+{
+ uv *= 0.001;
+ [loop]
+ for (uint i = 0; i < octaves; i++) {
+ uv += strength * frac(sin(float2(
+ dot(uv * scale, float2(12.9898, 78.233)),
+ dot(uv * scale, float2(36.7539, 50.3658)))) * 43758.5453123);
+ }
+ uv *= 1000;
+ return uv;
+}
+
+float determinant(float3x3 m)
+{
+ return (m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1])
+ - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]))
+ + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]);
+}
+
+float3x3 inverse(float3x3 m)
+{
+ float det = determinant(m);
+
+ float3x3 adj;
+ adj[0][0] = (m[1][1] * m[2][2] - m[1][2] * m[2][1]);
+ adj[0][1] = -(m[0][1] * m[2][2] - m[0][2] * m[2][1]);
+ adj[0][2] = (m[0][1] * m[1][2] - m[0][2] * m[1][1]);
+
+ adj[1][0] = -(m[1][0] * m[2][2] - m[1][2] * m[2][0]);
+ adj[1][1] = (m[0][0] * m[2][2] - m[0][2] * m[2][0]);
+ adj[1][2] = -(m[0][0] * m[1][2] - m[0][2] * m[1][0]);
+
+ adj[2][0] = (m[1][0] * m[2][1] - m[1][1] * m[2][0]);
+ adj[2][1] = -(m[0][0] * m[2][1] - m[0][1] * m[2][0]);
+ adj[2][2] = (m[0][0] * m[1][1] - m[0][1] * m[1][0]);
+
+ return adj * (1.0 / det);
+}
+
+float3 domainWarp3(float3 pos, uint octaves, float strength, float scale, float offset)
+{
+ [loop]
+ for (uint i = 0; i < octaves; i++) {
+ pos += strength * frac(sin(float3(
+ rand3_dot(pos * scale + offset),
+ rand3_dot(pos * scale + offset + 1),
+ rand3_dot(pos * scale + offset + 2)) * 43758.5453123));
+ }
+ return pos;
+}
+
+void domainWarp3Normals(inout float3 normal, inout float3 tangent, float3 basePos, uint octaves, float strength, float scale, float offset)
+{
+ // Use the actual vertex position for correct derivative evaluation.
+ float3 p = basePos;
+
+ // Start with the identity matrix for the total Jacobian.
+ float3x3 J = float3x3(
+ 1.0, 0.0, 0.0,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0
+ );
+
+ const float k = 43758.5453123;
+ // Updated constant vector to match that of rand3_dot (used in domainWarp3)
+ const float3 c = float3(151.0, 157.0, 163.0);
+
+ for (uint i = 0; i < octaves; i++)
+ {
+ // Compute the vector v using the same offsetting as in domainWarp3.
+ float3 v = float3(
+ dot(p * scale + float3(offset, offset, offset), c),
+ dot(p * scale + float3(offset + 1.0, offset + 1.0, offset + 1.0), c),
+ dot(p * scale + float3(offset + 2.0, offset + 2.0, offset + 2.0), c)
+ );
+
+ // Compute the warp offset with frac.
+ float3 f_val = frac(sin(v) * k);
+ float3 warpOffset = strength * f_val;
+
+ // Compute the derivative (Jacobian) of the offset.
+ float3 cos_v = cos(v);
+ float3x3 D = float3x3(
+ strength * k * scale * cos_v.x * c.x, strength * k * scale * cos_v.x * c.y, strength * k * scale * cos_v.x * c.z,
+ strength * k * scale * cos_v.y * c.x, strength * k * scale * cos_v.y * c.y, strength * k * scale * cos_v.y * c.z,
+ strength * k * scale * cos_v.z * c.x, strength * k * scale * cos_v.z * c.y, strength * k * scale * cos_v.z * c.z
+ );
+
+ // The per–octave Jacobian is I + D.
+ float3x3 iterJacobian = float3x3(
+ 1.0 + D[0][0], D[0][1], D[0][2],
+ D[1][0], 1.0 + D[1][1], D[1][2],
+ D[2][0], D[2][1], 1.0 + D[2][2]
+ );
+
+ // Chain this iteration's Jacobian.
+ J = mul(iterJacobian, J);
+
+ // Update p for the next iteration.
+ p += warpOffset;
+ }
+
+ // Transform the normal via the inverse-transpose of the total Jacobian.
+ float3x3 invTransJ = transpose(inverse(J));
+ normal = normalize(mul(invTransJ, normal));
+
+ // Transform the tangent via the forward total Jacobian.
+ tangent = normalize(mul(J, tangent));
+}
+
+// Alpha blend `dst` onto `src`.
+// Imagine two transparent planes. We're rendering a situation where you're
+// looking through `front` at `behind`.
+float4 alphaBlend(float4 behind, float4 front) {
+ return float4(front.rgb * front.a + behind.rgb * (1 - front.a), front.a + behind.a * (1 - front.a));
+}
+
+// Reoriented normal mapping
+// https://blog.selfshadow.com/publications/blending-in-detail/
+// Inputs are in tangent space.
+float3 blendNormalsHill12(float3 n0, float3 n1) {
+ n0.z += 1.0;
+ n1.xy = -n1.xy;
+
+ return normalize(n0 * dot(n0, n1) - n1 * n0.z);
+}
+
+float luminance(float3 color) {
+ return dot(color, float3(0.2126, 0.7152, 0.0722));
+}
+
+float median(float3 x) {
+ // Get the min and max.
+ float x_min= min(min(x.r, x.g), x.b);
+ float x_max = max(max(x.r, x.g), x.b);
+
+ // Compute (x.r + x.g + x.b) - (x_min + x_max). This gives us the median.
+ return (x.r + x.g + x.b) - (x_min + x_max);
+}
+
+// Quaternions
+float4 qmul(float4 q1, float4 q2)
+{
+ return float4(
+ q2.xyz * q1.w + q1.xyz * q2.w + cross(q1.xyz, q2.xyz),
+ q1.w * q2.w - dot(q1.xyz, q2.xyz));
+}
+
+// Vector rotation with a quaternion
+// https://blog.molecular-matters.com/2013/05/24/a-faster-quaternion-vector-multiplication/
+float3 rotate_vector(float3 v, float4 q)
+{
+ float3 t = 2.0 * cross(q.xyz, v);
+ return v + q.w * t + cross(q.xyz, t);
+}
+
+float4 get_quaternion(float3 axis_normal, float theta) {
+ return float4(axis_normal * sin(theta / 2), cos(theta / 2));
+}
+
+void calcNormalInScreenSpace(inout float3 normal, float3 objPos) {
+ normal = normalize(cross(ddy(objPos), ddx(objPos)));
+}
+
+// Formulae from here: https://www.rapidtables.com/convert/color/rgb-to-cmyk.html
+float4 rgbToCmyk(float3 rgb) {
+ float4 cmyk;
+ cmyk[3] = 1 - max(rgb.r, max(rgb.g, rgb.b));
+ cmyk[0] = (1 - rgb.r - cmyk[3]) / (1 - cmyk[3]);
+ cmyk[1] = (1 - rgb.g - cmyk[3]) / (1 - cmyk[3]);
+ cmyk[2] = (1 - rgb.b - cmyk[3]) / (1 - cmyk[3]);
+ return cmyk;
+}
+
+float rgbToCmyk_C(float3 rgb) {
+ float k = 1 - max(rgb.r, max(rgb.g, rgb.b));
+ float c = (1 - rgb.r - k) / (1 - k);
+ return c;
+}
+float rgbToCmyk_M(float3 rgb) {
+ float k = 1 - max(rgb.r, max(rgb.g, rgb.b));
+ float m = (1 - rgb.g - k) / (1 - k);
+ return m;
+}
+float rgbToCmyk_Y(float3 rgb) {
+ float k = 1 - max(rgb.r, max(rgb.g, rgb.b));
+ float y = (1 - rgb.b - k) / (1 - k);
+ return y;
+}
+float rgbToCmyk_K(float3 rgb) {
+ float k = 1 - max(rgb.r, max(rgb.g, rgb.b));
+ return k;
+}
+
+float3 cmykToRgb(float4 cmyk) {
+ return float3(
+ (1 - cmyk[0]) * (1 - cmyk[3]),
+ (1 - cmyk[1]) * (1 - cmyk[3]),
+ (1 - cmyk[2]) * (1 - cmyk[3]));
+}
+
+// Cartesian to cube hexagonal coordinates.
+// Based on this: https://backdrifting.net/post/064_hex_grids
+float3 cart_to_hex(float2 cart) {
+ float p = cart.x;
+ float q = dot(cart, float2(0.5f, SQRT_3_OVER_2));
+ float r = dot(cart, float2(0.5f, -SQRT_3_OVER_2));
+
+ return float3(p, q, r) * TWO_OVER_THREE;
+}
+
+float2 hex_to_cart(float3 cart) {
+ return float2(
+ cart[0] + (cart[1] + cart[2]) * 0.5f,
+ (cart[1] - cart[2]) * SQRT_3_OVER_2);
+}
+
// Rotate 45 degrees.
float2 rot45(float2 v) { return float2(v.x - v.y, v.x + v.y) * RCP_SQRT_2; }
@@ -334,30 +334,36 @@ float valueNoise2D(
// p = point to get noise for
float valueNoise3D(
float3 p) {
- // quantized part
- float3 q = floor(p);
- // fractional part
- float3 f = frac(p);
-
- float l000 = rand3(q);
- float l001 = rand3(q + float3(0, 0, 1));
- float l010 = rand3(q + float3(0, 1, 0));
- float l011 = rand3(q + float3(0, 1, 1));
- float l100 = rand3(q + float3(1, 0, 0));
- float l101 = rand3(q + float3(1, 0, 1));
- float l110 = rand3(q + float3(1, 1, 0));
- float l111 = rand3(q + float3(1, 1, 1));
-
- // Cubic interpolation.
- f = f * f * (3.0f - 2.0f * f);
-
- float l00 = lerp(l000, l001, f.z);
- float l01 = lerp(l010, l011, f.z);
- float l10 = lerp(l100, l101, f.z);
- float l11 = lerp(l110, l111, f.z);
- float l0 = lerp(l00, l01, f.y);
- float l1 = lerp(l10, l11, f.y);
- return lerp(l0, l1, f.x);
-}
-
-#endif // __MATH_INC
+ // quantized part
+ float3 q = floor(p);
+ // fractional part
+ float3 f = frac(p);
+
+ float l000 = rand3(q);
+ float l001 = rand3(q + float3(0, 0, 1));
+ float l010 = rand3(q + float3(0, 1, 0));
+ float l011 = rand3(q + float3(0, 1, 1));
+ float l100 = rand3(q + float3(1, 0, 0));
+ float l101 = rand3(q + float3(1, 0, 1));
+ float l110 = rand3(q + float3(1, 1, 0));
+ float l111 = rand3(q + float3(1, 1, 1));
+
+ // Cubic interpolation.
+ f = f * f * (3.0f - 2.0f * f);
+
+ float l00 = lerp(l000, l001, f.z);
+ float l01 = lerp(l010, l011, f.z);
+ float l10 = lerp(l100, l101, f.z);
+ float l11 = lerp(l110, l111, f.z);
+ float l0 = lerp(l00, l01, f.y);
+ float l1 = lerp(l10, l11, f.y);
+ return lerp(l0, l1, f.x);
+}
+
+// Fixed version of quilez's `tone` here:
+// https://iquilezles.org/articles/functions/
+float tone(float x, float k) {
+ return (x * (k + 1)) / (k * x + 1);
+}
+
+#endif // __MATH_INC
diff --git a/tessellation.cginc b/tessellation.cginc
index a80bf9c..d5b106a 100644
--- a/tessellation.cginc
+++ b/tessellation.cginc
@@ -194,7 +194,6 @@ v2f domain(
o.objPos = applyHeightmap(o.objPos, o.uv01.xy, o.normal, o.tangent);
o.pos = UnityObjectToClipPos(o.objPos);
- o.worldPos = mul(unity_ObjectToWorld, o.objPos).xyz;
//UNITY_TRANSFER_LIGHTING(o, v);
UNITY_TRANSFER_INSTANCE_ID(patch[0], o);
diff --git a/yum_brdf.cginc b/yum_brdf.cginc
index b1913f7..347c31d 100644
--- a/yum_brdf.cginc
+++ b/yum_brdf.cginc
@@ -74,7 +74,7 @@ float singleBounceAO(float visibility) {
return visibility; // Simplified version
}
-float4 YumBRDF(v2f i, const YumLighting light, YumPbr pbr) {
+float4 YumBRDF(v2f i, f2f f, const YumLighting light, YumPbr pbr) {
const float3 h = normalize(light.view_dir + light.dir);
const float LoH = saturate(dot(light.dir, h));
const float NoL = light.NoL;
@@ -208,7 +208,7 @@ float4 YumBRDF(v2f i, const YumLighting light, YumPbr pbr) {
#endif
UnityGIInput cc_data;
- cc_data.worldPos = i.worldPos;
+ cc_data.worldPos = f.worldPos;
cc_data.worldViewDir = light.view_dir;
cc_data.probeHDR[0] = unity_SpecCube0_HDR;
cc_data.probeHDR[1] = unity_SpecCube1_HDR;
@@ -229,7 +229,7 @@ float4 YumBRDF(v2f i, const YumLighting light, YumPbr pbr) {
// Set up data for fallback sampling similar to Unity's system
#ifdef UNITY_SPECCUBE_BOX_PROJECTION
- cc_reflect_dir = BoxProjectedCubemapDirection(cc_reflect_dir, i.worldPos, /*probe_position=*/0, /*box_min=*/-1, /*box_max=*/1);
+ cc_reflect_dir = BoxProjectedCubemapDirection(cc_reflect_dir, f.worldPos, /*probe_position=*/0, /*box_min=*/-1, /*box_max=*/1);
#endif
half mip = cc_roughness_perceptual * UNITY_SPECCUBE_LOD_STEPS;
diff --git a/yum_lighting.cginc b/yum_lighting.cginc
index 64c3c1d..7f2d922 100644
--- a/yum_lighting.cginc
+++ b/yum_lighting.cginc
@@ -114,33 +114,33 @@ struct YumLighting {
Light derivedLight;
};
-float getShadowAttenuation(v2f i)
+float getShadowAttenuation(v2f i, f2f f)
{
float attenuation;
float shadow;
// This whole block is yoinked from AutoLight.cginc. I needed a way to
// control shadow strength so I had to duplicate the code.
#if defined(DIRECTIONAL_COOKIE)
- DECLARE_LIGHT_COORD(i, i.worldPos);
- shadow = UNITY_SHADOW_ATTENUATION(i, i.worldPos);
+ DECLARE_LIGHT_COORD(i, f.worldPos);
+ shadow = UNITY_SHADOW_ATTENUATION(i, f.worldPos);
attenuation = tex2D(_LightTexture0, lightCoord).w;
#elif defined(POINT_COOKIE)
- DECLARE_LIGHT_COORD(i, i.worldPos);
- shadow = UNITY_SHADOW_ATTENUATION(i, i.worldPos);
+ DECLARE_LIGHT_COORD(i, f.worldPos);
+ shadow = UNITY_SHADOW_ATTENUATION(i, f.worldPos);
attenuation = tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).r *
texCUBE(_LightTexture0, lightCoord).w;
#elif defined(DIRECTIONAL)
- shadow = UNITY_SHADOW_ATTENUATION(i, i.worldPos);
+ shadow = UNITY_SHADOW_ATTENUATION(i, f.worldPos);
attenuation = 1;
#elif defined(SPOT)
- DECLARE_LIGHT_COORD(i, i.worldPos);
- shadow = UNITY_SHADOW_ATTENUATION(i, i.worldPos);
+ DECLARE_LIGHT_COORD(i, f.worldPos);
+ shadow = UNITY_SHADOW_ATTENUATION(i, f.worldPos);
attenuation = (lightCoord.z > 0) * UnitySpotCookie(lightCoord) *
UnitySpotAttenuate(lightCoord.xyz);
#elif defined(POINT)
unityShadowCoord3 lightCoord =
- mul(unity_WorldToLight, unityShadowCoord4(i.worldPos, 1)).xyz;
- shadow = UNITY_SHADOW_ATTENUATION(i, i.worldPos);
+ mul(unity_WorldToLight, unityShadowCoord4(f.worldPos, 1)).xyz;
+ shadow = UNITY_SHADOW_ATTENUATION(i, f.worldPos);
attenuation = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).r;
#else
shadow = 1;
@@ -148,14 +148,14 @@ float getShadowAttenuation(v2f i)
#endif
float realtimeAttenuation = attenuation * lerp(1, shadow, _Shadow_Strength);
- GetBakedAttenuation(realtimeAttenuation, i.uv01.zw, i.worldPos);
+ GetBakedAttenuation(realtimeAttenuation, i.uv01.zw, f.worldPos);
return realtimeAttenuation;
}
-float3 getDirectLightDirection(v2f i) {
+float3 getDirectLightDirection(v2f i, f2f f) {
#if defined(POINT) || defined(POINT_COOKIE) || defined(SPOT)
- return normalize((_WorldSpaceLightPos0 - i.worldPos).xyz);
+ return normalize((_WorldSpaceLightPos0 - f.worldPos).xyz);
#else
return _WorldSpaceLightPos0;
#endif
@@ -165,11 +165,11 @@ float GetLodRoughness(float roughness) {
return roughness * (1.7 - 0.7 * roughness);
}
-float3 getIndirectSpecular(v2f i, YumPbr pbr, float3 view_dir, float diffuse_luminance) {
+float3 getIndirectSpecular(v2f i, f2f f, YumPbr pbr, float3 view_dir, float diffuse_luminance) {
float3 reflect_dir = reflect(-view_dir, pbr.normal);
UnityGIInput data;
- data.worldPos = i.worldPos;
+ data.worldPos = f.worldPos;
data.worldViewDir = view_dir;
data.probeHDR[0] = unity_SpecCube0_HDR;
data.probeHDR[1] = unity_SpecCube1_HDR;
@@ -310,12 +310,13 @@ float3 yumSH9(float4 n, float3 worldPos, inout YumLighting light) {
}
float4 getIndirectDiffuse(v2f i,
+ f2f f,
float3 normal,
float4 vertexLightColor,
inout YumLighting light) {
float4 diffuse = vertexLightColor;
#if defined(FORWARD_BASE_PASS)
- diffuse.xyz += max(0, yumSH9(float4(normal, 0), i.worldPos, light));
+ diffuse.xyz += max(0, yumSH9(float4(normal, 0), f.worldPos, light));
#endif
return diffuse;
}
@@ -339,22 +340,22 @@ YumLighting GetYumLighting(v2f i, f2f f, YumPbr pbr) {
// normalize has no visibile impact in test scene
light.view_dir = -f.viewDir;
- light.dir = getDirectLightDirection(i);
+ light.dir = getDirectLightDirection(i, f);
// Use proper light color/intensity separation
light.direct = _LightColor0.rgb;
// Calculate attenuation first, before diffuse lighting
- light.attenuation = getShadowAttenuation(i);
+ light.attenuation = getShadowAttenuation(i, f);
- float3 tangentNormal = mul(pbr.normal, transpose(float3x3(i.tangent, f.binormal, i.normal)));
+ float3 tangentNormal = mul(f.tbn, pbr.normal);
float3x3 tangentToWorld = float3x3(i.tangent, f.binormal, i.normal);
// Use Bakery-aware irradiance function
#if defined(LIGHTMAP_ON)
light.diffuse = BakeryGI_Irradiance(
pbr.normal, // worldNormal
- i.worldPos, // worldPos
+ f.worldPos, // worldPos
float4(i.uv01.zw, 0, 0), // lightmapUV (xy = uv0, zw = uv1)
float3(0,0,0), // ambient (will be calculated internally)
light.attenuation, // attenuation
@@ -367,7 +368,7 @@ YumLighting GetYumLighting(v2f i, f2f f, YumPbr pbr) {
light.diffuse.gb = light.diffuse.r;
#endif
#else
- light.diffuse = getIndirectDiffuse(i, pbr.normal, float4(i.vertexLight.xyz, 0), light);
+ light.diffuse = getIndirectDiffuse(i, f, pbr.normal, float4(i.vertexLight.xyz, 0), light);
light.occlusion = 1;
#endif
@@ -376,13 +377,13 @@ YumLighting GetYumLighting(v2f i, f2f f, YumPbr pbr) {
#endif
light.diffuse_luminance = luminance(light.diffuse);
- light.specular = getIndirectSpecular(i, pbr, light.view_dir, light.diffuse_luminance);
+ light.specular = getIndirectSpecular(i, f, pbr, light.view_dir, light.diffuse_luminance);
#if defined(_LTCGI)
ltcgi_acc acc = (ltcgi_acc) 0;
LTCGI_Contribution(
acc,
- i.worldPos,
+ f.worldPos,
pbr.normal,
light.view_dir,
pbr.roughness_perceptual,
diff --git a/yum_pbr.cginc b/yum_pbr.cginc
index 515aef9..6049b35 100644
--- a/yum_pbr.cginc
+++ b/yum_pbr.cginc
@@ -144,7 +144,7 @@ float FurClip(v2f i, f2f f, inout YumPbr result) {
float2 fur_uv = i.uv01.xy * _Fur_Heightmap_ST.xy;
#if defined(_FUR_WARPING)
- float2 vnoise = valueNoise3D(i.objPos * _Fur_Warping_Frequency);
+ float2 vnoise = valueNoise3D(i.objPos * _Fur_Warping_Frequency) * 2 - 1;
float3 vnoise_tbn = mul(vnoise, f.tbn);
fur_uv += vnoise_tbn.xy * (_Fur_Warping_Strength / _Fur_Warping_Frequency);
#endif
@@ -152,6 +152,7 @@ float FurClip(v2f i, f2f f, inout YumPbr result) {
float fur_thickness = _Fur_Heightmap.SampleBias(
trilinear_aniso4_repeat_s, fur_uv,
_Fur_Heightmap_Mip_Bias).r;
+ fur_thickness = tone(fur_thickness, _Fur_Thickness_Power);
clip(fur_thickness - fur_layer);
return fur_thickness;
#else
@@ -308,6 +309,11 @@ YumPbr GetYumPbr(v2f i, f2f f) {
glitter_p.center_randomization_range = _Glitter_Center_Randomization_Range;
glitter_p.size_randomization_range = _Glitter_Size_Randomization_Range;
glitter_p.existence_chance = _Glitter_Existence_Chance;
+#if defined(_FUR)
+ glitter_p.seed = floor(i.vertexLight.w * _Fur_Layers);
+#else
+ glitter_p.seed = 0;
+#endif
#if defined(_GLITTER_ANGLE_LIMIT)
glitter_p.angle_limit = _Glitter_Angle_Limit;
glitter_p.angle_limit_transition_width = _Glitter_Angle_Limit_Transition_Width;