在 Lazarus 中学习 OpenGL
在 Lazarus 中学习 OpenGL
教学网站
https://learnopengl-cn.github.io/
API 查询
https://docs.gl/
创建窗口
Lazarus 可以在各个平台创建窗口,但这些窗口不包含 OpenGL 的上下文,所以这里借助 GLFW 来创建窗口和 OpenGL 上下文。GLFW 是一个专门针对 OpenGL 的 C 语言库,它提供了一些渲染物体所需的最低限度的接口。它允许用户创建 OpenGL 上下文、定义窗口参数以及处理用户输入。
GLFW
下载地址:https://www.glfw.org/download.html
下载源码并解压,然后进入解压后的目录,使用下面的脚本进行编译(Linux 环境):
#!/bin/sh
cmake -S . -B build
cd build
make
编译完毕后,在解压目录的 build/src 目录中找到 libglfw3.a 拿出来用即可。
GLFW 是用 C 语言编写的,要在 Lazarus 中使用,必须要有 Free Pascal 版本的函数绑定,在 Github 上有人维护了一份 GLFW 的 Free Pascal 绑定,可以从 https://github.com/Blueicaro/GLFW 下载它(只需要下载 Glfw33 目录中的 glfw.pas 即可)。
你好世界
在 Lazarus 的“工程”菜单中选择“新建工程”,在弹出的对话框中选择“简单程序”,然后点击“确定”。然后将该工程保存为“project1.lpi”(.lpi 是工程文件,同时还会生成 .lpr 程序源文件、.lps 设置文件、.res 资源文件,后两个文件我们用不到,可以删掉,但每次保存时都会自动生成这些文件)。然后在这些文件的旁边创建两个目录 Libs 和 Units,然后将 libglfw3.a 放入 Libs 目录中,并改名为 libGLFW3.a(因为在 glfw.pas 中,该库文件的名称被定义成了大写格式),将 glfw.pas 放入 Units 目录中。
然后在 Lazarus 的“工程”菜单中选择“工程选项”,在弹出的对话框中定位到“编译器选项->路径”选项卡,将“其它单元文件(-Fu)”的路径设置为 Units,这样我们就可以使用 glfw.pas 了。然后将“库(-Fl)”的路径设置为 Libs,这样在编译时就可以链接到 libGLFW.a 了。然后点击“确定”(忽略“非 ASCII”之类的警告)。再一次保存工程,将我们的设置信息保存起来(它只对当前工程有效)。
然后修改源代码为如下内容:
program OpenGL;
// 使用 ObjFPC 模式,功能更强大,启用长字符串支持。
{$mode objfpc}{$H+}
// libGLFW.a 需要动态链接 glibc 的数学库。
{$linklib libm.so}
// 使用刚下载的 glfw.pas,在 Linux 中,它依赖 Xlib。
// gl 中包含了基本的(旧)OpenGL 函数。
// glext 中包含了扩展的(新)OpenGL 函数。
uses
glfw, Xlib, gl, glext;
var
window: pGLFWwindow;
begin
// 初始化 GLFW(返回 0 表示初始化失败)
if glfwInit() = 0 then Halt(-1);
// 告诉 GLFW 我们要使用的 OpenGL 3.3 版,这样 GLFW 在创建 OpenGL 上下文时会做出适当调整。
// 这也可以确保用户在没有适当的 OpenGL 版本支持的情况下无法运行。
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
// 告诉 GLFW 我们使用的是核心模式,只使用 OpenGL 功能的一个子集(没有向后兼容特性)。
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// 创建窗口及其 OpenGL 上下文
window := glfwCreateWindow(800, 600, '你好,世界!', nil, nil);
if window = nil then begin
glfwTerminate();
Halt(-1);
end;
// 使窗口的上下文成为当前,必须在设置上下文之后,才能使用 OpenGL 的函数
glfwMakeContextCurrent(window);
// 加载所需版本的 OpenGL 函数
Load_GL_VERSION_3_3();
// glfwWindowShouldClose 函数用于检查 GLFW 是否被要求退出
while glfwWindowShouldClose(window) = 0 do begin
// 清屏
glClear(GL_COLOR_BUFFER_BIT);
// 交换前后缓冲区
glfwSwapBuffers(window);
// 检查有没有触发事件或更新窗口状态,并调用相应的回调函数
glfwPollEvents();
end;
// 销毁之前创建的所有资源
glfwTerminate();
end.
然后按 F9 运行程序即可。
教程中的例子(空白窗口)
program OpenGL;
{$mode objfpc}{$H+}
{$linklib libm.so}
uses
glfw, Xlib, gl, glext;
var
window: pGLFWwindow;
// 当窗口大小改变时要调用的回调函数
procedure FramebufferSizeCallback(window: PGLFWwindow; width, height: GLSizei); cdecl;
begin
glViewport(0, 0, width, height);
end;
// 键盘事件处理函数
procedure processInput(window: PGLFWwindow);
begin
if glfwGetKey(window, GLFW_KEY_ESCAPE) = GLFW_PRESS then
glfwSetWindowShouldClose(window, GLFW_INT(True));
end;
begin
if glfwInit() = 0 then begin
WriteLn(stderr, '初始化 GLFW 失败');
Halt(-1);
end;
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
window := glfwCreateWindow(800, 600, '', nil, nil);
if window = nil then begin
WriteLn(stderr, '创建 GLFW 窗口失败');
glfwTerminate();
Halt(-1);
end;
glfwMakeContextCurrent(window);
// 设置当窗口尺寸改变时需要调用的回调函数
glfwSetFramebufferSizeCallback(window, @FramebufferSizeCallback);
// 获取 OpenGL 版本,显示在标题栏中
glfwSetWindowTitle(window, glGetString(GL_VERSION));
Load_GL_VERSION_3_3();
while glfwWindowShouldClose(window) = 0 do begin
// 处理键盘事件
processInput(window);
// 用指定颜色清屏
glClearColor(0.2, 0.3, 0.3, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glfwSwapBuffers(window);
glfwPollEvents();
end;
glfwTerminate();
end.
教程中的例子(三角形)
program OpenGL;
{$mode objfpc}{$H+}
{$linklib libm.so}
uses
glfw, Xlib, gl, glext;
var
window: pGLFWwindow;
procedure FramebufferSizeCallback(window: PGLFWwindow; width, height: GLSizei); cdecl;
begin
glViewport(0, 0, width, height);
end;
procedure processInput(window: PGLFWwindow);
begin
if glfwGetKey(window, GLFW_KEY_ESCAPE) = GLFW_PRESS then
glfwSetWindowShouldClose(window, GLFW_INT(True));
end;
var
vertexShader : Cardinal;
fragmentShader: Cardinal;
shaderProgram : Cardinal;
success : Integer;
infoLog : String;
vertices: array of single = (
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0,
0.0, 0.5, 0.0
);
VBO: Cardinal;
VAO: Cardinal;
const
vertexShaderSource: String = '#version 330 core'#10+
'layout (location = 0) in vec3 aPos;'#10+
'void main()'#10+
'{'#10+
' gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);'#10+
'}';
fragmentShaderSource: PChar = '#version 330 core'#10+
'out vec4 FragColor;'#10+
'void main()'#10+
'{'#10+
' FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);'#10+
'}';
begin
if glfwInit() = 0 then begin
WriteLn(stderr, '初始化 GLFW 失败');
Halt(-1);
end;
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
window := glfwCreateWindow(800, 600, '', nil, nil);
if window = nil then begin
WriteLn(stderr, '创建 GLFW 窗口失败');
glfwTerminate();
Halt(-1);
end;
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, @FramebufferSizeCallback);
glfwSetWindowTitle(window, glGetString(GL_VERSION));
Load_GL_VERSION_3_3();
vertexShader := glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, @vertexShaderSource, nil);
glCompileShader(vertexShader);
SetLength(infoLog, 512);
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, @success);
if success = 0 then begin
glGetShaderInfoLog(vertexShader, 512, nil, PChar(infoLog));
WriteLn(stderr, 'ERROR::SHADER::VERTEX::COMPILATION_FAILED'#10, infoLog, #10);
end;
fragmentShader := glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, @fragmentShaderSource, nil);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, @success);
if success = 0 then begin
glGetShaderInfoLog(vertexShader, 512, nil, PChar(infoLog));
WriteLn(stderr, 'ERROR::SHADER::FRAGMENT::COMPILATION_FAILED'#10, infoLog, #10);
end;
shaderProgram := glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glGetShaderiv(shaderProgram, GL_COMPILE_STATUS, @success);
if success = 0 then begin
glGetShaderInfoLog(shaderProgram, 512, nil, PChar(infoLog));
WriteLn(stderr, 'ERROR::SHADER::PROGRAM::LINKING_FAILED'#10, infoLog, #10);
end;
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
glGenVertexArrays(1, @VAO);
glGenBuffers(1, @VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, Length(vertices) * sizeof(single), Pointer(vertices), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(Single), Pointer(0));
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
while glfwWindowShouldClose(window) = 0 do begin
processInput(window);
glClearColor(0.2, 0.3, 0.3, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glfwSwapBuffers(window);
glfwPollEvents();
end;
glDeleteVertexArrays(1, @VAO);
glDeleteBuffers(1, @VBO);
glDeleteProgram(shaderProgram);
glfwTerminate();
end.