原生openGL Android应用入门

这篇帖子是Medium的另一篇转贴。 我前段时间决定删除我的帐户,但是在此之前,不从那里转移(并改善)我的一些帖子将是一种浪费。 这就是其中之一。

在假期(圣诞节2018)期间,我想尝试一些不同的东西。 我一生中从未做过太多移动开发工作。 但是由于我有一点时间,我认为我应该放手一搏。

但是做一些简单的事情(例如表单应用程序)有什么乐趣呢。 我想学习一些东西,因此决定不使用任何第三方库就做一个简单的OpenGL应用程序。 从头开始的一切。

请记住,很久以前,我对OpenGL开发(使用C ++)感到满意。

我四处寻找教程,在各处找到了点点滴滴,最后设法创建了想要的东西。

这些是我想要实现的目标:

  • 画一个圆倾斜手机时使圆圈移动。

这就是我的想法

这是学习两件事的绝佳机会

  • 传感器如何工作OpenGL如何在Android上工作。

Setting up the project

因此,我下载了Android Studio并创建了一个空项目。

至此,我已经阅读了一些文章和教程,因此我有些模糊的想法。 我必须创建一个视图还有一个渲染器。

My OpenGLView needs to extend GLSurfaceView.

public class OpenGLView extends GLSurfaceView {

    public OpenGLView(Context context) {
        super(context);
        init();
    }

    public OpenGLView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init(){
        setEGLContextClientVersion(2);  // OpenGL ES Version
        setPreserveEGLContextOnPause(true);
        setRenderer(new OpenGLRenderer());
    }
}

Then my OpenGLRenderer had to implement GLSurfaceView.Renderer.

public class OpenGLRenderer implements GLSurfaceView.Renderer {
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        GLES20.glClearColor(0.9f, 0.9f,0.9f,1f);
    }
   @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
    }
    @Override
    public void onDrawFrame(GL10 gl) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
    }
}

此时,Renderer除了设置背景色外没有做任何其他事情。

然后,我们需要进入布局并添加我们刚刚创建的视图。 这样view元素就会填满我们的屏幕。


        android:id="@+id/openGLView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

最后在我们的MainActivity中(我命名为MainGameActivity)应如下所示:

public class MainGameActivity extends AppCompatActivity {
private OpenGLView openGLView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_game);
        openGLView = findViewById(R.id.openGLView);
    }
    @Override
    protected void onResume(){
        super.onResume();
        openGLView.onResume();
    }
    @Override
    protected void onPause(){
        super.onPause();
        openGLView.onPause();
    }
    @Override
    public void onPointerCaptureChanged(boolean hasCapture) {
    }
}

……如果我们执行代码。 我们将获得具有我们定义的任何颜色的视图。 我定义的颜色几乎是白色,因此不应有任何颜色。

Implementing SensorEventListener

Before we draw our circle lets set up the SensorEventListener to listen to the Accelerometer. Ok, the accelerometer might not be the best sensor for what we try to achieve (cause it will only work when you are not moving) so we could switch to the gyroscope but for the time, I guess, it's fine.

public class MainGameActivity extends AppCompatActivity implements SensorEventListener {
...
    @Override
    public void onSensorChanged(SensorEvent event) {
        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
        }
    }
    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {

    }
}

加速度计有3个值

  • 值[0]影响x轴值[1]影响y轴值[2]影响z轴

我们将仅使用x和y。 我们也将这些值四舍五入到小数点后第二位,因为特别是我们不希望高精度。 我们的功能将如下所示:

@Override
    public void onSensorChanged(SensorEvent event) {
        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
          float x = Math.round(event.values[0] * 100.0) / 100f;
          float y = Math.round(event.values[1] * 100.0) / 100f;
    }
}

Drawing the circle

所以我们有一些圆的坐标。 现在我们需要使用OpenGL的功能绘制圆。

  1. 我们将创建一个圆班 with the following functions.
public class Circle {
    // basically a circle is a linestring so we need its centre
    // radius and how many segments it will consist of
    public Circle(float cx, float cy, float radius, int segments) {
    }

    // calculate the segments
    public void calculatePoints(float cx, float cy, float radius, int segments) {
    }
    // actuall openGL drawing
    public void draw() {
    }
}
  1. 在我们的圆班 we are going to add a function that compiles our shape shader. Basically shaders needs to be compiled by openGL.
public static int loadShader(int type, String shaderCode){
    int shader = GLES20.glCreateShader(type);
    GLES20.glShaderSource(shader, shaderCode);
    GLES20.glCompileShader(shader);
    return shader;
}
  1. In the Circle Class we will define two shaders (which don’t do much).
  2. Vertex Shader : for rendering the vertices of a shape.
  3. Fragment Shader : for rendering the face of a shape with colors or textures.
private final String vertexShaderCode =
        "attribute vec4 vPosition;" +
                "void main() {" +
                "  gl_Position = vPosition;" +
                "}";

private final String fragmentShaderCode =
        "precision mediump float;" +
                "uniform vec4 vColor;" +
                "void main() {" +
                "  gl_FragColor = vColor;" +
                "}";
  1. 是时候计算圆点了。 我们将这些点存储在FloatBuffer中。

只需看一下下面的代码,就会发现有些奇怪。 那是显示指标。 这里的问题是由于OpenGL画布是方形的,并且屏幕坐标的映射范围是-1到1。如果我们绘制圆,则最终会变形。 我们需要屏幕的宽度和高度来计算纵横比,以便我们可以压缩一个尺寸并产生一个实际的圆。

private FloatBuffer vertexBuffer;
private static final int COORDS_PER_VERTEX = 3;
public void CalculatePoints(float cx, float cy, float radius, int segments) {
    DisplayMetrics dm = Resources.getSystem().getDisplayMetrics();

    float[] coordinates = new float[segments * COORDS_PER_VERTEX];

    for (int i = 0; i < segments * 3; i += 3) {
        float percent = (i / (segments - 1f));
        float rad = percent * 2f * (float) Math.PI;

        //Vertex position
        float xi = cx + radius * (float) Math.cos(rad);
        float yi = cy + radius * (float) Math.sin(rad);

        coordinates[i] = xi;
        coordinates[i + 1] = yi / (((float) dm.heightPixels / (float) dm.widthPixels));
        coordinates[i + 2] = 0.0f;
    }

    // initialise vertex byte buffer for shape coordinates
    ByteBuffer bb = ByteBuffer.allocateDirect(coordinates.length * 4);
    // use the device hardware's native byte order
    bb.order(ByteOrder.nativeOrder());

    // create a floating point buffer from the ByteBuffer
    vertexBuffer = bb.asFloatBuffer();
    // add the coordinates to the FloatBuffer
    vertexBuffer.put(coordinates);
    // set the buffer to read the first coordinate
    vertexBuffer.position(0);
}
  1. 是时候实现构造函数了。 在以下情况下:

让我们只想绘制一个形状并结束。

我们不需要检查应用程序是否已初始化,但是因为我们打算在每次收到传感器事件时更新对象坐标。 我们不应多次加载着色器/应用程序/链接。

private int app = -1;
public Circle(float cx, float cy, float radius, int segments) {
    CalculatePoints(cx, cy, radius, segments);
    if (app == -1) {
        int vertexShader = OpenGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
                vertexShaderCode);
        int fragmentShader = OpenGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
                fragmentShaderCode);

        // create empty OpenGL ES Program
        app = GLES20.glCreateProgram();

        // add the vertex shader to program
        GLES20.glAttachShader(app, vertexShader);

        // add the fragment shader to program
        GLES20.glAttachShader(app, fragmentShader);

        // creates OpenGL ES program executables
        GLES20.glLinkProgram(app);
    }
}
  1. The draw function
public void draw() {

    int vertexCount = vertexBuffer.remaining() / COORDS_PER_VERTEX;

    // Add program to the environment
    GLES20.glUseProgram(app);

    // get handle to vertex shader's vPosition member
    int mPositionHandle = GLES20.glGetAttribLocation(app, "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(app, "vColor");

    // Draw the triangle, using triangle fan is the easiest way
    GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, vertexCount);

    // Disable vertex array
    GLES20.glDisableVertexAttribArray(mPositionHandle);

    // Set color of the shape (circle)
    GLES20.glUniform4fv(mColorHandle, 1, new float[]{0.5f, 0.3f, 0.1f, 1f}, 0);
}
  1. 最后,我们回到渲染器并添加一个新的圆对象。 我们将首先在x = 0,y = 0,半径= 0.1且线段= 55的情况下绘制圆。对象就绪* switch will come in handy when we will start updating the object from the sensor events.
public class OpenGLRenderer implements GLSurfaceView.Renderer {
    private Circle circle;
    public boolean objectsReady = false;
    public Circle getCircle() {
        return circle;
    }
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        GLES20.glClearColor(0.9f, 0.9f,0.9f,1f);
        circle = new Circle(0,0, 0.1f, 55);
        objectsReady = true;
    }
...
}

在这一点上,如果我们运行我们的应用程序,我们应该在屏幕中间出现一个棕色的圆圈。 所以我们onSensorChanged会变成。规模用于将我们需要的传感器数据(-4,4)映射到OpenGL视图(-1,1)。

private final static int SCALE = 4;
@Override
    public void onSensorChanged(SensorEvent event) {
        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
          float x = Math.round(event.values[0] * 100.0) / 100f;
          float y = Math.round(event.values[1] * 100.0) / 100f;
if (openGLView.renderer.objectsReady) {
              openGLView.renderer.getCircle().CalculatePoints(x /    SCALE, y / SCALE, 0.1f, 55);
              openGLView.requestRender();
          }
    }
}

最终,我们的圈子还活着并且很好,但是它的动作有些紧张。

Normalise sensor data

SensorEventListener每秒可以触发数千个事件,并且具有很高的准确性,因此,为了使移动平滑,我们需要使用某种统计方法对数据进行规范化。 这个问题的明显选择(至少对我来说是显而易见的)是使用移动平均线。

移动平均值不过是x个最近读数的平均值。 这很容易做到。 我们只需要向我们添加以下内容主要活动。

private final static int OVERFLOW_LIMIT = 20;
private float[][] movingAverage = new float[2][OVERFLOW_LIMIT];
@Override
public void onSensorChanged(SensorEvent event) {
    if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
        float x = Math.round(event.values[0] * 100.0) / 100f;
        float y = Math.round(event.values[1] * 100.0) / 100f;

        movingAverage[0][overflow] = x;
        movingAverage[1][overflow] = y;

        float s1 = calculateAverage(movingAverage[0]);
        float s2 = calculateAverage(movingAverage[1]);

        if (openGLView.renderer.objectsReady) {
            openGLView.renderer.getCircle().CalculatePoints(s1 / SCALE, s2 / SCALE, 0.1f, 55);
            openGLView.requestRender();
         }
    }
    overflow += 1;
    if (overflow >= OVERFLOW_LIMIT) {
        overflow = 0;
    }
}
private float calculateAverage(float[] input) {
    DoubleStream io = IntStream.range(0, input.length)
            .mapToDouble(i -> input[i]);
    float sum = (float)io.sum();
    return sum/OVERFLOW_LIMIT;
}

再次运行该应用程序,现在我们的形状运动更加流畅。

from: https://dev.to//elasticrash/getting-started-with-native-opengl-android-app-19e7

你可能感兴趣的:(原生openGL Android应用入门)