For other humbled programmers that just watched this: "I designed and tested this program very carefully for days before I began recording the video, in order to avoid consuming video-time in debugging and in other uninteresting activities. Within the video, I just retrace the steps of creating the program in a natural and a structural manner, with mistakes mostly removed." - From a FAQ on one of his other videos. I'm still impressed, but I feel a lot less terrible about my own coding speed now. ;)
Watching such code being easily written, I began to seriously doubt my competency in coding. Then I scroll down and felt so satisfied it was just rebuilding the code.
for those who still feel inadequate: try recording your own work (1fps, then speed up to 60 in post to create reasonably long timelapse) and watch it, you'll be surprised how cool and pro it looks and feels to watch, even if it didn't feel that way while working ;)
If you code in notepad++ (.... :P if...) you can also just hold down ctrl+z or ctrl+y to scroll through time. Watching a canned refactor be undone is :_(
@Jason Sun That's a good thing, coding is not really a natural talent so you can keep improving. I'm mostly a cut and paste coder because 1: I don't get paid enough and 2: I don't get paid enough. So don't feel too bad lol.
+Rodrigo Appendino And they are right. But it may still make sense for several reasons: 1) Learning how to do things. 2) It is fun. 3) Existing libraries or programs might not be applicable to your needs, or are too difficult to find.
@@rodrigoappendino you don't always have to reinvent the wheel, but it's definitely not bad to do so. By creating an engine from scratch you can make it to satisfy your requirements and add the optimization techniques to the project that you want. You can decide which utilities functions you want and performance depends on your code not on the developers of the game engine. The application weigh is less than with a game engine like unity or unreal. Also most of the big companies do their own game engine, so if you want to work professionally as an hired developer you most likely will need some knowledge of frameworks/api
It is not very advanced, the impressive thing is how fast he is writing the code. We had to create our own fully 3d engine as a collage course with global illumination, interactive objects and stuff like that. The mathematics around lighting the scene and the camera work, calculating world/camera cooridantes was the worst thing for me. But if you find this magic, you should check some codes from modern engines like cryengine 3, what they are doing is more of a magic.
je3f0o - yeah, i know. It is something different then this. But if you really want to see the bleeding edge stuff that is really amazing technology-wise today, that is the way to go. I understand this, but most of things in the modern engines are beyond my understanding.
+Bisqwit: 15:30ish. Dude... dude... no one has EVER explained so perfectly why learning formulas in math class is important. Especially with Google these days it's NOT the formula itself. It's having done it so that when you run across a problem in the future you have the experience that says "ah-ha! It might be _______" because you remember it from class. Equally important though I think is teachers giving real life examples like this video which, from my experience, they don't do often. I keep getting more and more in love with these videos, you really do a fantastic job!
Most "a-ha" moments in life come from self-study, especially in math. What you experienced was the small intersection where a great educator meets a ready student.
That's most of school tbh. Having a frame of reference, or more importantly - a purpose, is paramount. I was awful at programming ins school becasue we were writing programs which didn't really do anything, and the ones that did were boring shit for accounting and stuff. Fast forward 20 years and I program my own games now and I know six languages due to it. Because I know exactly what I want to happen on screen now and have a passion to see it happen it's given me a focal point and a thought process which will make me learn much faster. It just goes to show how very important proper motivation is in life, yet most people are stuck in jobs they hate and will just be producing the bare minimum to get by.
A reminder to everyone! If you post questions as comments to my RUclips videos, please make sure you have permitted replies to your comments. Otherwise you'll be an equine. The relevant privacy option is probably found somewhere within the Google+-RUclips frankenstein abomination monster.
Bisqwit Are you sure you mean equine? That refers to horses, which are relatively intelligent animals. If you're going for an animal pejorative, I would go with sheep. Sheep are extraordinarily stupid. Great video! I'm always interested in how the underbelly of computer game engines and other low level stuff (low level for me is C, I've never ventured into assembly)
Michael Hoffman With "equine" I was referring to "donkey", or "ass" as it's better known in the context of that expression. However, I was aiming for humor, with the simultaneous goal of avoiding vulgar language, considering that the word has another meaning too. While I don't know which of the meanings is the original etymology for that expression, I know it translates literally very well to Finnish using the animal's name, which seems to lend credibility to the theory that the word used in that expression in English indeed refers to the animal. As for sheep, in terms of metaphors, they are commonly associated with flock behavior and innocence. These associations hardly fit in my original post.
Bisqwit Thx I got it to work, but I am wondering how to get it to know where 3rd party library header files and lib files are located? I am using MinGw g++
I learned quite recently that Ken Silverman wrote the original Build engine when he was just 17/18 years old! I find that to be amazing, I actually screamed "WHAT" when I found out. As a nineteen year old who started when he was 17, I cannot help but to feel jealous, yet inspired, of his talent.
I found this to be immensely humorous and informative. I could watch videos like this all day long. Keep it up. "I have no idea why it works, but it does and that's the important thing."
@G E T R E K T 905 The language itself might be easy, but it is much harder to create something meaningful in it. You have to do an insane amount of bookkeeping. If you program in higher-level languages, the compiler does most of that work for you. So when people are saying that assembly is hard, they don't necessarily mean that the language itself is hard to understand; just that it is hard to code in it
@@isodoublet Seeing it spelled like that made me cringe so hard. You're right. It's all based on concepts you would find in any decent linear algebra course.
The above all, hands down, most important skill you should learn is the ability to identify the type of answer you are looking for, and then look for it" -Bisqwit I like that quote, it's going to help me man. I've watched this video like 10 times now. I love the music and style of the video, it's perfect.
all programming, of anything is based off the Finnish... But I think TTS may be doing the language teaching there... :P (Explains why programming is so natural.)
Ok I have to say this. Even now, after 3 years this video is so amazing to me. You littelary made whole freakin' 3D graphics generation engine. Just big wow...
You are getting so many brownie points from me just for all of the stuff you include in your Downloads page. I've got your sample code, the completed version, the text editor you used, the textures including their source and the license they were published under. I'm having such an easy time following along with this. You're getting a sub, sir. and by the way your accent is amazing.
You're not the dull one, Bisqwit. It's been very long since I've come across someone with such clear and concise thought. Thank you for this, and all other demonstrations you so religiously upload.
This video is excellent - I have been looking for a video tutorial of a raycaster-style engine which is very detailed. I found your narration style to be excellent as well, so please don't apologize for that. Thankyou for sharing your knowledge!
This video has been a true inspiration. It reminds me that I can make anything from scratch. No programming task is too complicated if you have the time and motivation. I have returned to this video many times over the years. Eventually I wrote my own engine of this type along with a level editor.
I cannot thank you enough for this video. Before this video, 3D graphics programming was a mystery to me. I'd read tutorials and followed them, but I didn't fully understand what was going on. After this video, things magically "clicked" and I suddenly understood everything. Now I don't need tutorials. Again, thank you for this awesome video.
whats funny is its the opposite for me, watching these videos of him typing the code so fast kinda overwhelms me and I still dont get 3D but its still entertaining to watch it develop over time. only difference is I also dont get other videos
The above code is for qb64 and work in qb too..download qb64,copy the code,and paste,press f5 to run..you will get the output and .exe file where the source file is saved..writing directly to .exe file doesnt work either
Hopkroft Thanks! I forgot to explain in my video why exactly Doom requires the slow and expensive process of building a BSP though :) I only explained why Duke3D doesn't need it. Good thing there are books, after all.
Bisqwit , maybe it's just a coincidence, but Ken Silverman started with Basic, like you :) I hope that you will create another videos with explaining of technical detail. P.S. Are you working as QA or developer?
Arlo Jeremy Try this article: doom.wikia.com/wiki/Doom_rendering_engine#Node_building Or this: en.wikipedia.org/wiki/Binary_space_partitioning I haven't studied the BSP technique, so I would have to read these first (and potentially much more) in order to say much about it.
I wrote my first code when I was 6. It was in Sinclair Basic. I tried BBC Basic after that. This is what made me scared of being a programmer for many years, presuming I couldn't do it. Now I have been coding for 20 years, I know 10 different languages including scripting and cheekily adding HTML as a markup, because it follows the same principles of what I believe is coding. BUT.... I see basic code, and I still run a mile. That is some horrific code to remember. The logic is so progressively linear that it's either genius to your mind, or makes no real sense. Thank the lord for modern languages.
Cannot belive you just say that! Basic is basic and it is the only very easy language I want to use even now! I have a bunch of modern versions of basic one even for making 3D videogames!
This is one of the few videos I have found on such a subject to be straight to the point and properly broken down into steps. It's a quick reference and very effective. Great job! As for the description about your English, I think it is perfectly decipherable and your written English is excellent too.
Honestly if you're going to build a 3D engine yourself you might as well go true 3D, this stuff is fun and all but it's way too restrictive by current standards.
This is an inspiring production... In-depth, humorous, informative, made with a high regard for quality. It seriously motivates me to improve on both my computer graphics and C programming skills.
Duke Nukem's 3D engine on PC was the same, that's why it looked a bit warped when if you look up in it, and that's also why DOOM's view was locked from looking up and down. (This rendering method was changed in the console versions, as they were too slow to software render it, and the hardware rendered mode was better 3D anyway.)
Hello. I don't know anything about programming, but I really enjoyed this video. Watching the code, reading the comment, listening to the explanations and watching the engine become more and more complex was very interesting. I don't have any problem with your accent, probably because I'm French and we also have horrible accents when we speak english ! Thank you for your interesting channel :)
+Leeematty RS Oh crap. You posted a good question, and I wrote you a long and insightful answer, but then after the fifteenth edit, I accidentally chose "Remove this comment" instead of "Edit". Both your post and my reply are gone. And RUclips did _not_ offer me the undo option that it usually does when you accidentally click "Remove". I am very sorry. I _hope_ my reply is at least visible on your Google+ page, but I can't view it for whatever possible privacy related reason. And I can't even tag your name into this post, why is that I can't even fathom.
Bisqwit Don't worry, I can still see the Question and Answer (I'll post it below in case you want to see it again) Thanks for the great answer, I somehow completely forgot about global variables :s Also, I have always been told that making anything "static" is bad practice, but I don't know if that is the case always back to my question: the function changes/ adds data to the global variables from the structs. Makes sense, thanks :) also I got my question here: One thing I been trying to understand for a while is how your LoadData() function works, because it is a void function and so returns no value; and also does not take in any data. Surely the data loaded is lost at the scope of said function? Or perhaps is there something I am missing here, maybe because it is "static void" (which I have no idea what that does)? I got your answer here: It reads a disk file, and stores the data read from that file into global variables (sectors, player, and also NumSectors). Global variables are accessible to all functions and scopes without having to be passed as parameters or returned as return values. In "static void LoadData(void)", "static" means that the function is not visible to possible other compilation units linked into the same program (does not matter for this program, but it is a wise habit to do always unless you specifically intend the function or global variable to be used from another module); the first "void" means that it returns no value, and the second void means that it takes no parameters. In C++, you could omit the second void, but in C, if you do, it means the parameters list is undeclared, not that it's empty. In C, you could also omit the first void, but if you do, implicit "int" return value type will be assumed. In C++, it is not possible to omit the return value type for non-lambda functions. This declaration: static struct sector { float floor, ceil; struct xy { float x,y; } *vertex; // Each vertex has an x and y coordinate signed char *neighbors; // Each edge may have a corresponding neighboring sector unsigned npoints; // How many vertexes there are } *sectors = NULL; Contains the following elements: static *sectors = NULL; declares "sectors" as a variable of type pointer to zero or more , preinitializes the variable as NULL, and limits the visibility of that variable's name into the current module. Then furthermore, that is defined as "struct sector". Lastly, what the "struct sector" means, is defined immediately in that very same statement, with the parts between "{" and "}". This struct is a collection of the following members: "floor" and "ceil", which are of "float" type. It will contain "vertex", which is of type pointer to zero or more "struct xy". And what "struct xy" means is immediately defined as well. "neighbors" is of type pointer to zero or more "signed char". "npoints" is of type "unsigned", or "unsigned int".
Leeematty RS Thank you! Also, there are two or more meanings to the word "static" in both C and C++. In global scope, it has the meaning that the visibility of the symbol (whether a variable or a function) is limited to the current compilation unit. This is a good and meritable thing. It reduces namespace pollution and makes linking faster. This applies also to C++, even though most people recommend using anonymous namespaces instead for an equivalent effect. While anonymous namespaces are the _only_ way to achieve this effect for struct methods (for which "static" has a different meaning, explained below), I don't always use them, because I like to indent namespace content, and too many indentation levels makes the code more unreadable. Breaking in/out from the anonymous namespace for the occasional function or variable that you _do_ want to export is also a contributing factor to unsightful code. In function scope (between of { and }), it has the meaning that the variable acts like a global variable, but its visibility (scope) is limited not only to the current compilation unit, but into the function it's declared in. This means it preserves its value across calls, and storage for it remains allocated throughout the entire program. This feature should only be used when other solutions would be significantly more cumbersome. This applies also to C++. Additionally in C++, having a function-static variable and initializing it with a non-constant initializer (which is not permitted in C) will incur the creation of an invisible static "guard" variable that ensures the variable is only initialized once, yet another reason to avoid this syntax. A third, less commonly known use of the "static" word in C looks like this: void foo(char s[static 100]); In this context, it means that foo's parameter "s", of type char*, must point to an array of at least 100 chars. I have never seen this practise in "the wild", and can't say anything about whether it's recommended or not. This feature doesn't exist in C++. In C++, variable-type members of structs, unions and classes can also be declared "static". It basically creates a global variable that is restricted into that struct's scope, and has no associated instance. With a const/constexpr attribute, it is a good thing with many honorable uses. With non-const variables, the benefits compared to making it a possibly-namespaced global are significantly fewer. This is not possible in C, because C lacks the scope resolution operator "::" and its semantics for struct scopes are more limited. In C++, function-type members (i.e. methods) of structs, union and classes can be declared "static" as well. It basically creates a global function that is restricted into the struct's scope, and does not take the hidden "this" parameter pointing to an instance. There are many good uses for this kind of "static" functions. It does not make linking faster, but it does reduce namespace pollution. These functions also get access to "private" members of the class, which global functions do not. This is not possible in C, because methods don't exist in C in the first place.
sometimes I am just really happy to be alive and find out that people like you exist. I love being amazed by what other people can do. thank you and happy new year.
One of the best raycasting video tutorials out there. It's hard to find any that don't just show you something and not explain what's going on. Keep up the great work!
Raycasting is a technique where you basically shoot bullets and see what they hit, and render a tiny column of a wall with the smaller height the farther the bullet went before hitting something. The technique described in this video is completely different. They both can be used for pseudo-3D scenes.
Very, very well presented and interesting video. I always had my little thoughts of how DooM/Quake/Half-Life/etc... rendered the game world but never really looked into it and I think this video explained the methods used in such engines very clearly. It's also interesting to see that what kind of problems people like John Carmack had to overcome while writing the first 3D renderers like this.
@z3 it wasnt exactly made to be "easier" than assembly, rather, it was made to be cross-platform. Allowing the same code to work on an entirely different system.
3:19 I did reconstruct where the vector produkt comes from. First of you start proofing pytharoras then you get to the sinus formula in a triangle which leads you to the cosinus formula in a trinagle which enables you to derive the scalarproduct by adding vector principles and when you complete this stuff you can deduce the vectorproduct. this operation is insane. by streching the possibilities of the structures you find by just proofing their concepts you get to the very formula of the vector product. it tells you that multiplication of 2 vectors will result in a third vector which is standing on the plane that the first two create. the length of that vector is the size of the route those two vectors create. The Vectorproduct is only to be deducted in the 3rd dimension other spaces doesn't have such solution. It is like you start of with one two three and then suddenly a selfware structure emerges.
Please keep doing what you're doing as much as you can. You took a complex and sophisticated topic in programming and game design and boiled it down to its simplest components to make it easier for laymen like me to understand. You are a god, sir.
Really cool, I've never ever attempted to build my own engine, instead relying on stuff already made like unity or löve but watching do this so apparently seamlessly and easily made me wanna try my hand at it, granted, as people in the comments pointed out you didn't just do this on your first go, instead going through the necessary steps and practicing before doing the final video but despite knowing I will fail miserably on the first couple of tries I wanna try doing this and seeing how it goes
It's been quite a while since i last watched this, and I'm still no better at programming than i was then, but i still absolutely love this video. You do such a fantastic job of demonstrating such a complex system, and made something seemingly mythical seem regarding approachable. Thank you so much for making this!!
I really love how the engine breaks the laws of physics! Go under the stairs and there's a ceiling which is higher up than the steps of the stairs, but can't be seen from there. On the upper floor, the floor above the stairs is on the same height as the last step, but it can't be seen from the stairs.
Thanks! Yeah, these things obviously fascinate me as well. And just to show that the engine can cope with _normal_ physics as well I added the windows in the stairs that work perfectly normally as one would expect.
+Conex Xenon to be fair, he actually did it over a few months. He has written a robot that will basically write some code for him, as long as he himself has already written it. He tells the robot what order to code stuff in etc. Plus he has around 22'years of experience;)
you found you a new subscriber. i am a big fan of the handmade movement and fan of the scene for a very long time. also i had very good memories of QBasic too : )
can someone please shade some light on the second view transformation equations (@1:37)? It is the view were the player is fixed but the world is moving around him. He also refers to a OpenGL video that explains this but I couldn't find it.
The OpenGL video is here: ruclips.net/video/vkUwT9U1GzA/видео.html I will wait and see if someone knows a good way to explain the part that you asked about, before I proceed to just reiterate what I said in the video.
I can try. Basically, in terms of rendering, the camera is always the "origin." All the objects in the world have to be positioned, rotated, and transformed relative to the camera, and not the other way around. Once the objects are placed in the world, in this case the wall, it needs to be transformed so it's position is relative to the camera's. So, if your "world space" camera is in position (5, -3), you need to transform the camera's position to (0, 0). Doing that requires you to modify the objects in the world as well. Think of it like making a drawing with all the walls or objects in their 'world' position, then moving the piece of paper until the camera is at the absolute center, which, in turn, moves everything else on the drawing with it. Then, you just need to rotate that world (drawing) to make the camera's angle 0 degrees. Doing that, you just have to take whatever 'world space' angle the camera is at and make it 0. Same as above, that requires you move everything else in the world along with it. You have to know a bit of basic trigonometry (look up 'sine cosine unit circle'), but it's not as hard as it looks. TL;DR: The camera starts off in an absolute 'world space' area, detached from all other objects. You've got to transform the world so that it moves and revolves around the camera, which is always at the origin. Hopefully that helps a bit.
HalfBurnToast, it calculates the intersection between two lines (not line segments). First the line (the wall, in two-dimensional coordinates where X represents horizontal movement across the screen and Z represents distance from viewer), is clipped against the left edge of the screen (which is a line that starts from origo and extends diagonally towards the distant left), and then the right edge of the screen. In my BASIC program these edge-lines are presented as ad-hoc constants that sort of work because BASIC itself does clipping on the rendered lines, but there is a real formula that can be used to calculate a precise line along the edge of the field of the vision. I just didn’t know it when I made this video. Both intersections produce a coordinate (X,Z) as a response. Then a set of comparisons is used to select which ends of the original line (wall) are replaced with each intersection point (if any). A two-dimensional line can be represented with an equation in the form of a*x + b*y + c = 0, for some constants a, b, and c. The result of a*x + b*y + c will be zero for all points (x,y) that belong to the line, and nonzero for points that are not along the line. You can see an example of a line represented in that format at: www.wolframalpha.com/input/?i=5*x%2B4*y%2B6%3D0 Calculating the intersection between two lines is a matter of solving an equation pair for (x,y): a₁*x + b₁*y + c₁ = 0 a₂*x + b₂*y + c₂ = 0 The result is the point where the two lines intersect. What the Intersection function in my BASIC code is these two tasks in one: Converting a line from (x₁,y₁,x₂,y₂) representation into (a,b,c) representation, and solving the equation pair. How exactly it accomplishes that with just two divisions is magic, but once you have the lines in (a,b,c) format, doing the rest of the solution is trivial.
That does make more sense than the BASIC code makes it appear, and explains the static variables being passed. I suppose I spent most of my time trying to figure out the math 'magic' part behind the cross product and determinants without any luck. I really don't understand that part at all and it might as well be sorcery. But, if it works then it works. I'll try implementing your examples in my engine.
Basically you are applying multiple matrix multiplications to transform vertices onto the rendering surface (screen, framebuffer, texture, ...). So first you need to transform moving models (monsters and their body parts) into world coordinates. Then you need to tranform world into view coordinates (relative to camera) so camera is at zero looking into (not sure if negative or positive) z direction. And last step is to "project" world into the cube with x and y coordinates from -1 (left, bottom) to 1 (right, top) and depth from (not sure) from 0 to 1 with anything outside of this range ommited. OpenGL then uses that kind of coordinates to render scene into the screen. Note that some effects might be computed in world coordinates (shading) or camera coordinates (shading if light has fixed position relative to camera, fog at distance, ...)
I beleive the little mario is Terminate and Stay Resident trick that overwrites the top line of the screen at periodic interval. He also has temperature and time-of-day.
A whim perhaps. At some early point in the process of converting the BASIC prototype into C, I decided to try how far I could get writing bare C before it would hurt, and then I spent time trying to mitigate this hurt as much as possible. So there we have realloc, free and #define . Those are the biggest hurts I think. At least I could have my variable-length array thanks to C99.
Superddrdan The QuickBASIC version has pretty much the same features as the version I created in this video, albeit with some bugs remaining that I later had fixed in the C version. You can download it from the webpage linked to in the video description.
can anyone explain why opengl and other rendering machines prefer revolving the world around the camera instead of having the camera move and render dynamically?
When the renderer can assume that the camera is at (0,0,0) and looking towards (0,0,1), many equations become simpler and a lot faster. In general, anything variable that is either zero or one makes equations easier.
Bisqwit ooh i see :D thanks. while I'm at it, I'm kinda weirded out by how this engine requires portals/windows for non-convex sectors but at the same time a z-buffer can be calculated. why doesn't using the distance to determine the render order work well in every instance? and one more question in case you have the time: do you think the hardware in -93 could have handled a more complex engine with overlapping sectors (many floors etc) and a fully rotating camera? I wonder if making Doom, like it turned out to be, was primarily a design choice, a necessity because of computing power or just laziness. :D Ja salakielellä: nää sun videot on superkiinnostavia vaikken koodata osaakaan. :) toivotan menestystä kanavalle jatkossakin!
Z-buffer could be used just fine for determining the render order, yes. However, there are -three- five problems with Z-buffer: ⁃ It requires memory. This is not so much a problem today as it was in the 1990s when e.g. Descent and Duke Nukem 3D were developed. Still, any extra memory accesses mean that fewer things will fit in the CPU cache. ⁃ You must actually calculate a Z value (depth) for each pixel. This may be inconvenient and a performance issue, if the only reason you must calculate it is for the Z-buffer. ⁃ You must choose on a precision for the Z-buffer. Choose too high precision, and your code is slower and uses more memory than necessary. Choose too low precision, and you may get visual artifacts, where two surfaces cannot quite agree which one is in front of the other. You can actually see this problem quite prominently in my OpenGL programming example, the truecolor demo video at ruclips.net/video/1JiDsG3EdK8/видео.html. ⁃ Z-buffer only solves the rendering order problem. It does not reduce the rendering _workload_. If your scene is large and contains ‹many› polygons, you must still render each and every one of them. Only when rendering individual pixels, you make the decision whether to draw it or not. On the other hand: portal rendering, depth culling and BSP help you decide which polygons are totally not worth the effort at all, potentially reducing the workload dramatically, thereby making the renderer faster and improving the framerate. ⁃ Z-buffer only works with euclidean geometry. Anything more complicated, such as Portal-style portals, self-intersecting shapes like the Klein bottle or the very scene featured in this video, require more sophisticated approaches. Z-buffer can still be used as the final step. As for hardware performance, well there’s Descent, but it came out in 1995, not 1993. Minimum requirements for Descent were 386-33, 4 MB RAM (though 486 or Pentium was recommended). The first Pentium processors were introduced in 1993. So, yes, true 3D with no camera/topology restrictions was possible in 1993. Descent used portal rendering, by the way. Descent also used true 3D models for NPCs, instead of sprites like Doom. It also used sprites, though. I don’t know how Descent did the rendering order / hidden surface removal for the 3D models; whether it used small-scale Z-buffer or what. Kiitoksia :-) Kirjoita vapaasti milloin vaan tulee kysyttävää mieleen.
This is amazing. Very well put through, well paced, insightful, well illustrated.. I could honestly go on praising this for a while. Great, great job and a definite subscribe from me.
To demystify some of the black magic you speak of for vector cross product. It's easier to think of it like this: A cross of two vectors will produce a third vector perpendicular (or normal) to the two vectors. That is, the tails will be on the same point. The reason it works can be better described with a matrix. Imagine a 3x3 matrix instead, "a cross b" will produce a 3x3 determinant "[i, j, k] [a.x, a.y, a.z] [b.x, b.y, b.z]". Note now how "a cross b" is equivalent to "-b cross a"
That is, it's anti-communtative. So, assuming a and b are in the XZ plane, the direction of the cross-product vector will point upwards if the rotation from a to b is counter clockwise, and downards if the rotation is clockwise. This more or less achieves the "effect" of always returning perpendicular vector (or normal) to the plane defined by both vectors.
Dale Weiler Also note that the length of the cross product vector is proportional to a and b, as well as the sine of the angle between the two vectors |v| = |a| | b| * sin(angle), where angle is the angle between the two vectors. In the video, there is mention of using the cross product to compute the angle between two vectors, well that is how that works (it's called an inner product)
I know this is late, but thanks for your tutorial! I have recreated the prototype (QBasic) version in Python, and is avaliable here: gist.github.com/limdingwen/c8bb49474de7765f92ee198a1f0f31d5 I've replaced the "black magic" intersection code with a function defined as intersect(x1, y1, x2, y2), which returns the x value where the line (infinitely long) intersects the x-axis. Basically, if only one point is behind the player, instead of rendering the lines to the point behind the player, the engine only renders to the point (intersecting x, 0.001). Kinda faster, and doesn't have much black magic in it.
So i'm confused about one thing in this code. I'm trying to create my own Doom game engine. What I don't understand is what the value of 16 represents in his code when calculating the perspective transformed x co-ordinates. I see you don't actually multiply by anything in your code. From changing the value around in my own code I can see that it has something to do with the wall width maybe? or field of view? I would just like some clarification please,
I only found the number 16 in this context in the BASIC code (that does not do anything Doom-related, it just illustrates perspective projection), so I assume you are talking about these lines of code: x1 = -tx1 * 16 / tz1: y1a = -50 / tz1: y1b = 50 / tz1 x2 = -tx2 * 16 / tz2: y2a = -50 / tz2: y2b = 50 / tz2 These numbers 16 and 50 just control the field of vision (or really, the zoom). They are pretty much arbitrary, chosen by feel-good. There is actual mathematics how it should/could be done, but this was just a simple illustration.
This is the cool stuff that made me want to be a programmer. Then I found out most programmers outside of silicon valley just work on outdated boring business platforms all day and I lost interest.
Thank you so much for this video! Finally I found something about creating BUILD-like engine! Neither eDuke32, nor Chocolate Duke (though Chocolate cleaned up some of initial BUILD mess, original build.c takes more than 10,000 lines of code) source codes couldn't give me comprehensible code to compile in my mind. Portal-based engine has so much capabilities that I do not want to miss: big, complex, yes fast and lightweight levels; real-time level creation; dynamic geometry; ability to create illusions of "pocket dimensions" - big rooms in small buildings. Yet, there are very interesting challenges that I want to personally deal with: proper up-down looking and (more importantly) "real" room-over-room. Floor portals were used earlier but still hadn't proper rendering of the next sector. I will look into your code for sure. Please, sir, do not abandon this one. Too much capabilities. Would be glad if someday you will find some time to show your progress on this engine. Putting it on C++ rails could take it even further: walls, sectors and portals as objects and such... Thank you again and good luck in your endeavors! Greets from Russia, neighbour!
I'm not sure if you were joking or not but the cross product of two vectors give you a vector that is orthogonal to both of the vectors that were crossed. Meaning the output vector is at a 90 degree angle from the first vector and also a 90 degree angle from the second vector.
+Masterchief461 Yes I did. It is Hebrew language, and most often translated as "peace", but it is a wishing of general well-being, including health. I find this greeting more meaningful than "good day" or "morning" or "hello", and I often use it in real life too. You can find more information about the word here: www.therefinersfire.org/meaning_of_shalom.htm
For other humbled programmers that just watched this: "I designed and tested this program very carefully for days before I began recording the video, in order to avoid consuming video-time in debugging and in other uninteresting activities. Within the video, I just retrace the steps of creating the program in a natural and a structural manner, with mistakes mostly removed." - From a FAQ on one of his other videos. I'm still impressed, but I feel a lot less terrible about my own coding speed now. ;)
+Alexander Johnson And for more information: ruclips.net/video/5Da6ZyQJjE0/видео.html
Watching such code being easily written, I began to seriously doubt my competency in coding. Then I scroll down and felt so satisfied it was just rebuilding the code.
for those who still feel inadequate: try recording your own work (1fps, then speed up to 60 in post to create reasonably long timelapse) and watch it, you'll be surprised how cool and pro it looks and feels to watch, even if it didn't feel that way while working ;)
If you code in notepad++ (.... :P if...) you can also just hold down ctrl+z or ctrl+y to scroll through time. Watching a canned refactor be undone is :_(
@Jason Sun
That's a good thing, coding is not really a natural talent so you can keep improving. I'm mostly a cut and paste coder because 1: I don't get paid enough and 2: I don't get paid enough. So don't feel too bad lol.
Bisqwit : Hello how's life?
Me : It's absolutlry terii-
Bisqwit : ah thats nice
were you going to say "terrific" or "terrible"?
"terr-"
ah, that's great
Nice
@@Zmunk19 Terra nova et caelum a Deo
Thats what we call, HardCoded. Ok ok, what they :'(
This channel is a very non-traditional channel and I have never seen anything quite like this. I like it. ;)
I Always wanted to learn to program things from scratch, but, usually, people say you shouldn't reinvente the wheel.
+Rodrigo Appendino And they are right. But it may still make sense for several reasons: 1) Learning how to do things. 2) It is fun. 3) Existing libraries or programs might not be applicable to your needs, or are too difficult to find.
Good content.
@@rodrigoappendino you don't always have to reinvent the wheel, but it's definitely not bad to do so.
By creating an engine from scratch you can make it to satisfy your requirements and add the optimization techniques to the project that you want.
You can decide which utilities functions you want and performance depends on your code not on the developers of the game engine. The application weigh is less than with a game engine like unity or unreal.
Also most of the big companies do their own game engine, so if you want to work professionally as an hired developer you most likely will need some knowledge of frameworks/api
This level of programing is just magic to me but I absolutely love seeing someone understanding it and being so passionate about it.
Hrnek Bezucha I watch him cause of his accent
Interesting, where do you watch him?
It is not very advanced, the impressive thing is how fast he is writing the code. We had to create our own fully 3d engine as a collage course with global illumination, interactive objects and stuff like that. The mathematics around lighting the scene and the camera work, calculating world/camera cooridantes was the worst thing for me.
But if you find this magic, you should check some codes from modern engines like cryengine 3, what they are doing is more of a magic.
Cry engine is real OpenGL Real 3D renderer. In this video he shows is not real 3D engine. It is a top down shooter 2D engine with 3D illusion.
je3f0o - yeah, i know. It is something different then this. But if you really want to see the bleeding edge stuff that is really amazing technology-wise today, that is the way to go. I understand this, but most of things in the modern engines are beyond my understanding.
you are like the bob ross of programing.
Happy little lines of code!
nice job copying from the above comment
we are going to add a little friend to that line of code...
Because everyone needs a friend
Oh a bug! Beat the devil of it tac...tac...tac
@@batcavemastersThose are not bugs, just happy little features.
+Bisqwit: 15:30ish. Dude... dude... no one has EVER explained so perfectly why learning formulas in math class is important. Especially with Google these days it's NOT the formula itself. It's having done it so that when you run across a problem in the future you have the experience that says "ah-ha! It might be _______" because you remember it from class. Equally important though I think is teachers giving real life examples like this video which, from my experience, they don't do often. I keep getting more and more in love with these videos, you really do a fantastic job!
Most "a-ha" moments in life come from self-study, especially in math. What you experienced was the small intersection where a great educator meets a ready student.
That's most of school tbh. Having a frame of reference, or more importantly - a purpose, is paramount. I was awful at programming ins school becasue we were writing programs which didn't really do anything, and the ones that did were boring shit for accounting and stuff. Fast forward 20 years and I program my own games now and I know six languages due to it.
Because I know exactly what I want to happen on screen now and have a passion to see it happen it's given me a focal point and a thought process which will make me learn much faster.
It just goes to show how very important proper motivation is in life, yet most people are stuck in jobs they hate and will just be producing the bare minimum to get by.
youre right but only if its a natural occurrence. Because this is a personal interest it doesnt count. Youre pretty much looking to do these equations
A reminder to everyone! If you post questions as comments to my RUclips videos, please make sure you have permitted replies to your comments. Otherwise you'll be an equine. The relevant privacy option is probably found somewhere within the Google+-RUclips frankenstein abomination monster.
Bisqwit Are you sure you mean equine? That refers to horses, which are relatively intelligent animals. If you're going for an animal pejorative, I would go with sheep. Sheep are extraordinarily stupid.
Great video! I'm always interested in how the underbelly of computer game engines and other low level stuff (low level for me is C, I've never ventured into assembly)
Michael Hoffman With "equine" I was referring to "donkey", or "ass" as it's better known in the context of that expression. However, I was aiming for humor, with the simultaneous goal of avoiding vulgar language, considering that the word has another meaning too. While I don't know which of the meanings is the original etymology for that expression, I know it translates literally very well to Finnish using the animal's name, which seems to lend credibility to the theory that the word used in that expression in English indeed refers to the animal. As for sheep, in terms of metaphors, they are commonly associated with flock behavior and innocence. These associations hardly fit in my original post.
Bisqwit Do you mind sending me a download link to your editor? I haven't been able to find it on your website.
shotgunanarms Try reading the video description.
Bisqwit Thx I got it to work, but I am wondering how to get it to know where 3rd party library header files and lib files are located? I am using MinGw g++
I learned quite recently that Ken Silverman wrote the original Build engine when he was just 17/18 years old! I find that to be amazing, I actually screamed "WHAT" when I found out. As a nineteen year old who started when he was 17, I cannot help but to feel jealous, yet inspired, of his talent.
I totally agree. There's always someone smarter. Lots of people I look upward to.
Start young and have access to the right resources.
Look who is saying it
Wow I can't believe hopson replied to this vid XD moreover no "fan boys" are going crazy over it XD
Seems like you broke the trend.
I found this to be immensely humorous and informative. I could watch videos like this all day long. Keep it up.
"I have no idea why it works, but it does and that's the important thing."
"It seems to work right until it doesn't work right."
Perfect.
Sums up my university life
Every 60 seconds in Africa a minute passes.
It's funny how that guy recreated the evolution of almost 30 years of graphic programming and 3D rendering in one video.
@BOOZE & METAL Not to mention that certain critical algorithms were implemented in assembly! Quite impressive work.
@BOOZE & METAL I suppose so, but assembly is still quite romanticised for me, no matter how simple I realise it really is.
@G E T R E K T 905 The language itself might be easy, but it is much harder to create something meaningful in it. You have to do an insane amount of bookkeeping. If you program in higher-level languages, the compiler does most of that work for you.
So when people are saying that assembly is hard, they don't necessarily mean that the language itself is hard to understand; just that it is hard to code in it
so this black magic is just linear algebra right?
Found the mathematician.
Not exactly it is proyective geometry, but proyective geometry can be linearized if you use.the concept of homogeneous coordinates.
First, it's proJective, with a J, not a y ffs. Secondly, yes, its's linear algebra, without any qualifiers.
@@isodoublet Seeing it spelled like that made me cringe so hard. You're right. It's all based on concepts you would find in any decent linear algebra course.
@Manek Iridius do you have nothing better to do than yell at people smarter than you?
The above all, hands down, most important skill you should learn is the ability to identify the type of answer you are looking for, and then look for it" -Bisqwit
I like that quote, it's going to help me man. I've watched this video like 10 times now. I love the music and style of the video, it's perfect.
"The mix, that made a better batter, better"
-Bisquick
Yeah, I've also watched it many times; I love this video.
the next most important skill is googling, so useful when you are like me and know nothing.
Seriously. This is a quote to live by.
This guy sounds like a text to speech.
Too much talmud.
All TTS is based on the Finnish, true fact
all programming, of anything is based off the Finnish... But I think TTS may be doing the language teaching there... :P (Explains why programming is so natural.)
That's the sound of pure intelligence.
The Speech of Alpha Programmer.
I love this video. The tone, the subject I love everything about it!
Ok I have to say this. Even now, after 3 years this video is so amazing to me. You littelary made whole freakin' 3D graphics generation engine. Just big wow...
Bisqwit: makes 3D Game Engine in 15 minutes
Me: finds out how to make cube move after 3 hours
Me: finds how to print hello world after 24 hours
@@may21136 Me: *finds out how to link the correct compiler in a fortnight*
he didnt make it in 15 mins he commented that he has retraced his steps in the recording.
It takes me month before making the cube move.
In later video he told that it took him years to research about this engine.
"It seems to work alright until... well, it doesn't work right."
Love it.
Just discovered this channel a couple hours ago and already my favorite channel! Stay awesome.
Dr. in Educational Science here. Great didactical skills shown. Thumbs up + subbed.
Thank you!
15:35 for example, is pedagogical gold :-)
You are getting so many brownie points from me just for all of the stuff you include in your Downloads page. I've got your sample code, the completed version, the text editor you used, the textures including their source and the license they were published under. I'm having such an easy time following along with this. You're getting a sub, sir.
and by the way your accent is amazing.
You're not the dull one, Bisqwit. It's been very long since I've come across someone with such clear and concise thought. Thank you for this, and all other demonstrations you so religiously upload.
This video is excellent - I have been looking for a video tutorial of a raycaster-style engine which is very detailed. I found your narration style to be excellent as well, so please don't apologize for that. Thankyou for sharing your knowledge!
מה הסיכויים שאני סתם מאמצע שום מקום אמצע ערוץ כזה טוב בעברית שמכין סרטונים מעולים? אתה מלך אחי, כל הכבוד
sagiksp 👌
חשבתי שאני הישראלי היחיד שתסופה ב bisqwit
This video has been a true inspiration. It reminds me that I can make anything from scratch. No programming task is too complicated if you have the time and motivation. I have returned to this video many times over the years. Eventually I wrote my own engine of this type along with a level editor.
Thank you so much for the subtitles! It really means a lot!
I cannot thank you enough for this video. Before this video, 3D graphics programming was a mystery to me. I'd read tutorials and followed them, but I didn't fully understand what was going on. After this video, things magically "clicked" and I suddenly understood everything. Now I don't need tutorials.
Again, thank you for this awesome video.
whats funny is its the opposite for me, watching these videos of him typing the code so fast kinda overwhelms me and I still dont get 3D but its still entertaining to watch it develop over time.
only difference is I also dont get other videos
For those using QB64:
DECLARE SUB Intersect (x1!, y1!, x2!, y2!, x3!, y3!, x4!, y4!, x!, y!)
DECLARE FUNCTION FNcross (x1, y1, x2, y2)
' Vertex rotation test
SCREEN 7
' The end coordinates for the line segment representing a "wall"
vx1 = 70: vy1 = 20
vx2 = 70: vy2 = 70
' Coordinates of the player
px = 50
py = 50
angle = 0
DO
' Draw the absolute map
VIEW (4, 40)-(103, 149), 0, 1
LINE (px, py)-(px + 5 * COS(angle), py + 5 * SIN(angle)), 8
LINE (vx1, vy1)-(vx2, vy2), 14
PSET (px, py), 15
' Draw the transformed map
VIEW (109, 40)-(208, 149), 0, 2
' Transform the vertexes relative to the player
tx1 = vx1 - px: ty1 = vy1 - py
tx2 = vx2 - px: ty2 = vy2 - py
' Rotate them around the player's view
tz1 = tx1 * COS(angle) + ty1 * SIN(angle)
tz2 = tx2 * COS(angle) + ty2 * SIN(angle)
tx1 = tx1 * SIN(angle) - ty1 * COS(angle)
tx2 = tx2 * SIN(angle) - ty2 * COS(angle)
LINE (50 - tx1, 50 - tz1)-(50 - tx2, 50 - tz2), 14
LINE (50, 50)-(50, 45), 8
PSET (50, 50), 15
' Draw the perspective-transformed map
VIEW (214, 40)-(315, 149), 0, 3
IF tz1 > 0 OR tz2 > 0 THEN
' If tz1 is behind viewer, scale: e = (b-a)*(f-d)/(c-a)+d with b=0.1, a=tz1, c=tz2, d=tx1, f=tx2
CALL Intersect(tx1, tz1, tx2, tz2, -.0001, .0001, -20, 5, ix1, iz1)
CALL Intersect(tx1, tz1, tx2, tz2, .0001, .0001, 20, 5, ix2, iz2)
IF tz1 0 THEN tx1 = ix1: tz1 = iz1 ELSE tx1 = ix2: tz1 = iz2
IF tz2 0 THEN tx2 = ix1: tz2 = iz1 ELSE tx2 = ix2: tz2 = iz2
x1 = -tx1 * 16 / tz1: y1a = -50 / tz1: y1b = 50 / tz1
x2 = -tx2 * 16 / tz2: y2a = -50 / tz2: y2b = 50 / tz2
FOR x = x1 TO x2
ya = y1a + (X - x1) * CLNG(y2a - y1a) / (x2 - x1)
yb = y1b + (X - x1) * CLNG(y2b - y1b) / (x2 - x1)
LINE (50 + x, 0) - (50 + x, 50 + -ya), 8
LINE (50 + x, 50 + yb) - (50 + x, 140), 1
LINE (50 + x, 50 + ya) - (50 + x, 50 + yb), 14
NEXT
LINE (50 + x1, 50 + y1a) - (50 + x1, 50 + y1b), 6
LINE (50 + x2, 50 + y2a) - (50 + x2, 50 + y2b), 6
END IF
inputs:
' Wait for screen refresh and swap page
SCREEN , , page%, 1 - page%: page% = 1 - page%
WAIT &H3DA, &H8, &H8: WAIT &H3DA, &H8
SELECT CASE INP(&H60)
CASE &H48: up = 1: CASE &HC8: up = 0
CASE &H50: up = -1: CASE &HD8: up = 0
CASE &H4B: lt = 1: CASE &HCB: lt = 0
CASE &H4D: lt = -1: CASE &HCD: lt = 0
CASE &H1E: st = 1: CASE &H9E: st = 0
CASE &H20: st = -1: CASE &HA0: st = 0
END SELECT
SELECT CASE INKEY$
'CASE CHR$(0) + "H": px = px + COS(angle): py = py + SIN(angle)
'CASE CHR$(0) + "P": px = px - COS(angle): py = py - SIN(angle)
'CASE CHR$(0) + "K": angle = angle - .1
'CASE CHR$(0) + "M": angle = angle + .1
'CASE "a", "A": px = px + SIN(angle): py = py - COS(angle)
'CASE "d", "D": px = px - SIN(angle): py = py + COS(angle)
CASE "q", "Q", CHR$(27): EXIT DO
END SELECT
IF up THEN px = px + up * COS(angle) * .3: py = py + up * SIN(angle) * .3
angle = angle - .02 * lt
IF st THEN px = px + st * SIN(angle) * .3: py = py - st * COS(angle) * .3
LOOP
SCREEN 0, 1, 0, 0: WIDTH 80, 25
FUNCTION FNcross (x1, y1, x2, y2)
FNCROSS = X1 * Y2 - Y1 * X2
END FUNCTION
SUB Intersect (x1, y1, x2, y2, x3, y3, x4, y4, x, y)
X = FNcross(X1, Y1, X2, Y2)
Y = FNcross(X3, Y3, X4, Y4)
det = FNcross(X1 - X2, Y1 - Y2, X3 - X4, Y3 - Y4)
X = FNcross(X, X1 - X2, Y, X3 - X4) / det
Y = FNcross(X, Y1 - Y2, Y, Y3 - Y4) / det
END SUB
Thanks, hero! :)
I need help
I copy and pasted this into a text file named "3dgame.exe" but when I press enter nothing happens :(
Kappa
The above code is for qb64 and work in qb too..download qb64,copy the code,and paste,press f5 to run..you will get the output and .exe file where the source file is saved..writing directly to .exe file doesnt work either
Hey, I tried this code in QB64 but it gives a lot of Illegal function call errors, can you help?
Tremor244 yes i tried..give me the line number at where the error occured.
Great explanation of what everything does.
There are many cool books about DOOM technology and your video combine all of them.
Keep going!
Hopkroft Thanks! I forgot to explain in my video why exactly Doom requires the slow and expensive process of building a BSP though :) I only explained why Duke3D doesn't need it. Good thing there are books, after all.
Bisqwit Now that you mention it, I just realized too. Follow-up video? (I was hoping to hear of an answer, thanks!)
Bisqwit , maybe it's just a coincidence, but Ken Silverman started with Basic, like you :)
I hope that you will create another videos with explaining of technical detail.
P.S. Are you working as QA or developer?
Hopkroft No, I am a coach driver nowadays. I do some (much more mundane) programming irregularly as a side job though.
Arlo Jeremy Try this article: doom.wikia.com/wiki/Doom_rendering_engine#Node_building Or this: en.wikipedia.org/wiki/Binary_space_partitioning
I haven't studied the BSP technique, so I would have to read these first (and potentially much more) in order to say much about it.
This is really amazing. I love how simple you made it and actually explained from scratch.
I wrote my first code when I was 6. It was in Sinclair Basic. I tried BBC Basic after that. This is what made me scared of being a programmer for many years, presuming I couldn't do it. Now I have been coding for 20 years, I know 10 different languages including scripting and cheekily adding HTML as a markup, because it follows the same principles of what I believe is coding. BUT....
I see basic code, and I still run a mile. That is some horrific code to remember. The logic is so progressively linear that it's either genius to your mind, or makes no real sense. Thank the lord for modern languages.
Cannot belive you just say that! Basic is basic and it is the only very easy language I want to use even now! I have a bunch of modern versions of basic one even for making 3D videogames!
This is one of the few videos I have found on such a subject to be straight to the point and properly broken down into steps. It's a quick reference and very effective. Great job! As for the description about your English, I think it is perfectly decipherable and your written English is excellent too.
This is kind of amazing. Its sad there is no youtubers i saw out their like you that go though and show how things like this are done.
I do in my channel! :-)
Hands down one of the most informative videos on programming on youtube, period!
Thank you for the kind words!
Last year I told myself "One day I will understand how to do this myself"...
I still don't.
I have failed as a teacher.
Did you compile, run and play around with the code? I believe that helps!
Honestly if you're going to build a 3D engine yourself you might as well go true 3D, this stuff is fun and all but it's way too restrictive by current standards.
I've been trying to understand programming language for 4 years now but still no luck..
Check javidx9 out, he's got a great tutorial on getting started with "3D".
This is an inspiring production... In-depth, humorous, informative, made with a high regard for quality. It seriously motivates me to improve on both my computer graphics and C programming skills.
"Creating a Doom Styled Game in C" *goes on to reference or use duke nukem as an example*
Duke Nukem's 3D engine on PC was the same, that's why it looked a bit warped when if you look up in it, and that's also why DOOM's view was locked from looking up and down. (This rendering method was changed in the console versions, as they were too slow to software render it, and the hardware rendered mode was better 3D anyway.)
@Axion Yeah, I already mentioned it. Read the last sentence.
@@silentfilms7459 I meant to say virtually the same, both use pixel-based 2D rendering to achieve their effects
tbh, he first showed the method of the rendering engine by making a sudo raytracing engine, like wolfenstein, but then makes what doom uses
@@silentfilms7459 what algorithm did duke nukem 3d use
Hello. I don't know anything about programming, but I really enjoyed this video. Watching the code, reading the comment, listening to the explanations and watching the engine become more and more complex was very interesting. I don't have any problem with your accent, probably because I'm French and we also have horrible accents when we speak english !
Thank you for your interesting channel :)
+Leeematty RS Oh crap. You posted a good question, and I wrote you a long and insightful answer, but then after the fifteenth edit, I accidentally chose "Remove this comment" instead of "Edit". Both your post and my reply are gone. And RUclips did _not_ offer me the undo option that it usually does when you accidentally click "Remove". I am very sorry. I _hope_ my reply is at least visible on your Google+ page, but I can't view it for whatever possible privacy related reason. And I can't even tag your name into this post, why is that I can't even fathom.
Bisqwit Don't worry, I can still see the Question and Answer (I'll post it below in case you want to see it again)
Thanks for the great answer, I somehow completely forgot about global variables :s Also, I have always been told that making anything "static" is bad practice, but I don't know if that is the case always
back to my question: the function changes/ adds data to the global variables from the structs. Makes sense, thanks :)
also
I got my question here:
One thing I been trying to understand for a while is how your LoadData()
function works, because it is a void function and so returns no value;
and also does not take in any data. Surely the data loaded is lost at
the scope of said function? Or perhaps is there something I am missing
here, maybe because it is "static void" (which I have no idea what that
does)?
I got your answer here:
It reads a disk file, and stores the data read from that file into
global variables (sectors, player, and also NumSectors). Global
variables are accessible to all functions and scopes without having to
be passed as parameters or returned as return values.
In "static void LoadData(void)", "static" means that the function is not
visible to possible other compilation units linked into the same
program (does not matter for this program, but it is a wise habit to do
always unless you specifically intend the function or global variable to
be used from another module); the first "void" means that it returns no
value, and the second void means that it takes no parameters.
In C++, you could omit the second void, but in C, if you do, it means
the parameters list is undeclared, not that it's empty. In C, you could
also omit the first void, but if you do, implicit "int" return value
type will be assumed. In C++, it is not possible to omit the return
value type for non-lambda functions.
This declaration:
static struct sector
{
float floor, ceil;
struct xy { float x,y; } *vertex; // Each vertex has an x and y
coordinate
signed char *neighbors; // Each edge may have a
corresponding neighboring sector
unsigned npoints; // How many vertexes there are
} *sectors = NULL;
Contains the following elements: static *sectors =
NULL; declares "sectors" as a variable of type pointer to zero or more
, preinitializes the variable as NULL, and limits the
visibility of that variable's name into the current module.
Then furthermore, that is defined as "struct sector".
Lastly, what the "struct sector" means, is defined immediately in that
very same statement, with the parts between "{" and "}".
This struct is a collection of the following members: "floor" and
"ceil", which are of "float" type. It will contain "vertex", which is of
type pointer to zero or more "struct xy". And what "struct xy" means is
immediately defined as well. "neighbors" is of type pointer to zero or
more "signed char". "npoints" is of type "unsigned", or "unsigned int".
Leeematty RS Thank you! Also, there are two or more meanings to the word "static" in both C and C++.
In global scope, it has the meaning that the visibility of the symbol (whether a variable or a function) is limited to the current compilation unit. This is a good and meritable thing. It reduces namespace pollution and makes linking faster. This applies also to C++, even though most people recommend using anonymous namespaces instead for an equivalent effect. While anonymous namespaces are the _only_ way to achieve this effect for struct methods (for which "static" has a different meaning, explained below), I don't always use them, because I like to indent namespace content, and too many indentation levels makes the code more unreadable. Breaking in/out from the anonymous namespace for the occasional function or variable that you _do_ want to export is also a contributing factor to unsightful code.
In function scope (between of { and }), it has the meaning that the variable acts like a global variable, but its visibility (scope) is limited not only to the current compilation unit, but into the function it's declared in. This means it preserves its value across calls, and storage for it remains allocated throughout the entire program. This feature should only be used when other solutions would be significantly more cumbersome. This applies also to C++. Additionally in C++, having a function-static variable and initializing it with a non-constant initializer (which is not permitted in C) will incur the creation of an invisible static "guard" variable that ensures the variable is only initialized once, yet another reason to avoid this syntax.
A third, less commonly known use of the "static" word in C looks like this: void foo(char s[static 100]); In this context, it means that foo's parameter "s", of type char*, must point to an array of at least 100 chars. I have never seen this practise in "the wild", and can't say anything about whether it's recommended or not. This feature doesn't exist in C++.
In C++, variable-type members of structs, unions and classes can also be declared "static". It basically creates a global variable that is restricted into that struct's scope, and has no associated instance. With a const/constexpr attribute, it is a good thing with many honorable uses. With non-const variables, the benefits compared to making it a possibly-namespaced global are significantly fewer. This is not possible in C, because C lacks the scope resolution operator "::" and its semantics for struct scopes are more limited.
In C++, function-type members (i.e. methods) of structs, union and classes can be declared "static" as well. It basically creates a global function that is restricted into the struct's scope, and does not take the hidden "this" parameter pointing to an instance. There are many good uses for this kind of "static" functions. It does not make linking faster, but it does reduce namespace pollution. These functions also get access to "private" members of the class, which global functions do not. This is not possible in C, because methods don't exist in C in the first place.
Bisqwit You are a very good at explaining things, thank you for detailed answers!
sometimes I am just really happy to be alive and find out that people like you exist. I love being amazed by what other people can do. thank you and happy new year.
Thank you for those kind words!
have you done anything with vulkan yet?
you crazy finn! Damn, you are good.
I believe you meant god ;)
One of the best raycasting video tutorials out there. It's hard to find any that don't just show you something and not explain what's going on. Keep up the great work!
It is not raycasting, though.
Bisqwit Fine lol, pseudo-3D. Sorry, I'm a newbie on the subject...
Raycasting is a technique where you basically shoot bullets and see what they hit, and render a tiny column of a wall with the smaller height the farther the bullet went before hitting something. The technique described in this video is completely different.
They both can be used for pseudo-3D scenes.
Interesting. Thanks for explaining.
Is there a name for the technique shown in the video?
"File Edit View Search Run *Bogus Menu Bar*"
LOL
Because it's not a real menu bar. It is just a QuickBASIC masquerade over his own editor.
No kidding.
Look at Mario running on top of your code though xD
This video is SO GOOD. I've been a developer for a long time, and I've never seen a coding video as good as this one! Nice work.
Very, very well presented and interesting video. I always had my little thoughts of how DooM/Quake/Half-Life/etc... rendered the game world but never really looked into it and I think this video explained the methods used in such engines very clearly. It's also interesting to see that what kind of problems people like John Carmack had to overcome while writing the first 3D renderers like this.
Amazing video as usual. As a big fan of games like Doom and Duke3D I found it interesting and fascinating. Your explanation was also awesome! :)
+Vittorio Romeo amazing video! Well, now I"m subscribing both of you :) thanks for videos!
My brain hurts
Only from how he type the word, its like he fast forward the video :(
@z3 it wasnt exactly made to be "easier" than assembly, rather, it was made to be cross-platform. Allowing the same code to work on an entirely different system.
3:19 I did reconstruct where the vector produkt comes from. First of you start proofing pytharoras then you get to the sinus formula in a triangle which leads you to the cosinus formula in a trinagle which enables you to derive the scalarproduct by adding vector principles and when you complete this stuff you can deduce the vectorproduct. this operation is insane.
by streching the possibilities of the structures you find by just proofing their concepts you get to the very formula of the vector product. it tells you that multiplication of 2 vectors will result in a third vector which is standing on the plane that the first two create. the length of that vector is the size of the route those two vectors create.
The Vectorproduct is only to be deducted in the 3rd dimension other spaces doesn't have such solution.
It is like you start of with one two three and then suddenly a selfware structure emerges.
I have no idea about C code or any other code but I enjoyed watching this strangely.
Please keep doing what you're doing as much as you can. You took a complex and sophisticated topic in programming and game design and boiled it down to its simplest components to make it easier for laymen like me to understand. You are a god, sir.
Glad to hear! Thank you sir.
the map you designed hurts my brain because it could not exist in a real three dimensional physical space
This is my favorite video of yours. I always come back to this video to improve my own programming skills.
But can you program Hello World?
Really cool, I've never ever attempted to build my own engine, instead relying on stuff already made like unity or löve but watching do this so apparently seamlessly and easily made me wanna try my hand at it, granted, as people in the comments pointed out you didn't just do this on your first go, instead going through the necessary steps and practicing before doing the final video but despite knowing I will fail miserably on the first couple of tries I wanna try doing this and seeing how it goes
Where have you been all my life?
This is the video that sucked me in to this unique realm of programming couple of years back... haven't left since then.
Hi Bisqwit,
Very nice tutorial! And C language is very good! Please keep doing it! BR, Alan
HUE HUE. jk.
It's been quite a while since i last watched this, and I'm still no better at programming than i was then, but i still absolutely love this video. You do such a fantastic job of demonstrating such a complex system, and made something seemingly mythical seem regarding approachable. Thank you so much for making this!!
I really love how the engine breaks the laws of physics!
Go under the stairs and there's a ceiling which is higher up than the steps of the stairs, but can't be seen from there.
On the upper floor, the floor above the stairs is on the same height as the last step, but it can't be seen from the stairs.
Thanks! Yeah, these things obviously fascinate me as well. And just to show that the engine can cope with _normal_ physics as well I added the windows in the stairs that work perfectly normally as one would expect.
Yeah I didn't quite understand why the stairs open celling turned into a solid floor. Was that an accident or on purpose?
I am not sure what you are asking, but there were no accidents in this video.
This is so brilliant that it deserves a thumbs up!
You managed to program more in 1 video, than i did using xna and c# in 2 years.....
You've got catching up to do! :-)
Bisqwit Yeah, haha. Its my first game related project and i have used it as testing ground too.
+Conex Xenon to be fair, he actually did it over a few months. He has written a robot that will basically write some code for him, as long as he himself has already written it. He tells the robot what order to code stuff in etc.
Plus he has around 22'years of experience;)
24 and counting :)
Bisqwit Damn you :D
you found you a new subscriber. i am a big fan of the handmade movement and fan of the scene for a very long time. also i had very good memories of QBasic too : )
Thank you very much!
can someone please shade some light on the second view transformation equations (@1:37)? It is the view were the player is fixed but the world is moving around him. He also refers to a OpenGL video that explains this but I couldn't find it.
The OpenGL video is here: ruclips.net/video/vkUwT9U1GzA/видео.html
I will wait and see if someone knows a good way to explain the part that you asked about, before I proceed to just reiterate what I said in the video.
I can try. Basically, in terms of rendering, the camera is always the "origin." All the objects in the world have to be positioned, rotated, and transformed relative to the camera, and not the other way around. Once the objects are placed in the world, in this case the wall, it needs to be transformed so it's position is relative to the camera's. So, if your "world space" camera is in position (5, -3), you need to transform the camera's position to (0, 0). Doing that requires you to modify the objects in the world as well. Think of it like making a drawing with all the walls or objects in their 'world' position, then moving the piece of paper until the camera is at the absolute center, which, in turn, moves everything else on the drawing with it.
Then, you just need to rotate that world (drawing) to make the camera's angle 0 degrees. Doing that, you just have to take whatever 'world space' angle the camera is at and make it 0. Same as above, that requires you move everything else in the world along with it. You have to know a bit of basic trigonometry (look up 'sine cosine unit circle'), but it's not as hard as it looks.
TL;DR: The camera starts off in an absolute 'world space' area, detached from all other objects. You've got to transform the world so that it moves and revolves around the camera, which is always at the origin. Hopefully that helps a bit.
HalfBurnToast, it calculates the intersection between two lines (not line segments). First the line (the wall, in two-dimensional coordinates where X represents horizontal movement across the screen and Z represents distance from viewer), is clipped against the left edge of the screen (which is a line that starts from origo and extends diagonally towards the distant left), and then the right edge of the screen. In my BASIC program these edge-lines are presented as ad-hoc constants that sort of work because BASIC itself does clipping on the rendered lines, but there is a real formula that can be used to calculate a precise line along the edge of the field of the vision. I just didn’t know it when I made this video. Both intersections produce a coordinate (X,Z) as a response. Then a set of comparisons is used to select which ends of the original line (wall) are replaced with each intersection point (if any).
A two-dimensional line can be represented with an equation in the form of a*x + b*y + c = 0, for some constants a, b, and c. The result of a*x + b*y + c will be zero for all points (x,y) that belong to the line, and nonzero for points that are not along the line. You can see an example of a line represented in that format at: www.wolframalpha.com/input/?i=5*x%2B4*y%2B6%3D0
Calculating the intersection between two lines is a matter of solving an equation pair for (x,y):
a₁*x + b₁*y + c₁ = 0
a₂*x + b₂*y + c₂ = 0
The result is the point where the two lines intersect. What the Intersection function in my BASIC code is these two tasks in one: Converting a line from (x₁,y₁,x₂,y₂) representation into (a,b,c) representation, and solving the equation pair. How exactly it accomplishes that with just two divisions is magic, but once you have the lines in (a,b,c) format, doing the rest of the solution is trivial.
That does make more sense than the BASIC code makes it appear, and explains the static variables being passed. I suppose I spent most of my time trying to figure out the math 'magic' part behind the cross product and determinants without any luck. I really don't understand that part at all and it might as well be sorcery.
But, if it works then it works. I'll try implementing your examples in my engine.
Basically you are applying multiple matrix multiplications to transform vertices onto the rendering surface (screen, framebuffer, texture, ...). So first you need to transform moving models (monsters and their body parts) into world coordinates. Then you need to tranform world into view coordinates (relative to camera) so camera is at zero looking into (not sure if negative or positive) z direction. And last step is to "project" world into the cube with x and y coordinates from -1 (left, bottom) to 1 (right, top) and depth from (not sure) from 0 to 1 with anything outside of this range ommited. OpenGL then uses that kind of coordinates to render scene into the screen.
Note that some effects might be computed in world coordinates (shading) or camera coordinates (shading if light has fixed position relative to camera, fog at distance, ...)
The explanatory clip at 13:40 with the "slowmo" drawing of the environment is genius.
Wow i feel so dumb now...
Joseph Clarke i programed a spam program with many lines codes and he does in seconds a 3d engine with only a few lines
The No Reason Hater I program a soap that avoid feces game for a month.
It’s because your are dumb indeed
@@okktok your
Lol
@@okktok no u
Your english was good enough but thanks for having closed captions. This was a helpful and motivating video
Can't understand anything. But this video is awesome!
I revisit this video occasionally and I understand a little more each time.
Here i am a year later doing the same thing!
do it again
It's insane how much better it looks with the lighting!
You should code a level editor.
I pretty much didn't understand a single thing yet I watched it to the end. Awesome.
Dude, you should create some games, some puzzle games, i dont know.
I've watched this video a thousand times. This is my favorite programming video.
Thank you Bisqwit.
Greetings from Brazil
hue.
What editor is that? I love the little mario!
I believe he wrote it himself, it's designed to work like JOE (Joe's Own Editor) but for DOS.
I beleive the little mario is Terminate and Stay Resident trick that overwrites the top line of the screen at periodic interval. He also has temperature and time-of-day.
Sylvain Pypebros It is not a TSR. The Mario animation is built into the editor.
stands corrected. Thank you for the detail.
(you mimmic'd MS QBasic editor fairly well, then)
You're god-sent, bisqwit. Keep up the amazing work, and thanks for the inspiration each of your videos gives me!
Why you did not use C++?
A whim perhaps. At some early point in the process of converting the BASIC prototype into C, I decided to try how far I could get writing bare C before it would hurt, and then I spent time trying to mitigate this hurt as much as possible. So there we have realloc, free and #define . Those are the biggest hurts I think. At least I could have my variable-length array thanks to C99.
Bisqwit how far did you get with the BASIC prototype? I would love to see that
Superddrdan The QuickBASIC version has pretty much the same features as the version I created in this video, albeit with some bugs remaining that I later had fixed in the C version. You can download it from the webpage linked to in the video description.
And here I am still trying to figure out top/bottom faces in traditional raycasting. You are insane man!
can anyone explain why opengl and other rendering machines prefer revolving the world around the camera instead of having the camera move and render dynamically?
When the renderer can assume that the camera is at (0,0,0) and looking towards (0,0,1), many equations become simpler and a lot faster. In general, anything variable that is either zero or one makes equations easier.
Bisqwit ooh i see :D thanks.
while I'm at it, I'm kinda weirded out by how this engine requires portals/windows for non-convex sectors but at the same time a z-buffer can be calculated. why doesn't using the distance to determine the render order work well in every instance?
and one more question in case you have the time: do you think the hardware in -93 could have handled a more complex engine with overlapping sectors (many floors etc) and a fully rotating camera? I wonder if making Doom, like it turned out to be, was primarily a design choice, a necessity because of computing power or just laziness. :D
Ja salakielellä: nää sun videot on superkiinnostavia vaikken koodata osaakaan. :) toivotan menestystä kanavalle jatkossakin!
Z-buffer could be used just fine for determining the render order, yes.
However, there are -three- five problems with Z-buffer:
⁃ It requires memory. This is not so much a problem today as it was in the 1990s when e.g. Descent and Duke Nukem 3D were developed. Still, any extra memory accesses mean that fewer things will fit in the CPU cache.
⁃ You must actually calculate a Z value (depth) for each pixel. This may be inconvenient and a performance issue, if the only reason you must calculate it is for the Z-buffer.
⁃ You must choose on a precision for the Z-buffer. Choose too high precision, and your code is slower and uses more memory than necessary. Choose too low precision, and you may get visual artifacts, where two surfaces cannot quite agree which one is in front of the other. You can actually see this problem quite prominently in my OpenGL programming example, the truecolor demo video at ruclips.net/video/1JiDsG3EdK8/видео.html.
⁃ Z-buffer only solves the rendering order problem. It does not reduce the rendering _workload_. If your scene is large and contains ‹many› polygons, you must still render each and every one of them. Only when rendering individual pixels, you make the decision whether to draw it or not. On the other hand: portal rendering, depth culling and BSP help you decide which polygons are totally not worth the effort at all, potentially reducing the workload dramatically, thereby making the renderer faster and improving the framerate.
⁃ Z-buffer only works with euclidean geometry. Anything more complicated, such as Portal-style portals, self-intersecting shapes like the Klein bottle or the very scene featured in this video, require more sophisticated approaches. Z-buffer can still be used as the final step.
As for hardware performance, well there’s Descent, but it came out in 1995, not 1993. Minimum requirements for Descent were 386-33, 4 MB RAM (though 486 or Pentium was recommended). The first Pentium processors were introduced in 1993. So, yes, true 3D with no camera/topology restrictions was possible in 1993.
Descent used portal rendering, by the way. Descent also used true 3D models for NPCs, instead of sprites like Doom. It also used sprites, though. I don’t know how Descent did the rendering order / hidden surface removal for the 3D models; whether it used small-scale Z-buffer or what.
Kiitoksia :-) Kirjoita vapaasti milloin vaan tulee kysyttävää mieleen.
I admit I almost skipped this video after hearing your accent, but I'm so glad I didn't. You're a fantastic teacher and an amazingly smart person.
You have a magical voice
This is amazing. Very well put through, well paced, insightful, well illustrated.. I could honestly go on praising this for a while. Great, great job and a definite subscribe from me.
are you 14 or 40?
21, you stu
Both
xDDD
Created range (14, 40).
I'll assume he's 28.
he can be both
To demystify some of the black magic you speak of for vector cross product. It's easier to think of it like this: A cross of two vectors will produce a third vector perpendicular (or normal) to the two vectors. That is, the tails will be on the same point. The reason it works can be better described with a matrix. Imagine a 3x3 matrix instead, "a cross b" will produce a 3x3 determinant "[i, j, k] [a.x, a.y, a.z] [b.x, b.y, b.z]". Note now how "a cross b" is equivalent to "-b cross a"
That is, it's anti-communtative. So, assuming a and b are in the XZ plane, the direction of the cross-product vector will point upwards if the rotation from a to b is counter clockwise, and downards if the rotation is clockwise. This more or less achieves the "effect" of always returning perpendicular vector (or normal) to the plane defined by both vectors.
Dale Weiler Also note that the length of the cross product vector is proportional to a and b, as well as the sine of the angle between the two vectors |v| = |a| | b| * sin(angle), where angle is the angle between the two vectors. In the video, there is mention of using the cross product to compute the angle between two vectors, well that is how that works (it's called an inner product)
teach me your ways fam
What's a fam?
Fam is slang for family.
Or friend
It is very soothing to hear your voice and watch you program.
I know this is late, but thanks for your tutorial! I have recreated the prototype (QBasic) version in Python, and is avaliable here: gist.github.com/limdingwen/c8bb49474de7765f92ee198a1f0f31d5
I've replaced the "black magic" intersection code with a function defined as intersect(x1, y1, x2, y2), which returns the x value where the line (infinitely long) intersects the x-axis. Basically, if only one point is behind the player, instead of rendering the lines to the point behind the player, the engine only renders to the point (intersecting x, 0.001). Kinda faster, and doesn't have much black magic in it.
Cool stuff!
Thank you!
So i'm confused about one thing in this code. I'm trying to create my own Doom game engine. What I don't understand is what the value of 16 represents in his code when calculating the perspective transformed x co-ordinates. I see you don't actually multiply by anything in your code. From changing the value around in my own code I can see that it has something to do with the wall width maybe? or field of view?
I would just like some clarification please,
I only found the number 16 in this context in the BASIC code (that does not do anything Doom-related, it just illustrates perspective projection), so I assume you are talking about these lines of code:
x1 = -tx1 * 16 / tz1: y1a = -50 / tz1: y1b = 50 / tz1
x2 = -tx2 * 16 / tz2: y2a = -50 / tz2: y2b = 50 / tz2
These numbers 16 and 50 just control the field of vision (or really, the zoom). They are pretty much arbitrary, chosen by feel-good. There is actual mathematics how it should/could be done, but this was just a simple illustration.
Wow, I never even noticed the 16 inside his code. Good catch!
Your vids are hypnotic, man. Superb. Subbed.
wesmatron Thank you very much!
Your accent is fun.
It's da sexy Finnish accent aka Rallienglanti
I think this is my favourite video from you so far. I learned a lot from this, and its great to see some C for a change haha.
You are amazing.
Thanks!
You can see how far you've come with puzzles at the beggining. Also, this is cool.
This is the cool stuff that made me want to be a programmer. Then I found out most programmers outside of silicon valley just work on outdated boring business platforms all day and I lost interest.
Yes thats true
then go do gamedev !!
Thank you so much for this video! Finally I found something about creating BUILD-like engine! Neither eDuke32, nor Chocolate Duke (though Chocolate cleaned up some of initial BUILD mess, original build.c takes more than 10,000 lines of code) source codes couldn't give me comprehensible code to compile in my mind. Portal-based engine has so much capabilities that I do not want to miss: big, complex, yes fast and lightweight levels; real-time level creation; dynamic geometry; ability to create illusions of "pocket dimensions" - big rooms in small buildings.
Yet, there are very interesting challenges that I want to personally deal with: proper up-down looking and (more importantly) "real" room-over-room. Floor portals were used earlier but still hadn't proper rendering of the next sector.
I will look into your code for sure.
Please, sir, do not abandon this one. Too much capabilities. Would be glad if someday you will find some time to show your progress on this engine. Putting it on C++ rails could take it even further: walls, sectors and portals as objects and such...
Thank you again and good luck in your endeavors! Greets from Russia, neighbour!
+John Smith Thank you for your feedback! I will definitely get back to this topic soon(ish).
are you a robot?
Aivan mahtava! Vanha video, mutta nyt vasta katsoin. Kiitos huippusisällöstä! Tämä täytyy jakaa oppilaillekin.
Mitä opetat?
@@Bisqwit Lukiomatematiikkaa. Luokalla on pari ohjelmoinnista kiinnostunutta.
This guys sounds like a Steven Hawking's voice synthesizer but with a Nordic accent.
this is very informative, and your voice is very calming
you sound like Stephen Hawking voice
i was just thinking that!
All of us are somewhere in the autistic spectrum. It just describes an individual's strengths and weaknesses.
I like his voice. It's soothing. (no homo)
I'm not sure if you were joking or not but the cross product of two vectors give you a vector that is orthogonal to both of the vectors that were crossed. Meaning the output vector is at a 90 degree angle from the first vector and also a 90 degree angle from the second vector.
Did you say 'Shalom' in the intro? Hm...
+Masterchief461 Yes I did. It is Hebrew language, and most often translated as "peace", but it is a wishing of general well-being, including health. I find this greeting more meaningful than "good day" or "morning" or "hello", and I often use it in real life too. You can find more information about the word here: www.therefinersfire.org/meaning_of_shalom.htm