问题描述
我是 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 保存纹理信息,包括位置和比例因子.
- Batcher 一次性绘制所有精灵.这是粗略的实现.
- 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_TEXTURE0
和 GL_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?
请指出正确的方向.
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);
}
}
我的精灵
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 文档中,纹理 名称)和纹理单位:
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.
您在创建纹理时正确使用了纹理 id,方法是使用 glGenTextures()
生成 id,使用 glBindTexture()
将其绑定,然后设置纹理.
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());
采样器uniform的值不是纹理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:
- 注意uniform值是纹理单元的索引(
0
),而glActiveTexture()
的参数是对应的enum(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 ofglActiveTexture()
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 afterglActiveTexture()
.- 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 图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!