当前位置: 首页 > news >正文

JOGL 从入门到精通:开启 Java 3D 图形编程之旅

一、引言

Java 作为一门广泛应用的编程语言,在图形编程领域也有着强大的工具和库。JOGL(Java OpenGL)便是其中之一,它为 Java 开发者提供了访问 OpenGL(Open Graphics Library)功能的接口,使得在 Java 平台上创建高性能、交互式的 2D 和 3D 图形应用程序成为可能。从简单的图形绘制到复杂的 3D 游戏开发、科学可视化等领域,JOGL 都展现出了其独特的优势和广泛的应用前景。本文将带你逐步深入学习 JOGL,从基础的环境搭建到高级的图形渲染技巧,助你掌握这一强大的图形编程工具。

二、JOGL 基础概述

(一)什么是 JOGL

JOGL 是一个 Java 绑定的 OpenGL 库,它允许 Java 程序利用 OpenGL 的强大图形渲染能力。OpenGL 是一个跨平台的图形 API,提供了丰富的函数和方法来创建、操作和渲染 2D 和 3D 图形。JOGL 通过 Java 本地接口(JNI)将这些功能封装起来,使得 Java 开发者可以使用熟悉的 Java 语法来调用 OpenGL 的函数,从而在 Java 应用程序中实现高性能的图形处理。

(二)JOGL 的特点

  • 高性能:由于基于 OpenGL,JOGL 能够充分利用显卡的硬件加速功能,实现高效的图形渲染,对于复杂的 3D 场景和大量图形元素的处理表现出色。
  • 跨平台性:与 Java 语言的特性一致,JOGL 应用程序可以在不同的操作系统上运行,如 Windows、Linux、macOS 等,只要系统安装了相应的 OpenGL 驱动程序,就能够保证图形程序的正常执行,大大提高了代码的可移植性和应用范围。
  • 丰富的功能:支持 2D 和 3D 图形的绘制,包括基本图形(如点、线、三角形等)、复杂多边形、纹理映射、光照效果、几何变换(平移、旋转、缩放)、视图控制、模型加载等一系列图形处理功能,能够满足从简单图形界面到复杂 3D 游戏和专业图形应用的开发需求。

三、环境搭建

(一)安装 Java 开发环境

首先,确保已经安装了最新版本的 Java 开发工具包(JDK)。可以从 Oracle 官方网站或 OpenJDK 项目网站下载适合操作系统的 JDK 版本,并按照安装向导进行安装。安装完成后,通过在命令行中输入 java -version 命令来验证 Java 是否正确安装以及查看安装的版本信息。

(二)下载和配置 JOGL

  1. 从 JOGL 的官方网站(JOGL - Java Binding for the OpenGL API)下载 JOGL 的二进制发布包。通常会提供针对不同操作系统和 Java 版本的预编译库文件。
  2. 解压下载的文件,将 JOGL 的库文件(.jar 文件)添加到项目的类路径中。在使用集成开发环境(IDE)如 Eclipse 或 IntelliJ IDEA 时,可以通过项目设置中的 “Libraries” 或 “Dependencies” 选项来添加这些库文件。同时,还需要将 JOGL 依赖的本地库文件(.dll 文件 for Windows,.so 文件 for Linux,.dylib 文件 for macOS)所在的目录添加到系统的库路径中,这一步骤可能因操作系统和 IDE 的不同而有所差异,一般可以通过修改环境变量(如 PATH 或 LD_LIBRARY_PATH)来实现。

(三)创建第一个 JOGL 项目

在 IDE 中创建一个新的 Java 项目,然后创建一个简单的 Java 类作为入口点。在类中导入 JOGL 的相关包,例如:

import com.jogamp.opengl.GL;
import com.jogamp.opengl.GL2;
import com.jogamp.opengl.GLAutoDrawable;
import com.jogamp.opengl.GLCapabilities;
import com.jogamp.opengl.GLEventListener;
import com.jogamp.opengl.awt.GLCanvas;
import javax.swing.JFrame;

这些包涵盖了 JOGL 中用于初始化 OpenGL 上下文、处理图形绘制事件以及创建显示窗口等基本功能的类和接口。接下来,就可以开始编写代码来创建一个简单的 JOGL 窗口并进行基本的图形绘制操作。

四、JOGL 图形绘制基础

(一)创建 OpenGL 上下文和窗口

public class FirstJOGLApp {public static void main(String[] args) {// 设置 OpenGL 能力GLCapabilities capabilities = new GLCapabilities(GLProfile.get(GLProfile.GL2));// 创建 GLCanvasGLCanvas canvas = new GLCanvas(capabilities);// 创建 JFrame 窗口JFrame frame = new JFrame("First JOGL Application");frame.getContentPane().add(canvas);frame.setSize(640, 480);frame.setVisible(true);frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);}
}

在上述代码中,首先通过 GLCapabilities 类设置所需的 OpenGL 版本(这里选择 GL2),然后创建 GLCanvas 对象作为 OpenGL 绘图的画布,并将其添加到 JFrame 窗口中。最后设置窗口的大小、可见性和关闭操作,这样就创建了一个基本的 JOGL 应用程序窗口,尽管此时窗口中还没有绘制任何图形。

(二)绘制基本图形

为了在窗口中绘制图形,需要实现 GLEventListener 接口,该接口定义了一系列用于处理 OpenGL 绘图事件的方法,其中关键的是 display 方法,在这个方法中进行图形绘制操作。

public class SimpleShapeDrawer implements GLEventListener {@Overridepublic void init(GLAutoDrawable drawable) {// 初始化操作,这里可以设置一些 OpenGL 的初始状态GL2 gl = drawable.getGL().getGL2();gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // 设置背景颜色为黑色}@Overridepublic void display(GLAutoDrawable drawable) {GL2 gl = drawable.getGL().getGL2();gl.glClear(GL.GL_COLOR_BUFFER_BIT); // 清除颜色缓冲区gl.glBegin(GL2.GL_TRIANGLES); // 开始绘制三角形gl.glColor3f(1.0f, 0.0f, 0.0f); // 设置当前颜色为红色gl.glVertex2f(-0.5f, -0.5f); // 三角形的第一个顶点gl.glColor3f(0.0f, 1.0f, 0.0f); // 设置当前颜色为绿色gl.glVertex2f(0.5f, -0.5f); // 三角形的第二个顶点gl.glColor3f(0.0f, 0.0f, 1.0f); // 设置当前颜色为蓝色gl.glVertex2f(0.0f, 0.5f); // 三角形的第三个顶点gl.glEnd(); // 结束绘制三角形}@Overridepublic void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {// 处理窗口大小改变事件}@Overridepublic void dispose(GLAutoDrawable drawable) {// 资源释放操作}
}

在 display 方法中,首先使用 glClear 方法清除颜色缓冲区,然后使用 glBegin 和 glEnd 方法定义了一个三角形的绘制过程,通过 glColor3f 方法设置每个顶点的颜色,glVertex2f 方法指定顶点的坐标,这样就在窗口中绘制了一个彩色的三角形。

(三)理解 OpenGL 坐标系统和绘图原理

OpenGL 使用一个右手坐标系,在 2D 情况下,原点通常位于窗口的左下角,x 轴向右为正方向,y 轴向上为正方向。在 3D 情况下,增加了 z 轴,指向屏幕外为正方向。当调用 glVertex 函数时,就是在这个坐标系中指定图形的顶点位置。而 glBegin 和 glEnd 之间的一系列顶点定义了一个基本图形(如三角形、四边形等),OpenGL 根据这些顶点信息进行图形的渲染。不同的基本图形绘制模式(如 GL_TRIANGLESGL_QUADS 等)决定了如何将这些顶点组合成最终的图形,理解这些坐标系统和绘图原理是进行复杂图形绘制和 3D 建模的基础。

五、JOGL 图形渲染进阶

(一)纹理映射

纹理映射是将 2D 图像(纹理)应用到 3D 模型表面的技术,使得模型更加逼真和生动。

public class TextureMappingExample implements GLEventListener {private int textureId;@Overridepublic void init(GLAutoDrawable drawable) {GL2 gl = drawable.getGL().getGL2();try {// 加载纹理图像BufferedImage image = ImageIO.read(new File("texture.jpg"));// 生成纹理对象textureId = createTexture(gl, image);} catch (IOException e) {e.printStackTrace();}}private int createTexture(GL2 gl, BufferedImage image) {int[] textureIds = new int[1];gl.glGenTextures(1, textureIds, 0);int textureId = textureIds[0];gl.glBindTexture(GL2.GL_TEXTURE_2D, textureId);gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_LINEAR);gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_LINEAR);gl.glTexImage2D(GL2.GL_TEXTURE_2D, 0, GL2.GL_RGB, image.getWidth(), image.getHeight(),0, GL2.GL_RGB, GL2.GL_UNSIGNED_BYTE, new DataBufferByte(image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth()), image.getWidth() * image.getHeight() * 3));return textureId;}@Overridepublic void display(GLAutoDrawable drawable) {GL2 gl = drawable.getGL().getGL2();gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);gl.glEnable(GL2.GL_TEXTURE_2D);gl.glBindTexture(GL2.GL_TEXTURE_2D, textureId);gl.glBegin(GL2.GL_QUADS);gl.glTexCoord2f(0.0f, 0.0f); gl.glVertex3f(-0.5f, -0.5f, 0.0f);gl.glTexCoord2f(1.0f, 0.0f); gl.glVertex3f(0.5f, -0.5f, 0.0f);gl.glTexCoord2f(1.0f, 1.0f); gl.glVertex3f(0.5f, 0.5f, 0.0f);gl.glTexCoord2f(0.0f, 1.0f); gl.glVertex3f(-0.5f, 0.5f, 0.0f);gl.glEnd();gl.glDisable(GL2.GL_TEXTURE_2D);}// 其他方法的实现...
}

在上述代码中,init 方法用于加载纹理图像并生成纹理对象,通过 glGenTextures 生成纹理 ID,然后使用 glTexParameteri 设置纹理过滤参数,最后使用 glTexImage2D 将图像数据上传到纹理对象中。在 display 方法中,首先启用纹理,绑定纹理对象,然后在绘制四边形时,通过 glTexCoord2f 方法指定每个顶点对应的纹理坐标,这样就将纹理正确地映射到了四边形表面。

(二)光照效果

光照效果可以增强 3D 场景的真实感,JOGL 支持多种光照模型,如环境光、漫反射光、镜面反射光等。

public class LightingExample implements GLEventListener {@Overridepublic void init(GLAutoDrawable drawable) {GL2 gl = drawable.getGL().getGL2();gl.glEnable(GL2.GL_LIGHTING);gl.glEnable(GL2.GL_LIGHT0);// 设置环境光float[] ambientLight = { 0.2f, 0.2f, 0.2f, 1.0f };gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_AMBIENT, ambientLight, 0);// 设置漫反射光float[] diffuseLight = { 0.8f, 0.8f, 0.8f, 1.0f };gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_DIFFUSE, diffuseLight, 0);// 设置光源位置float[] lightPosition = { 0.0f, 0.0f, 2.0f, 1.0f };gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_POSITION, lightPosition, 0);gl.glEnable(GL2.GL_COLOR_MATERIAL);}@Overridepublic void display(GLAutoDrawable drawable) {GL2 gl = drawable.getGL().getGL2();gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);gl.glPushMatrix();gl.glTranslatef(0.0f, 0.0f, -2.0f);// 绘制一个简单的 3D 物体,如球体glut.glutSolidSphere(0.5f, 32, 32);gl.glPopMatrix();}// 其他方法的实现...
}

在 init 方法中,首先启用光照和一个光源(GL_LIGHT0),然后分别设置环境光、漫反射光的颜色和光源的位置,并启用颜色材质,使得物体的颜色能够受到光照的影响。在 display 方法中,绘制一个球体,并通过 glTranslatef 方法将其移到合适的位置,在光照的作用下,球体将呈现出更加真实的光影效果。

(三)几何变换

几何变换包括平移、旋转和缩放,通过这些变换可以实现 3D 模型的动态效果和场景的构建。

public class TransformationExample implements GLEventListener {private float angle = 0.0f;@Overridepublic void display(GLAutoDrawable drawable) {GL2 gl = drawable.getGL().getGL2();gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);gl.glLoadIdentity();gl.glTranslatef(0.0f, 0.0f, -3.0f);gl.glRotatef(angle, 0.0f, 1.0f, 0.0f);// 绘制一个立方体drawCube(gl);angle += 0.5f;}private void drawCube(GL2 gl) {gl.glBegin(GL2.GL_QUADS);// 前面gl.glColor3f(1.0f, 0.0f, 0.0f);gl.glVertex3f(-0.5f, -0.5f, 0.5f);gl.glVertex3f(0.5f, -0.5f, 0.5f);gl.glVertex3f(0.5f, 0.5f, 0.5f);gl.glVertex3f(-0.5f, 0.5f, 0.5f);// 后面gl.glColor3f(0.0f, 1.0f, 0.0f);gl.glVertex3f(-0.5f, -0.5f, -0.5f);gl.glVertex3f(0.5f, -0.5f, -0.5f);gl.glVertex3f(0.5f, 0.5f, -0.5f);gl.glVertex3f(-0.5f, 0.5f, -0.5f);// 其他面的绘制...gl.glEnd();}// 其他方法的实现...
}

在 display 方法中,首先使用 glLoadIdentity 重置当前矩阵为单位矩阵,然后通过 glTranslatef 将坐标系原点移动到合适的位置,接着使用 glRotatef 方法根据不断变化的角度绕 y 轴旋转,在每次绘制立方体时,立方体都会绕 y 轴旋转一定角度,从而实现动态的旋转效果。通过类似的方法,还可以实现平移和缩放变换,以及组合多种变换来创建复杂的动画效果和场景布局。

六、JOGL 高级应用

(一)3D 模型加载与渲染

JOGL 可以与一些 3D 模型加载库(如 Assimp 等)结合使用,来加载复杂的 3D 模型并进行渲染。以下是一个使用 Assimp 加载 3D 模型并在 JOGL 中渲染的简单示例:

import com.jogamp.opengl.GL2;
import com.jogamp.opengl.GLAutoDrawable;
import com.jogamp.opengl.GLEventListener;
import com.jogamp.opengl.awt.GLCanvas;
import javax.swing.JFrame;
import org.lwjgl.assimp.AIAnimation;
import org.lwjgl.assimp.AIColor4D;
import org.lwjgl.assimp.AIFace;
import org.lwjgl.assimp.AIMesh;
import org.lwjgl.assimp.AINode;
import org.lwjgl.assimp.AIScene;
import org.lwjgl.assimp.AIVector3D;
import org.lwjgl.assimp.Assimp;import java.nio.FloatBuffer;
import java.nio.IntBuffer;import static org.lwjgl.assimp.Assimp.aiGetMeshBoundingBox;
import static org.lwjgl.assimp.Assimp.aiProcess_Triangulate;
import static org.lwjgl.assimp.Assimp.aiProcess_GenSmoothNormals;
import static org.lwjgl.assimp.Assimp.aiProcess_FlipUVs;
import static org.lwjgl.assimp.Assimp.aiProcess_CalcTangentSpace;
import static org.lwjgl.system.MemoryUtil.memAllocFloat;
import static org.lwjgl.system.MemoryUtil.memAllocInt;
import static org.lwjgl.system.MemoryUtil.memFree;public class ModelLoadingExample implements GLEventListener {private int[] vaoId = new int[1];private int[] vboId = new int[3];private AIScene scene;public ModelLoadingExample() {// 加载 3D 模型,这里以一个简单的 cube.obj 为例scene = Assimp.aiImportFile("cube.obj", aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs | aiProcess_CalcTangentSpace);}@Overridepublic void init(GLAutoDrawable drawable) {GL2 gl = drawable.getGL().getGL2();// 生成并绑定 VAOgl.glGenVertexArrays(1, vaoId, 0);gl.glBindVertexArray(vaoId[0]);// 处理模型的每个网格for (int i = 0; i < scene.mNumMeshes(); i++) {AIMesh mesh = AIMesh.create(scene.mMeshes().get(i));// 顶点坐标数据FloatBuffer vertices = memAllocFloat(mesh.mNumVertices() * 3);for (int j = 0; j < mesh.mNumVertices(); j++) {AIVector3D vertex = mesh.mVertices().get(j);vertices.put(vertex.x()).put(vertex.y()).put(vertex.z());}vertices.flip();// 顶点法向量数据FloatBuffer normals = memAllocFloat(mesh.mNumVertices() * 3);for (int j = 0; j < mesh.mNumVertices(); j++) {AIVector3D normal = mesh.mNormals().get(j);normals.put(normal.x()).put(normal.y()).put(normal.z());}normals.flip();// 面索引数据IntBuffer indices = memAllocInt(mesh.mNumFaces() * 3);for (int j = 0; j < mesh.mNumFaces(); j++) {AIFace face = mesh.mFaces().get(j);indices.put(face.mIndices());}indices.flip();// 生成并绑定 VBOgl.glGenBuffers(3, vboId, 0);// 顶点坐标 VBOgl.glBindBuffer(GL2.GL_ARRAY_BUFFER, vboId[0]);gl.glBufferData(GL2.GL_ARRAY_BUFFER, vertices, GL2.GL_STATIC_DRAW);gl.glVertexAttribPointer(0, 3, GL2.GL_FLOAT, false, 0, 0);// 顶点法向量 VBOgl.glBindBuffer(GL2.GL_ARRAY_BUFFER, vboId[1]);gl.glBufferData(GL2.GL_ARRAY_BUFFER, normals, GL2.GL_STATIC_DRAW);gl.glVertexAttribPointer(1, 3, GL2.GL_FLOAT, false, 0, 0);// 面索引 VBO(Element Buffer Object)gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, vboId[2]);gl.glBufferData(GL2.GL_ELEMENT_ARRAY_BUFFER, indices, GL2.GL_STATIC_DRAW);// 解绑 VBO 和 VAOgl.glBindBuffer(GL2.GL_ARRAY_BUFFER, 0);gl.glBindVertexArray(0);// 释放内存memFree(vertices);memFree(normals);memFree(indices);}// 设置一些渲染状态gl.glEnable(GL2.GL_DEPTH_TEST);gl.glEnable(GL2.GL_CULL_FACE);}@Overridepublic void display(GLAutoDrawable drawable) {GL2 gl = drawable.getGL().getGL2();gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);gl.glBindVertexArray(vaoId[0]);gl.glEnableVertexAttribArray(0);gl.glEnableVertexAttribArray(1);// 绘制模型的每个网格for (int i = 0; i < scene.mNumMeshes(); i++) {AIMesh mesh = AIMesh.create(scene.mMeshes().get(i));gl.glDrawElements(GL2.GL_TRIANGLES, mesh.mNumFaces() * 3, GL2.GL_UNSIGNED_INT, 0);}gl.glDisableVertexAttribArray(0);gl.glDisableVertexAttribArray(1);gl.glBindVertexArray(0);}@Overridepublic void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {GL2 gl = drawable.getGL().getGL2();gl.glViewport(0, 0, width, height);gl.glMatrixMode(GL2.GL_PROJECTION);gl.glLoadIdentity();glu.gluPerspective(45.0f, (float) width / height, 0.1f, 100.0f);gl.glMatrixMode(GL2.GL_MODELVIEW);}@Overridepublic void dispose(GLAutoDrawable drawable) {GL2 gl = drawable.getGL().getGL2();gl.glDeleteVertexArrays(1, vaoId, 0);gl.glDeleteBuffers(3, vboId, 0);// 释放 Assimp 场景资源Assimp.aiReleaseImport(scene);}public static void main(String[] args) {GLCapabilities capabilities = new GLCapabilities(GLProfile.get(GLProfile.GL2));GLCanvas canvas = new GLCanvas(capabilities);ModelLoadingExample example = new ModelLoadingExample();canvas.addGLEventListener(example);JFrame frame = new JFrame("3D Model Loading in JOGL");frame.getContentPane().add(canvas);frame.setSize(800, 600);frame.setVisible(true);frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);}
}

在上述代码中,首先使用 Assimp 库加载 3D 模型文件(这里以 cube.obj 为例),在 init 方法中,对模型的每个网格进行处理,将顶点坐标、法向量和面索引数据分别存储到顶点缓冲对象(VBO)中,并创建顶点数组对象(VAO)来管理这些 VBO。在 display 方法中,通过绑定 VAO 和相应的 VBO,启用顶点属性数组,使用 glDrawElements 方法绘制模型的三角形面,从而将 3D 模型渲染到屏幕上。同时,在 reshape 方法中处理窗口大小改变事件,调整投影矩阵,在 dispose 方法中释放 JOGL 和 Assimp 相关的资源。

(二)创建复杂 3D 场景

要创建复杂的 3D 场景,可以结合多个模型、光照、纹理以及几何变换等技术。例如,构建一个包含多个 3D 物体(如建筑物、树木、人物等)的室外场景:

public class ComplexSceneExample implements GLEventListener {// 存储不同模型的加载和渲染实例private ModelLoadingExample buildingModel;private ModelLoadingExample treeModel;private ModelLoadingExample characterModel;public ComplexSceneExample() {buildingModel = new ModelLoadingExample("building.obj");treeModel = new ModelLoadingExample("tree.obj");characterModel = new ModelLoadingExample("character.obj");}@Overridepublic void init(GLAutoDrawable drawable) {GL2 gl = drawable.getGL().getGL2();// 初始化每个模型的 OpenGL 资源buildingModel.init(drawable);treeModel.init(drawable);characterModel.init(drawable);// 设置场景的光照效果(与之前的光照示例类似)gl.glEnable(GL2.GL_LIGHTING);gl.glEnable(GL2.GL_LIGHT0);// 设置环境光、漫反射光和光源位置等参数//...// 设置其他场景相关的 OpenGL 状态,如深度测试、面剔除等gl.glEnable(GL2.GL_DEPTH_TEST);gl.glEnable(GL2.GL_CULL_FACE);}@Overridepublic void display(GLAutoDrawable drawable) {GL2 gl = drawable.getGL().getGL2();gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);// 绘制建筑物,并应用适当的几何变换(如平移、旋转、缩放)gl.glPushMatrix();gl.glTranslatef(-2.0f, 0.0f, -5.0f);gl.glScalef(2.0f, 2.0f, 2.0f);buildingModel.display(drawable);gl.glPopMatrix();// 绘制树木,并分布在场景中的不同位置for (int i = 0; i < 5; i++) {gl.glPushMatrix();gl.glTranslatef((float) Math.random() * 4 - 2, 0.0f, (float) Math.random() * 4 - 2);gl.glScalef(0.5f, 0.5f, 0.5f);treeModel.display(drawable);gl.glPopMatrix();}// 绘制人物,并设置其动画效果(如果模型支持动画)gl.glPushMatrix();gl.glTranslatef(2.0f, 0.0f, -3.0f);characterModel.display(drawable);gl.glPopMatrix();}// 实现 reshape 和 dispose 方法,与之前的示例类似,处理窗口大小改变和资源释放@Overridepublic void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {GL2 gl = drawable.getGL().getGL2();gl.glViewport(0, 0, width, height);gl.glMatrixMode(GL2.GL_PROJECTION);gl.glLoadIdentity();glu.gluPerspective(45.0f, (float) width / height, 0.1f, 100.0f);gl.glMatrixMode(GL2.GL_MODELVIEW);}@Overridepublic void dispose(GLAutoDrawable drawable) {GL2 gl = drawable.getGL().getGL2();// 释放每个模型的 OpenGL 资源buildingModel.dispose(drawable);treeModel.dispose(drawable);characterModel.dispose(drawable);}public static void main(String[] args) {GLCapabilities capabilities = new GLCapabilities(GLProfile.get(GLProfile.GL2));GLCanvas canvas = new GLCanvas(capabilities);ComplexSceneExample example = new ComplexSceneExample();canvas.addGLEventListener(example);JFrame frame = new JFrame("Complex 3D Scene in JOGL");frame.getContentPane().add(canvas);frame.setSize(800, 600);frame.setVisible(true);frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);}
}

在这个示例中,创建了一个 ComplexSceneExample 类,在其构造函数中初始化了不同的 3D 模型加载实例(假设已经有相应的模型文件)。在 init 方法中,对每个模型进行初始化,并设置场景的光照和其他 OpenGL 状态。在 display 方法中,通过多次调用每个模型的 display 方法,并结合不同的几何变换,将建筑物、树木和人物等模型放置在场景中的合适位置,从而构建出一个复杂的室外 3D 场景。同时,在 reshape 和 dispose 方法中处理窗口大小改变和资源释放的操作,确保程序的正确性和性能。

(三)优化与性能调优

随着 3D 场景的复杂性增加,性能优化变得至关重要。以下是一些 JOGL 应用程序的性能优化技巧:

  • 顶点缓冲对象(VBO)和顶点数组对象(VAO)优化
    • 合理组织顶点数据,尽量减少数据的冗余存储。例如,对于多个共享相同顶点坐标和法向量的三角形面,可以使用索引缓冲对象(Element Buffer Object,EBO)来避免重复存储顶点数据,提高内存使用效率和渲染性能。
    • 在创建和更新 VBO 和 VAO 时,遵循最佳实践。例如,尽量减少不必要的 glBindBuffer 和 glVertexAttribPointer 调用,将相关的操作集中在一起,以减少 OpenGL 状态的切换开销。
  • 纹理优化
    • 对于纹理图像,选择合适的图像格式和压缩方式。例如,使用 DXT 压缩格式(在支持的情况下)可以减少纹理数据的内存占用,同时提高纹理加载和渲染速度。
    • 合理设置纹理过滤参数(如 GL_TEXTURE_MIN_FILTER 和 GL_TEXTURE_MAG_FILTER),根据场景的需求选择合适的过滤模式,如线性过滤(GL_LINEAR)或最近邻过滤(GL_NEAREST),以平衡图像质量和性能。
  • 渲染管线优化
    • 减少不必要的渲染操作,例如通过视锥体裁剪(Frustum Culling)技术,只渲染在摄像机视锥体范围内的物体,避免对不可见物体的渲染计算,从而提高渲染效率。
    • 合理使用深度测试(GL_DEPTH_TEST)和面剔除(GL_CULL_FACE)等技术,减少不必要的片元处理,提高渲染性能。同时,注意面剔除的设置,确保正确剔除不可见的面(如背面剔除)。
  • 模型简化与细节层次(LOD)
    • 对于复杂的 3D 模型,在不影响视觉效果的前提下,可以进行简化处理,减少模型的顶点和面的数量,降低渲染开销。例如,使用一些模型简化算法,如基于边收缩的方法,生成不同细节层次的模型版本。
    • 实现细节层次(LOD)技术,根据物体与摄像机的距离,动态切换使用不同细节层次的模型,近距离使用高细节模型,远距离使用低细节模型,从而在保证视觉效果的同时,提高整体场景的渲染性能。

通过综合运用这些优化技巧,可以显著提高 JOGL 应用程序的性能,使其能够流畅地渲染复杂的 3D 场景,提供更好的用户体验。同时,性能优化是一个持续的过程,需要根据具体的应用场景和硬件条件进行不断的测试和调整,以找到最佳的性能平衡点。

七、JOGL 在不同领域的应用案例

(一)游戏开发

在 2D 和 3D 游戏开发中,JOGL 可以用于创建精美的游戏场景、角色模型和特效。例如,一款 3D 冒险游戏可以利用 JOGL 的强大渲染能力来构建逼真的游戏世界,包括地形、建筑、怪物等元素。通过加载不同的 3D 模型和应用纹理、光照效果,使游戏画面更加生动。同时,利用 JOGL 的几何变换和动画技术,实现角色的移动、攻击动作以及场景的动态变化,为玩家提供沉浸式的游戏体验。

(二)科学可视化

在科学研究领域,JOGL 可以将复杂的科学数据可视化,帮助研究人员更好地理解和分析数据。例如,在气象学中,可以使用 JOGL 绘制 3D 天气模型,展示大气环流、云层分布等信息;在生物学中,通过 JOGL 渲染分子结构模型,直观呈现蛋白质、DNA 等生物大分子的三维形态,辅助科学家进行结构分析和功能研究;在物理学中,模拟粒子系统、磁场分布等物理现象,并以 3D 图形的形式展示出来。

(三)虚拟现实(VR)和增强现实(AR)应用

随着 VR 和 AR 技术的兴起,JOGL 也能在这些领域发挥重要作用。在 VR 应用中,它可以用于创建高度沉浸式的虚拟环境,用户通过头戴式显示设备能够身临其境地感受虚拟世界中的场景和物体交互。例如,构建一个虚拟的培训场景,如飞行模拟训练,通过精确的 3D 模型渲染和实时的图形更新,模拟出真实的飞行仪表、驾驶舱环境以及外部的天空、地形等景象,配合上追踪设备获取用户的头部和手部动作,实现逼真的操控体验。

对于 AR 应用,JOGL 可以将虚拟的 3D 模型或信息叠加到现实世界的图像上,增强用户对现实场景的感知。比如在一款 AR 家居装修应用中,用户使用手机摄像头拍摄房间,JOGL 可以将各种家具的 3D 模型实时渲染并准确地放置在摄像头捕捉到的画面中,用户可以从不同角度查看家具的摆放效果,还可以对家具进行缩放、旋转等操作,帮助他们在购买前更好地规划家居布局。

(四)教育领域

在教育领域,JOGL 为创建交互式的教学工具提供了可能性。例如,在数学和几何教学中,可以利用 JOGL 开发动态的几何图形演示软件,通过直观地展示 3D 几何形状的旋转、平移、变形等操作,帮助学生更好地理解空间几何概念。对于物理学科,能够创建物理实验的模拟环境,如牛顿力学中的物体运动、碰撞实验,电学中的电路连接与电流走向等,让学生通过亲手操作虚拟实验设备,观察实验现象,深入理解物理原理,并且不用担心实际实验中的设备损坏和安全问题。

(五)建筑设计与可视化

建筑师可以使用 JOGL 来创建建筑设计的 3D 模型,并进行实时的可视化和修改。在设计阶段,能够快速地将设计草图转化为详细的 3D 建筑模型,展示建筑的外观、内部结构以及周边环境。通过 JOGL 的光影效果模拟,设计师可以直观地看到不同时间段阳光照射下建筑物的阴影变化,评估采光效果;还可以在模型中模拟人员在建筑内部的行走路径,优化空间布局和交通流线。同时,与客户沟通时,利用 JOGL 生成的高质量渲染图和实时交互演示,能够让客户更清晰地理解设计方案,提出更具体的修改意见,从而提高设计效率和客户满意度。

八、常见问题与解决方案

(一)图形显示异常

  • 问题描述:模型出现破面、闪烁、纹理拉伸或错误等情况。
  • 可能原因及解决方案
    • 顶点数据错误:检查模型的顶点坐标、法向量等数据是否正确生成和传递。可能是在模型导入或数据处理过程中出现了精度丢失或数据损坏的情况。可以使用调试工具查看顶点数据的值,确保其符合预期的几何形状和拓扑结构。
    • 纹理坐标问题:纹理拉伸或错误可能是由于纹理坐标设置不正确。确认纹理坐标的范围是否在 [0, 1] 之间,并且与纹理图像的尺寸和模型的表面对应关系是否准确。如果使用了自动生成纹理坐标的算法,检查其是否适用于当前的模型几何形状。
    • 深度测试和面剔除设置不当:破面和闪烁问题可能与深度测试或面剔除的设置有关。确保深度测试(GL_DEPTH_TEST)已正确启用,并且深度缓冲区的清除和写入操作正常。对于面剔除,检查面剔除模式(如 GL_BACKGL_FRONT 或 GL_FRONT_AND_BACK)是否符合模型的要求,避免错误地剔除了应该显示的面。

(二)性能瓶颈

  • 问题描述:应用程序在渲染复杂场景时帧率过低,出现卡顿现象。
  • 可能原因及解决方案
    • 过度绘制:检查场景中是否存在大量被遮挡但仍然被绘制的物体。可以通过使用视锥体裁剪技术,只绘制在摄像机视锥体范围内且可见的物体,减少不必要的绘制操作。同时,优化场景的层次结构,将远处的物体和近处的物体分别进行管理和绘制,避免远处的小物体频繁更新和绘制对性能的影响。
    • 低效的图形算法:某些复杂的图形算法,如光照计算、阴影生成等,如果实现方式不够高效,可能会导致性能下降。对于光照计算,可以采用更简单的光照模型(如 Phong 光照模型的简化版本)或者使用光照贴图等预计算技术,减少实时计算的开销。对于阴影生成,考虑使用基于深度贴图的阴影算法,相比于传统的阴影体积算法,它通常具有更好的性能表现。
    • 内存管理不善:频繁地创建和销毁图形对象(如纹理、VBO、VAO 等)可能会导致内存碎片化和性能下降。优化内存管理策略,尽量在程序初始化阶段创建常用的图形对象,并在整个程序生命周期中重复使用,避免不必要的内存分配和释放操作。同时,检查是否存在内存泄漏问题,确保不再使用的图形资源被正确释放。

(三)兼容性问题

  • 问题描述:应用程序在某些操作系统或显卡驱动下出现崩溃、无法启动或图形显示异常等兼容性问题。
  • 可能原因及解决方案
    • 显卡驱动版本:不同版本的显卡驱动对 OpenGL 的支持程度可能有所差异,某些较新的 JOGL 功能可能在旧版本的驱动中不被支持,或者旧的 JOGL 代码在新驱动下可能出现兼容性问题。建议更新显卡驱动到最新版本,同时查看 JOGL 的官方文档和社区论坛,了解是否存在已知的与特定显卡驱动版本相关的兼容性问题,并遵循官方的建议进行解决。
    • 操作系统差异:JOGL 在不同操作系统上的表现可能略有不同,尤其是在处理窗口系统事件、内存管理和图形上下文创建等方面。对于跨平台开发,需要进行充分的测试,确保应用程序在各个目标操作系统上都能正常运行。可以使用条件编译或运行时检测机制,针对不同操作系统的特定问题进行代码调整,例如在某些操作系统上可能需要额外的库加载步骤或环境变量设置才能正确运行 JOGL 应用程序。

九、学习资源

(一)在线教程

  • JOGL 官方网站:包含了 JOGL 的各种信息,如教程、文档和示例代码等,是学习 JOGL 的重要起点1.
  • JOGL Wiki:有大量关于 JOGL 的文章和教程,涵盖了从开发环境搭建、基础 3D 图形绘制到高级特性应用等多个主题.
  • Prutor JOGL Useful Resources:整合了 JOGL 的相关资源,包括官网、论坛、邮件列表、GitHub 仓库、书籍推荐等,为学习者提供了全面的学习途径.
  • 微信公众号文章《JOGL,一个 Java OpenGL 的画笔大师!》:对 JOGL 的核心概念、基本用法、环境搭建以及实战案例进行了介绍,还提供了一些性能提升和常见问题解决的建议.

(二)书籍

  • 《Beginning JOGL: A Practical Guide to 3D Graphics Programming with Java》:作者 Jonathan Blow,全面介绍了 JOGL 的基础知识和实践应用,适合初学者快速入门.
  • 《JOGL: The Java OpenGL Library》:作者 Romain Guy,深入讲解了 JOGL 的各种特性和功能,帮助读者深入理解和掌握 JOGL 的高级应用.
  • 《OpenGL Programming Guide: The Official Guide to Learning OpenGL, Version 4.3》:作者 Dave Shreiner、Kevin K. Suffern 和 Graham Sellers,虽然不是专门针对 JOGL 的书籍,但作为 OpenGL 的权威指南,对于理解 JOGL 所基于的 OpenGL 原理和技术非常有帮助,可辅助读者更好地学习 JOGL.
  • 《The OpenGL ES 2.0 Programming Guide》:作者 Aaftab Munshi、Dan Ginsburg 和 Dave Shreiner,主要聚焦于 OpenGL ES 2.0,对于想要在移动设备或嵌入式系统中使用 JOGL 的开发者来说,是一本很有价值的参考书籍.

(三)示例代码与项目

  • JOGL Demos:官方提供的一系列示例项目,涵盖了各种 JOGL 的应用场景和技术点,如基本图形绘制、纹理映射、光照效果、动画等,通过学习这些示例代码,能够快速掌握 JOGL 的实际应用技巧.
  • GitHub 上的 JOGL 相关项目:在 GitHub 上搜索 “JOGL”,可以找到许多开源的 JOGL 项目,这些项目可以作为学习和参考的资源,帮助读者了解不同开发者如何在实际项目中运用 JOGL,学习到一些优秀的编程实践和设计模式.

(四)论坛与社区

  • JOGL Forums:这是 JOGL 的官方论坛,开发者可以在这里提问、分享经验、交流心得,还能获取其他 JOGL 用户的帮助和建议,对于解决学习和开发过程中遇到的问题非常有帮助.
  • Stack Overflow:作为一个知名的技术问答平台,也有许多关于 JOGL 的问题和解答,通过搜索相关问题,可以找到很多有用的信息和解决方案,同时也可以自己提问,获得社区的帮助.

十、结语

JOGL 作为 Java 平台上强大的图形编程库,为开发者提供了广阔的创作空间,从基础的图形绘制到复杂的 3D 场景构建、跨领域的应用开发,它都展现出了卓越的性能和丰富的功能。通过深入学习 JOGL 的各个方面,从环境搭建、基础图形绘制、渲染进阶技术,到高级应用开发以及性能优化和常见问题解决,开发者能够逐步掌握这一工具,将创意转化为精美的图形应用程序。无论是游戏开发、科学研究、教育教学还是其他众多领域,JOGL 都有潜力成为实现创新和价值的有力武器,随着技术的不断发展和社区的持续贡献,JOGL 的应用前景也将更加广阔,期待更多的开发者能够利用 JOGL 创造出令人惊叹的图形应用作品,推动 Java 图形编程领域的不断进步和发展。

相关文章:

JOGL 从入门到精通:开启 Java 3D 图形编程之旅

一、引言 Java 作为一门广泛应用的编程语言&#xff0c;在图形编程领域也有着强大的工具和库。JOGL&#xff08;Java OpenGL&#xff09;便是其中之一&#xff0c;它为 Java 开发者提供了访问 OpenGL&#xff08;Open Graphics Library&#xff09;功能的接口&#xff0c;使得…...

汽车网络安全基线安全研究报告

一、引言 随着汽车行业朝着智能网联方向飞速发展&#xff0c;汽车网络安全已成为保障用户安全和行业健康发展的关键要素。本报告将深入探讨汽车网络安全相关内容&#xff0c;以及国际、国内重要的汽车网络安全标准基线和相应防护措施等内容。 二、汽车网络安全的重要性 &…...

Eclipse 修改项目栏字体大小

1、菜单栏选择window->preference&#xff0c;然后选择General->Appearance->Colors and Fonts&#xff0c;在搜索栏输入"tree"&#xff0c;点击"Edit"修改字体。 2、修改字体&#xff0c;选择"四号字体"&#xff0c;点击"确定&qu…...

【PCIe 总线及设备入门学习专栏 5.1 -- PCIe 引脚 PRSNT 与热插拔】

文章目录 OverviewPRSNT 与热插拔PRSNT 硬件设计 Overview Spec 定义的热插拔是把一个PCIe卡&#xff08;设备&#xff09;从一个正在运行的背板或者系统中插入/或者移除。这个过程需要不影响系统的其他功能。插入的新的设备可以正确工作。 显然&#xff0c;这里面需要考虑的问…...

【YOLO】YOLOv5原理

概述 YOLOv5的主要架构 Backbone&#xff08;主干网络&#xff09;&#xff1a;负责提取输入图像的多层次特征 Neck&#xff08;颈部网络&#xff09;&#xff1a;进行特征融合和多尺度特征处理&#xff0c;通常包含FPN&#xff08;特征金字塔网络&#xff09;和PAN&#xff0…...

uniapp中wx.getFuzzyLocation报错如何解决

一、用wx.getLocation接口审核不通过 用uniapp开发小程序时难免需要获取当前地理位置。 代码如下&#xff1a; uni.getLocation({type: wgs84,success: function (res) {console.log(当前位置的经度&#xff1a; res.longitude);console.log(当前位置的纬度&#xff1a; r…...

opencv图像直方图

【欢迎关注编码小哥&#xff0c;学习更多实用的编程方法和技巧】 1、基本直方图计算 // 灰度图直方图 cv::Mat calculateGrayscaleHistogram(const cv::Mat& image) {cv::Mat histogram;int histSize 256; // 灰度级别float range[] {0, 256};const float* histRange …...

OpenCV计算机视觉 03 椒盐噪声的添加与常见的平滑处理方式(均值、方框、高斯、中值)

上一篇文章&#xff1a;OpenCV计算机视觉 02 图片修改 图像运算 边缘填充 阈值处理 目录 添加椒盐噪声 图像平滑常见处理方式 均值滤波 (blur) 方框滤波 (boxFilter) ​高斯滤波 (GaussianBlur) 中值滤波 (medianBlur) 添加椒盐噪声 def add_peppersalt_noise(image, n…...

【嵌入式C语言】内存分布

内存分布 内存分布图内存的属性&#xff1a;只读空间只读空间的特点编程注意事项 栈空间栈的工作原理栈的特点栈溢出与堆的区别 堆空间堆的特点内存分配函数内存泄漏总结 内存分布图 内存的属性&#xff1a; 在C语言中&#xff0c;内存的属性主要取决于它是如何分配的以及它在…...

【brainpan靶场渗透】

文章目录 一、基础信息 二、信息收集 三、反弹shell 四、提权 一、基础信息 Kali IP&#xff1a;192.168.20.146 靶机 IP&#xff1a;192.168.20.155 二、信息收集 似乎开放了9999&#xff0c;10000端口&#xff0c;访问页面没有太多内容&#xff0c;扫描一下目录 dirs…...

Java实现观察者模式

一、前言 观察者模式&#xff0c;又称为发布订阅模式&#xff0c;是一种行为设置模式&#xff0c;允许对象之间建立一对多的依赖关系&#xff0c;这样当一个对象状态改变时&#xff0c;它的所有依赖者&#xff08;观察者&#xff09;都会收到通知并自动更新。 二、具体实现 …...

通过百度api处理交通数据

通过百度api处理交通数据 1、读取excel获取道路数据 //道路名称Data EqualsAndHashCode public class RoadName {ExcelProperty("Name")private String name; }/*** 获取excel中的道路名称*/private static List<String> getRoadName() {// 定义文件路径&…...

探索CSDN博客数据:使用Python爬虫技术

探索CSDN博客数据&#xff1a;使用Python爬虫技术 在数字化的浪潮中&#xff0c;数据的获取与分析变得日益关键。CSDN作为中国领先的IT社区和服务平台&#xff0c;汇聚了海量的技术博客与文章&#xff0c;成为一座蕴藏丰富的数据宝库。本文将引领您穿梭于Python的requests和py…...

b站ip属地评论和主页不一样怎么回事

在浏览B站时&#xff0c;细心的用户可能会发现一个有趣的现象&#xff1a;某些用户的评论IP属地与主页显示的IP属地并不一致。这种差异引发了用户的好奇和猜测&#xff0c;究竟是什么原因导致了这种情况的发生呢&#xff1f;本文将对此进行深入解析&#xff0c;帮助大家揭开这一…...

如何查看服务器内存占用情况?

如何查看服务器的内存占用情况&#xff1f;你知道内存使用情况对服务器性能的重要性吗&#xff1f;内存是服务器运行的核心资源之一&#xff0c;了解内存的占用情况可以帮助你优化系统性能。 要查看服务器的内存占用情况&#xff0c;首先需要确定你使用的是哪种操作系统。不同…...

流架构的读书笔记(2)

流架构的读书笔记&#xff08;2&#xff09; 一、建模工具之一沃德利地图 推测技术的发展,交流和辩论思想的最有力的方法是沃德利地图 沃德利地图的制作步骤 1确定范围和用户需求 2确定满足用户需求所需的组件 3在一条范围从全新到被人们接受的演进轴上评估这些组成 部分的演…...

E6 中的 扩展运算符(Spread) 和 剩余运算符(Rest)

时间&#xff1a;2024.12.29 之前看到 Es6 中的 三点运算符&#xff0c;有如下的几种写法&#xff0c;有时候三点运算符放在左边&#xff0c;有时候三点运算符放在右边&#xff0c;老是混淆。今天记录下&#xff0c;加强理解。 先看一个问题 最近在看 《ECMAScript 6 入门》关于…...

Python的简单爬虫框架

爬虫为网络爬虫&#xff08;又称为网页蜘蛛&#xff0c;网络机器人&#xff0c;在FOAF社区中间&#xff0c;更经常的称为网页追逐者&#xff09;&#xff0c;是一种按照一定的规则&#xff0c;自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、…...

使用 uni-app 开发的微信小程序中,如何在从 B 页面回来时,重新拉取数据?

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的全栈工程师 欢迎分享 / 收藏 / 赞 / 在看…...

Windows API Set:那些“只存在但不被使用“的DLL

API Set 是什么&#xff1f; 想象一下&#xff0c;Windows就像一个大型图书馆&#xff0c;而API Set就是这个图书馆的索引系统。但这个索引系统非常特别&#xff1a;它是直接内置在Windows加载器中的"虚拟目录"。 // 一个典型的API Set映射示例 api-ms-win-core-mem…...

黑神话悟空鼠标光标分享

效果图&#xff1a; 鼠标光标特点 这套鼠标光标的设计灵感来源于《黑神话&#xff1a;悟空》游戏中的角色和元素&#xff0c;具有以下特点&#xff1a; • 主题鲜明&#xff1a;光标设计紧扣游戏主题&#xff0c;采用了游戏中的元素&#xff0c;让玩家在使用电脑时也能感受到…...

编写一个简单的引导加载程序(bootloader)

编写一个简单的引导加载程序&#xff08;bootloader&#xff09;通常用于嵌入式系统或自定义操作系统。这里&#xff0c;我将为你提供一个基于x86架构的简单汇编语言 bootloader 示例。这个 bootloader 将会在启动时打印一条消息到屏幕上。 使用 NASM 汇编器来编写这个 bootlo…...

【Linux基础】进程(上) —— 概念、状态、优先级与环境变量

目录 一、进程的概念 1. 什么是进程 PCB进程控制块的理解 2. 查看进程的方式 ps ajx 指令 getpid系统调用 3. 另外一种查看进程的方式(了解) 4. 进程的常见调用 fork 创建子进程 现象说明 二、进程的状态 1. 操作系统层面的进程状态 ① 运行状态 ② 阻塞状态 ③…...

Rust: enum 和 i32 的区别和互换

在Rust编程语言中&#xff0c;enum&#xff08;枚举&#xff09;和i32是两种不同类型的数据结构&#xff0c;它们各自有不同的用途和特性。 i32 i32是一个32位的有符号整数类型。它用于存储整数值&#xff0c;范围从-2,147,483,648到2,147,483,647。i32是Rust中的基本数据类型…...

2024年终回顾

前言 很久没有更新博客&#xff0c;因为工作内容主要是内场开发&#xff0c;后来有点和互联网脱轨&#xff0c;断断续续上来看一下。这个总结应该也很简单&#xff0c;涉及以下的几个内容进行逐一说明 一、就业问题 这个问题可能很尖锐&#xff0c;从大环境来说&#xff0c;去…...

RGB、HSV颜色模型及MATLAB互换应用实例

一、前言 RGB和HSV模型是数字图像处理中颜色空间中的两种重要表示方式&#xff0c;RGB和HSV都是描述颜色的数学模型&#xff0c;可以用于表示和处理图像中的颜色信息。 RGB模型是一种基于光的颜色模型&#xff0c;由红&#xff08;Red&#xff09;、绿&#xff08;Green&#x…...

# 【超全面了解鸿蒙生命周期】-生命周期补充

【超全面了解鸿蒙生命周期】-生命周期补充 鸿蒙所有的生命周期函数梳理 文章目录 【超全面了解鸿蒙生命周期】-生命周期补充前言一、AbilityStage的生命周期二、ExtensionAbility卡片生命周期三、Web组件常用生命周期 前言 本文是继之前写的生命周期函数梳理的进一步补充&…...

黑神话悟空游戏鼠标光标使用教程与下载

效果图&#xff1a; 鼠标光标特点 这套鼠标光标的设计灵感来源于《黑神话&#xff1a;悟空》游戏中的角色和元素&#xff0c;具有以下特点&#xff1a; • 主题鲜明&#xff1a;光标设计紧扣游戏主题&#xff0c;采用了游戏中的元素&#xff0c;让玩家在使用电脑时也能感受到…...

【机器学习】梯度下降

文章目录 1. 梯度下降概念2. 梯度下降的技巧2.1 动态设置学习率2.2 Adagrad调整梯度2.3 随机梯度下降&#xff08;SGD&#xff09;2.4 特征缩放 3. 梯度下降理论基础 1. 梯度下降概念 梯度&#xff1a;Loss 对参数在某一点的偏微分&#xff0c;函数沿梯度的方向具有最大的变化…...

【leetcode 07】707.设计链表

要点⭐ 链表的常见操作 获取第n个节点的值 头部插入节点 尾部插入节点 第n个节点前插入&#xff08;先立新&#xff0c;在破旧&#xff09; 删除第n个节点 class ListNode{int val;ListNode next;ListNode(){};ListNode(int val){this.valval;} } class MyLinkedList {//链表大…...