Posted: 2026-03-03
Desc: Shades of math
What a month. Glad I started writing these in two bursts, first in the middle of the month, and the second slightly after the month is over, much prefer being able to unwind at the end via recap.
(My friends are the absolute coolest I swear)
I woke up on the 9th to Team MAIL (friend group of mine) talking about some entertaining little web toy demo thing. One of those two-tone bouncy balls that you'd likely see kids in malls back in the day buy out of a machine, and throw them as hard as they could manage, down to the first floor, so they could see it bounce all the way back up to the second floor.
At least, that's what the cool kids did. I was not one of the cool kids, I simply quietly observed them do that, and then subsequently saw them get yelled at. Kinda sums up a lot of my childhood, "I watched other kids get into trouble, saw the consequences of their actions, and wondered why they did that"
Refocusing. In my soupy, half-awake state, I squinted at the chat, squinted at the low quality Discord preview, and instantly my mind woke up to break down how it was created exactly, because it closely resembled the shader previews I have to stare at in Unity during long projects. Conveniently already rendered in a sphere-shaped output as well, along with utilizing some effects I already know about through VRChat "work" (god I wish I was commissionable). Without having to look at any code or variables attached to sliders, I was already thinking about the math behind it. Or, at least what I suspected the math "felt" like.
Draw a point in 3D space, define its radius, and you have a sphere. Draw a second point equidistant to the radius and center, and you've now split it into two hemispheres for two colors. The rest should be able to be handled with normal maps and noise. Some of the glitter also doesn't appear in the center, and appears more frequently on the outer edges of the view, kinda looks like it uses something vaguely...Fresnel-shaped as a mask ?
I needed a shower upon waking up, and getting it in my head that the math behind this is at least simple enough to me that I feel confident enough to break down the process myself, I asked the question that has fueled my coolest projects
The first thing I noticed was, the little render simulation program...thing, allows you to separate the ball into two parts. The moment I did this, I immediately noticed something...
I know those sparkles, I've seen this before because I've USED these...
This is an effect I used from poiyomi's toon shader on a whim because I thought it looked nice. This immediately gave me the background thought of "Is this little project someone made just using poiyomi's shader, or is it simply the case that the math to create a glitter effect is similar enough that it's always going to look this way ?"
Put a pin in this, it will be relevant later.
Regardless, it wasn't super relevant, because I wanted to make this myself, so I could understand the process on a fundamental level better, given the mix of skill-based value, and "rule of cool" it felt like. After all, I've gotten a lot better the past year about learning new skills
So, after a lot of digging, and being vaguely dissatisfied with the immediate shader graph tutorials I was seeing, and a hunger for some pain, I decided to do this all on hardmode, and go a step below shader graph setup. I was going to just straight up code it.
...me, coding. I'm not a coder, I've always been okay at understanding the ideas behind coding, what certain things do, but time and time again whenever I'd approach it, something about the logic of it would just grind absolutely everything to a halt, with so many tutorials online feeling like they were showing me what tools there were, but not really being able to grip onto how to apply them in my own project ideas. Maybe this time would be different
Once I ruled out shader graph as not being what I wanted to use and learn, there really only existed one immensely valuable resource. Freya Holmér has three videos on the process of coding basic shaders step by step, line by line, along with four auxiliary videos about mathematics in a game dev environment from 2020. Total runtime: 24h, 50m, 12s. For the rest of the month, I'd be running and re-running through these videos multiple times to keep taking in the same information until it truly stuck on a level where I could explain clearly how it was used.
Last year, I took a similar approach when it came to "Hey 3D models seem interesting, how are those made" which caused me to stumble upon a 12 hour stream from kaideart that went from nothing to a basic functioning model in one sitting, and served as a starting off point for me that was absolutely invaluable. Something about following along strictly with someone else's process, decision by decision slowly while starting from the bottom up, then having a finished product and tearing it apart top down to see how things break has worked for me incredibly well when the resources I have line up just right
So, rewinding, we started on the 9th with this decision to figure out how shaders were coded. How far did I get ?
Looking back through my chat logs, progress at the start was fairly quick, I managed to create a simple "Make something red" shader the same day, and spent the 10th largely just steeping myself in the mathematics and logic of it all slowly, trying to get every single "why" of something to stick, figure out how my own mind best ties things together. By the 11th, I was messing with using the vertex part of the shader processing to displace vertices based on time values for transparent shader animations.
Then, everything clicked
Perhaps not everything, but I suddenly got this MASSIVE rush of energy and excitement when I realized that "Oh fuck, I'm actually coding and doing mathematics, and it isn't all completely falling apart" and just like last year, my mind was buzzing with a somewhat overwhelming amount of ambitious ideas when I realized how truly powerful this was.
By the 12th, I had managed to figure out how to create some fairly basic but effective rim lighting, after digging too deep into what Fresnel-related effects were, and realizing that simply doing rim lighting was far more reasonable. For some psychotic reason, I had expected this to only take me a couple days maximum, and was surprised at how slowly progress was going. I took a moment to remind myself that my first 3D model from nothing took me about 12 days, and looked awful at the start, and that perhaps I should temper my expectations juuuuuust a skosh
What I did not expect was a solid week of no progress, as I continued to consume mathematical concepts, and techniques in 3D rendering. I had gotten the rim lighting down, and I thought "oh yeah, glitter should be easy next".
Glitter was not easy.
Thing is, there's a lot of different, strange ways one could go about this, and I was going at it blind with no idea how the original bouncy ball author was approaching all of this. I could see possibly how others did it if they had a tutorial for their method, I could try to conceptualize (been enjoying that word a lot recently) it myself based on how I know it behaves in the real world.
For a solid week, I was stumped, and kept hitting highly complicated dead ends that seemed promising, like parallax mapping to actually create depth, shell texturing that could be inverted to create the "shells" inside the mesh itself.
I decided to vent about this to like, the one artist friend I know that venting to actually seems to have a positive impact on me for, as most often I just feel like I get nothing out of venting (hi goodmode <3) and the advice that I did not immediately take that would unjam me, and that I am about to paraphrase ?
"Have you tried directly reaching out to someone that knows about this kind of stuff"
...HANG ON, WHO ACTUALLY MADE THAT BOUNCY BALL TOY PROGRAM
For fffFF-
*sigh*
Of course they have a website, and of course they detailed almost every single technique used in that bouncy ball on a Basic Shader Techniques page. It was not a wasted week, but it was certainly a week I could've spent making more progress doing instead of just processing more and more complicated mathematics, and studying a lot of various terminology. Had I stumbled on this page from the start, I don't think it would've immediately helped me anyhow, as that week of no progress also involved me understanding better and better how vectors, normalization, and dot products all work, in that way where knowledge sometimes needs to be processed before you can actually understand how it is realistically used and applied to problems.
Also, remember when I said "Put a pin in this, it will be relevant later." about six images ago ? As it turns out no, this is someone who actually knows their shit, and the joke is on me for thinking something was shortcut in the process.
So, now that I had the documented process more or less, I could finally unstick myself
Well...almost.
It wasn't particularly hard to calculate the colors for the top and bottom half of the sphere, if anything I could've figured this out in under an hour, but instead I was stubborn enough to want glitter first.
_Primary( "Primary Color" , Color ) = ( 1, 0, 0, 1 )
_Secondary( "Secondary Color" , Color ) = ( 0, 0, 1, 1 )
float3 objPos : TEXCOORD3 ;
o.objPos = v.vertex.xyz ;
float t = ( i.objPos.y - -0.13 ) / ( 0.13 - -0.13 ) ;
t = saturate( t ) ;
float4 mixedcolor = lerp( _Primary, _Secondary, t ) ;
Originally these were user-set variables, but I decided that I kinda just liked the look of both being set to 0.13 instead. The "almost" comes from the glitter, which even after reading and re-reading the guide, I got it, but suspected something I was defining in the vertex shader wasn't right, given how many different types of data I was trying to juggle. The more you add that requires a specific way of defining how an object relates to the world around it, the harder it can be to figure out precisely what was wrong exactly. It almost worked, but certain angles just didn't seem to behave right...
A few more days passed without much further progress, and I decided to break the "Pull in case of emergency" glass, and just contact the creator to see what I was doing wrong, just get it over with and stop having this weird stubborn idea that I have to figure everything out myself and work on talking to other people as a separate skill to be refined that can bolster all others. Perhaps they'd find it cool that someone else was inspired by their work.
But...well, if someone's possibly gonna look at my horrifying code I barely comprehend myself, I should at least clean it up first, and while I'm at it, may as well test a few things...
So I tested a few things while cleaning up, including making myself a "debug texture" of sorts, basically just a 256x256 texture where each pixel is Red 255, then Green 255, then Blue 255 in a loop, so I could see if it was an XYZ to RGB issue, or if this was specifically some kind of direction issue.
This clued me in on something big. The sparkle strength is different at certain orthographic angles, even with all rotation transforms at zero, and all transforms also at zero. In fact, at a very precise angle, there appears to be no sparkles whatsoever, something that my mind instantly screamed out silently "That's just X, Y, and Z at negative values"
I squinted at my own car crash code for a long while, and just went off intuition of what could be wrong...
*squints*
float sparkles = saturate( dot(normalize ( - i.viewDir) , ( col - i.worldNormal ) ) ) ;
...
wait...
Is "normalize" only normalizing i.viewDir...
What if, hear me out...
float sparkles = saturate( dot(normalize ( - i.viewDir) , normalize( col - i.worldNormal ) ) ) ;
*sigh*
Well hey, I figured it out all on my own ! I should still reach out, because brute forcing it by studying my own code long enough doesn't really excuse being afraid to talk to people. That, and it's still kinda...bright ? Like, blindingly- HANG ON
Alright that's WAY closer. If we're measuring my shark-brand bouncy ball with the fancy name brand one at the .io store (this analogy doesn't work because it's just a free silly program, but now I make it sound like I'm trying to copy something you have to pay for, but I still like comparing what I'm doing to the knock-off brand), I'd say we're maybe 75-80% of the way there. My sparkles are square instead of slightly rectangular, and not as luminous, but I kinda like my way for the time being. To get them more rectangular, I'd just set Tiling on X to 2, but 1.5 for Y, distorting it ever so slightly.
The final big wall to deconstruct is the distinct "crunchiness" that coats the outside, like a gummy shark that rolled around in that sour invert sugar I love because I will take sour over sweet any day of the week.
...can I just, buy invert sugar ? How's that made...
Irrelevant, one of the last things I wanted to achieve with this was the aforementioned crunchiness, and this just looked like a really fine grain normal map. I already had a halfway guesstimate of how to do this from Freya's videos, and cross-referenced the info there, with the original sparkle shader creator's website.
Basically, no way to get around it, we're gonna have to work with bitangents and matrix math. At this point, I've climbed over enough hurdles that once felt insurmountable that new definitions, words, and math aren't scaring me off. I might even finish this before the month is over, which would be a wonderful change of pace
and then I was out of time. Also I temporarily lost access to my meds, which sent my productivity into an instant nosedive. But hey, I'm here now !
Eventually I will actually clean up the code of this and implement some extra features (I added back in the ability to change color mixing distance after being given a picture of the real life version of a bouncy ball) so that it matches more closely Rob's version, but for the time being ? Having a working proof of concept, and understanding the how of it ?
Truly wild. I coded a thing... I MADE that thing...
What else, what else, what else...
Oh, I have an Atom feed now, so if you're one of my friends reading this now, there's no excuse to miss out on my posts anymore.
"Okay but what's Atom"
Think open source "Subscribe" button, modern search engines might be rotting away, but information on it is easy enough to find that explaining it further here feels a bit long-winded. I originally wrote the skeleton of an Atom feed back in November, but other projects pulled my focus away for long enough that I put it on the backburner. After that marathon of coding (by my poor standards), going back to it and finally finishing it was not difficult at all.
If there's any issues with it, or you're actually the type that uses these feeds religiously, and you notice a problem with mine, contact me
Once again, I am out of time, as I didn't expect this post to scrape beyond 3,000 words, and would like to get it out on the 3rd. For March, I think I'm gonna focus more on trying to reach out to people more, be known more, stick my stupid shark snoot into other people's projects to see what they're up to and how they handle things.
Until next time...I'm gonna go play some more Nuclear Throne.
...?
*Somewhere in the distance, boss fight music can be heard*
Oh... that's...
an intimidating timeline for a few things...
Well. I am nothing if not a capable shark. I now have some very important project organizing to do...
🧡
- Aethena, 2026-03-03