feat(render): 实现高性能OpenGL渲染面板

- 添加ModelGLPanel类支持离屏OpenGL渲染
- 集成LWJGL3.3.6版本并更新相关依赖
- 实现模型树节点转换功能
- 添加纹理读取与显示错误处理机制
- 引入CommonMark库支持Markdown解析
-优化物理系统注释信息
- 禁用部分调试日志输出
- 添加测试用例TestModelGLPanel
This commit is contained in:
tzdwindows 7
2025-10-13 10:56:56 +08:00
parent b501da0254
commit 082478cdb6
10 changed files with 440 additions and 13 deletions

View File

@@ -40,6 +40,7 @@ dependencies {
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
implementation 'org.commonmark:commonmark:0.24.0'
implementation 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.1'
implementation 'com.google.code.gson:gson:2.8.9'
@@ -81,13 +82,16 @@ dependencies {
implementation 'com.github.javaparser:javaparser-core:3.25.1'
implementation 'com.1stleg:jnativehook:2.1.0'
implementation 'org.json:json:20230618'
implementation 'org.lwjgl:lwjgl:3.3.1'
implementation 'org.lwjgl:lwjgl-stb:3.3.3'
implementation 'org.lwjgl:lwjgl-glfw:3.3.1'
implementation 'org.lwjgl:lwjgl-opengl:3.3.1'
implementation 'org.lwjgl:lwjgl:3.3.1:natives-windows'
implementation 'org.lwjgl:lwjgl-glfw:3.3.1:natives-windows'
implementation 'org.lwjgl:lwjgl-opengl:3.3.1:natives-windows'
implementation 'org.lwjgl:lwjgl:3.3.6'
implementation 'org.lwjgl:lwjgl-stb:3.3.6'
implementation 'org.lwjgl:lwjgl-glfw:3.3.6'
implementation 'org.lwjgl:lwjgl-opengl:3.3.6'
implementation 'org.lwjgl:lwjgl-jawt:3.3.5'
runtimeOnly 'org.lwjgl:lwjgl:3.3.6:natives-windows'
runtimeOnly 'org.lwjgl:lwjgl-glfw:3.3.6:natives-windows'
runtimeOnly 'org.lwjgl:lwjgl-opengl:3.3.6:natives-windows'
runtimeOnly 'org.lwjgl:lwjgl-stb:3.3.6:natives-windows'
implementation 'com.badlogicgames.gdx:gdx:1.12.1'
implementation 'org.joml:joml:1.10.7'
implementation 'org.bytedeco:javacv-platform:1.5.7'

View File

@@ -0,0 +1,379 @@
package com.chuangzhou.vivid2D.render;
import com.chuangzhou.vivid2D.render.model.Model2D;
import org.lwjgl.glfw.*;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.system.MemoryUtil;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.nio.ByteBuffer;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
/**
* 修复版高性能 OpenGL 渲染面板
*/
public class ModelGLPanel extends JPanel {
private final AtomicReference<Model2D> modelRef = new AtomicReference<>();
private long windowId;
private volatile boolean running = true;
private Thread renderThread;
private final int width;
private final int height;
private BufferedImage currentFrame;
private boolean contextInitialized = false;
private final CompletableFuture<Void> contextReady = new CompletableFuture<>();
private final String modelPath;
/**
* 构造函数:使用模型路径
*/
public ModelGLPanel(String modelPath, int width, int height) {
this.modelPath = modelPath;
this.width = width;
this.height = height;
initialize();
}
/**
* 构造函数:使用已加载模型
*/
public ModelGLPanel(Model2D model, int width, int height) {
this.modelPath = null;
this.width = width;
this.height = height;
this.modelRef.set(model);
initialize();
}
private void initialize() {
setLayout(new BorderLayout());
setPreferredSize(new Dimension(width, height));
// 初始化 GLFW
if (!GLFW.glfwInit()) {
throw new RuntimeException("无法初始化 GLFW");
}
// 创建渲染线程
startRendering();
}
/**
* 创建离屏 OpenGL 上下文
*/
private void createOffscreenContext() throws Exception {
// 设置窗口提示
GLFW.glfwDefaultWindowHints();
GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE);
GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 3);
GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 3);
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE);
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT, GL11.GL_TRUE);
GLFW.glfwWindowHint(GLFW.GLFW_SAMPLES, 4);
// 创建离屏窗口
windowId = GLFW.glfwCreateWindow(width, height, "Offscreen Render", MemoryUtil.NULL, MemoryUtil.NULL);
if (windowId == MemoryUtil.NULL) {
throw new Exception("无法创建离屏 OpenGL 上下文");
}
// 设置为当前上下文并初始化
GLFW.glfwMakeContextCurrent(windowId);
GL.createCapabilities();
// 初始化 OpenGL 状态
GL11.glEnable(GL11.GL_DEPTH_TEST);
// 检查是否支持多重采样
if (GL.getCapabilities().OpenGL13) {
GL11.glEnable(GL13.GL_MULTISAMPLE);
System.out.println("多重采样已启用");
} else {
System.out.println("不支持多重采样,跳过启用");
}
GL11.glViewport(0, 0, width, height);
ModelRender.initialize();
contextInitialized = true;
// 在正确的上下文中加载模型
loadModelInContext();
// 通知上下文已准备就绪
contextReady.complete(null);
}
/**
* 在 OpenGL 上下文中加载模型
*/
private void loadModelInContext() {
try {
if (modelPath != null) {
Model2D model = Model2D.loadFromFile(modelPath);
modelRef.set(model);
System.out.println("模型加载成功: " + modelPath);
}
} catch (Exception e) {
System.err.println("模型加载失败: " + e.getMessage());
e.printStackTrace();
// 创建错误模型或使用默认模型
createErrorModel();
}
}
/**
* 创建错误模型作为回退
*/
private void createErrorModel() {
try {
// 这里可以创建一个简单的默认模型
// 或者保持 modelRef 为 null在渲染时显示错误信息
System.out.println("使用默认错误模型");
} catch (Exception e) {
System.err.println("创建错误模型失败: " + e.getMessage());
}
}
/**
* 启动渲染线程
*/
private void startRendering() {
renderThread = new Thread(() -> {
try {
createOffscreenContext();
// 等待上下文就绪后再开始渲染循环
contextReady.get();
// 高性能渲染循环
while (running && !GLFW.glfwWindowShouldClose(windowId)) {
renderFrame();
// 控制帧率
try {
Thread.sleep(1000 / 60); // 60 FPS
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
cleanup();
}
});
renderThread.setDaemon(true);
renderThread.setName("GL-Render-Thread");
renderThread.start();
}
/**
* 渲染单帧并读取到 BufferedImage
*/
private void renderFrame() {
if (!contextInitialized || windowId == 0) return;
// 确保在当前上下文中
GLFW.glfwMakeContextCurrent(windowId);
Model2D currentModel = modelRef.get();
if (currentModel != null) {
try {
// 清除缓冲区
GL11.glClearColor(0.18f, 0.18f, 0.25f, 1f);
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
// 渲染模型
ModelRender.render(1.0f / 60f, currentModel);
// 读取像素数据到 BufferedImage
readPixelsToImage();
} catch (Exception e) {
System.err.println("渲染错误: " + e.getMessage());
renderErrorFrame(e.getMessage());
}
} else {
// 没有模型时显示默认背景
renderDefaultBackground();
}
// 在 Swing EDT 中更新显示
SwingUtilities.invokeLater(this::repaint);
}
/**
* 渲染错误帧
*/
private void renderErrorFrame(String errorMessage) {
GL11.glClearColor(0.3f, 0.1f, 0.1f, 1f);
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
readPixelsToImage();
// 创建错误图像
BufferedImage errorImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = errorImage.createGraphics();
g2d.setColor(Color.DARK_GRAY);
g2d.fillRect(0, 0, width, height);
g2d.setColor(Color.RED);
g2d.drawString("渲染错误: " + errorMessage, 10, 20);
g2d.dispose();
currentFrame = errorImage;
}
/**
* 渲染默认背景
*/
private void renderDefaultBackground() {
GL11.glClearColor(0.1f, 0.1f, 0.15f, 1f);
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
readPixelsToImage();
}
/**
* 读取 OpenGL 像素数据到 BufferedImage
*/
private void readPixelsToImage() {
try {
ByteBuffer buffer = ByteBuffer.allocateDirect(width * height * 4);
GL11.glReadPixels(0, 0, width, height, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer);
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// 转换像素数据
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int i = (x + (height - y - 1) * width) * 4; // 翻转 Y 轴
int r = buffer.get(i) & 0xFF;
int g = buffer.get(i + 1) & 0xFF;
int b = buffer.get(i + 2) & 0xFF;
int rgb = (r << 16) | (g << 8) | b;
image.setRGB(x, y, rgb);
}
}
currentFrame = image;
} catch (Exception e) {
System.err.println("读取像素数据错误: " + e.getMessage());
// 创建错误图像
BufferedImage errorImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = errorImage.createGraphics();
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, width, height);
g2d.setColor(Color.RED);
g2d.drawString("像素读取失败", 10, 20);
g2d.dispose();
currentFrame = errorImage;
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (currentFrame != null) {
// 绘制当前帧到面板
g.drawImage(currentFrame, 0, 0, getWidth(), getHeight(), null);
} else {
// 显示加载中信息
g.setColor(Color.DARK_GRAY);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.WHITE);
g.drawString("初始化中...", getWidth() / 2 - 30, getHeight() / 2);
}
// 如果模型为空,显示提示
if (modelRef.get() == null) {
g.setColor(Color.YELLOW);
g.drawString("模型未加载", 10, 20);
}
}
/**
* 设置模型(线程安全)
*/
public void setModel(Model2D model) {
// 等待上下文就绪后再设置模型
contextReady.thenRun(() -> {
modelRef.set(model);
System.out.println("模型已更新");
});
}
/**
* 获取当前渲染的模型
*/
public Model2D getModel() {
return modelRef.get();
}
/**
* 重新设置面板大小
*/
public void resize(int newWidth, int newHeight) {
setPreferredSize(new Dimension(newWidth, newHeight));
revalidate();
// 在渲染线程中更新视口
contextReady.thenRun(() -> {
if (contextInitialized && windowId != 0) {
GLFW.glfwMakeContextCurrent(windowId);
GL11.glViewport(0, 0, newWidth, newHeight);
// 重新创建帧缓冲图像
currentFrame = null;
}
});
}
/**
* 等待渲染上下文准备就绪
*/
public CompletableFuture<Void> waitForContext() {
return contextReady;
}
/**
* 检查是否正在运行
*/
public boolean isRunning() {
return running && contextInitialized;
}
/**
* 清理资源
*/
public void dispose() {
running = false;
if (renderThread != null) {
try {
renderThread.join(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
cleanup();
}
private void cleanup() {
if (windowId != 0) {
GLFW.glfwDestroyWindow(windowId);
windowId = 0;
}
GLFW.glfwTerminate();
System.out.println("OpenGL 资源已清理");
}
}

View File

@@ -792,7 +792,7 @@ public final class ModelRender {
private static void checkGLError(String op) {
int e = GL11.glGetError();
if (e != GL11.GL_NO_ERROR) {
logger.error("OpenGL error during {}: {}", op, getGLErrorString(e));
//logger.error("OpenGL error during {}: {}", op, getGLErrorString(e));
}
}

View File

@@ -1,5 +1,6 @@
package com.chuangzhou.vivid2D.render.model;
import javax.swing.tree.DefaultMutableTreeNode;
import com.chuangzhou.vivid2D.render.model.data.ModelData;
import com.chuangzhou.vivid2D.render.model.data.ModelMetadata;
import com.chuangzhou.vivid2D.render.model.util.*;
@@ -350,6 +351,14 @@ public class Model2D {
}
}
public DefaultMutableTreeNode toTreeNode() {
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(this.name != null ? this.name : "模型");
for (ModelPart part : parts) {
rootNode.add(part.toTreeNode());
}
return rootNode;
}
/**
* 捕获当前模型状态到姿态
*/

View File

@@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.swing.tree.DefaultMutableTreeNode;
/**
* 2D模型部件支持层级变换和变形器
@@ -133,6 +134,14 @@ public class ModelPart {
return new ArrayList<>(children);
}
public DefaultMutableTreeNode toTreeNode() {
DefaultMutableTreeNode node = new DefaultMutableTreeNode(this.name != null ? this.name : "部件");
for (ModelPart child : children) {
node.add(child.toTreeNode());
}
return node;
}
/**
* 根据名称查找子部件
*/

View File

@@ -1,6 +1,5 @@
package com.chuangzhou.vivid2D.render.model.util;
import com.chuangzhou.vivid2D.render.model.ModelPart;
import org.joml.Vector2f;
import java.nio.FloatBuffer;
@@ -503,7 +502,7 @@ public class Mesh2D {
org.lwjgl.system.MemoryUtil.memFree(fb);
}
} else {
logger.warn("警告: 着色器中未找到 uModelMatrix 或 uModel uniform");
//logger.warn("警告: 着色器中未找到 uModelMatrix 或 uModel uniform");
}
// 绘制

View File

@@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap;
* - 在碰撞处理里同时更新位置与速度(冲量响应),并避免在碰撞后覆盖冲量
* - 约束迭代次数可调,布料等需要多个迭代(默认 3
*
* @author tzdwindows 7 (modified)
* @author tzdwindows 7
*/
public class PhysicsSystem {
// ==================== 物理参数 ====================

View File

@@ -6,14 +6,12 @@ import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL45;
import org.lwjgl.stb.STBImage;
import org.lwjgl.stb.STBImageWrite;
import org.lwjgl.system.MemoryUtil;
import java.io.File;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.HashMap;
import java.util.Map;

View File

@@ -0,0 +1,29 @@
package com.chuangzhou.vivid2D.test;
import com.chuangzhou.vivid2D.render.ModelGLPanel;
import com.chuangzhou.vivid2D.render.model.Model2D;
import javax.swing.*;
public class TestModelGLPanel {
private static final String MODEL_PATH = "C:\\Users\\Administrator\\Desktop\\trump_texture.model";
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("ModelGLPanel Demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//com.chuangzhou.vivid2D.render.model.Model2D model = com.chuangzhou.vivid2D.render.model.Model2D.loadFromFile(MODEL_PATH);
ModelGLPanel glPanel = null;
try {
Model2D model2D = new Model2D("Hi");
glPanel = new ModelGLPanel(MODEL_PATH, 800, 600);
} catch (Exception e) {
throw new RuntimeException(e);
}
frame.add(glPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB