Build Your Own Audio Visualization In a Shader
Getting into shaders and writing GLSL might sound intimidating, but don't be scared! Char Stiles will teach us to code our own visualizations in the browser.
Links & Resources
Click to expand the full transcript
Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.
JASON: Hello, everyone, and welcome to another episode of "Learn with Jason." Today on the show, we have Char Stiles. Char, how are you doing?
CHAR: I'm good, Jason, how are you?
JASON: I'm doing great. I'm super excited to have you here. This is a really fun topic for me. We've been doing a bit of a creative coding week on "Learn with Jason." So, we had Dan Gorlick on Tuesday, did generative music with title cycles, and, you know, that was super fun. And I know that you and Dan have worked together before, right?
CHAR: Yes, we collaborate quite often.
JASON: Yes, so, before we dig into that part of it, I would love for folks who aren't familiar with your work, would you like to give us a bit of a background on yourself?
CHAR: Yeah. I am a live visual live programmer. So, as a performance, I'll write Shader code on stage, where everyone can see my code, and show visuals on side, usually algorithmic music, but it can work with any music or audience that will appreciate looking at code as they see the 1:1 correspondence of the code, that makes the visuals. Yeah, it's something I love doing, and I love sharing it, and love teaching it. So I'm excited to be going through it together. I always end up learning something, too. So learning together.
JASON: I'm so excited, because I've talked about this a lot, but I just love this idea of code being a creative thing. I think that a lot of times we look at programming as being a practical or pragmatic thing where it has to serve a purpose, it's for business, it's for accomplishing goals. And what I think is really interesting about what you're doing with this kind of performance, is you're taking something that somebody might look at and say, well, there's no way that can be art. It's just code. And you're clearly making art with it.
CHAR: Yes, yes.
JASON: I think that's so fun. So, how did you get into -- actually, let me ask it a different way. At what point did the light bulb go on, where you realized that code could be an artistic medium?
CHAR: Oh, my gosh. Oh, lord. So, I was very lucky to have met my mentor early on in my, I guess, coding career. So, I learned how to code when I was like 17. And it was from Golan. So, it was really like the first time that I kind of ever was getting into coding was through creative coding. Gosh, I wonder when did I realize people thought code wasn't creative, maybe that's the other question. The first piece of code I ever wrote was in Processing, a Java library for creating graphics. Like you tell the turtle where to go, put the pencil down, pick the pencil up, and creates a plotter-type drawing.
JASON: Nice, nice. This is a whole world that is so new to me. Like, I haven't really explored any of the generative art and stuff. It's more of like I've seen other people do it, and I always thought, well, that's too hard. I don't think I can do that. Because it's not -- today, we're going to be using -- you called it GLSL, is that right?
CHAR: Exactly, yes.
JASON: So, when I look at that, to me as somebody who comes in from the front-end world, it's very intimidating. Looking at things like vex, looking at full class definitions and stuff. And the things that I've seen before in other times of code, but it's very kind of -- just feels all brand-new. Oh, my goodness, I don't know enough to be able to do this well.
CHAR: Yeah, it's a whole world. It really is. So many types of creative coding. From, you know, there's like -- you can do creative coding in Python, in C, you could probably also do it in Assembly, I'm sure people have done that.
JASON: Yeah, I mean, and it's wild how broad that world is, right, you can do so many cool things. I think when Dan was on he was talking about somebody who Dove into the byte code of an old NES and reconfigured that byte code to make art. I didn't even think that was a possibility. I've seen people play with Game Boys, incredible things people are doing with Gameboys. So many amazing opportunities to do something that is for the love of creation, not necessarily for collecting a paycheck or accomplishing a practical goal. It's to see something exist that didn't exist before. So, you're doing that in the form of visualizations. And what -- I guess you said there are a lot of ways to do this. So, what is your preferred medium for doing this?
CHAR: Well, it's what I'm teaching you today. It's writing Shaders. Shaders, if people don't know, it is code that is run on the GPU. And it's run one time per pixel. So, we're going to write a function today that is being executed -- I'm going to say this again and again, one time per pixel, and the input is the pixel's position, and the output is a color. So, your input is like a graph, like a 2D coordinate. Your job is to transform that 2D coordinate into a color. That's it. It's such a nice little sand box, where you can, you know, you start to imagine all of the different things you could do with this very, very, very limited input. People have done amazing things. We won't get to it today, but you could, using linear algebra, create a tiny renderer that looks into a 3D scene. You could make a video game, only input is the pixel's position, but then you can also get your mouse position and stuff like that. And, you know, you can pass in something called a uniform, which is a shared buffer between the GPU and the CPU, but it's still limited, the base is an X/Y position, and an output is a red channel, green channel, and a blue channel.
JASON: Yeah, yeah. And it's amazing, because all the stuff is both simple and hard all at the same time, right, it's like a piano. A piano is incredibly simple, because it's just buttons you push. There's nothing particularly complex about how to use a piano, but to make really beautiful things, there's a lot of skill. There can be a lifetime of effort behind using it really well and doing something truly unique. And this feels the same way. All we're doing is changing the color of a pixel. Okay, I get that fundamentally, but the things you see people do with it, oh, my goodness, they are incredible. So, yeah, I think it's really, really interesting. David is saying pianos are complex. The insides of pianos are very complex. But for me, oh, buttons! (Laughing). But, yeah, so, I am kind of interested in, as you start looking at this, you're talking about we're changing one pixel at a time. And in order to do that, we start getting into what I consider algorithmic, we're looking at fairly heavy computer science-y things. Okay, let's take this, then I'm going to do something interesting with it, and we're going to try to make this look like it's pulsing or whatever. And trying to make, you know, a circle get bigger or smaller when you're doing it one pixel at a time, suddenly the math gets interesting.
CHAR: Yes, there's so much math, and I love it. Like a lot of times with programming, you don't necessarily get to do math in the way that you do math when you're coding Shaders. Because of this very limited -- it's almost like it's so close to the hardware, it's so at a lower level, you have to start speaking their language, which is --
JASON: Yeah, and I think the thing that's interesting about math, I feel like when I was a kid, I was really resistant to math. People would show me math, and I would be like this is not interesting. Like, it's not useful to do theoretical math longhand on a piece of paper.
CHAR: The way that people teach math is like -- it could be so much more fun.
JASON: That's where a light bulb went off for me, when I started looking at this creative coding, where I realized if you start bringing in some of the more advanced geometry, trigonometry, suddenly you're doing really interesting stuff here.
JASON: Beautiful patterns, really reactive things, really gorgeous outcomes. It's the same math, just in a much more interesting application.
CHAR: An example, last night I was reading a book that's like -- it's like math for game engines. And it was going over what dot product was. And it was like this is how you compute dot product, this is what it looks like in code, and after it was done with dot product. I was like, wait, but it didn't explain that you can use dot product to create a very kind of simple way to shade 3D objects. I'm like, you could have included one line, like dot product is a way you can get the percentage of array from -- well, the difference of a raised direction from the light's direction, then you can color an object based on that, using just the dot product. I feel had I not known that before, I would have forgotten everything about dot product. But this application of dot product, I think, really made it stick to my brain. But I didn't learn it from the book, I learned it from Shader coding, doing this kind of stuff.
JASON: That's such a good point, because by making a topic interesting, in whatever way you find things to be interesting, is so important for retaining that information. And I have some anecdotal evidence for this, because I didn't like math in high school. But there's one thing that I remember vividly, which is parabollic math. And the reason that I remember that is that to learn parabolic math, I did an extra credit projects, where I went out and bought an old TV satellite dish, covered it in aluminum foil, took out the transeiver, used it to cook marshmallows. It was this goofy project, but now I understand why parabolas work the way they do, why satellites are made the way they are. All these things about the math that were practical. And it made it interesting, and it made it fun, and I got to make S'mores for my whole class by taking a satellite dish outside. This is the only piece of math that I could do now. None of the rest of it stuck.
CHAR: Amazing. Oh, my gosh. I need to see this. I want to eat it. I want to eat this math marshmallow.
JASON: Yeah, and I think, you know, this is the same thing. When I started learning physics for -- like right now you can see a couple people have dropped the boops, and thank you, chat, for participating. I see a hype train is going. Thank you for the subs, thank you all so much for the sub, I really appreciate it.
CHAR: You made this boops, you made these?
JASON: Yeah, this is physics. I'm using a library called Matter JS, but it helped me understand things like drag, and things like gravity, and how something could bounce, you know, how to simulate friction. And that was really interesting versus the more theoretical parts of physics and the math that goes into it.
JASON: I don't know, calculating gravity. Yeah, 9.8 meters per second per second. I memorized that. Doesn't mean anything. But when you go outside and do the test, like the Myth Busters I think did one, where they shot a bullet and dropped something at the same time. And the bullet and the coin hit the ground at the exact same moment, because gravity still works, even if you're moving sideways. What? Now I get the math.
CHAR: Yeah, yeah. Oh, it's so cool. There's such a world that opens when you realize that math isn't just practice problems.
JASON: Yes. Oh, my goodness, yeah. So many amazing things. And, like, math is shorthand to describe the world around us. And I think we've abstracted it so far away that it feels like a list of chores and not a description of the world around us.
CHAR: Exactly. So true.
JASON: Yeah, geez. I miss MythBusters, too. Were you a fan?
CHAR: I was. MythBusters has a great example of how Shaders work, where they loaded -- they showed how a CPU works. They had a paintball gun, like a one at a time paint a picture by shooting the paint, made a smiley face. Then they were like this is how GPU works, then they had an array of like, you know, I'd say hundreds of paintball guns in one go, shot it all at once, poof, Mona Lisa. I ruined the suspense if anyone is going to look at it, but I use that when I teach my course, I use that GIF to illustrate. This is how you make an image with a CPU, you have to do it one time sequentially, as opposed to the GPU, where it happens all at once per frame.
JASON: Oh, interesting. This feels like something that's relatively new, GPUs. And the ability to do this heavy graphics.
CHAR: To program your GPU, yeah, to program your GPU the first time you were able to do that was with Render Man, which Pixar came out. Before that, GPUs were not programmable. Pixar came out that allowed you to write a pixel shader in 2006. You know, little less than 20 years old.
JASON: Uh-huh. So cool. So, so, so cool.
JASON: All right. So, I have one more question for you, which is you have, you mentioned, taken to doing this on stage. Like you do it as performance. How did you get into that? Where does one even discover this world of coding as performance?
CHAR: Yes. So, I just found it online, because those were my interests of making visuals, visualizations, art, I was a painter for a bit. And then I also was really interested in computer science. This was just kind of -- it made sense. Also, I was terrified of people looking at my screen. Like I would freeze up, and I wouldn't be able to move if I felt the presence of someone behind me. A, I have to get over this fear. And, B, this is right in my alley of creating art. So, I just was like founded online by Googling, I was in Pittsburgh at the time. I just started to decide to call myself a live coder. And kind of the community was like, what, because it's such a niche thing. Everyone kind of knows each other. So they kind of welcomed me in. And now I'm a part of -- now I'm in Brooklyn, a part of Live Code NYC Collective here. That's how I got into it, through the community kind of welcoming me with my weird niche interest of both computation and art.
JASON: I find that so freaking cool that you started, because the thing that you started terrified you.
CHAR: Oh, my gosh, yeah. That's a theme in my life.
JASON: I love that as a, you know, I know this doesn't work for everybody, but it's something that I've kind of adopted, as well. Which is when I'm uncomfortable, I have to move toward it. Okay, clearly, there's something that I need to do that's going to help me grow here. And I love, I love, seeing people just kind of say, all right, this makes me super nervous, here we go, let's do it.
CHAR: Let's do more of it, yeah.
JASON: And I love it, too, because it's so interesting to, you know, this is a whole world that I didn't realize existed. I didn't realize it could exist.
CHAR: Well, welcome.
JASON: And I'm so excited, because I come from a world of live performance. I was a musician, I toured for a long time.
JASON: There was a few years when I played, like, 200 shows a year around the western U.S. And the whole time, I loved being up on stage, and I was learning how to build stuff, because our band didn't make any money, because we were terrible.
CHAR: That's not going to change with live coding.
JASON: But, you know, I designed our merch, I built our website, and I managed our Myspace page, did all these customizations. And I was learning all this stuff and had no idea there was a way to merge those interests. Identify always talked about it as being a musician taught me everything about being a coder except live performance. That's the only skill that didn't transfer over. Now you're showing me actually all of it can transfer over. I can use every bit of my former career as a struggling musician and actually bring that into this new skill set. And I think that is amazing.
CHAR: Okay, we're putting you on the next bill. You're going to perform.
JASON: It's so cool. I hope this is inspiring for the chat, too. I have spent a lot of time talking to people in this chat and across Twitter, a surprising number of developers started out as musicians. I know David is in the chat today. He's a musician, right? I used to be in a band. A lot of people I work with play instruments, used to be in bands. Want to be in bands. It's such an interesting origin story that so many people have musical backgrounds and end up in coding. But almost none of us are still making music. We're all like, well, I'm hoping some day I get enough free time to have a musical hobby again. This feels a little more approachable than me practicing guitar or piano. I don't have a ton of time for that, but I practice code all day. I can step into algorithmic music. Anyways, this is very exciting. Yeah, also a former musician. Wing Matt is saying the only person at your company who plays guitar. Start like I did and learn Wonder Wall.
CHAR: Oh, nice. For me, I started getting into playing guitar through live coding. I went the other way through coding. And I learned -- my Wonder Wall was video Games.
JASON: Nice, nice, nice. This is so cool. I love it. So, how old somebody find a show near them? Is there a community where I can say, hey, I'm in Portland, Oregon, I want to go find a live coding performance, or a group of live coders near me.
CHAR: So, there's toplap.org. I believe there's the different nodes of live code -- wait, is it broken? No, they changed the website, oh, my gosh. Yeah, so, Toplap, I bet it's in Toplap Wiki. Somewhere has the different nodes of where people live. I bet if you look up Algo Rave and your city, there's a small chance there will be a community already. And if not, you're always welcome to start one. I was one of the founders for the Pittsburgh group that we made. I know there's one in New York. Dan talked about the adjacent kind of live code group in San Francisco. There's also a bunch in Europe and all that. The scene over here in America is different than the scene in Europe. It's much more academic, which is fun.
JASON: Interesting, okay.
CHAR: There's also a New Mexico scene and a style of New Mexico live coding that I've heard -- if anyone is from New Mexico and is like that's not how we do it, I'm sorry, this is a rumor. You have to start from a blank page and it's only text-based code. Fun fact about New Mexico.
JASON: Nice, nice. I love that. That's super fun.
JASON: All right. So, what do you think, should we start actually looking at this? I am dying to see how this all works. So, let's switch over and get into paired programming view here. And while we're doing that, I'm going to do a quick shout-out to our live captioning today. We've got Ashly with us from White Coat Captioning taking all this stuff down. You can see those captions on the home page of the site. And while you're checking that out, make sure you take a look at our sponsors, because that is who makes this live captioning possible. Netlify, Fauna, Auth0, making the show accessible and keep the other lights on, as well. We are talking to Char today, so if you want to get more information, give her a follow on the old Twitter. And this is that Mona Lisa video. Make sure this goes into the show notes. I probably won't watch this, just in case there's a take down. And then I found Toplap, so here's the communities and the nodes. So, that being said, I'm ready. I want to try to dive in, and get my first Shader up and running. How would one start?
CHAR: So, there's many ways to start. You can have -- you can actually just go to a website, like my website that I use -- the thing with live coding, the joke is everyone kind of eventually makes their own tool. So, this is the tool that I made. This is Shader.Place. So, you can either go there, which -- or you can also use a native app, like Code Life, which is what I use a lot of the times. And you can also use a different online app that's like The Force. So many different ways.
JASON: Sorry, is it an app?
CHAR: It's with a "k." Like a Kardashian.
JASON: Like a Kardashian!
CHAR: The Kardashian of applications. Yeah, yeah, I use that for my performances. They are great people, too.
CHAR: So, that's for if you want to do native. But online, I either use my own Shader.Place, yes, right there. Or you can use -- I'm going to also pitch this at the end, you can use the internal editor or The Book of Shaders. There's so many ways to get into it. I wish I had a more succinct answer lined up, but there's many, many, many tools.
JASON: And the nice thing about it is it shows there's not -- there are no rules here.
CHAR: Absolutely. It's chaos.
JASON: Chaos is where I live, so I'm very excited about that. So, today we're using Shader.Place. So, we set up a room. The reason that -- the reason we're going to use this, this is realtime collaborative, which means both Char and I are going to be in the same room. However, because you hooligans can't be trusted, we're not telling you which room we're in. So, we set one up ahead of time.
CHAR: So, you can see me up here. If you look at my mouse underneath the uniform, hi, this is Char. So, that's where my mouse is.
JASON: That's right.
CHAR: So, this is a way that I started teaching Shaders over the pandemic virtually. I would always be doing it through Zoom, and the refresh rate on Zoom wasn't smooth, and the students -- it was weird translation. I was like, okay, the code is actually way less information, and also the ultimate compression algorithm for creating this image. And, so, I decided that it would just be easier to send each other the code and have it render locally.
CHAR: That's the reason why this app, this website, exists.
JASON: So, looking at this --
CHAR: Also open source.
JASON: What we're looking at here, this is all of the code that's making this visualization in the background, is that correct?
CHAR: Exactly, yes.
JASON: That is wild.
CHAR: Yeah. It's such a -- it's a very strong language that is very particular and peculiar, but it is extremely rewarding, because the bugs that you get are beautiful. Like what programming experience do you have where you're like this is the most -- I couldn't have imagined to make something more beautiful than my mistake. That's another reason why I think this is a wonderful way to get into creative code. It's C syntax. So if some of you are C programmers, you might be looking at this, oh, I recognize what's happening here. So, that makes it kind of finicky, because if you get a semicolon, it freaks out.
JASON: Right, rights.
CHAR: So, it's high risk, high reward.
JASON: That's the only way I like to live code.
CHAR: Yes! Yes, okay, great. Okay, so, I'll do a little explanation, what is happening here.
JASON: Yes, please.
CHAR: I'm expecting -- like my expectation for someone watching this is they have some coding experience. So, I'm not going to explain variables or something like that. But they haven't seen a Shader before. So, that's the expectations. I'm going to over-explain some things that you might already understand.
JASON: That's what we want. Especially when we're looking at C-like syntax. I have no idea what I'm doing in here.
CHAR: Super, wonderful. I love that. That means that, you know, you have like a blank slate for this, and we're going to see beautiful things happen.
JASON: Head empty. Only vibes.
CHAR: Yes. All about that life. So, you're probably wondering what these variables are doing up here. You see where I highlighted it, these are the shared memory between the CPU and the GPU. So, these are things that the CPU can see, and it tells the GPU this is the resolution of your screen. This is a variable that is increasing that we're calling time. So, time is something that is a number that is just ever increasing. You know, plus-plus on the CPU. Also, this volume variable is -- we already got our volume in, so it's like taking in our microphone. We have a number that goes from 0 to 1, corresponding to the volume of the input. And, so, that's what this is. And uniform is basically, that's what is telling the Shader program this is a shared piece of memory.
JASON: Got ya, got ya.
CHAR: We're not going to change any of this. I want to tell everyone. What this does up here, line three, this basically sets the precision of a float. You don't need to worry about it. We're not going to change it. Now, getting into this big function right here. Main. This is the function that is being run one time per pixel. And if we do the math, I can't get the exact number, but say this is 1080 by 720 -- wait, let's say it's that. This is running at 60 times a second. That's over 100 million times a second. Yes. That's like, you know, like eight zeros. Huge amount of time it's running per second.
JASON: Really made these rocks think fast.
CHAR: Exactly, yeah. Wonderful, yes. So, that's why we can do so much with such little code. We can even make something more beautiful with less code. But I kind of made it a little superfluous so it's easy to pick apart and understand.
JASON: Sure, yeah.
CHAR: So, let's get into the first variable. So, this norm cord. This is the pixel's position. So, I mentioned before the input is the position and output is the color. This is what this is. We get our position from this GL frag cord. This is a global that belongs to GLSL. And the X/X position is the coordinate. Divide it by the resolution. It's easier to think about things in terms of 0 to 1. That's a repeating theme in Shader coding. Think about things in 0 to 1, including color.
JASON: 00 is going to be like the very top left?
CHAR: Bottom left.
JASON: Bottom left. Everything will kind of go this way I guess, up to the right.
CHAR: Exactly. We can visualize that. The output, on line 29, see the output is the vector 4, red channel, green channel, blue channel, and an alpha channel, which we're not going to get into. You can ignore it, make it 1. So, if we make the color cord -- norm coord is a vector 2. If we put the third channel, make it zero, then we can -- I put a dot there. Hold on. There we go. Norm coord. Oh, I spelled that wrong. Great. So, now we are visualizing the position.
CHAR: So we see in the bottom left-hand corner it's black. In the top right corner it's yellow. For some reason in Shader coding, red and green make yellow, which isn't necessarily true for paints, but here --
JASON: Solid math, right? That's exactly what I learned when I did the color wheel.
CHAR: Yeah, yeah. But we can make it white if we make the blue channel 1. So, if the red, blue, green channel are all one, it becomes white in the corner. This is the most basic Shader you can get. We can delete all of this. This is very simple basic Shader. You basically visualize the pixels position as a color.
CHAR: But I want to show you everything else happening here. So, I'll put this back to where it was before. Great. So, here what's happening on this next -- also, we're going to get to writing code. I want to explain everything.
JASON: Sure, sure.
CHAR: Before I start changing things and incorporating the volume and stuff like that. So, this UV right here, we're basically just -- like if we visualize this UV, we're basically moving it such that UV is in the center. So, let's make this. So, we see that the center is 00. And the bottom left is -1, -1, and the top right is 1, 1. Also like a convention. You don't necessarily have to do that, but it makes creating things easier to think about. And, finally, we have our red, what I have as the red channel. And, basically, what's happening here is on line 20, we have the uv.x. we can see it's just a fade from the representation of the x position. And then when we add time, it --
JASON: Moves it along.
CHAR: Exactly, yes.
JASON: A key dot now connects, because the way we're animating is by using the clock.
JASON: What we're basically saying, I want to do some kind of loop, so I need to say, if I want it to happen at 60 frames a second, then if the clock is advancing at a thousand milliseconds, you'd divide that by 60 and every whatever that increment is, because I'm bad at mental math, we move the thing forward one frame.
JASON: But in this case what you're doing, take the sine wave and bump it over by the amount of time. And that way --
CHAR: Plus uv.x.
JASON: Continuously flows. Okay, first dot connected, chat.
CHAR: Yeah. Yes. And then you can do something like increase the frequency. So, if we were to multiply this by 10, we see the frequency increases. That's actually what we're doing down here for the green channel. We basically just increase the frequency, and then we -- that's all it does. We also switch it from the Y to X. So, if I were to visualize the green channel, now it's visualizing the Y position at a higher frequency. That's why it's kind of going up and down. Great. And then, finally, the b channel, the blue channel. So what's happening here, this is one that's more kind of going to have to be like trust me. Basically, it's like when we say X divided by Y, X over Y, we see that -- so, let's just do it without the mod. Here. So, this is what it looks like. So, basically, like when X and Y are both 1, which you can see in the right corner, it's 1. And when X decreases, it becomes darker. But then when X is negative, it goes back to -- when X and Y are both negative, it's a lighter color. That's what's happening here. What the mod does, that basically has it repeat over and over again. Do you think I should explain --
CHAR: Exactly, yes, exactly. Yeah. Actually, I could have actually just done fract, which is the same thing as mod of one. Because I did mod of 1, fract takes away whatever whole number is in front of the output.
JASON: But with -- if we wanted to make it like a mod of 2 or something, which in this case I don't think would be what we want.
CHAR: Because color only goes from -- yeah. Color only goes from 0 to 1. If we did mod of 2, it would be a bunch of -- like you see where it's all white. There is information there, but it's not being perceived, because color only goes from 0 to 1. This is kind of nice. If you go out of range, it doesn't error out. I can set the color to 1,000, and it would render it white as if I set it to 1. So, that's kind of nice.
CHAR: Normalize all the things. Yes. Great, okay. So, what I wanted to kind of get into today is I wanted to a little bit of -- do a little bit of trigonometry beyond sine. What time do we go till? I should have asked before.
JASON: We have about -- call it 45 minutes before we'll have to start wrapping it up.
CHAR: Okay, great, super. I can do this. So, what I wanted to do today, is I wanted to, with you, write a polar coordinate system to create a circular visualizer. You know when you think of visuals, you think of something that's a kaleidoscope radiating from the center that you put your face right in front of and zone out. So, polar coordinates are very good for that experience.
JASON: Okay, all right.
CHAR: Down to learn about polar coordinates in Shaders?
JASON: I'm so down. I'm so ready.
CHAR: I'm so excited. I love this, because it uses atan, and the last time I used it was in high school. Or in math class, where you're trying to learn about triangles or something like that. There are no triangles in this situation. Not going to be drawing triangles.
JASON: So, can we simplify this up a little bit to start? Maybe just start with --
JASON: Plain white.
CHAR: We can keep the uv, because we're going to use that.
JASON: Okay, I'm going to get rid of those.
CHAR: Super. Okay, so, now what we want to do is -- hold on, let me just set something up really quick on my end. Okay, what we want to do first is to start visualizing things in terms of polar coordinates. So, what polar coordinates are is an angle and a radius. So, you can access any point on a screen by just giving an angle, like where it is around the screen, and a distance from the center. So, the same way you can access any pixel given a coordinate X/Y. And it's easy to convert to polar coordinates. So, first, there is a function that's built into GLSL to get a length of any pixel to a 0, 0 point. And it's just called length. So, if we were to set up a float variable, so if you write float, and let's call this radius, radius equals, then length of UV. And we get our semicolon. And if we put that into color, you can write. One of them or all of them, because --
JASON: Oh, if you do it like that?
CHAR: That's shorthand for filling out all four channels with the same thing.
JASON: Got ya.
CHAR: Now we have visualized the radius of where the pixel is in the screen. And if you wanted to do something interesting to have it kind of be fading outwards, you could put it -- kind of canonical way of bringing -- of animating, is, obviously, adding time. But time is ever increasing, so we need to bring it back down from zero to 1, so we can put it inside of a sine wave or cosine wave. So, in line 23 -- actually, we can make a new variable. Say on line 24, float rings. Equals sine of time plus radius. Unfortunately, there are only round boys in GLSL.
JASON: Only round boys.
CHAR: There's no variance, unfortunately.
JASON: All right, I guess we can live with that.
CHAR: Sadly. I was looking forward to square boys in the round boys, but alas, it is not happening today. So, if you wanted to increase the frequency and have it look more like rings, inside of radius, length of uv, if we multiply it, then it will oscillate through UV. You have to put a dot, because it's a float, because it's syntax.
JASON: Got it.
CHAR: Now we have our beautiful rings kind of coming out.
JASON: You are getting sleepy.
CHAR: You're getting very, very sleepy. And if we wanted it to go the opposite direction, on line 25, if I just make this a minus or subtracting time, now it's expanding outwards. I can't explain why, but I feel it's more hypnotic.
JASON: I would agree, yes.
CHAR: The next thing we want to do, now we have our radius kind of expanding outwards. We want to get an angle. So the angle where the pixel is. So, how we do that is we use a function, atan. So, for all of the -- I can only relate to American school system. So, if you were not in an American school system, sorry if you can't relate to this. But does anyone remember Socatoa? So, basically, toa, right, we want to use the -- to get an angle, right, because tan of something -- tan of an angle gets you the toa. So, if we only have the opposite over the adjacent and want to get the angle, then we're going to use atan. The reason I keep saying opposite over adjacent, because if you were to think about an XY coordinate on a screen, that looks like the two legs of a triangle, right? So, we have our X and Y and the difference is the hypotenuse.
JASON: Chat is on a pun tear. Let's SOCAGOA. All right, deeply disappointed in you, chat, but really excited about the atan here. Do I want to do that to replace the sine wave, or doing that in addition?
CHAR: Let's do it in addition. I kind of want to have both. Right now -- no, no, let's have it be separate. So, because we can use both of these kind of visualizations of the position in our final music visualization. We can do it above or below, whichever one you prefer. It is executed in order. Sometimes you do have to think about your --
JASON: Execution order and all that?
CHAR: Yes, but because this is modified in place, we don't have to think about it. We can say float angle equals atan, and you want the opposite over adjacent. So, this is actually, if there's anyone here who has evil eyes. And then if we visualize it. Sorry, my brain jumped from one thing to another. What if we visualize it, perfect, yes. If anyone here knows what it should look like, might look off. This is actually atan 2, which is different, because it doesn't have this discontinuity if we were to use -- this is what regular atan looks like. Has the two discontinuities. Atan 2 flips it over, just a continuous curve, or rather gradient, until you finish it. That's a little GLSL tidbit. Then again, we see we're losing a lot of information. Only the upper right-hand quadrant, which is between 0 and 1. I'll do this, so we can actually also put it in -- rather we can put it in sine and add time, the same we did before. Now we see the smooth gradient coming around. So, we have written our polar coordinate system. Oh, oh, okay, great. Right now, I really want you to think about it. Yes, yes, you did it. Wonderful.
JASON: And now, okay, I think at this point we just watched this for the remaining 40 minutes, right?
CHAR: Yeah. Just start drooling and all that good stuff.
JASON: There's a question in the chat. So, this is an oval, because we're building it against the screen resolution.
JASON: Do we want to make it a circle?
CHAR: We can, we can, we absolutely can. Unfortunately, our screens are not square, and we have to deal with it. Sorry, I warned you about this. We have to deal with it that our screens are not square, unfortunately. If we had a square screen, it would look very perfectly circular. So, how we do that is we need to add a scaler. This is where you think about order. So, after you define UV, you want to change the -- you want to redeclare UV x. I think I'll write this one out. We're going to use the, basically, percentage of UV.x divided by UV.y to rescale. U resolution dot x.
JASON: Now all the math is based off 0, 0, and we've converted the X to be the proper, like, aspect ratio.
CHAR: Now X, instead of going from -1 to 1, it goes from -1.2 to whatever.
JASON: What's the aspect ratio for 1280x720? 56.25%, right? Now it goes 0 to .5625.
CHAR: Wonderful, okay, I learned something today, too.
JASON: No, backwards. I went backwards. It's fine, it's fine. We're basically doing that little bit of math and that gives us the right, like, you know, relatively speaking, they are square, but because it's a wider view port than a tall view port, yeah, it's all that. All that good stuff.
CHAR: A lot of this does translate to video editing and all that good stuff.
JASON: Interesting thing about this, that math, that aspect ratio math, that's a thing that I use as part of building responsive image containers. This is a thing that I do in front-end web development using CSS. So, the math is practical math. It's not like we're doing things that are wildly out of character for us as programmers. Just using something that's visually cool to look at, not trying to make an image to squish somebody's head.
CHAR: And it's frustrating in a different kind of way than CSS. Like if you forget a semicolon. My favorite error, if you forget a semicolon, the error happens on the line below it, makes people be like what's wrong with this line, they look at it for 20 minutes. No, it's the line above it that's the problem.
JASON: This is so freaking cool, though, so we're getting this. This feels like a lot of visualization having spent ten minutes on it.
CHAR: Yeah, right? Exactly. Another thing is that you can go and just start putting random stuff in, like I don't know what would happen if we did uv.x divided by y. I don't know what happens. That's what happens. Something like that. So, you can really start playing around with all the numbers and things. I saw in the chat, let's have it oscillate between the circles, you know, to have this undulating. That's the beauty of this kind of coding, gets someone so excited about, you know, you see the immediate realization of your code and makes you excited and gets all these ideas. Like a totally different way of making images. I believe that if Bob Ross was a programmer, he would be a Shader programmer, because it has so much joy and fun in making mistakes.
JASON: Happy little accidents.
CHAR: Happy little accidents, exactly.
JASON: I love it. I'm super excited.
CHAR: Me, too. I love it so much. Okay, then the final thing I wanted to -- so, we have some different things going on. I have two more things I want to hit before we, you know, start going crazy and start doing all sorts of things. I want to get a third -- right now we have our angle, our radius visualized. I want to create a visualization that incorporates both, and I also want to show you how to traverse through color space in a different way that's not necessarily red channel you are this, blue channel you are this. Instead, traversing through a color palette, a cosine palette. Those are the next two things, then we can start taking requests, you know, we can do anything.
JASON: Yeah, let's do it.
CHAR: So, let's have our -- I'll have you kind of write this, because it can -- I basically want you to combine, basically, the length and the -- the radius and the angle in some way. Because if you combine both of those, you potentially can either get a swirl-type situation, or some, like, something that is incorporating both. Swirly, perfecto. So, you can say -- it can be like, you know, sine of rings, plus angle, plus time. Just to see what happens. And then if we visualize, yeah, and if we just visualize that. So, we see that it is this kind of swirly type thing. Like these crescent moons coming around.
JASON: Oh, yeah. Okay. So, then we can play with it the other way.
CHAR: You can also do something like put a cosine in an angle. You can put them inside of each other. Did that make it go away? It actually didn't do much.
JASON: Let's isolate these for a second and see what we're doing.
CHAR: Yeah, still this kind of crescent moon type situation. Would it be outside? Trying to think. Cosine of just this, see what happens. Okay, cool, we have this kind of --
JASON: I'm going to change this to zero. All right, now we have interesting stuff going on.
CHAR: Basically, what's happening here is we're incorporating both the angle as it goes around and the distance as it kind of comes out. That's why we're getting features that are influenced by the position of its angle and distance to the center. They look like ripples. Visually exploring math. Exactly.
JASON: Yeah, it's so, so wild that we can do this stuff. Then, you know, if I want to make it, okay, let's have it go a different way, right, we can start messing with it this way. Now it's weirder. This is very, very interesting.
CHAR: What happens if we divide it? Nothing really. Who knows? Okay, great. Now we have a combo of both. The next thing I'm going to do is I'm going to copy and paste in a function called a cosine palette. Just because we could write it out, but, like, I feel like it's a little easier to -- there we go. It's a little easier to just kind of copy and paste it in. Then I'll explain it. I'm putting it above the main function, because we're going to use it in main function. We see here in the -- this is wrong. I'll just -- if you want to read more about cosine palette, it's kind of like a way of visualizing color that's very conical in Shader coding. I'm not sure it can be traced back to one person discovering, but a good article how it's used is by this programmer who made Shader Toy, if you're familiar with that. He has a great article about it here, it's in the comments, and also I have it graphed out on a graphing calculator to visually be able to look at it. So, basically, what's happening here, it's very small function. It's basically just a cosine. This is 2 pi, if you recognize it. 2 pi of your oscillation times T plus the phase. T is the number that's going to be traversing through your color palette. And you use these following variables to define what your palette looks like. You can change the brightness, the contrast. The oscillation is how quickly it traverses through the palette. The phase is where each channel starts oscillating its cosine in the palette.
JASON: Got it. So, to use this then, I would get down here, and I would set, like, float of r, and that would be CosPalette, am I getting ahead of myself here?
CHAR: I think this is great.
JASON: Angle, then get the other values in here.
CHAR: Yeah, we can define brightness. I can copy and paste in some brightness.
JASON: Yeah, just for the sake of not manually typing out things I don't understand.
CHAR: So, I have brightness, contrast, oscillation, phase. And it's not going to compile, because cosine palette returns a vector 3, because it's a color. Whoops, sorry.
JASON: I did a bad thing. Got excited over here.
CHAR: Really putting the --
JASON: Wait, this is the whole thing over here.
CHAR: Yeah, yeah, yeah. Exactly.
JASON: I didn't understand what was going on. So, this is our vector 3.
CHAR: Exactly. So, we could do this plus swirl. Swirly. And now we have this -- wow, crazy! We have this kind of, you know, traversing through the palette we created using, you know, the adjusted cosine. Sorry.
JASON: Holy buckets, y'all, look at this.
CHAR: Final thing, I had one more thing I wanted to show you. We actually want to be able to incorporate, because this is all being music visualizer. We can incorporate a number that is being passed in through the browser of the volume of the input. You know, music. If we scroll up here, line 8, that's the floating point variable. It's a number that goes from zero to one based on how loud it is coming in. So, we can do that somewhere. Let's do -- we can put it in -- what would be a good place?
JASON: Rings instead the time?
CHAR: Yes, exactly. Little junkie. We can divide that by -- times 0.1. Yeah, perfect.
JASON: Wow! So, if I make noise --
CHAR: Not the best visualizer, but it does its job.
JASON: What we probably want to do, if we were going to get really into this, is ease that somehow, right?
CHAR: Exactly, yeah.
JASON: How does easing work in this environment? If the function is getting called fresh, do we have an easy way to kind of say level this out a little bit, don't make it quite so reactive?
JASON: It's so fun we're getting that kind of reactivity. It's not this mic input, like why my voice is doing it. If I snap, we can see it happen. This is so freaking cool. This is not just a piece of art anymore. This is an interactive thing. What's incredible about this, volume isn't the only input we can get. I imagine we can also get data from the camera, for example, or data from other inputs?
CHAR: Jason, I think you might be a creative coder. I'm hearing you right now, and I'm hearing you, this is something that you're really natural at, and it's just going to, you know, I'm super excited to see what other things you make. Exactly, you can have input from your camera, even from a back buffer and have it be this -- create feedback and really beautiful things like that. So limitless. One of us, yes!
JASON: I am so, so blown away by how powerful all this stuff is. Okay, take it down more, make it really subtle. We can go here. Oh, night mode. Everybody settled in, you're watching -- old jazz stations.
JASON: AM600 talk radio. I'm so excited about this, because I can just see how many fun things start happening here as we dig in deeper and deeper. Okay, let's turn the contrast down. Now it's subtle. Or turn way the heck up. That's too much.
CHAR: One of my favorite things to do, turn the contrast down, and then we -- I have to put this. Enter right here. Hold on. Yeah, thank you. It was the mouse. If we increase the brightness, we kind of get this -- well, little too bright. We get this nice pastel situation. Another cool thing you can do is also just start playing around with these numbers. So, I want to introduce a new function called mix, which I really like as a former painter, which you can mix between two numbers. So, we can say mix 0.7 to 0.1. Then you can say sine of time. I messed up my parentheses. Okay, great. Mix, sine of time, goes here, great. We see this is now changing from sine of time. The reason why it's kind of going up to white, because sign of time oscillates from -1 to 1. So, to normalize, we say plus 1, divided by 2.
JASON: Oh, cool, okay.
CHAR: Also, another thing we can do -- still mad at me, hold on.
JASON: That was my fault.
CHAR: No worries. We can also do something like length of UV. Then we can have it kind of come in and out from the center. Because now it's taking in from the center and we can increase this by 20 and get this kind of, you know, ring effect again. But now the ring effect is oscillating from the brightness. So many things you can do from here.
JASON: We have a ring effect oscillating from the brightness, reacting to my voice. This is wild.
CHAR: Yes, I love it. Yeah, just becomes completely impossible. You know, to stop. That's why I still do it all the time today. I stay up way too late doing these kinds of things. Do you know how much time we have left?
JASON: We have -- call it like ten, maybe 15 minutes, to go.
CHAR: Nice, nice. Okay, so, is there any kind of area in which you want to explore some more? Or I can kind of take the lead and say start introducing new functions.
JASON: Let's have you take the lead, because I feel like there are some things that you know exist that I don't, so you can probably show us more interesting things than I'll be able to imagine in the next ten minutes.
CHAR: Great. So, one thing that I can show -- oh, okay. One thing that I really like is this function called mod polar. I know we converted it to polar coordinates. Do I want to do that one, or do I want to do -- oh, we can either -- I can introduce a function that gives you kaleidoscope, or introduce a function that you can use to get a BPM, or rather that spikes at a certain BPM, so you can have a variable, like you can set the BPM to this function that will return a number that spikes at zero or like a beat.
CHAR: Okay. Yes to both.
JASON: No, let's do the beat. Let's do a beat, that sounds like fun.
CHAR: Beat, so, I'm going to copy and paste this function in. And it's going to be -- I'm putting it right below cosine palette. This function takes in beats per minute, and then it returns a number. And this number will just spike that many times per minute.
JASON: Nice, okay.
CHAR: Easy to use, so we can figure out where to put it. Let's say we wanted the -- oh. Let's have the phase.
JASON: All right, let's do it.
CHAR: What would be cool? Oh, let's first just create a floating point number. So, float, and let's just call this beat, beat, equals -- oh, I'm on line 62, sorry. What's a good BPM one?
JASON: 120 is the standard, right?
CHAR: Now we have this beat variable we can put in different places. Now we can multiply the phase -- let's multiply one variable by the beat, in case it's too strobe-y. Can we see a difference? I don't see a difference. Going to put it in different places. Maybe the contrast. Wait a second, is it working?
JASON: I don't know if it's working.
CHAR: There's something wrong. Maybe it's because u time is different. I should have tested this before. Sadly, this is also another thing about GLSL. It's very -- because it's a very globally used language, you could use it in all these different situations, transferring functions from one place to another can sometimes -- I usually used the force, which is where I wrote this function. Doesn't matter. BPM, eventually I can get it to work. Maybe if we have time in the end. I'll show you instead the other thing I was going to show you. Which is the kaleidoscope effect, which is always really fun. Set this to color again. Color, beautiful. Okay, so, the other function I wanted to show you is this mod polar function.
CHAR: And, so, this one I'm going to paste up above again. Also, I have like -- I should probably paste the link, but I have this gist of what I call sticker sheets, which is basically functions that you can kind of copy and paste in.
JASON: Oh, nice.
CHAR: Oh, oh, I used -- hold on. We can do this, actually. This is my own modified version of mod polar. We can define pi up here. Pi, 3.14. And then why is it still mad at me? Oh, yeah, u time. We can actually delete this. Now, this function what it's going to do, it's going to modify the uv vertex that it repeats in a pi slice going around the center. So, mod polar, returns a vector 2. So, we're going to see how many pi slices do we want.
JASON: Okay, okay.
CHAR: So, after we fix UV resolution, change UV, before we use it for anything else. UV equals mod polar, say we do 4 to begin with. Did I spell this wrong? Oho, because this has to be point. Okay, great. Now we see it's repeating. In these four directions. So, if we wanted it to be like, say, 8, you can see it's repeating in 8 slices. And we can say 10. We can also have this be a number times sine of time. And we see that it's increasing and decreasing.
JASON: Yes. Oh, wow!
CHAR: Then another thing we can do that's interesting, because our visualization is already pretty centered, if we kind of add a little offset, you can kind of see -- oh. I have to add that after I do the mod polar. Here we go. Great, now we can see it change a bit more. So, we're offsetting the center so it's less symmetrical from the center, if that makes sense.
JASON: No, this is amazing. Okay. So, for this we probably want, like -- that's dope.
CHAR: I love that moment. I love that moment, where you're just like mid-sentence, yeah.
JASON: So, if we break this, four slices then, we get kind of a standard effect. So, like a star burst. Oh, we just did 40 is what I just did.
CHAR: I love that. That's so pretty, though. That's beautiful.
JASON: Now we get a cool star burst kind of flower thing.
CHAR: Looks like a marble effect.
JASON: Yeah. And what's interesting about this, I have absolutely no idea what I'm doing, and we kind of just stumbled around and put different stuff in place and ended up with this really cool-looking thing. So, we can simplify it further. There's 4, that's fine. But what about 20?
JASON: Now I can kind of, you know, flap my arms around and see what happens. And we're getting some really, really interesting stuff. I love this. This is scratching a lot of my desire to be creative. I always want to -- I'm also -- this is embedded in a browser. We're looking at a web page. Which means if you wanted to do something like reacting to the volume as part of your home page on your website, for example, you could do that. You could embed these types of visualizations right into your site as a standing interactive piece. Or you can, you know, go and do this as a live performance, which is also incredible. So, when you do a live performance, how do you poll -- is there a way to pull the music in here, or is that what you're using the apps for?
CHAR: That's what I use the apps for. I don't think I've performed with Shader.Place. It's more of a teaching tool. The tool I use, Kode Life, will break up the input wave of the music, give me the different bins, so I can get a range of, you know, oh, this sound is within this range, and I'll have that be linked to some sort of variable.
JASON: So, when using something like KodeLife, you can get frequency ranges. I'm going to isolate the kick drum, and this is going to be the value.
JASON: Okay, all right. Things are clicking into place.
CHAR: Yes. Another thing I did with my collaborator Danielle, she was using Middy in her performances and sent it also to me, which I was able to get and, you know, do something linked to the Middy coming in.
JASON: Okay, okay, you got me hooked.
CHAR: Hell yes!
JASON: I need to come up with a deejay name now, because this is clearly going to happen.
JASON: I'm so, so excited about this. This feels like the sort of thing, you know, we did this in the span of an hour. We were able to create something that's way beyond what I thought I would be able to create the first time out. We have really interesting things going on here that are not, you know, what we're doing here is not mind-blowing, ninja-level math.
CHAR: High school trigonometry.
JASON: Yeah. This is stuff that makes sense to me. I don't need to know how to calculate sine. I need to know what it's going to do when I call this function. Just like most programming, I don't know what a for loop does when it breaks down a code, I just know it runs one. This is really, really cool stuff that we can do here. Chat, who's trying this? Who's going to get out there and do one of these? Get into Shader.Place right now. Anybody doing this along with us? Anybody coding along? And while people are taking time to think, I will share Shader.Place here, for folks who want to give it a try. Where else? This is KodeLife.
CHAR: Yeah, this is your more professional performance tool.
JASON: Love it. You mentioned earlier The Book of Shaders. Oh, this is to read it, a gentle step-by-step guide through the abstract and complex universe of Fragment Shaders. Very, very, very, very cool. A Shader place controlled by a Twitch chat. You beautiful monsters. Yeah, we probably should do that.
CHAR: That would be fun.
JASON: There is a Twitch what now?
CHAR: Yes, there is a Twitch. I taught a class -- I was teaching classes on how to do this over the pandemic, and two of my students met at my class and started a Twitch stream called "Curiously Minded" and sit together and code Shaders. Huge fan of them. Wonderful, so happy they came to my class. Yeah, that's them. They bring different people, because I have a certain style of coding Shaders. So, it's interesting to see different folks' approaches to coding Shaders.
JASON: Wow, these are beautiful. Screen Shots shaders made?
JASON: For someone who wants to get started, Shader.Place. Book of Shaders, to get the academics. Anywhere else somebody should look if they want to take next steps here?
CHAR: Yes. So, to get inspiration and also possibly be intimidated, you can go to Shader Toy. I think it's ShaderToy.com. What we're writing is a fragment Shader. These are all fragment Shaders. It's so intimidating. They also write the codes such that it's all optimized, so it's not really meant to be super human readable. It's more for optimization. This is the place where if you want to get inspired, slash extremely intimidated. Perfect, to see the limitations of what's possible. People have written a Linux kernel in a Shader.
CHAR: Machine learning in a Shader. Most ridiculous things.
JASON: Okay, all right, I get what you mean by being intimidated. Holy crap.
CHAR: This is using a technique called brain marching. Using linear algebra allows you to write a small renderer. It can be 100 lines of code in GLSL to create a 3D scene only using the 2D coordinates input.
JASON: So cool. So cool. Just incredible stuff. Absolutely beautiful what's going on here. So, all right, anywhere else that you want people to look at?
CHAR: I think I don't want to overload people. I think that's a good starting place to get into the community. Curiously Minded, also Book of Shaders is a great way to do it asynchronously on your own pace.
JASON: In terms of community directly, we have TopLap you mentioned. In terms of communities of people doing this stuff, where you can kind of see what other folks are doing. This would be one way. Any other places people should look?
CHAR: One more, Shader.Zone. This is one more community specifically tailored for just Shaders. Run by who made The Book of Shaders, and it's a wonderful place for people to learn the Shader program. I'm throwing out 70 million things.
JASON: Oh, it's a Discord, awesome. Great, great, great.
CHAR: I think those are all of the things that I want to plug.
JASON: Go back here, so I don't lose it for the show notes. Yeah, this has been an absolute blast. Y'all, if you've been watching along, make sure you go and follow Char on Twitter, so you can get even more of this amazing stuff. And on your website, I assume you have links to things if people want to see what you've been working on?
CHAR: Exactly. Mostly on my Twitter and Instagram and stuff. My website is a little not updated.
JASON: Got you.
CHAR: I have an email list, if you want to keep -- if you wanted to take one of my lessons or Shader classes. I teach the 3D technique. But I don't have any upcoming classes, but if I do, I'll email to my email list.
JASON: So, if you want hands-on instruction, you got to sign up for this list.
JASON: All right, well, I think that is a good stopping point for us. So, I will do another shout-out to our live captioning. We've had Ashly with us from White Coat Captioning all day. Thank you so much for being here, Ashly. And that is made possible through the support of our sponsors, Netlify, Fauna, and Auth0. They are all kicking in to make the captioning possible, to help keep all the lights on the show. Very much appreciated. And we have new sponsors coming up that I'm really, really excited about, so stay tuned for that soon. While you're checking out the site, make sure to look at the schedule. We've got incredible things coming up. We're going to learn about how to use the Islands architecture, which is being called partialization, other things, this is a way to make more performance sites without sacrificing Java-based frameworks. Ben has been doing amazing things in this space. Alex is going to teach about tRPC. Sounds very cool, don't really know what it means, can't wait to learn more. Stephanie is going to teach how to do better content. And so, so many more things are coming up. Please, go here, get on the calendar. You'll see everything that's coming up. You can follow on Twitch, if you sub on Twitch to help support the show. And I think that's all of the things that I have. So, with that, Char, any parting words for everyone?
CHAR: Everyone, just the most important thing after learning Shaders is to sleep tonight and then try again tomorrow. Because it's like there's a lot of stuff I threw at all of you. If you sleep and then try again tomorrow, I'll guarantee you'll be magically better at it. It's crazy how that works. Parting words, get some sleep tonight.
JASON: I love it, I love it. All right, chat, we're going to go find somebody to raid. Thank you all for hanging out. We will see you next time.
CHAR: Thank you!
Closed captioning and more are made possible by our sponsors: