在 android 上使用 Opengl 2.0 我正在尝试创建一个类来帮助我从纹理图集中绘制方形精灵。我遇到了一些奇怪的图形故障(有时线条/正方形缺少表单纹理),尤其是当设备从睡眠中恢复时。
以下是它的外观:
以下是从睡眠中唤醒手机后出现问题的示例:
和我的渲染器代码。
package com.krazy.androidopengl;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import android.os.SystemClock;
public class GameRenderer implements GLSurfaceView.Renderer
{
private int spriteWidth, spriteHeight;
private int spriteRows,spriteColumns;
private Bitmap atlas;
private float mScale;
private float[] spritePositionData = new float[12];
private float[] cubeTextureCoordinateData = new float[12];
/** Used for debug logs. */
private static final String TAG = "LessonFourRenderer";
private final Context mActivityContext;
/**
* 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 float[] mModelMatrix = new float[16];
/**
* Store the view matrix. This can be thought of as our camera. This matrix transforms world space to eye space;
* it positions things relative to our eye.
*/
private float[] mViewMatrix = new float[16];
/** Store the projection matrix. This is used to project the scene onto a 2D viewport. */
private float[] mProjectionMatrix = new float[16];
/** Allocate storage for the final combined matrix. This will be passed into the shader program. */
private float[] mMVPMatrix = new float[16];
/** Store our model data in a float buffer. */
private FloatBuffer mCubePositions;
private FloatBuffer mCubeTextureCoordinates;
/** This will be used to pass in the transformation matrix. */
private int mMVPMatrixHandle;
/** This will be used to pass in the modelview matrix. */
private int mMVMatrixHandle;
/** This will be used to pass in the texture. */
private int mTextureUniformHandle;
/** This will be used to pass in model position information. */
private int mPositionHandle;
/** This will be used to pass in model texture coordinate information. */
private int mTextureCoordinateHandle;
/** How many bytes per float. */
private final int mBytesPerFloat = 4;
/** Size of the position data in elements. */
private final int mPositionDataSize = 3;
/** Size of the texture coordinate data in elements. */
private final int mTextureCoordinateDataSize = 2;
/** This is a handle to our cube shading program. */
private int mProgramHandle;
/** This is a handle to our texture data. */
private int mTextureDataHandle;
/**
* Initialize the model data.
*/
private Drawer drawer;
public GameRenderer(final Context activityContext, Drawer d)
{
mActivityContext = activityContext;
drawer = d;
}
@Override
public void onSurfaceCreated(GL10
glUnused, EGLConfig config)
{
// Set the background clear color to black.
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
// Use culling to remove back faces.
GLES20.glEnable(GLES20.GL_CULL_FACE);
// Enable depth testing
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
// Enable texture mapping
GLES20.glEnable(GLES20.GL_TEXTURE_2D);
// Position the eye in front of the origin.
final float eyeX = 0.0f;
final float eyeY = 0.0f;
final float eyeZ = -0.5f;
// Facing negative because of anti clockwise triangles are only visible in that direction
// We are looking toward the distance
final float lookX = 0.0f;
final float lookY = 0.0f;
final float lookZ = -1.0f;
// Set our up vector. This is where our head would be pointing were we holding the camera.
final float upX = 0.0f;
final float upY = 1.0f;
final float upZ = 0.0f;
// Set the view matrix. This matrix can be said to represent the camera position.
// NOTE: In OpenGL 1, a ModelView matrix is used, which is a combination of a model and
// view matrix. In OpenGL 2, we can keep track of these matrices separately if we choose.
Matrix.setLookAtM(mViewMatrix, 0, eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ);
final String vertexShader = getVertexShader();
final String fragmentShader = getFragmentShader();
final int vertexShaderHandle = ShaderHelper.compileShader(GLES20.GL_VERTEX_SHADER, vertexShader);
final int fragmentShaderHandle = ShaderHelper.compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShader);
mProgramHandle = ShaderHelper.createAndLinkProgram(vertexShaderHandle, fragmentShaderHandle,
new String[] {"a_Position", "a_Color", "a_Normal", "a_TexCoordinate"});
// Load the texture
mTextureDataHandle = loadTexture(atlas);
}
@Override
public void onSurfaceChanged(GL10 glUnused, int width, int height)
{
// Set the OpenGL viewport to the same size as the surface.
GLES20.glViewport(0, 0, width, height);
// Create a new perspective projection matrix. The height will stay the same
// while the width will vary as per aspect ratio.
final float left = 0f;
final float right = width;
final float bottom = 0f;
final float top = height;
final float near = 0f;
final float far = 10.0f;
Matrix.orthoM(mProjectionMatrix, 0, left, right, bottom, top, near, far);
}
public void setAtlas(int w, int h, Bitmap bitmap, float scale)
{
spriteWidth = w;
spriteHeight = h;
spriteRows = bitmap.getHeight()/spriteHeight;
spriteColumns = bitmap.getWidth()/spriteWidth;
atlas = bitmap;
mScale = scale;
spritePositionData = spriteCoords(spriteWidth * scale,spriteHeight * scale);
// Initialize the buffer.
mCubePositions = ByteBuffer.allocateDirect(spritePositionData.length * mBytesPerFloat)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mCubePositions.put(spritePositionData).position(0);
// Initialize the texture buffer.
mCubeTextureCoordinates = ByteBuffer.allocateDirect(12 * mBytesPerFloat)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
}
@Override
public void onDrawFrame(GL10 glUnused)
{
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
// Set our per-vertex lighting program.
GLES20.glUseProgram(mProgramHandle);
// Set program handles for drawing.
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_MVPMatrix");
mMVMatrixHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_MVMatrix");
mTextureUniformHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_Texture");
mPositionHandle = GLES20.glGetAttribLocation(mProgramHandle, "a_Position");
mTextureCoordinateHandle = GLES20.glGetAttribLocation(mProgramHandle, "a_TexCoordinate");
// Set the active texture unit to texture unit 0.
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
// Bind the texture to this unit.
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataHandle);
// Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 0.
GLES20.glUniform1i(mTextureUniformHandle, 0);
// Pass on drawing to Drawer
drawer.onDrawFrame(this);
}
/**
* Draws a cube.
*/
public void drawSprite(float x, float y, int t, float scale)
{
if(Math.abs(scale-1f) > 0.00001)
{
spritePositionData = spriteCoords(spriteWidth * scale,spriteHeight * scale);
mCubePositions.put(spritePositionData).position(0);
}
else
{
spritePositionData = spriteCoords(spriteWidth * mScale,spriteHeight * mScale);
mCubePositions.put(spritePositionData).position(0);
}
int row = t/spriteColumns -1;
int column = (t-1)%spriteColumns;
float rowHeight = 1f/spriteRows;
float columnWidth = 1f/spriteColumns;
cubeTextureCoordinateData[0] = column*columnWidth;
cubeTextureCoordinateData[1] = row*rowHeight;
cubeTextureCoordinateData[2] = column*columnWidth;
cubeTextureCoordinateData[3] =(row+1)*rowHeight;
cubeTextureCoordinateData[4] =(column+1)*columnWidth;
cubeTextureCoordinateData[5] = row*rowHeight;
cubeTextureCoordinateData[6] =column*columnWidth;
cubeTextureCoordinateData[7] = (row+1)*rowHeight;
cubeTextureCoordinateData[8] = (column+1)*columnWidth;
cubeTextureCoordinateData[9] = (row+1)*rowHeight;
cubeTextureCoordinateData[10] =(column+1)*columnWidth;
cubeTextureCoordinateData[11] = row*rowHeight;
mCubeTextureCoordinates.put(cubeTextureCoordinateData).position(0);
// Translate/ Rotate
Matrix.setIdentityM(mModelMatrix, 0);
Matrix.translateM(mModelMatrix, 0, x, y, -1f);
// Pass in the position information
mCubePositions.position(0);
GLES20.glVertexAttribPointer(mPositionHandle, mPositionDataSize, GLES20.GL_FLOAT, false,
0, mCubePositions);
GLES20.glEnableVertexAttribArray(mPositionHandle);
// Pass in the texture coordinate information
mCubeTextureCoordinates.position(0);
GLES20.glVertexAttribPointer(mTextureCoordinateHandle, mTextureCoordinateDataSize, GLES20.GL_FLOAT, false,
0, mCubeTextureCoordinates);
GLES20.glEnableVertexAttribArray(mTextureCoordinateHandle);
// This multiplies the view matrix by the model matrix, and stores the result in the MVP matrix
// (which currently contains model * view).
Matrix.multiplyMM(mMVPMatrix, 0, mViewMatrix, 0, mModelMatrix, 0);
// Pass in the modelview matrix.
GLES20.glUniformMatrix4fv(mMVMatrixHandle, 1, false, mMVPMatrix, 0);
// This multiplies the modelview matrix by the projection matrix, and stores the result in the MVP matrix
// (which now contains model * view * projection).
Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0);
// Pass in the combined matrix.
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVPMatrix, 0);
// Draw the cube.
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 36);
}
public static int loadTexture(Bitmap bitmap)
{
final int[] textureHandle = new int[1];
GLES20.glGenTextures(1, textureHandle, 0);
if (textureHandle[0] != 0)
{
// Bind to the texture in OpenGL
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);
// Set filtering
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
// Load the bitmap into the bound texture.
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
// Recycle the bitmap, since its data has been loaded into OpenGL.
bitmap.recycle();
}
if (textureHandle[0] == 0)
{
throw new RuntimeException("Error loading texture.");
}
return textureHandle[0];
}
private float[] spriteCoords(float x, float y)
{
x /= 2;
y /= 2;
final float[] spritePositionData =
{
// In OpenGL counter-clockwise winding is default. This means that when we look at a triangle,
// if the points are counter-clockwise we are looking at the "front". If not we are looking at
// the back. OpenGL has an optimization where all back-facing triangles are culled, since they
// usually represent the backside of an object and aren't visible anyways.
-x, y, 0f,
-x, -y, 0f,
x, y, 0f,
-x, -y, 0f,
x, -y, 0f,
x, y,0f,
};
return spritePositionData;
}
protected String getVertexShader()
{
return RawResourceReader.readTextFileFromRawResource(mActivityContext, R.raw.per_pixel_vertex_shader);
}
protected String getFragmentShader()
{
return RawResourceReader.readTextFileFromRawResource(mActivityContext, R.raw.per_pixel_fragment_shader);
}
}