Advanced Creative Coding with WebGL & Shaders Rim Lighting
Transcript from the "Rim Lighting" Lesson
>> So I have multiple of these spheres. And so all I'm doing is I'm wrapping the code that we've written so far into a for loop. I'm just repeating it and placing these spheres in different positions. I mean everything goes within the for loop except we don't put the geometry stuff inside the for loop.
[00:00:20] Because it's good to just have a single geometry and reuse that across many meshes. But you can reuse a material, but you don't need to reuse a material because if you specify 100 materials, and they're all using the same shader, three js is gonna know to combine those under the hood into the same sort of GPU thing.
[00:00:41] But if you specify 100 geometries, it's not going to be able to combine those and so it's going to end up running slowly. And so yeah, this is just getting multiple spheres and using reso colors, changing the colors of the sphere based on some randomness, so sometimes I'm going to say a uniform color for the the fill color of the sphere.
[00:01:03] But then also there's another uniform color for the point color. And so instead of always mixing it and going from color to white, it's going from color A to color B. And here, it's introducing rim lighting. And so if you want to introduce rim lighting, there's actually a snippet in the repo that you can pretty much just copy paste into your frag shader.
[00:01:42] So here's just a little reminder about GLSL in three js. And then we finally get into the rim light shader. So rim lighting is like a backlighting almost, so we can copy and paste this chunk. We can paste it into our fragment shader above the void main because all the functions go outside non one of the functions go within functions.
[00:02:06] And then we can use it just like in this example here. And we can use this to create some sort of fake lighting. where maybe we'll use the frag color and we'll increase the frag color based on some rim amount, but maybe not so drastically, we'll scale it down.
[00:02:28] So that's how I'm creating this rim light effect. You can see this function here how it works, it's not the most efficient way of doing this. One of the things that you'll start to find is this might start to slow down if you have tons and tons of geometries or tons and tons of meshes going with the same shader.
[00:02:48] And there's a couple reasons for that. One is this chunk here is actually not very efficient. We've done this because it allows us to understand things like, how to get an array into a shader and how to use defines. But it's not a very efficient way of doing this because every single pixel in this sphere has to loop over every single point.
[00:03:09] And so a more efficient approach, which we're not gonna try and get into now because it's quite advanced but just to tell you a more efficient approach and you can see it in the source. For those that are really curious. Inside source, inside demos, there's this kusama-sphere-advanced. And what this is gonna do, instead of always looking across every single point to find the nearest point it's going to store the nearest three points for each face in this sphere.
[00:03:41] And so then in the shader it just needs to say, I'm going to go through the nearest three points. Because this point here, for example, never needs to compare itself with this point way over here. Because it's always gonna just be within the points nearest to it, within the three or maybe four points nearest to it.
[00:04:00] So that's how this advanced approach is working. It runs much faster, you can get hundreds or potentially thousands of spheres with this approach. They can all be spinning and moving in real-time, but if you try and do that with this code, it's gonna start to slow down a little bit.
[00:04:16] Another thing that might be slowing down your code here is that generally when we do things like lighting and rim lighting, a lot of this computation can be moved upwards to the vertex shader. And so there's this concept in graphics programming is, if you're running things in your fragment shader, a lot of times you can just move this data up into your vertex shader.
[00:04:37] Do the same math same computation in your vertex shader and then pass the information down to your fragment shader. And because of the way that OpenGL works it will interpolate all the information across the triangles and across the vertices. And so you can sometimes lift that up and move the, the complex math into the vertex shader, and then just pass the value down.
[00:05:00] That's a very common way of optimizing your fragment shaders. And so here we have finally, pretty much kind of the end result. There's a few other things that I could mention. I'll mention a few other things, [LAUGH] but does anyone have questions? Yeah.
>> So you mentioned the pipelines, is that how the pipelines always work, they go from vertex shader to fragment shader?
>> Yeah, so this pipeline this idea of the pipeline it exists like this on pretty much every GPU and graphics processing library, like whether it's in unity or whether it's in unreal engine or something else, but it's always under the hood. It's always working the same way where some vertex data, so that might be buffed like, for example, in three js.
[00:05:54] It's called a buffer geometry. But really a buffer geometry is just a set of arrays that have X, Y, Z values for each vertex and it stores other data. It stores things like position. Stores things like UV coordinates, or normals, or maybe the colors for each vertex. And that information that's stored inside of the geometry gets passed into a vertex shader.
[00:06:17] And then the vertex shader takes those attributes and it turns those attributes into a screen space position here, which is this line. But it also can do other things. It can pass down the information down this waterfall, and this is what we've been doing here. We're taking the attributes and we're taking the information that we have in our vertex shader and passing it downwards to our fragment shader.
[00:06:39] And then the fragment shaders receiving those as varyings and it's using those in the the fragment shader. And one of the things that isn't immediately obvious, but if you have, let's say, an OpenGL triangle, this is like the Hello World of OpenGL. So here, this triangle is made up of three vertices and each vertex has a different colour associated with it.
[00:07:08] So this vertex here it has a defined position, but it also has a defined color which is green. This vertex is red, and this vertex is blue. And when we render this, maybe the vertex shader is gonna look like this. So I'm just gonna write this real quick.
[00:07:33] Just to show you. So maybe the vertex shader is going to have attribute vec3 color, attribute vec3 position. And now in three js we don't need to write these things manually because three js comes built in with color position UV and some other things. But if we were doing manual stuff or if we were including our own attributes that are not built into three js, we'd have to actually write attribute vec3 color.
[00:08:02] So we extract the vertex data and then we're going to pass it down, varying vec3 vColor. So v is just like a Common way of saying this is a varying, so vColor = color. And now what we're doing is we're passing it down to the fragment shader, and the fragment shader is gonna interpolate automatically between the values we get from these vertices.
[00:08:27] So although we've only passed down red and we've only passed down green and we've only passed down blue. The fragment shader is going to say okay for this pixel here, I'm going to figure out the value of the color. I'm going to figure that value out based on how far I am in the triangle towards this vertex or towards this vertex or towards this vertex.
[00:08:49] So the pixels that are here, they're almost entirely the same as this vertex. So these pixels are going to be very, very red. The pixels here are going to be very blue, but as we go into the center it’s going to sort of mix these three colors together into some sort of other color.
[00:09:04] And so that’s another thing that’s kind of happening within the pipeline. And all of this is pretty much happening in every graphics library, every graphics rendering engine. There's some differences here and there, but ultimately it's the same concept. And once it goes through the fragment shader, then it goes through a final step which basically takes these rastri triangles and puts it onto our screen and applies some other stuff like clipping depending on things like where it appears in the screen.
[00:09:32] And things like that, but it's ultimately, it's almost the final step.