One, the introduction

In Android OpenGL base (a, draw a triangle quadrilateral), we simply achieve the function of drawing a triangle. You may notice that we are declaring an equilateral triangle in standard device coordinates:

class Triangle {
    // The coordinate values of the three points in the triangle
    private var triangleCoords = floatArrayOf(
        0.0 f.0.5 f.0.0 f.// top
        -0.5 f, -0.5 f.0.0 f.// bottom left
        0.5 f, -0.5 f.0.0 f      // bottom right)}Copy the code

But the actual result of the plot is:

This is because OpenGL assumes that the screen uses a uniform square coordinate system, so when drawing the standard coordinate system to a non-square screen, there will be a stretch:

So how do we make sure that the vertices are actually drawn the way we want them to be drawn, that we want equilateral triangles. To solve this problem, we can transform coordinates by applying OpenGL camera View and Projection mode so that our graphics objects will be drawn to the correct scale on any screen.

Coordinate system

After setting the object’s vertex coordinates in the program, OpenGL also needs to go through a series of coordinate transformation, and finally transform to the screen coordinates, to determine the actual drawing position of the vertex. This process requires several transformation matrices, the most important of which are Model, View and Projection matrices. Our vertex Coordinate starts with Local Coordinate, and then it’s going to be World Coordinate, View Coordinate, Clip Coordinate, And then you end up with Screen Coordinate. The following is an example to show the main work of each transformation process. Suppose we want to draw a cube and a triangular pyramid. After defining a cube and a triangular pyramid, they are actually drawn on the screen and undergo the following coordinate transformations:

2.1 Local Coordinate

First of all, we need to set the vertex position of cube and triangular pyramid. At this time, we can set the coordinates of their own vertex in their local coordinates respectively. The local coordinates of both are a standard coordinate system (-1,1), and there is no relationship between the two.

2.2 World Coordinate

After the local coordinates of each object are set, objects in the real world are scattered in different places. As shown in the figure below, the cube and triangular pyramid are placed in different positions respectively. Only at this time can they have their position relationship with each other.

The coordinates of the cube and triangular pyramid will be transformed from local to world space. The transformation is realized by Model Matrix. A model matrix is a transformation matrix that moves, scales, and rotates an object to the position or orientation it should be in. At present, Android OpenGL base (a, draw triangle quadrilateral) in the example of drawing a triangle is relatively simple, geometry is placed in the center of the world coordinates, so there is no need to specify the model matrix temporarily. Just a quick overview for now.

2.3 View Coordinate

After placing the positions of the cube and trigonal pyramid in the world coordinates, we need to set the position we want to observe. When observing the cube and trigonal pyramid in the world coordinates from different positions, we can see different looks. For example, from the position of the camera in Section 2.2, the results are as follows:

Viewing space is often referred to as OpenGL’s Camera (not to be confused with an Android phone’s Camera). The transformation from world space to observation space is usually accomplished by a series of combinations of shifts and rotations, and these combined transformations are usually stored in a View Matrix.

2.4 Clip Coordinate

After the observation matrix is set, all objects in the OpenGL world have been presented in front of our field of vision, but it is not necessary to display all objects in the actual display, so it is necessary to cut out the parts that are not displayed and ignore the clipped parts in the drawing process to reduce the amount of calculation in the drawing process. Deciding which parts can be displayed is determined by the projection matrix (because it is possible to Project 3D coordinates into a standardized device coordinate system that can be easily mapped to 2D).

To transform vertex coordinates from observation to clipping space, we need to define a Projection Matrix that specifies a range of coordinates, such as -1000 to 1000 on each dimension. The projection matrix then transforms the coordinates within this specified range to the range of normalized device coordinates (-1.0, 1.0), and the vertex coordinates outside the projection range of the matrix are clipped. The projection matrix that transforms the observed coordinates into clipping coordinates has two forms, each of which defines a different flat truncated head. We can choose to create either an Orthographic Projection Matrix or a Perspective Projection Matrix. The Viewing Box created by the projection matrix is called a Frustum, and each coordinate that appears within the Frustum range will eventually appear on the user’s screen.

2.4.1 Orthographic projection

The orthographic projection matrix defines a cube-like flatcutter box, as shown in the figure below, that defines a clipping space where all vertices outside are clipped. To create a orthographic projection matrix, you need to specify the width, height, and length of the visible flat-cut body. After the orthographic projection matrix is transformed into the clipping space, all coordinates in the body of the flat frustum will not be clipped. The green cube below will not be cropped:

2.4.2 Perspective projection

Perspective means that in real life, objects that are farther away from us look smaller.

To achieve perspective, you need to use a perspective projection matrix. In addition to giving the frustum range, the matrix modifies the w value of each vertex coordinate so that the further away from the observer the vertex coordinate w component is larger. A perspective flat frustum can be thought of as a box with an uneven shape, in which each coordinate inside the box is mapped to a point on the clipping space:

2.4.3 difference

A perspective projection is shown on the left and an orthographic projection on the right. Perspective projection is used for real life scenes. Orthographic projection is mainly used for 2d rendering and some architectural or engineering programs where it is desirable that the vertices are not disturbed by perspective.

2.5 Screen Coordinate

The final vertex should be assigned to gl_Position in the vertex shader, and OpenGL will then use arguments inside glViewPort to map standardized device coordinates to screen coordinates, each associated with a point on the phone’s screen. This process is called viewport transformation.

In the Android OpenGL basics example, the Settings in onSurfaceChanged in Section 2.1.2 tell OpenGL to map viewport transformations to screen coordinates.

class MyGLRenderer : GLSurfaceView.Renderer {
    override fun onSurfaceCreated(gl: GL10? , config:EGLConfig?). {
        // Set the background color to black
        GLES20.glClearColor(0.0 f.0.0 f.0.0 f.1.0 f)}override fun onSurfaceChanged(gl: GL10? , width:Int, height: Int) {
        // OpenGL uses standardized device coordinates;
        GLES20.glViewport(0.0, width, height)
    }

    override fun onDrawFrame(gl: GL10?). {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
    }
}
Copy the code

2.6 summarize

OpenGL creates a transformation matrix for each of these steps: the model matrix, the observation matrix, and the projection matrix. A vertex coordinate will be converted to clipping coordinates according to the following procedure:


V c l i p = M p r o j e c t i o n M v i e w M m o d e l V l o c a l = M_ V_ {clip} {the projection} ⋅ M_ {view} ⋅ M_ {model} ⋅ V_ {local}

Note that the order of matrix operations is reversed (remember we need to read matrix multiplication from right to left). The final vertex is assigned to gl_Position in the vertex shader, and OpenGL will automatically perform perspective division and cropping.

Basic usage

To draw equilateral triangles, we need to apply OpenGL camera view and projection mode to transform coordinates so that our graphics objects will be drawn to the correct scale on any screen.

3.1 Setting observation Matrix Projection matrix

First, set the projection matrix after the size of GLSurfaceView is determined or changed, set the observation matrix when executing the drawing method, and transfer the calculation results of model matrix, observation matrix and projection matrix to other objects that need to be drawn (objects in this example are in the center of the screen, not involving the model matrix for the time being) :

class MyGLRenderer : GLSurfaceView.Renderer {

    private lateinit var triangle: Triangle
    
    // mvPMatrix is an abbreviation of "Model View Projection Matrix"
    private val mvPMatrix = FloatArray(16)
    // Projection matrix
    private val projectionMatrix = FloatArray(16)
    // Observe the matrix
    private val viewMatrix = FloatArray(16)

    override fun onSurfaceCreated(gl: GL10? , config:EGLConfig?). {
        GLES20.glClearColor(0.0 f.0.0 f.0.0 f.1.0 f)
        triangle = Triangle()
    }

    override fun onSurfaceChanged(gl: GL10? , width:Int, height: Int) {
        GLES20.glViewport(0.0, width, height)
        val ratio: Float = width.toFloat() / height.toFloat()
        // Set the range of the frustum to represent the projection matrix
        Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1f.1f.3f.7f)}override fun onDrawFrame(gl: GL10?). {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
        // Set the observation matrix
        Matrix.setLookAtM(viewMatrix, 0.0f.0f.3f.0f.0f.0f.0f.1.0 f.0.0 f)
        // Calculate the projection matrix, observe the matrix transformation results, save to vPMatrix
        Matrix.multiplyMM(mvPMatrix, 0, projectionMatrix, 0, viewMatrix, 0)
        // The calculation results of model matrix, observation matrix and projection matrix vPMatrix are passed to other objects, and other objects are transformed according to the matrix
        triangle.draw(mvPMatrix)
    }
}
Copy the code

3.2 GLSL code

The code to modify the vertex shader in Triangle is as follows:

class Triangle {
    /** * Vertex shader code; * /
    private val vertexShaderCode =
    // uMVPMatrix variable is the matrix needed for vertex coordinate transformation
        // As a hook entry, used to pass in model matrix, observation matrix, projection matrix when drawing
        "uniform mat4 uMVPMatrix;" +
                "attribute vec4 vPosition;" +
                "void main() {" +
                // Pass the vPosition vertex matrix transformation to gl_Position
                " gl_Position = uMVPMatrix * vPosition;" +
                "}"
}
Copy the code

3.3 Drawing Process

Next is the triangle drawing process, the general process is consistent with the Android OpenGL foundation (a, draw triangle quadrigram), the difference is that before drawing, need to obtain the shader program uMVPMatrix variable, and the matrix value to uMVPMatrix, The process of matrix conversion is performed when vertex rendering is performed:

class Triangle {
    fun draw(mvpMatrix: FloatArray) {
        // Activate the shader program
        GLES20.glUseProgram(mProgram)
        // Get the vPosition variable in the vertex shader (it can be obtained from the shader program because the shader code has been compiled previously); It's represented by a unique ID
        val positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition")
        // Get the uMVPMatrix variable in the vertex shader code; It's represented by a unique ID
        vPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix")
        // Pass the calculation results of the model matrix, observation matrix, and projection matrix to the vPMatrixHandle vertex shader code
        GLES20.glUniformMatrix4fv(vPMatrixHandle, 1.false, mvpMatrix, 0)
        // Draw a triangle
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount)
        // Allows manipulation of the vertex object position
        GLES20.glEnableVertexAttribArray(positionHandle)
        // Pass vertex data to the vPosition variable pointed to by position
        GLES20.glVertexAttribPointer(
            positionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT,
            false, vertexStride, vertexBuffer)
        // Get the vColor variable in the fragment shader
        val colorHandle = GLES20.glGetUniformLocation(mProgram, "vColor")
        // Use colorHandle to set the color value to draw
        GLES20.glUniform4fv(colorHandle, 1, color, 0)
        // Draw the vertex array;
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount)
        // After the operation, the vertex object position is disallowed
        GLES20.glDisableVertexAttribArray(positionHandle)
    }
}
Copy the code

The drawing result is as follows:

If I change the perspective Matrix to matrix. frustumM(projectionMatrix, 0, -ratio, ratio, -1f, 1f, 4f, 7f) and run the program again, I can’t see the triangles. This is because the position of the camera observation point is matrix. setLookAtM(viewMatrix, 0, 0f, 0f, 3F, 0f, 0F, 0F, 0F, 1.0F, 0.0F), the z-axis of our triangle is 0, and the distance from the camera observation point is 3. While the near surface of the perspective matrix is 4, our triangle is no longer within the scope of the flat frustum.

3.4 Function Description

3.4.1 Projection matrix

As mentioned in Section 2.4, the Viewing Box created by the projection matrix is called a Frustum, and each coordinate appearing in the Frustum range will eventually appear on the user’s screen. Generally, the projection matrix is described by setting the range of flat truncated head. There are several ways to create it:

  • Matrix. OrthoM () : Orthographic projection
  • Matrix. PerspectiveM () : Perspective projection
  • Matrix.frustumm () : Perspective projection
/** * generate the projection matrix, output the result to the first data *@paramM: Output result *@paramOffset: output offset *@paramLeft: left * near the surface of the truncated head@paramRight: Right * near the surface of the truncated head@paramBottom: the bottom * near the surface of the head@paramTop: Top * near the truncated head@paramNear: value of the observation point of the near surface distance of the flat frustum; That is, the closest side you can see on the screen *@paramFar: value of the observation point on the far plane of the flat truncated body; The farthest side that can be seen on the screen **/
public static void frustumM(float[] m, int offset,
        float left, float right, float bottom, float top,
        float near, float far) { }
Copy the code

3.4.1 Observation matrix

/** * generates the observation matrix and prints the result to the first argument *@paramRm: Output result *@paramRmOffset: offset of the output result *@paramEyeX: The x value of the observation point *@paramEyeY: y value of the observation point *@paramEyeZ: The z value of the observation point *@paramCenterX: the x value * of the observation point@paramCenterY: X value * of the target point that the observation point looks at@paramCenterZ: The value of the observation point x *@paramUpX: X component of the upward direction of the observation point * (i.e. how to place the OpenGL camera, even if the observation point and the target point are not moving, the contents of the camera are different according to the camera placement mode, so the up vector needs to be set) *@paramUpY: y component * of the direction of the observation point@paramUpZ: Z component **/ in the direction of the observation point
public static void setLookAtM(float[] rm, int rmOffset,
        float eyeX, float eyeY, float eyeZ,
        float centerX, float centerY, float centerZ, float upX, float upY,
        float upZ) { }
Copy the code

The End

Please follow me to unlock more skills: BC’s home page ~ ~ 💐💐💐

Android OpenGL developer documentation: developer.android.com/guide/topic… IO/Android OpengL 内 容 提 要 :juejin.cn/post/707675… Android OpenGL basic (2, coordinate system) :juejin.cn/post/707713…