Posted: 2026-07-01
Desc: So much to cover...
Welcome, to the big one.
The original plan was to have this post be a double feature, as several projects were overlapping and unfinished around the end of April, but May was the harbinger of a lot of stressful bad news, as the abstract concept of death and loss circled my very being, like a constant static charge that made moving, reaching out, touching anything trigger that panicked reflex of sudden, unexpected pain.
But, you're not here for my private life, the vicissitudes of it, and I don't particularly want to talk about it in a public-personal spot like this. Let's talk about the good shit, the exciting projects that just keep rolling along faster and faster as I improve further, climb higher up the infinite tower, because this is one of the coolest, and most fascinating posts thus far. I added anchor links for all images, meaning you can skip right to Furality or Custom Shader Stuff if immediately hearing me talk about Warframe isn't interesting.
If I'm excited to write this, the person that so rarely gets excited about anything, then you know it has to be at least a little intriguing
This gargantuan post is carved up into three major segments, or "arcs" as I occasionally am joked about having, and I'll use these pictures to denote what arc I'm currently writing about. In chronological order, they are "Warframe", "Furality", and "Custom Shaders".
So, we start first, with Warframe. My account with them is approximately 11 years old...
Actually no I wanna get a precise number for this...
Wait wait, there's no "click a button to get my data" option in account settings ?
Nah nah nah nah...
Alright we'll see if they get back to me in time on this. Remember to be kind to people who work customer support, as they aren't out to make your life worse.
Right, Warframe.
To say I've played it for over a decade is disingenuous. My current "days logged in" reward at the time of writing is currently at
[they didn't get back to me in time]. The reality is, I had three major "phases" of when I played Warframe...
But wait, why are we talking about Warframe ?
The topic came up with a friend when discussing the concept of what I called "digital sherpas", guides in MMOs, often roleplaying in some way, that assist and direct beginners around simply because they like to assist. That and MMOs with a high degree of atmosphere that one would want to simply exist in for a while.
I confessed that MMOs aren't really my thing, that they tend to encourage a habitual, often daily pattern that I find equal parts difficult to conform to, and already has a history of bothering me ala Duolingo. But I had to ask myself if Warframe was an MMO, and I reluctantly seemed to think so. Just because I had been primarily a solo player for 99% of my play time, doesn't mean that it wasn't an MMO. It was an online game where you can do it solo, but you're forced to connect to a server, forced to go to hubs where you can at the pure minimum, see other people moving around.
It got me thinking way too much about Warframe, and specifically why it didn't seem to suck, at least from common online sentiments I was seeing here and there. Critique was mild as fuck, and largely over things that didn't seem to matter that much. In a time where everything seems to be getting worse, why wasn't Warframe following that trend ? Their player count was increasing.
So, I dipped my toes back in, and instantly found myself completely submerged and thrashing about like a wild animal suddenly caged, unaware that what was happening to me was a good thing. All I saw was the cage, the amount of time I'd wind up sinking into it day after day as I caught up with all the content. I didn't know it at the time, but I needed a game like Warframe quite badly, though it manifested in strange ways.
One was finally overcoming a feeling I have with any pseudo-online game; Actually playing with randoms, other people.
I'm glad I did it, gave me a lot of much needed social validation, forced me to reflect a ton on how I act and respond to people behaving certain ways. I'm no stranger to online games, hell I'm not even a stranger to competitive online games, but there's a distinct anxiety and tension that no matter how comfortable I am with the game itself, will always be present, if I have to interact with others to achieve a goal that can be measured in some way as good or bad, a metric that proves how correctly or incorrectly you are playing the game
Social confidence aside, I got to talk to a lot of new people who either didn't seem to mind me talking about how much stuff I had going on in Blender, or were actively interested and wanted to learn more. I eventually somehow convinced (inaccurate wording, I mentioned grinding prime stuff to trade up for the platinum, and they just gave it to me no questions asked) someone to gift me platinum to change my username to my website name.
Thank you, Cephalon-Ennui#912, I will not forget your generosity. 🧡
From this username shift, I was able to get outsider perspectives about my site, initial first impressions that gave me really vital data of what someone expects, along with being awoken to the realization that people are likely mistyping my URL as athena, not aethena, that when people think of art, they think of drawings, that 403 and 404 pages incite confusion and lack a clear "this is under construction" vibe to them
Now I hear your teeth gnashing, and sense your blood pressure rising, the desire for art and projects far stronger than my strange Warframe interactions, so I will conclude this mini-section by mentioning that I got a private concert from someone in their ship, I did a lot of lighthearted heckling of people in trade hubs, and overall felt more comfortable simply existing and being seen in Warframe, a genuine first for me.
The artistic side to all this; Warframe was a personal subconscious inspiration of mine back in 2015. It had a really clear style that I didn't know how to emulate (it likely didn't even cross my mind to try and emulate it back then, as games felt too incomprehensible in their building blocks to believe a "dumb little shit like me", as I'd have probably phrased it at the time, would be able to figure it out) but knew that there was something fascinating and inspiring there. Faceless, eyeless, humanoid kinda-sorta-robots that had crazy powers, and didn't quite look like anything I had ever seen before.
I want to make a Warframe of my own. Not in name, not in lore, not even in straight style, but in spirit. A vessel from which I can sell, and allow other people to witness and pilot the captivating coolness I felt when I played Warframe all those years ago. Not quite a tribute, but something spiritually connected in a very loose, personal way.
If I was going to try and make my first base model to sell, then I felt I needed to marinate in the aesthetics of Warframe for a while, which is how I learned to not thrash about and panic in that cage, but to simply let it carry me, to trust my instincts, and take in the information that passed me by
So, as I queued, and slummed around ingame hubs, I got to work on sculpting a base to work from.
...and subsequently got humbled hardcore when I realized how difficult and finicky digital sculpting is. At least, for Blender, I can't speak for other software, but I had to wind up learning a lot of tough lessons by trial and error, and routinely overwhelmed myself, had to reverse progress, and felt this crushing sense of doom and inadequacy that wasn't really like me.
Theory one, to influence attempt two; Concept art exists for a reason, and trying to sculpt based on vibes is like pulling teeth. Agony, hated every moment of it
I found the process of trying to make concept art required an easing of the pain I usually feel when doing 2D art that has a strict purpose, instead of the increasing of energy and speed that 3D work tends to extract from me and my loud music.
Heckling the trade chat of Maroo's Bazaar (notorious for having scammers that try to swindle new players with absurd trade prices) was certainly an entertaining start that helped me at least visualize some ideas better by way of low-stress doodling.
Something about this was admittedly fascinating to watch as well, people who'd stand there for long periods of time, trying to offer overcharged items. Thing is, if you stick around there long enough, someone's going to heckle one of the other scammers, and it can lead to bizarre conversations.
Then, after a couple days of this kind of thing, and playing more with randoms, I get a message after cracking relics with a squad, from one that decided to stick around
Tennogen.
In retrospect, this should've been absurdly obvious to me as like...a thing I could do. I'm a 3D artist after all...why wouldn't I take a crack at it ? This isn't 2015 anymore, and my passions are clearly angled towards 3D work.
What ensued was digging around the documentation of how Tennogen submissions are done, and the lack of serious, accessible documentation that didn't feel intensely out of date and archaic. It was a reprieve from the mental anguish I was feeling from trying to sculpt this base model, but I didn't really make too much forward progress beyond accessing the tools and basic documentation.
So, I'm gonna call a few of my shots right here and now for what's gonna happen by the end of the year, and state that one of them will be at least one, but personally aiming for two Tennogen submissions. They will almost certainly not be accepted into the game, but I at least want to take a shot at submitting something, if anything to prove that my skill level and quality matches other submissions
Truthfully, I cracked under the pressure of something so significantly personal like a first base model. I accepted that, what I was working on would not be the final result, but was instead just some feeling out of certain ideas to see what would work, along with general sculpting practice. Gotta say though, considering I haven't been sculpting that long, I certainly don't dislike what I have, but it simply wasn't the direction I wanted to push this, and bad decisions stacked up.
But, clay can be reshaped, and I learned a ton from my couple attempts. Not everything will be scrapped, but I know better what I want now, and the headspace I work in gives me a decent amount of plasticity for ideas like this.
This concludes our Warframe section, as Furality is around the corner, and I've been soaking in Warframe long enough to have excess creative and mental energy to tackle pre-con ideas, and I felt that putting the sculpt on the backburner for a bit was a wise idea, as I don't do great work when each moment spent on something is excruciating
Ahh, Furality.
The sickening time-crunch panic of wanting to be ready for it.
A treat I've come to adore the exciting feeling of~
Truth of the matter though ? I didn't feel like I had concrete company to go to it with, as everyone from my initial squad had their own life-related reasons for not feeling confident in going through with it, where the gamut of excuses felt highly understandable and out of anyone's control.
So, I had to hunt for randoms yet again, because the idea of doing Furality solo gave me flashbacks to doing Megaplex 2019 solo, and the immense, suffocating overwhelmedness of being at a convention alone. Nobody to shadow, no-one to fall back on if things felt like too much.
I did have a plan this time, as while the weeks remaining until it started got smaller and smaller, I did have two distinct meet-ups I could hunt around for new people in
But if you're gonna hunt, you need bait. Mail's pretty good bait, and I got it in my head to create mail that I could drop anywhere, and use as an interactable lure that hinted to me being someone to approach.
After all, doesn't matter what game I inhabit, Team MAIL's gotta rep their incredible postal abilities, and capacity to connect others together.
So I made a super basic red envelope and threw in some placeholder text from a friend onto a texture, and added a few blendshapes to have a cheap animation.
But, hang on...wouldn't it be helpful to know if someone was near it, looking at it...and if I knew they were touching it ?
See, this is how you get what I called "Spymail". Mail that allows for remote tracking of proximity and contact, and lights up my armor with info when either of these variables changes. The way I wired this all up behind the scenes is less than ideal, as I used an unnecessary UV transform instead of just animating an emission value, which will have a cascading effect later on in the final segment of this post, but the "how" isn't too hard
We use two receivers, two parameters (
Player-Mail-Contact, bool, and Player-Prox, float) and three animation layers. One to check for player contact on the seal, which plays an open animation that eventually closes, one that lights up the yellow light on my wrist using the motion time of Player-Prox, and one to use Player-Mail-Contact as a toggle for the green light on my wrist.
I make this sound easy, but the truly difficult part was "dropping" the mail somewhere, due to how VRChat models work.
Alas, you can't just spawn and throw a physics object, (exceptions for VRChat developer made/sold ones I think) but we don't even need to deal with physics here, and we still have to shortcut it with a hack-y method; VRCFury's World Drop component, which by the way will seemingly not work if the object you're dropping is part of the same skinned mesh render. This sadly seemed to be completely and totally unavoidable, and the mail became the second skinned mesh render on my model. One more, and I'd be stuck at a performance rating of Medium, a threshold I do not allow this model to cross.
By the time it was done, I had exactly two weeks, and three possible meetups, before Furality. Three opportunities to headhunt some company
The first one arrived before I could actually finish my mail, a Chimera Network meetup. Effectively, a meetup for anyone who owns or enjoys the Chimera models, the same ones I've spent over a year customizing, studying, and thinking about, as if it were some great body of work to pour over. As an aside, if anyone knows how to convey to another artist how much their work has fundamentally shaped very deep-rooted aesthetic tastes and identity in the avatars one chooses to use, let me know, cause I have no clue how I'd word that kind of thing live
...I mean, that's how I feel about it, but I also think the models break a common trend I see everywhere of "sex sells", where the Chimera models are just fucking cool first and foremost.
I had kept putting off these meetups due to my own anxieties and fears of how they'd go, how to act, what to expect, and for the most part, yes I was intensely overwhelmed, and barely said anything. I wanted desperately to just speak unfiltered about how much these models have inspired me, how much intimate connection I have with them, my own personal lore about it all. But I've been burned so many times in the past by speaking without modulating what I speak about, that I default to simply not saying anything at all in unfamiliar situations.
However, we need to enhance that image
(We can't afford better enhancement technology here, it's just me making camera zoom sounds)
See this Rotormantid next to me ?
Remember them, as they're going to become very important, very soon. They were one of the only people to talk to me, to approach me, and to very gently match my nervous pace of flighty, stunned conversation.
It helped to some degree that, the Chimera Aqua model is not the most popular of the Chimera models by a longshot, even though it is eternally my favorite, so when you combine that with being orange as fuck, and if you're me...you tend to stick out. This will come to be a very important lesson for me in how to exist in VR.
But, we added each other, and went our separate ways, leading me to finish up my spymail in time for meetup number two; The penultimate Furality New Player Night meetup. To me, this was the big opportunity, the one I had to succeed in
...fuck. It was this world.
This was one of the first worlds I was ever brought to when I was introduced and guided through VRChat, and it has a lot of rough, complicated emotions tied up in it. Suffice it to say, I don't think I even said anything to anyone. I did a lot of eavesdropping on the average conversation, and just felt so out of place, felt like I didn't belong here.
So alright, swing and a miss. I had no way of anticipating they'd use this world of all places to host that one specific meetup. What about the final meetup ?
This one I didn't even eavesdrop for. I realized on that meetup that the "New Player" part didn't mean shit, and that there was no real guidance or expectation for newer players that might be a bit nervous to actually be interacted with. Worse yet, for some reason, I wasn't getting any readouts from my spymail, and kept feeling like it could be broken in some way, that maybe only I was able to interact with it.
That fear would turn out to be false, much like many of my fears, or negative assumptions about my own skills or abilities. At this point, I got desperate, and with maybe three days before Furality started, I got desperate, and did one last check to see if I could get any kind of backup for Furality
Be kind to bugs, too.
Instantly, I felt far more comfortable, excited, and full of purpose now that I had backup, and the dread that once possessed me had dissolved. Purpose that would drive one last idea I would work on between bouts of existing in Furality.
See, all ideas I work on, I feel get connected together by the fascia of the interactions and events between those ideas. The mail was created from feelings of social insecurity, and a want to learn how player contacts work. Now that we're deep in the Furality section of this post, we can talk about the small idea that morphed into the biggest idea I've ever successfully executed on
I may not seem like the type, but I fucking love a good rave. To quote that one trance track, Hijack the System...
"Man, this world might have failed me, but a rave never did"
I've said it before and I'll say it again, but I don't exist without music, and the greatest things I'll ever create will almost always be with something blasting in the background, lifting me up and giving me life.
By extension, this means that AudioLink is a truly vital component to any avatar I may use, right ? You may be surprised, but I don't really have any good examples of my AudioLink texturing abilities. Well, what if we made a holographic rave outfit that does something with AudioLink I feel is unique...
(I'm also only remembering now how high I was during that set, probably the best night of my life thus far)
So, whenever there was downtime between bouts of Furality activities, I started just...extruding faces, and projecting an outfit across my model. It all started very gradually, slowly, without a solid image of what I wanted to do, or have it look like.
The texturework, and reactivity with AudioLink was all very haphazard and rushed, but I knew in the back of my mind that this was the perfect time to not have everything about it look decent, as I'd be spending the most amount of time in VR, and would be exposed to the lack of certain things I'd want in a rave outfit.
Now, do you remember the Rotormantid I mentioned ?
Well, the first day of Furality didn't go great. For whatever reasons possessed me at the time, I just...couldn't speak, didn't know how to act, I got massively overwhelmed, enough so to actually force me to tap out and take a stress nap for a big chunk of the first day.
When I woke up, I jumped on solo, to see if I could check out the main hub world for Furality (I could not, they had technical issues that prevented it from being accessible at the time)
and by sheer chance, that Rotormantid I mentioned, requested an invite to where I was. So I quickly shuffled out of the default VRChat home world, and back to that hotel room world I used last Furality, and allowed them to join in on me. They asked if I went to the robots meetup, or the Chimera meetup for the convention, and I mentioned how no, I took a nap. They then informed me that meetups stay open up to 24 hours after their marked time on the schedule, and that there were still a few people lingering at the robots meetup, if I wanted to come join
I'm gonna circle another important character to this story that will become relevant once we exit the Furality section of this post.
By some miracle, I had been pulled into a meetup that was a fair bit lower energy, less people, and the fact that I joined late made me an interesting anomaly. The lower the player count, the better I do with being able to speak. Even though the meetup had ended three hours ago, I was still able to get in on what would end up being a much needed social win in my mind that scrubbed the rocky start to Furality away, and laid the roots for more branching connections
Something strange I found as I continued to linger while people trickled out, was how being closer to 30 than 20 surprises me from time to time where, I've met people deep into their 30s who seemed to stagnate from the worldview they had when they were 15, and I've met people younger than me that I'd swear by their level of mental and social steadiness, and overall confidence and resolve, would be older than me, but would wind up in the "closer to 20 than 30" camp.
That, and the really precise feeling I was finally able to put words to where, VRChat is a game. Games are typically entertaining. Therefore, existing in VRChat sets a precedent to be entertaining, which then dictates the average behavior of those existing in VRChat. It's a precedent I don't fit into cleanly, because I really don't rely on humor to make connections. I'm a rough person to talk to if frequencies don't line up, because I'm rarely inherently entertaining.
Still, I stick to my guns, because I'm very comfortable with who I am, and how I perceive and carry myself. Problems only arise when I have to adjust that to fit into certain situations, as I can be really inflexible in how I act due to that very comfort
I unfortunately have to skim over a lot of neat stories for the sake of not having this post wind up novel length, but it was quite a magical time I treasured deeply, and I'm glad everything shook out in the end, because it felt like nothing was a guarantee leading up to it.
I still find it to be some kind of miracle that people actually like being around me.
Plus, I had the scraps of quite a cool outfit that wound up looking way better than I expected, and a very strong desire to turn it into something even cooler, now that I knew the base idea worked visually.
So naturally, I ripped my own jaw out, and broke it
Yeah, I had made the executive decision that if I was going to spend a lot more time with this model, that I'd correct something that's always bothered me about this model; The lack of visemes, due to using a jaw flap bone.
I've been told it adds character, but I've never liked it, and while I have my own problems with the imprecision of visemes, I do prefer them over the cartoonish properties of a flap bone. After some brainstorming of how to emulate lip movements on a solid jaw, the best I could come up with was to slice it a few times, remodel it separate from the body, attach each jaw part with metal pins, and control how each jaw part moves by using the pins to push and pull the jaw's position.
The result when viewed in static screenshots was drop dead gorgeous, and worth every second I spent on it, though I confess that the visemes themselves in action still don't feel properly dialed in. How much of that is the fault of visemes versus me, is hard to say, but attaining perfection with it misses the wider feel and lore of this model. If it looks a little wrong, then I'm doing it right
It was around the 11th, that I realized there was a scheduled Chimera meetup on the 13th, and I felt myself kick into overdrive to get this outfit to a similar "good enough" state where it would look striking, but whatever meetup came after it would be what made it truly shine, giving people something to anticipate, to look forward to.
In retrospect, as I write this near the end of the month, it's quite cute that I considered this specific couple days of rushing to finish this project by pulling 16 hour work days to be noteworthy, as what comes after completely and totally eclipses it. Perhaps it made me more comfortable with getting into the swing of having more exhausting but fulfilling days. Who's to say
One day remained, and while the skeleton of the outfit was finished and UV unwrapped, and a placeholder texture and shader was splattered across it for the time being, I still had a few key problems, and recognized that with the amount of time I had remaining, I had to triage this model to get it working in time.
Some silver linings that past decisions gave me for in-the-moment flexibility in my decisions; The tri count was below 30k, so I had a massive amount of headroom to go nuts with the outfit, and only wound up using some 4k tris once finished. Same with bones, and overall physbone transforms, so I could afford to experiment with a higher physbone count to dial it in just right.
Problem one, dialing in the right physbone sliders for paper strip (or tassel, if you want to call it that) physics was extremely tough to get right, and at the time of writing this, I still don't have it dialed in quite right. the way you pose a model in Blender or Unity is nothing like how you idly direct your arms in VR.
Problem two, just sloshing three separate patterns across red, green, and blue doesn't make for a visually pleasing pattern across three AudioLink channels, and when it comes to texturework, I hate half measures, because it is what I love the most.
I guess I didn't have a problem three that I felt passionate enough about to mention here. *shrug*
Something that perpetually bothered me was how root physbone transforms, in theory, could just be mirrored, as the paper-screen strip-tassels were symmetrical, and while I didn't figure out how to merge the left and right root transforms down to a singular center transform, (I suspect the answer is AAO, or Avatar Optimizer, is the answer, but this feeling is not tested yet) I did in fact have the spare headroom to allow an extra physbone transform to not stress it for the time being.
What bothered me more and kept slowing me down, was how often I had to go back to Blender, and adjust how each bone for the paper-screen strip-tassels (or PSST, if we want to be efficient) is weighted to each of the four bones, and then find out that it wasn't mirroring correctly, or suddenly one of my eyes had vertex data for the third bone of the right PSST.
Maddening. Weight painting is often the part of model creation or modification that I hear people hate the most, alongside UV unwrapping (personally I love UV unwrapping, but I will offer no backup for when weight painting starts a random gas station brawl). UV unwrapping is very absolute, while weight painting is an infuriatingly speculative process, no matter what I do to mask the vertices I paint over
The texturing however, while ultimately yes I did rush this part, I was very careful with what sections I kept a basic pattern on, versus what I hand drawn, due to the imminent deadline.
The jaw I knew was going to be a focal point, which meant that the head and shoulders by proxy, would also be where people's eyes would be drawn to, and the rest I could settle for ideas, instead of absolute concrete execution. The end result was fantastic, I'm not one to subscribe to the "less is more" theory of visual design, I find that more is more, and the art is in trimming excess and extremes by as little as possible to still execute on an idea beautifully, instead of playing with scraps, and assembling something from those scraps.
There's an argument to be made that the latter is more impressive on a technical level, akin to making the most out of one note in a song, but as time goes on, I find myself disagreeing more and more with that perspective in the subjective qualities of what I enjoy, and how I approach ideas when making art of any kind. That, the impressiveness of both making the most out of the least, and arranging cohesion out of a high volume of ideas at once, have equal merit, yet only minimalism is commonly held up to this focal point.
(Fucking hell I really didn't expect the back half of this post to get so philosophical, but I was really in the mood to wax poetic about this stuff I suppose. No regrets though, it's unashamedly who I am)
The payoff.
The payoff was so good...
...and yeah, that's right, I'm breaking my usual 1100x500 resolution rule for these two photos, they're special to me.
As it turns out, when you are a goddamn shark-shaped neon sign that breathes with the rhythm of the world, and the music within it, you become very hard to ignore at first. Keyword at first, people do get accustomed to it, but the initial point of impact, its depth, the force it generates, sticks with people. I remember clearly that "Woah" moment some had where their stream of consciousness was interrupted by something they weren't expecting.
You'll start to notice now, the two recurring characters I established before will reappear in the two group photos, both of them lent me an immense amount of comfort and mental fortitude to stand my ground in the proximity of others, and adamantly exist
I wound up spending almost half my day in VRChat. I was that comfortable. It honestly makes me a little emotional to type about, emotions that I don't have the words to convey. Complicated ones, but happy ones too. An affirmation that my social skills hadn't completely calcified.
This double meetup revelation would wind up being the most significant moment for me, and would act as a starting pistol for me to sprint down a path with a degree of passionate, single-minded focus I have never had before in my life. Every day was a minimum of 12 hours (though most days were 15-18hr ones, I'm just being conservative) of working on all aspects of this shader, of implementing features, getting UI working, getting icons done, solving problems, anything and everything at whatever cost it took from me. Complete and total indefatigability.
I can't imagine what I looked like from an outside perspective during this time, but the level of devotion I had towards creating something complete, something absolute and without major compromise was transcendental. It was all I talked about, all I thought about.
This is the end of the Furality section, and the start of the final lap. The custom shader section
It started with being dissatisfied by a specific feature of poiyomi's toon shader that I found myself relying on a lot, for its very unique and striking properties. The TN display effect, of which I applied to a red sphere and cube above.
By their own words that I will copy paste verbatim here
"Simulates a Twisted Nemanic (TN) display, characterized as being fast and cheaper among the other main types of display panels. The downside of a TN display is that is has bad viewing angles, meaning portions of the picture can fade away when looking at it from an angle. It can also produce weaker color accuracy."
On the surface, that's a pretty good summarization of what a twisted nematic display does, but both the description, and how it gets implemented as an actual effect are imperfect in subtle but fascinating ways.
Which is what I would say, but for the sake of transparency, I'm about to discredit myself and the core purpose of this shader when I say that twisted nematic panels assembled correctly and used in an ideal environment do not have separate hue distortions for the left and right side.
See, when writing this, I wanted to find concrete, solid evidence of a TN panel's hue distortion from both steep left and right viewing angles. When I was originally writing this shader, I couldn't find anything substantial beyond a few odd images from very bland manufacturing sites, and a couple videos that only showed one side to viewing distortion.
Then I remembered RTings, and their debacle with fighting against bots scraping their data, and how they had to privatize a lot of their data to paying viewers only. I figured "You know, I bet they'd have something on this, if I'm actually crazy and hallucinated a subset of distortion, they're super comprehensive about these details"
Well, fuck.
Ah well, I'm happy with the shader, and we'll just say the "acid" part of Twisted Nematic Acid is uh...the asymmetrical hue shifting. It's a damn cool effect after all, even if I've now strayed well beyond the initial goal of a higher degree of accuracy. We'll just blame the struggles of current year misinformation and move on.
The thing I did wind up getting right however, was the inversion effect from below, and the gamma blowout from above, as both of these are already documented extensively with screenshots and videos, with the former being something that poiyomi's toon shader doesn't seem to do.
For context, the center of this image is the actual distortion that happens, while in the bottom right is me inverting the original image, then distorting it to the monitor's perspective.
See, a lot of people erroneously call the effect when viewing a TN panel from a low, sharp viewing angle (or if you wish to think of it like this, with the panel tilted upwards from a dead-on view), an "inverted" image, but there's a cool nuance lost in that description, because while the colors are inverted, everything also loses a significant chunk of luminance, as both blacks and whites converge to that lower, deeper grey. Blacks actually remain relatively close to their original luminance. It's just a very... distinct look that I recognize on sight, and felt like emulating shouldn't be THAT hard
...after all, how hard can it be ?
Let's start with the simple (now proven incorrect) left-right hue shift, and how this wound up being the most mind-bendingly complex thing I've ever written to date. In fact, I'll just give you the code right now, no credit needed ! (Credit's always cool, but not an obligation for just some inverse trig and formulas I didn't invent, and don't have the time to break down how it gets passed to the fragment shader)
float3 LumaHueShift( float3 col , float angle )
{
float rad = radians( angle ) ;
float cosAngle = cos( rad ) ;
float sinAngle = sin( rad ) ;
float3 axisLuma = float3( 0.299 , 0.587 , 0.114 ) ;
return col * cosAngle + cross( axisLuma, col ) * sinAngle + axisLuma * dot( axisLuma, col ) * ( 1.0 - cosAngle ) ;
}
Voila, a kinda sketchy approximation of Rodrigues's rotation formula. Let's break this down line by line, and hopefully my explaining isn't too incoherent, and covers most hypothetical questions.
float3 LumaHueShift( float3 col , float angle )
We're setting this up alongside our initial data variables as its own equation of sorts, that a float3 called LumaHueShift that holds two more bits of data as its own internal variables; float3 col, and float angle. We can swap these out later with our own data in the fragment shader, so that our color updating is accurate.
float rad = radians( angle ) ;
We use the
radians() function to convert our angle we put in, to a radian (basically radians = degrees * ( pi / 180 ) ) which is required due to Unity needing to (didn't try to break it to see if I'm wrong) work with radians for sin(), cos(), and tan(), instead of our filthy degrees. Picky, picky, picky...
float cosAngle = cos( rad ) ;
float sinAngle = sin( rad ) ;
From here, we're gonna chip away at pre-calculating parts of that rotation formula bit by bit now that we have a value in radians instead of degrees, with angles. I tried combining these into the singular line at the end, but suspect my syntax was wrong.
float3 axisLuma = float3( 0.299 , 0.587 , 0.114 ) ;
What, you thought I was referencing Furality's Luma shader with that variable naming ? Nope, I was using the actual terminology for video brightness ! I'm so devious.
But yes, these are just copy-pasted from Wikipedia's page on Luma, and the choice of
0.299 , 0.587 , 0.114 was me arbitrarily picking one. You could easily replace it with any of the following, just to be different.
float3 axisLuma = float3( 0.2126 , 0.7152 , 0.0722 ) ;
float3 axisLuma = float3( 0.212 , 0.701 , 0.087 ) ;
float3 axisLuma = float3( 0.234 , 0.789 , 0.123 ) ;
...anyway
return col * cosAngle + cross( axisLuma, col ) * sinAngle + axisLuma * dot( axisLuma, col ) * ( 1.0 - cosAngle ) ;
Here's the culmination of all our preplanning. If you squint, you can genuinely see that it's...really just Rodrigues's rotation formula, except spoken in shader-tongue. If I were to break down each aspect of how the shader works however, this would genuinely never come out, as I'd have another 392 lines to translate. I've found that just, randomly picking examples of practical trigonometry uses in shaders has helped me a ton to more easily conceptualize a lot of the rougher, scarier math. If it has a purpose, and I can see its effect, it sticks much easier.
Next, inversion with black and white smoothed to grey. I'm not as happy with this one, and what you're seeing was an edit after the fact that's slightly more accurate to what I believe TN display distortion looks like, where it has that muddy, washed out look to it, but for the record I stuck with just a straight up inversion by way of a
lerp().
float3 colDownInv = lerp( col , 1.0 - col , _AE_DownInvert ) ;
col = lerp( col , colDownInv , downMask ) ;
The gamma blowout of viewing from above is so unremarkable and simple that I didn't find it worth writing about, or so my notes tell me to say. With that, we have up, down, left and right distortion achieved...and still a long way to go
For the foreseeable length of however much more I continue writing, I'll be using the finished shader to detail my journey here, and discard the prototype I've been showing thus far.
My next order of business was to smash the bouncy ball shader from February, so I can have some two-tone blending with sparkly bits, and in the moment, it worked without any need to adjust a damn thing, which ended up creating such a wonderful color contrast, that I almost completely ignored the toggle for if I were to use a base texture instead. Luxuries of writing a shader for me first, I can just adjust things how I want them
If you're keeping track of how long this has run on thus far, this is where we're at. Color distortion, sparklies, and two-tone adjustable colors. The two last major things we're missing (or so I fooled myself into believing was all that was left at the time) is emission, and AudioLink...and maybe a transparency slider, so I can just fade it in.
AudioLink would be the biggest overall timesink that would lead me down numerous false paths and incorrect data, so I'm gonna break it down right now, precisely how the variables I use work
I find that the big thing I get caught up on when trying to process how code works in my head, is understanding communication. More precisely, how does my shader know what AudioLink is "saying" to it ?
Per the documentation at the time of writing, the magic line to open up a line of communication between the two, is
#include "Packages/com.llealloo.audiolink/Runtime/Shaders/AudioLink.cginc"
Smash that at the top, below your boilerplate
#include "UnityCG.cginc", and you now have access to the variables used by AudioLink. If any source online starts yapping to you about using either Texture2D<float4> or uniform float4 _AudioTexture_TexelSize;, stop.
They're giving you outdated instructions meant for surface shaders, not fragment shaders. What we...or rather me...I, am writing right now, is a "Fragment-Vertex shader in Unity's Built-in Pipeline" shader, where we deal with HLSL code at a lower level. There seems to be an endless glut of outdated sources scattered about that use the semantics and logic of the much older "Surface shader in Unity's Built-in Pipeline".
Refocusing. To start working with AudioLink data down in the fragment part after punching in your `#include` up above, personally I use a ternary operator
float isAudioLinkPresent = AudioLinkIsAvailable() ? 1.0 : 0.0 ;
Where we can now have a kinda bootleg true-false (fragment-vertex shaders do not have
bool support by the way, hence 1 and 0 instead of true and false) section that tells us what to do when there is AudioLink data present to sample from. From there...well, I wanted to be able to pull from all four major channels (Bass, Lo-Mid, Hi-Mid, and Treble), but I only have R, G, and B to work with. So, I set up two passes for Red, Green, and Blue...
float ampOneR = 1.0 ; float ampOneG = 1.0 ; float ampOneB = 1.0 ;
float ampTwoR = 1.0 ; float ampTwoG = 1.0 ; float ampTwoB = 1.0 ;
ampOneR = AudioLinkLerp( ALPASS_FILTEREDAUDIOLINK + int2( 15 , _AE_BandOneR ) ).rrrr ;
ampOneG = AudioLinkLerp( ALPASS_FILTEREDAUDIOLINK + int2( 15 , _AE_BandOneG ) ).gggg ;
etc...
then masked it with the miracle line...
ampOneA = AudioLinkLerp( ALPASS_AUDIOLINK + float2( i.uv5.x * ( AUDIOLINK_WIDTH - _AE_AL_PulseSpeed ) , i.uv5.y * 4 ) ).rrrr ;
"Why are you up to UV5 ?"
Plain and simple, I find that editing UVs gives me a TON of controls to effects that are tough to achieve any other way, and I encourage anyone working on their own model to experiment with using alternative UV maps. I did wind up designing myself into a corner however after using UV2 and UV3 for having a separate texture for it, and animating a mail UV transform as an animation. WHole thing kinda spiraled further and further, and I found myself going all the way up to UV6 to correct a perspective issue I wrote myself into near the start.
FBX gives you UV0 to UV7, and if anyone starts acting superior to you, asking you what the hell you need eight UVs for, tell them that FBX supports 8, and you want to use all 8. Don't let anyone boss you around when experimenting and learning, and if anyone starts yapping about the industry standard when you have no interest in working within that industry, take their info, but discard their toxic attitude, because there's so many valid reasons why "industry standard" can be a negative thing to follow.
The result though made my excitement go fucking non-linear
What works about this so well is precisely the fact that I'm masking it with a completely separate UV, allowing for trailing masks that go around my model asymmetrically, separated by audio channel. I had an astute point made by a friend that most implementations of AudioLink on avatars are "center-out", where they originate from the middle of a texture, and pulse outwards, while trailing AudioLink seems much rarer, and stands out more.
This did raise multiple issues with emissivity (apparently the word for it isn't emissiveness, go figure) that I wasn't quite expecting at first, and that I want to detail here as a side warning; HDR lighting behaves pretty strangely
See, the issue is channel clipping. If you want a super bright blue emissive color on something, you can't just crank up the intensity to get there, as eventually the blue channel gets capped out, and it has to sample from the next closest color, either red or green.
Now, if you want a high intensity level, and you just...want a CMY emissive look, this would be absolutely fine. But instead of just swapping it to a regular sRGB-SDR color, I found a different solution that I was absolutely in love with
Invert it !
The laws of HDR don't mean shit if you just invert it, and the effect when you can selectively mask out red, green, and/or blue channels on one's emission-AudioLink mask to mix and match is genuinely very striking. A probably better example of a ternary operator's usefulness is for situations like this exactly
float ampRedInversion = _AE_AL_RedInvert > 0 ? -1 : 1 ;
float ampGreenInversion = _AE_AL_GreenInvert > 0 ? -1 : 1 ;
float ampBlueInversion = _AE_AL_BlueInvert > 0 ? -1 : 1 ;
You wind up with three values you can apply to each channel where, if RedInvert is toggled on (greater than zero), multiply whatever the final result of that channel is by -1 to turn everything about it negative. Otherwise, multiply it by 1, and nothing will change because any number times 1 is just 1
(No, I don't regret more or less locking myself into 1100x500px photos, why would you ask ?)
All the while, I was chipping away at understanding how C# works, as custom shaders always have cool, bespoke UI, so clearly mine should too.
This was an AGONIZING experience that made me really hate how fickle and picky (a pickled ficky, if you will) C# is with the sheer amount of boilerplate needed to set up anything about it.
I actually suspect that Visual Studio's half-hearted implementation of shader code where I get the syntax, but I can't see it error out, is part of why I feel so much more comfortable working with it over something like C#, because the second I change a single thing about C# code, the error log has a fucking CONNIPTION and claims 129 different things are wrong, you fucked everything up, what are you DOING.
While HLSL's lack of immediate feedback to me about if something I wrote is wrong or not results in me being a fuck of a lot more confident in what I'm writing, and parts I get wrong are only pointed out to me when I save, and check Unity, with the issue ending up being "oh I didn't adjust the name of something near the top" or "oh yeah I didn't specify this was a float3, not just a float".
The end result isn't even that much different from the regular UI. It ends up being a lot more organized and personalized, sure, but I don't know how worth it I feel it was, beyond finally being able to say that I wrote C# myself, as that's a pleasant confidence boost to be able to state, given how I really do not feel like a programmer, I just like my shader math. Next time I do this, I'm turning off the error log for 99% of it, because I really truly hated that kind of immediate negative feedback while I'm writing with complete and total rancor
Speaking of UI, the more fun side I had to learn, was how custom menus worked.
I set myself the lofty goal (that wound up revealing to me that my drawing tablet was likely dying) of having a custom icon for each menu variable, and discovered at some point that I was confusing up and down in my variable and menu names, so now the whole thing behind the scenes is a lot more jumbled, but I do have those menu icons now
"Wait hang on, is one of those for dividing by zero ? Also, why is this in a dropdown box, and didn't you use this already without explaining how it works..."
You would be correct, I'll explain dividing by zero in a moment. As for this dropdown box, I've found myself arguing with the voice in my head that tries to take all words I write in the most reasonably hostile way possible to be a really useful way to elaborate on side tangents without de-railing everything. While I do also think it could be misconstrued as a straw-imbued target for poor arguments...at the same time, it's very much just, not my intention to use it for that purpose. I'm here to talk passionately about my projects first and foremost.
The reality of dividing by zero is equal parts extremely cool, and quite uneventful.
The concept of NaN (Not a Number) for shaders is the cool part, because often we'll just interpret whatever the hell we want as data to pass to the GPU. Turn RGB into XYZ ? Sure, we have swizzling for a reason. Use time to adjust vector positions, vector positions to determine color, it is highly uncommon we can't pass something as something else, so long as it is formatted correctly.
NaN is one of the big exceptions, because as a data point, it corrupts, or "propagates" to whatever else it is calculated alongside. If your R, G, and B values are ( 105 , 85 , 65 ), and you wish to apply something whose value is NaN...well
105 + NaN = NaN
85 + NaN = NaN
65 + NaN = NaN
...making our new color value;
( NaN , NaN , NaN ). Multiply it by your lighting, and how it reflects off, and now your lighting becomes NaN.
If you want to see more, look up "NaN propagation"
Now, if you were worried about me having made a crasher, let me extinguish those thoughts with this side question.
"What's a crasher ?"
A crasher is a malicious type of custom shader used to deliberately lock up one's game, or severely disorient someone's vision. While normally in any other medium, I'd consider these a somewhat harmless form of trolling, VR opens up a completely different, much more negatively intimate vector of attack, given that you have two screens strapped rightup to your eyes.
Do not contact me about how to make a crasher, or ask me to make one for you, as I want nothing to do with you if I have even the faintest sense that you are simply learning about it for malicious purposes. My interest in malicious shaders is strictly white hat in nature, and my own curiosity to how these attacks are performed, to inform and be informed.
To paraphrase someone else paraphrasing Plato from what I believe was Republic (though I am not 100% certain on this, feel free to contact me with a source if I'm right or wrong), "The truly skilled doctor is one that knows both how to heal someone, and how to harm them." Apply this to psychology, cybersecurity, or any skill that takes intricate knowledge, and hopefully you can see why I find these morally dubious concepts fascinating, because it gives me a better understanding of the measures more skilled people have gone through already to protect those that are subjected to the craft
The reality of what this winds up looking like both in-game, and in Unity, is quite uneventful, as Unity has a built in fallback color set when NaN is detected, and that fallback seems to be identical for VRChat. Still, I included it as a toggle because I can absolutely envision a conversation where I mention what someone is looking at is a shader I made. I scroll through the options, then offhand mention I can divide by zero, and I now have an opportunity on the fly to nerd out about how shaders work.
I am now going to proceed to gloss over the many, many problems I faced, because I don't find that it makes for great writing at the moment
- Running out of parameters, and reducing the AudioLink channel switching to cycle through bool values instead of using a radial float
- Believing that VRCFury was corrupting something about my shader, and wasting a lot of time trying to manually compress parameters (turned out to be untrue, though AudioLink emission intensity did default to 1,000% for a while)
- Realizing I fucked up my view direction code, and believing that manually adjusting the individual faces of my clothing on UV0 would be easier (in ways it was, but it was a stupid approach, because now I'm locked into using tangent space and I don't feel like fixing it because it just works for the time being)
- Radial puppet menu parameters getting jammed (I put unnecessary information into a field, and despite deleting that data, it somehow persisted, and caused the menu to be stuck)
- Wasting time trying to figure out why my UI code was spitting out HelpBox errors (it was a different plugin's fault, not mine)
and much more that I'm blanking on. I intended to actually do the exceedingly rare act of mine where I posted about this project somewhere, but I suck at using social media platforms, they aren't built for how I work or relax. The only time I ever open Reddit is to solve a problem, and even then the quality dip over time's been getting worse. Social media gives me no satisfaction, and by default my headspace goes to "why would you care ?"
I truly hope I never have to make a post this long again. I mean it was a little fun, but it should feel celebratory, not like I'm cramming.
Get excited for more watch stuff next month, along with a possible bonus writeup of the FBT setup I am constructing from Altoids tins, and I suppose an update on however well my shader winds up being received by those lucky enough to witness my fabulous glory. If the length of this post is not a testament to my commitment and passion for this kind of work, then I suspect I would never be able to convince you otherwise.
If you are wonderfully demented enough to have read through all of this, thank you. I have an RSS-Atom feed if you want to do the open source equivalent of subscribing to what I post on this blog (you don't even have to give me your e-mail, your phone number, ID, or your blood ! Pretty good deal I'd say), but if you're just stopping by to learn about the shader, then I hope I was able to demystify the process a little bit.
🧡
- Aethena, 2026-07-01