update chapter 04 and 05

This commit is contained in:
Mouse
2019-10-10 23:26:05 +08:00
parent 9036e661e7
commit d96594f1d4
2 changed files with 11 additions and 11 deletions

View File

@@ -2,25 +2,25 @@
在本章中我们将学习用OpenGL渲染场景时要做的事项。如果你已经习惯了OpenGL的旧版本习惯了使用固定管线你可能会跳过这一章不想知道为什么它需要这么复杂。它其实更简单、更灵活你只需要给它一个表现的机会。现代OpenGL使你只需考虑一个问题这可以使你以更合理的方式组织代码和开发。
将三维表示映射到二维屏幕的一系列步骤被统称为图形管线(`Graphics Pipeline`。OpenGL最初的版本使用了一个被称为固定管线`Fixed-function Pipeline`的模型。该模型在绘制过程中定义了一组固定的操作步骤程序员被每一步骤可用的函数集约束可以使用的效果和可进行的操作受到API例如“设置雾”或“添加光照”的限制但是这些功能的实现是固定的并且不能修改。
将三维表示映射到二维屏幕的一系列步骤被统称为图形管线Graphics Pipeline。OpenGL最初的版本使用了一个被称为固定管线Fixed-function Pipeline的模型。该模型在绘制过程中定义了一组固定的操作步骤程序员被每一步骤可用的函数集约束可以使用的效果和可进行的操作受到API例如“设置雾”或“添加光照”的限制但是这些功能的实现是固定的并且不能修改。
图形管线由以下操作步骤组成:
![图形管线](_static/04/rendering_pipeline.png)
OpenGL 2.0 引入了可编程管线(`Programmable Pipeline`)的概念。在该模型中,组成图形管线的不同步骤可以通过使用一组叫做着色器(`Shader`的特定程序来控制或编程。下图简单的展示了OpenGL可编程管线
OpenGL 2.0 引入了可编程管线Programmable Pipeline的概念。在该模型中组成图形管线的不同步骤可以通过使用一组叫做着色器Shader的特定程序来控制或编程。下图简单的展示了OpenGL可编程管线
![可编程管线](_static/04/rendering_pipeline_2.png)
该渲染方式最初将以顶点缓冲区为形式的一系列顶点作为输入。但是,什么是顶点?顶点(`Vertex`是描述二维或者三维空间中的点的数据结构。如何描述三维空间中的一个点呢通过指定其X、Y和Z坐标。什么是顶点缓冲区顶点缓冲区`Vertex Buffer`)是使用顶点数组来包装所有需要渲染的顶点的另一种数据结构,并使这些数据能够在图形管线的着色器中使用。
该渲染方式最初将以顶点缓冲区为形式的一系列顶点作为输入。但是什么是顶点顶点Vertex是描述二维或者三维空间中的点的数据结构。如何描述三维空间中的一个点呢通过指定其X、Y和Z坐标。什么是顶点缓冲区顶点缓冲区Vertex Buffer是使用顶点数组来包装所有需要渲染的顶点的另一种数据结构并使这些数据能够在图形管线的着色器中使用。
这些顶点由顶点着色器(`Vertex Shader`)处理,顶点着色器的功能是计算每个顶点到屏幕空间中的投影位置。该着色器还可以生成与颜色或纹理相关的其他输出,但其主要目的还是将顶点投影到屏幕空间中,即生成点。
这些顶点由顶点着色器Vertex Shader处理顶点着色器的功能是计算每个顶点到屏幕空间中的投影位置。该着色器还可以生成与颜色或纹理相关的其他输出但其主要目的还是将顶点投影到屏幕空间中即生成点。
几何处理阶段(`Geometry Processing`)将由顶点着色器变换的顶点连接成三角形。它依照顶点储存的顺序,使用不同的模型对顶点进行分组。为什么是三角形?三角形就是显卡的基本工作单元,它是一个简单的几何形状,可以组合和变换,以构建复杂的三维场景。此阶段还可以使用特定的着色器来对顶点进行分组。
几何处理阶段Geometry Processing将由顶点着色器变换的顶点连接成三角形。它依照顶点储存的顺序使用不同的模型对顶点进行分组。为什么是三角形三角形就是显卡的基本工作单元它是一个简单的几何形状可以组合和变换以构建复杂的三维场景。此阶段还可以使用特定的着色器来对顶点进行分组。
光栅化(`Rasterization`)阶段接收此前生成的三角形,剪辑它们,并将它们转换为像素大小的片元。
光栅化Rasterization阶段接收此前生成的三角形剪辑它们并将它们转换为像素大小的片元。
这些片元将在片元处理阶段(`Fragment Processing`)被片元着色器(`Fragment Shader`)使用,以生成写入到帧缓冲区的像素的最终颜色。帧缓冲区(`Framebuffer`)是图形管线的最终输出,它储存了每个像素应该被绘制到屏幕上的值。
这些片元将在片元处理阶段Fragment Processing被片元着色器Fragment Shader使用以生成写入到帧缓冲区的像素的最终颜色。帧缓冲区Framebuffer是图形管线的最终输出它储存了每个像素应该被绘制到屏幕上的值。
注意,显卡被设计成并行处理上述所有操作,输入的数据可以并行处理以生成最终场景。
@@ -58,7 +58,7 @@ void main()
首先需要把这些块变成对我们有意义的数据。现在规定从位置0开始我们期望接收由三个属性(X, Y, Z)组成的向量。
着色器有个`main`代码块就像任何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来更多地了解这一点。
着色器有个`main`代码块就像任何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`(扩展名片元着色器英文简写)的文件,内容如下:
@@ -225,7 +225,7 @@ float[] vertices = new float[]{
![三角形](_static/04/triangle_coordinates.png)
现在我们有了坐标需要把它们储存到显卡中并告诉OpenGL它的数据结构。现在将介绍两个重要的概念顶点数组对象`Vertex Array Object`VAO和顶点缓冲对象`Vertex Buffer Object`VBO。如果你对接下来的代码感到困惑请记住现在所做的是把将要绘制的模型对象的数据传递到显存中。当储存它的时候我们会得到一个ID稍后绘制时会使用它。
现在我们有了坐标需要把它们储存到显卡中并告诉OpenGL它的数据结构。现在将介绍两个重要的概念顶点数组对象Vertex Array ObjectVAO和顶点缓冲对象Vertex Buffer ObjectVBO。如果你对接下来的代码感到困惑请记住现在所做的是把将要绘制的模型对象的数据传递到显存中。当储存它的时候我们会得到一个ID稍后绘制时会使用它。
先介绍顶点缓冲对象VBOVBO只是显存中存储顶点的内存缓冲区。这是用来暂存一组用于建模三角形的浮点数的地方。如上所述OpenGL对我们的数据结构一无所知。事实上它不仅可以储存坐标还可以储存其他信息比如纹理、颜色等。
@@ -240,7 +240,7 @@ FloatBuffer verticesBuffer = MemoryUtil.memAllocFloat(vertices.length);
verticesBuffer.put(vertices).flip();
```
我们使用`MemoryUtil`类来在堆外内存中创建了一个缓冲区以便OpenGL库访问它。在储存了数据调用`put`方法)之后,我们需要调用`flip`方法将缓冲区的位置重置为0也就是说我们已经完成了对它的写入。记住Java中的对象被分配在一个叫堆`Heap`的内存空间。堆是JVM内存中保留的一大堆内存储存在堆中的对象不能通过本地代码访问JNI这种机制使得Java不能直接调用本地代码。Java代码和本地代码直接共享内存数据的唯一方法是在Java中直接地分配内存。
我们使用`MemoryUtil`类来在堆外内存中创建了一个缓冲区以便OpenGL库访问它。在储存了数据调用`put`方法)之后,我们需要调用`flip`方法将缓冲区的位置重置为0也就是说我们已经完成了对它的写入。记住Java中的对象被分配在一个叫堆Heap的内存空间。堆是JVM内存中保留的一大堆内存储存在堆中的对象不能通过本地代码访问JNI这种机制使得Java不能直接调用本地代码。Java代码和本地代码直接共享内存数据的唯一方法是在Java中直接地分配内存。
如果你来自LWJGL的旧版本强调一些要点是很重要的。你可能注意到了我们不使用工具类`BufferUtils`,而使用`MemoryUtil`类来创建缓冲区。这是由于`BufferUtils`不是非常有效的并且仅被用于向下兼容。LWJGL3提供了两种缓冲区的管理方法