From 17c667471c9acc1ec67788f2df229f29a55a5345 Mon Sep 17 00:00:00 2001 From: Mouse <937279156@qq.com> Date: Fri, 15 Jun 2018 10:17:23 +0800 Subject: [PATCH] Update --- source/04-rendering.md | 356 +++++++++++++++++++++ source/_static/04/rendering_pipeline.png | Bin 0 -> 7632 bytes source/_static/04/rendering_pipeline_2.png | Bin 0 -> 11944 bytes source/_static/04/triangle_coordinates.png | Bin 0 -> 6425 bytes source/index.rst | 1 + 5 files changed, 357 insertions(+) create mode 100644 source/04-rendering.md create mode 100644 source/_static/04/rendering_pipeline.png create mode 100644 source/_static/04/rendering_pipeline_2.png create mode 100644 source/_static/04/triangle_coordinates.png diff --git a/source/04-rendering.md b/source/04-rendering.md new file mode 100644 index 0000000..ac462b1 --- /dev/null +++ b/source/04-rendering.md @@ -0,0 +1,356 @@ +# 渲染(Rendering) + +在本章中,我们将学习用OpenGL渲染场景时所要做的事情。如果你已经习惯了OpenGL的旧版本,就是习惯使用固定管线,你可能会跳过这一章,不想知道为什么它需要这么复杂。让我给你一个建议吧。它其实更简单,更灵活。你只需要给它一个机会。现代OpenGL让你只需要考虑一个问题,这可以让你以更合理的方式组织代码和开发。 + +将三维表示映射到二维屏幕的一系列步骤被称为图形管线(`Graphics Pipeline`)。OpenGL最初的版本使用了一个被称为固定管线(`Fixed-function Pipeline`)的模型。该模型在绘制过程中定义了一组固定的操作步骤。程序员被每一步可用的函数集约束,可以使用的效果和进行的操作受到API本身(例如,“设置雾”或“添加光照”)的限制,但是这些功能的实现是固定的,并且不能改变。 + +固定管线由以下步骤组成: + +![Graphics Pipeline](_static/04/rendering_pipeline.png) + +OpenGL 2.0 引入了可编程管线(`Programmable Pipeline`)的概念。在这个模型中,组成图形管线的不同步骤可以通过使用一组叫做着色器(`Shader`)的特定程序来控制或编程。下面的图片简单的描述了OpenGL可编程管线: + +![Programmable Pipeline](_static/04/rendering_pipeline_2.png) + +该渲染最初将以顶点缓冲区为形式的一系列顶点作为输入。但是,什么是顶点?顶点(`Vertex`)是描述二维或者三维空间中的点的数据结构。如何描述三维空间中的一个点呢?通过指定其X、Y和Z坐标。什么是顶点缓冲区?顶点缓冲区(`Vertex Buffer`)是使用顶点数组来包装所有需要渲染的顶点的另一种数据结构,并使这些数据能在图形管线的着色器中使用。 + +这些顶点由顶点着色器(`Vertex Shader`)处理,其主要目的是计算每个顶点到屏幕空间中的投影位置。该着色器还可以生成与颜色或纹理相关的其他输出,但其主要目的是将顶点投影到屏幕空间中,即生成点。 + +几何处理(`Geometry Processing`)阶段将由顶点着色器变化的顶点连接成三角形。它考虑到顶点储存的顺序,并使用不同的模型对它们进行分组。为什么是三角形?三角形(`Triangle`)就是显卡的基本工作单元。它是一个简单的几何形状,可以组合和变换,以构建复杂的三维场景。这个阶段还可以使用特定的着色器来对顶点进行分组。 + +光栅化(`Rasterization`)阶段接收之前阶段生成的三角形,剪辑他们,并将它们转换成像素大小的片元。 + +这些片元将在片元处理(`Fragment Processing`)阶段被片元着色器(`Fragment Shader`)使用,以生成像素写入到帧缓冲区的最终颜色。帧缓冲区(`Framebuffer`)是图形管线的最终输出。它储存了每个像素应该被绘制到屏幕上的值。 + +注意,显卡被设计成并行处理上述所有操作。输入数据可以并行处理以生成最终场景。 + +让我们开始写我们的第一个着色器程序。着色器是使用基于ANSI C的GLSL(OpenGL着色器语言)编写的。首先,我们在`resources`目录下创建一个名为“`vertex.vs`”(扩展名为顶点着色器英文简写)的文件,内容如下: + +```glsl +#version 330 + +layout (location=0) in vec3 position; + +void main() +{ + gl_Position = vec4(position, 1.0); +} +``` + +第一行是一个表示我们正在使用的GLSL语言版本的标识符。下表是GLSL版本、与该版本匹配的OpenGL版本和使用方法(来自维基百科: [https://en.wikipedia.org/wiki/OpenGL\_Shading\_Language\#Versions](https://en.wikipedia.org/wiki/OpenGL_Shading_Language#Versions))。 + +| GLSL版本 | OpenGL版本 | 着色器标识符 | +| --- | --- | --- | +| 1.10.59 | 2.0 | \#version 110 | +| 1.20.8 | 2.1 | \#version 120 | +| 1.30.10 | 3.0 | \#version 130 | +| 1.40.08 | 3.1 | \#version 140 | +| 1.50.11 | 3.2 | \#version 150 | +| 3.30.6 | 3.3 | \#version 330 | +| 4.00.9 | 4.0 | \#version 400 | +| 4.10.6 | 4.1 | \#version 410 | +| 4.20.11 | 4.2 | \#version 420 | +| 4.30.8 | 4.3 | \#version 430 | +| 4.40 | 4.4 | \#version 440 | +| 4.50 | 4.5 | \#version 450 | + +第二行指定此着色器的输入格式。OpenGL缓冲区中的数据可以是我们想要的任何东西,也就是说,语言不会强迫你传递预定义语义的任何特定数据结构。从着色器的角度来看,它希望接收带有数据的缓冲区。它可以是一个坐标,一个有一些附加信息的坐标,或者我们想要的任何东西。顶点着色器只接收浮点数组。当我们填充缓冲区时,我们定义要由着色器处理的缓冲块。 + +首先,我们需要把这些块变成对我们有意义的东西。现在,我们要求,从位置0开始,我们期望接收由三个属性(X、Y、Z)组成的向量。 + +着色器有个主代码块,就像任何C语言程序一样,这是示例是非常简单的。它只是将接收到的坐标不经任何变换返回到`gl_Position`。你现在可能想知道为什么三个属性的向量被转换成四个属性的向量(`vec4`)。这是因为`gl_Position`仅接受`vec4`类型的数据,因为它是齐次坐标(`Homogeneous Coordinates`)。也就是说,它希望接收到形似(X, Y, Z, W)的东西,其中W代表一个额外的维度。为什么还要添加另一个维度?在之后的章节中,你会看到我们需要做的大部分操作都是基于向量和矩阵的。如果没有额外的维度,一些操作不能组合。例如,我们不能把旋转和变换操作组合起来。(如果你想学习更多有关于这方面的知识,这个额外的维度允许我们组合仿射和线性变换。你可以通过阅读《3D Math Primer for Graphics and Game development》(作者是Fletcher Dunn 和 Ian Parberry)来更多地了解这一点。) + +现在让我们来看看我们的第一个片元着色器。我们将在`resources`目录下创建一个名为`fragment.fs`(扩展名片元着色器英文简写)的文件,内容如下: + +```glsl +#version 330 + +out vec4 fragColor; + +void main() +{ + fragColor = vec4(0.0, 0.5, 0.5, 1.0); +} +``` + +该结构与我们的顶点着色器非常相似。现在,我们将为每个片元设置固定的颜色。输出被定义为第二行的`vec4`类型的`fragColor`变量。 + +现在我们已经创建了我们的着色器,我们该如何使用它们呢?以下我们要做的一系列步骤: +1. 创建OpenGL程序 +2. 载入顶点和片元着色器文件 +3. 对每个着色器创建一个新的着色器程序并指定它的类型(顶点或片元) +4. 编译着色器 +5. 将着色器绑定到OpenGL程序上 +6. 连接程序 + +最后,着色器将会被载入到显卡中,我们可以通过引用程序ID来使用它。 + +```java +package org.lwjglb.engine.graph; + +import static org.lwjgl.opengl.GL20.*; + +public class ShaderProgram { + + private final int programId; + + private int vertexShaderId; + + private int fragmentShaderId; + + public ShaderProgram() throws Exception { + programId = glCreateProgram(); + if (programId == 0) { + throw new Exception("Could not create Shader"); + } + } + + public void createVertexShader(String shaderCode) throws Exception { + vertexShaderId = createShader(shaderCode, GL_VERTEX_SHADER); + } + + public void createFragmentShader(String shaderCode) throws Exception { + fragmentShaderId = createShader(shaderCode, GL_FRAGMENT_SHADER); + } + + protected int createShader(String shaderCode, int shaderType) throws Exception { + int shaderId = glCreateShader(shaderType); + if (shaderId == 0) { + throw new Exception("Error creating shader. Type: " + shaderType); + } + + glShaderSource(shaderId, shaderCode); + glCompileShader(shaderId); + + if (glGetShaderi(shaderId, GL_COMPILE_STATUS) == 0) { + throw new Exception("Error compiling Shader code: " + glGetShaderInfoLog(shaderId, 1024)); + } + + glAttachShader(programId, shaderId); + + return shaderId; + } + + public void link() throws Exception { + glLinkProgram(programId); + if (glGetProgrami(programId, GL_LINK_STATUS) == 0) { + throw new Exception("Error linking Shader code: " + glGetProgramInfoLog(programId, 1024)); + } + + if (vertexShaderId != 0) { + glDetachShader(programId, vertexShaderId); + } + if (fragmentShaderId != 0) { + glDetachShader(programId, fragmentShaderId); + } + + glValidateProgram(programId); + if (glGetProgrami(programId, GL_VALIDATE_STATUS) == 0) { + System.err.println("Warning validating Shader code: " + glGetProgramInfoLog(programId, 1024)); + } + + } + + public void bind() { + glUseProgram(programId); + } + + public void unbind() { + glUseProgram(0); + } + + public void cleanup() { + unbind(); + if (programId != 0) { + glDeleteProgram(programId); + } + } +} +``` + +`ShaderProgram`类的构造函数在OpenGL中创建一个新的程序,并提供添加顶点和片元着色器的方法。这些着色器被编译并绑定到OpenGL程序中。当所有的着色器都被绑定时,应该调用`link`方法,来连接所有代码并严重所有操作都已正确地完成。 + +一旦着色器程序被连接,编译的顶点和片元着色器可以被释放(通过调用`glDetachShader`方法) + +关于验证,是通过调用`glValidateProgram`方法完成的。此方法主要用于调试,当游戏到达生产阶段时,应将其删除。此方法将验证在**当前OpenGL状态**下着色器是否正确。这意味着,即使着色器是正确的,在某些情况下也可能验证失败,这是因为当前状态不够完整(一些数据可能尚未加载),无法运行着色器。因此,我们可以将错误信息输出到标准错误输出中。 + +`ShaderProgram`类还提供了在渲染时激活该程序(绑定)和停止使用它(解绑)的方法。最后,它提供了一个`cleanup`方法,用于当它不再被需要时,释放所有资源。 + +既然我们有一个清理方法,让我们更改我们的`IGameLogic`接口来添加一个`cleanup`方法: + +```java +void cleanup(); +``` + +这个方法将在游戏循环结束时调用,所以我们需要修改`GameEngine`类的`run`方法: + +```java +@Override +public void run() { + try { + init(); + gameLoop(); + } catch (Exception excp) { + excp.printStackTrace(); + } finally { + cleanup(); + } +} +``` + +现在我们可以使用着色器来显示一个三角形。我们将在我们的`Renderer`类的`init`方法中进行。首先,我们要创建着色器程序: + +```java +public void init() throws Exception { + shaderProgram = new ShaderProgram(); + shaderProgram.createVertexShader(Utils.loadResource("/vertex.vs")); + shaderProgram.createFragmentShader(Utils.loadResource("/fragment.fs")); + shaderProgram.link(); +} +``` + +我们已经创建了一个工具类,它提供了一个从类路径中取得文件内容的方法。此方法用于取得着色器的内容。 + +现在我们可以把我们的三角形定义为一组浮点数。我们创建一个一维浮点数组,它将定义三角形的顶点。正如你所看到的,数组中没有结构。就目前而言,OpenGL无法知道该数组的结构。这只是一组浮点数: + +```java +float[] vertices = new float[]{ + 0.0f, 0.5f, 0.0f, + -0.5f, -0.5f, 0.0f, + 0.5f, -0.5f, 0.0f +}; +``` + +下图体现了在坐标系中的三角形。 + +![Coordinates Syste](_static/04/triangle_coordinates.png) + +现在我们有了坐标,我们需要把它们储存到我们的显卡中,并告诉OpenGL它的结构。现在我们将介绍两个重要的概念,顶点数组对象(`Vertex Array Object`, VAO)和顶点缓冲对象(`Vertex Buffer Object`, VBO)。如果你对下一段代码感到疑惑,请记住我们所做的是我们将要绘制的模型对象数据传递到显卡的储存器中。当我们储存它的时候,我们将得到一个ID,稍后再绘制时我们会使用它。 + +让我们先介绍顶点缓冲对象(VBO)吧。VBO只是显卡储存器中存储顶点的内存缓冲区。这是我们用来暂存一组三角形模型的浮点数的地方。正如我们之前所说的,OpenGL对我们的数据结构一无所知。事实上,它不仅可以保存坐标,还可以保存其他信息,比如纹理、颜色等。 + +顶点数组对象(VAO)是包含一个或多个VBO的对象,通常被称为属性列表。每个属性列表可以保存一种类型的数据:位置、颜色、纹理等。在每个渲染间隔中,你可以自由地储存所需的任何数据。 + +一个VAO就像是一个包装,它按一组定义对储存在显卡中的数据分组。当我们创建一个VAO是,我们得到一个ID。我们使用这个ID来渲染它和使用它在创建过程中按特定定义的数据。 + +让我们继续编写我们的示例。我们必须做的第一件事就是把我们的浮点数储存在一个`FloatBuffer`中。这主要是因为我们必须使用基于C语言的OpenGL库的接口,所以我们必须把浮点数转换成可以由库管理的东西。 + +```java +FloatBuffer verticesBuffer = MemoryUtil.memAllocFloat(vertices.length); +verticesBuffer.put(vertices).flip(); +``` + +我们使用`MemoryUtil`类来在堆内存中创建一个缓冲区,以便于OpenGL库访问。在我们储存了数据(调用`put`方法)之后,我们需要调用`flip`方法将缓冲区的位置重置为0(也就是说,我们已经完成了对它的写入)。记住,Java中的对象,被分配在一个叫堆(`Heap`)的内存空间。堆是JVM内存中保留的一大堆内存。储存在堆中的对象不能通过本地代码访问(JNI,这种机制使得Java不能直接调用本地代码)。Java代码和本地代码直接共享内存数据的唯一方法是直接在Java分配内存。 + +如果你来自LWJGL的旧版本,强调一些要点是很重要的。你可能注意到了,我们不使用工具类`BufferUtils`来创建缓冲区。相反,我们使用`MemoryUtil`类。这是由于`BufferUtils`不是非常有效的,并且仅被用于向后兼容。相反,LWJGL 3提供了两种缓冲区的管理方法: + +* 自动管理缓冲区,即由垃圾回收器自动回收的缓冲区。这些缓冲区主要用于短暂的操作,或者用于传递到GPU的数据,并且不需要存在于进程内存中。这是通过使用`org.lwjgl.system.MemoryStack`实现的。 +* 手动管理缓冲区。这种情况下,一旦我们完成操作,我们需要小心地释放它们。这些缓冲区用于长时间操作或者大量的数据。这是通过使用`MemoryUtil`类实现的。 + +你可以在此处查阅细节: +[https://blog.lwjgl.org/memory-management-in-lwjgl-3/](https://blog.lwjgl.org/memory-management-in-lwjgl-3/ "here") + +在这种情况下,我们的数据被发送到GPU,这样我们就可以使用自动管理的缓冲区。但是,稍后我们将使用他们来储存可能需要手动管理的大量数据。这就是为什么我们使用`MemoryUtil`类,因此,这就是为什么我们要在最后一个块中释放缓冲区资源。在下一章中,我们将学习如何使用自动管理缓冲区。 + +现在我们需要创建VAO然后绑定它。 + +```java +vaoId = glGenVertexArrays(); +glBindVertexArray(vaoId); +``` + +然后我们需要创建VBO,绑定它并将数据输入。 + +```java +vboId = glGenBuffers(); +glBindBuffer(GL_ARRAY_BUFFER, vboId); +glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW); +memFree(verticesBuffer); +``` + +现在是最重要的部分。我们需要定义数据的结构,并将其储存在VAO的属性列表中。这是用下面一行代码完成的。 + +```java +glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0); +``` + +它的参数是: + +* index: 指定着色器期望此数据的位置。 +* size: 指定每个顶点属性的数据数(从1到4)。现在,我们使用三维坐标,所以它应该是3。 +* type: 指定数组中每个数据的类型,现在是浮点数。 +* normalized: 指定值是否应规范化。 +* stride: 指定连续顶点数据之间的字节偏移量(稍后我们再解释)。 +* offset: 指定缓冲区中第一个数据的偏移量。 + +在我们完成了我们的VBO之后,我们可以解除它和VAO的绑定(绑定到0)。 + +```java +// 解绑VBO +glBindBuffer(GL_ARRAY_BUFFER, 0); + +// 解绑VAO +glBindVertexArray(0); +``` + +一旦完成,我们**必须**释放由`FloatBuffer`占用的堆内存,这是通过手动调用`memFree`方法完成的,因为Java垃圾回收不会清理分配的堆内存。 + +```java +if (verticesBuffer != null) { + MemoryUtil.memFree(verticesBuffer); +} +``` + +这就是我们的`init`方法应该有的代码。我们的数据已经在显卡中,准备使用了。我们只需要修改我们的`render`方法在游戏循环中进行渲染。 + +```java +public void render(Window window) { + clear(); + + if ( window.isResized() ) { + glViewport(0, 0, window.getWidth(), window.getHeight()); + window.setResized(false); + } + + shaderProgram.bind(); + + // 绑定VAO + glBindVertexArray(vaoId); + glEnableVertexAttribArray(0); + + // 绘制顶点 + glDrawArrays(GL_TRIANGLES, 0, 3); + + // 还原状态 + glDisableVertexAttribArray(0); + glBindVertexArray(0); + + shaderProgram.unbind(); +} +``` + +正如你所看到的,我们只需要清理窗口,绑定着色器程序,绑定VAO,绘制储存在VAO关联的VBO中的顶点,然后还原状态。仅此而已。 + +我们还在我们的`Renderer`类中添加了一个`cleanup`方法用于释放资源。 + +```java +public void cleanup() { + if (shaderProgram != null) { + shaderProgram.cleanup(); + } + + glDisableVertexAttribArray(0); + + // 删除VBO + glBindBuffer(GL_ARRAY_BUFFER, 0); + glDeleteBuffers(vboId); + + // 删除VAO + glBindVertexArray(0); + glDeleteVertexArrays(vaoId); +} +``` + +就这样!如果你小心地按着上述步骤做,你会看到类似的东西。 + +这是我们的第一个三角形!你也许会想这并不会使它成为前十名的游戏,这是正确的。你也可以认为这是一件无聊的工作来画一个无聊的三角形。但请记住,我们正在介绍关键的概念,并准备基于架构来做更复杂的事情。请耐心等待,继续阅读。 diff --git a/source/_static/04/rendering_pipeline.png b/source/_static/04/rendering_pipeline.png new file mode 100644 index 0000000000000000000000000000000000000000..c6da42acebc8398a1ea672d2ba6e2d8dffeaa2d6 GIT binary patch literal 7632 zcmcIpXIPV4wgy2uf^-n6N-rWsKtc(GDj>Zmy(APxf+!e@l+Zf}N=FcoDiEZDG((Zz zL+>>pEp)iinR936o;mYt?vJc5dq4a6_Orgd_PgHouF%I?D&!1AHW4)DPWnhH2LWznSPR@bp(;uoqWZa6qMT7Ug; zkuLeRI5@ZM)RYwTJpD8(Q}SjP(p1>+4gdutZJR&uCnb#891H|-&-&+&Xy$5Z=?iQ*?! zn4_auNj&PR)v8$^*lODA7#8cr^`Sgf3s&qL20f`tEaG-?>Yh^?9Xh#4<(Cix^4MP? zzb}~-IvHRab6w%|G!u|>ZE%bb%}yKBC<~$Z{Py5b0j7FAr<4Lk?Qu0jDt9#ih6MCq zEXKjhsOSuiwzEveDbNmnIZ4X-x(|jO*F$lE^R;wg zx`hRV(1b_NkY6N=inlKyKXp{~rKAFYG zT8>YZz=ZPGAt>^(d3*P>=;E$#24G@HuqTV))`7i1s{jx;6ahpUD`bKh2F$hf1SdM_(^BK?17$z%SzhPyD1X35xe+ z-gK9|i0MMKf9sNkXmbp!p|Xh#c=+)gLHn66CVrg&Eia|wyR_*>LDkOg8!Kvo z(zF=1&3u)ixFg4pWR%j3R>St%5j@{Md}(w;T|0h30MQ20HPI;0DbY()WCewZ zi3$`Y84%KTWI=rASEwa;grl+wDrfw4%V0dkC)`1&c|i}^D}%QxOvA3a^22`>V46-3x~q!5O|6#ts9p8l%jsi^D5Q>x2IEyHJng zq}?p;r>wpw0kDCV1^2fYfwZg3d3rmwL#VL+_A-gnC_bu!1cR|KzdCsMDZIXM;*wZz zj09D|k>2EW*gSfqp8TS?TNHW=RUvJDv8(L;#-bFwB!K{bs$N}tcy#11?Q|3|6@*E} zIeLW|+hwdF|2xk8!?o1)$%?9~*C%_+ZmYc)0WaM!TN9-8Le9^gu{%j{J@4Dz-j=&M z(Y6g?c;92+k>(dV>9)H7vW1UJx~-5w!Ile{>Zfe{Z9kfuaRm_!4x;5^6x9-p{n;{e67mwhsQ-mpB-zy_(&PEryWHosC18jbB z&1u^NU?7f|%toW#Ya=wZUX@|{*~HdjPC7y#s6VasY(7DLs~-;NTyGZ;Hp3T?kcWw7 zafrh>e2TVq18K3a)*eq#m1C`a7hh{gTKR6Sb%>m?<68`5WnAwS*JH(#F&0sgL4>0# zo)!59Gd#pj<(v8o6)DD6P~l>Sn}h%ROhB^fyE?v`g=1_asrFr#?Y4{pMSen{)&RT4 zJa;mY(E-Pd8dlae0`$Oe`3#@K0B2#Y{nr31DBBPIQN&lOfz`-QhEqiim9(sbzt@y> z8yX5m1SKXO@(Qc6|U z%mHW+oEYQB1IoXbJd7^TEq}D<^$;>-ee}ARgw`l^@2GY&`o$E=|fkE>_{cDLM|WcE>xA3ea2+c zv6)hab1Q=?p*xrn4N_EgkS-5B`&95_2h(cTz)Os)-8YX}H|I(tZmX~G9t8IY7khXf z5u9g}P@mBjLbVA z7tM%{Fuu0_`p10FAwh{UU?!4c=tv84RLt#>b2&iunf!Qs+$}CKO%s7=hORZWYa(L8 z`bW464vW%^pI{^P4brM6ptEK{#=%qu zL8ecGqq#W?AEWbDHgJ_*&a#7hO~k>!{kY{2T@={usdbrpKPMNw&5fZ;i@h!6KV`R# z3-x%Q(3@IU;D%l3OnS?)PmQWD)G>QPZ@`ZXRcK=6k10ww*6oBYF@B}bWy}O{1AVCX z1T@mVwZN=$aZ!q59ch1MvDdIwIE)pisgyBQ&X%gZsO>#mRfjdB$ShHqj_Upc@=-0J-^3!o6il|wnJE&M&3oS zcsM{$kcJ8w{2wRE}9bzsC! z>kSNL$Hb-&QA1OmGcPQN?iJ5P>#l3B_$P zY}yQ`7v|t^`~E=`9P^V}OQQ<5d8WMFrU@wF3A35ju* z4r<>#mF3EFyGWVw5eu@m=Lgf($={P>5AIX0ws+tP?p8c%=(kSx z>#5|nMX0${)2azpO!r5;Iy53sn)3|YvvB$K;b9*@MVS?A+c;ykQIVHBPbk$g$kjzwuRt9G)vpXD{*cHV?Ta z6jC7X2BG^ft_15!<^sL}b*i5qlw9gR@Y@l7O;U`dkAViYYo3nV-TK$sFJ5LoPqyQd z=uf}lY3&n>9t<}2-@b(6GWI$||r!uQFj(c2nkBN7)ILwGMP*Y=NBKyYu*C|ok zYbYGbZ#d}K!26Z*?DzP(ba|cfJ;?4nu}U+h8VgjG8g>=tY(H##w-bfi1Sdyv61Ve) zR*rny52;P$8a5@J2oRZtxP1JnxZE)y2BgG8sOS!tf?xhMzQ|opYo!z zKdusCJkB$tG7fHj&1sCVh{3aiX8!FBA2*GDy&+)hron4~*)Cir9hfOocc5NuLKJxK z-{s;zXXk&++;fE;gLd)ej>H4@SNk~cwtdG|nz6sGC0HVb9eYdP@f-c^mb#)*;*Gv0 zlI|PBzzxY<7m6}V-_h9rTi2RM&&r;kG^9*6j7<~veX;bGl!cx50asWNGE_(gNBn-4 z&d3sBVt@psFZnn3Rm;hkcfp6+`D_k;TFE;SHlw{u-$aiyz{Gw|Gcm!&VGd7%0~!a| zywtrxJE~a#s$JBOzRu}D!rlG^c!?TX;VKCoY0S4TwmhEZA)GjLUf%hewSoEfZOp$#- zj*ZUqo4me8oh;SUuvyY~AU5bBE$bG#o1?aIEAJsW>`WM3^(uj`VeC-5Y0`U-nO(UL z2bPF5wonV&J6|r0ZuH1MfA1zW>EkJ~wCb!C7GH^M>bPSO_FdoOVs(DiLe4Nzzcj@H zaC$K(o>)xGeTj;n4P3D4!FTNw>7sS_KD4-U^EjrrFQv$SvzmN=+u=K4h{vI2nWap` zVLB5~*a{fRUxkdlq!?o`SA_JbyJ|~ppK#?bvgA+Oi2ohr_ak9luF-?reoSUgq<`Y= zzr+hsZx{xH5wd9X+Mbd+U!~Mt+?uGM7kWC5LIFpevA{k(ZRT^)1^i(zy43rv4PE20 z%TW$=PL*=Q(nD;ZJr#Dfy&*Uh|6bTot{J^?iFPH^5SEowX@;JSho5|$rmdcVAt@|@yyu+Hd& z_TGtR@sdr(zGeE6Bl5_pp889l;0I5335XzX zwjdp>Z(?Y1f!ZeZ9Gx5lh$cm2wU$U!F8Jk@kM zpt^0*aPMWUk4t{l)$Xait!?cb`bPS**}as?e7+vR84w#hw8AUJ?7I8{)u?nJT`RHN zXhFe-8vk-aZ!1j--H_>7$Y)1^i2n2%HdcA?1B9No2_4zW#7(K<5C*1qVQb#qEY3Nv zlHK0lPlZ@TGLiH^Z=%cIdf~iNAlwUY9FIGWE%A|BAiY`04as#!Z^HPTfI7t~bj|$X zmZ~C=nD_FsZ@VlNe^d!YICZbv#k!4#UyrgjpZeV3vb~M~_gYC4%8XDWdgWncY^=67 zu}FdlhWtNS>Z)K18aW&v3sFhmOBr2k;K_P3{SG|)3Px-kKCOXX|2g= zX$j^;*_>i1NipEt#Mqes0H*Nq&c;0C43w-z?>23U^0}t|pz(bAql)_qH9=%-1Z(s@ z-iJM?f|X(!kyZ0)zg6_{T}77|r$+a6-P7yH^IKscK)W!XdE~q2%=Jv!HSG?%5~9!O z>bgLhY&Rb|=W9t?OZ2g*=mB+l^&eQMl7&2x1NBm3+I$AkJ0KToPM3Uo_)LAyhnqWg zM?6^oAh}w4eE3w1dOvJ(d6Ln2n)OTPKLgDl%DI6X=aK9VpNk24nb(aw_CT_ z5h~M2WgV1QYgr7Scw>xy#9n&1cQF7H0@D%ny~V$g*R~LLS;BK7i`00XMja1WFn<+| zMY-)1<+;Jg71^lcjR$LePv7zEnHQ1L4RfD9BqSL87+Y77g<%dIXksg(L=A=eTB$bD zL`K>kb-HnDR?A+UmQj6rGkw0HV_LqySV|=JJZiU+yD4euL51e>QNUO)!E*5K=~{Sj zpG6}u)3qY!wb0(trW}RK)miBM>?3Gz!|5V9FoFeD@f0gKsGW$c*1Gxb{QN0g^pp@= z8n{x3bj5AByMS3qM+vd=1dH5R9~MUSYCF0Cb~YQP3NXVqVc{G0Sb!DVh8m*LO>H^u z0hpp2M+#eu!oD#ZUSAF`YQ5?UfH1?bGq%Lb6Qp-NRMyH8kOe6G?p;uOHuvG|-p(W7 z7z+5W>MWM&f=@A9XtY_Q*U40k+i~Aj&i9E5u<+CBFO`+>q6uvIQ&(4aek~IIAH@o? zCeIuHz35jNxabktdk8dCIA)16*x7(wBDf&Y3&&_zIx1cj=T3)@IEI|*Wd*$Fg;MwiaN61rvWi{!Z#!jIRcI8 zAllrO(jgnTc8>&VoA|j?wnEoBmwAX3H&iDl(R#wFM7DmIm%3%#G$OCwQHNbqQFx3R zPXJ2tVM5SLOHdk}lwajFvoduvK6*VTp*29AH;ybn^{GFtJ#6VYw48~S)S#p5GyQi$ zgaS<3VGLJ@$9j1WVjj>qm@X!eg7Pm{i44nLcgJoOK>-QCZuL1ppo*hzQBALFA;{4A zBkhVKX%~w)UsRCyVi4$upV-YuSPvb2!!=ZxlHSq(e@RI3?FkWH=4odU}q^~0rpb?B=hUcyrO<&<#pJbC@ftvr3by>px8 z68>)U+s15-#Em1$Y=O__Sjn=?(i=;b=5{Ntp8ib$ozj!YEulq#L4Piu4J|2fw*$tJ z7ME-zCzhLM?k64wa7+=+k%GX*e)O5gvRBKuH(1DtJk$Rra~xUCd9SM5f8)LqnT+=-&F}p6e6}&N zi1OB0sfEg~dYUDP)E55UbN~A(o-%b6;eRfo|EjqDtNC#;m@F(X)}Q`p26?P5wP3Zk zr;<^b(yBm%hd_$*!I%^Gy4#;OI68Z80!z|K(1TEj&nT1)&WSV+K_}*jfAV8~G zu9@dsybrLxRTZ>Ni`qPQ_Vs)gF5W#%3yX3AZ1l9`B!n(lF4J%u} z<%Z`4?d1~ni=i>3DJXtA74q0~wa7zmuWs?Ou*lkJ zjJYv$kw|#kn=7DjvA`ZVT5KYoOv@$F!!k$#1;52mlK;0YR$<5XI|#q+I1yxc?Ga;X zRQ=g@-ESp+|6pAYtHf9PUdjGe;w4|bdUbqhH;=BGHCMfjJV3M4ZS_f0{Y#ru@#Jjh aiZB5%O9YVVsKs_+anwLsN+m!`|NjBih1+rf literal 0 HcmV?d00001 diff --git a/source/_static/04/rendering_pipeline_2.png b/source/_static/04/rendering_pipeline_2.png new file mode 100644 index 0000000000000000000000000000000000000000..27ebbad4a834fe5ee87e1c45336c0b0bea7b338d GIT binary patch literal 11944 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 WX%U+1UGQ@T#LUFPxCDJU>c0Sh%lPX6 literal 0 HcmV?d00001 diff --git a/source/index.rst b/source/index.rst index a442cca..ef3537c 100644 --- a/source/index.rst +++ b/source/index.rst @@ -8,3 +8,4 @@ 01-first-steps 02-the-game-loop 03-a-brief-about-coordinates + 04-rendering