Tumgik
#the brain is 3d and insanely complicated obviously
athetos · 2 years
Text
When I was in undergrad and had to memorize basic neuroanatomy, I kind of constructed a metroidvania-type game in my head, full of interconnected rooms, each that represented a region of the brain. I then came up with enemies, obstacles, and tilesets that could be found there to help me understand their function. For example, the corpus colossum, which connected the left and right hemispheres of the brain, was a massive spiderweb-like bridge. But instead of forgetting it as soon as I passed the class, I kept thinking about it, the metaphor kept expanding, and now I have a big document of ideas for an actual game that I’ll never make that helps me study to this day.
9 notes · View notes
n7punk · 1 year
Text
"the long way down" Fic Notes
TLWD is done now so I get to be a little insane about it. I’ve had meta about Outside of the War fics before, but this one… is certainly something Else, so I’ve enough to say to need the structure of a fic notes post.
Chapter 1:
⦁ Off the bat, the Mara thing: again, I’ve said this a couple times, but I don’t think her surviving in the portal is canon. I’m not sure what the timeline of the Heart going off and Mara moving Etheria was, much less what was up with the first version of the portal (something happened from what Razz said, but who knows it if it was the exact same thing), so it’s hard to say if she even would have gotten suspended like that in the first place. It was fun to write that fic, but I didn’t want to carry that continuity into this one. I wanted this idea to stand on its own and Mara being alive would have complicated the hell out of this. She would have known stuff, and either wanted or really not wanted to see the ruins of Eternia, and it just… would have been so many complications, especially if she didn’t go with them on that journey, because then they would have had to debate going back for her or not and…. Yeah it’s just Too Much.
⦁ The little italic preview (something I use so sparingly I think this might be my first time ever) at the beginning of the fic is obviously from the scene in chapter three, but cut down to size because again, sparing. It’s contextless here and half-context wouldn’t have served it.
⦁ It has come up a lot in my fics, but Adora being into cartography is something Aimee (her VA) mentioned at a con as like, a headcanon, and my brain immediately accepted that and went right that’s canon now lol. It just fits really well so it’s come up in my post-canon fics a lot as a hobby, but this is the first time it has been plot-important.
⦁ Catra getting jealous of paper is from a previous OotW fic, return from civility.
⦁ George and Lance literally have a 3D-projection star chart in their library that they show off in the season 2 finale, but the star charts Adora pulls out are very different from that because it’s a 2D inscription of flight paths between systems rather than a chart of constellations, so it wasn’t blatantly obvious what it was until they could start translating things with Adora’s help.
⦁ Let’s talk about the chapter art because you cannot imagine how much work that took! Like it’s stupid! I wanted essentially an “arrow” made from First Ones numbers pointing in the direction of Eternia. I went through a lot of rough drafts before I decided, for simplicity’s sake (my sanity) instead of it literally saying “One thousand four hundred and sixteen” it would say “1416” because god is that a lot to cram into a small space and stay readable, much less connected. I’m like 95% sure what I produced is correct by the guidelines of the language, but it’s a lot of lines to track, so if it’s wrong… I don’t want to hear it. Some details, though. The shadowy background compass has an “indent” in the center of it — that’s actually the letter E, standing for “Eternia,” in the First Ones’ script. Each of the numbers/words gets progressively smaller as it goes on, which isn’t in the rules, but in my opinion it makes it easier to read because you can intuit that you’re supposed to start at least one of the ends, and Clare approved, so I’m keeping it. (Yeah, if you know what post I’m talking about, I’ve been sitting on this fic idea that long. I wrote half the fic then and got distracted by shiny new things lmao). It was really hard picking colors for the image that would show up well on various site backgrounds, but in the end I decided it was impossible to accommodate for most skins, so I should focus on the two most-used ones instead and ended up with the blue and gray that show up decently on both the white and dark mode site skins. It’s not perfect on either, but it’s pretty good on both, and that’s the best I could ask for. I purposefully picked numbers that were one syllable so I didn’t have to deal with multiple syllable branches since making this image do what I want was already so much work lmao. I tried to figure out the words by myself first but I always double-check my First Ones script work with the translator on itch.io so thank you to whoever made that you have been a life-saver multiple times for me <3
⦁ I went back and forth on whether they’d be wives by the time of this one, and then I remembered it’s somewhere around two years post-canon and they’re lesbians. I think they probably got married shortly before leaving. I also stand by the idea of Glimbow getting engaged first but married way later (royal wedding nonsense, it came up in either "to make a home," or "her heart on her sleeve." Maybe both) and since they were getting married right after this trip, that would mean Catra and Adora are married.
⦁ Trolla is a planet in the MOTU. It’s where Orko is from… which means nothing to you if you haven’t watched the original He-man, but google an image of him, because somehow that exact character design pops up in so much shit. I’m pretty sure there’s a character like that in Kingdom Hearts, which means there’s also a character like that in Final Fantasy, but don’t quote me on that. I feel like he and Loo-Kee were pretty equivalent to me when I was a kid and Loo-Kee was at the end of every damn She-ra episode with his little “today’s moral” shtick.
Chapter 2:
⦁ Honestly, part of the reason Catra is wielding a staff in the cult scene is because I did all the research for the dolls and her accessory in that line was a staff. Which… okay sure, but she does use them in canon. Like twice. But it felt right here with the potential flashback stuff.
⦁ I made sure to include mention of the two moons originally shown around Eternia in my Children of the Crystal series. Just a little bit of consistency.
⦁ I thought about the… manufactured planet thing from Mass Effect Andromeda (listen, I haven’t played the game since it came out, I don’t really remember) while describing Eternia, and it got me thinking about how in sci-fi and stuff, structures like this are always in grayscale. They’re gray, or maybe white, but never like… yellow, and certainly not rainbow. The First Ones ruins we’ve seen have all been very colorful, so I thought a vibrant, rainbow planet, indicating life and art and culture, but still from this destructive society, would really be interesting.
⦁ The “ruins incident” is the impotence for a fic I’ve never written and probably never will but damn has that not stopped me thinking it through every few months. The timeline doesn’t even work out because that’s supposed to happen like five years after this but it’s mostly a reference for myself lmao.
⦁ I know Catra and Adora have “memes” personal to them dating back to their time in the Horde and still forming to this day, but I imagine Catra and Melog have their own jokes too that no one else has even the faintest idea about because it all happens in their heads.
⦁ Originally I explained it in the fic before I decided the Gatekeeper wouldn’t know it, but the reason there were only signs of evacuation deep inside was because part of Protocol Zero’s mandatory evacuation was evacuation tiers. It’s the highest threat level possible and that means everybody needs to leave and could clog the skies, making it impossible for anyone to really escape. There’s also bullshit prioritization of “well, these people deserve to get out more than others” so it's stratified to ensure some people (the most important) get out first. First in line was royalty and the highest ranks of the military (to coordinate rebuilding efforts once escaped), then the highest ranks of state-sponsored scientists and nobility, followed by the mid ranks of state-sponsors scientists and high rank state-sponsored artists, and then concurrently mid-level military, more scientists, and high-rank citizens. Finally, the rest of the artists and citizens (aka free-for-all tier).
⦁ The Citadel is a reference to Mass Effect.
⦁ A whole decade ago I saw a post on Tumblr claiming a “moment” used to be an actual measurement of time (roughly 90 seconds) that I never forgot and it turns out that was actually true (this was the era of Alexandria’s Genesis so you know… easily could have been bullshit I never bothered to google) so I threw it in in this fic as a fun fact about medieval sundial measurement.
⦁ Almost nothing works on the planet because it all ran on wireless power, and the transmission towers for that were completely destroyed by the Horde’s invasion. The Horde them pulverized everything else it could find, tracing down every last electrical signal or sign of life and snuffing it out. The only thing that survived were bugs that were hibernating underground or in the rare piece of plant life that wasn’t destroyed. Seeds in the earth also sprouted later, leaving those as the only survivors of the massacre. The Horde occupied Eternia for a while making sure nothing else would pop up, but eventually Prime was satisfied and got the fuck out of there, afraid of their integration of tech and magic and tired of losing clones to lingering mechanical boobytraps.
⦁ Okay the meta for Mara’s painting is that one of the quickest paths to state sponsorship was art depicting the planet. The Eternians really did view it as the ultimate art piece. Mara did something groundbreaking for her time and portrayed it as it originally was before all the terraforming. The establishment didn’t like that. Even worse, she used an ephemeral medium involving a mix of materials that would break down over time, and while it was good and above average, she was far from the most impressive artist that year alone. Mara really did think the transformation of Eternia was awe-inspiring at the time, but doubt set in after she was assigned to the brand-new (not even begun, really) colony on Etheria and started falling in love with its vibrant nature. Eternia still had plenty of it, but it was carefully cultivated through landscaping and terraforming. Ironically, it has become a lot closer to Etheria’s untamed state by the time of the fic, thanks to the lack of direct intervention and partially-damaged terraformers. (Side note: the terraforming system ran on its own network with a million safeties in place because, you know, if they ever shut off the path of the planet around the sun could permanently be altered from its irregular shape. It was Very Important they survive anything, and Horde Prime didn't try to destroy them while occupying it because he also needed it to not be flung into the sun)
⦁ Being a state-sponsored artist basically means you get a salary instead of living commission-to-commission, access to grant programs to create big pieces of art, essentially advertising and general public approval, and access to exclusive display locations, especially in architecture or high-traffic areas (think the mosaics they found). Mara wanted to try, so she applied to the symposium basically fresh out of high school with the plan that if it didn’t work out she would follow in her parent’s footsteps (military family) and join a colony exploration program. If she got approval or even just a grant she would go to art school, but, well, she didn’t. So she ended up training in military academy, bouncing around a few colonies, and then making her way to Commander rank and Etheria.
Chapter 3:
⦁ The “three workshops that they know about” thing is because Catra is sure Entrapta is hiding more questionable experiments from them in the walls. And she’s right.
⦁ The data crystal art says “Final log” on its front and “Distress” (as in, distress signal) in the background. I never really translate that in the fic but it was also going in the final chapter when the fic notes were about to be posted so I wasn’t too worried about it.
⦁ My idea for the crystal was that it would be really hard to find any logs of what happened because, well, everybody who would have made or kept those logs was dead and the Horde destroyed a lot of what they found. This was a transmission that was intercepted at long-range on this smaller mining planet and was basically a portent of their imminent doom to them, the Horde arriving shortly after they received it, leaving this as one of the few logs the Eternians managed to preserve.
⦁ I didn’t actually reference a data crystal from the show for my drawing, I just ran off my memory of what it looked like, so it’s probably a little off, but I thought that would actually fit with the “artistic representation” thing like the planet had. The downward slanting of the words (and especially the descending of the sounds in the word “Distress”) are a visual representation of the signal dropping off and the “long way down” metaphor.
⦁ I didn’t actually intend to have an illustration per chapter for this fic initially. It was just supposed to be the first one. I drew that all the way back in January/February when I first started working on this fic, but as I was describing Eternia, I was like… this would actually be fun to draw. And then it seemed weird to have a drawing for 2 out of 3 chapters, so I added the data crystal, and I like how that worked out since I got to do the “subtle” stuff (so subtle it needs to be explained lmao) with the distress signal in the background.
⦁ Yeah so what’s up with that metaphor, right? I don’t know. Like, okay, I kind of do, but basically that song latched onto this fic and then they fed off each other and I have NO idea why. Like, it fits, but it’s also like… why this. For the people who don’t listen to songs when fics link them (I’ll be honest, I rarely do, even though I discovered my favorite band in the world that way), “Long Way Down” is a song about going to hell. I think the specific story is supposed to reflect preachers and religious folk who espouse how everyone else is full of sin and going to hell, but really they’re just going to end up down there too (“I’ve been fucking around while you’ve been saving the world... from nothing”). It’s not a direct fit in that way, but in the hubris, where you think you’re giants but you’re actually going to fall like all the others? Yeah, seemed fitting for the First Ones.
⦁ Tellus is earth in Latin. Being that the originals are 80s cartoons, Earth is of course canon and there was a Christmas special set there. As I remember it, Earth was some #Isekai shit (okay not really but) where it was just the normal modern world and then He-man and She-ra showed up to take teens on adventures so Americans could teach them the true meaning of Christmas or something. I haven’t watched it since I was a very small child so I could be wrong. I got sidetracked. Yes Earth went kaput in this universe. Yes that had consequences. Yes he was saying “Commander Adam, signing off.”
⦁ Having just come off of CotC where I finally really went into my time travel headcanon… Yeah a lot of people knew this wasn’t going to end well. Honestly, when I initially planned this fic, I had this idea that Adora would touch the console and it would greet her as the lost princess — or even that she would find a recording from her lost twin brother — but either way she would find out that she probably came from the past. The problem was… that also doesn’t match my headcanon? (Refresher/If you didn’t read CotC: my headcanon is that since dimensional portals were cut off in Despondos, only time portals worked, so Light Hope pulled Adora through after spending a long time building one and trying to figure out where to open it to get her. This is why she popped out in the middle of a field: that’s just where she was physically located in the past.) My headcanon is that Adora was a regular child to settlers on Etheria when the Horde attacked and Light Hope eventually made the time portal and snatched her away before she could die. The system back home on Eternia probably wouldn’t recognize her as a random baby in the colonies, so it just didn’t track (especially the princess thing. That was something I thought about when I was first forming the headcanon in 2020, but it didn’t stick. Adora being “normal” is what made sense to me). I did consider having it be some thing where like, well maybe she was still born on Eternia or some major colony where she would be registered in the system, but honestly, that was all too definitive, and I liked the idea of it recognizing She-ra and them learning a little more about Mara.
⦁ Honestly, I don’t think Adora and Adam are even twins in this verse for me. I think cousins makes the most sense here (especially since he was like 20-30 in the recording and Adora would have been Very Small). If he were her sibling, that would make him some random twin she doesn’t know and never learns much about, but this way he’s still important to present Adora because his recording is the one that really made her accept what happened, and isn’t that the kind of thing that older siblings are supposed to do? Take care of their siblings, loving them and telling them the hard truths, through losing their family/home/way of life, etc? Idk, this somehow felt closer to the spirit of the OG canon than giving her a random twin that she didn’t know much about. Her finding a living twin who lives somewhere out in space didn’t feel like the spirit of this SPOP either (especially since they were expressly forbidden to even consider Adam/He-man), so we ended up here.
⦁ This is gonna sound weird but — canonically — I don’t think Adora has parents unless they make a movie. Like the time travel theory still makes sense to me, and if they ever made a movie I feel like finding her parents out in space would be an obvious plot point to get a bit more of a “happy ending” and tie up loose ends from the show, but like… I don’t think the show — in isolation — intends for them to be out there, and I think her not finding them is more tragic if they’re alive somewhere than the time travel thing where they’ve been gone for a thousand years so there’s no chance of recovery.
⦁ After some discussion with a Science Person I determined that it was possible for the DNA changes to be passed down in people who constantly produce sperm, and for people with eggs too if the process also affected the eggs inside of them (since they're only produced once). Also, magic bullshit is floating around helps and all of this is all theoretical anyway so I’m doing what I want lol.
⦁ Edit: Somehow I forgot to talk about this but. The First Ones. Yeah so, in this... I mean they are basically human. Their terraforming (and general tech) processes changed them into something distinguishable, but not all that different. After a thousand years, any First Ones that did survive the original genocide would have to have intermixed with humans to survive. The changes from terraforming would long be diluted and mutated away until humans really are all that's left. Catra's speculation about resettling an old colony planet is the best chance for them still existing, and like she said, they wouldn't even know that they're secretly picking up the genetic signatures of Eternians. I've never been sure if the First Ones are actually different from humans, or just a nationality, or perhaps an ancestor/divergent race from them (I think that question is purposefully left open-ended), so I did... this, which is kind of a combination. IDK, I just thought it was a cool possibility that would explain their humanness while still making them distinct in a (fairly) plausible way. The materials our modern world is made of are causing changes to our bodies (from lettuce insecticide in our blood to earlier hormone development) so something as extensive as terraforming would produce changes that could diverge them enough to be detectable. This leads to things like Adora's puberty being a bit different (unnaturally fast) and her alcohol tolerance being wild (I mean, that's something that varies between people already without a genetic splinter) but leaves her still - essentially - human. First Ones and humans started from the same place but splintered eventually. This isn't like some other things where it's my firm headcanon, but I do think it's a cool idea, so it was fun to do here and it both acts as another hint to the time travel possibility while also explaining how Adora could have parents and there could be "surviving First Ones" in the present. It wasn't part of the original plan for the fic, but I'm really glad it developed as I wrote because it's really interesting to me.
⦁ Catra is right about the cycle wearing on Adora, but it’s a little more than that: Adora could only convince herself to leave Eternia by turning her attention back to their mission, and the moment there was a real pause in it she realized that she didn’t want to keep doing this after all.
⦁ Adora will have questions for a while, and wonder about it occasionally, but she has kind of accepted that what really makes her happy is Etheria. She wishes she knew the answers, but not enough to abandon where she's actually happy and search for them. She doesn't want to fly for a year to some location in far space for answers, she wants her true family, and that's Catra and her friends.
Meta:
Compass art (1416)
Eternia art
Data crystal art
Upcoming:
Uhhhh I really don’t know right now? Could be a lot of things. My brain can’t settle on anything long enough to choose right now so it’ll probably be a week before I’m even sure enough to tell y’all. Check back here I guess lol
16 notes · View notes
gargaj · 4 years
Text
A breakdown of the Revision 2020 Threeway Battle shader
Those of you who have been following this year's edition of Revision probably remember the unexpected twist in Sunday's timeline, where I was pitted in a coding "battle" against two of the best shader-coders in the world to fend for myself. Admittedly the buzz it caused caught me by surprise, but not as much as the feedback on the final shader I produced, so I hope to shed some light on how the shader works, in a way that's hopefully understandable to beginners and at least entertaining to experts, as well as providing some glimpses into my thought process along the way.
youtube
Recorded video of the event
But before we dive into the math and code, however, I think it's important to get some context by recounting the story of how we got here.
A brief history of demoscene live-coding
Visual coding has been massively opened up when graphics APIs began to introduce programmable fragment rendering, perhaps best known to most people as "pixel shaders"; this allowed programmers to run entire programmable functions on each pixel of a triangle, and none was more adamant to do that than a fellow named Iñigo Quilez (IQ), an understated genius who early on recognized the opportunity in covering the entire screen with a single polygon, and just doing the heavy lifting of creating geometry in the shader itself. His vision eventually spiraled into not only the modern 4k scene, but also the website ShaderToy, which almost every graphics programmer uses to test prototypes or just play around with algorithms. IQ, an old friend of mine since the mid-00s, eventually moved to the US, worked at Pixar and Oculus, and became something of a world-revered guru of computer graphics, but that (and life) has unfortunately caused him to shift away from the scene.
His vision of single-shader-single-quad-single-pass shader coding, in the meantime, created a very spectacular kind of live coding competition in the scene where two coders get only 25 minutes and the attention of an entire party hall, and they have to improvise their way out of the duel - this has been wildly successful at parties for the sheer showmanship and spectacle akin to rap battles, and none emerged from this little sport more remarkably than Flopine, a bubbly French girl who routinely shuffled up on stage wearing round spectacles and cat ears (actually they might be pony ears on second thought), and mopped the floor up with the competition. Her and a handful of other live-coders regularly stream on Twitch as practice, and have honed their live-coding craft for a few years at this point, garnering a considerable following.
youtube
Just a sample of insanity these people can do.
My contribution to this little sub-scene was coming up with a fancy name for it ("Shader Showdown"), as well as providing a little tool I called Bonzomatic (named after Bonzaj / Plastic, a mutual friend of IQ and myself, and the first person to create a live coding environment for demoparties) that I still maintain, but even though I feel a degree of involvement through the architectural side, I myself haven't been interested in participating: I know I can do okay under time pressure, but I don't really enjoy it, and while there's a certain overlap in what they do and what I do, I was always more interested in things like visual detail and representative geometry aided by editing and direction rather than looping abstract, fractal-like things. It just wasn't my thing.
Mistakes were made
But if I'm not attracted to this type of competition, how did I end up in the crossfire anyway? What I can't say is that it wasn't, to a considerable degree, my fault: as Revision 2020 was entirely online, most of the scene took it to themselves to sit in the demoscene Discord to get an experience closest to on-site socializing, given the somber circumstances of physical distancing. This also allowed a number of people who hasn't been around for a while to pop in to chat - like IQ, who, given his past, was mostly interested in the showdowns (during which Flopine crushed the competition) and the 4k compo.
As I haven't seen him around for a while, and as my mind is always looking for an angle, I somehow put two and two together, and asked him if he would consider taking part in a showdown at some point; he replied that he was up for it - this was around Saturday 10PM. I quickly pinged the rest of the showdown participants and organizers, as I spotted that Bullet was doing a DJ set the next day (which would've been in a relatively convenient timezone for IQ in California as well), and assumed that he didn't really have visuals for it - as there was already a "coding jam" over Ronny's set the day before, I figured there's a chance for squeezing an "extra round" of coding. Flopine was, of course, beyond excited by just the prospect of going against IQ, and by midnight we essentially got everything planned out (Bullet's consent notwithstanding, as he was completely out of the loop on this), and I was excited to watch...
...that is, until Havoc, the head honcho for the showdowns, off-handedly asked me about an at that point entirely hypothetical scenario: what would happen if IQ would, for some reason, challenge me instead of Flopine? Now, as said, I wasn't really into this, but being one to not let a good plan go to waste (especially if it was mine), I told Havoc I'd take one for the team and do it, although it probably wouldn't be very fun to watch. I then proceeded to quickly brief IQ in private and run him through the technicalities of the setup, the tool, the traditions and so on, and all is swell...
...that is, until IQ (this is at around 2AM) offhandedly mentions that "Havoc suggested we do a three-way with me, Flopine... and you." I quickly try to backpedal, but IQ seems to be into the idea, and worst of all, I've already essentially agreed to it, and to me, the only thing worse than being whipped in front of a few thousand people would be going back on your word. The only way out was through.
Weeks of coding can spare you hours of thinking
So now that I've got myself into this jar of pickles, I needed some ideas, and quick. (I didn't sleep much that night.) First off, I didn't want to do anything obviously 3D - both IQ and Flopine are masters of this, and I find it exhausting and frustrating, and it would've failed on every level possible. Fractals I'm awful at and while they do provide a decent amount of visual detail, they need a lot of practice and routine to get right. I also didn't want something very basic 2D, like a byte-beat, because those have a very limited degree of variation available, and the end result always looks a bit crude.
Luckily a few months ago an article I saw do rounds was a write-up by Sasha Martinsen on how to do "FUI"-s, or Fictional User Interfaces; overly complicated and abstract user interfaces that are prominent in sci-fi, with Gmunk being the Michael Jordan of the genre.
Tumblr media
Image courtesy of Sasha Martinsen.
Sasha's idea is simple: make a few basic decent looking elements, and then just pile them on top of each other until it looks nice, maybe choose some careful colors, move them around a bit, place them around tastefully in 3D, et voilà, you're hacking the Gibson. It's something I attempted before, if somewhat unsuccessfully, in "Reboot", but I came back to it a few more times in my little private motion graphics experiments with much better results, and my prediction was that it would be doable in the given timeframe - or at least I hoped that my hazy 3AM brain was on the right track.
A bit of math
How to make this whole thing work? First, let's think about our rendering: We have a single rectangle and a single-pass shader that runs on it: this means no meshes, no geometry, no custom textures, no postprocessing, no particle systems and no fonts, which isn't a good place to start from. However, looking at some of Sasha's 3D GIFs, some of them look like they're variations of the same render put on planes one after the other - and as long as we can do one, we can do multiple of that.
Tumblr media
Rough sketch of what we want to do; the planes would obviously be infinite in size but this representation is good enough for now.
Can we render multiple planes via a single shader? Sure, but we want them to look nice, and that requires a bit of thinking: The most common technique to render a "2D" shader and get a "3D" look is raymarching, specifically with signed distance fields - starting on a ray, and continually testing distances until a hit is found. This is a good method for "solid-ish" looking objects and scenes, but the idea for us is to have many infinite planes that also have some sort of alpha channel, so we'd have a big problem with 1) inaccuracy, as we'd never find a hit, just something "reasonably close", and even that would take us a few dozen steps, which is costly even for a single plane and 2) the handling of an alpha map can be really annoying, since we'd only find out our alpha value after our initial march, after which if our alpha is transparent we'd need to march again.
But wait - it's just infinite planes and a ray, right? So why don't we just assume that our ray is always hitting the plane (which it is, since we're looking at it), and just calculate an intersection the analytical way?
Note: I would normally refer to this method as "raytracing", but after some consultation with people smarter than I am, we concluded that the terms are used somewhat ambiguously, so let's just stick to "analytical ray solving" or something equally pedantic.
We know the mathematical equation for a ray is position = origin + direction * t (where t is a scalar that represents the distance/progress from the ray origin), and we know that the formula for a plane is A * x + B * y + C * z + D = 0, where (A, B, C) is the normal vector of the plane, and D is the distance from the origin. First, since the intersection will be the point in space that satisfies both equations, we substitute the ray (the above o + d * t for each axis) into the plane:
A * (ox + dx * t) + B * (oy + dy * t) + C * (oz + dz * t) + D = 0
To find out where this point is in space, we need to solve this for t, but it's currently mighty complicated. Luckily, since we assume that our planes are parallel to the X-Y plane, we know our (A, B, C) normal is (0, 0, 1), so we can simplify it down to:
oz + dz * t + D = 0
Which we can easily solve to t:
t = (D - oz) / dz
That's right: analytically finding a ray hit of a plane is literally a single subtraction and a division! Our frame rate (on this part) should be safe, and we're always guaranteed a hit as long as we're not looking completely perpendicular to the planes; we should have everything to start setting up our code.
Full disclosure: Given my (and in a way IQ's) lack of "live coding" experience, we agreed that there would be no voting for the round, and it'd be for glory only, but also that I'd be allowed to use a small cheat sheet of math like the equations for 2D rotation or e.g. the above final equation since I don't do this often enough to remember these things by heart, and I only had a few hours notice before the whole thing.
Setting up the rendering
Time to start coding then. First, let's calculate our texture coordinates in the 0..1 domain using the screen coordinates and the known backbuffer resolution (which is provided to us in Bonzomatic):
vec2 uv = vec2(gl_FragCoord.x / v2Resolution.x, gl_FragCoord.y / v2Resolution.y);
Then, let's create a ray from that:
vec3 rayDir = vec3( uv * 2 - 1, -1.0 ); rayDir.x *= v2Resolution.x / v2Resolution.y; // adjust for aspect ratio vec3 rayOrigin = vec3( 0, 0, 0 );
This creates a 3D vector for our direction that is -1,-1,-1 in the top left corner and 1,1,-1 in the bottom right (i.e. we're looking so that Z is decreasing into the screen), then we adjust the X coordinate since our screen isn't square, but our coordinates currently are - no need to even bother with normalizing, it'll be fine. Our origin is currently just sitting in the center.
Then, let's define (loosely) our plane, which is parallel to the XY plane:
float planeDist = 1.0f; // distance between each plane float planeZ = -5.0f; // Z position of the first plane
And solve our equation to t, as math'd out above:
float t = (planeZ - rayOrigin.z) / rayDir.z;
Then, calculate WHERE the hit is by taking that t by inserting it back to the original ray equation using our current direction and origin:
vec3 hitPos = rayOrigin + t * rayDir;
And now we have our intersection; since we already know the Z value, we can texture our plane by using the X and Y components to get a color value:
vec4 color = fui( hitPos.xy ); // XY plane our_color = color;
Of course we're gonna need the actual FUI function, which will be our procedural animated FUI texture, but let's just put something dummy there now, like a simple circle:
vec4 fui ( vec2 uv ) { return length(uv - 0.5) < 0.5 ? vec4(1) : vec(0); }
And here we go:
Tumblr media
Very good, we have a single circle and if we animate the camera we can indeed tell that it is on a plane.
So first, let's tile it by using a modulo function; the modulo (or modulus) function simply wraps a number around another number (kinda like the remainder after a division, but for floating point numbers) and thus becomes extremely useful for tiling or repeating things:
Tumblr media
We'll be using the modulo function rather extensively in this little exercise, so strap in. (Illustration via the Desmos calculator.)
vec4 layer = fui( mod( hitPos.xy, 1.0 ) );
This will wrap the texture coordinates of -inf..inf between 0..1:
Tumblr media
We also need multiple planes, but how do we combine them? We could just blend them additively, but with the amount of content we have, we'd just burn them in to white and it'd look like a mess (and not the good kind of mess). We could instead just use normal "crossfade" / "lerp" blending based on the alpha value; the only trick here is to make sure we're rendering them from back to front since the front renders will blend over the back renders:
int steps = 10; float planeDist = 1.0f; for (int i=steps; i>=0; i--) { float planeZ = -1.0f * i * planeDist; float t = (planeZ - rayOrigin.z) / rayDir.z; if (t > 0.0f) // check if "t" is in front of us { vec3 hitPos = rayOrigin + t * rayDir; vec4 layer = fui( hitPos.xy, 2.0 ); // blend layers based on alpha output colour = mix( colour, layer, layer.a ); } }
And here we go:
Tumblr media
We decreased the circles a bit in size to see the effect more.
Not bad! First thing we can do is just fade off the back layers, as if they were in a fog:
layer *= (steps - i) / float(steps);
Tumblr media
We have a problem though: we should probably increase the sci-fi effect by moving the camera continually forward, but if we do, we're gonna run into a problem: Currently, since our planeZ is fixed to the 0.0 origin, they won't move with the camera. We could just add our camera Z to them, but then they would be fixed with the camera and wouldn't appear moving. What we instead want is to just render them AS IF they would be the closest 10 planes in front of the camera; the way we could do that is that if e.g. our planes' distance from each other is 5, then round the camera Z down to the nearest multiple of 5 (e.g. if the Z is at 13, we round down to 10), and start drawing from there; rounding up would be more accurate, but rounding down is easier, since we can just subtract the division remainder from Z like so:
float planeZ = (rayOrigin.z - mod(rayOrigin.z, planeDist)) - i * planeDist;
Tumblr media
And now we have movement! Our basic rendering path is done.
Our little fictional UI
So now that we have the basic pipeline in place, let's see which elements can we adapt from Sasha's design pieces.
The first one I decided to go with wasn't strictly speaking in the set, but it was something that I saw used as design elements over the last two decades, and that's a thick hatch pattern element; I think it's often used because it has a nice industrial feel with it. Doing it in 2D is easy: We just add X and Y together, which will result in a diagonal gradient, and then we just turn that into an alternating pattern using, again, the modulo. All we need to do is limit it between two strips, and we have a perfectly functional "Police Line Do Not Cross" simulation.
return mod( uv.x + uv.y, 1 ) < 0.5 ? vec4(1) : vec4(0);
Tumblr media
So let's stop here for a few moments; this isn't bad, but we're gonna need a few things. First, the repetition doesn't give us the nice symmetric look that Sasha recommends us to do, and secondly, we want them to look alive, to animate a bit.
Solving symmetry can be done just by modifying our repetition code a bit: instead of a straight up modulo with 1.0 that gives us a 0..1 range, let's use 2.0 to get a 0..2 range, then subtract 1.0 to get a -1..1 range, and then take the absolute value.
Tumblr media
vec4 layer = fui( abs( mod( hitPos.xy, 2.0 ) - 1 ) );
This will give us a triangle-wave-like function, that goes from 0 to 1, then back to 0, then back to 1; in terms of texture coordinates, it will go back and forth between mirroring the texture in both directions, which, let's face it, looks Totally Sweet.
Tumblr media
For animation, first I needed some sort of random value, but one that stayed deterministic based on a seed - in other words, I needed a function that took in a value, and returned a mangled version of it, but in a way that if I sent that value in twice, it would return the same mangled value twice. The most common way of doing it is taking the incoming "seed" value, and then driving it into some sort of function with a very large value that causes the function to alias, and then just returning the fraction portion of the number:
float rand(float x) { return fract(sin(x) * 430147.8193); }
Does it make any sense? No. Is it secure? No. Will it serve our purpose perfectly? Oh yes.
So how do we animate our layers? The obvious choice is animating both the hatch "gradient" value to make it crawl, and the start and end of our hatch pattern which causes the hatched strip to move up and down: simply take a random - seeded by our time value - of somewhere sensible (like between 0.2 and 0.8 so that it doesn't touch the edges) and add another random to it, seasoned to taste - we can even take a binary random to pick between horizontal and vertical strips:
Tumblr media
The problems here are, of course, that currently they're moving 1) way too fast and 2) in unison. The fast motion obviously happens because the time value changes every frame, so it seeds our random differently every frame - this is easy to solve by just rounding our time value down to the nearest integer: this will result in some lovely jittery "digital" motion. The unison is also easy to solve: simply take the number of the layer, and add it to our time, thus shifting the time value for each layer; I also chose to multiply the layer ID with a random-ish number so that the layers actually animate independently, and the stutter doesn't happen in unison either:
vec4 fui( vec2 uv, float t ) { t = int(t); float start = rand(t) * 0.8 + 0.1; float end = start + 0.1; [...] } vec4 layer = fui( abs(mod(hitPos.xy, 2.0)-1), fGlobalTime + i * 4.7 );
Tumblr media
Lovely!
Note: In hindsight using the Z coordinate of the plane would've given a more consistent result, but the way it animates, it doesn't really matter.
So let's think of more elements: the best looking one that seems to get the best mileage out in Sasha's blog is what I can best describe as the "slant" or "hockey stick" - a simple line, with a 45-degree turn in it. What I love about it is that the symmetry allows it to create little tunnels, gates, corridors, which will work great for our motion.
Creating it is easy: We just take a thin horizontal rectangle, and attach another rectangle to the end, but shift the coordinate of the second rectangle vertically, so that it gives us the 45-degree angle:
float p1 = 0.2; float p2 = 0.5; float p3 = 0.7; float y = 0.5; float thicc = 0.0025; if (p1 < uv.x && uv.x < p2 && y - thicc < uv.y && uv.y < y + thicc ) { return vec4(1); } if (p2 < uv.x && uv.x < p3 && y - thicc < uv.y - (uv.x - p2) && uv.y - (uv.x - p2) < y + thicc ) { return vec4(1); }
Tumblr media
Note: In the final code, I had a rect() call which I originally intended to use as baking glow around my rectangle using a little routine I prototyped out earlier that morning, but I was ultimately too stressed to properly pull that off. Also, it's amazing how juvenile your variable names turn when people are watching.
Looks nice, but since this is such a thin sparse element, let's just... add more of it!
Tumblr media
So what more can we add? Well, no sci-fi FUI is complete without random text and numbers, but we don't really have a font at hand. Or do we? For years, Bonzomatic has been "shipping" with this really gross checkerboard texture ostensibly for UV map testing:
Tumblr media
What if we just desaturate and invert it?
Tumblr media
We can then "slice" it up and render little sprites all over our texture: we already know how to draw a rectangle, so all we need is just 1) calculate which sprite we want to show 2) calculate the texture coordinate WITHIN that sprite and 3) sample the texture:
float sx = 0.3; float sy = 0.3; float size = 0.1; if (sx < uv.x && uv.x < sx + size && sy < uv.y &&uv.y < sy + size) { float spx = 2.0 / 8.0; // we have 8 tiles in the texture float spy = 3.0 / 8.0; vec2 spriteUV = (uv - vec2(sx,sy)) / size; vec4 sam = texture( texChecker, vec2(spx,spy) + spriteUV / 8.0 ); return dot( sam.rgb, vec3(0.33) ); }
Note: In the final code, I was only using the red component instead of desaturation because I forgot the texture doesn't always have red content - I stared at it for waaaay too long during the round trying to figure out why some sprites weren't working.
Tumblr media
And again, let's just have more of it:
Tumblr media
Getting there!
At this point the last thing I added was just circles and dots, because I was running out of ideas; but I also felt my visual content amount was getting to where I wanted them to be; it was also time to make it look a bit prettier.
Tumblr media
Post-production / compositing
So we have our layers, they move, they might even have colors, but I'm still not happy with the visual result, since they are too single-colored, there's not enough tone in the picture.
The first thing I try nowadays when I'm on a black background is to just add either a single color, or a gradient:
vec4 colour = renderPlanes(uv); vec4 gradient = mix( vec4(0,0,0.2,1), vec4(0,0,0,1), uv.y); vec4 finalRender = mix( gradient, vec4(colour.xyz,1), colour.a);
Tumblr media
This added a good chunk of depth considerably to the image, but I was still not happy with the too much separation between colors.
A very common method used in compositing in digital graphics is to just add bloom / glow; when used right, this helps us add us more luminance content to areas that would otherwise be solid color, and it helps the colors to blend a bit by providing some middle ground; unfortunately if we only have a single pass, the only way to get blur (and by extension, bloom) is repeatedly rendering the picture, and that'd tank our frame rate quickly.
Instead, I went back to one of the classics: the Variform "pixelize" overlay:
Tumblr media
This is almost the same as a bloom effect, except instead of blurring the image, all you do is turn it into a lower resolution nearest point sampled version of itself, and blend that over the original image - since this doesn't need more than one sample per pixel (as we can reproduce pixelation by just messing with the texture coordinates), we can get away by rendering the scene only twice:
vec4 colour = renderPlanes(uv); colour += renderPlanes(uv - mod( uv, 0.1 ) ) * 0.4;
Tumblr media
Much better tonal content!
So what else can we do? Well, most of the colors I chose are in the blue/orange/red range, and we don't get a lot of the green content; one of the things that I learned that it can look quite pretty if one takes a two-tone picture, and uses color-grading to push the midrange of a third tone - that way, the dominant colors will stay in the highlights, and the third tone will cover the mid-tones. (Naturally you have to be careful with this.)
"Boosting" a color in the mids is easy: lucky for us, if we consider the 0..1 range, exponential functions suit our purpose perfectly, because they start at 0, end at 1, but we can change how they get here:
Tumblr media
So let's just push the green channel a tiny bit:
finalRender.g = pow(finalRender.g, 0.7);
Tumblr media
Now all we need is to roll our camera for maximum cyberspace effect and we're done!
Tumblr media
Best laid plans of OBS
As you can see from the code I posted the above, I wrote the final shader in GLSL; those who know me know that I'm a lot more comfortable with DirectX / HLSL, and may wonder why I switched, but of course there's another story here:
Given the remote nature of the event, all of the shader coding competition was performed online as well: since transmitting video from the coder's computer to a mixer, and then to another mixer, and then to a streaming provider, and then to the end user would've probably turned the image to mush, Alkama and Nusan came up with the idea of skipping a step and rigging up a version of Bonzo that ran on the coder's computer, but instead of streaming video, it sent the shader down to another instance of Bonzo, running on Diffty's computer, who then captured that instance and streamed it to the main Revision streaming hub. This, of course, meant that in a three-way, Diffty had to run three separate instances of Bonzo - but it worked fine with GLSL earlier, so why worry?
What we didn't necessarily realize at the time, is that the DirectX 11 shader compiler takes no hostages, and as soon as the shader reached un-unrollable level of complexity, it thoroughly locked down Diffty's machine, to the point that even the video of the DJ set he was playing started to drop out. I, on the other hand, didn't notice any of this, since my single local instance was doing fine, so I spent the first 15 minutes casually nuking Diffty's PC to shreds remotely, until I noticed Diffty and Havoc pleading on Discord to switch to GLSL because I'm setting things on fire unknowingly.
Tumblr media
This is fine.
I was reluctant to do so, simply because of the muscle memory, but I was also aware that I should keep the show going if I can because if I bow out without a result, that would be a colossal embarrassment to everyone involved, and I only can take one of those once every week, and I was already above my quota - so, I quickly closed the DX11 version of Bonzo, loaded the shader up in a text editor, replaced "floatX" with "vecX" (fun drinking game: take a shot every time I messed it up during the live event), commented the whole thing out, loaded it into a GLSL bonzo, and quickly fixed all the other syntax differences (of which there were luckily not many, stuff like "mix" instead of "lerp", constructors, etc.), and within a few minutes I was back up and running.
This, weirdly, helped my morale a bit, because it was the kind of clutch move that for some reason appealed to me, and made me quite happy - although at that point I locked in so bad that not only did I pay absolutely not attention to the stream to see what the other two are doing, but that the drinks and snacks I prepared for the hour of battling went completely untouched.
In the end, when the hour clocked off, the shader itself turned out more or less how I wanted it, it worked really well with Bullet's techno-/psy-/hardtrance mix (not necessarily my jam, as everyone knows I'm more a broken beat guy, but pounding monotony can go well with coding focus), and I came away satisfied, although the perhaps saddest point of the adventure was yet to come: the lack of cathartic real-life ending that was taken from us due to the physical distance, when after all the excitement, all the cheers and hugs were merely lines of text on a screen - but you gotta deal with what you gotta deal with.
Tumblr media
A small sampling of the Twitch reaction.
Conclusion
In the end, what was my takeaway from the experience?
First off, scoping is everything: Always aim to get an idea where you can maximize the outcome of the time invested with the highest amount of confidence of pulling it off. In this case, even though I was on short notice and in an environment I was unfamiliar with, I relied on something I knew, something I've done before, but no one else really has.
Secondly, broaden your influence: You never know when you can take something that seems initially unrelated, and bend it into something that you're doing with good results.
Thirdly, and perhaps most importantly, step out of your comfort zone every so often; you'll never know what you'll find.
(And don't agree to everything willy-nilly, you absolute moron.)
10 notes · View notes