This commit is contained in:
Miloslav Ciz 2025-04-12 12:45:37 +02:00
parent f19efc7b70
commit 5604db85d2
24 changed files with 2048 additions and 2026 deletions

View file

@ -19,23 +19,23 @@ A graphics programmer quickly comes to realize that **3D rendering is to a great
Having said this, let's now take a look at possible **classifications** of 3D rendering methods. As seen, there are many ways:
- by **order**:
- **object order**: The method iterates on objects and draws object by object, one after another. This results in pixels being drawn to "random" places on the screen and possibly already drawn pixels being [overdrawn](overdraw.md) with new pixels (though this can be further reduced). Typically requires a [frame buffer](frame_buffer.md) and [double buffering](double_buffering.md), often also [z-buffer](z_buffer.md) (or [sorting](sorting.md)), i.e. requires a lot of memory. This method is also a bit ugly but typically also faster than the alternative, so it is prevailing nowadays.
- **image order**: The method iterates on screen pixels, typically going pixel by pixel from left to right, top to bottom, deciding the color of each pixel independently. May be easier to program and require less memory (no frame buffer is needed, see e.g. [frameless rendering](frameless.md)), however though [parallelism](parallelism.md) is applicable here (many pixels may potentially be independently computed in parallel, speeding up rendering), the algorithms used (e.g. [path tracing](path_tracing.md)) often have to expensively simulate light behavior and so performance is still an issue.
- **object order**: The method iterates on objects (3D models, sprites, ...), i.e. draws object by object, one after another. This results in pixels being drawn to "random" places on the screen, allowing undesired [overdraw](overdraw.md) (drawing pixels over already drawn pixels) and leaving "holes". This typically requires a [frame buffer](frame_buffer.md) and [double buffering](double_buffering.md) (which may sometimes be avoided with painter's algorithm, i.e. [sorting](sorting.md)), implying high memory demands. This method is also a bit ugly but typically also faster than the alternative, so it is prevailing nowadays.
- **image order**: The method iterates on screen pixels, typically going pixel by pixel from left to right, top to bottom, computing the [color](color.md) of each visited pixel independently. May be easier to program and require less memory (no frame buffer is needed, see e.g. [frameless rendering](frameless.md)), however though [parallelism](parallelism.md) is applicable here (many pixels may potentially be independently computed in parallel, speeding up rendering), the algorithms used (e.g. [path tracing](path_tracing.md)) are normally too slow to be used in real time without limitations (as for each of the millions of pixels ALL objects have to be checked somehow).
- by **speed**:
- **[realtime](realtime.md)**: Able to render at interactive [FPS](fps.md), typically used in [games](game.md) etc.
- **[offline](offline.md)**: Spends a lot of time (even many minutes) on rendering each frame with the goal to produce output of extreme quality, typically used to render 3D movies etc.
- by **relative limitation**:
- **primitive/"pseudo3D"/2.5D/...**: Older methods that produce 3D views but had great limitations e.g. in camera degrees of freedom or possible environment geometry that was usually limited to a "2D sector map" (see e.g. [Doom](doom.md)).
- **full/"true" 3D**: The "new" way of 3D rendering that allows freely rotating camera, arbitrary 3D geometry etc. Though this still has limitations (as any computer approximation of reality), many people just call this the "true" 3D.
- **[realtime](realtime.md)**: Able to render at interactive [FPS](fps.md) (let's say 10 and above), typically used in [games](game.md), [virtual reality](vr.md) etc.
- **[offline](offline.md)**: Spends a lot of time (even many minutes and possibly hours) to render a frame with the goal of producing output of very high quality, used for example for 3D movies, posters etc.
- by **freedoms and restrictions**:
- **[primitive/"pseudo3D"/2.5D/3D-ish/fake 3D/...](primitive_3d.md)**: Methods producing relatively cheap 3D looking renders but for the price of limitations, e.g. those in camera degrees of freedom or possible environment geometry, popular mainly in older video games such as [Doom](doom.md).
- **full/"true" 3D, 6 DOF**: Relatively unrestricted 3D rendering allowing full camera freedom, arbitrary 3D geometry etc. However there will always exist limitations (because any computer only ever [approximates](approximation.md) reality), for example only finite resolution of textures and 3D model etc.
- by **approach** (sides of above mentioned rendering spectrum):
- **appearance based**: Focuses on achieving desired appearance by any means necessary, faking, "cheating", not trying to stay physically correct. This is typically faster.
- **appearance based**: Focuses on achieving desired appearance by any means necessary, faking, "cheating", not honoring physical correctness. This is typically faster.
- **[physics](physics.md) simulation** (see also [physically based rendering](pbr.md)): Focuses on simulating the underlying physics of reality with high correctness so that we also get a very realistic result.
- by **main method/algorithm** (see also the table below):
- **rasterization**: Appearance based object order methods further based on a relatively simple algorithm capable of drawing (rasterizing) a simple geometric shape (such as a triangle) which we then use to draw the whole complex 3D scene (composed of great many of triangles).
- **ray casting/tracing**: Physics simulation image order methods further based on tracing paths of light in a manner that's closer to reality.
- **rasterization**: Appearance based object order methods based on a relatively simple algorithm capable of drawing (rasterizing) a simple [two-dimensional](2d.md) geometric shape (such as a [triangle](triangle.md)) which is subsequently used to draw the whole complex 3D scene (composed of many triangles).
- **ray/[path](pathtracing.md)/cone(/...) [casting](raycasting.md)/[tracing](raytracing.md)**: Physics simulation image order methods based on tracing paths of light.
- ...
- by **3D data** ([vector](vector.md) vs [raster](raster.md) classification applies here just as in 2D graphics):
- **triangle meshes** (vector, and other boundary representations)
- **triangle meshes** ([vector](vector.md), and other boundary representations)
- **[voxels](voxel.md)** (raster, and potentially other volumetric representations)
- **[point clouds](point_cloud.md)**
- **[heightmaps](heightmap.md)**
@ -44,16 +44,16 @@ Having said this, let's now take a look at possible **classifications** of 3D re
- **2D sectors** (e.g. [Doom](doom.md)'s [BSP](bsp.md) "pseudo 3D" rendering)
- ...
- by **[hardware](hw.md)**:
- **[software rendering](sw_rendering.md)**: Rendering only with [CPU](cpu.md). This is typically slower as a CPU mostly performs sequential computation, eliminating the possible parallelism optimization, however the approach is more [KISS](kiss.md) and [portable](portablity.md).
- **[GPU](gpu.md) accelerated**: Making use of specialized graphics rendering hardware (GPU) that typically uses heavy parallelism to immensely speed up rendering. While this is the mainstream, extremely fast way of rendering, it is also highly [bloated](bloat.md) while often being an [overkill](overkill.md) that overcomplicates programming and makes programs less [portable](portability.md), less [future proof](future_proof.md) etc.
- **[software rendering](sw_rendering.md)**: Rendering only on the [CPU](cpu.md). This is typically slower as a CPU lacks higher levels of parallelism needed to process many elements (pixels, vertices, ...) at once, however the approach is more [KISS](kiss.md) and [portable](portablity.md).
- **[GPU](gpu.md) accelerated**: Making use of specialized graphics rendering hardware (GPU) capable of heavy parallelism to immensely speed up rendering. Although this is the [mainstream](mainstream.md), fastest way of rendering, it is also highly [bloated](bloat.md) and often also an [overkill](overkill.md) that overcomplicates programming and makes programs less [portable](portability.md), less [future proof](future_proof.md) etc.
- by **realism** of output:
- **[photorealistic](photorealism.md)**
- **stylized**, flat [shaded](shading.md), [wireframe](wireframe.md), ...
- ...
- **[hybrids](hybrid.md)**: Methods may be combined and/or lie in between different extremes, for example we may see a rasterizing renderer that uses ray tracing to add detail (shadows, reflections, ...) to the scene, ones that allow triangle meshes as well as voxels etc. { One nice hybrid looking engine is e.g. [Chasm: The Rift](chasm_the_rift.md). ~drummyfish }
- **[hybrids](hybrid.md)**: Methods may be combined and/or stand in between different extremes, for instance we may see a rasterizing renderer that uses ray tracing to add detail ([shadows](shadow.md), reflections, ...) to the scene, ones supporting polygonal meshes and voxels at the same time etc. { One nice hybrid looking engine was used in [Chasm: The Rift](chasm_the_rift.md). ~drummyfish }
- ...
Finally a table of some common 3D rendering methods follows, including the most simple, most advanced and some unconventional ones. Note that here we talk about methods and techniques rather than algorithms, i.e. general approaches that are often modified and combined into a specific rendering algorithm. For example the traditional triangle rasterization is sometimes combined with raytracing to add e.g. realistic reflections. The methods may also be further enriched with features such as [texturing](texture.md), [antialiasing](antialiasing.md) and so on. The table below should help you choose the base 3D rendering method for your specific program.
Finally a table of some common 3D rendering methods follows, including the most primitive, most sophisticated and some unconventional ones. Note that here we highlight methods and techniques rather than algorithms, i.e. general approaches that are frequently modified and combined into a concrete rendering algorithm. As an example the traditional triangle rasterization has recently started to be combined with raytracing to add realistic reflections etc. The methods may also be additionally enriched with features such as [texturing](texture.md), [antialiasing](antialiasing.md) and so on. The table below should help finding the base 3D rendering method to further shape to any program's specific needs.
The methods may be tagged with the following:
@ -100,11 +100,11 @@ TODO: VoxelQuest has some innovative voxel rendering, check it out (https://www.
## 3D Rendering Basics For Nubs
If you're a complete noob and are asking what the essence of 3D is or just how to render simple 3Dish pictures for your game without needing a [PhD](phd.md), here's the very basics. Yes, you can use some 3D engine such as [Godot](godot.md) that has all the 3D rendering preprogrammed, but you'll surrender to [bloat](bloat.md), you won't really know what's going on and your ability to tinker with the rendering or optimizing it will be basically zero... AND you'll miss on all the [fun](fun.md) :) So here we just foreshadow some concepts you should start with if you want to program your own 3D rendering.
If you're a complete [noob](noob.md) and are asking what the essence of 3D is or just how to render simple 3Dish pictures for your game without needing a [PhD](phd.md), here are the 101 basics. Yes, you can use a 3D engine such as [Godot](godot.md) with all the 3D rendering preprogrammed, but you'll surrender to [bloat](bloat.md), you won't really know what's going on and your ability to tinker with the rendering or optimizing it will be basically [zero](zero.md)... AND you'll miss on all the [fun](fun.md) :) You might as well ask ChatGPT to program it for you, right? So let's just foreshadow some concepts you should start with if you want to program your own 3D rendering.
The first, 101 most basic concept in 3D is probably **[perspective](perspective.md)**, or the effect by which "things further away look smaller". This is basically the number one idea to know which will enable you to very quickly make simple 3D pictures, even though there are many more effects and concepts that "make pictures look 3D" and which you can potentially study later (lighting, shadows, [focus and blur](depth_of_field.md), [stereoscopy](stereo.md), [parallax](parallax.md), visibility/obstruction etc.). { It's probably possible to make something akin to "3D" even without perspective, just with [orthographic](ortho.md) projection, but that's just getting to details now. Let's just suppose we need perspective. ~drummyfish }
Arguably the first, most elementary concept in 3D is that of **[perspective](perspective.md)**, or the effect by which "things further away look smaller". Practically speaking, this is the grand idea opening the door to start making simple 3D pictures, even though there are many more effects and concepts that eventually "make pictures look 3D" and which you can potentially study later (lighting, shadows, [focus and blur](depth_of_field.md), [stereoscopy](stereo.md), [parallax](parallax.md), visibility/obstruction etc.). { It's probably possible to make something akin to "3D" even without perspective, just with [orthographic](ortho.md) projection, but that's just getting to details now. Let's just suppose we need perspective. ~drummyfish }
If you don't have rotating camera and other fancy things, perspective is actually mathematically very simple, you basically just **divide the object's size by its distance from the viewer**, i.e. its Z coordinate (you may divide by some multiple of Z coordinate, e.g. by 2 * Z to get different [field of view](fov.md)) -- the further away it is, the bigger number its size gets divided by so the smaller it becomes. This "dividing by distance" ultimately applies to all distances, so in the end even the details on the object get scaled according to their individual distance, but as a first approximation you may just consider scaling objects as a whole. Just keep in mind you should only draw objects whose Z coordinate is above some threshold (usually called a *near plane*) so that you don't divide by 0! With this "dividing by distance" trick you can make an extremely simple "3Dish" renderer that just draws [sprites](sprite.md) on the screen and scales them according to the perspective rules (e.g. some space simulator where the sprites are balls representing planets). There is one more thing you'll need to handle: **[visibility](visibility.md)**, i.e. nearer objects have to cover the further away objects -- you can do this by simply [sorting](sorting.md) the objects by distance and drawing them back-to-front ([painter's algorithm](painters_algorithm.md)).
If you don't require rotating camera and other fancy stuff, perspective is actually mathematically very simple, you basically just **divide the object's size by its distance from the viewer**, i.e. its Z coordinate (you may divide by some multiple of Z coordinate, e.g. by 2 * Z to get different [field of view](fov.md)) -- the further away it is, the bigger number its size gets divided by so the smaller it becomes. This "dividing by distance" ultimately applies to all [distances](distance.md), so in the end even the details on the object get scaled according to their individual distance, but as a first approximation you may just consider scaling objects as a whole. Just keep in mind you should only draw objects whose Z coordinate is above some threshold (usually called a *near plane*) so that you don't divide by 0! With this "dividing by distance" trick you can make an extremely simple "3Dish" renderer that just draws [sprites](sprite.md) on the screen and scales them according to the perspective rules (e.g. some space simulator where the sprites are balls representing planets). There is one more thing you'll need to handle: **[visibility](visibility.md)**, i.e. nearer objects have to cover the further away objects -- you can do this by simply [sorting](sorting.md) the objects by distance and drawing them back-to-front ([painter's algorithm](painters_algorithm.md)).
Here is some "simple" [C](c.md) code that demonstrates perspective and draws a basic animated wireframe cuboid as ASCII in terminal:
@ -244,7 +244,7 @@ One frame of the animation should look like this:
press key to animate
```
**PRO TIP**: It will also help if you learn a bit about [photography](photo.md) because 3D usually tries to simulate [cameras](camera.md) and 3D programmers adopt many terms and concepts from photography. At least learn the very basics such as [focal length](focal_length.md), [pinhole camera](pinhole_camera.md), the "exposure triangle" (shutter speed, [aperture](aperture.md), [ISO](iso.md)) etc. You should know how focal length is related to [FOV](fov.md), what the "f number" means, how to use exposure settings to increase or decrease things like [motion blur](motion_blur.md) and [depth of field](depth_of_field.md), what [HDR](hdr.md) means etc.
**PRO TIP**: It will also help if you learn a bit about [photography](photo.md) because 3D usually tries to simulate [cameras](camera.md) and 3D programmers adopt many terms and concepts from photography. At least learn the very basics such as [focal length](focal_length.md), [pinhole camera](pinhole_camera.md), the "exposure triangle" (shutter speed, [aperture](aperture.md), [ISO](iso.md)) etc. You should know how focal length is related to [FOV](fov.md), what the "f number" means, how exposure settings affect [motion blur](motion_blur.md), what [depth of field](depth_of_field.md) means, what [HDR](hdr.md) is etc.
## Mainstream Realtime 3D
@ -256,11 +256,11 @@ This mainstream rendering is classified as an [object order](object_order.md) ap
Additionally things such as [z-buffering](z_buffer.md) (for determining correct overlap of triangles) and [double buffering](double_buffering.md) are used, which makes this approach very memory ([RAM](ram.md)/[VRAM](vram.md)) expensive -- of course mainstream computers have more than enough memory but smaller computers (e.g. [embedded](embedded.md)) may suffer and be incapable of handling this kind of rendering. Thankfully it is possible to adapt and imitate this kind of rendering even on "small" computers -- even those that don't have a GPU, i.e. with pure [software rendering](sw_rendering.md). For this we e.g. replace z-buffering with [painter's algorithm](painters_algorithm.md) (triangle sorting), drop features like [perspective correction](perspective_correction.md), [MIP mapping](mip_mapping.md) etc. (of course quality of the output will go down).
Also additionally there's a lot of [bloat](bloat.md) added in such as complex [screen space](screen_space.md) shaders, [pathtracing](pathtracing.md) (popularly known as *raytracing*), [megatexturing](megatexturing.md), [shadow rendering](shadow.md), [postprocessing](postprocessing.md), [compute shaders](compute_shader.md) etc. This may make it difficult to get into "modern" 3D rendering. Remember to [keep it simple](kiss.md).
Also on top of that there's a lot of [bloat](bloat.md) added in such as complex [screen space](screen_space.md) shaders, [pathtracing](pathtracing.md) (popularly known as *raytracing*), [megatexturing](megatexturing.md), [shadow rendering](shadow.md), [postprocessing](postprocessing.md), [compute shaders](compute_shader.md) etc. This may make it difficult to get into "modern" 3D rendering. Remember to [keep it simple](kiss.md).
On [PCs](pc.md) the whole rendering process is hardware-accelerated with a [GPU](gpu.md) (graphics card). GPU is a special hardware capable of performing many operations in [parallel](parallelism.md) (as opposed to a [CPU](cpu.md) which mostly computes sequentially with low level of parallelism) -- this is ideal for graphics because we can for example perform mapping and drawing of many triangles at once, greatly increasing the speed of rendering ([FPS](fps.md)). However this hugely increases the [complexity](complexity.md) of the whole rendering system, we have to have a special [API](api.md) and [drivers](driver.md) for communication with the GPU and we have to upload data (3D models, textures, ...) to the GPU before we want to render them. [Debugging](debugging.md) gets a lot more difficult. So again, this is [bloat](bloat.md), consider avoiding GPUs.
On [PCs](pc.md) (and mobiles, ...) the whole rendering process is hardware-accelerated with a [GPU](gpu.md) (graphics card). GPU is a special hardware capable of performing many operations in [parallel](parallelism.md) (as opposed to a [CPU](cpu.md) which mostly computes sequentially with low level of parallelism) -- this is ideal for graphics because we can for example perform mapping and drawing of many triangles at once, greatly increasing the speed of rendering ([FPS](fps.md)). However this hugely increases the [complexity](complexity.md) of the whole rendering system, we have to have a special [API](api.md) and [drivers](driver.md) for communication with the GPU and we have to upload data (3D models, textures, ...) to the GPU before we want to render them. [Debugging](debugging.md) gets a lot more difficult. So again, this is [bloat](bloat.md), consider avoiding GPUs.
GPUs nowadays are no longer just focusing on graphics, but are kind of a general computing device usable for more than just 3D rendering (e.g. [crypto](crypto.md) mining, training [AI](ai.md) etc.) and can no longer even perform 3D rendering completely by themselves -- for this they have to be programmed. I.e. if we want to use a GPU for rendering, not only do we need a GPU but also some extra code. This code is provided by "systems" such as [OpenGL](opengl.md) or [Vulkan](vulkan.md) which consist of an [API](api.md) (an [interface](interface.md) we use from a [programming language](programming_language.md)) and the underlying implementation in a form of a [driver](driver.md) (e.g. [Mesa3D](mesa3d.md)). Any such rendering system has its own architecture and details of how it works, so we have to study it a bit if we want to use it.
GPUs nowadays are no longer just focusing on graphics, but are a kind of "bitch for everything", a general computing device usable for more than just 3D rendering (e.g. [crypto](crypto.md) mining, training [AI](ai.md) etc.) and can no longer even perform 3D rendering completely by themselves -- for this they have to be programmed. I.e. if we want to use a GPU for rendering, not only do we need a GPU but also some extra code. This code is provided by "systems" such as [OpenGL](opengl.md) or [Vulkan](vulkan.md) which consist of an [API](api.md) (an [interface](interface.md) we use from a [programming language](programming_language.md)) and the underlying implementation in a form of a [driver](driver.md) (e.g. [Mesa3D](mesa3d.md)). Any such rendering system has its own architecture and details of how it works, so we have to study it a bit if we want to use it.
The important part of a system such as OpenGL is its **rendering [pipeline](pipeline.md)**. Pipeline is the "path" through which data go through during the rendering process. Each rendering system and even potentially each of its version may have a slightly different pipeline (but generally all mainstream pipelines somehow achieve rasterizing triangles, the difference is in details of how they achieve it). The pipeline consists of **stages** that follow one after another (e.g. the mentioned mapping of vertices and drawing of triangles constitute separate stages). A very important fact is that some (not all) of these stages are programmable with so called **[shaders](shader.md)**. A shader is a program written in a special language (e.g. [GLSL](glsl.md) for OpenGL) running on the GPU that processes the data in some stage of the pipeline (therefore we distinguish different types of shaders based on at which part of the pipeline they reside). In early GPUs stages were not programmable but they became so as to give a greater flexibility -- shaders allow us to implement all kinds of effects that would otherwise be impossible.