在 OpenGL-ES 2.0 中渲染多个 2D 图像
Rendering multiple 2D images in OpenGL-ES 2.0
我是 OpenGL 的新手,正在尝试学习 ES 2.0。
首先,我正在开发纸牌游戏,我需要在其中渲染多个纸牌图像。我关注了这个http://www.learnopengles.com/android-lesson-four-introducing-basic-texturing/
我创建了一些 classes 来处理数据和操作。
- MySprite 保存纹理信息,包括位置和比例因子。
- Batcher 一次性绘制出所有精灵。这是粗略的实现。
- ShaderHelper 管理着色器的创建并将它们链接到程序。
- GLRenderer 是处理渲染的地方(它实现了 `Renderer`。)
Q1
我的程序正确地渲染了一张图像。问题是,当我渲染 2 个图像时,第一个图像被后面的图像替换,因此第二个图像被渲染两次。
我怀疑这与我在 MySprite
class 中创建纹理的方式有关。但我不确定为什么。你能帮忙吗?
Q2
我读到如果我必须渲染 2 个图像,我需要使用 GL_TEXTURE0
和 GL_TEXTURE1
,而不是只使用 GL_TEXTURE0
.
_GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
但是由于这些常量是有限的(0 到 31),有没有更好的方法来渲染超过 32 个小图像而不丢失图像的唯一性?
请指出正确的方向。
密码
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;
}
}
批处理程序:
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);
}
}
我的精灵
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.");
}
}
...
}
首先,我将指出一些关于 OpenGL 的一般情况:
每个纹理都是一个大的正方形图像。将该图像加载到 gpu 的内存中需要时间,因为您不能主动将图像交换到 gpu 的纹理内存中并希望快速 运行 时间。
Q1: 只显示第二张图片的原因是你的 sprite 中有这条线 class:
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bmp, 0);
您调用了两次,因此 texture0 被第二张图像替换,并且只调用了该图像。
为了解决这个问题,开发人员加载了一个图像,其中包含许多较小的图像,也就是纹理贴图。可以加载的图像大小很大程度上取决于gpu。 Android 设备范围大致从 1024^2 像素到 4096^2 像素。
要为精灵使用较小的纹理部分,您必须手动定义批处理程序中的 uvArray class。
假设我们的纹理有 4 个图像,划分如下:
(0.0, 0.0) top left _____ (1.0, 0.0) top right
|__|__| middle of the square is (0.5, 0.5) middle
(0.0, 1.0) bot left |__|__|(1.0, 1.0) bot right
这意味着左上角图像的 uv 值为:
static float[] uvArray = new float[]{
0.0f, 0.0f, //top left
0.0f, 0.5f, //bot left
0.5f, 0.5f, //bot right
0.5f, 0.0f //top right
};
这样你就可以在纹理上拥有四倍的精灵数量。
因此,您不仅需要传递精灵所在的纹理,还需要传递批处理程序应使用的自定义 uv。
您的代码混淆了两个概念:texture ids(或者,在官方 OpenGL 文档中称为 texture names) , 和纹理 units:
- 纹理id是每个纹理对象的唯一id,纹理对象拥有实际数据,以及采样参数。您实际上可以拥有无限数量的纹理对象,实际限制通常是您机器上的内存量。
- 纹理单元是 table 当前绑定的纹理中的一个条目,可供着色器采样。此 table 的最大大小是一个依赖于实现的限制,可以使用
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, ...)
查询。符合 ES 2.0 实现的保证最小值为 8。
您在创建纹理时正确使用了纹理 ID,方法是使用 glGenTextures()
生成 ID,将其与 glBindTexture()
绑定,然后设置纹理。
问题在于您在哪里设置绘图纹理:
GLES20.glUniform1i(u_texture, ms.getTextureId());
采样器uniform的值不是一个纹理id,它是一个纹理单元的索引。然后,您需要将要使用的纹理绑定到您指定的纹理单元。
使用纹理单元 0,正确的代码如下所示:
GLES20.glUniform1i(u_texture, 0);
GLES20.glActiveTexture(GL_TEXTURE0);
GLES20.glBindTexture(ms.getTextureId());
关于这个代码序列的几点说明:
- 注意统一值是纹理单元的索引(
0
),而glActiveTexture()
的参数是对应的枚举(GL_TEXTURE0
)。那是因为……它是那样定义的。不幸的 API 设计,恕我直言,但你只需要注意它。
glBindTexture()
将纹理绑定到当前活动的纹理单元,因此需要在 glActiveTexture()
. 之后
- 如果您只使用一个纹理,则不需要调用
glActiveTexture()
。 GL_TEXTURE0
是默认值。我把它放在那里是为了说明纹理单元和纹理id之间的联系是如何建立的。
如果要在同一着色器中对多个纹理进行采样,则使用多个纹理单元。
我是 OpenGL 的新手,正在尝试学习 ES 2.0。
首先,我正在开发纸牌游戏,我需要在其中渲染多个纸牌图像。我关注了这个http://www.learnopengles.com/android-lesson-four-introducing-basic-texturing/
我创建了一些 classes 来处理数据和操作。
- MySprite 保存纹理信息,包括位置和比例因子。
- Batcher 一次性绘制出所有精灵。这是粗略的实现。
- ShaderHelper 管理着色器的创建并将它们链接到程序。
- GLRenderer 是处理渲染的地方(它实现了 `Renderer`。)
Q1
我的程序正确地渲染了一张图像。问题是,当我渲染 2 个图像时,第一个图像被后面的图像替换,因此第二个图像被渲染两次。
我怀疑这与我在 MySprite
class 中创建纹理的方式有关。但我不确定为什么。你能帮忙吗?
Q2
我读到如果我必须渲染 2 个图像,我需要使用 GL_TEXTURE0
和 GL_TEXTURE1
,而不是只使用 GL_TEXTURE0
.
_GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
但是由于这些常量是有限的(0 到 31),有没有更好的方法来渲染超过 32 个小图像而不丢失图像的唯一性?
请指出正确的方向。
密码
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;
}
}
批处理程序:
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);
}
}
我的精灵
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.");
}
}
...
}
首先,我将指出一些关于 OpenGL 的一般情况:
每个纹理都是一个大的正方形图像。将该图像加载到 gpu 的内存中需要时间,因为您不能主动将图像交换到 gpu 的纹理内存中并希望快速 运行 时间。
Q1: 只显示第二张图片的原因是你的 sprite 中有这条线 class:
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bmp, 0);
您调用了两次,因此 texture0 被第二张图像替换,并且只调用了该图像。
为了解决这个问题,开发人员加载了一个图像,其中包含许多较小的图像,也就是纹理贴图。可以加载的图像大小很大程度上取决于gpu。 Android 设备范围大致从 1024^2 像素到 4096^2 像素。
要为精灵使用较小的纹理部分,您必须手动定义批处理程序中的 uvArray class。
假设我们的纹理有 4 个图像,划分如下:
(0.0, 0.0) top left _____ (1.0, 0.0) top right
|__|__| middle of the square is (0.5, 0.5) middle
(0.0, 1.0) bot left |__|__|(1.0, 1.0) bot right
这意味着左上角图像的 uv 值为:
static float[] uvArray = new float[]{
0.0f, 0.0f, //top left
0.0f, 0.5f, //bot left
0.5f, 0.5f, //bot right
0.5f, 0.0f //top right
};
这样你就可以在纹理上拥有四倍的精灵数量。
因此,您不仅需要传递精灵所在的纹理,还需要传递批处理程序应使用的自定义 uv。
您的代码混淆了两个概念:texture ids(或者,在官方 OpenGL 文档中称为 texture names) , 和纹理 units:
- 纹理id是每个纹理对象的唯一id,纹理对象拥有实际数据,以及采样参数。您实际上可以拥有无限数量的纹理对象,实际限制通常是您机器上的内存量。
- 纹理单元是 table 当前绑定的纹理中的一个条目,可供着色器采样。此 table 的最大大小是一个依赖于实现的限制,可以使用
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, ...)
查询。符合 ES 2.0 实现的保证最小值为 8。
您在创建纹理时正确使用了纹理 ID,方法是使用 glGenTextures()
生成 ID,将其与 glBindTexture()
绑定,然后设置纹理。
问题在于您在哪里设置绘图纹理:
GLES20.glUniform1i(u_texture, ms.getTextureId());
采样器uniform的值不是一个纹理id,它是一个纹理单元的索引。然后,您需要将要使用的纹理绑定到您指定的纹理单元。
使用纹理单元 0,正确的代码如下所示:
GLES20.glUniform1i(u_texture, 0);
GLES20.glActiveTexture(GL_TEXTURE0);
GLES20.glBindTexture(ms.getTextureId());
关于这个代码序列的几点说明:
- 注意统一值是纹理单元的索引(
0
),而glActiveTexture()
的参数是对应的枚举(GL_TEXTURE0
)。那是因为……它是那样定义的。不幸的 API 设计,恕我直言,但你只需要注意它。 glBindTexture()
将纹理绑定到当前活动的纹理单元,因此需要在glActiveTexture()
. 之后
- 如果您只使用一个纹理,则不需要调用
glActiveTexture()
。GL_TEXTURE0
是默认值。我把它放在那里是为了说明纹理单元和纹理id之间的联系是如何建立的。
如果要在同一着色器中对多个纹理进行采样,则使用多个纹理单元。