mirror of
https://github.com/mouse0w0/lwjglbook-CN-Translation.git
synced 2025-08-22 12:15:30 +08:00
update chapter 04 and 05
This commit is contained in:
@@ -2,25 +2,25 @@
|
||||
|
||||
在本章中,我们将学习用OpenGL渲染场景时要做的事项。如果你已经习惯了OpenGL的旧版本,习惯了使用固定管线,你可能会跳过这一章,不想知道为什么它需要这么复杂。它其实更简单、更灵活,你只需要给它一个表现的机会。现代OpenGL使你只需考虑一个问题,这可以使你以更合理的方式组织代码和开发。
|
||||
|
||||
将三维表示映射到二维屏幕的一系列步骤被统称为图形管线(`Graphics Pipeline`)。OpenGL最初的版本使用了一个被称为固定管线(`Fixed-function Pipeline`)的模型。该模型在绘制过程中定义了一组固定的操作步骤,程序员被每一步骤可用的函数集约束,可以使用的效果和可进行的操作受到API(例如,“设置雾”或“添加光照”)的限制,但是这些功能的实现是固定的,并且不能修改。
|
||||
将三维表示映射到二维屏幕的一系列步骤被统称为图形管线(Graphics Pipeline)。OpenGL最初的版本使用了一个被称为固定管线(Fixed-function Pipeline)的模型。该模型在绘制过程中定义了一组固定的操作步骤,程序员被每一步骤可用的函数集约束,可以使用的效果和可进行的操作受到API(例如,“设置雾”或“添加光照”)的限制,但是这些功能的实现是固定的,并且不能修改。
|
||||
|
||||
图形管线由以下操作步骤组成:
|
||||
|
||||

|
||||
|
||||
OpenGL 2.0 引入了可编程管线(`Programmable Pipeline`)的概念。在该模型中,组成图形管线的不同步骤可以通过使用一组叫做着色器(`Shader`)的特定程序来控制或编程。下图简单的展示了OpenGL可编程管线:
|
||||
OpenGL 2.0 引入了可编程管线(Programmable Pipeline)的概念。在该模型中,组成图形管线的不同步骤可以通过使用一组叫做着色器(Shader)的特定程序来控制或编程。下图简单的展示了OpenGL可编程管线:
|
||||
|
||||

|
||||
|
||||
该渲染方式最初将以顶点缓冲区为形式的一系列顶点作为输入。但是,什么是顶点?顶点(`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[]{
|
||||
|
||||

|
||||
|
||||
现在我们有了坐标,需要把它们储存到显卡中,并告诉OpenGL它的数据结构。现在将介绍两个重要的概念,顶点数组对象(`Vertex Array Object`,VAO)和顶点缓冲对象(`Vertex Buffer Object`,VBO)。如果你对接下来的代码感到困惑,请记住,现在所做的是把将要绘制的模型对象的数据传递到显存中。当储存它的时候,我们会得到一个ID,稍后绘制时会使用它。
|
||||
现在我们有了坐标,需要把它们储存到显卡中,并告诉OpenGL它的数据结构。现在将介绍两个重要的概念,顶点数组对象(Vertex Array Object,VAO)和顶点缓冲对象(Vertex Buffer Object,VBO)。如果你对接下来的代码感到困惑,请记住,现在所做的是把将要绘制的模型对象的数据传递到显存中。当储存它的时候,我们会得到一个ID,稍后绘制时会使用它。
|
||||
|
||||
先介绍顶点缓冲对象(VBO)吧,VBO只是显存中存储顶点的内存缓冲区。这是用来暂存一组用于建模三角形的浮点数的地方。如上所述,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提供了两种缓冲区的管理方法:
|
||||
|
||||
|
@@ -132,7 +132,7 @@ public void init() throws Exception {
|
||||
|
||||

|
||||
|
||||
最后,我们需要更多的内存来储存重复的数据,这就是索引缓冲区(`Index Buffer`)大显身手的时候。为了绘制四边形,我们只需要以这样的方式指定每个顶点:V1, V2, V3, V4。每个顶点在数组中都有一个位置。V1在位置0上,V2在位置1上,等等:
|
||||
最后,我们需要更多的内存来储存重复的数据,这就是索引缓冲区(Index Buffer)大显身手的时候。为了绘制四边形,我们只需要以这样的方式指定每个顶点:V1, V2, V3, V4。每个顶点在数组中都有一个位置。V1在位置0上,V2在位置1上,等等:
|
||||
|
||||
| V1 | V2 | V3 | V4 |
|
||||
| --- | --- | --- | --- |
|
||||
|
Reference in New Issue
Block a user