From 1e00167b4a4d39369c697bf7f1d3a6fbd3eb4d7b Mon Sep 17 00:00:00 2001 From: Miloslav Ciz Date: Mon, 13 Mar 2023 21:33:27 +0100 Subject: [PATCH] Update --- modern.md | 10 +++++----- optimization.md | 3 ++- recursion.md | 2 +- ssao.md | 6 +++--- sw_rendering.md | 27 +++++++++++++++++++++------ wiki_style.md | 2 +- xonotic.md | 6 ++++-- 7 files changed, 37 insertions(+), 19 deletions(-) diff --git a/modern.md b/modern.md index 9931821..d3dc760 100644 --- a/modern.md +++ b/modern.md @@ -13,13 +13,13 @@ It's sad and dangerous that newer generation won't even remember technology used (INB4 "it was faster and longer on battery etc. because it was simpler" -- **yes, that is exactly the point**.) - Old technology was simpler and **better engineered with minimum [bloat](bloat.md)**. Fewer incompetent people were present in the field and capitalism wasn't yet pushing as hard on extreme development speed and abuse of the user, products still tried to compete by their quality. -- **Old computers were faster** and astronomically more efficient. Computers with a few MHz single-core CPU and under a megabyte of RAM booted faster to [DOS](dos.md) than modern computers boot to Windows 10, despite [Moore's law](moores_law.md) (this shittiness is known as [Wirth's law](wirths_law.md)). Old tech also **reacted faster to input** (had shorter input latency/lag), e.g. thanks to shorter input and output processing [pipelines](pipeline.md). { I've heard this confirmed from [John Carmack](carmack.md) himself in a talk on his development of [VR](vr.md). ~drummyfish } +- **Old computers were faster** and astronomically more efficient. Computers with a few MHz single-core CPU and under a megabyte of RAM booted faster to [DOS](dos.md) than modern computers boot to Windows 10, despite [Moore's law](moores_law.md) (this shittiness is known as [Wirth's law](wirths_law.md)). Old tech also **reacted faster to input** (had shorter input latency/lag), e.g. thanks to shorter input and output processing [pipelines](pipeline.md). { I've heard this confirmed from [John Carmack](carmack.md) himself in a talk on his development of [VR](vr.md). ~drummyfish } Back in the day things had to work smoothly -- if in the 90s you showed people a phone that you wake up and have to wait 20 seconds before it starts to react, they would laugh at it and on one would buy it -- nowadays such technology is the standard. - Old devices such as cell phones **lasted much, much longer on battery**. The old phones such as Nokia 3310 would **last long over a week** on stand-by. -- **Old software was shipped finished, complete and with minimum [bugs](bug.md)**. Nowadays newly released "[apps](app.md)" and [games](game.md) are normally released unfinished, even in pre-alpha states and even "finished" ones have [bugs](bug.md) often rendering the software unsuable (see Cyberpunk 2077, GTA: Definiteve Edition etc.), user is supposed to wait years for fixes (without any guarantees), pay for content or even subscriptions. Old software was difficult or even impossible to patch (e.g. on [Gameboy](gameboy.md)) so it had to work. +- **Old software was shipped finished, complete and with minimum [bugs](bug.md)**. Nowadays newly released "[apps](app.md)" and [games](game.md) are normally released unfinished, even in pre-alpha states and even "finished" ones have [bugs](bug.md) often rendering the software unsuable (see Cyberpunk 2077, GTA: Definiteve Edition etc.), user is supposed to wait years for fixes (without any guarantees), pay for content or even subscriptions. Some software "products" even spend their whole commercial life unfinished. Old software was difficult or even impossible to patch (e.g. [Gameboy](gameboy.md) cartridges) so it had to work. - **Old tech had minimum malicious features**. There wasn't [spyware in CPUs](intel_me.md), **[DRM](drm.md) was either absent or primitive**, there weren't ads in file explorers, there weren't [microtransactions](microtransaction.md) in games, there weren't [autoupdates](autoupdate.md), there weren't psychologically abusive [social networks](social_network.md), technology was **designed to last**, with replaceable parts; not to be [consoomed](consumerism.md), there was much less [censorship](censorship.md). - **Old tech was much easier to repair, modify and customize**, thanks to not being so overcomplicated and not containing so many anti-repair "features". Old software wasn't in the [cloud](cloud.md) which makes it impossible to modify. -- **Old software was better programmed** because it was firstly made by actually the smartest people such as mathematicians and physicist (who were considering the big picture and saw e.g. the necessity for minimalism) and secondly without such a great pressure of the market because software was more a subject of research and experimenting rather than dirty fight for consumers. For example the [Doom](doom.md) engine was written very nicely, in an extremely [portable](portability.md) way (which actually became legendary), with things such as an elegant deterministic FPS-independent physics (which results in many advantages and is basically THE only correct way of writing an engine) and [software rendering](software_rendering.md) that ran smooth even on that time's slow CPUs. Later engines from the same creators -- those of [Quake](quake.md) games -- began to suffer from worse design (no deterministic physics, dropping of software rendering etc.). Nowadays software is written by high schoolers, women and incompetent minorities forced into tech just for diversity quotas and generally anyone who can just copy paste snippets of code from the web -- game engines (like anything else) nowadays are indescribably badly written, non-portable, non-deterministic, bloated, running slow even on computers thousands of times faster than those that ran Doom (even if you lower graphic details of a 2023 game to the looks of a 2000s game, it will likely run under 10 FPS on a 2020 computer). -- **Old tech was much more independent and freedom friendly**, did not require Internet connectivity, subscription etc. Thanks to its simplicity and better hackability it was possible for people to partly control their devices, even if the devices were [proprietary](proprietary.md). +- **Old software was better programmed** because it was firstly made by actually the smartest people such as mathematicians and physicist (who were considering the big picture and saw e.g. the necessity for minimalism) and secondly without such a great pressure of the market because software was more a subject of research and experimenting rather than dirty fight for consumers. This can be seen even on commercial software such as games: for example the [Doom](doom.md) engine was written very nicely, in an extremely [portable](portability.md) way (which actually became legendary), with things such as an elegant deterministic FPS-independent physics (which results in many advantages and is basically THE only correct way of writing an engine) and [software rendering](software_rendering.md) that ran smooth even on that time's slow CPUs. Later engines from the same creators -- those of [Quake](quake.md) games -- began to suffer from worse design (no deterministic physics, dropping of software rendering etc.). Nowadays software is written by high schoolers, women and incompetent minorities forced into tech just for diversity quotas and generally anyone who can just copy paste snippets of code from the web, extremely tight deadlines in the market race make it impossible to tidy any piece of software -- game engines (like anything else) nowadays are indescribably badly written, non-portable, non-deterministic, bloated, running slow even on computers thousands of times faster than those that ran Doom (even if you lower graphic details of a 2023 game to the looks of a 2000s game, it will likely run under 10 FPS on a 2020 computer). +- **Old tech was much more independent and freedom friendly**, did not require Internet connectivity, subscription etc. Thanks to its simplicity and better hackability it was possible for people to partly control their devices, even if the devices were [proprietary](proprietary.md). Nowadays if the manufactures of your phone (or even a car) decides it's time for you to buy a new model, he just remotely kills your device, and you can hardly do anything about it (this is actually happening e.g. with [iPhones](iphone.md)). - There was **minimum [bullshit](bullshit.md)**. True usefulness was more important than killer features and marketing. -- Old tech was **simpler and more [fun](fun.md) to program**, allowing direct access to hardware, not complicating things with [OOP](oop.md) and similar [shit](shit.md), and so **old programmers were more [productive](productivity_cult.md)**. +- Old tech was **simpler and more [fun](fun.md) to program**, allowing direct access to hardware, not complicating things with [OOP](oop.md) and similar [shit](shit.md), and so **old programmers were more "[productive](productivity_cult.md)"**, less frustrated and stressed. - **Old "look n feel" of software was objectively better**. Just compare the graphics of [Doom](doom.md) and any shitty soulless "modern" game. \ No newline at end of file diff --git a/optimization.md b/optimization.md index 5e01df8..b69cfeb 100644 --- a/optimization.md +++ b/optimization.md @@ -17,7 +17,7 @@ These are mainly for [C](c.md), but may be usable in other languages as well. - **Learn about [dynamic programming](dynamic_programming.md)**. - **Avoid branches (ifs)** if you can (remember [ternary operators](ternary_operator.md), loop conditions etc. are branches as well). They break prediction in CPU pipelines and instruction preloading and are often source of great performance losses. Don't forget that you can many times compare and use the result of operations without using any branching (e.g. `x = (y == 5) + 1;` instead of `x = (y == 5) ? 2 : 1;`). - **Use iteration instead of [recursion](recursion.md)** if possible (calling a function costs something). -- **You can use good-enough [approximations](approximation.md) instead of completely accurate calculations**, e.g. taxicab distance instead of Euclidean distance, and gain speed or memory without trading. +- **You can use good-enough [approximations](approximation.md) instead of completely accurate calculations**, e.g. taxicab distance instead of Euclidean distance, and gain speed or memory without trading. Nice examples can be found in [computer graphics](graphics.md), e.g. some [software renderers](sw_rendering.md) use perspective-correct texturing only for large near triangles and cheaper affine texturing for other triangles, which mostly looks OK. - **Use quick opt-out conditions**: many times before performing some expensive calculation you can quickly check whether it's even worth performing it and potentially skip it. For example in physics [collision detections](collision_detection.md) you may first quickly check whether the bounding spheres of the bodies collide before running an expensive precise collision detection -- if bounding spheres of objects don't collide, it is not possible for the bodies to collide and so we can skip further collision detection. - **Operations on static data can be accelerated with accelerating structures** ([look-up tables](lut.md) for functions, [indices](indexing.md) for database lookups, spatial grids for collision checking, various [trees](tree.md) ...). - **Use powers of 2** (1, 2, 4, 8, 16, 32, ...) whenever possible, this is efficient thanks to computers working in [binary](binary.md). Not only may this help nice utilization and alignment of memory, but mainly multiplication and division can be optimized by the compiler to mere bit shifts which is a tremendous speedup. @@ -40,6 +40,7 @@ These are mainly for [C](c.md), but may be usable in other languages as well. - Similarly **order if-sequences and switch cases from most probable**: If you have a sequences of ifs such as `if (x) ... else if (y) ... else if (z) ...`, make it so that the most likely condition to hold gets checked first, then second most likely etc. Compiler most likely can't know the probabilities of the conditions so it can't automatically help with this. Do the same with the `switch` statement -- even though switch typically gets compiled to a table of jump addresses, in which case order of the cases doesn't matter, it may also get compiled in a way similar to the if sequence (e.g. as part of size optimization if the cases are sparse) and then it may matter again. - **You can save space by "squeezing" variables** -- this is a space-time tradeoff, it's a no brainer but nubs may be unaware of it -- for example you may store 2 4bit values in a single `char` variable (8bit data type), one in the lower 4bits, one in the higher 4bits (use bit shifts etc.). So instead of 16 memory-aligned booleans you may create one `int` and use its individual bits for each boolean value. This is useful in environments with extremely limited RAM such as 8bit Arduinos. - **You can optimize critical parts of code in [assembly](assembly.md)**, i.e. manually write the assembly code that takes most of the running time of the program, with as few and as inexpensive instructions as possible (but beware, popular compilers are very smart and it's often hard to beat them). But note that such code loses [portability](portability.md)! So ALWAYS have a C (or whatever language you are using) [fallback](fallback.md) code for other platforms, use [ifdefs](ifdef.md) to switch to the fallback version on platforms running on different assembly languages. +- **Loop unrolling/splitting/fusion, function inlining etc.**: there are optimizations that are usually done at [assembly](assembly.md) level (e.g. loop unrolling physically replaces a loop by repeated commands which gains speed but also makes the program bigger) and higher level languages try to perform them automatically. However if you're writing in assembly or have a dumb compiler (or are even writing a compiler) you may do these automatically, e.g. with macros/templates etc. Sometimes you can hint a compiler to perform these optimizations, so look this up. - **[Parallelism](parallelism.md) ([multithreading](multithreading.md), [compute shaders](compute_shader.md), ...) can astronomically accelerate many programs**, it is one of the most effective techniques of speeding up programs -- we can simply perform several computations at once and save a lot of time -- but there are a few notes. Firstly not all problems can be parallelized, some problem are sequential in nature, even though most problems can probably be parallelized to some degree. Secondly it is hard to do, opens the door for many new types of bugs, requires hardware support (software simulated parallelism can't work here of course) and introduces [dependencies](dependency.md); in other words it is huge [bloat](bloat.md), we don't recommend parallelization unless a very, very good reason is given. Optional use of [SIMD](simd.md) instructions can be a reasonable midway to going full parallel computation. - **Specialized hardware (e.g. a [GPU](gpu.md)) astronomically accelerates programs**, but as with the previous point, portablity and simplicity greatly suffers, your program becomes bloated and gains dependencies, always consider using specialized hardware and offer software fallbacks. diff --git a/recursion.md b/recursion.md index 239ccea..abc2694 100644 --- a/recursion.md +++ b/recursion.md @@ -2,7 +2,7 @@ *See [recursion](recursion.md).* -In general recursion is a situation in which a [definition](definition.md) refers to itself; for example the definition of a human's ancestor as "the human's parents and the ancestors of his parents" ([fractals](fractal.md) are also very nice example of what a simple recursive definition can achieve). In programming recursion takes on a meaning of a **function that calls itself**; this is the meaning we'll suppose in this article, unless noted otherwise. +Recursion (from Latin recursio, "running back") in general is a situation in which a [definition](definition.md) refers to itself; for example the definition of a human's ancestor as "the human's parents and the ancestors of his parents" ([fractals](fractal.md) are also very nice example of what a simple recursive definition can achieve). In programming recursion takes on a meaning of a **function that calls itself**; this is the meaning we'll suppose in this article, unless noted otherwise. We divide recursion to a **direct** and **indirect** one. In direct recursion the function calls itself directly, in indirect function *A* calls a function *B* which ends up (even possibly by calling some more functions) calling *A* again. Indirect recursion is tricky because it may appear by mistake and cause a [bug](bug.md) (which is nevertheless easily noticed as the program will mostly run out of memory and crash). diff --git a/ssao.md b/ssao.md index 9e99195..9fe70d4 100644 --- a/ssao.md +++ b/ssao.md @@ -1,9 +1,9 @@ # SSAO -Screen space ambient occlusion (SSAO) is a technique in [computer graphics](graphics.md) of **[approximating](approximation.md)** ambient occlusions (basically dim shadows in corners) in a way that's easy and not so expensive to implement to run in [real time](real_time.md). +Screen space ambient occlusion (SSAO) is a [screen space](screen_space.md) technique used in 3D [computer graphics](graphics.md) for **[approximating](approximation.md)** [ambient occlusion](ambient_occlusion.md) (basically "dim shadows in corners", which itself is an approximation of true [global illumination](global_illumination.md)) in a way that's easy and not so expensive to implement to run in [real time](real_time.md). The effect however looks ugly many times and is often criticized, see e.g. an excellent article at https://nothings.org/gamedev/ssao/. -Exact ambient occlusions can be computed with algorithms such as [path tracing](path_tracing.md) and this requires complete information about the geometry as to compute the exact light bounces, but that is way too slow for real time (even though actually path tracing is already kind of possible in real time, but requires expensive consoomer hardware). So game devs cheat and use a cheap approximation: SSAO is implemented as a [post-processing](post_processing.md) [shader](shader.md) and only uses the information available on the screen, specifically in the [depth buffer](z_buffer.md). This gives only partial information, the algorithm doesn't know what the back facing geometry looks like and has to make guesses which sometimes results in inaccuracies. +Exact ambient occlusions can be computed with algorithms such as RTAO (which uses [raytracing](raytracing.md)), but this requires complete information about the geometry and is too slow without special hardware. Therefore some game devs cheat and use a cheap approximation: SSAO is implemented as a [post-processing](post_processing.md) [shader](shader.md) and only uses the information available on the screen, specifically in the [depth buffer](z_buffer.md) -- this gives only partial information about the actual scene geometry, i.e. the algorithm doesn't know what the back facing, screen-perpendicular or off-screen geometry looks like and has to make guesses which sometimes result in quite visible inaccuracies. This methods is notoriously ugly in certain conditions and many [modern](modern.md) [games](game.md) suffer from this, even the supposedly "photorealistic" engines like Unreal -- if someone is standing in front of a wall there is a shadow outline around him that looks so unbelievably ugly you literally want to puke. But normie eyes can't see this lol, they think that's how reality looks and they are okay with this shit, they allow this to happen. Normies literally destroy computer graphics by not being able to see correctly. -What to do then? The most [suckless](suckless.md) way is to simply do no ambient occlusions -- seriously test how it looks and if it's okay just save yourself the effort, performance and complexity. Back in the 90s we didn't have this shit and games unironically looked 100 times better. You can also just [bake](baking.md) the ambient occlusions in textures themselves, either directly in the color texture or use so called **[light maps](light_map.md)**. Note that this makes the ambient occlusions static and with light maps you'll need more memory for textures. Finally, if you absolutely have to use SSAO, at least use it very lightly (there are parameters you can lower to make it less prominent). \ No newline at end of file +What to do then? The most [suckless](suckless.md) way is to simply do no ambient occlusion -- seriously test how it looks and if it's okay just save yourself the effort, performance and complexity. Back in the 90s we didn't have this shit and games unironically looked 100 times better. You can also just [bake](baking.md) the ambient occlusion in textures themselves, either directly in the color texture or use [light maps](light_map.md). Note that this makes the ambient occlusions static and with light maps you'll need more memory for textures. Finally, if you absolutely have to use SSAO, at least use it very lightly (there are parameters you can lower to make it less prominent). \ No newline at end of file diff --git a/sw_rendering.md b/sw_rendering.md index 02e4bee..9f3a558 100644 --- a/sw_rendering.md +++ b/sw_rendering.md @@ -1,16 +1,16 @@ # Software Rendering -Sofware (SW) rendering refers to [rendering](rendering.md) [computer graphics](graphics.md) without the help of [graphics card](gpu.md) (GPU), i.e. computing images only with [CPU](cpu.md). This mostly means rendering [3D](3d.md) graphics but can also refer to other kinds of graphics such as drawing [fonts](font.md) or [video](video.md). Before GPUs were invented, all rendering was done in software, of course -- games such as [Quake](quake.md) were designed with SW rendering and only added possible GPU acceleration later. SW rendering for traditional 3D graphics is also called software [rasterization](rasterization.md). +Sofware (SW) rendering refers to [rendering](rendering.md) [computer graphics](graphics.md) without the help of [graphics card](gpu.md) (GPU), i.e. computing images only with [CPU](cpu.md). This mostly means rendering [3D](3d.md) graphics but can also refer to other kinds of graphics such as drawing [fonts](font.md) or [video](video.md). Before GPUs were invented, all rendering was done in software, of course -- games such as [Quake](quake.md) or Thief were designed with SW rendering and only added optional GPU acceleration later. SW rendering for traditional 3D graphics is also called software [rasterization](rasterization.md), as rasterization is the basis of current real-time 3D graphics. -SW rendering has advantages and disadvantages. Firstly it is **much slower** -- GPUs are designed to perform graphics-specific operations very quickly and, more importantly, they can process many pixels (and other elements) in [parallel](parallelism.md), while a CPU has to compute pixels sequentially one by one and that in addition to all other computations it is otherwise performing. This causes a much lower [FPS](fps.md) in SW rendering. For this reasons SW rendering is also normally of **lower quality** (lower resolution, [nearest neighbour](nn.md) texture filtering, ...) to allow workable FPS. Nevertheless thanks to the ginormous speeds of today's CPUs simple fullscreen SW rendering can be pretty fast on PCs and achieve even above 60 FPS; on slower CPUs (typically [embedded](embedded.md)) SW rendering is usable normally at around 30 FPS if resolutions are kept small. +SW rendering has advantages and disadvantages, though from our point of view its advantages prevail (at least given only capitalist GPUs exist nowadays). Firstly it is **much slower** than GPU graphics -- GPUs are designed to perform graphics-specific operations very quickly and, more importantly, they can process many pixels (and other elements) in [parallel](parallelism.md), while a CPU has to compute pixels sequentially one by one and that in addition to all other computations it is otherwise performing. This causes a much lower [FPS](fps.md) in SW rendering. For this reasons SW rendering is also normally of **lower quality** (lower resolution, [nearest neighbour](nn.md) texture filtering, ...) to allow workable FPS. Nevertheless thanks to the ginormous speeds of today's CPUs simple fullscreen SW rendering can be pretty fast on PCs and achieve even above 60 FPS; on slower CPUs (typically [embedded](embedded.md)) SW rendering is usable normally at around 30 FPS if resolutions are kept small. On the other hand SW rendering is more [portable](portability.md) (as it can be written purely in a portable language such as [C](c.md)), less [bloated](bloat.md) and **eliminates the [dependency](dependency.md) on GPU** so it will be supported almost anywhere as every computer has a CPU, while not all computers (such as [embedded](embedded.md) devices) have a GPU (or, if they have it, it may not be sufficient, supported or have a required [driver](driver.md)). SW rendering may also be implemented in a simpler way and it may be easier to deal with as there is e.g. no need to write [shaders](shader.md) in a special language, manage transfer of data between CPU and GPU or deal with parallel programming. SW rendering is the [KISS](kiss.md) approach. -SW rendering may also utilize a much wider variety of rendering techniques than only 3D [rasterization](rasterization.md) traditionally used with [GPUs](gpu.md) and their [APIs](api.md), thanks to not being limited by hard-wired pipelines. This may include [splatting](splatting.md), [raytracing](raytracing.md) or [BSP rendering](bsp.md) (and many other ["pseudo 3D"](pseudo3d.md) techniques). +SW rendering may also utilize a much wider variety of rendering techniques than only 3D [rasterization](rasterization.md) traditionally used with [GPUs](gpu.md) and their [APIs](api.md), thanks to not being limited by hard-wired pipelines, i.e. it is more flexible. This may include [splatting](splatting.md), [raytracing](raytracing.md) or [BSP rendering](bsp.md) (and many other ["pseudo 3D"](pseudo3d.md) techniques). A lot of software and rendering frameworks offer both options: accelerated rendering using GPU and SW rendering as a [fallback](fallback.md) (in case the first option is not possible). Sometimes there exists a rendering [API](api.md) that has both an accelerated and software implementation (e.g. [TinyGL](tinygl.md) for [OpenGL](opengl.md)). -For simpler and even somewhat more complex graphics **purely software rendering is a lot of times the best choice**. [LRS](lrs.md) suggests you prefer this kind of rendering for its simplicity and portability, at least as one possible option. On devices with lower resolution not many pixels need to be computed so SW rendering can actually be pretty fast despite low specs, and on "big" computers there is nowadays usually an extremely fast CPU available that can handle comfortable FPS at higher resolutions. There is a LRS software renderer you can use: [small3dlib](s3l.md). +For simpler and even somewhat more complex graphics **purely software rendering is mostly the best choice**. [LRS](lrs.md) suggests you prefer this kind of rendering for its simplicity and portability, at least as one possible option. On devices with lower resolution not many pixels need to be computed so SW rendering can actually be pretty fast despite low specs, and on "big" computers there is nowadays usually an extremely fast CPU available that can handle comfortable FPS at higher resolutions. There is a LRS software renderer you can use: [small3dlib](s3l.md). SW renderers are also written for the purpose of verifying rendering hardware, i.e. as a [reference implementation](reference_implementation.md). @@ -18,7 +18,20 @@ Note that SW rendering doesn't mean our program is never touching [GPU](gpu.md) Some SW renderers make use of specialized CPU instructions such as [MMX](mmx.md) which can make SW rendering faster thanks to handling multiple data in a single step. This is kind of a mid way: it is not using a GPU per se but only a mild form of hardware acceleration. The speed won't reach that of a GPU but will outperform a "pure" SW renderer. However the disadvantage of a hardware dependency is still present: the CPU has to support the MMX instruction set. Good renderers only use these instructions optionally and fall back to general implementation in case MMX is not supported. -**Programming your own 3D software rasterizer** (in case [small3dlib](small3dlib.md) is somehow not enough for you): difficulty of this task depends on features you want -- a super simple [flat shaded](flat_shading.md) (no textures, no smooth shading) renderer is relatively easy to make, especially if you don't need movable camera, can afford to use [floating point](float.md) etc. See the details of [3D rendering](3d_rendering.md), especially how the GPU pipelines work, and try to imitate them in software. The core of these renderers is the **[triangle](triangle.md) [rasterization](rasterization.md)** algorithm which, if you want, can be very simple -- even a naive one will give workable results -- or pretty complex and advanced, using various optimizations and things such as the [top-left rule](top_left_rule.md) to guarantee no holes and overlaps of triangles. Remember this function will likely be the performance [bottleneck](bottleneck.md) of your renderer so you want to put effort into [optimizing](optimization.md) it to achieve good [FPS](fps.md). Once you have triangle rasterization, you can draw 3D models which consist of vertices (points in 3D space) and triangles between these vertices (it's very simple to load simple 3D models e.g. from the [obj](obj.md) format) -- you simply project (using [perspective](perspective.md)) 3D position of each vertex to screen coordinates and draw triangles between these pixels with the rasterization algorithm. Here you need to also solve [visibility](visibility.md), i.e. possible overlap of triangles on the screen and correctly drawing those nearer the view in front of those that are further away -- a very simple solution is a [z buffer](z_buffer.md), but to save memory you can also e.g. [sort](sorting.md) the triangles by distance and draw them back-to-front ([painter's algorithm](painters_algorithm.md)). You may add a [scene](scene.md) data structure that can hold multiple models to be rendered. If you additionally want to have movable camera and models that can be transformed (moved, rotated, scaled, ...), you will additionally need to look into some [linear algebra](linear_algebra.md) and [transform matrices](transform_matrix.md) that allow to efficiently compute positions of vertices of a transformed model against a transformed camera -- you do this the same way as basically all other 3D engines (look up e.g. some [OpenGL](opengl.md) tutorials). If you also want texturing, the matter gets again a bit more complicated, you need to compute [barycentric](barycentric.md) coordinates (special coordinates within a triangle) as you're rasterizing the triangle, and possibly apply [perspective correction](perspective_correction.md) (otherwise you'll be seeing distortions). You then map the barycentrics of each rasterized pixel to [UV](uv.md) (texturing) coordinates which you use to retrieve specific pixels from a texture. On top of all this you may start adding all the advanced features of typical engines such as [acceleration structures](acceleration_structure.md) that for example discard models that are completely out of view, [LOD](lod.mf), instancing, [MIP maps](mip_map.md) and so on. +## Programming A Software Rasterizer + +{ In case [small3dlib](small3dlib.md) is somehow not enough for you :) ~drummyfish } + +Difficulty of this task depends on features you want -- a super simple [flat shaded](flat_shading.md) (no textures, no smooth [shading](shading.md) renderer is relatively easy to make, especially if you don't need movable camera, can afford to use [floating point](float.md) etc. See the details of [3D rendering](3d_rendering.md), especially how the GPU pipelines work, and try to imitate them in software. The core of these renderers is the **[triangle](triangle.md) [rasterization](rasterization.md)** algorithm which, if you want, can be very simple -- even a naive one will give workable results -- or pretty complex and advanced, using various optimizations and things such as the [top-left rule](top_left_rule.md) to guarantee no holes and overlaps of triangles. Remember this function will likely be the performance [bottleneck](bottleneck.md) of your renderer so you want to put effort into [optimizing](optimization.md) it to achieve good [FPS](fps.md). Once you have triangle rasterization, you can draw 3D models which consist of vertices (points in 3D space) and triangles between these vertices (it's very simple to load simple 3D models e.g. from the [obj](obj.md) format) -- you simply project (using [perspective](perspective.md)) 3D position of each vertex to screen coordinates and draw triangles between these pixels with the rasterization algorithm. Here you need to also solve [visibility](visibility.md), i.e. possible overlap of triangles on the screen and correctly drawing those nearer the view in front of those that are further away -- a very simple solution is a [z buffer](z_buffer.md), but to save memory you can also e.g. [sort](sorting.md) the triangles by distance and draw them back-to-front ([painter's algorithm](painters_algorithm.md)). You may add a [scene](scene.md) data structure that can hold multiple models to be rendered. If you additionally want to have movable camera and models that can be transformed (moved, rotated, scaled, ...), you will additionally need to look into some [linear algebra](linear_algebra.md) and [transform matrices](transform_matrix.md) that allow to efficiently compute positions of vertices of a transformed model against a transformed camera -- you do this the same way as basically all other 3D engines (look up e.g. some [OpenGL](opengl.md) tutorials, see model/view/projection [matrices](matrix.md) etc.). If you also want texturing, the matters get again a bit more complicated, you need to compute [barycentric](barycentric.md) coordinates (special coordinates within a triangle) as you're rasterizing the triangle, and possibly apply [perspective correction](perspective_correction.md) (otherwise you'll be seeing distortions). You then map the barycentrics of each rasterized pixel to [UV](uv.md) (texturing) coordinates which you use to retrieve specific pixels from a texture. On top of all this you may start adding all the advanced features of typical engines such as [acceleration structures](acceleration_structure.md) that for example discard models that are completely out of view, [LOD](lod.mf), instancing, [MIP maps](mip_map.md) and so on. + +Possible tricks, cheats and [optimizations](optimization.md) you may utilize include: + +- Using painter's algorithm (sorting triangles and drawing back to front) instead of z-buffer if you need to save a lot of RAM. But remember sorting doesn't work perfectly, glitches will inevitably appear, and you will probably gain overdraw penalty. +- Ad previous point: you don't have to perform whole triangle sorting each frame if you need to save speed, it may be good enough to perform a constant continuous sorting by performing only a few iterations of some sorting algorithm per frame. +- You may lower the quality of far-away objects in many ways, e.g. with [LOD](lod.md), only using affine texturing for them (as opposed to perspective-correct one) or even just using a constant color (average color of the texture), maybe even just drawing 2D sprites instead of 3D models etc. This may help a lot. +- Generally use cheap [approximations](approximation.md) such as [Gouraud](gouraud.md) (per-vertex) [shading](shading.md) instead of [Phong](phong.md) (per-pixel), nearest neighbour texture sampling, only approximate perspective correction (every N pixels), simplified handling of near-plane culling (e.g. just pushing the vertices in front of camera instead of actually culling a triangle) etc. +- Use general [optimization](optimization.md) techniques: e.g. using power of two resolution for textures, fixed screen resolution that's known at compile time or inlining of your shader function will probably help performance. +- TODO: MORE ## Specific Renderers @@ -26,11 +39,13 @@ These are some notable software renderers: - **[Build engine](build_engine.md)**: While not a "true 3D", this was a very popular [proprietary](proprietary.md) portal-rendering engine for older games like Duke Nukem 3D or Blood. - **[BRender](brender.md)**: Old commercial renderer used in games such as Carmageddon, Croc or Harry Potter 1. Later made [FOSS](foss.md). -- **[id Tech](id_tech.md)**: Multiple engines by [Id software](id.md) (later made [FOSS](foss.md)) used for games like [Quake](quake.md) included a software renderer. +- **[Dark Engine](dark_engine.md)**: Old proprietary game engine which includes a SW renderer, used mainly in the game Thief. The author writes about it at https://nothings.org/gamedev/thief_rendering.html. +- **[id Tech](id_tech.md)**: Multiple engines by [Id software](id.md) (later made [FOSS](foss.md)) used for games like [Quake](quake.md) included a software renderer. Quake's SW renderer was partially described in the *Michael Abrash's Graphics Programming Black Book*. - **[Irrlich](irrlicht.md)**: [FOSS](foss.md) game engine including a software renderer as one of its [backends](backend.md). - **[Mesa](mesa.md)**: [FOSS](foss.md) implementation of [OpenGL](opengl.md) that includes a software rasterizer. - **[small3dlib](s3l.md)**: [LRS](lrs.md) [C](c.md) 3D rasterizer, very simple. - **SSRE**: The guy who wrote [LIL](lil.md) also made this renderer named Shitty Software Rendering Engine, accessible [here](http://runtimeterror.com/tech/ssre/). +- **[System Shock](system_shock.md) engine**: Old proprietary game engine. - **[TinyGL](tinygl.md)**: Implements a subset of [OpenGL](opengl.md). - Many old [games](game.md) in the 90s implemented their own software renderers, so you can look there. diff --git a/wiki_style.md b/wiki_style.md index 37f9dcc..35fd6c5 100644 --- a/wiki_style.md +++ b/wiki_style.md @@ -22,7 +22,7 @@ If you contribute, add yourself to [wiki authors](wiki_authors.md)! You can also - The style of this wiki is inspired by the classic hacker culture works such as the [WikiWikiWeb](wikiwikiweb.md) and [Jargon File](jargon_file.md). - **Writing style should be relaxed and in many parts informal**. Formality is used where it is useful (e.g. definitions), most of other text can benefit from being written as a tech conversation among friends. - **Depth/complexity/level of articles**: Articles shouldn't try to go to unnecessary depth, but also shouldn't be shallow. This is written mainly for programmers of [less retarded society](less_retarded_society.md), the complexity should follow from that. Again, start simple and go more into depth later on in the article, very complex things should rather be explained intuitively, no need for complex proofs etc. -- **Political incorectness, slurs and "offensive speech" is highly encouraged**. Of course this is not to "offend" anyone, this helps people unlearn being offended. +- **Political incorectness, slurs and "offensive speech" is highly encouraged**. Avoid the use of the word "person" (use "man", "guy", "human", "one" etc., possibly "individual" at worst). Of course this is not to "offend" anyone, this helps people unlearn being offended. - **Images**: for now don't embed images. [ASCII art](ascii_art.md) can be used in many places instead of an image. Thousand words are worth a picture. Non-embedding links to images may be okay. - **You can leave comments right in the text of articles**, e.g. like this: { I disagree with this [shit](shit.md). ~drummyfish }. diff --git a/xonotic.md b/xonotic.md index f27a8e1..d3f8efe 100644 --- a/xonotic.md +++ b/xonotic.md @@ -65,11 +65,13 @@ Here are some [pro](pro.md) tips to git gud, impress your frens and generally ha - some **tactics/strategy/habits**: - **Chasing low health players is usually a bad idea** and a mistake made by nubs: such player, if he's not a complete newbie, can just spam electro/nades behind himself and easily kill you. - **Unpredictable movement and positioning is key** to avoiding damage: nubs think that when they move fast they won't get hit -- this is only partially true, it is about just as important to move unpredictably and avoid spots that get regularly spammed by enemies. When you move from point A to B on a map and the enemy sees you doing so, you should take a randomly long short pause in doing so, or maybe even switch direction completely, otherwise he can quite accurately predict when and where you'll appear at the destination and kill you with prefire. It's a great advantage if your opponent doesn't know where you are. One sneaky tactical move is "the return" when you pretend you run away from somewhere, then wait 3 seconds and return back (the opponent expects you to run anywhere but to where you ran away from). - - When in fight, **don't be an easy target and don't stand near a wall** (this makes you be easily hit with splash damage). Try to always have a quick escape path. - - **Stay together with your teammates** if you want to win. Fighting alone against multiple enemies is usually a bad idea and a quick death without dealing much damage. Especially if you're a nub playing clan arena, stay with your team or else you'll die in 3 seconds and will have to wait until the end of the round. + - When in fight, **don't be an easy target and don't stand near a wall** (this makes you be easily hit with splash damage). Don't stand in a corner. Try to always have a quick escape path. + - **Stay together with your teammates** (if you want to win). Fighting alone against multiple enemies is usually just a quick death without dealing much damage, even if you're very good and they're mediocre. Especially if you're a nub playing clan arena, stay with your team or else you'll die in 3 seconds and will have to wait until the end of the round. - **Higher ground is usually good**. - When microcamping (camping for a short time with vortex somewhere), the only direction you can be hit from should be the one you're looking in. Nubs sometimes camp in a spot that can be seen from multiple directions -- that's just asking to be fragged. - **Use machine gun (MG) secondary to finish off low HP enemies**: MG is [hitscan](hitscan.md), i.e. it hits instantly on a distance and its easier to hit with than vortex (which fires just one strong shot) -- the secondary fire has no bullet spread unlike the primary fire, so it's ideal to just deal quick small hit that's enough to finish someone. But watch out, some people bitch about using MG this way being too nub, going for slap will entertain the audience better. + - **Take the items**, many new players just walk by mega health without taking taking it, that's pretty stupid. + - It's sometimes good in a fight to be directly above or under the opponent as it's harder for him to aim precisely looking completely up or down, and with many weapons it's generally harder to just hit someone in air, especially for noobs. Noobs mostly don't even see you if you're above their heads, it's basically free damage. ## See Also