Cardboard VR Projects for Android
上QQ阅读APP看书,第一时间看更新

Hello, cube!

A flat triangle floating in 3D space may be amazing, but it's nothing compared to what we're going to do next: a 3D cube!

The cube model data

The triangle, with just three vertices, was declared in the MainActivity class to keep the example simple. Now, we will introduce more complex geometry. We'll put it in a class named Cube.

Okay, it's just a cube that is composed of eight distinct vertices, forming six faces, right?

Well, GPUs prefer to render triangles rather than quads, so subdivide each face into two triangles; that's 12 triangles in total. To define each triangle separately, that's a total of 36 vertices, with proper winding directions, defining our model, as shown in CUBE_COORDS. Why not just define eight vertices and reuse them? We'll show you how to do this later.

Note

Remember that we always need to be careful of the winding order of the vertices (counter-clockwise) so that the visible side of each triangle is facing outward.

In Android Studio, in the Android project hierarchy pane on the left-hand side, find your Java code folder (such as com.cardbookvr.cardboardbox). Right-click on it, and go to New | Java Class. Then, set Name: Cube, and click on OK. Then, edit the file, as follows (remember that the code for the projects in this book are available for download from the publisher's website and from the book's public GitHub repositories):

package com.cardbookvr.cardboardbox;

public class Cube {

    public static final float[] CUBE_COORDS = new float[] {
        // Front face
        -1.0f, 1.0f, 1.0f,
        -1.0f, -1.0f, 1.0f,
        1.0f, 1.0f, 1.0f,
        -1.0f, -1.0f, 1.0f,
        1.0f, -1.0f, 1.0f,
        1.0f, 1.0f, 1.0f,

        // Right face
        1.0f, 1.0f, 1.0f,
        1.0f, -1.0f, 1.0f,
        1.0f, 1.0f, -1.0f,
        1.0f, -1.0f, 1.0f,
        1.0f, -1.0f, -1.0f,
        1.0f, 1.0f, -1.0f,

        // Back face
        1.0f, 1.0f, -1.0f,
        1.0f, -1.0f, -1.0f,
        -1.0f, 1.0f, -1.0f,
        1.0f, -1.0f, -1.0f,
        -1.0f, -1.0f, -1.0f,
        -1.0f, 1.0f, -1.0f,

        // Left face
        -1.0f, 1.0f, -1.0f,
        -1.0f, -1.0f, -1.0f,
        -1.0f, 1.0f, 1.0f,
        -1.0f, -1.0f, -1.0f,
        -1.0f, -1.0f, 1.0f,
        -1.0f, 1.0f, 1.0f,

        // Top face
        -1.0f, 1.0f, -1.0f,
        -1.0f, 1.0f, 1.0f,
        1.0f, 1.0f, -1.0f,
        -1.0f, 1.0f, 1.0f,
        1.0f, 1.0f, 1.0f,
        1.0f, 1.0f, -1.0f,

        // Bottom face
        1.0f, -1.0f, -1.0f,
        1.0f, -1.0f, 1.0f,
        -1.0f, -1.0f, -1.0f,
        1.0f, -1.0f, 1.0f,
        -1.0f, -1.0f, 1.0f,
        -1.0f, -1.0f, -1.0f,
    };
}

Cube code

Returning to the MainActivity file, we'll just copy/paste/edit the triangle code and reuse it for the cube. Obviously, this isn't ideal, and once we see a good pattern, we can abstract out some of this into reusable methods. Also, we'll use the same shaders as those of the triangle, and then in the next section, we'll replace them with a better lighting model. That is to say, we'll implement lighting or what a 2D artist might call shading, which we haven't done so far.

Like the triangle, we declare a bunch of variables that we are going to need. The vertex count, obviously, should come from the new Cube.CUBE_COORDS array:

    // Model variables
    private static float cubeCoords[] = Cube.CUBE_COORDS;
    private final int cubeVertexCount = cubeCoords.length / COORDS_PER_VERTEX;
    private float cubeColor[] = { 0.8f, 0.6f, 0.2f, 0.0f }; // yellow-ish
    private float[] cubeTransform;
    private float cubeDistance = 5f;

    // Viewing variables
    private float[] cubeView;

    // Rendering variables
    private FloatBuffer cubeVerticesBuffer;
    private int cubeProgram;
    private int cubePositionParam;
    private int cubeColorParam;
    private int cubeMVPMatrixParam;

Add the following code to onCreate:

        cubeTransform = new float[16];
        cubeView = new float[16];

Add the following code to onSurfaceCreated:

        prepareRenderingCube();

Write the prepareRenderingCube method, as follows:

private void prepareRenderingCube() {
        // Allocate buffers
        ByteBuffer bb = ByteBuffer.allocateDirect(cubeCoords.length * 4);
        bb.order(ByteOrder.nativeOrder());
        cubeVerticesBuffer = bb.asFloatBuffer();
        cubeVerticesBuffer.put(cubeCoords);
        cubeVerticesBuffer.position(0);

        // Create GL program
        cubeProgram = GLES20.glCreateProgram();
        GLES20.glAttachShader(cubeProgram, simpleVertexShader);
        GLES20.glAttachShader(cubeProgram, simpleFragmentShader);
        GLES20.glLinkProgram(cubeProgram);
        GLES20.glUseProgram(cubeProgram);

        // Get shader params
        cubePositionParam = GLES20.glGetAttribLocation(cubeProgram, "a_Position");
        cubeColorParam = GLES20.glGetUniformLocation(cubeProgram, "u_Color");
        cubeMVPMatrixParam = GLES20.glGetUniformLocation(cubeProgram, "u_MVP");

        // Enable arrays
        GLES20.glEnableVertexAttribArray(cubePositionParam);
    }

We will position the cube 5 units away and rotate it 30 degrees on a diagonal axis of (1, 1, 0). Without the rotation, we'll just see the square of the front face. Add the following code to initializeScene:

        // Rotate and position the cube
        Matrix.setIdentityM(cubeTransform, 0);
        Matrix.translateM(cubeTransform, 0, 0, 0, -cubeDistance);
        Matrix.rotateM(cubeTransform, 0, 30, 1, 1, 0);

Add the following code to onDrawEye to calculate the MVP matrix, including the cubeTransform matrix, and then draw the cube:

        Matrix.multiplyMM(cubeView, 0, view, 0, cubeTransform, 0);
        Matrix.multiplyMM(modelViewProjection, 0, perspective, 0, cubeView, 0);
        drawCube();

Write the drawCube method, which is very similar to the drawTri method, as follows:

    private void drawCube() {
        GLES20.glUseProgram(cubeProgram);
        GLES20.glUniformMatrix4fv(cubeMVPMatrixParam, 1, false, modelViewProjection, 0);
        GLES20.glVertexAttribPointer(cubePositionParam, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false, 0, cubeVerticesBuffer);
        GLES20.glUniform4fv(cubeColorParam, 1, cubeColor, 0);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, cubeVertexCount);
    }

Build and run it. You will now see a 3D view of the cube, as shown in the following screenshot. It needs shading.

Cube code