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

使用SSBO批量渲染2D Tile时仅渲染单个纹素的问题

2D Tile批量渲染纹理坐标错误排查与修复

我正在开发2D Tile渲染器,因切换VAO性能开销过高,采用批量渲染方案:使用单张纹理图集,通过修改纹理坐标为每个Tile分配对应纹理,借助SSBO存储数据,在着色器中通过gl_InstanceId索引对应数据。这是我第二次提问,已应用上次建议修正了Tile位置偏移问题,但当前所有Tile无法正确应用纹理,仅显示单个纹素的纯色效果。通过统一纹理坐标测试确认纹理本身可用,问题定位在纹理坐标的批量处理逻辑,现寻求正确的纹理坐标批量处理方法。

最小复现代码

package test;

import gfx.Render;
import gfx.ShaderProgram;
import gfx.Texture;
import org.joml.Matrix4f;
import org.lwjgl.glfw.GLFWKeyCallback;
import org.lwjgl.opengl.GL;
import org.lwjgl.system.MemoryUtil;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray;
import static org.lwjgl.opengl.GL20.glVertexAttribPointer;
import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.opengl.GL31.glDrawElementsInstanced;
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
import static org.lwjgl.system.MemoryUtil.memFree;

public class MinimalReproducibleExample {
    private int mapWidth = 50, mapHeight = 50;
    private float tileSize = 0.1f;
    private int vaoId;
    private int iboId;
    public float camX = 0.0f, camY = 0.0f;
    public float scale = 1.0f;

    private int usboId;
    Texture tt;

    ShaderProgram shaderProgram;

    private float[] vertices;
    private final int[] indices = new int[]{
            0, 1, 3, 3, 1, 2,
    };

    private FloatBuffer offsetBuffer;
    private FloatBuffer tpBuffer;
    long windowId;
    private Matrix4f projectionMatrix;

    static float safeZoneX, safeZoneY;

    private void setupOrthographicProjection() {
        float orthoHeight = 10.0f; // Adjust this based on your scene's needs
        float orthoWidth = orthoHeight * 1080/920;
        safeZoneX = orthoWidth;
        safeZoneY = orthoHeight;
        projectionMatrix = new Matrix4f().setOrtho2D(-orthoWidth / 2, orthoWidth / 2, -orthoHeight / 2, orthoHeight / 2);
    }

    public MinimalReproducibleExample() {
        if(!glfwInit())
            System.exit(1);
        windowId = glfwCreateWindow(1080, 920, "TEST WINDOW", 0, 0);
        if(windowId == 0)
            System.exit(1);

        glfwMakeContextCurrent(windowId);

        glfwShowWindow(windowId);


        GL.createCapabilities();

        setupOrthographicProjection();
        ShaderProgram.loadShaderPrograms();

        tt = Texture.loadTexture("texture_atlases/atlas-1.png");

        float[] offsets = new float[mapWidth * mapHeight * 2];
        float[] texPos = new float[mapWidth * mapHeight * 8];

        vertices = new float[]{
                -tileSize, tileSize, 0.0f,
                -tileSize, -tileSize, 0.0f,
                tileSize, -tileSize, 0.0f,
                tileSize, tileSize, 0.0f,
        };

        float[] test = new float[]{
                0.0f, 1.0f,
                0.0f, 0.0f,
                1.0f, 0.0f,
                1.0f, 1.0f
        };

        int index = 0;
        for (int x = 0; x < mapWidth; x++) {
            for (int y = 0; y < mapHeight; y++) {
                float xPos = (float) x * tileSize;
                float yPos = (float) y * tileSize;
                offsets[index++] = xPos;
                offsets[index++] = yPos;
            }
        }
        index = 0;
        for(int i = 0; i < mapWidth * mapHeight * 8; i++) {
            texPos[i] = test[index];

            index++;
            if(index >= 8) {
                index = 0;
            }
        }

        offsetBuffer = MemoryUtil.memAllocFloat(offsets.length);
        offsetBuffer.put(offsets).flip();

        tpBuffer = MemoryUtil.memAllocFloat(texPos.length);
        tpBuffer.put(texPos).flip();

        shaderProgram = ShaderProgram.getShaderProgram("test_program");
        ShaderProgram.useProgram("test_program");

        vaoId = glGenVertexArrays();
        glBindVertexArray(vaoId);

        // Store vertex data off-heap
        FloatBuffer vertexData = MemoryUtil.memAllocFloat(vertices.length);
        vertexData.put(vertices).flip();

        // Create and bind VBO for vertices
        int vboId = glGenBuffers();
        glBindBuffer(GL_ARRAY_BUFFER, vboId);
        glBufferData(GL_ARRAY_BUFFER, vertexData, GL_STATIC_DRAW);
        glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
        glEnableVertexAttribArray(0);
        memFree(vertexData);

        // Store index data off-heap
        IntBuffer indexData = MemoryUtil.memAllocInt(indices.length);
        indexData.put(indices).flip();

        // Create and bind IBO for indices
        iboId = glGenBuffers();
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboId);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexData, GL_STATIC_DRAW);
        memFree(indexData);

        // Create and bind the SSBO for offsets
        int ssboId = glGenBuffers();
        glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboId);
        glBufferData(GL_SHADER_STORAGE_BUFFER, offsetBuffer, GL_STATIC_DRAW);
        glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssboId);

        // Create and bind the SSBO for texture coordinates
        usboId = glGenBuffers();
        glBindBuffer(GL_SHADER_STORAGE_BUFFER, usboId);
        glBufferData(GL_SHADER_STORAGE_BUFFER, tpBuffer, GL_STATIC_DRAW);
        glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, usboId);

        // Unbind VAO
        glBindVertexArray(0);

        glfwSetKeyCallback(windowId, new GLFWKeyCallback() {
            @Override
            public void invoke(long l, int i, int i1, int i2, int i3) {
                if(i == GLFW_KEY_F1) {
                    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
                    glDisable(GL_TEXTURE_2D);
                }
                if(i == GLFW_KEY_F2) {
                    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
                    glEnable(GL_TEXTURE_2D);
                }
            }
        });



        while(!glfwWindowShouldClose(windowId)) {
            glfwPollEvents();

            //handle camera movement;
            if(glfwGetKey(windowId, GLFW_KEY_D) == GLFW_TRUE) {
                camX -= 0.01f;
            }

            if(glfwGetKey(windowId, GLFW_KEY_A) == GLFW_TRUE) {
                camX += 0.01f;
            }
            if(glfwGetKey(windowId, GLFW_KEY_W) == GLFW_TRUE) {
                camY -= 0.01f;
            }

            if(glfwGetKey(windowId, GLFW_KEY_S) == GLFW_TRUE) {
                camY += 0.01f;
            }
            if(glfwGetKey(windowId, GLFW_KEY_E) == GLFW_TRUE) {
                scale += 0.001f;
            }
            if(glfwGetKey(windowId, GLFW_KEY_Q) == GLFW_TRUE) {
                scale -= 0.001f;
            }
            if(scale >=10.0f) {
                scale = 10.0f;
            }
            if(scale<=0.01f) {
                scale = 0.01f;
            }

            render();
        }
        glfwDestroyWindow(windowId);
        glfwTerminate();

    }

    float tick = 0;
    public void render() {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


        tick += 0.01f;
        ShaderProgram.useProgram("test_program");
        glBindVertexArray(vaoId);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboId);
        shaderProgram.setUniform("delta", tick);
        shaderProgram.setUniform("projectionMatrix", projectionMatrix);
        shaderProgram.setUniform("texture_sampler", 0); // Ensure correct texture unit
        shaderProgram.setUniform("scale", scale);
        shaderProgram.setUniform("camPos", camX, camY);

        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, tt.getId());

        glDrawElementsInstanced(GL_TRIANGLES, indices.length, GL_UNSIGNED_INT, 0, mapWidth * mapHeight);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        glBindVertexArray(0);
        ShaderProgram.unbindProgram();


        glfwSwapBuffers(windowId);
    }



    public static void main(String[] args) {
        new MinimalReproducibleExample();
    }
}

着色器代码

片段着色器

#version 460

out vec4 fragColor;

uniform sampler2D texture_sampler;

in vec2 outTP;

void main() {
    fragColor = texture(texture_sampler, outTP);
}

顶点着色器

#version 460

layout(location = 0) in vec3 position;

layout(std430, binding = 0) buffer OffsetPositions {
    vec2 offsets[];
};

layout(std430, binding = 1) buffer TexturePositions {
    vec2 textCoords[];
};

uniform float delta;
uniform mat4 projectionMatrix;
uniform vec2 camPos;
uniform float scale;

out vec2 outTP;

void main() {
    int index = gl_InstanceID;
    vec2 offset = offsets[index];
    outTP = textCoords[index];

    gl_Position = projectionMatrix * vec4(
        position.xy * scale + offset * scale + camPos * scale,
        position.z,
        1.0
    );
}

问题代码片段

index = 0;
for(int i = 0; i < mapWidth * mapHeight * 8; i++) {
    texPos[i] = test[index];

    index++;
    if(index >= 8) {
        index = 0;
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 19:30:53