From 4885fb42bd42f5dfb7fdc198bd0cc42be7959aaf Mon Sep 17 00:00:00 2001 From: yum Date: Sat, 24 Jan 2026 15:03:30 -0800 Subject: Remove worldPos interpolator --- 2ner.cginc | 61 +++-- 2ner.shader | 6 +- glitter.cginc | 5 +- globals.cginc | 4 + interpolators.cginc | 16 +- math.cginc | 678 ++++++++++++++++++++++++++-------------------------- tessellation.cginc | 1 - yum_brdf.cginc | 6 +- yum_lighting.cginc | 47 ++-- yum_pbr.cginc | 8 +- 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 stream) { #if defined(_FUR) #if defined(_FUR_MASK) @@ -233,31 +233,44 @@ void geom(triangle v2f input[3], inout TriangleStream 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; -- cgit v1.2.3