Coding Challenge #146: Rendering Raycasting

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
(bell ringing) - Welcome to a surprise extra coding challenge. I don't know if it's surprise or it's extra. But it came up after I did my raycasting, 2D ray casting challenge that Coding Train viewer Gustavo Petzi has created a very similar project to this previously where with a kind of particle moving around a maze and casting out rays and then taking those rays and rendering a view of the scene according to what it's seeing. So, Gustavo gave me some great advice about his implementation and I'm going to see if I can do something similar to it here with the version that I made in the previous coding challenge here. So, let me explain what I'm going to try to do. So, in the previous coding challenge, if you watched it, I had a particle that cast out rays in all directions, then there were some arbitrary boundaries within its world and it would cast out the rays to see how far away the closest boundary is. So, the modifications that I need to make are, number one, is I want to think about this agent as only being able to see within a particular cone of vision. Right, so, as a human being in a sort of like first person style game, you don't have eyes in the back of your head, you can't see in 360 view. So, I'm going to make an arbitrary decision about what that range of view is. Then what I want to do is, assuming that range of view has say 400 angles (laughs) between zero and 400 and I have a view between zero and 400, each ray, the distance at which it sees something will correspond to a slice, a pixel slice that I'll render something in. So, the farther away it is, the smaller the slices that I'll render maybe the darker, the closer, the larger and the brighter and that should give me the sense of looking out into this maze of walls, I think. This is, you know, along the lines of actual ray tracing, it's an over simplification of ray tracing itself but this gets at that idea of casting out rays from a point in space and using what those rays see and the distance of what they see as the data for rendering the 3D scene. Alright, so, one thing that I've done before I started this is I just hard coded in pixel values for width and height because I want to create a canvas that's larger than the scene so I can see the sort of overhead view of the scene and then the view of this thing moving around to the right. So, that's what I'm going to do first. So, the first thing that I need to do with my particle is I need to not let it see out in perfect 360 degrees. So, I'm going to pick something really arbitrary and I'm going to say, let's actually give it a 40 degree range of view. So, I'll just do 320 to 360 or zero to 40 degrees. Let's just try that, see what happens. So, okay, so that's what it's actually able to see. So, it's not turning, I am going to want to add like maybe turning to this, but this is the basic idea right. So, it's moving around, it's only seeing that 40 degrees. Let's see what happens. So, now, what I want to do is I want to create, I'm going to create a variable called like scene and it's going to be an array, let me just hard code it as an array with 40 spots in it, because ultimately what I want to do is fill this scene with 40 distance values, I can just make it an empty array. So, as the particle looks at the walls, maybe I should have that return an array. So, I want it to return the array of the scene. I'm not so sure about this but I'm going to keep going. So, now, let me create that scene here and then for each ray if assuming there's a closest, I want to say the scene index i is that record distance if there isn't a closest then that scene index i is Infinity. I think it would get that actually 'cause record starts as Infinity. So, I can actually just do this right after here. So, all I'm doing is detecting that distance and keeping it in an array. Derrick Woods, it's going to be like a five minute long video, I'm sure I'm going to come up with something that's wrong. Now, if I have that scene here there's no reason why I shouldn't be able to for let i equals zero, i is less than scene.length i plus plus. So, the width here of each one of these slices is the scene width divided by scene.length and actually I don't need this global array, this is really just giving me that array. So, I have a scene and I have the width based on the length and now what I want to do is I want to draw a rectangle at i times w comma zero, i (laughs) with a width of w and a height of, right now just height. So, I'm going to do this. I'm going to say fill based on scene the distance, just try something for right now. I'm going to say translate to the middle, 'cause I want to draw this on the right hand side, push and pop. Just to revisit this. What am I doing? Basically what I'm doing is I'm recording all of those distance values. All these rays. How far is the thing that it saw and recording every one of those distances in an array. There's currently 40 elements. Those are in an array called scene. I'm going to draw a slice with the brightness according to that distance. Okay cannot read property length of undefined sketch.js line 38. So, what's my error here. (bell ringing) I wrote this look function, I made a scene array, I filled it with stuff and then I forgot to return it. So, let's return that scene array. Great, so this makes sense right. I'm seeing what it's seeing. This is good, so this is a good start, okay. Interesting, now, let's try to take this a little bit further (laughs). One thing is I'm not inverting right. The things that are farther away are appearing brighter. So, couple things that I need to do to make this really look like the scene. Number one, let me just say noStroke. Okay, so, that is a little bit better to start with. Now, let me invert that. So, basically, what I want to do is I want to say the brightness should be inverted. So, I'm going to say b equals map, scene index i which has a range between let's just say the furthest something away could be like the scene width, it's not exactly right but let's just do that and then I want to have a brightness of 255 all the way down to zero. (bell ringing) So, by the way, field of view is the term that I'm looking for and I keep talking about like cone of view, range of view, peripheral vision and right now my field of view is 40 degrees which is not very large, and some people in the chat are telling me to make it like at least pi divided by two which is 90 degrees. So, let's actually change that just to sort of like see, let's make this 90 degrees just to like have more stuff to look at. Okay, great. Now the question is does everything calculate perfectly from that? Do I have anything hard coded? I feel like I had something hard coded but maybe not. (bell ringing) I've got a major issue here which is that I only calculated this value b, I forgot to put it here in fill. So, let's add that. So, now I have a 90 degree view and I actually am putting b in fill. There we go, this is more what I expected to see. So, you know, I'm not controlling where the stuff is. So, this would be helpful for me also now that I could to have a little more agency over debugging this. For me to update the particle based on the mouse. So, I'm going to update the particle based on the mouse location. This will also be nice for when I want to rotate the view. So, this will let me do a lot more. So, I can sort of see, okay, look at this. That is what I'm seeing and this makes as I go out, if I come really close to here, we can see it's quite bright on that side and then quite distant there. So, the other thing that I want to add now is objects that are further away appear smaller than things that are closer. So, I also want the height of these rectangles. So, one way that's going to be helpful if I just use rectMode CENTER and then if the location is i times w plus w divided by 2 and then this is just, and this shouldn't be height, this should be scene height and this should be scene height divided by two. This should give me the same exact thing now. This is the same exact thing but now that I'm drawing the rectangle from the center, it's height could also be mapped. So, I could say the height is map scene index i with some maximum range between zero and sceneW to between scene height and zero. So, again, so what I'm doing is I'm saying the further away the shorter the height, the closer it is, the taller the height. So, now, if I put h in here, we're sort of seeing (laughs) my 3D view. Now, I guess maybe 90 degrees is too big of a view of the scene but you can see look that's what I'm seeing. Look, here I am, I'm looking at that wall, let's give me less view. (laughs) Let's go back to like 45 degrees. So, you can see there's a wall, there's another wall further away. There's a bunch of walls further away from each other and now let's try turning. So, I want to be able to rotate, I want to be able to rotate my view. So, the particle can have maybe an offset. In the particle I can add like a rotate function and I can rotate by some angle. Maybe I could call this heading, like the particle has a heading and I can say this.heading plus equal angle and then I can just go through all the rays like this and say this.ray and this will be 'cause there's 45, this is hard coded but let's change this to i and let's change this to rays.length and I could say this.rays index i setAngle the i, i plus this.heading. So, this would be rotating the heading and then setting the angle of the rays. Right. I think this is right. So, now, if I were to do something like say a function keyPressed, if key equals let's just say a, oops, key equals a particle.rotate by like 0.01, else if key equals z, rotate by negative 0.01. Is this going to work? So, here I am looking here and now I'm going to rotate. Oops, no I'm in the console. Sorry, I'm looking over here and now I'm going to rotate, rays is not defined, this.rays.length. Oh, this should be radians i. Yikes, I'm really like, this has gotten quite convoluted but I'm trying to just get at the idea here so I'm sticking with what I have. So, if I do this now. If I should be able to. Okay, angle is not defined. So, first of all, it's not registering my key strokes. Oh, 'cause I think, oh and I've made it z. This should be s (laughs) and then angles not defined at ray.setAngle from oh, a whatever. Okay, so, now, yes, so let's rotate by a larger amount just to like. Yes, so now I can turn, oh, that's fun. A nice change I can do, I don't know why I was not doing this in the first place is in draw I can just use key as down. So, I could say if keyIsDown and I can actually just say LEFT ARROW, then rotate the particle, else if keyIsDown RIGHT ARROW, rotate the particle the other way. So, this should be, and I don't need the key press function. This should make my already really terrible and awkward interaction much better. Oh and I'm missing a parenthesis there, there we go. So, let's try this, I was just looking at the keyIsDown documentation. So, let's try this. So, now, I should be able to use the arrows. Yeah, okay, so at least now I can turn much more elegantly. Okay, this is better and it would be nice if I could actually just move with forward and back. I don't know if my angles are going in weird directions but I'll let you sort that out but I want to sort of like fix the view here because one thing is I'm seeing the boundaries and first of all I would like it to be black as I look off into infinity. So, let's, do I have those wall boundaries? That's a question. I can't remember if I've left those in there. So, let's take out these wall boundaries and now, so, this I like better because off into infinity there's nothing. But I do have this kind of like fish eye effect. Let's try a 60 degree field of view. So, I'm being told from the chat that I should use an inverse square law for the brightness. So, I believe what that means (laughs) if I'm rendering it down here, is that I should map the scene squared. So, let me do s square is scene index i times scene index i, and with square is sceneW times sceneW. So, I should change this to map sq between zero and the scene with square, to 255 down to zero. Let's try that. So, that looks better. I kind of now like want to put those outer walls back in 'cause it looks so weird not seeing anything, further away, like it looks nice to see things that are further away and also why am I getting this like space between those? I feel like this might need to be like plus one here. Yeah, that's better. I don't know why 'cause without this stroke or I could add a stroke to a, so, that's better in terms of seeing the walls. Let's put the actual like wall boundaries back in. Yeah, 'cause they're so far away, now this is better. Oh, I like this. Let's turn around and see this wall and move. This is kind of interesting here, like this spot right here, like there's that wall. Okay, let's add the Perlin noise moving back 'cause I think that'll be fun. I don't want to move it with the mouse anymore. Let's add Perlin noise back. And there it is, there's my thing walking around and I can turn. (bell ringing) You know, so this ability to being able to rotate around with Perlin noise, this is like sort of very like a weird interaction. So, even though the perspective is wrong and various things are wrong about this, I think what would actually make this most effective is for me to be able to actually also move forward and back. So, the thing that I want to do. I think I really need to clean up the way that I'm thinking about the angles of the rays and so, right now, if I go into the particle, the angles that I'm starting with are from zero to 60 degrees. So, what I really want is to be from negative 30 to 30. Like I want those angles and the heading is zero. So, I'm starting with an object, the object just looking directly to the right and let me turn the Perlin noise off. I'm going to turn the Perlin noise off, this is not useful and then what I want to do is I'm going to add else if keyIsDown up arrow. I want to say particle.move one and then else if keyIsDown DOWN ARROW, particle.move negative one. And I think I have these backwards and they're like a different amount. So, I should be rotating positive one for the right arrow 0.1 and negative 0.1 for the left arrow. Okay, so, now, I need a move function in the particle. So, the move function, the heading should also really be a vector but that's fine, it's an angle, it's fine. The move function which is getting moved by some length, some amount, I need a vector, really it's velocity that's pointed in the direction of the heading and that vector's magnitude should be the amount that I'm moving which is just one, so this is redundant. And then I should be saying, I should add that vector to the position. So, now, at least if I refresh, I can actually walk around in a way that you might expect. Now, I'm kind of a little bit off. Something is a little off here. So, first of all, let me see why what's off here. Oh here's the problem. So, I need to use this same loop here but my index is not going to work with the angle so I can just manually add an index that should fix that. And this should be the angle. There we go. There we go. So, now at least finally I can drive around this scene which was the thing that I wanted to do. (bell ringing) So, now that I can drive around the scene, let's add, and I realize the perspective is wrong but let's add one more thing to this at least, let's add a little slider so I can adjust that field of view 'cause I don't think will be a nice thing to look at. So, can I adjust the field of view in real time? So, I have my field of view variable, I could create a slider. So, let's say, let's have sliderFOV and let's say sliderFOV equals createSlider and I have a range between 0 and 360 and I'm going to start with a 45 degree field of view. So, now, we should, I have this slider. So, in theory, I can then have an event like sliderFOV input change field of view. So, I'm going to write a function called change field of view and get the field of view from the slider and then say particle update field of view FOV. So, now, if I go into the particle and I say, I write a function called updateFOV then what I should be doing is just resetting all of these rays. So, I'm going to do this again but I'm going to say this.rays I'm going to empty it out. So, in theory now, oh, hello. I have got to actually update the field of view value with the new value coming in and recreate all the rays. So, now, I can do this. Fun. So, now, I can drive around my crazy scene backwards and forwards in some nonsensical way and I can also adjust the field of view. (bell ringing) I'm cutting into this coding challenge to redo the ending a few days later where I just left off, where you just saw me leave off was right here. And you'll notice while this is working, there's this kind of a fish eye like effect in terms of the perspective, which is actually quite nice, especially once you like kind of increase the field of view, you get these kind of beautiful effects. The other thing is all of a sudden now, once I change the field of view, my driving seems to have been broken. So, there's a few bugs that I left off last week that I want to fix before I finish this off and release the code to you. So, bug number one let me fix the driving. One of the things that is sort of key to the way that I've designed this, which is not by any means a requirement to how you need to do this. But i and the rays in the world are always relative to the vehicle's velocity, or the direction that it's moving in, I don't know if vehicles the right word. The person, the thing that's moving around in space and the angle of that is expressed as a heading. So, the rays are always assigned some direction relative to that heading on either side. So, in the code whenever I call rotate for example, I set every single ray's angle relative to a variable a plus that offset of it's heading. But I forgot when I'm updating the field of view to do that. So, that's a really important thing, that I just need to add this.heading right here. So, let's fix that and I'm going to rerun this sketch and we can see now as I turn around and move backwards, and then increase the field of view, I can still steer around and it works. So, I really quite love this in the sort of bizarre rendering thing that I've got here. But let's look at how it really works to kind of reduce this fish eye effect. One of the resources that was suggested to me that I really should have read over more carefully before I started the coding challenge is this computer graphics tutorial on raycasting. It's really wonderful, I suggest reading the whole thing. But if you scroll all the way down, you'll notice that it says, the distance you should calculate should be the distance projected on the camera direction, the raw Euclidean distance will give the fish eye effect. So, let's say I have this particle which is really the camera and its camera is pointing in a particular direction, that's the view, that's the camera's view. Now, I have any particular ray that's emanating from that camera's view within the field of view. So, let's say I have the wall is something like this. So, the wall, this particular ray is going to hit the wall here and this is the distance that I'm currently using, that raw Euclidean distance. But imagine there was a light source above here casting a shadow down on to the plane, the direction of the camera. That would be this distance here. This projection of the ray on to the camera's view. And so, this is actually the distance that I should use which I can get by the Euclidean distance times cosine of this angle and there's a variety of different ways I could calculate this but that's what I'm going to apply in the code. So, in the code where I am calculating the Euclidean distance is right here. So, in addition to the Euclidean distance I could get the angle of the ray relative to the direction of the camera which would be a equals ray.heading minus this.heading and then I could just multiply d by cosine of that angle. So, I'm taking the component, the projection of the ray's vector on to the camera's vector and now if I use that as the distance instead and hit refresh, oh boy, ray.heading is not a function. Okay, oh, it's the direction vector that's part of the ray. So, it's the direction vector that's part of the ray. If I hit refresh now, assignment to a constant variable. Oh, I'm so good about using consts these days but if I'm going to adjust it a little after the fact I have to use let which I think is fine here. And now, let me hit refresh and finally I think that I have gotten it. Here's a pretty good setup, you could see this looks less fisheyey. It looks more correct like what you would expect. Just to be sure about this. Let me add something to just sort of switch it on and off. I mean this a very, a checkbox or something would be better but I'm just going to say if not mouse is pressed. So, I can click the mouse and I'll turn off this adjusting that distance calculation with cosine of the angle. (bell ringing) Alright, I found a nice random configuration of walls with the camera pointing there. This is with cosine of the angle. Now, when I click the mouse, you'll see it's not that different. But let's increase the field of view. Let me click the mouse. So, you can probably see the distance most notably right over here on the right edge with the raw Euclidean distance, you can see that curve around the edge versus the wall actually emanating out there. So, that's a pretty slight difference and I'm sure there's a lot more to this and this math. But I still, my favorite thing is to just make it 360 degrees which is really kind of crazy looking. And we can see what is the difference now with a 360 degree field of view with the cosine of the angle or without. You can see how notable different that is. I think I'm going to wrap this up here. Thanks for watching this coding challenge, I think there were a rich set of possibilities you could explore just by building off of this very basic example. First of all, creating an actual proper maze with constraints about where the vehicle particle camera thing can drive around would be one thing to start with. Adding colors to the wall so each line could represent a different color and you could display that color in the view. Texturing the walls would be a really interesting challenge, automating how this sort of particle moves about the space, multiple particles, different kinds of boundaries. So many possibilities. Ultimately, making a hyper-realistic raytracing engine is not the goal here. If you can take this basic idea and make this strange view of the world we live in, that's kind of exciting and interesting. So, if you make something, please share it with me. An instruction for how to do that in this video's description and I'll see you in a future coding challenge. (whistle blowing) (energetic club music) (bell ringing)
Info
Channel: The Coding Train
Views: 169,503
Rating: undefined out of 5
Keywords: daniel shiffman, coding, the coding train, coding challenge, code challenge, programming challenge, ray casting, game development, 2d ray casting, ray marching, ray casting javascript, p5.js, ray casting p5.js, raycasting explained, ray casting algorithm, ray casting tutorial, 2d ray casting tutorial, p5.js tutorial, ray casting 3d, wolfenstein 3d ray casting, raycasting rendering, raycasting 3d
Id: vYgIKn7iDH8
Channel Id: undefined
Length: 28min 51sec (1731 seconds)
Published: Fri May 10 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.