You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在com.jogamp.opengl.awt.GLCanvas中实现BufferedImage变化时更新纹理

如何在GLCanvas中动态更新BufferedImage纹理

嘿,我来帮你搞定这个动态更新纹理的问题!你已经能在GLCanvas上绘制单个纹理了,现在要处理BufferedImage变化时的更新,核心思路是复用纹理ID+按需更新纹理数据——每次图像变化就重建纹理会导致内存泄漏,咱们得更高效地处理~

第一步:准备必要的成员变量

首先要记录纹理ID、上一次的图像尺寸,避免重复创建纹理;如果是多线程更新图像,还要加个锁保证线程安全:

private GL2 gl2;
private BufferedImage image;
private int textureId = 0;
private int prevImageWidth = 0;
private int prevImageHeight = 0;
// 多线程更新图像时的同步锁
private final Object imageLock = new Object();

第二步:完善display方法

这里分两种场景处理:

  1. 第一次加载图像,或者图像尺寸变化了——需要重新创建纹理
  2. 只是图像内容变化,尺寸不变——用glTexSubImage2D高效更新纹理数据

完整的display方法代码如下:

public void display(GLAutoDrawable glAutoDrawable) {
    try {
        if (gl2 == null) {
            gl2 = glAutoDrawable.getGL().getGL2();
            // 初始化正交投影,让纹理能铺满窗口
            setupOrthoProjection(gl2, glAutoDrawable.getWidth(), glAutoDrawable.getHeight());
        }

        gl2.glClear(gl2.GL_COLOR_BUFFER_BIT | gl2.GL_DEPTH_BUFFER_BIT);
        gl2.glLoadIdentity();

        // 同步读取图像,避免多线程冲突
        BufferedImage currentImage;
        synchronized (imageLock) {
            currentImage = this.image;
        }

        if (currentImage != null) {
            int width = currentImage.getWidth();
            int height = currentImage.getHeight();

            // 场景1:首次加载或图像尺寸变化,重建纹理
            if (textureId == 0 || width != prevImageWidth || height != prevImageHeight) {
                // 先删除旧纹理,防止内存泄漏
                if (textureId != 0) {
                    gl2.glDeleteTextures(1, new int[]{textureId}, 0);
                }

                // 生成新纹理ID
                int[] texIds = new int[1];
                gl2.glGenTextures(1, texIds, 0);
                textureId = texIds[0];

                // 绑定纹理并设置参数(过滤、环绕规则)
                gl2.glBindTexture(gl2.GL_TEXTURE_2D, textureId);
                gl2.glTexParameteri(gl2.GL_TEXTURE_2D, gl2.GL_TEXTURE_MIN_FILTER, gl2.GL_LINEAR);
                gl2.glTexParameteri(gl2.GL_TEXTURE_2D, gl2.GL_TEXTURE_MAG_FILTER, gl2.GL_LINEAR);
                gl2.glTexParameteri(gl2.GL_TEXTURE_2D, gl2.GL_TEXTURE_WRAP_S, gl2.GL_CLAMP_TO_EDGE);
                gl2.glTexParameteri(gl2.GL_TEXTURE_2D, gl2.GL_TEXTURE_WRAP_T, gl2.GL_CLAMP_TO_EDGE);

                // 转换BufferedImage为OpenGL兼容的ByteBuffer格式
                ByteBuffer pixelBuffer = convertBufferedImageToByteBuffer(currentImage);

                // 上传纹理数据到GPU
                gl2.glTexImage2D(gl2.GL_TEXTURE_2D, 0, gl2.GL_RGBA, width, height, 0, 
                                gl2.GL_RGBA, gl2.GL_UNSIGNED_BYTE, pixelBuffer);

                // 更新记录的图像尺寸
                prevImageWidth = width;
                prevImageHeight = height;
            } else {
                // 场景2:仅图像内容变化,高效更新纹理数据
                gl2.glBindTexture(gl2.GL_TEXTURE_2D, textureId);
                ByteBuffer pixelBuffer = convertBufferedImageToByteBuffer(currentImage);
                gl2.glTexSubImage2D(gl2.GL_TEXTURE_2D, 0, 0, 0, width, height, 
                                   gl2.GL_RGBA, gl2.GL_UNSIGNED_BYTE, pixelBuffer);
            }

            // 绘制纹理到整个窗口
            drawFullScreenTexture(gl2, width, height);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

第三步:实现辅助方法

1. 设置正交投影

让纹理能铺满整个GLCanvas窗口:

private void setupOrthoProjection(GL2 gl2, int screenWidth, int screenHeight) {
    gl2.glMatrixMode(gl2.GL_PROJECTION);
    gl2.glLoadIdentity();
    // 正交投影坐标范围:(0,0)到(screenWidth, screenHeight)
    gl2.glOrtho(0, screenWidth, screenHeight, 0, -1, 1);
    gl2.glMatrixMode(gl2.GL_MODELVIEW);
    
    // 启用纹理和混合(支持透明通道)
    gl2.glEnable(gl2.GL_TEXTURE_2D);
    gl2.glEnable(gl2.GL_BLEND);
    gl2.glBlendFunc(gl2.GL_SRC_ALPHA, gl2.GL_ONE_MINUS_SRC_ALPHA);
}

2. 转换BufferedImage到ByteBuffer

把Java的BufferedImage转换成OpenGL能识别的像素格式:

private ByteBuffer convertBufferedImageToByteBuffer(BufferedImage image) {
    // 统一转换为RGBA格式,避免格式不兼容问题
    BufferedImage rgbaImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2d = rgbaImage.createGraphics();
    g2d.drawImage(image, 0, 0, null);
    g2d.dispose();

    int[] pixels = ((DataBufferInt) rgbaImage.getRaster().getDataBuffer()).getData();
    ByteBuffer buffer = ByteBuffer.allocateDirect(pixels.length * 4);
    buffer.order(ByteOrder.nativeOrder());

    // 逐个像素拆分RGBA通道
    for (int pixel : pixels) {
        buffer.put((byte) ((pixel >> 16) & 0xFF)); // 红色通道
        buffer.put((byte) ((pixel >> 8) & 0xFF));  // 绿色通道
        buffer.put((byte) (pixel & 0xFF));         // 蓝色通道
        buffer.put((byte) ((pixel >> 24) & 0xFF)); // 透明通道
    }

    buffer.flip();
    return buffer;
}

3. 绘制全屏纹理

用四边形把纹理铺满整个窗口:

private void drawFullScreenTexture(GL2 gl2, int imageWidth, int imageHeight) {
    gl2.glBindTexture(gl2.GL_TEXTURE_2D, textureId);
    gl2.glBegin(gl2.GL_QUADS);

    // 纹理坐标与窗口坐标对应
    gl2.glTexCoord2f(0, 0);
    gl2.glVertex2f(0, 0);

    gl2.glTexCoord2f(1, 0);
    gl2.glVertex2f(imageWidth, 0);

    gl2.glTexCoord2f(1, 1);
    gl2.glVertex2f(imageWidth, imageHeight);

    gl2.glTexCoord2f(0, 1);
    gl2.glVertex2f(0, imageHeight);

    gl2.glEnd();
}

第四步:外部更新图像的注意事项

如果你的image是在其他线程(比如UI线程、后台处理线程)更新的,一定要用同步块保证线程安全,并且触发GLCanvas重绘:

public void updateImage(BufferedImage newImage) {
    synchronized (imageLock) {
        this.image = newImage;
    }
    // 触发GLCanvas立即重绘
    glCanvas.display();
}

关键要点总结

  • 复用纹理ID:只有图像尺寸变化时才重建纹理,避免内存泄漏
  • 高效更新:用glTexSubImage2D代替glTexImage2D更新内容,性能提升明显
  • 线程安全:多线程更新图像时必须同步访问,防止读取到半更新的图像
  • 格式匹配:统一用RGBA格式处理图像,避免OpenGL与Java图像格式不兼容的问题

内容的提问来源于stack exchange,提问作者Erkan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:22:28