在OpenGL-ES 2.0中渲染多个2D图像 [英] Rendering multiple 2D images in OpenGL-ES 2.0

查看:93
本文介绍了在OpenGL-ES 2.0中渲染多个2D图像的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是OpenGL的新手,正在尝试学习ES 2.0.

I am new to OpenGL, and trying to learn ES 2.0.

首先,我正在玩纸牌游戏,我需要渲染多张纸牌图像.我遵循了这个 http://www.learnopengles.com/android-lesson -四介绍基本纹理/

To start with, I am working on a card game, where I need to render multiple card images. I followed this http://www.learnopengles.com/android-lesson-four-introducing-basic-texturing/

我创建了一些类来处理数据和操作.

I have created a few classes to handle the data and actions.

  • MySprite保存纹理信息,包括位置和比例因子.
  • 批处理程序一次性绘制所有精灵.这是粗略的实现.
  • ShaderHelper管理着色器的创建并将其链接到程序.
  • GLRenderer是处理渲染的地方(它实现了"Renderer".)
  • MySprite holds the texture information, including the location and scale factors.
  • Batcher draws all the sprites in one go. It is rough implementation.
  • ShaderHelper manages creation of shaders and linking them to a program.
  • GLRenderer is where the rendering is handled (it implements `Renderer`.)

我的程序正确渲染一张图像.问题是,当我渲染2张图像时,第一个被替换为第二个,因此第二个被渲染了两次.

My program renders one image correctly. Problem is that when I render 2 images, first one is replaced by the later one in its place, hence second one is rendered twice.

我怀疑这与我在MySprite类中创建纹理的方式有关.但是我不确定为什么.你能帮忙吗?

I suspect it is something related to how I create textures in MySprite class. But I am not sure why. Can you help?

我读到如果必须渲染2张图像,则需要使用GL_TEXTURE0GL_TEXTURE1,而不是仅使用GL_TEXTURE0.

I read that if I have to render 2 images, I need to use GL_TEXTURE0 and GL_TEXTURE1, instead of just using GL_TEXTURE0.

_GLES20.glActiveTexture(GLES20.GL_TEXTURE0);

但是由于这些常量是有限的(0到31),是否有更好的方法来渲染32个以上的小图像而不丢失图像的唯一性?

But since these constants are limited (0 to 31), is there a better way to render more than 32 small images without losing the images' uniqueness?

请为我指明正确的方向.

Please point me to the right direction.

GLRenderer:

GLRenderer:

public class GLRenderer implements Renderer {
    ArrayList<MySprite> images = new ArrayList<MySprite>();
    Batcher batch;
    int x = 0;
...
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        batch = new Batcher();
        MySprite s = MySprite.createGLSprite(mContext.getAssets(), "menu/back.png");
        images.add(s);
        s.XScale = 2;
        s.YScale = 3;

        images.add(MySprite.createGLSprite(mContext.getAssets(), "menu/play.png"));

        // Set the clear color to black
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1);

        ShaderHelper.initGlProgram();
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        mScreenWidth = width;
        mScreenHeight = height;

        // Redo the Viewport, making it fullscreen.
        GLES20.glViewport(0, 0, mScreenWidth, mScreenHeight);

        batch.setScreenDimension(width, height);

        // Set our shader programm
        GLES20.glUseProgram(ShaderHelper.programTexture);
    }

    @Override
    public void onDrawFrame(GL10 unused) {
        // clear Screen and Depth Buffer, we have set the clear color as black.
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

        batch.begin();
        int y = 0;
        for (MySprite s : images) {
            s.X = x;
            s.Y = y;
            batch.draw(s);

            y += 200;
        }
        batch.end();

        x += 1;
    }
}

批处理程序

public class Batcher {
    // Store the model matrix. This matrix is used to move models from object space (where each model can be thought
    // of being located at the center of the universe) to world space.
    private final float[] mtrxModel = new float[16];
    // Store the projection matrix. This is used to project the scene onto a 2D viewport.
    private static final float[] mtrxProjection = new float[16];
    // Allocate storage for the final combined matrix. This will be passed into the shader program.
    private final float[] mtrxMVP = new float[16];

    // Create our UV coordinates.
    static float[] uvArray = new float[]{
            0.0f, 0.0f,
            0.0f, 1.0f,
            1.0f, 1.0f,
            1.0f, 0.0f
    };
    static FloatBuffer uvBuffer;
    static FloatBuffer vertexBuffer;
    static boolean staticInitialized = false;
    static short[] indices = new short[]{0, 1, 2, 0, 2, 3}; // The order of vertexrendering.
    static ShortBuffer indicesBuffer;

    ArrayList<MySprite> sprites = new ArrayList<MySprite>();

    public Batcher() {
        if (!staticInitialized) {
            // The texture buffer
            uvBuffer = ByteBuffer.allocateDirect(uvArray.length * 4)
                    .order(ByteOrder.nativeOrder())
                    .asFloatBuffer();
            uvBuffer.put(uvArray)
                    .position(0);

            // initialize byte buffer for the draw list
            indicesBuffer = ByteBuffer.allocateDirect(indices.length * 2)
                    .order(ByteOrder.nativeOrder())
                    .asShortBuffer();
            indicesBuffer.put(indices)
                    .position(0);

            float[] vertices = new float[] {
                    0, 0, 0,
                    0, 1, 0,
                    1, 1, 0,
                    1, 0, 0
            };

            // The vertex buffer.
            vertexBuffer = ByteBuffer.allocateDirect(vertices.length * 4)
                    .order(ByteOrder.nativeOrder())
                    .asFloatBuffer();
            vertexBuffer.put(vertices)
                    .position(0);

            staticInitialized = true;
        }
    }

    public void setScreenDimension(int screenWidth, int screenHeight) {
        Matrix.setIdentityM(mtrxProjection, 0);
        // (0,0)--->
        //   |
        //   v
        //I want it to be more natural like desktop screen
        Matrix.orthoM(mtrxProjection, 0,
                -1f, screenWidth,
                screenHeight, -1f,
                -1f, 1f);
    }

    public void begin() {
        sprites.clear();
    }

    public void draw(MySprite sprite) {
        sprites.add(sprite);
    }

    public void end() {
        // Get handle to shape's transformation matrix
        int u_MVPMatrix = GLES20.glGetUniformLocation(ShaderHelper.programTexture, "u_MVPMatrix");
        int a_Position = GLES20.glGetAttribLocation(ShaderHelper.programTexture, "a_Position");
        int a_texCoord = GLES20.glGetAttribLocation(ShaderHelper.programTexture, "a_texCoord");
        int u_texture = GLES20.glGetUniformLocation(ShaderHelper.programTexture, "u_texture");

        GLES20.glEnableVertexAttribArray(a_Position);
        GLES20.glEnableVertexAttribArray(a_texCoord);

        //loop all sprites
        for (int i = 0; i < sprites.size(); i++) {
            MySprite ms = sprites.get(i);

            // Matrix op - start
                Matrix.setIdentityM(mtrxMVP, 0);
                Matrix.setIdentityM(mtrxModel, 0);

                Matrix.translateM(mtrxModel, 0, ms.X, ms.Y, 0f);
                Matrix.scaleM(mtrxModel, 0, ms.getWidth() * ms.XScale, ms.getHeight() * ms.YScale, 0f);

                Matrix.multiplyMM(mtrxMVP, 0, mtrxModel, 0, mtrxMVP, 0);
                Matrix.multiplyMM(mtrxMVP, 0, mtrxProjection, 0, mtrxMVP, 0);
            // Matrix op - end

            // Pass the data to shaders - start
                // Prepare the triangle coordinate data
                GLES20.glVertexAttribPointer(a_Position, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer);

                // Prepare the texturecoordinates
                GLES20.glVertexAttribPointer(a_texCoord, 2, GLES20.GL_FLOAT, false, 0, uvBuffer);

                GLES20.glUniformMatrix4fv(u_MVPMatrix, 1, false, mtrxMVP, 0);

                // Set the sampler texture unit to where we have saved the texture.
                GLES20.glUniform1i(u_texture, ms.getTextureId());
            // Pass the data to shaders - end

            // Draw the triangles
            GLES20.glDrawElements(GLES20.GL_TRIANGLES, indices.length, GLES20.GL_UNSIGNED_SHORT, indicesBuffer);
        }
    }
}

ShaderHelper

ShaderHelper

public class ShaderHelper {
    static final String vs_Image =
        "uniform mat4 u_MVPMatrix;" +
        "attribute vec4 a_Position;" +
        "attribute vec2 a_texCoord;" +
        "varying vec2 v_texCoord;" +
        "void main() {" +
        "  gl_Position = u_MVPMatrix * a_Position;" +
        "  v_texCoord = a_texCoord;" +
        "}";

    static final String fs_Image =
        "precision mediump float;" +
        "uniform sampler2D u_texture;" +
        "varying vec2 v_texCoord;" +
        "void main() {" +
        "  gl_FragColor = texture2D(u_texture, v_texCoord);" +
        "}";

    // Program variables
    public static int programTexture;
    public static int vertexShaderImage, fragmentShaderImage;

    public static int loadShader(int type, String shaderCode){

        // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
        // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
        int shader = GLES20.glCreateShader(type);

        // add the source code to the shader and compile it
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);

        // return the shader
        return shader;
    }

    public static void initGlProgram() {
        // Create the shaders, images
        vertexShaderImage = ShaderHelper.loadShader(GLES20.GL_VERTEX_SHADER, ShaderHelper.vs_Image);
        fragmentShaderImage = ShaderHelper.loadShader(GLES20.GL_FRAGMENT_SHADER, ShaderHelper.fs_Image);

        ShaderHelper.programTexture = GLES20.glCreateProgram();             // create empty OpenGL ES Program
        GLES20.glAttachShader(ShaderHelper.programTexture, vertexShaderImage);   // add the vertex shader to program
        GLES20.glAttachShader(ShaderHelper.programTexture, fragmentShaderImage); // add the fragment shader to program
        GLES20.glLinkProgram(ShaderHelper.programTexture);                  // creates OpenGL ES program executables
    }

    public static void dispose() {
        GLES20.glDetachShader(ShaderHelper.programTexture, ShaderHelper.vertexShaderImage);
        GLES20.glDetachShader(ShaderHelper.programTexture, ShaderHelper.fragmentShaderImage);

        GLES20.glDeleteShader(ShaderHelper.fragmentShaderImage);
        GLES20.glDeleteShader(ShaderHelper.vertexShaderImage);

        GLES20.glDeleteProgram(ShaderHelper.programTexture);
    }
}

MySprite

public class MySprite {
    public int X, Y;
    public float XScale, YScale;
    private int w, h;

    int textureId = -1;

    private MySprite(Bitmap bmp, int textureId) {
        this.w = bmp.getWidth();
        this.h = bmp.getHeight();
        this.textureId = textureId;
        this.XScale = this.YScale = 1f;
    }

    public static MySprite createGLSprite(final AssetManager assets, final String assetImagePath) {
        Bitmap bmp = TextureHelper.getBitmapFromAsset(assets, assetImagePath);
        if (bmp == null) return null;

        MySprite ms = new MySprite(bmp, createGlTexture());
        Log.d("G1", "image id = " + ms.getTextureId());

        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

        // Load the bitmap into the bound texture.
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bmp, 0);

        bmp.recycle();
        return ms;
    }

    private static int createGlTexture() {
        // Generate Textures, if more needed, alter these numbers.
        final int[] textureHandles = new int[1];
        GLES20.glGenTextures(1, textureHandles, 0);

        if (textureHandles[0] != 0) {
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandles[0]);
            return textureHandles[0];
        } else {
            throw new RuntimeException("Error loading texture.");
        }
    }
...
}

推荐答案

您的代码混合了两个概念:纹理 id (或者,在官方OpenGL文档中称为纹理名称)和纹理单位:

Your code mixes up two concepts: texture ids (or, as they are called in the official OpenGL documentation, texture names), and texture units:

  • 纹理ID是每个纹理对象的唯一ID,其中纹理对象拥有实际数据以及采样参数.您可以拥有几乎无限数量的纹理对象,实际的限制通常是计算机上的内存量.
  • 纹理单元是当前绑定的纹理表中的一个条目,可用于着色器进行采样.该表的最大大小是与实现有关的限制,可以使用glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, ...)进行查询.符合标准的ES 2.0实现的最低保证是8.
  • A texture id is a unique id for each texture object, where a texture object owns the actual data, as well as sampling parameters. You can have a virtually unlimited number of texture objects, with the practical limit typically being the amount of memory on your machine.
  • A texture unit is an entry in a table of textures that are currently bound, and available to be sampled by a shader. The maximum size of this table is an implementation dependent limit, which can be queried with glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, ...). The guaranteed minimum for compliant ES 2.0 implementations is 8.

通过使用glGenTextures()生成ID,将其与glBindTexture()绑定,然后设置纹理,您可以在创建纹理时正确使用纹理ID.

You're using texture ids correctly while creating your textures, by generating an id with glGenTextures(), binding it with glBindTexture(), and then setting up the texture.

问题在于您设置了用于绘制的纹理:

The problem is where you set up the textures for drawing:

GLES20.glUniform1i(u_texture, ms.getTextureId());

采样器统一的值不是纹理id,它是纹理单位的索引.然后,您需要将要使用的纹理绑定到您指定的纹理单元.

The value of the sampler uniform is not a texture id, it is the index of a texture unit. You then need to bind the texture you want to use to the texture unit you specify.

使用纹理单位0,正确的代码如下:

Using texture unit 0, the correct code looks like this:

GLES20.glUniform1i(u_texture, 0);
GLES20.glActiveTexture(GL_TEXTURE0);
GLES20.glBindTexture(ms.getTextureId());

此代码序列的几点说明:

A few remarks on this code sequence:

  • 请注意,统一值是纹理单位(0)的索引,而glActiveTexture()的参数是相应的枚举(GL_TEXTURE0).那是因为...就是这样定义的.不幸的API设计,恕我直言,但您只需要意识到这一点即可.
  • glBindTexture()将纹理绑定到当前活动的纹理单元,因此需要在glActiveTexture()之后.
  • 如果仅使用一种纹理,则实际上并不需要glActiveTexture()调用. GL_TEXTURE0是默认值.我将其放在此处以说明如何在纹理单元和纹理id之间建立联系.
  • Note that the uniform value is the index of the texture unit (0), while the argument of glActiveTexture() is the corresponding enum (GL_TEXTURE0). That's because... it was defined that way. Unfortunate API design, IMHO, but you just need to be aware of it.
  • glBindTexture() binds the texture to the currently active texture unit, so it needs to come after glActiveTexture().
  • The glActiveTexture() call is not really needed if you only ever use one texture. GL_TEXTURE0 is the default value. I put it there to illustrate how the connection between texture unit and texture id is established.

如果要在同一着色器中采样多个纹理,则使用多个纹理单位.

Multiple texture units are used if you want to sample multiple textures in the same shader.

这篇关于在OpenGL-ES 2.0中渲染多个2D图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆