在 OpenGL-ES 2.0 中渲染多个 2D 图像

2024-03-17

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

首先,我正在开发一款纸牌游戏,需要渲染多个纸牌图像。我跟着这个http://www.learnopengles.com/android-lesson-four-introducing-basic-texturing/ http://www.learnopengles.com/android-lesson-four-introducing-basic-texturing/

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

  • MySprite 保存纹理信息,包括位置和比例因子。
  • Batcher 一次性绘制了所有精灵。这是粗略的实施。
  • ShaderHelper 管理着色器的创建并将它们链接到程序。
  • GLRenderer 是处理渲染的地方(它实现了“Renderer”。)

Q1

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

我怀疑这与我如何创建纹理有关MySprite班级。但我不知道为什么。你能帮我吗?

Q2

我读到如果我必须渲染 2 个图像,我需要使用GL_TEXTURE0 and GL_TEXTURE1,而不是仅仅使用GL_TEXTURE0.

_GLES20.glActiveTexture(GLES20.GL_TEXTURE0);

但由于这些常量是有限的(0 到 31),是否有更好的方法来渲染超过 32 个小图像而不失去图像的独特性?

请指出正确的方向。


The code

GL渲染器:

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;
    }
}

Batcher:

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);
        }
    }
}

着色器助手

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.");
        }
    }
...
}

您的代码混合了两个概念:纹理ids(或者,正如 OpenGL 官方文档中所称的那样,纹理names)和纹理units:

  • 纹理id是每个纹理对象的唯一id,其中纹理对象拥有实际数据以及采样参数。您可以拥有几乎无限数量的纹理对象,实际限制通常是计算机上的内存量。
  • 纹理单元是当前绑定的纹理表中的一个条目,可供着色器采样。该表的最大大小是一个与实现相关的限制,可以使用以下命令进行查询glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, ...)。兼容 ES 2.0 实现的保证最小值为 8。

您在创建纹理时正确使用纹理 id,通过使用以下命令生成 idglGenTextures(),将其与glBindTexture(),然后设置纹理。

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

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

采样器制服的值不是纹理ID,它是纹理单元的索引。然后,您需要将要使用的纹理绑定到您指定的纹理单元。

使用纹理单元 0,正确的代码如下所示:

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

关于此代码序列的一些注释:

  • 请注意,统一值是纹理单元的索引(0),而论证glActiveTexture()是对应的枚举(GL_TEXTURE0)。那是因为……它是这样定义的。恕我直言,API 设计很不幸,但你只需要意识到这一点即可。
  • glBindTexture()将纹理绑定到当前活动的纹理单元,因此它需要位于glActiveTexture().
  • The glActiveTexture()如果您只使用一种纹理,则实际上不需要调用。GL_TEXTURE0是默认值。我把它放在那里是为了说明纹理单元和纹理 id 之间的连接是如何建立的。

如果您想在同一个着色器中对多个纹理进行采样,则可以使用多个纹理单元。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 OpenGL-ES 2.0 中渲染多个 2D 图像 的相关文章

  • 如何制作像 Google+ 应用程序中那样的气泡? [关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 有谁知道如何使用 Google An
  • 升级到最新支持库后Android JACK编译器错误

    Android Studio 2 2 3 Windows 10 64位 构建工具版本 25 Android Gradle插件版本2 2 3 升级到最新的支持库 从 23 4 0 到 25 1 0 并更改编译版本 从 23 到 25 后 我收
  • 为什么不能在 Fragment 中使用 ViewPager?它实际上是

    有信息无法使用ViewPager在一个Fragment在许多来源中 例如 Android 开发者繁忙编码指南 http commonsware com 作者 Mark Murphy 或者类似的帖子this https stackoverfl
  • 我无法再在后台应用程序中接收任何 FCM 消息

    当应用程序处于后台时 我无法再在应用程序中接收任何数据消息 请注意 直到最近它在我的应用程序中都运行良好 也许在我的开发环境最近更新后它停止工作了 我不能说 所以我尝试用快速入门 android 项目 https github com fi
  • 如何在 Android 应用程序中隐藏 Flutterwave API 密钥

    我正在构建一个 Android 应用程序 目前正在将 Flutterwave 集成到我的应用程序中以进行支付 建议我永远不要将 Flutterwave API 密钥放在我的应用程序上 那么我该如何隐藏这些键呢 我正在使用 Retrofit
  • 无法在 Android Studio 中导出签名的 APK

    当我使用keytool list keystore path to keyfile jks并提供我的密码 我可以看到那里的条目 但是当我在尝试使用相同的密码生成签名的 APK 时使用相同的密码时 我收到错误 无法加载密钥库 密钥库被篡改 或
  • 如何使用 RecyclerView 创建此布局?

    我正在尝试使用这种类型的布局创建回收器视图 这些项目是字符串 可以以不同的大小出现 我不知道每行中有多少项目 我可以使用 StaggeredGridLayoutManager 来做到这一点吗 该图像只是一个假示例 每行可以有更多项目 您可能
  • 如何在Android中的DataBinding的ViewModel类中获取R.string

    我目前正在使用databinding对于我的 Android 应用程序项目 我想设置error留言在我的CustomTextView from R string txtOldPassWordError并从另一个名为的类中进行设置ViewMo
  • java.lang.IllegalAccessError:预验证类中的类引用在运行测试项目时解析为意外实现?

    在实施项目工作正常之后 我使用第三方库 zxing 实施了项目 然后在我编写了一个测试项目对我的项目进行单元测试之后 运行测试项目后 主项目 类及其方法没有给出任何信息错误 但如果在主项目的该方法中使用任何 zxing 框架类 则会在运行时
  • 使用 OkHttp 下载损坏的文件

    我编写的下载文件的方法总是会产生损坏的文件 public static String okDownloadToFileSync final String link final String fileName final boolean te
  • 什么是 Android 测试协调器?

    谷歌最近发布了Android测试支持库1 0 读完后overview https android developers googleblog com 2017 07 android testing support library 10 is
  • 在光标所在行强制关闭!

    嘿 我正在尝试创建一个应用程序来查找存储在 SQlite 数据库中的 GPS 数据 但我面临一个问题 我构建了一个 DbAdapter 类来创建数据库 现在我尝试使用以下函数从另一个类获取所有数据上的光标 public Cursor fet
  • Toast 消息消失后​​完成活动吗?

    有谁知道 是否有可能对 Toast 消息执行某些操作 在我的情况下完成活动 将被关闭 您只需创建一个Thread持续时间只要Toast显示 然后您就可以完成您的Activity public void onCreate Bundle sav
  • 在 Android 中加密/解密字符串的简单方法

    我的问题是如何加密String String AndroidId Override public void onCreate Bundle savedInstanceState super onCreate savedInstanceSta
  • 如何在 60 分钟后删除共享首选项

    我想存储登录数据 但希望在 60 分钟后删除该数据 执行此操作的正确方法是什么 在这 60 分钟内可以关闭 停止 打开应用程序 我不想使用内部数据库 这是我的访问代码SharedPreferences sharedpreferences g
  • Android:WebView/BaseInputConnection 中的退格键

    我在 Android 4 2 中遇到软键盘退格问题 我在 WebView CodeMirror 中有一个自定义编辑器 它使用一个空的
  • Android 操作项上的通知徽章

    我想在操作栏中放置的购物车图像上添加一个通知徽章 并以编程方式操作它 有帮助吗 您可以显示自定义MenuItem on ActionBar通过创建一个custom layout for MenuItem 要设置自定义布局 您必须使用菜单项属
  • Expresso 的 Android 测试首选项片段

    我在通过 Expresso 测试我的代码时遇到问题 我写了这段代码 public class SettingsActivity extends Activity Override protected void onCreate Bundle
  • 使 Recyclerview 固定高度并可滚动

    已解决以下检查答案 所以我试图为我的 Android 应用程序创建评论功能 我想在 recyclerview 中显示评论 然后在 recyclerview 下方有一个按钮和文本视图来添加评论 我想让 recyclerview 具有一定的高度
  • Android AppWidgetManager 方法 updateAppWidget 无法设置意图、加载数据。而且它是随机发生的

    我的小部件由 2 个按钮和一个显示数据的列表视图组成 大多数时候 当调用小部件提供程序的 onUpdate 方法时 一切都会正常加载 每个人都很高兴 但是我注意到有时在调用更新方法后 小部件完全无法加载其数据 列表视图为空 所有按钮均无响应

随机推荐