现在,我们将研究如何将对象的顶点从其本地3D坐标系转换为2D屏幕显示空间。多个对象位于3D世界坐标空间中。单个顶点具有X,Y,Z坐标。这些必须从3D世界空间转换为正在看它们的相机的3D坐标空间。从这里,将顶点从相机空间(也称为视图空间)转换为3D屏幕空间,然后最终在2D屏幕显示空间中显示。形成三角形的三个顶点的组在此过程的早期阶段保持“连接”,直到将它们双线插值进行双线插值,以产生代表三角形以显示在屏幕上的三角形的单个片段的集合。每个片段都有一个3D屏幕X,Y,Z坐标,可用于隐藏表面拆卸等物品。要从3D对象空间移动到3D屏幕空间,使用了模型视图投影矩阵。这些阶段中的每个阶段将在以下小节中更详细地探讨。
我们将从一个由两个三角形组成的对象开始,然后是一个涉及立方体的示例,然后是一组立方体。
两个三角形对象
// ***************************************************
/* THE DATA
*/
// anticlockwise/counterclockwise ordering
private float[] vertices = {
// position (x,y,z), colour (r,g,b), tex coords (s,t)
-0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // top left
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, // bottom right
0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f // top right
};
private int vertexStride = 8; // 8 floats per vertex
private int vertexXYZFloats = 3; // 3 floats for the position
private int vertexColourFloats = 3; // 3 flopats for the colour
private int vertexTexFloats = 2; // 2 floats for the texture coordinates
private int[] indices = { // Note that we start from 0
0, 1, 2, // Triangle 1 is made up of vertices 0, 1 and 2
// in anticlockwise ordering
0, 2, 3 // Triangle 2 is made up of vertices 0, 2 and 3
// in anticlockwise ordering
};
上述代码给出数据结构和下一个代码给出了渲染方法。绘制了两个三角形,并用瓦特(Watt)书的前盖绘制纹理。这两个三角形已从观看者旋转,因此从观看者中看到。
public void render(GL3 gl) {
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
double elapsedTime = getSeconds()-startTime;
Mat4 projectionMatrix = Mat4Transform.perspective(45, aspect);
float zposition = 2f;
//float zposition = 2f+(float)(Math.sin(Math.toRadians(elapsedTime*50)));
Vec3 position = new Vec3(0,0,zposition);
Mat4 viewMatrix = Mat4Transform.lookAt(position, new Vec3(0,0,0), new Vec3(0,1,0));
float angle = -55f;
//float angle = (float)(-115*Math.sin(Math.toRadians(elapsedTime*50)));
Mat4 modelMatrix = Mat4Transform.rotateAroundX(angle);
Mat4 mvpMatrix = Mat4.multiply(viewMatrix, modelMatrix);
mvpMatrix = Mat4.multiply(projectionMatrix, mvpMatrix);
shader.use(gl);
shader.setFloatArray(gl, "model", modelMatrix.toFloatArrayForGLSL());
shader.setFloatArray(gl, "view", viewMatrix.toFloatArrayForGLSL());
shader.setFloatArray(gl, "projection", projectionMatrix.toFloatArrayForGLSL());
shader.setFloatArray(gl, "mvpMatrix", mvpMatrix.toFloatArrayForGLSL());
gl.glActiveTexture(GL.GL_TEXTURE0);
gl.glBindTexture(GL.GL_TEXTURE_2D, textureId1[0]);
gl.glBindVertexArray(vertexArrayId[0]);
gl.glDrawElements(GL.GL_TRIANGLES, indices.length, GL.GL_UNSIGNED_INT, 0);
gl.glBindVertexArray(0);
}
使用静态方法mat4transform.perspective()创建投影矩阵。然后使用静态方法mat4transform.lookat(从,到,向上)创建视图矩阵。这需要三个参数:相机位置,相机正在看的目标以及世界的名义向量;在此示例中,摄像机处于位置(0,0,2),着眼于世界原产地,最初的向量(0,1,0)。革兰氏– SCHMIDT过程(如讲座中所述)用于为视图创建坐标框架(即相机)。
选择–55度的角度将两个三角形旋转X轴旋转,该轴将旋转远离观看器的顶部。静态方法mat4transform.rotatearoundx()用于设置所需的模型转换矩阵。
在下一步中,计算模型视图投影矩阵。重要的是:请记住,矩阵在它们正在转换的顶点之前。因此,它们是按照投影,视图,模型的,因此模型矩阵是第一个应用于顶点的。
然后,使用单独的统一制度将ModelMatrix,ViewMatrix,IdjotectionMatrix和MVPMATRIX的每一个都传递到顶点着色器。为此,必须首先将4x4矩阵转换为浮子数组。方法Mat4.tofloatarrayforglsl()用于执行此操作。它将行柱有序的矩阵转换为存储在浮子数组中的列有序矩阵,这是顶点着色器中使用的数据类型中所需的内容。还将一种新方法添加到着色器类中,以在顶点着色器中设置相关的统一。
在此示例中,我已经将所有4个矩阵传递给着色器,以证明我们可以(i)将模型,视图和投影矩阵分别传递到着色器,或者(ii)将它们组合到CPU上的模型视频预测矩阵中。然后将其传递给着色器,或(iii)同时使用。我们还可以将单个矩阵传递,并将其组合成GPU上的模型观察矩阵 - 这就是Joey在他的教程中所做的。在顶点着色器中编写模型视图预测矩阵的问题是,然后重复对象中每个顶点的相同计算。但是,这对于某些程序可能是必要的,具体取决于所需的内容。在将来的程序中,我们将仅通过模型矩阵和模型视图预测矩阵。原因将在稍后进行解释。
接下来要看的是顶点着色器,该阴影器在下一个代码中给出。大多数应该对您熟悉。新的制服被称为MAT4型,例如统一MAT4 MVPMATRIX,其中包含从主程序发送的模型视图投影矩阵。这用于乘以顶点的位置,以将其转换为3D屏幕空间(或已知的剪辑空间)。评论的输出线显示,可以在顶点着色器中计算模型观看预测矩阵。
#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
layout (location = 2) in vec2 texCoord;
out vec3 aColor;
out vec2 aTexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform mat4 mvpMatrix;
void main() {
//mat4 mvpMatrix2 = projection * view * model;
//gl_Position = mvpMatrix2 * vec4(position, 1.0);
gl_Position = mvpMatrix * vec4(position, 1.0);
aColor = color;
aTexCoord = texCoord;
}
顶点着色器的每个实例在不同的顶点上执行相同的操作。 所有顶点都发送到管道的下一个阶段,在那里它们被重新连接在一起以制作三角形,然后将其栅格隔开以产生一组由片段着色器处理的片段。 片段着色器不需要从我们以前的程序中更改。 它只是使用相关纹理坐标来算出片段的颜色。 我们在三角数据结构中使用的颜色属性被忽略(因此可以从示例中删除)。
我们现在有一个工作的3D世界。
(1) 一个立方体对象
立方体的每个面都有纹理坐标在0.0,0.0至1.0,1.0的范围内
// ***************************************************
/* THE DATA
*/
// anticlockwise/counterclockwise ordering
private float[] vertices = new float[] { // x,y,z, colour, s,t
-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // 0
-0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // 1
-0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // 2
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // 3
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // 4
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // 5
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // 6
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // 7
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 8
-0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, // 9
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, // 10
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // 11
0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, // 12
0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 13
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // 14
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, // 15
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // 16
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, // 17
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, // 18
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // 19
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, // 20
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // 21
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // 22
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f // 23
};
private int[] indices = new int[] {
0,1,3, // x -ve
3,2,0, // x -ve
4,6,7, // x +ve
7,5,4, // x +ve
9,13,15, // z +ve
15,11,9, // z +ve
8,10,14, // z -ve
14,12,8, // z -ve
16,20,21, // y -ve
21,17,16, // y -ve
23,22,18, // y +ve
18,19,23 // y +ve
};
private int vertexStride = 8;
private int vertexXYZFloats = 3;
private int vertexColourFloats = 3;
private int vertexTexFloats = 2;
给出渲染方法和随附的方法,用于防止渲染方法变得混乱。 getModelMatrix()返回模型变换矩阵。 在这里,它只是返回身份矩阵。 GetViewMatrix()将相机设置为位置(2,3,4),查看原点(0,0,0),名义上向量为(0,1,0)。
public void render(GL3 gl) {
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
Mat4 projectionMatrix = Mat4Transform.perspective(45, aspect);
Mat4 viewMatrix = getViewMatrix();
Mat4 modelMatrix = getModelMatrix();
Mat4 mvpMatrix = Mat4.multiply(projectionMatrix, Mat4.multiply(viewMatrix, modelMatrix));
shader.use(gl);
shader.setFloatArray(gl, "model", modelMatrix.toFloatArrayForGLSL());
shader.setFloatArray(gl, "view", viewMatrix.toFloatArrayForGLSL());
shader.setFloatArray(gl, "projection", projectionMatrix.toFloatArrayForGLSL());
shader.setFloatArray(gl, "mvpMatrix", mvpMatrix.toFloatArrayForGLSL());
gl.glActiveTexture(GL.GL_TEXTURE0);
gl.glBindTexture(GL.GL_TEXTURE_2D, textureId1[0]);
gl.glBindVertexArray(vertexArrayId[0]);
gl.glDrawElements(GL.GL_TRIANGLES, indices.length, GL.GL_UNSIGNED_INT, 0);
gl.glBindVertexArray(0);
}
private Mat4 getModelMatrix() {
double elapsedTime = getSeconds()-startTime;
//float angle = -55;
//float angle = (float)(-115*Math.sin(Math.toRadians(elapsedTime*50)));
Mat4 modelMatrix = new Mat4(1);
//modelMatrix = Mat4.multiply(Mat4Transform.rotateAroundY(angle), modelMatrix);
//modelMatrix = Mat4.multiply(Mat4Transform.rotateAroundX(angle), modelMatrix);
return modelMatrix;
}
private Mat4 getViewMatrix() {
double elapsedTime = getSeconds()-startTime;
float xposition = 2;
float yposition = 3;
float zposition = 4;
//float xposition = 3.0f*(float)(Math.sin(Math.toRadians(elapsedTime*50)));
//float zposition = 3.0f*(float)(Math.cos(Math.toRadians(elapsedTime*50)));
Mat4 viewMatrix = Mat4Transform.lookAt(new Vec3(xposition,yposition,zposition),
new Vec3(0,0,0), new Vec3(0,1,0));
return viewMatrix;
}
练习
首先尝试GetViewMatrix()中的评论线。 这些将使围绕世界轴的相机位置旋转,以便相机一直在看世界的起源。 只有一个物体以屏幕上的世界来源为中心,它可以(令人困惑)看起来像对象正在旋转,而不是摄像机旋转。
在更改GetModelMatrix()之前,将视图位置设置回2,3,4的固定值。 现在,在GetModelMatrix()中评论一些行。 在运行程序之前尝试猜测效果。
(2)一群立方体
多维数据集数据结构中使用的纹理坐标已更改,以便每个面使用纹理的一部分。 您应该在v03_gleventlistener.java中检查此数据结构。
Rendering a 5x5 grid of cubes
public void render(GL3 gl) {
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
Mat4 projectionMatrix = Mat4Transform.perspective(45, aspect);
Mat4 viewMatrix = getViewMatrix();
shader.use(gl);
shader.setFloatArray(gl, "view", viewMatrix.toFloatArrayForGLSL());
shader.setFloatArray(gl, "projection", projectionMatrix.toFloatArrayForGLSL());
gl.glActiveTexture(GL.GL_TEXTURE0);
gl.glBindTexture(GL.GL_TEXTURE_2D, textureId1[0]);
for (int i=-2; i<3; ++i) {
for (int j=-2; j<3; ++j) {
Mat4 modelMatrix = getModelMatrix(2f*i, 2f*j);
Mat4 mvpMatrix = Mat4.multiply(projectionMatrix, Mat4.multiply(viewMatrix, modelMatrix));
shader.setFloatArray(gl, "model", modelMatrix.toFloatArrayForGLSL());
shader.setFloatArray(gl, "mvpMatrix", mvpMatrix.toFloatArrayForGLSL());
gl.glBindVertexArray(vertexArrayId[0]);
gl.glDrawElements(GL.GL_TRIANGLES, indices.length, GL.GL_UNSIGNED_INT, 0);
gl.glBindVertexArray(0);
}
}
}
private Mat4 getModelMatrix(float i, float j) {
double elapsedTime = getSeconds()-startTime;
float angle = (float)(elapsedTime*50);
Mat4 modelMatrix = new Mat4(1);
//modelMatrix = Mat4.multiply(modelMatrix, Mat4Transform.rotateAroundY(angle));
modelMatrix = Mat4.multiply(modelMatrix, Mat4Transform.translate(i, 0, j));
//modelMatrix = Mat4.multiply(modelMatrix, Mat4Transform.rotateAroundX(angle));
modelMatrix = Mat4.multiply(modelMatrix, Mat4Transform.rotateAroundY(angle));
return modelMatrix;
}
private Mat4 getViewMatrix() {
double elapsedTime = getSeconds()-startTime;
Vec3 pos = new Vec3(4,6,10);
Mat4 viewMatrix = Mat4Transform.lookAt(pos, new Vec3(0,0,0), new Vec3(0,1,0));
return viewMatrix;
}
(3)更多的立方体
此示例显示了100个随机定位的立方体。该程序类似于v03.java,其中一个主要区别。 立方体位于随机位置,而不是正常网格的一部分。 每次渲染场景时,都必须生成相同的随机位置。 这是使用程序运行后一次创建的随机数的全局数组来完成的。
public void render(GL3 gl) {
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
Mat4 projectionMatrix = Mat4Transform.perspective(45, aspect);
Mat4 viewMatrix = getViewMatrix();
shader.use(gl);
shader.setFloatArray(gl, "view", viewMatrix.toFloatArrayForGLSL());
shader.setFloatArray(gl, "projection", projectionMatrix.toFloatArrayForGLSL());
gl.glActiveTexture(GL.GL_TEXTURE0);
gl.glBindTexture(GL.GL_TEXTURE_2D, textureId1[0]);
for (int i=0; i<100; ++i) {
Mat4 modelMatrix = getModelMatrix(i);
Mat4 mvpMatrix = Mat4.multiply(projectionMatrix, Mat4.multiply(viewMatrix, modelMatrix));
shader.setFloatArray(gl, "model", modelMatrix.toFloatArrayForGLSL());
shader.setFloatArray(gl, "mvpMatrix", mvpMatrix.toFloatArrayForGLSL());
gl.glBindVertexArray(vertexArrayId[0]);
gl.glDrawElements(GL.GL_TRIANGLES, indices.length, GL.GL_UNSIGNED_INT, 0);
gl.glBindVertexArray(0);
}
}