OpenGL ES 绘制三角形,操作键盘移动位置

PS:想快速看到效果的小伙伴,可以在引入依赖后,先跳到完整代码部分

第一步:依赖引入

<properties><lwjgl.version>3.2.3</lwjgl.version><joml.version>1.9.25</joml.version><steamworks4j.version>1.8.0</steamworks4j.version><steamworks4j-server.version>1.8.0</steamworks4j-server.version></properties><profiles><profile><id>lwjgl-natives-macos-amd64</id><activation><os><family>mac</family><arch>amd64</arch></os></activation><properties><lwjgl.natives>natives-macos</lwjgl.natives></properties><dependencies><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-vulkan</artifactId><classifier>natives-macos</classifier></dependency></dependencies></profile><profile><id>lwjgl-natives-windows-amd64</id><activation><os><family>windows</family><arch>amd64</arch></os></activation><properties><lwjgl.natives>natives-windows</lwjgl.natives></properties></profile><profile><id>lwjgl-natives-windows-x86</id><activation><os><family>windows</family><arch>x86</arch></os></activation><properties><lwjgl.natives>natives-windows-x86</lwjgl.natives></properties></profile></profiles><dependencyManagement><dependencies><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-bom</artifactId><version>${lwjgl.version}</version><scope>import</scope><type>pom</type></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-assimp</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-bgfx</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-cuda</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-egl</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-glfw</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-jawt</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-jemalloc</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-libdivide</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-llvm</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-lmdb</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-lz4</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-meow</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-nanovg</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-nfd</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-nuklear</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-odbc</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-openal</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-opencl</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-opengl</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-opengles</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-openvr</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-opus</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-ovr</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-par</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-remotery</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-rpmalloc</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-shaderc</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-sse</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-stb</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-tinyexr</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-tinyfd</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-tootle</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-vma</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-vulkan</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-xxhash</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-yoga</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-zstd</artifactId></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-assimp</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-bgfx</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-glfw</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-jemalloc</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-libdivide</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-llvm</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-lmdb</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-lz4</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-meow</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-nanovg</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-nfd</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-nuklear</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-openal</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-opengl</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-opengles</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-openvr</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-opus</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-ovr</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-par</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-remotery</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-rpmalloc</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-shaderc</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-sse</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-stb</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-tinyexr</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-tinyfd</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-tootle</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-vma</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-xxhash</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-yoga</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-zstd</artifactId><classifier>${lwjgl.natives}</classifier></dependency><dependency><groupId>org.joml</groupId><artifactId>joml</artifactId><version>${joml.version}</version></dependency><dependency><groupId>com.code-disaster.steamworks4j</groupId><artifactId>steamworks4j</artifactId><version>${steamworks4j.version}</version></dependency><dependency><groupId>com.code-disaster.steamworks4j</groupId><artifactId>steamworks4j-server</artifactId><version>${steamworks4j-server.version}</version></dependency></dependencies>

第二步:创建类,引入需要的包,设置全局参数

1.创建类

public class TriangleExample {......}

2. 包引入

import org.lwjgl.glfw.*;import org.lwjgl.opengl.*;import org.lwjgl.system.MemoryStack;import static org.lwjgl.glfw.GLFW.*;import static org.lwjgl.opengl.GL11.*;import static org.lwjgl.system.MemoryUtil.*;import java.nio.IntBuffer;import java.util.Objects;import java.util.Random;

3. 全局参数

// 窗口句柄private long window;// x坐标private float triangleX = 0.0f;// y坐标private float triangleY = 0.0f;// 步长private final float triangleSpeed = 0.05f;

第三步:定义一个初始化方法 init()

1. GLFW 错误信息的回调函数

这样做,在发生 GLFW 错误时,错误信息将被打印到标准错误流(System.err)

GLFWErrorCallback.createPrint(System.err).set();

2. 初始化 GLFW 库

if (!glfwInit()) { throw new IllegalStateException("Unable to initialize GLFW"); }

3. 窗口设置

//设置默认的窗口提示参数glfwDefaultWindowHints();//设置窗口不可见glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);//设置窗口可调整大小glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);//指定窗口宽度int width = 500;//指定窗口高度int height = 500;//创建一个指定大小和标题的窗口。window = glfwCreateWindow(width, height, "OpenGL Triangle Example", NULL, NULL);//如果创建失败,抛出一个运行时异常if (window == NULL) {throw new RuntimeException("Failed to create the GLFW window");}

4. 设置键盘回调函数

  • 参数说明:
    • window: 表示触发键盘事件的窗口句柄。它指定了事件发生的窗口。
    • key:表示被按下或释放的键的键码。每个按键都有一个对应的唯一键码,用于标识不同的按键。
    • scancode:表示键的扫描码。扫描码是键盘硬件生成的唯一标识符,它与特定的键码相对应。
    • action: 表示键盘事件的动作类型。它可以有以下几种取值
      • GLFW_PRESS:按下了键。
      • GLFW_RELEASE:释放了键。
      • GLFW_REPEAT:重复按下了键,通常在按键持续按下时触发。
    • mods: 表示与键盘事件同时按下的修饰键(如 Shift、Ctrl、Alt 等),它是一个位字段,通过对特定位进行设置来表示修饰键的状态。

我们这里监听上下左右键,aswd键和一个esc退出键

glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> {if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {glfwSetWindowShouldClose(window, true);}if (key == GLFW_KEY_LEFT || key == GLFW_KEY_A) {triangleX -= triangleSpeed;} else if (key == GLFW_KEY_RIGHT || key == GLFW_KEY_D) {triangleX += triangleSpeed;} else if (key == GLFW_KEY_UP || key == GLFW_KEY_W) {triangleY += triangleSpeed;} else if (key == GLFW_KEY_DOWN || key == GLFW_KEY_S) {triangleY -= triangleSpeed;}});

5. 创建一个内存栈,窗口定位

try (MemoryStack stack = MemoryStack.stackPush()) {//使用内存栈分配两个整型缓冲区,用于存储窗口的宽度和高度IntBuffer pWidth = stack.mallocInt(1);IntBuffer pHeight = stack.mallocInt(1);//获取窗口的实际大小,并将宽度和高度存储到缓冲区中glfwGetWindowSize(window, pWidth, pHeight);//获取当前主显示器的视频模式GLFWVidMode vidMode = glfwGetVideoMode(glfwGetPrimaryMonitor());//将窗口定位到屏幕中心,根据窗口和屏幕尺寸计算得到位置。glfwSetWindowPos(window, (Objects.requireNonNull(vidMode).width() - pWidth.get(0)) / 2, (vidMode.height() - pHeight.get(0)) / 2);}

6. 将指定的窗口设置为当前上下文

在进行 OpenGL 渲染之前,需要将窗口的 OpenGL 上下文设置为当前上下文
以确保 OpenGL 命令在正确的上下文中执行。

glfwMakeContextCurrent(window);

7. 设置垂直同步(Vertical Sync)的刷新率

垂直同步是一种同步机制,它将渲染的帧率与显示器的刷新率进行同步
以避免出现画面撕裂(Tearing)的现象。
通过设置刷新率为1,表示与显示器的刷新率保持同步
即每次渲染完一帧后等待显示器垂直同步信号再进行下一帧的渲染。

glfwSwapInterval(1);

8. 显示指定的窗口

在创建窗口后,默认是不可见的,通过调用该函数可以将窗口显示出来

glfwShowWindow(window);

9. init()代码

private void init() {GLFWErrorCallback.createPrint(System.err).set();if (!glfwInit()) {throw new IllegalStateException("Unable to initialize GLFW");}glfwDefaultWindowHints();glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);int width = 500;int height = 500;window = glfwCreateWindow(width, height, "OpenGL Triangle Example", NULL, NULL);if (window == NULL) {throw new RuntimeException("Failed to create the GLFW window");}glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> {if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {glfwSetWindowShouldClose(window, true);}if (key == GLFW_KEY_LEFT || key == GLFW_KEY_A) {triangleX -= triangleSpeed;} else if (key == GLFW_KEY_RIGHT || key == GLFW_KEY_D) {triangleX += triangleSpeed;} else if (key == GLFW_KEY_UP || key == GLFW_KEY_W) {triangleY += triangleSpeed;} else if (key == GLFW_KEY_DOWN || key == GLFW_KEY_S) {triangleY -= triangleSpeed;}});try (MemoryStack stack = MemoryStack.stackPush()) {IntBuffer pWidth = stack.mallocInt(1);IntBuffer pHeight = stack.mallocInt(1);glfwGetWindowSize(window, pWidth, pHeight);GLFWVidMode vidMode = glfwGetVideoMode(glfwGetPrimaryMonitor());glfwSetWindowPos(window, (Objects.requireNonNull(vidMode).width() - pWidth.get(0)) / 2, (vidMode.height() - pHeight.get(0)) / 2);}glfwMakeContextCurrent(window);glfwSwapInterval(1);glfwShowWindow(window);}

第四步:定义主循环体loop()

1. 创建并激活 OpenGL 上下文的功能

GL.createCapabilities();

2. 设置清空颜色缓冲区的颜色

该行代码将清空颜色设置为黑色

glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

3. 主循环

用于不断渲染场景,直到窗口被关闭

while (!glfwWindowShouldClose(window)){......}

----------------------------------以下属于循环体中的内容----------------------------------

4. 清空颜色缓冲区和深度缓冲区

将之前设置的清空颜色应用到缓冲区,并清除之前渲染的内容

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

5. 设置坐标系的原点

glLoadIdentity();

6. 根据给定的偏移量将当前矩阵进行移动

triangleX和triangleY是控制三角形位置的变量,通过平移变换可以改变三角形的位置

 glTranslatef(triangleX, triangleY, 0.0f);

7. 创建三角形

这个步骤可以简单理解为:着色–>画画–>着色–>画画–>…

// 定义一个三角形的顶点序列glBegin(GL_TRIANGLES);//设置当前绘制的图元的颜色为红色glColor3f(1.0f, 0.0f, 0.0f);//定义三角形的一个顶点坐标glVertex2f(-0.6f, -0.4f);//设置当前绘制的图元的颜色为绿色glColor3f(0.0f, 1.0f, 0.0f);//定义三角形的第二个顶点坐标glVertex2f(0.6f, -0.4f);//设置当前绘制的图元的颜色为蓝色glColor3f(0.0f, 0.0f, 1.0f);//定义三角形的第三个顶点坐标glVertex2f(0.0f, 0.6f);//结束定义图元的顶点序列glEnd();

8. 交换前后缓冲区的内容,用于显示渲染结果

glfwSwapBuffers(window);

9. 处理窗口事件,使窗口保持响应状态

例如处理键盘输入、鼠标事件等

glfwPollEvents();

10. loop()代码

private void loop() {GL.createCapabilities();glClearColor(0.0f, 0.0f, 0.0f, 0.0f);while (!glfwWindowShouldClose(window)) {glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);glLoadIdentity();glTranslatef(triangleX, triangleY, 0.0f);glBegin(GL_TRIANGLES);glColor3f(1.0f, 0.0f, 0.0f);glVertex2f(-0.6f, -0.4f);glColor3f(0.0f, 1.0f, 0.0f);glVertex2f(0.6f, -0.4f);glColor3f(0.0f, 0.0f, 1.0f);glVertex2f(0.0f, 0.6f);glEnd();glfwSwapBuffers(window);glfwPollEvents();}}

第五步:定义执行方法run()

1. 执行init()loop()

init();loop();

2. 销毁窗口,关闭程序

用于销毁指定的 GLFW 窗口。它接受一个窗口句柄作为参数,通过调用该函数,可以释放窗口所占用的资源并关闭窗口

glfwDestroyWindow(window);

用于终止 GLFW 库。它用于释放 GLFW 所使用的资源,并进行清理操作。在你的程序即将退出时,调用glfwTerminate()是一个好的实践,以确保资源被正确释放。

glfwTerminate();

4. run()代码

 public void run() {init();loop();glfwDestroyWindow(window);glfwTerminate();}

完整代码

import org.lwjgl.glfw.*;import org.lwjgl.opengl.*;import org.lwjgl.system.MemoryStack;import static org.lwjgl.glfw.GLFW.*;import static org.lwjgl.opengl.GL11.*;import static org.lwjgl.system.MemoryUtil.*;import java.nio.IntBuffer;import java.util.Objects;import java.util.Random;public class TriangleExample {private long window;private float triangleX = 0.0f;private float triangleY = 0.0f;private final float triangleSpeed = 0.05f;public void run() {init();loop();glfwDestroyWindow(window);glfwTerminate();}private void init() {GLFWErrorCallback.createPrint(System.err).set();if (!glfwInit()) {throw new IllegalStateException("Unable to initialize GLFW");}glfwDefaultWindowHints();glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);int width = 500;int height = 500;window = glfwCreateWindow(width, height, "OpenGL Triangle Example", NULL, NULL);if (window == NULL) {throw new RuntimeException("Failed to create the GLFW window");}glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> {if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {glfwSetWindowShouldClose(window, true);}if (key == GLFW_KEY_LEFT || key == GLFW_KEY_A) {triangleX -= triangleSpeed;} else if (key == GLFW_KEY_RIGHT || key == GLFW_KEY_D) {triangleX += triangleSpeed;} else if (key == GLFW_KEY_UP || key == GLFW_KEY_W) {triangleY += triangleSpeed;} else if (key == GLFW_KEY_DOWN || key == GLFW_KEY_S) {triangleY -= triangleSpeed;}});try (MemoryStack stack = MemoryStack.stackPush()) {IntBuffer pWidth = stack.mallocInt(1);IntBuffer pHeight = stack.mallocInt(1);glfwGetWindowSize(window, pWidth, pHeight);GLFWVidMode vidMode = glfwGetVideoMode(glfwGetPrimaryMonitor());glfwSetWindowPos(window, (Objects.requireNonNull(vidMode).width() - pWidth.get(0)) / 2, (vidMode.height() - pHeight.get(0)) / 2);}glfwMakeContextCurrent(window);glfwSwapInterval(1);glfwShowWindow(window);}private void loop() {GL.createCapabilities();glClearColor(0.0f, 0.0f, 0.0f, 0.0f);while (!glfwWindowShouldClose(window)) {glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);glLoadIdentity();glTranslatef(triangleX, triangleY, 0.0f);glBegin(GL_TRIANGLES);glColor3f(1.0f, 0.0f, 0.0f);glVertex2f(-0.6f, -0.4f);glColor3f(0.0f, 1.0f, 0.0f);glVertex2f(0.6f, -0.4f);glColor3f(0.0f, 0.0f, 1.0f);glVertex2f(0.0f, 0.6f);glEnd();glfwSwapBuffers(window);glfwPollEvents();}}public static void main(String[] args) {new TriangleExample().run();}}

效果图

画圆

glBegin(GL_TRIANGLE_FAN);float centerX = 0.0f;// 圆心的X坐标float centerY = 0.0f;// 圆心的Y坐标float radius = 0.5f; // 圆的半径int numSegments = 99;// 圆的细分段数glColor3f(0.0f, 2.4f, 5.5f);// 添加圆心顶点0glVertex2f(centerX, centerY);Random random = new Random();// 添加圆上的顶点for (int i = 0; i <= numSegments; i++) {float v = random.nextFloat();float v1 = random.nextFloat();float v2 = random.nextFloat();glColor3f(v, v1, v2);float angle = (float) (2.0 * Math.PI * i / numSegments);float x = centerX + radius * (float) Math.cos(angle);float y = centerY + radius * (float) Math.sin(angle);glVertex2f(x, y);}

效果图

记得把三角形的代码注释了,不然可能会出现下面的效果