OpenGL ES 2.0 Android - 立方体旋转错误
OpenGL ES 2.0 Android - cube rotation bug
我使用用于 android 应用程序的 OpenGL ES 2.0 实现了彩色 3d 立方体的可视化。我的目标:立方体应该对滑动事件(左、右、上、下)做出反应,然后立方体应该在相应的方向上 rotated。
旋转:
- 停止如果 (current_angle % 90 == 0) -> 所以你从一张脸滑动到另一张脸
- 一次只旋转一圈(并且只绕 x 轴或 y 轴)
- 也应该分步完成(例如:5 度),因此不会立即完成 -> 可以被用户看到
我的代码:
public class Cube20 {
private volatile int angleX;
private volatile int angleY;
private volatile Cube.RotateDirection rotateDirection;
public Cube.RotateDirection getRotateDirection() {
return rotateDirection;
}
public void setRotation(Cube.RotateDirection rotateDirection) {
this.rotateDirection = Cube.RotateDirection.getDirectionForID(rotateDirection.getId());
}
public int getAngleX() {
return angleX;
}
public void setAngleX(int angleX) {
this.angleX = angleX;
}
public int getAngleY() {
return angleY;
}
public void setAngleY(int angleY) {
this.angleY = angleY;
}
private final String vertexShaderCode =
// This matrix member variable provides a hook to manipulate
// the coordinates of the objects that use this vertex shader
"uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" +
"void main() {" +
// The matrix must be included as a modifier of gl_Position.
// Note that the uMVPMatrix factor *must be first* in order
// for the matrix multiplication product to be correct.
" gl_Position = uMVPMatrix * vPosition;" +
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
private final FloatBuffer vertexBuffer;
//private final ShortBuffer drawListBuffer;
private final int mProgram;
private final ShortBuffer indexBuffer;
private int mPositionHandle;
private int mColorHandle;
private int mMVPMatrixHandle;
// number of coordinates per vertex in this array
static final int COORDS_PER_VERTEX = 3;
private float[] vertices = { // Vertices of the 6 faces
// FRONT
-1.0f, -1.0f, 1.0f, // 0. left-bottom-front (0)
1.0f, -1.0f, 1.0f, // 1. right-bottom-front
-1.0f, 1.0f, 1.0f, // 2. left-top-front
1.0f, 1.0f, 1.0f, // 3. right-top-front
// BACK
1.0f, -1.0f, -1.0f, // 6. right-bottom-back (4)
-1.0f, -1.0f, -1.0f, // 4. left-bottom-back
1.0f, 1.0f, -1.0f, // 7. right-top-back
-1.0f, 1.0f, -1.0f, // 5. left-top-back
// LEFT
-1.0f, -1.0f, -1.0f, // 4. left-bottom-back (8)
-1.0f, -1.0f, 1.0f, // 0. left-bottom-front
-1.0f, 1.0f, -1.0f, // 5. left-top-back
-1.0f, 1.0f, 1.0f, // 2. left-top-front
// RIGHT
1.0f, -1.0f, 1.0f, // 1. right-bottom-front (12)
1.0f, -1.0f, -1.0f, // 6. right-bottom-back
1.0f, 1.0f, 1.0f, // 3. right-top-front
1.0f, 1.0f, -1.0f, // 7. right-top-back
// TOP
-1.0f, 1.0f, 1.0f, // 2. left-top-front
1.0f, 1.0f, 1.0f, // 3. right-top-front
-1.0f, 1.0f, -1.0f, // 5. left-top-back
1.0f, 1.0f, -1.0f, // 7. right-top-back
// BOTTOM
-1.0f, -1.0f, -1.0f, // 4. left-bottom-back
1.0f, -1.0f, -1.0f, // 6. right-bottom-back
-1.0f, -1.0f, 1.0f, // 0. left-bottom-front
1.0f, -1.0f, 1.0f // 1. right-bottom-front
};
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
private float[][] colors = { // Colors of the 6 faces
{1.0f, 0.5f, 0.0f, 1.0f}, // 0. orange
{1.0f, 0.0f, 1.0f, 1.0f}, // 1. violet
{0.0f, 1.0f, 0.0f, 1.0f}, // 2. green
{0.0f, 0.0f, 1.0f, 1.0f}, // 3. blue
{1.0f, 0.0f, 0.0f, 1.0f}, // 4. red
{1.0f, 1.0f, 0.0f, 1.0f} // 5. yellow
};
short[] indices = {
0, 1, 2, 2, 1, 3, // FRONT
4, 5, 6, 6, 5, 7, // BACK
8, 9, 10, 10, 9, 11, // LEFT
12, 13, 14, 14, 13, 15, // RIGHT
16, 17, 18, 18, 17, 19, // TOP
20, 21, 22, 22, 21, 23, // BOTTOM
};
private int numFaces = 6;
/**
* Sets up the drawing object data for use in an OpenGL ES context.
*/
public Cube20() {
// initialize vertex byte buffer for shape coordinates
ByteBuffer bb = ByteBuffer.allocateDirect(
// (# of coordinate values * 4 bytes per float)
vertices.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);
// initialize byte buffer for the draw list
indexBuffer = ByteBuffer.allocateDirect(indices.length * 2).order(ByteOrder.nativeOrder()).asShortBuffer();
indexBuffer.put(indices).position(0);
// prepare shaders and OpenGL program
int vertexShader = RenderUtils.loadShader(
GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = RenderUtils.loadShader(
GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);
mProgram = GLES20.glCreateProgram(); // create empty OpenGL Program
GLES20.glAttachShader(mProgram, vertexShader); // add the vertex shader to program
GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
GLES20.glLinkProgram(mProgram); // create OpenGL program executables
this.rotateDirection = Cube.RotateDirection.NONE;
}
/**
* Encapsulates the OpenGL ES instructions for drawing this shape.
*
* @param mvpMatrix - The Model View Project matrix in which to draw
* this shape.
*/
public void draw(float[] mvpMatrix) {
// Add program to OpenGL environment
GLES20.glUseProgram(mProgram);
GLES20.glFrontFace(GLES20.GL_CCW);
GLES20.glEnable(GLES20.GL_CULL_FACE);
GLES20.glCullFace(GLES20.GL_BACK);
// scale
float scale_matrix[] = new float[16];
Matrix.setIdentityM(scale_matrix, 0);
Matrix.scaleM(scale_matrix, 0, 0.5f, 0.5f, 1);
Matrix.multiplyMM(mvpMatrix, 0, scale_matrix, 0, mvpMatrix, 0);
// get handle to vertex shader's vPosition member
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
// Enable a handle to the triangle vertices
GLES20.glEnableVertexAttribArray(mPositionHandle);
// Prepare the triangle coordinate data
GLES20.glVertexAttribPointer(
mPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
// get handle to fragment shader's vColor member
mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
// Set color for drawing the triangle
//GLES20.glUniform4fv(mColorHandle, 1, color, 0);
// get handle to shape's transformation matrix
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
RenderUtils.checkGlError("glGetUniformLocation");
// Apply the projection and view transformation
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
RenderUtils.checkGlError("glUniformMatrix4fv");
// Render all the faces
for (int face = 0; face < numFaces; face++) {
// Set the color for each of the faces
GLES20.glUniform4fv(mColorHandle, 1, colors[face], 0);
indexBuffer.position(face * 6);
GLES20.glDrawElements(GLES20.GL_TRIANGLES, 6, GLES20.GL_UNSIGNED_SHORT, indexBuffer);
}
// Disable vertex array
GLES20.glDisableVertexAttribArray(mPositionHandle);
GLES20.glDisable(GLES20.GL_CULL_FACE);
}
}
public class MyGLSurfaceView extends GLSurfaceView {
private volatile MyGLRenderer myGLRenderer;
public MyGLSurfaceView(Context context) {
super(context);
// Create an OpenGL ES 2.0 context
setEGLContextClientVersion(2);
myGLRenderer = new MyGLRenderer(context);
setRenderer(myGLRenderer); // Use a custom renderer
setOnTouchListener(new OnSwipeListener(context));
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
class OnSwipeListener implements View.OnTouchListener {
private final GestureDetector gestureDetector;
public OnSwipeListener(Context context) {
this.gestureDetector = new GestureDetector(context, new OnFlingListener());
}
@Override
public boolean onTouch(View v, MotionEvent event) {
return this.gestureDetector.onTouchEvent(event);
}
private class OnFlingListener extends GestureDetector.SimpleOnGestureListener {
private final Object LOCK = new Object();
@Override
public boolean onDown(MotionEvent e) {
return true;
}
/**
* @return true if the event is consumed, else false
*/
@Override
public boolean onFling(MotionEvent down, MotionEvent up, float velocityX, float velocityY) {
//super.onFling(down, up, velocityX, velocityY);
float distanceX = up.getX() - down.getX();
float distanceY = up.getY() - down.getY();
final Cube20 cube = myGLRenderer.getCube();
if (!cube.getRotateDirection().equals(Cube.RotateDirection.NONE)) {
return false;
}
if (Math.abs(distanceX) > Math.abs(distanceY)) {
if (distanceX > 0) {
// RIGHT
cube.setRotation(Cube.RotateDirection.RIGHT);
} else {
// LEFT
cube.setRotation(Cube.RotateDirection.LEFT);
}
} else {
if (distanceY < 0) {
// TOP
cube.setRotation(Cube.RotateDirection.UP);
} else {
// DOWN
cube.setRotation(Cube.RotateDirection.DOWN);
}
}
//requestRender();
return true;
}
}
}
}
public class MyGLRenderer implements GLSurfaceView.Renderer {
Context context; // Application's context
private volatile int rotationAngle;
private volatile float rotateX;
private volatile float rotateY;
private boolean firstRotation = true;
private Cube20 cube;
// mMVPMatrix is an abbreviation for "Model View Projection Matrix"
private final float[] mMVPMatrix = new float[16];
private final float[] mProjectionMatrix = new float[16];
private final float[] mViewMatrix = new float[16];
// Constructor with global application context
public MyGLRenderer(Context context) {
this.context = context;
}
public float getRotateX() {
return rotateX;
}
public void setRotateX(float rotateX) {
this.rotateX = rotateX;
}
public float getRotateY() {
return rotateY;
}
public void setRotateY(float rotateY) {
this.rotateY = rotateY;
}
public float getAngle() {
return rotationAngle;
}
public void setAngle(int angle) {
rotationAngle = angle;
}
// Call back when the surface is first created or re-created
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// Set the background frame color
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
this.cube = new Cube20();
}
// Call back to draw the current frame.
@Override
public void onDrawFrame(GL10 gl) {
// Redraw background color
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
// Set the camera position (View matrix)
Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -1, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
// Calculate the projection and view transformation
Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);
// zoom out a bit
Matrix.translateM(mMVPMatrix, 0, 0, 0, 4.5f);
int angleOffset = 5;
// update the angles for the x and y rotation
if (cube.getRotateDirection().equals(Cube.RotateDirection.LEFT)) {
cube.setAngleY(cube.getAngleY() - angleOffset);
} else if (cube.getRotateDirection().equals(Cube.RotateDirection.RIGHT)) {
cube.setAngleY(cube.getAngleY() + angleOffset);
} else if (cube.getRotateDirection().equals(Cube.RotateDirection.UP)) {
cube.setAngleX(cube.getAngleX() + angleOffset);
} else if (cube.getRotateDirection().equals(Cube.RotateDirection.DOWN)) {
cube.setAngleX(cube.getAngleX() - angleOffset);
}
firstRotation = false;
// rotate and draw
rotate();
cube.draw(mMVPMatrix);
// test if rotation should be stopped (lock in each 90° step)
if (cube.getRotateDirection().equals(Cube.RotateDirection.LEFT) ||
cube.getRotateDirection().equals(Cube.RotateDirection.RIGHT)) {
if (!firstRotation && cube.getAngleY() % 90 == 0) {
cube.setRotation(Cube.RotateDirection.NONE);
}
}
if (cube.getRotateDirection().equals(Cube.RotateDirection.UP) ||
cube.getRotateDirection().equals(Cube.RotateDirection.DOWN)) {
if (!firstRotation && cube.getAngleX() % 90 == 0) {
cube.setRotation(Cube.RotateDirection.NONE);
}
}
Log.i("MyGLRENDER~ ", cube.getRotateDirection().toString());
}
private void rotate() {
float[] rotationMatrix = new float[16];
Matrix.setIdentityM(rotationMatrix, 0);
// rotate in x and y direction, apply that to the intermediate matrix
Matrix.rotateM(rotationMatrix, 0, cube.getAngleX(), 1, 0, 0);
Matrix.rotateM(rotationMatrix, 0, cube.getAngleY(), 0, 1, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mMVPMatrix, 0, rotationMatrix, 0);
}
// Call back after onSurfaceCreated() or whenever the window's size changes
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
float ratio = (float) width / height;
// this projection matrix is applied to object coordinates
// in the onDrawFrame() method
Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
}
public Cube20 getCube() {
return cube;
}
}
public class MyGLActivity extends Activity {
private GLSurfaceView glView; // Use GLSurfaceView
// Call back when the activity is started, to initialize the view
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
glView = new MyGLSurfaceView(this); // Allocate a GLSurfaceView
this.setContentView(glView); // This activity sets to GLSurfaceView
}
// Call back when the activity is going into the background
@Override
protected void onPause() {
super.onPause();
glView.onPause();
}
// Call back after onPause()
@Override
protected void onResume() {
super.onResume();
glView.onResume();
}
}
我期望的行为:
成功滑动反应并绕 x 轴旋转
成功滑动反应并绕 y 轴旋转
使用此设置我遇到以下问题:
- 有时旋转是围绕 z 轴完成的
我从来没有为立方体实现绕 z 轴的旋转,我不确定为什么要进行这种旋转。我只能想象 MyGLSurfaceView
和 MyGLRenderer
处的滑动侦听器之间可能存在线程问题(其中包含对多维数据集的引用)。
旋转在 MyGLRenderer.rotate
完成。立方体使用单独的角度 (x/y),还有一个 属性 当前已完成旋转(左、右、上、下、NONE),它在 OnSwipeListener
.
- 此外,如果发生 z 旋转 (1.) 的错误,则围绕 x 或 y 轴的旋转是在 错误的 方向上完成的(left/right , up/down 交换)
我的猜测是角度或 RotateDirection
未正确更新。
该应用程序在 OnePlus 3T @ Android 8.0 上进行了测试,IDE 是 Android Studio 3.2.1.
如果你把一个立方体绕X轴旋转了90°,那么立方体绕Y轴的旋转当然就是世界上绕Z轴的旋转。您必须围绕世界的 Y 轴旋转,而不是围绕立方体的 Y 轴旋转。
为此,您必须将立方体的串联旋转存储在旋转矩阵中,并将新的旋转应用到该矩阵。
为旋转矩阵创建一个成员:
private final float[] mRotationMatrix = new float[16];
用单位矩阵初始化它:
Matrix.setIdentityM(mRotationMatrix, 0);
在方法rotate
中,您必须将当前动画应用于旋转矩阵。顺序必须是 animationMatrix * mRotationMatrix
。矩阵乘法不是commutative。如果您不遵守乘法的顺序,您将得到与以前相同的结果,并且旋转将围绕立方体的轴而不是围绕世界的轴。
private void rotate() {
float[] animationMatrix = new float[16];
Matrix.setIdentityM(animationMatrix, 0);
// rotate in x and y direction, apply that to the intermediate matrix
Matrix.rotateM(animationMatrix, 0, cube.getAngleX(), 1, 0, 0);
Matrix.rotateM(animationMatrix, 0, cube.getAngleY(), 0, 1, 0);
// concatenate the animation and the rotation matrix; the order is important
Matrix.multiplyMM(animationMatrix, 0, animationMatrix, 0, mRotationMatrix, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mMVPMatrix, 0, animationMatrix, 0);
}
当动画达到完整的 90° 时,您必须更改旋转矩阵并重置旋转角度:
if (cube.getRotateDirection().equals(Cube.RotateDirection.LEFT) ||
cube.getRotateDirection().equals(Cube.RotateDirection.RIGHT)) {
if (!firstRotation && cube.getAngleY() % 90 == 0) {
float[] newRotationMatrix = new float[16];
Matrix.setIdentityM(newRotationMatrix, 0);
Matrix.rotateM(newRotationMatrix, 0, cube.getAngleY(), 0, 1, 0);
// concatenate the new 90 rotation to the rotation matrix
Matrix.multiplyMM(mRotationMatrix, 0, newRotationMatrix, 0, mRotationMatrix, 0);
// reset the angle
cube.setAngleY(0);
cube.setRotation(Cube.RotateDirection.NONE);
}
}
if (cube.getRotateDirection().equals(Cube.RotateDirection.UP) ||
cube.getRotateDirection().equals(Cube.RotateDirection.DOWN)) {
if (!firstRotation && cube.getAngleX() % 90 == 0) {
float[] newRotationMatrix = new float[16];
Matrix.setIdentityM(newRotationMatrix, 0);
Matrix.rotateM(newRotationMatrix, 0, cube.getAngleX(), 1, 0, 0);
// concatenate the new 90 rotation to the rotation matrix
Matrix.multiplyMM(mRotationMatrix, 0, newRotationMatrix, 0, mRotationMatrix, 0);
// reset the angle
cube.setAngleX(0);
cube.setRotation(Cube.RotateDirection.NONE);
}
}
我使用用于 android 应用程序的 OpenGL ES 2.0 实现了彩色 3d 立方体的可视化。我的目标:立方体应该对滑动事件(左、右、上、下)做出反应,然后立方体应该在相应的方向上 rotated。
旋转:
- 停止如果 (current_angle % 90 == 0) -> 所以你从一张脸滑动到另一张脸
- 一次只旋转一圈(并且只绕 x 轴或 y 轴)
- 也应该分步完成(例如:5 度),因此不会立即完成 -> 可以被用户看到
我的代码:
public class Cube20 {
private volatile int angleX;
private volatile int angleY;
private volatile Cube.RotateDirection rotateDirection;
public Cube.RotateDirection getRotateDirection() {
return rotateDirection;
}
public void setRotation(Cube.RotateDirection rotateDirection) {
this.rotateDirection = Cube.RotateDirection.getDirectionForID(rotateDirection.getId());
}
public int getAngleX() {
return angleX;
}
public void setAngleX(int angleX) {
this.angleX = angleX;
}
public int getAngleY() {
return angleY;
}
public void setAngleY(int angleY) {
this.angleY = angleY;
}
private final String vertexShaderCode =
// This matrix member variable provides a hook to manipulate
// the coordinates of the objects that use this vertex shader
"uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" +
"void main() {" +
// The matrix must be included as a modifier of gl_Position.
// Note that the uMVPMatrix factor *must be first* in order
// for the matrix multiplication product to be correct.
" gl_Position = uMVPMatrix * vPosition;" +
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
private final FloatBuffer vertexBuffer;
//private final ShortBuffer drawListBuffer;
private final int mProgram;
private final ShortBuffer indexBuffer;
private int mPositionHandle;
private int mColorHandle;
private int mMVPMatrixHandle;
// number of coordinates per vertex in this array
static final int COORDS_PER_VERTEX = 3;
private float[] vertices = { // Vertices of the 6 faces
// FRONT
-1.0f, -1.0f, 1.0f, // 0. left-bottom-front (0)
1.0f, -1.0f, 1.0f, // 1. right-bottom-front
-1.0f, 1.0f, 1.0f, // 2. left-top-front
1.0f, 1.0f, 1.0f, // 3. right-top-front
// BACK
1.0f, -1.0f, -1.0f, // 6. right-bottom-back (4)
-1.0f, -1.0f, -1.0f, // 4. left-bottom-back
1.0f, 1.0f, -1.0f, // 7. right-top-back
-1.0f, 1.0f, -1.0f, // 5. left-top-back
// LEFT
-1.0f, -1.0f, -1.0f, // 4. left-bottom-back (8)
-1.0f, -1.0f, 1.0f, // 0. left-bottom-front
-1.0f, 1.0f, -1.0f, // 5. left-top-back
-1.0f, 1.0f, 1.0f, // 2. left-top-front
// RIGHT
1.0f, -1.0f, 1.0f, // 1. right-bottom-front (12)
1.0f, -1.0f, -1.0f, // 6. right-bottom-back
1.0f, 1.0f, 1.0f, // 3. right-top-front
1.0f, 1.0f, -1.0f, // 7. right-top-back
// TOP
-1.0f, 1.0f, 1.0f, // 2. left-top-front
1.0f, 1.0f, 1.0f, // 3. right-top-front
-1.0f, 1.0f, -1.0f, // 5. left-top-back
1.0f, 1.0f, -1.0f, // 7. right-top-back
// BOTTOM
-1.0f, -1.0f, -1.0f, // 4. left-bottom-back
1.0f, -1.0f, -1.0f, // 6. right-bottom-back
-1.0f, -1.0f, 1.0f, // 0. left-bottom-front
1.0f, -1.0f, 1.0f // 1. right-bottom-front
};
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
private float[][] colors = { // Colors of the 6 faces
{1.0f, 0.5f, 0.0f, 1.0f}, // 0. orange
{1.0f, 0.0f, 1.0f, 1.0f}, // 1. violet
{0.0f, 1.0f, 0.0f, 1.0f}, // 2. green
{0.0f, 0.0f, 1.0f, 1.0f}, // 3. blue
{1.0f, 0.0f, 0.0f, 1.0f}, // 4. red
{1.0f, 1.0f, 0.0f, 1.0f} // 5. yellow
};
short[] indices = {
0, 1, 2, 2, 1, 3, // FRONT
4, 5, 6, 6, 5, 7, // BACK
8, 9, 10, 10, 9, 11, // LEFT
12, 13, 14, 14, 13, 15, // RIGHT
16, 17, 18, 18, 17, 19, // TOP
20, 21, 22, 22, 21, 23, // BOTTOM
};
private int numFaces = 6;
/**
* Sets up the drawing object data for use in an OpenGL ES context.
*/
public Cube20() {
// initialize vertex byte buffer for shape coordinates
ByteBuffer bb = ByteBuffer.allocateDirect(
// (# of coordinate values * 4 bytes per float)
vertices.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);
// initialize byte buffer for the draw list
indexBuffer = ByteBuffer.allocateDirect(indices.length * 2).order(ByteOrder.nativeOrder()).asShortBuffer();
indexBuffer.put(indices).position(0);
// prepare shaders and OpenGL program
int vertexShader = RenderUtils.loadShader(
GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = RenderUtils.loadShader(
GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);
mProgram = GLES20.glCreateProgram(); // create empty OpenGL Program
GLES20.glAttachShader(mProgram, vertexShader); // add the vertex shader to program
GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
GLES20.glLinkProgram(mProgram); // create OpenGL program executables
this.rotateDirection = Cube.RotateDirection.NONE;
}
/**
* Encapsulates the OpenGL ES instructions for drawing this shape.
*
* @param mvpMatrix - The Model View Project matrix in which to draw
* this shape.
*/
public void draw(float[] mvpMatrix) {
// Add program to OpenGL environment
GLES20.glUseProgram(mProgram);
GLES20.glFrontFace(GLES20.GL_CCW);
GLES20.glEnable(GLES20.GL_CULL_FACE);
GLES20.glCullFace(GLES20.GL_BACK);
// scale
float scale_matrix[] = new float[16];
Matrix.setIdentityM(scale_matrix, 0);
Matrix.scaleM(scale_matrix, 0, 0.5f, 0.5f, 1);
Matrix.multiplyMM(mvpMatrix, 0, scale_matrix, 0, mvpMatrix, 0);
// get handle to vertex shader's vPosition member
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
// Enable a handle to the triangle vertices
GLES20.glEnableVertexAttribArray(mPositionHandle);
// Prepare the triangle coordinate data
GLES20.glVertexAttribPointer(
mPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
// get handle to fragment shader's vColor member
mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
// Set color for drawing the triangle
//GLES20.glUniform4fv(mColorHandle, 1, color, 0);
// get handle to shape's transformation matrix
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
RenderUtils.checkGlError("glGetUniformLocation");
// Apply the projection and view transformation
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
RenderUtils.checkGlError("glUniformMatrix4fv");
// Render all the faces
for (int face = 0; face < numFaces; face++) {
// Set the color for each of the faces
GLES20.glUniform4fv(mColorHandle, 1, colors[face], 0);
indexBuffer.position(face * 6);
GLES20.glDrawElements(GLES20.GL_TRIANGLES, 6, GLES20.GL_UNSIGNED_SHORT, indexBuffer);
}
// Disable vertex array
GLES20.glDisableVertexAttribArray(mPositionHandle);
GLES20.glDisable(GLES20.GL_CULL_FACE);
}
}
public class MyGLSurfaceView extends GLSurfaceView {
private volatile MyGLRenderer myGLRenderer;
public MyGLSurfaceView(Context context) {
super(context);
// Create an OpenGL ES 2.0 context
setEGLContextClientVersion(2);
myGLRenderer = new MyGLRenderer(context);
setRenderer(myGLRenderer); // Use a custom renderer
setOnTouchListener(new OnSwipeListener(context));
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
class OnSwipeListener implements View.OnTouchListener {
private final GestureDetector gestureDetector;
public OnSwipeListener(Context context) {
this.gestureDetector = new GestureDetector(context, new OnFlingListener());
}
@Override
public boolean onTouch(View v, MotionEvent event) {
return this.gestureDetector.onTouchEvent(event);
}
private class OnFlingListener extends GestureDetector.SimpleOnGestureListener {
private final Object LOCK = new Object();
@Override
public boolean onDown(MotionEvent e) {
return true;
}
/**
* @return true if the event is consumed, else false
*/
@Override
public boolean onFling(MotionEvent down, MotionEvent up, float velocityX, float velocityY) {
//super.onFling(down, up, velocityX, velocityY);
float distanceX = up.getX() - down.getX();
float distanceY = up.getY() - down.getY();
final Cube20 cube = myGLRenderer.getCube();
if (!cube.getRotateDirection().equals(Cube.RotateDirection.NONE)) {
return false;
}
if (Math.abs(distanceX) > Math.abs(distanceY)) {
if (distanceX > 0) {
// RIGHT
cube.setRotation(Cube.RotateDirection.RIGHT);
} else {
// LEFT
cube.setRotation(Cube.RotateDirection.LEFT);
}
} else {
if (distanceY < 0) {
// TOP
cube.setRotation(Cube.RotateDirection.UP);
} else {
// DOWN
cube.setRotation(Cube.RotateDirection.DOWN);
}
}
//requestRender();
return true;
}
}
}
}
public class MyGLRenderer implements GLSurfaceView.Renderer {
Context context; // Application's context
private volatile int rotationAngle;
private volatile float rotateX;
private volatile float rotateY;
private boolean firstRotation = true;
private Cube20 cube;
// mMVPMatrix is an abbreviation for "Model View Projection Matrix"
private final float[] mMVPMatrix = new float[16];
private final float[] mProjectionMatrix = new float[16];
private final float[] mViewMatrix = new float[16];
// Constructor with global application context
public MyGLRenderer(Context context) {
this.context = context;
}
public float getRotateX() {
return rotateX;
}
public void setRotateX(float rotateX) {
this.rotateX = rotateX;
}
public float getRotateY() {
return rotateY;
}
public void setRotateY(float rotateY) {
this.rotateY = rotateY;
}
public float getAngle() {
return rotationAngle;
}
public void setAngle(int angle) {
rotationAngle = angle;
}
// Call back when the surface is first created or re-created
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// Set the background frame color
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
this.cube = new Cube20();
}
// Call back to draw the current frame.
@Override
public void onDrawFrame(GL10 gl) {
// Redraw background color
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
// Set the camera position (View matrix)
Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -1, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
// Calculate the projection and view transformation
Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);
// zoom out a bit
Matrix.translateM(mMVPMatrix, 0, 0, 0, 4.5f);
int angleOffset = 5;
// update the angles for the x and y rotation
if (cube.getRotateDirection().equals(Cube.RotateDirection.LEFT)) {
cube.setAngleY(cube.getAngleY() - angleOffset);
} else if (cube.getRotateDirection().equals(Cube.RotateDirection.RIGHT)) {
cube.setAngleY(cube.getAngleY() + angleOffset);
} else if (cube.getRotateDirection().equals(Cube.RotateDirection.UP)) {
cube.setAngleX(cube.getAngleX() + angleOffset);
} else if (cube.getRotateDirection().equals(Cube.RotateDirection.DOWN)) {
cube.setAngleX(cube.getAngleX() - angleOffset);
}
firstRotation = false;
// rotate and draw
rotate();
cube.draw(mMVPMatrix);
// test if rotation should be stopped (lock in each 90° step)
if (cube.getRotateDirection().equals(Cube.RotateDirection.LEFT) ||
cube.getRotateDirection().equals(Cube.RotateDirection.RIGHT)) {
if (!firstRotation && cube.getAngleY() % 90 == 0) {
cube.setRotation(Cube.RotateDirection.NONE);
}
}
if (cube.getRotateDirection().equals(Cube.RotateDirection.UP) ||
cube.getRotateDirection().equals(Cube.RotateDirection.DOWN)) {
if (!firstRotation && cube.getAngleX() % 90 == 0) {
cube.setRotation(Cube.RotateDirection.NONE);
}
}
Log.i("MyGLRENDER~ ", cube.getRotateDirection().toString());
}
private void rotate() {
float[] rotationMatrix = new float[16];
Matrix.setIdentityM(rotationMatrix, 0);
// rotate in x and y direction, apply that to the intermediate matrix
Matrix.rotateM(rotationMatrix, 0, cube.getAngleX(), 1, 0, 0);
Matrix.rotateM(rotationMatrix, 0, cube.getAngleY(), 0, 1, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mMVPMatrix, 0, rotationMatrix, 0);
}
// Call back after onSurfaceCreated() or whenever the window's size changes
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
float ratio = (float) width / height;
// this projection matrix is applied to object coordinates
// in the onDrawFrame() method
Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
}
public Cube20 getCube() {
return cube;
}
}
public class MyGLActivity extends Activity {
private GLSurfaceView glView; // Use GLSurfaceView
// Call back when the activity is started, to initialize the view
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
glView = new MyGLSurfaceView(this); // Allocate a GLSurfaceView
this.setContentView(glView); // This activity sets to GLSurfaceView
}
// Call back when the activity is going into the background
@Override
protected void onPause() {
super.onPause();
glView.onPause();
}
// Call back after onPause()
@Override
protected void onResume() {
super.onResume();
glView.onResume();
}
}
我期望的行为:
成功滑动反应并绕 x 轴旋转
成功滑动反应并绕 y 轴旋转
使用此设置我遇到以下问题:
- 有时旋转是围绕 z 轴完成的
我从来没有为立方体实现绕 z 轴的旋转,我不确定为什么要进行这种旋转。我只能想象MyGLSurfaceView
和MyGLRenderer
处的滑动侦听器之间可能存在线程问题(其中包含对多维数据集的引用)。
旋转在 MyGLRenderer.rotate
完成。立方体使用单独的角度 (x/y),还有一个 属性 当前已完成旋转(左、右、上、下、NONE),它在 OnSwipeListener
.
- 此外,如果发生 z 旋转 (1.) 的错误,则围绕 x 或 y 轴的旋转是在 错误的 方向上完成的(left/right , up/down 交换)
我的猜测是角度或 RotateDirection
未正确更新。
该应用程序在 OnePlus 3T @ Android 8.0 上进行了测试,IDE 是 Android Studio 3.2.1.
如果你把一个立方体绕X轴旋转了90°,那么立方体绕Y轴的旋转当然就是世界上绕Z轴的旋转。您必须围绕世界的 Y 轴旋转,而不是围绕立方体的 Y 轴旋转。
为此,您必须将立方体的串联旋转存储在旋转矩阵中,并将新的旋转应用到该矩阵。
为旋转矩阵创建一个成员:
private final float[] mRotationMatrix = new float[16];
用单位矩阵初始化它:
Matrix.setIdentityM(mRotationMatrix, 0);
在方法rotate
中,您必须将当前动画应用于旋转矩阵。顺序必须是 animationMatrix * mRotationMatrix
。矩阵乘法不是commutative。如果您不遵守乘法的顺序,您将得到与以前相同的结果,并且旋转将围绕立方体的轴而不是围绕世界的轴。
private void rotate() {
float[] animationMatrix = new float[16];
Matrix.setIdentityM(animationMatrix, 0);
// rotate in x and y direction, apply that to the intermediate matrix
Matrix.rotateM(animationMatrix, 0, cube.getAngleX(), 1, 0, 0);
Matrix.rotateM(animationMatrix, 0, cube.getAngleY(), 0, 1, 0);
// concatenate the animation and the rotation matrix; the order is important
Matrix.multiplyMM(animationMatrix, 0, animationMatrix, 0, mRotationMatrix, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mMVPMatrix, 0, animationMatrix, 0);
}
当动画达到完整的 90° 时,您必须更改旋转矩阵并重置旋转角度:
if (cube.getRotateDirection().equals(Cube.RotateDirection.LEFT) ||
cube.getRotateDirection().equals(Cube.RotateDirection.RIGHT)) {
if (!firstRotation && cube.getAngleY() % 90 == 0) {
float[] newRotationMatrix = new float[16];
Matrix.setIdentityM(newRotationMatrix, 0);
Matrix.rotateM(newRotationMatrix, 0, cube.getAngleY(), 0, 1, 0);
// concatenate the new 90 rotation to the rotation matrix
Matrix.multiplyMM(mRotationMatrix, 0, newRotationMatrix, 0, mRotationMatrix, 0);
// reset the angle
cube.setAngleY(0);
cube.setRotation(Cube.RotateDirection.NONE);
}
}
if (cube.getRotateDirection().equals(Cube.RotateDirection.UP) ||
cube.getRotateDirection().equals(Cube.RotateDirection.DOWN)) {
if (!firstRotation && cube.getAngleX() % 90 == 0) {
float[] newRotationMatrix = new float[16];
Matrix.setIdentityM(newRotationMatrix, 0);
Matrix.rotateM(newRotationMatrix, 0, cube.getAngleX(), 1, 0, 0);
// concatenate the new 90 rotation to the rotation matrix
Matrix.multiplyMM(mRotationMatrix, 0, newRotationMatrix, 0, mRotationMatrix, 0);
// reset the angle
cube.setAngleX(0);
cube.setRotation(Cube.RotateDirection.NONE);
}
}