1.9 坐标系
OpenGL要求每个顶点着色器运行后,所有需要显示的顶点都应转换为归一化设备坐标。即每个顶点的x、y、z坐标应在-1.0至1.0之间;超出此范围的坐标将不可见。通常做法是:在自定义坐标范围内定义顶点,然后在顶点着色器中将这些坐标转换为NDC坐标。这些NDC坐标随后传递给光栅化器,最终转换为屏幕上的2D坐标/像素。
将坐标转换为NDC再转换为屏幕坐标的过程通常分步完成:先将物体的顶点依次转换到多个坐标系中,最后转换为屏幕坐标。这种分步转换至多个中间坐标系的优势在于,某些操作/计算在特定坐标系中更易于实现。坐标系有5中:
Local space 局部对象(对象空间)World space 时间空间View space 视图空间(视点空间)Clip space 裁剪空间Screen space 屏幕空间
这些都是顶点最终转换为像素片段前经过的不同变换状态。
1.9.1 整体视图
要将一个空间的坐标转换到另一个坐标空间,我们将使用多个变换矩阵,其中最重要的包括模型矩阵、视图矩阵和投影矩阵。顶点坐标最初以局部坐标的形式存在于局部空间,随后经过处理转化为世界坐标、视图坐标、裁剪坐标,最终形成屏幕坐标。下图展示了整个过程及各变换的作用:

局部坐标是物体相对于其局部原点的坐标,即物体初始所在的位置。下一步是将局部坐标转换为世界空间坐标,这些坐标基于更广阔的世界空间。它们相对于世界的全局原点,同时其他众多物体也相对于世界原点进行定位。随后我们将世界坐标转换为视图空间坐标,使每个坐标都呈现为摄像机或观察者视角下的视图。坐标转换至视图空间后,我们需要将其投影为裁剪坐标。裁剪坐标经过处理后落在-1.0至1.0的范围内,从而决定哪些顶点最终会显示在屏幕上。最后,我们通过称为视口变换的过程将裁剪坐标转换为屏幕坐标,该过程将坐标范围从-1.0至1.0转换为GL.Viewport定义的坐标范围。最终生成的坐标将发送至光栅化器进行片元渲染。
将顶点转换至不同空间的原因在于:某些操作在特定坐标系中更合理或更易实现。例如:修改对象时最适合在局部空间进行,而基于其他对象位置计算特定操作时则以世界坐标系最为合理,以此类推。若想简化流程,可定义单个变换矩阵实现局部空间到裁剪空间的直接转换,但这会降低操作灵活性。
接下来我们将探讨各坐标系特性。
1.9.2 局部空间 Local space
局部空间是指对象所在的坐标空间,即对象的起始位置。假设你在建模软件(如Blender)中创建了一个立方体。尽管最终应用中立方体可能位于不同位置,但其初始原点通常设在(0,0,0)。你创建的所有模型初始位置很可能都是(0,0,0)。因此模型所有顶点都处于局部空间,它们都属于该对象的局部坐标系。
我们使用的容器顶点坐标范围设定为-0.5至0.5,原点为0.0。这些即为局部坐标。
1.9.3 世界空间 World space
如果我们将所有对象直接导入应用程序,它们很可能会在世界原点(0,0,0)附近互相堆叠,这并非我们所期望的效果。我们需要为每个对象定义位置,以便将其置于更广阔的世界中。世界空间坐标顾名思义:即所有顶点相对于(游戏)世界的坐标。这是你需要将对象转换到的坐标空间,使它们能够分散在场景中(最好以逼真的方式呈现)。对象坐标通过模型矩阵从局部空间转换到世界空间。
模型矩阵是一种变换矩阵,它通过平移、缩放或旋转将对象放置到世界中应有的位置/方向。可以将其理解为对房屋进行变换:缩小其尺寸(因为在局部空间中它略显庞大),将其平移至郊区城镇,并在y轴方向向左旋转一点,使其与临近房屋完美契合。上一章节中用于在场景中定位容器的矩阵也可视为模型矩阵的变体;我们通过该矩阵将容器的局部坐标系变换至场景/世界中的不同位置。
1.9.4 视图空间 View space
视图空间即OpenGL中通常所指的摄像机空间(有时也称为摄像机空间或视点空间)。视图空间是将世界空间坐标转换为用户视线前方坐标的结果。因此视图空间即从摄像机视角所见的空间。通常通过平移与旋转组合实现场景变换,使特定元素移至摄像机前方。这些组合变换通常存储在视图矩阵中,用于将世界坐标转换为视图空间坐标。在后续教程中,我们将详细探讨如何创建此类视图矩阵以模拟摄像机。
1.9.5 裁剪空间 Clip space
每次顶点着色器运行结束时,OpenGL期望坐标值处于特定范围内,任何超出此范围的坐标都会被裁剪。被裁剪的坐标将被丢弃,因此剩余坐标最终会成为屏幕上可见的片元。这也是裁剪空间名称的由来。
由于将所有可见坐标限定在-1.0至1.0范围内并不直观,我们定义了专用的坐标集进行操作,并将其转换回OpenGL要求的NDC坐标系。
为了将顶点坐标从视图空间转换至裁剪空间,我们定义了所谓的投影矩阵,该矩阵为每个维度指定坐标范围(例如-1000至1000)。投影矩阵将此范围内坐标转换为归一化设备坐标(-1.0至1.0)。超出该范围的坐标不会映射到-1.0至1.0区间,从而被裁剪。以投影矩阵中定义的范围为例,坐标(1250,500,750)将不可见—-因其x坐标超出范围,转换后在NDC中成为大于1.0的值,故被裁剪。
投影矩阵生成的这个视锥体成为截头锥,最终落入该截头锥内的每个坐标都会呈现在用户屏幕上。将指定范围内的坐标转换为易于映射到二维视图空间坐标的NDC(标准化设备坐标)的完整过程称为投影—-因为投影矩阵将三维坐标投影到易于映射的二维标准化设备坐标上
当所有顶点转换至裁剪空间后,将执行最终的透视除法操作:通过将位置向量的x、y、z分量除以向量的齐次w分量,将4D裁剪空间坐标转换为3D归一化设备坐标。此步骤在每次顶点着色器运行结束时自动执行。
经过次阶段后,生成的坐标将映射至屏幕坐标(使用glViewport设置)并转换为片段。
用于将视图坐标转换为裁剪坐标的投影矩阵可采用两种形式,每种形式定义独特的截锥体。可创建正交投影矩阵或透视投影矩阵。
1.9.6 正交投影 Orthographic projection
正交投影矩阵定义了一个类似立方体的截头锥体,该锥体界定了裁剪空间—-所有位于此锥体外部的顶点会被裁剪。创建正交投影矩阵时,我们需指定可见截头锥体的宽度、高度和长度。所有经正交投影矩阵转换至裁剪空间后仍位于该截头锥体内部的坐标都不会被裁剪。这个截头锥体类似于一个容器:

截头锥定义可见坐标范围,由宽度、高度及远近平面限定。近平面前方的坐标将被裁剪,远平面后方的坐标将被裁剪。正交截头锥体将锥体内部所有坐标直接映射至归一化设备坐标,因为每个向量的w分量保持不变;当w分量等于1.0时,透视除法不会改变坐标。
创建正交投影矩阵,使用OpenTK的CreateOrthographicOffCenter方法:
Matrix4.CreateOrthographicOffCenter(0.0f,800.0f,0.0f,600.0f,0.1f,100.0f);
前两个参数指定截头锥体的左、右坐标,第三、四参数则指定截头锥体的底、顶部分。通过这四个点,我们定义了近平面与远平面的尺寸,第五、六参数则确定了近远平面之间的距离。该投影矩阵将所有坐标在x、y、z范围内的值转换为归一化设备坐标。
正交投影矩阵将坐标直接映射到屏幕的二维平面,但实际中直接投影会产生不真实的效果,因为它忽略了透视关系。透视投影矩阵正是为解决这一问题而存在。
1.9.7 透视投影 Perspective projection
在现实世界中,远处的物体格外渺小。这种现象被称为透视效果。当眺望无尽的高速公路或铁路尽头时,透视效果尤其明显。如下图所示:

随着距离增加,线条在透视作用下逐渐趋于重合。这正是透视投影视图模拟的效果,其实现依赖透视投影矩阵。该矩阵将给定截锥范围映射至裁剪空间,同时通过调整每个顶点坐标的w值实现:顶点坐标距观察者越远,其w分量值越高。坐标转换至裁剪空间后,其值范围为-w至w(超出范围的坐标将被裁剪)。OpenGL要求最终顶点着色器输出的可见坐标落在-1.0至1.0之间,因此坐标进入裁剪空间后,需对裁剪空间坐标进行透视除法运算:

顶点坐标的每个分量都会除以其w分量,使得顶点离观察者越远,其坐标值越小。这正是w分量至关重要的另一原因,因为它有助于实现透视投影。最终得到的坐标将处于归一化设备空间中。
在OpenTk中可通过以下方法创建透视投影矩阵:
Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegressToRadians(45.0f),(float)width/(float)height,0.1f,100.0f);
Matrix4.CreatePerspectiveFieldOfView的核心功能是创建一个定义可见空间的巨大截锥体。截锥体外的物体将无法进入裁剪空间体积,从而被裁剪掉。透视截锥体可视化为一个非均匀形状的盒子,盒内每个坐标都会被映射到裁剪空间中的对应点。下图展示了透视截锥体的示意图:

其第一个参数定义视场角值(fovy),即视野范围(其中y代表y轴,表示垂直视场角),用于设定视域空间的大小。为获得真实感视角,通常设置为45度;第二个参数设定宽高比,通过视口宽度除以高度计算得出。第三、第四参数分别设定截头锥的近截面和平面。通常将近距离设为0.1f,远距离设为100.0f。介于近截面与远截面之间且位于截头锥内部的所有顶点都将被渲染。
当透视矩阵的近距离值设置过高(如10.0f)时,OpenGL会裁剪所有靠近摄像机的坐标(0.0f至10.0f区间),这在电子游戏中会产生熟悉的视觉效果—-当玩家过于靠近某些物体时,会出现透视穿透现象。
使用正交投影时,每个顶点坐标都会直接映射到裁剪空间,无需复制的透视分割(它仍会进行透视分割,但w分量保持不变始终为1,因此不产生影响)。由于正交投影不采用透视投影,远处的物体不会显得更小,从而产生奇怪的视觉效果。正因如此,正交投影主要应用于二维渲染及建筑/工程领域—–这些场景中我们需要避免顶点因透视变形而失真。
1.9.8 综合应用
我们为上述每个步骤创建一个变换矩阵:模型矩阵、视图矩阵和投影矩阵。顶点坐标随后按以下方式转换为裁剪坐标:

需要注意矩阵乘法的顺序被逆转(需记住矩阵乘法应从右向左读取)。最终生成的顶点应在顶点着色器中赋值给gl_Position,OpenGL将自动执行透视除法和裁剪操作。
顶点着色器的输出需要坐标转换为裁剪空间坐标,这正是我们通过变换矩阵完成的操作。OpenGL接着对裁剪空间坐标执行透视分割,将其转换为设备归一化坐标。随后通过GL.Viewport参数将设备归一化坐标映射到屏幕坐标,每个坐标对应屏幕上的一个点(本例为800*600屏幕)。此过程称为视口变换。
1.9.9 3D
要开始3D绘制,首先要创建模型矩阵。该矩阵包含平移、缩放及旋转操作。
Matrix4 model = Matrix4.CreateRotationX(MathHelper.DegreesToRadians(-55.0f));
通过将顶点坐标与该模型矩阵相乘,我们将其转换为世界坐标系中的坐标。因此,原本略微贴近地面的平面便代表了全局世界中的平面。
接下来需要创建视图矩阵。我们希望在场景中稍向后移动以使物体可见(当处于世界空间时,我们位于原点(0,0,0))。要实现场景移动,请理解一下原理
将摄像机向后移动,等同于将整个场景向前移动。
这正是视图矩阵的作用原理:通过反向移动整个场景来实现摄像机所需的移动方向。由于我们需要向后移动,且OpenGL采用右手坐标系,因此需沿正z轴方向移动。具体实现方式是将场景沿负z轴方向平移,从而产生向后移动的视觉效果。
OpenGL采用右手坐标系。这意味着正x轴位于你的右侧,正y轴向上,正z轴向后。想象屏幕是三轴中心,正z轴穿过屏幕指向你。坐标轴绘制方式如下:

理解右手坐标系原理可以进行一下操作:
将右手沿正y轴伸直,掌心朝上。拇指指向右侧。食指指向正上方。将中指向下弯曲90度。
若操作正确,拇指应该指向正x轴方向,食指指向正y轴方向,中指指向正z轴方向。若用左臂演示,你会发现z轴方向相反。这种称为左手坐标系的系统常被DirectX采用。需注意在标准化设备坐标系中,OpenGL实际使用左手坐标系(投影矩阵会切换坐标系方向)。
目前视图矩阵如下所示:
Matrix4 view = Matrix4.CreateTranslation(0.0f, 0.0f, -3.0f);
最后需要定义的是投影矩阵。由于场景采用透视投影,我们将投影矩阵声明为:
Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(45.0f), Width / Height, 0.1f, 100.0f);
创建变换矩阵后,需要将它们传递给着色器。首先在点点着色器中将变换矩阵声明为统一变量,然后与顶点坐标相乘。
还需要将矩阵发送至着色器(由于变换矩阵通常变化频繁,此操作通常在每次渲染迭代时执行)。
现在顶点坐标已经通过模型、视图和投影矩阵进行变换,最终呈现为:
向后倾斜至地面离我们稍远一些以透视效果呈现(顶点越远,物体越小)。
VertexShader.glsl修改后代码如下:
#version 330 core
layout(location = 0) in vec3 aPosition;
layout(location = 1) in vec2 aTexCoord;
out vec2 texCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main(void)
{
gl_Position = projection * view * model * vec4(aPosition, 1.0);
texCoord = aTexCoord;
}
Game.cs修改后代码如下:
using OpenTK.Compute.OpenCL;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Common.Input;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace LearnOpenTK
{
public class Game : GameWindow
{
float[] _vertices =
{
//Position Texture coordinates
0.5f, 0.5f, 0.0f, 1.0f, 1.0f, // top right
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f, 0.0f, 1.0f // top left
};
float[] texCoords = {
0.0f, 0.0f, // lower-left corner
1.0f, 0.0f, // lower-right corner
0.5f, 1.0f // top-center corner
};
private readonly float[] vertices =
{
// positions // colors
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // bottom left
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // top
};
uint[] indices = { // note that we start from 0!
0, 1, 3, // first triangle
1, 2, 3 // second triangle
};
int VertexBufferObject;//存储顶点缓冲区对象句柄(VBO)
int VertexBufferArray;//顶点数组对象句柄(VAO)
int ElementBufferObject;//元素缓冲对象句柄(EBO)
Shader shader;
Matrix4 model;
Matrix4 view;
Matrix4 projection;
Texture texture1;
Texture texture2;
Stopwatch stopwatch;
int Width = 0;
int Height = 0;
public Game(int width, int height, string title) : base(GameWindowSettings.Default, new NativeWindowSettings() { ClientSize = (width, height), Title = title })
{
Width = width;
Height = height;
}
protected override void OnUpdateFrame(FrameEventArgs args)
{
base.OnUpdateFrame(args);
if (KeyboardState.IsKeyDown(Keys.Escape))
{
Close();
}
}
protected override void OnLoad()
{
base.OnLoad();
stopwatch = new Stopwatch();
stopwatch.Start();
shader = new Shader("VertexShader.glsl", "FragmentShader.glsl");
texture1 = new Texture("./images/original.png", TextureUnit.Texture0);
texture2 = new Texture("./images/Snipaste.png", TextureUnit.Texture1);
//生成独一无二的标识符(初始化)
VertexBufferObject = GL.GenBuffer();
VertexBufferArray = GL.GenVertexArray();
ElementBufferObject = GL.GenBuffer();
//绑定VAO
GL.BindVertexArray(VertexBufferObject);
//将新建缓冲区绑定至BufferTarget.ArrayBuffer目标
GL.BindBuffer(BufferTarget.ArrayBuffer, VertexBufferObject);
GL.BindBuffer(BufferTarget.ElementArrayBuffer, ElementBufferObject);
//将用户定义的数据复制到当前绑定缓冲区
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
////设置顶点属性指针
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), 0);
GL.EnableVertexAttribArray(0);
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float));
GL.EnableVertexAttribArray(1);
model = Matrix4.CreateRotationX(MathHelper.DegreesToRadians(-55.0f));
view = Matrix4.CreateTranslation(0.0f, 0.0f, -3.0f);
projection = Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(45.0f), (float)(Width / Height), 0.1f, 100.0f);
//model = Matrix4.Identity;
// view = Matrix4.LookAt(new Vector3(0, 0, 3), new Vector3(0, 0, 0), Vector3.UnitY);
// projection = Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(45f),
// (float)( Width / Height), 0.1f, 100.0f);
//设置清屏颜色
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
}
protected override void OnRenderFrame(FrameEventArgs args)
{
base.OnRenderFrame(args);
GL.Clear(ClearBufferMask.ColorBufferBit);
//绑定VAO
GL.BindVertexArray(VertexBufferArray);
shader.Use();
//Matrix4 rotation = Matrix4.CreateRotationZ(MathHelper.DegreesToRadians(90.0f));
//Matrix4 scale = Matrix4.CreateScale(0.5f, 0.5f, 0.5f);
//Matrix4 trans = rotation * scale;
//int modelLocation = GL.GetUniformLocation(shader.Handle, "transform");
//GL.UniformMatrix4(modelLocation, false, ref trans);
//uniform mat4 model;
//uniform mat4 view;
//uniform mat4 projection;
int modelLocation = GL.GetUniformLocation(shader.Handle, "model");
GL.UniformMatrix4(modelLocation, false, ref model);
int viewLocation = GL.GetUniformLocation(shader.Handle, "view");
GL.UniformMatrix4(viewLocation, false, ref view);
int projectionLocation = GL.GetUniformLocation(shader.Handle, "projection");
GL.UniformMatrix4(projectionLocation, false, ref projection);
// 设置纹理单元0 - 第一个纹理
shader.SetInt("texture1", 0);
// 设置纹理单元1 - 第二个纹理
shader.SetInt("texture2", 1);
//参数1:绘制图形 参数2:要绘制的顶点数组起始索引 参数3:要绘制顶点个数
GL.DrawArrays(PrimitiveType.TriangleFan, 0, 4);
//交换缓存区
SwapBuffers();
}
protected override void OnFramebufferResize(FramebufferResizeEventArgs e)
{
base.OnFramebufferResize(e);
Width = e.Width;
Height = e.Height;
GL.Viewport(0, 0, e.Width, e.Height);
}
protected override void OnUnload()
{
base.OnUnload();
shader.Dispose();
}
}
}
运行效果如下:

1.9.10 进阶3D
我们现在一直在3D空间处理2D平面,接下来把2D平面扩展为3D立方体。然后让立方体随时间进行旋转。
Game.cs代码如下:
using OpenTK.Compute.OpenCL;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Common.Input;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace LearnOpenTK
{
public class Game : GameWindow
{
float[] _vertices =
{
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f
};
float[] texCoords = {
0.0f, 0.0f, // lower-left corner
1.0f, 0.0f, // lower-right corner
0.5f, 1.0f // top-center corner
};
private readonly float[] vertices =
{
// positions // colors
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // bottom left
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // top
};
uint[] indices = { // note that we start from 0!
0, 1, 3, // first triangle
1, 2, 3 // second triangle
};
int VertexBufferObject;//存储顶点缓冲区对象句柄(VBO)
int VertexBufferArray;//顶点数组对象句柄(VAO)
int ElementBufferObject;//元素缓冲对象句柄(EBO)
Shader shader;
Matrix4 model;
Matrix4 view;
Matrix4 projection;
Texture texture1;
Texture texture2;
Stopwatch stopwatch;
int Width = 0;
int Height = 0;
public Game(int width, int height, string title) : base(GameWindowSettings.Default, new NativeWindowSettings() { ClientSize = (width, height), Title = title })
{
Width = width;
Height = height;
}
protected override void OnUpdateFrame(FrameEventArgs args)
{
base.OnUpdateFrame(args);
if (KeyboardState.IsKeyDown(Keys.Escape))
{
Close();
}
}
protected override void OnLoad()
{
base.OnLoad();
stopwatch = new Stopwatch();
stopwatch.Start();
shader = new Shader("VertexShader.glsl", "FragmentShader.glsl");
texture1 = new Texture("./images/original.png", TextureUnit.Texture0);
texture2 = new Texture("./images/Snipaste.png", TextureUnit.Texture1);
// GL.Enable(EnableCap.DepthTest);//启用深度测试
//生成独一无二的标识符(初始化)
VertexBufferObject = GL.GenBuffer();
VertexBufferArray = GL.GenVertexArray();
ElementBufferObject = GL.GenBuffer();
//绑定VAO
GL.BindVertexArray(VertexBufferObject);
//将新建缓冲区绑定至BufferTarget.ArrayBuffer目标
GL.BindBuffer(BufferTarget.ArrayBuffer, VertexBufferObject);
GL.BindBuffer(BufferTarget.ElementArrayBuffer, ElementBufferObject);
//将用户定义的数据复制到当前绑定缓冲区
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
////设置顶点属性指针
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), 0);
GL.EnableVertexAttribArray(0);
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float));
GL.EnableVertexAttribArray(1);
model = Matrix4.CreateRotationX(MathHelper.DegreesToRadians(55.0f));
view = Matrix4.CreateTranslation(0.0f, 0.0f, -3.0f);
projection = Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(45.0f), (float)(Width / Height), 0.1f, 100.0f);
//model = Matrix4.Identity;
// view = Matrix4.LookAt(new Vector3(0, 0, 3), new Vector3(0, 0, 0), Vector3.UnitY);
// projection = Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(45f),
// (float)( Width / Height), 0.1f, 100.0f);
//设置清屏颜色
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
}
float temp = 0;
protected override void OnRenderFrame(FrameEventArgs args)
{
base.OnRenderFrame(args);
GL.Clear(ClearBufferMask.ColorBufferBit);
//GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
//绑定VAO
GL.BindVertexArray(VertexBufferArray);
shader.Use();
//Matrix4 rotation = Matrix4.CreateRotationZ(MathHelper.DegreesToRadians(90.0f));
//Matrix4 scale = Matrix4.CreateScale(0.5f, 0.5f, 0.5f);
//Matrix4 trans = rotation * scale;
//int modelLocation = GL.GetUniformLocation(shader.Handle, "transform");
//GL.UniformMatrix4(modelLocation, false, ref trans);
temp += 0.01f;
model = Matrix4.CreateRotationX((float)MathHelper.DegreesToRadians(temp%180));
int modelLocation = GL.GetUniformLocation(shader.Handle, "model");
GL.UniformMatrix4(modelLocation, false, ref model);
int viewLocation = GL.GetUniformLocation(shader.Handle, "view");
GL.UniformMatrix4(viewLocation, false, ref view);
int projectionLocation = GL.GetUniformLocation(shader.Handle, "projection");
GL.UniformMatrix4(projectionLocation, false, ref projection);
// 设置纹理单元0 - 第一个纹理
shader.SetInt("texture1", 0);
// 设置纹理单元1 - 第二个纹理
shader.SetInt("texture2", 1);
//参数1:绘制图形 参数2:要绘制的顶点数组起始索引 参数3:要绘制顶点个数
GL.DrawArrays(PrimitiveType.TriangleFan, 0, 36);
//交换缓存区
SwapBuffers();
}
protected override void OnFramebufferResize(FramebufferResizeEventArgs e)
{
base.OnFramebufferResize(e);
Width = e.Width;
Height = e.Height;
GL.Viewport(0, 0, e.Width, e.Height);
}
protected override void OnUnload()
{
base.OnUnload();
shader.Dispose();
}
}
}
这时会发现旋转立方体旋转到某些面时会出现其他面覆盖的情况,这种情况需要对OpenGL进行深度测试