Check out a free preview of the full Testing and Modular Front-End course

The "Coding WebGL" Lesson is part of the full, Testing and Modular Front-End course featured in this preview video. Here's what you'd learn in this lesson:

James starts to create a project with WebGL using the regl library.


Transcript from the "Coding WebGL" Lesson

>> Okay, so let's write some WebGL. Like I've been advocating this entire time, we should start from zero, because that's a really great way to learn stuff and to have it in your working memory. And then you're not gonna be stuck with a bunch of choices that some boilerplate made for you that you'd never made and don't really know what's even in your project anymore.

So I'll call this directory webgl. I'll cd into it, so new directory. I'm gonna use a library called regl, R-E-G-L, so you can npm install that. I'm gonna instantiate it in place. Maybe a bit later, we can make a WebGL app that lives inside of a Choo app, because you could also do that and we could use the same state system.

I don't know, we'll see how far we get. So the main idea with regl is that regl really wants you to be writing shaders, because these are really critical to how the graphics pipeline in your computer actually works. So the first thing that we need to do is have some geometry.

And then the shaders' job, there's gonna be two shaders, a fragment shader and a vertex shader. The vertex shader's job is to take that geometry, which can be however you like it, but it needs to map that geometry onto screen coordinates. So whatever geometry you have needs to be represented as a location on the screen, from negative 1 to 1 in both the x and the y-axis.

So we can get some geometry. Here's a module called icosphere. There's also one called bunny that's really good. You can npm install bunny. Here's what icosphere looks like, when you require it and you give it a resolution like 3. It has these elements. It has positions array. So these are coordinates, so xyz coordinates between negative 1 to 1 for all of the vertices in a sphere.

And also all of the indexes, which are called cells that comprise the triangle. So inside of that positions array, this says, okay, a triangle is gonna be the first thing in positions and the thing at index 2 and the thing at index 3. So that's the first triangle.

Second one is made of those vertices that have been specified elsewhere. Okay, so we're gonna now have a mesh from icosphere with 3. I think between one to five is the different kinds of the fineness of the mesh, how many vertices there are. So if you want a really crude looking 90s sphere, you can set it to one.

If you want something a bit higher res, you can set it higher. 3 is a pretty good trade-off. The next thing that we can do is we're gonna create a draw call. So you call regl with some options. One of those is a fragment shader, which we can use template strings for it.

And in a moment, we'll be using a tagged template string function. So we're using some stuff we've already seen. We also need to specify a vertex shader. And the other thing that we need to do is specify attributes. So attributes are defined for every vertex in a piece of geometry.

And how that looks is if we define an attribute called position, then in the vertex shader, we can load that. So unfortunately, I'm gonna be using a language you maybe haven't seen before. It's called GLSL and it's based on C. But hopefully just by looking at it, you can kinda poke around at it and just play with it, cuz I think that's the best way to learn this stuff anyways.

So we have an attribute called position. You can have other attributes if you want. For example, if I want to get normals, I can load a package called angle-normals from npm. And then I can pass that along as well. I think it takes the cells first and then the positions.

It's the order of the arguments for that package. So in addition to position, I'm gonna want normal. So the vertex shader's job, like I mentioned, is to map that input geometry, which is provided by attributes, onto the screen. So we need to splat it onto the screen somehow.

So to do that, you can assign to gl_Position. I'm gonna use another package for doing that. So it's called regl-camera. It takes regl as the first argument. You can give it some other arguments if you want, like a distance. So I think distance of 4 maybe will be good.

And that camera is going to define some uniforms for us. So uniforms are variables that, unlike attributes which are defined kind of step-by-step for each vertices, uniforms are present for the entire shader, uniformly for every vertex. So the uniforms are gonna be called projection and view. So we can multiply those together.

This is just a bunch of matrix math. This is just a fancy way of turning those coordinates from the sphere onto the screen with a perspective projection. So we can then multiply by vec4 of the position and 1, because position is a vec3 and it needs to be a vec4 for reasons.

Okay, so then in our fragment shader, we also need to specify a function. And in that one, we assign to this thing called gl_FragColor. So I'm just gonna set everything to green. So this is red channel, green channel, blue channel, and alpha channel. The final thing we need to do with our fragment shaders is set the precision.

So for some reason, you need to specify how much precision your float should have. Because old hardware couldn't really deal with high precision floating point cuz it was really cheap or just bad. I don't know, but if you set it too high p, if that doesn't work, do medium p.

If that doesn't work, then you probably shouldn't even run WebGL on that thing at all cuz it's really old. Okay, so the final thing, let's see. So this is our draw call. I think this should basically work. Then we can set up a loop. This loop is gonna run at 60 fps using window.requestAnimationFrame behind the scenes.

We can call our draw function. We also need to, for reasons, wrap our draw function in the camera function, which is gonna set up some uniforms for us. Okay, so I think now if I run that in budo, Let me close that, and I'm gonna open up localhost there.

Okay, error compiling vertex shader. Right, it should be uniform, not uniforms. So this library is really great, because if you do make a syntax error, it actually tells you where. Unlike if you try to bind WebGL yourself or if you use some of the other libraries that wrap WebGL, it's really hard to debug your shaders, if you can even get your shaders into the geometry at all.

Okay, another bug. Let's see what it is. All right, it's every single frame. It looks like it's having this error, missing vertex count. All right, so I forgot to actually pass in the cells which are, remember we were looking at the icosphere and it had positions in cells.

I just forgot to pass in the triangles. There we go. Those errors are just from earlier. So there is a green sphere, and we can even move it around. I can zoom in and out. Whoa, that's really fast. Some other things we can do are set a background color.

So if I want to set a background color, I can do regl.clear with color. Maybe I'll set the background to be cyan. And I'm also gonna clear the depth buffer, which I'm not gonna go into how that works. So if I reload, now we have a cyan background and a green sphere.

Let's make the sphere dance, should be really fun. So we can do that by passing in another uniform, that's gonna be time. So to do that, you can pass in whatever uniforms you want, call it time. You can pass in whatever you like here, but regl already keeps track of a time variable, so we're just gonna do that.

And this is actually the longer form for this shortcut, because regl is already tracking that. So now that we have the time, what we can do is use the surface normal. A surface normal is something that points out from a face. So if you have a sphere, the surface normal is always gonna point out.

So it's the same as the coordinate normalized in this case, but whatever. If we take the normal and we use a package called snoise with this thing called glslify, then we can make the sphere dance. So there's this package called glsl-noise that we can load as a module into our shader code that's published to npm.

And so to do that, we need to require glslify. And then in our vertex shader, we can now have modules, just like we've been doing in our frontend code, but it looks weird. It looks like pragma glslify. And then you can define the name and you give it a require call.

So I'm gonna have 4d simplex noise. It's usually a good bet. This is like the Hello World of graphics. Just plot something with simplex noise and it'll look really blobby and cool. So if I go ahead and do that, and I'm gonna need another uniform that's a float called time.

So I take the normal, I multiply it by snoise of the position with time. And then a small, usually you don't want it to be the whole length. You want the blobs to sort of travel and look cool. And then I need to run budo with glslify transform, which looks like that.

So we ought to see cool blobbiness. It's blobbing, and you can spin it around and stuff. We could also use noise to draw the surface contours in the fragment shader. So why don't we go ahead and do that? That's also fun. It's easy to do now that we've got everything set up.

So in our fragment shader, we can do pragma glslify: snoise, just like before, noise/simplex/4d. And now, to pass in variables that we wanna use in our fragment shader, you can do this thing called varying. So these are the three kinds of variables. There's uniform variables defined everywhere. Attributes, which are defined per vertex.

And varying variables, which are gonna be interpolated based on texels. So how this works, so a lot of jargon, I just threw out all at once. How it works is when you have a vertex, so the program, the vertex shader's job is to take geometry from a bunny or a sphere or something, and to plot it on the screen.

So it only has to do that for every vertex. But if you imagine the number of pixels that you see on the screen, there are far fewer vertices than there are pixels. So when you have a varying parameter, what the fragment shader is gonna get is those variables that you've defined per vertex are gonna linearly interpolate from one vertex to the other.

So if you use a color, it'll fade from red to cyan or to orange or pink or something. All right, so anyways, we're gonna use one of those now. It's called vnorm and vpos, just to pass in the position. And, yeah, vpos = position, and vnorm = normal, just cuz we want those vertex attributes in our fragment shader.

So we do the same thing and you have to specify what type of thing that is. So it's a vec3 in this case. And once you've loaded them, so we can do something cool. We'll just start out by taking the vnorm+1.0 because it's actually gonna be from negative 1 to 1, and multiplying that by 0.5.

And so that's just gonna give us a cool color on top of green, I think, we'll see. Error. So the problem is we're actually doing this glslify thing, but we forgot that it's a tagged template string. So we need to put in the glsl before the backtick, just like in HTML length, but way tripper because we're in WebGL.

Okay, so another problem. Let's see. So we're trying to multiply vec3 with a vec4, which WebGL wasn't very happy about. So you need to be really careful about that kinda stuff. Otherwise, WebGL will not accept it. There we go. So that looks cool. You could see it's darker on the bottom because the surface normals point down versus point up.

That looks pretty cool. Now let's make it even cooler by using some simplex noise in our fragment shader now. So we can just add that in to this stuff. And we'll use the vpos maybe, we can use the vnorm, and also time. Times 0.2. And maybe we also wanna use HSL color space.

So there's HSL, which is hue, saturation, and luminosity, versus RGB. HSL is great because the hue parameter, as it goes from zero to one, it goes across the rainbow, from red, orange, yellow, green, and blue, which looks really cool. So let's use hsl2rgb. So hsl2rgb, like that. And now, hsl2rgb, we'll use simplex noise for the hue.

And maybe slow that down a bit. And then we'll just set the saturation and luminosity. Saturation of 1 looks cool because then it's, And there's a problem. Let's see what it is. Cool, people in the chat seem to like what's happening so far, that's good. [LAUGH] Right, so I forgot to actually set the uniform float time.

And we've already set up that uniform, so we can just do that in our fragment shader as well. There we go. It's pretty cool. Just kinda blobbing, blobbing gently. Anyways, that's just a little sampler of WebGL, which is now part of the web platform. So I think if your job is web developer, this is now part of your job.

It's your job to learn this.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now