OpenGL ESglClearColor()를 이용하여 배경색을 칠하는 간단한 테스트입니다.

 

glClearColor 함수 정의는 아래와 같습니다.

void glClearColor(           GLfloat red,
          GLfloat green,
          GLfloat blue,
          GLfloat alpha);

 

초기값은 모두 0이며 값을 넣어주면 Color Buffers를 입력한 값으로 채워서 색상을 표시합니다. 값의 범위는 0~1 사이 값을 입력합니다.

 

혼자 사용하지 않고 glClear() 함수와 함께 사용해야 반영됩니다.

void glClear(GLbitfield mask);

 

glClear()mask 값은 아래에 있는 값을 사용합니다.

 

GL_COLOR_BUFFER_BIT 입력된 color buffer 표시
GL_DEPTH_BUFFER_BIT 입력된 depth buffer 표시
GL_STENCIL_BUFFER_BIT 입력된 stencil buffer 표시

 

사용되는 목적에 따라 골라서 사용하면 됩니다.

 

 

예제를 만들기 위해 사용되는 파일은 설정파일 1개와 java 파일 3개입니다.

AndroidManifest.xml

MainActivity.java

MyGLRenderer.java

MyGlSurfaceView.java

 

다른 파일들은 수정하지 않기 때문에 4개의 파일만 설명합니다.

 

최종 결과물은 아래와 같은 화면이 나옵니다.

 

 

 

 

AndroidManifest.xml 파일에 사용하는 OpenGL ES 버전을 기록합니다.

 

<uses-feature android:glEsVersion="0x00020000" android:required="true" />

 

0x00020000 을 입력하여 OpenGL ES 2.0 버전을 사용할 수 있도록 합니다.

 

 

MyGLRenderer.java 파일을 생성하고 GLSurfaceView.Renderer를 상속받습니다.

 

 

 

 

실제 이미지를 그리는 파일로 3개의 기본 함수를 추가합니다.

 

 

@Override

    public void onSurfaceCreated(GL10 gl, EGLConfig config) {

        GLES20.glClearColor(1.0f, 0.0f, 0.0f, .2f); //red

    }



    @Override

    public void onSurfaceChanged(GL10 gl, int width, int height) {

        GLES20.glViewport(0, 0, width, height);

    }



    @Override

    public void onDrawFrame(GL10 gl) {

        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);



    }

 

 

onSurfaceCreated() : 최초 생성시 설정 값을 정하는 함수로 빨간색을 버터에 넣습니다.

onSurfaceChanged() : 변경사항이 발생할 때마다 호출되며 변경작업을 진행하는 함수입니다.

onDrawFrame() : 변경된 부분을 새로 그리는 함수입니다.

최초 빨강색을 화면가득 채워서 보여주는 작업이 계속 진행 됩니다.

 

이제 화면을 그릴 GLSurfaceView를 상속받은 MyGlSurfaceView.java 파일을 생성합니다.

 

 

 

 

그리고 Context를 받는 생성자를 하나 만들어 줍니다.

 

private MyGLRenderer myGLRenderer;
public MyGlSurfaceView(Context context) {
   
super(context);

    setEGLContextClientVersion(
2);
   
myGLRenderer = new MyGLRenderer();
    setRenderer(
myGLRenderer);
    setRenderMode(GLSurfaceView.
RENDERMODE_WHEN_DIRTY);
}

 

setEGLContextClientVersion(2) : OpenGL ES 2.0 context 생성합니다.

setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY):surface 생성되었을 렌더링을 합니다.

 

MainActivityh.java에서 모바일 화면에 보여주는 작업만 하면 됩니다.

 

 

GLSurfaceView glSurfaceView;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

//        setContentView(R.layout.activity_main);



        glSurfaceView = new MyGlSurfaceView(this);

        setContentView(glSurfaceView);

    }

 

기존 layout을 사용하지 않고 새로 생성한 MyGlSurfaceView를 화면에 보여주면 완료됩니다.

 

- copy coding -

 

 

이전 글에서 Google에서 학습을 위해 제공하는 OpenGL ES 3.0 관련 소스를 이용하여 삼각형과 사각형을 그려보았습니다.

 

Android OpenGL ES 3.0 삼각형 그리기(java 버전)

 


Android OpenGL ES 3.0 사각형 그리기(java 버전)

 

 

오늘은 직육면체를 그려보려고 하는데 소스는 구글 개발자 사이트에서 찾을 수 없어 인터넷을 찾아보다가 https://github.com/JimSeker/opengl 에서 적당한 소스를 찾아 소개를 드립니다.

프로그램 구성이 구글에서 설명하는 방식과 거의 동일한 구조로 되어있어서 이해하기가 쉬울것 같아 선택했습니다.

 

최종 구현 모습은 여러 곳에서 한번쯤 본듯한 형태입니다.

 

 

 

 

사각형을 그릴때 설명한것 처럼 삼각형을 2개 연결해서 사각형을 그렸습니다.  이번에도 삼각형을 그려서 사각형을 만들고 이 사각형들을 모아서 6면체를 만들게 됩니다.

사용되는 java 프로그램 소스는 처음에 삼각형 그리기 편에서 설명했던 내용이 변함 없이 적용 됩니다.  기본이되는 2개의 클래스가 있는데 이 클래스에 대한 자세한 설명은 삼각형 그리기를 참고하면 되고 여기서는 간략하게 어떤 내용이 있었는지 제목 정도만 알아보겠습니다.

 

GLSurfaceView 클래스 : 객체를 그리고 조작할 수 있는 View 이며 터치 리스너를 구현할 수 있습니다.

 

GLSurfaceView.Renderer 클래스 : 3개의 기본 메서드를 구현해야합니다.

  - onSurfaceCreated() : 최초 한번 호출되며 초기화가 필요한 내용 기술.

  - onDrawFrame() : GLSurfaceView를 다시 그릴때마다 호출되며 Object를 움직이거나 변형 가능.

  - onSurfaceChanged() : GLSurfaceView의 크기, 방향 변경시 호출된다.

 

 

다른 도형과 다르게 Cube 그리기에서 추가되는 부분이 있는데 터치 관련 기능입니다.  GLSurfaceVIew 클래스 설명에 터치 이벤트를 구현한다고 되어있는것 처럼 GLSurfaceView 클래스에 onTouchEvent()가 추가 됩니다.  구현할 때

 

private static final float TOUCH_SCALE_FACTOR = 0.0015f;

 

이 값을 변경하면 화면을 터치하고 Object를 이동 시킬때 한번에 이동하는 거리를 조절할 수 있습니다.

 

Renderer 프로그램에는 onDrawFrame()에서 물체의 변형이나 움직임을 구현할 수 있다고 하였는데 여기서는 육면체를 회전시키는 작업이 추가됩니다.  코드에서

 

mAngle+=4;

 

이 각도값을 변경하면 회전하는 속도를 조절할 수 있습니다.

 

myColor.java 라는 파일이 하나 추가되었는데 삼각형, 사각형은 2차원 이미지라 색상 값을 그냥 코드에 추가를 하였는데 정육면체는 6개의 색을 표현하기 위해 별도의 파일로 만들어 놓았습니다. 귀찮으면 그냥 파일을 만들지 말고 구현하거나 단색으로 테스트해도 됩니다.

설명을 이정도로 하고 실행 했을 때의 모습을 보겠습니다.

 

 

 

 

아래는 전체 코드입니다.  수정하지 않은 파일을 첨부하지 않았습니다.

위에서 소개한 페이지로 가서 소스를 다운받아 테스트 하셔도 됩니다.

 

AndroidManifest.xml

 

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools">



    <uses-feature android:glEsVersion="0x00030000" android:required="true" />



    <application

        android:allowBackup="true"

        android:dataExtractionRules="@xml/data_extraction_rules"

        android:fullBackupContent="@xml/backup_rules"

        android:icon="@mipmap/ic_launcher"

        android:label="@string/app_name"

        android:roundIcon="@mipmap/ic_launcher_round"

        android:supportsRtl="true"

        android:theme="@style/Theme.OpenGLTest"

        tools:targetApi="31">

        <activity

            android:name=".MainActivity"

            android:exported="true">

            <intent-filter>

                <action android:name="android.intent.action.MAIN" />



                <category android:name="android.intent.category.LAUNCHER" />

            </intent-filter>

        </activity>

    </application>



</manifest>

 

 

 

 

myGlSurfaceView.java

 

package com.example.opengltest;



import android.content.Context;

import android.opengl.GLSurfaceView;

import android.view.MotionEvent;



public class myGlSurfaceView extends GLSurfaceView {



    myRenderer myRender;



    public myGlSurfaceView(Context context) {

        super(context);



        // Create an OpenGL ES 3.0 context.

        setEGLContextClientVersion(3);



        super.setEGLConfigChooser(8, 8, 8, 8, 16, 0);



        // Set the Renderer for drawing on the GLSurfaceView

        myRender = new myRenderer(context);

        setRenderer(myRender);



        // Render the view only when there is a change in the drawing data

        setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);

    }



    //private final float TOUCH_SCALE_FACTOR = 180.0f / 320;

    private static final float TOUCH_SCALE_FACTOR = 0.0015f;

    private float mPreviousX;

    private float mPreviousY;



    @Override

    public boolean onTouchEvent(MotionEvent e) {

//        return super.onTouchEvent(event);



        float x = e.getX();

        float y = e.getY();



        switch (e.getAction()) {

            case MotionEvent.ACTION_MOVE:



                float dx = x - mPreviousX;

                //subtract, so the cube moves the same direction as your finger.

                //with plus it moves the opposite direction.

                myRender.setX(myRender.getX() - (dx * TOUCH_SCALE_FACTOR));



                float dy = y - mPreviousY;

                myRender.setY(myRender.getY() - (dy * TOUCH_SCALE_FACTOR));

        }



        mPreviousX = x;

        mPreviousY = y;

        return true;

    }

}

 

 

 

 

myRenderer.java

 

package com.example.opengltest;



import android.content.Context;

import android.opengl.GLES30;

import android.opengl.GLSurfaceView;

import android.opengl.Matrix;

import android.util.Log;



import javax.microedition.khronos.egl.EGLConfig;

import javax.microedition.khronos.opengles.GL10;



public class myRenderer implements GLSurfaceView.Renderer {



    private int mWidth;

    private int mHeight;

    private static String TAG = "myRenderer";

    public Cube mCube;

    private float mAngle =0;

    private float mTransY=0;

    private float mTransX=0;

    private static final float Z_NEAR = 1f;

    private static final float Z_FAR = 40f;



    // 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];

    private final float[] mRotationMatrix = new float[16];





    public myRenderer(Context context) {

        //cube can not be instianated here, because of "no egl context"  no clue.

        //do it in onSurfaceCreate and it is fine.  odd, but workable solution.

    }

    public static int LoadShader(int type, String shaderSrc) {

        int shader;

        int[] compiled = new int[1];



        // Create the shader object

        shader = GLES30.glCreateShader(type);



        if (shader == 0) {

            return 0;

        }



        // Load the shader source

        GLES30.glShaderSource(shader, shaderSrc);



        // Compile the shader

        GLES30.glCompileShader(shader);



        // Check the compile status

        GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, compiled, 0);



        if (compiled[0] == 0) {

            Log.e(TAG, "Erorr!!!!");

            Log.e(TAG, GLES30.glGetShaderInfoLog(shader));

            GLES30.glDeleteShader(shader);

            return 0;

        }



        return shader;

    }



    public static void checkGlError(String glOperation) {

        int error;

        while ((error = GLES30.glGetError()) != GLES30.GL_NO_ERROR) {

            Log.e(TAG, glOperation + ": glError " + error);

            throw new RuntimeException(glOperation + ": glError " + error);

        }

    }



    @Override

    public void onSurfaceCreated(GL10 gl, EGLConfig config) {



        //set the clear buffer color to light gray.

        //GLES30.glClearColor(0.9f, .9f, 0.9f, 0.9f);

        //set the clear buffer color to a dark grey.

        GLES30.glClearColor(0.1f, .1f, 0.1f, 0.9f);

        //initialize the cube code for drawing.

        mCube = new Cube();

        //if we had other objects setup them up here as well.

    }



    @Override

    public void onSurfaceChanged(GL10 gl, int width, int height) {



        mWidth = width;

        mHeight = height;

        // Set the viewport

        GLES30.glViewport(0, 0, mWidth, mHeight);

        float aspect = (float) width / height;



        // this projection matrix is applied to object coordinates

        //no idea why 53.13f, it was used in another example and it worked.

        Matrix.perspectiveM(mProjectionMatrix, 0, 53.13f, aspect, Z_NEAR, Z_FAR);

    }



    @Override

    public void onDrawFrame(GL10 gl) {



        // Clear the color buffer  set above by glClearColor.

        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);



        //need this otherwise, it will over right stuff and the cube will look wrong!

        GLES30.glEnable(GLES30.GL_DEPTH_TEST);



        // Set the camera position (View matrix)  note Matrix is an include, not a declared method.

        Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);



        // Create a rotation and translation for the cube

        Matrix.setIdentityM(mRotationMatrix, 0);



        //move the cube up/down and left/right

        Matrix.translateM(mRotationMatrix, 0, mTransX, mTransY, 0);



        //mangle is how fast, x,y,z which directions it rotates.

        Matrix.rotateM(mRotationMatrix, 0, mAngle, 1.0f, 1.0f, 1.0f);



        // combine the model with the view matrix

        Matrix.multiplyMM(mMVPMatrix, 0, mViewMatrix, 0, mRotationMatrix, 0);



        // combine the model-view with the projection matrix

        Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0);



        mCube.draw(mMVPMatrix);



        //change the angle, so the cube will spin.

        mAngle+=4;

    }



    //used the touch listener to move the cube up/down (y) and left/right (x)

    public float getY() {

        return mTransY;

    }



    public void setY(float mY) {

        mTransY = mY;

    }



    public float getX() {

        return mTransX;

    }



    public void setX(float mX) {

        mTransX = mX;

    }

}

 

 

Cube.java

 

package com.example.opengltest;



import android.opengl.GLES30;

import android.util.Log;



import java.nio.ByteBuffer;

import java.nio.ByteOrder;

import java.nio.FloatBuffer;



public class Cube {



    private int mProgramObject;

    private int mMVPMatrixHandle;

    private int mColorHandle;

    private FloatBuffer mVertices;



    //initial size of the cube.  set here, so it is easier to change later.

    float size = 0.4f;



    //this is the initial data, which will need to translated into the mVertices variable in the consturctor.

    float[] mVerticesData = new float[]{

            ////////////////////////////////////////////////////////////////////

            // FRONT

            ////////////////////////////////////////////////////////////////////

            // Triangle 1

            -size, size, size, // top-left

            -size, -size, size, // bottom-left

            size, -size, size, // bottom-right

            // Triangle 2

            size, -size, size, // bottom-right

            size, size, size, // top-right

            -size, size, size, // top-left

            ////////////////////////////////////////////////////////////////////

            // BACK

            ////////////////////////////////////////////////////////////////////

            // Triangle 1

            -size, size, -size, // top-left

            -size, -size, -size, // bottom-left

            size, -size, -size, // bottom-right

            // Triangle 2

            size, -size, -size, // bottom-right

            size, size, -size, // top-right

            -size, size, -size, // top-left



            ////////////////////////////////////////////////////////////////////

            // LEFT

            ////////////////////////////////////////////////////////////////////

            // Triangle 1

            -size, size, -size, // top-left

            -size, -size, -size, // bottom-left

            -size, -size, size, // bottom-right

            // Triangle 2

            -size, -size, size, // bottom-right

            -size, size, size, // top-right

            -size, size, -size, // top-left

            ////////////////////////////////////////////////////////////////////

            // RIGHT

            ////////////////////////////////////////////////////////////////////

            // Triangle 1

            size, size, -size, // top-left

            size, -size, -size, // bottom-left

            size, -size, size, // bottom-right

            // Triangle 2

            size, -size, size, // bottom-right

            size, size, size, // top-right

            size, size, -size, // top-left



            ////////////////////////////////////////////////////////////////////

            // TOP

            ////////////////////////////////////////////////////////////////////

            // Triangle 1

            -size, size, -size, // top-left

            -size, size, size, // bottom-left

            size, size, size, // bottom-right

            // Triangle 2

            size, size, size, // bottom-right

            size, size, -size, // top-right

            -size, size, -size, // top-left

            ////////////////////////////////////////////////////////////////////

            // BOTTOM

            ////////////////////////////////////////////////////////////////////

            // Triangle 1

            -size, -size, -size, // top-left

            -size, -size, size, // bottom-left

            size, -size, size, // bottom-right

            // Triangle 2

            size, -size, size, // bottom-right

            size, -size, -size, // top-right

            -size, -size, -size // top-left

    };



    float colorcyan[] = myColor.cyan();

    float colorblue[] = myColor.blue();

    float colorred[] = myColor.red();

    float colorgray[] = myColor.gray();

    float colorgreen[] = myColor.green();

    float coloryellow[] = myColor.yellow();



    //vertex shader code

    String vShaderStr =

            "#version 300 es           \n"

                    + "uniform mat4 uMVPMatrix;     \n"

                    + "in vec4 vPosition;           \n"

                    + "void main()                  \n"

                    + "{                            \n"

                    + "   gl_Position = uMVPMatrix * vPosition;  \n"

                    + "}                            \n";

    //fragment shader code.

    String fShaderStr =

            "#version 300 es                          \n"

                    + "precision mediump float;                   \n"

                    + "uniform vec4 vColor;                      \n"

                    + "out vec4 fragColor;                   \n"

                    + "void main()                                  \n"

                    + "{                                            \n"

                    + "  fragColor = vColor;                       \n"

                    + "}                                            \n";



    String TAG = "Cube";





    //finally some methods

    //constructor

    public Cube() {

        //first setup the mVertices correctly.

        mVertices = ByteBuffer

                .allocateDirect(mVerticesData.length * 4)

                .order(ByteOrder.nativeOrder())

                .asFloatBuffer()

                .put(mVerticesData);

        mVertices.position(0);



        //setup the shaders

        int vertexShader;

        int fragmentShader;

        int programObject;

        int[] linked = new int[1];



        // Load the vertex/fragment shaders

        vertexShader = myRenderer.LoadShader(GLES30.GL_VERTEX_SHADER, vShaderStr);

        fragmentShader = myRenderer.LoadShader(GLES30.GL_FRAGMENT_SHADER, fShaderStr);



        // Create the program object

        programObject = GLES30.glCreateProgram();



        if (programObject == 0) {

            Log.e(TAG, "So some kind of error, but what?");

            return;

        }



        GLES30.glAttachShader(programObject, vertexShader);

        GLES30.glAttachShader(programObject, fragmentShader);



        // Bind vPosition to attribute 0

        GLES30.glBindAttribLocation(programObject, 0, "vPosition");



        // Link the program

        GLES30.glLinkProgram(programObject);



        // Check the link status

        GLES30.glGetProgramiv(programObject, GLES30.GL_LINK_STATUS, linked, 0);



        if (linked[0] == 0) {

            Log.e(TAG, "Error linking program:");

            Log.e(TAG, GLES30.glGetProgramInfoLog(programObject));

            GLES30.glDeleteProgram(programObject);

            return;

        }



        // Store the program object

        mProgramObject = programObject;



        //now everything is setup and ready to draw.

    }



    public void draw(float[] mvpMatrix) {



        // Use the program object

        GLES30.glUseProgram(mProgramObject);



        // get handle to shape's transformation matrix

        mMVPMatrixHandle = GLES30.glGetUniformLocation(mProgramObject, "uMVPMatrix");

        myRenderer.checkGlError("glGetUniformLocation");



        // get handle to fragment shader's vColor member

        mColorHandle = GLES30.glGetUniformLocation(mProgramObject, "vColor");





        // Apply the projection and view transformation

        GLES30.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);

        myRenderer.checkGlError("glUniformMatrix4fv");



        int VERTEX_POS_INDX = 0;

        mVertices.position(VERTEX_POS_INDX);  //just in case.  We did it already though.



        //add all the points to the space, so they can be correct by the transformations.

        //would need to do this even if there were no transformations actually.

        GLES30.glVertexAttribPointer(VERTEX_POS_INDX, 3, GLES30.GL_FLOAT,

                false, 0, mVertices);

        GLES30.glEnableVertexAttribArray(VERTEX_POS_INDX);



        //Now we are ready to draw the cube finally.

        int startPos =0;

        int verticesPerface = 6;



        //draw front face

        GLES30.glUniform4fv(mColorHandle, 1, colorblue, 0);

        GLES30.glDrawArrays(GLES30.GL_TRIANGLES,startPos,verticesPerface);

        startPos += verticesPerface;



        //draw back face

        GLES30.glUniform4fv(mColorHandle, 1, colorcyan, 0);

        GLES30.glDrawArrays(GLES30.GL_TRIANGLES, startPos, verticesPerface);

        startPos += verticesPerface;



        //draw left face

        GLES30.glUniform4fv(mColorHandle, 1, colorred, 0);

        GLES30.glDrawArrays(GLES30.GL_TRIANGLES,startPos,verticesPerface);

        startPos += verticesPerface;



        //draw right face

        GLES30.glUniform4fv(mColorHandle, 1, colorgray, 0);

        GLES30.glDrawArrays(GLES30.GL_TRIANGLES,startPos,verticesPerface);

        startPos += verticesPerface;



        //draw top face

        GLES30.glUniform4fv(mColorHandle, 1, colorgreen, 0);

        GLES30.glDrawArrays(GLES30.GL_TRIANGLES,startPos,verticesPerface);

        startPos += verticesPerface;



        //draw bottom face

        GLES30.glUniform4fv(mColorHandle, 1, coloryellow, 0);

        GLES30.glDrawArrays(GLES30.GL_TRIANGLES,startPos,verticesPerface);

        //last face, so no need to increment.



    }

}

 

 

 

 

myColor.java

 

package com.example.opengltest;



import android.graphics.Color;



public class myColor {



    static float[] red() {

        return new float[]{

                Color.red(Color.RED) / 255f,

                Color.green(Color.RED) / 255f,

                Color.blue(Color.RED) / 255f,

                1.0f

        };

    }



    static float[] green() {

        return new float[]{

                Color.red(Color.GREEN) / 255f,

                Color.green(Color.GREEN) / 255f,

                Color.blue(Color.GREEN) / 255f,

                1.0f

        };

    }



    static float[] blue() {

        return new float[]{

                Color.red(Color.BLUE) / 255f,

                Color.green(Color.BLUE) / 255f,

                Color.blue(Color.BLUE) / 255f,

                1.0f

        };

    }



    static float[] yellow() {

        return new float[]{

                Color.red(Color.YELLOW) / 255f,

                Color.green(Color.YELLOW) / 255f,

                Color.blue(Color.YELLOW) / 255f,

                1.0f

        };

    }



    static float[] cyan() {

        return new float[]{

                Color.red(Color.CYAN) / 255f,

                Color.green(Color.CYAN) / 255f,

                Color.blue(Color.CYAN) / 255f,

                1.0f

        };

    }



    static float[] gray() {

        return new float[]{

                Color.red(Color.GRAY) / 255f,

                Color.green(Color.GRAY) / 255f,

                Color.blue(Color.GRAY) / 255f,

                1.0f

        };

    }

}

 

 

MainActivity.java

 

package com.example.opengltest;



import androidx.appcompat.app.AppCompatActivity;



import android.app.ActivityManager;

import android.content.Context;

import android.content.pm.ConfigurationInfo;

import android.os.Bundle;

import android.util.Log;

import android.view.View;



public class MainActivity extends AppCompatActivity {



    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

//        setContentView(R.layout.activity_main);



        if (detectOpenGLES30()) {

            //so we know it a opengl 3.0 and use our extended GLsurfaceview.

            setContentView(new myGlSurfaceView(this));

        } else {

            // This is where you could create an OpenGL ES 2.0 and/or 1.x compatible

            // renderer if you wanted to support both ES 1 and ES 2.

            Log.e("openglcube", "OpenGL ES 3.0 not supported on device.  Exiting...");

            finish();



        }

    }



    private boolean detectOpenGLES30() {

        ActivityManager am =

                (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);

        ConfigurationInfo info = am.getDeviceConfigurationInfo();

        return (info.reqGlEsVersion >= 0x30000);

    }



    @Override

    public void onWindowFocusChanged(boolean hasFocus) {

        super.onWindowFocusChanged(hasFocus);



        if (hasFocus) {

            getWindow().getDecorView().setSystemUiVisibility(

                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE

                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION

                            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

                            | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION

                            | View.SYSTEM_UI_FLAG_FULLSCREEN

                            | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);

        }

    }

}

 

 - copy coding -

 

Android에서 OpenGL ES 3.0 버전을 이용하여 사각형을 그리는 예제입니다.

Google developer 사이트에서 OPenGL을 이용하여 Android에서 삼각형과 사각형 도형을 그리기 위한 설명을 하는 페이지를 간단히 요약하여 소개하고 제공하는 소스를 이용하여 도형을 제작하는 방법까지 바로 이전 글에서 삼각형 관련된 예제로 알아보았습니다.

 


Android OpenGL ES 3.0
삼각형 그리기(java 버전)



 

그래서 자세한 설명은 이전 글을 참고하시기 바라며 이번에는 동일한 소스를 사용하여 사각형을 그리는 부분만 추가하여 테스트를 진행합니다.

삼각형을 그릴때는 Triangle.java 파일 만들었는데 사각형을 그리기 위해서 Square.java 파일을 생성하는데 일부 변경이 되고 Renderer 파일도 조금 수정해주면 됩니다.

 

다른 소스는 전에 생성한 내용을 그대로 사용하고 여기서는 SquareRenderer 파일의 변경된 부분만 설명합니다.

 

실행했을 때 결과화면은 아래와 같이 멋없는 사각형입니다.

 

 

 

 

Square.java 파일을 생성하고 사각형 용으로 도형의 좌표를 표기합니다.

 

 

static float squareCoords[] = {
            -0.5f,  0.5f, 0.0f,   // top left
            -0.5f, -0.5f, 0.0f,   // bottom left
             0.5f, -0.5f, 0.0f,   // bottom right
             0.5f,  0.5f, 0.0f }; // top right

 

그림에 있는 좌표를 표시하고 그리는 순서를 정하여 꼭지점의 순서를 정하게 됩니다.

특이한점은 사각형을 사각형으로 그리지 않고 3각형 2개를 그리는 방법으로 진행합니다.

private final short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices

이렇게 시계 반대방향으로 순서를 나열하여 좌측과 우측의 삼각형을 그리도록 정의합니다.

 

// initialize byte buffer for the draw list
        ByteBuffer dlb = ByteBuffer.allocateDirect(
                // (# of coordinate values * 2 bytes per short)
                drawOrder.length * 2);
        dlb.order(ByteOrder.nativeOrder());
        drawListBuffer = dlb.asShortBuffer();
        drawListBuffer.put(drawOrder);
        drawListBuffer.position(0);

 

이런 작업 순서는 ByteBuffer에 저장해 둡니다.

나머지 소스들은 삼각형일때 triangleCoords로 되어있던 부분을 squareCoords로 변경합니다.

 

draw() 함수에서 삼각형은 꼭지점을 이용하여 그리고 사각형은 Buffer에 있는 순서를 이용하여 그리도록 수정됩니다.

 

// Draw the triangle
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
 
// Draw the square
GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length,
                GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
 

 

 

MyGLRenderer.java 파일에서는 MVP Matrix가 사용됩니다.

MVP(Model, View, Projection)은 카메라에서 모델을 바라보는 형상을 표시하는 것인데

삼각형에서는 사용하지 않았는데 사각형에서는 왜 필요한지 지식이 짧아 좀더 학습이 필요하네요.

 

@Override
    public void onDrawFrame(GL10 gl) {
        float[] scratch = new float[16];
        // Draw background color
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);
        // Set the camera position (View matrix)
        Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
        // Calculate the projection and view transformation
        Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);
        // Draw square
        mSquare.draw(mMVPMatrix);
    }

 

그리는 부분에서 삼각형은 Array, 사각형은 Element의 차이인데 나중에 알게되면 내용 추가를 해야 할 것 같습니다.

수정되는 부분이 많지 않아 설명이 짧게 끝났습니다.

 

아래는 전체 소스입니다.

 

AndroidManifest.xml

 

<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
   
xmlns:tools="http://schemas.android.com/tools">

    <uses-feature
android:glEsVersion="0x00030000" android:required="true" />

    <application
       
android:allowBackup="true"
       
android:dataExtractionRules="@xml/data_extraction_rules"
       
android:fullBackupContent="@xml/backup_rules"
       
android:icon="@mipmap/ic_launcher"
       
android:label="@string/app_name"
       
android:roundIcon="@mipmap/ic_launcher_round"
       
android:supportsRtl="true"
       
android:theme="@style/Theme.OpenGLTriangle"
       
tools:targetApi="31">
        <activity
           
android:name=".MainActivity"
           
android:exported="true">
            <intent-filter>
                <action
android:name="android.intent.action.MAIN" />

                <category
android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

 

 

MainActivity.java

 

package com.copycoding.opengltriangle;

import
androidx.appcompat.app.AppCompatActivity;

import
android.opengl.GLSurfaceView;
import
android.os.Bundle;

public class
MainActivity extends AppCompatActivity {
   
private GLSurfaceView gLView;

   
@Override
   
protected void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        // Create a GLSurfaceView instance and set it
        // as the ContentView for this Activity.
       
gLView = new MyGLSurfaceView(this);
       
setContentView(gLView);
   
}
}

 

 

 

MyGLRenderer.java

  

package com.copycoding.openglsquare;



import android.opengl.GLES30;

import android.opengl.GLSurfaceView;

import android.opengl.Matrix;

import android.util.Log;



import javax.microedition.khronos.egl.EGLConfig;

import javax.microedition.khronos.opengles.GL10;



public class MyGLRenderer implements GLSurfaceView.Renderer {



//    private Triangle mTriangle;

    private Square mSquare;



    private static final String TAG = "MyGLRenderer";

    private final float[] mMVPMatrix = new float[16];

    private final float[] mProjectionMatrix = new float[16];

    private final float[] mViewMatrix = new float[16];

    private final float[] mRotationMatrix = new float[16];

    private float mAngle;



    @Override

    public void onSurfaceCreated(GL10 gl, EGLConfig config) {

        // Set the background frame color

        GLES30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

        // initialize a triangle

        mSquare = new Square();

    }



    @Override

    public void onSurfaceChanged(GL10 gl, int width, int height) {

        GLES30.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);

    }



    @Override

    public void onDrawFrame(GL10 gl) {

        float[] scratch = new float[16];

        // Draw background color

        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);

        // Set the camera position (View matrix)

        Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

        // Calculate the projection and view transformation

        Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);

        // Draw square

        mSquare.draw(mMVPMatrix);

    }

    public static int loadShader(int type, String shaderCode){



        // create a vertex shader type (GLES20.GL_VERTEX_SHADER)

        // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)

        int shader = GLES30.glCreateShader(type);



        // add the source code to the shader and compile it

        GLES30.glShaderSource(shader, shaderCode);

        GLES30.glCompileShader(shader);



        return shader;

    }



    public static void checkGlError(String glOperation) {

        int error;

        while ((error = GLES30.glGetError()) != GLES30.GL_NO_ERROR) {

            Log.e(TAG, glOperation + ": glError " + error);

            throw new RuntimeException(glOperation + ": glError " + error);

        }

    }

}

 

 

 

MyGLSurfaceView.java

 

package com.copycoding.opengltriangle;

import
android.content.Context;
import
android.opengl.GLSurfaceView;

public class
MyGLSurfaceView extends GLSurfaceView {

   
private final MyGLRenderer renderer;
    public
MyGLSurfaceView(Context context) {
       
super(context);

       
// Create an OpenGL ES 2.0 context
       
setEGLContextClientVersion(2);

       
renderer = new MyGLRenderer();

       
// Set the Renderer for drawing on the GLSurfaceView
       
setRenderer(renderer);
   
}
}

 

 

Square.java

 

package com.copycoding.openglsquare;



import android.opengl.GLES30;



import java.nio.ByteBuffer;

import java.nio.ByteOrder;

import java.nio.FloatBuffer;

import java.nio.ShortBuffer;



public class Square {



    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 FloatBuffer vertexBuffer;

    private ShortBuffer drawListBuffer;



    // number of coordinates per vertex in this array

    static final int COORDS_PER_VERTEX = 3;

    static float squareCoords[] = {

            -0.5f,  0.5f, 0.0f,   // top left

            -0.5f, -0.5f, 0.0f,   // bottom left

             0.5f, -0.5f, 0.0f,   // bottom right

             0.5f,  0.5f, 0.0f    // top right

    };

    private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices



    // Set color with red, green, blue and alpha (opacity) values

    float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };



    private final int mProgram;



    private int positionHandle;

    private int colorHandle;

    private int mVPMatrixHandle;



//    private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;

    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex





    public Square() {

        // initialize vertex byte buffer for shape coordinates

        ByteBuffer bb = ByteBuffer.allocateDirect(

                // (number of coordinate values * 4 bytes per float)

                squareCoords.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(squareCoords);

        // set the buffer to read the first coordinate

        vertexBuffer.position(0);



        // initialize byte buffer for the draw list

        ByteBuffer dlb = ByteBuffer.allocateDirect(

                // (# of coordinate values * 2 bytes per short)

                drawOrder.length * 2);

        dlb.order(ByteOrder.nativeOrder());

        drawListBuffer = dlb.asShortBuffer();

        drawListBuffer.put(drawOrder);

        drawListBuffer.position(0);



        int vertexShader = MyGLRenderer.loadShader(GLES30.GL_VERTEX_SHADER,

                vertexShaderCode);

        int fragmentShader = MyGLRenderer.loadShader(GLES30.GL_FRAGMENT_SHADER,

                fragmentShaderCode);



        // create empty OpenGL ES Program

        mProgram = GLES30.glCreateProgram();



        // add the vertex shader to program

        GLES30.glAttachShader(mProgram, vertexShader);



        // add the fragment shader to program

        GLES30.glAttachShader(mProgram, fragmentShader);



        // creates OpenGL ES program executables

        GLES30.glLinkProgram(mProgram);



    }



    public void draw(float[] mvpMatrix) {

        // Add program to OpenGL ES environment

        GLES30.glUseProgram(mProgram);



        // get handle to vertex shader's vPosition member

        positionHandle = GLES30.glGetAttribLocation(mProgram, "vPosition");



        // Enable a handle to the triangle vertices

        GLES30.glEnableVertexAttribArray(positionHandle);



        // Prepare the triangle coordinate data

        GLES30.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,

                GLES30.GL_FLOAT, false,

                vertexStride, vertexBuffer);



        // get handle to fragment shader's vColor member

        colorHandle = GLES30.glGetUniformLocation(mProgram, "vColor");



        // Set color for drawing the triangle

        GLES30.glUniform4fv(colorHandle, 1, color, 0);







        // get handle to shape's transformation matrix

        mVPMatrixHandle = GLES30.glGetUniformLocation(mProgram, "uMVPMatrix");

        MyGLRenderer.checkGlError("glGetUniformLocation");

        // Apply the projection and view transformation

        GLES30.glUniformMatrix4fv(mVPMatrixHandle, 1, false, mvpMatrix, 0);

        MyGLRenderer.checkGlError("glUniformMatrix4fv");

        // Draw the square

        GLES30.glDrawElements(

                GLES30.GL_TRIANGLES, drawOrder.length,

                GLES30.GL_UNSIGNED_SHORT, drawListBuffer);







        // Draw the triangle

//        GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, vertexCount);



        // Disable vertex array

        GLES30.glDisableVertexAttribArray(positionHandle);

    }

}

 

소스 바탕색이 이상하게 변하네요...

 

 - copy coding -

 

 

예전에 윈도우에서 테스트용으로 OpenGL로 몇 가지 만들어 본적이 있었는데 Android에서도 구현이 가능하다고 하여 간략하게 OpenGL의 구성과 사용법을 알아보기 위해 테스트를 진행하게 되었습니다.

Android에서의 OpenGL을 구현하는 방법에 대해서는 안드로이드 개발자 사이트에 설명을 해놓은 자료를 읽어보면 이해하는데 조금 도움이 됩니다.

아래에는 사이트에서 설명하고 있는 내용중 간략하게 중요하다고 생각되는 부분만 발취해 보았고 전체 내용은 아래 링크에서 읽어보는 것이 좋을 것 같습니다.

 

https://developer.android.com/guide/topics/graphics/opengl?hl=ko

 

· OpenGL ES 1.0 1.1 - API 사양은 Android 1.0 이상에서 지원됩니다.
· OpenGL ES 2.0 - API 사양은 Android 2.2(API 레벨 8) 이상에서 지원됩니다.
· OpenGL ES 3.0 - API 사양은 Android 4.3(API 레벨 18) 이상에서 지원됩니다.
· OpenGL ES 3.1 - API 사양은 Android 5.0(API 레벨 21) 이상에서 지원됩니다.

 

OpenGL ES 버전별로 지원하는 안드로이드 버전에 차이가 있는데 저는 OpenGL ES 3.0을 기준으로 작업을 진행 합니다.  프로젝트를 생성할 때 버전에 맞추어 선택을 합니다.

 

가장 기본이 되는 클래스가 2개가 있는데 하나는 GLSurfaceView

 

GLSurfaceView
클래스는 OpenGL API 호출을 사용하여 객체를 그리고 조작할 있는 View이며 SurfaceView 기능이 비슷합니다GLSurfaceView 인스턴스를 만들고 여기에 Renderer 추가하여 클래스를 사용할 있습니다. 그러나 터치 스크린 이벤트를 캡처하려면 OpenGL 학습 과정 터치 이벤트에 응답 표시된 대로 GLSurfaceView 클래스를 확장하여 터치 리스너를 구현해야 합니다.
 

 

GLSurfaceView는 화면(View)를 생성하는 클래스로 그려진 이미지를 표현하게 되고 화면 터치 이벤트는 직접 구현해야 한다고 되어있습니다.

OpenGL에서는 GLSurfaceViewObject로 생성하여 AndroidLayout 대신 사용합니다. activity_main.xml를 사용하는 방법도 있는지 좀더 학습을 해봐야겠지만 GLSurfaceView를 사용합니다.

 

두번째로 렌더링 관련 클래스입니다.

 

GLSurfaceView.Renderer
인터페이스를 통해서는 GLSurfaceView 그래픽을 그리는 필요한 메서드를 정의합니다. 개별 클래스로 구현된 인터페이스를 제공하고 GLSurfaceView.setRenderer() 사용하여 GLSurfaceView 인스턴스에 연결해야 합니다.
GLSurfaceView.Renderer 인터페이스를 사용하려면 다음 메서드를 구현해야 합니다.
  • onSurfaceCreated()GLSurfaceView 만들 시스템에서 메서드를 호출합니다. 메서드를 사용하여 OpenGL 환경 매개변수 설정 또는 OpenGL 그래픽 객체 초기화와 같이 번만 실행해야 하는 작업을 완료합니다.
  • onDrawFrame(): 시스템에서 GLSurfaceView 다시 그릴 때마다 메서드를 호출합니다. 메서드를 그래픽 객체 그리기( 다시 그리기) 기본 실행 지점으로 사용합니다.
  • onSurfaceChanged()GLSurfaceView 크기 변경 또는 기기 화면의 방향 변경을 비롯하여 GLSurfaceView 도형이 변경될 시스템에서 메서드를 호출합니다. 예를 들어 기기가 세로 모드에서 가로 모드로 방향을 변경하면 시스템에서 메서드를 호출합니다. 메서드를 사용하여 GLSurfaceView 컨테이너의 변경사항에 맞게 대응합니다.

 

GLSurfaceView.Renderer가 실제 이미지를 그리는 클랙스이고 위에 있는 3개의 메서드를 이용하여 생성과 화면의 변경사항에 따른 이미지 변경 작업등을 하게 됩니다.

 

OpenGL ES는 현재 3.1까지 진행이 되고 있는데 어떤 버전을 사용하여 작업하는지에 대한 설정을 AndroidManifest.xml 파일에 설정을 합니다.

 

<!-- Tell the system this app requires OpenGL ES 3.1. -->
<uses-feature android:glEsVersion="0x00030001" android:required="true" />
   

 

위에 표시한 예시 3개중 사용하려는 OpenGL ES 버전에 맞추어 선택해서 하나만 입력합니다.

 

OpenGL ES API 버전 선택을 위한 참고사항으로 다음과 같은 글이 있습니다.

 

OpenGL ES 1.0/1.1 API OpenGL ES 2.0 3.0 프로그래밍 방식은 상당히 다르므로 개발자는 API 사용하여 개발을 시작하기 전에 다음 요소를 신중하게 고려해야 합니다.

 

버전별로 구현하는 방법이 다르므로 버전선택을 잘 해야 합니다. 

 

지금까지 Google에서 OpenGLAndroid에 적용하기위해 필요한 사항을 정리한 페이지를 간략하게 알아보았습니다.

프로그래머는 실제 코딩을 봐야 이해가 되어서 샘플 프로젝트를 시작해 보도록 하겠습니다.

 

참고하려는 소스는 구글 개발자 사이트에서 제공하는 소스를 선택했습니다.

원본 내용을 보려면 설명과 함께 잘 정리되어있으니 한번 방문해서 스터디하시기 바랍니다.

 

https://developer.android.com/develop/ui/views/graphics/opengl/environment?hl=ko

 

안드로이드 버전은 Giraffe 2022.3.1으로 작업했습니다.

 

 

1. 프로젝트 생성

 

 

오늘 구현하려는 결과 이미지입니다.

 

 

 

앱을 만들기 위해 프로젝트를 하나 생성합니다.

 

 

먼저 [Empty Views Activity]를 선택한다.  예전 Android Studio 버전처럼 Empty Activity를 선택하면 언어 선택 항목이 없어서 java로 개발할 수 없습니다.

 

 

프로젝트 Name은 적당하게 기입하고 나머지 항목은 OpenGL ES 3.0버전에 맞추어 선택합니다.

Minimum SDK를 잘 선택하면 되겠네요.

프로젝트가 생성되면 아무것도 수정하지 않은 상태에서 실행을 해서 오류가 나는지 확인해 봅니다.

 

 

 

 

기본화면이 이상없이 잘 나오는군요.  가끔 업데이트를 하다 보면 초기 설정이 적절하지 못해 오류가 발생하여 버그 수정하는 시간이 아깝더군요.

이제 코딩을 시작 합니다.

 

AndroidManifest.xml 파일에 OpenGL ES 3.0을 사용하기 위한 설정을 진행합니다.

 

 

<uses-feature android:glEsVersion="0x00030000" android:required="true" />

 

 

프로젝트 생성시 Minimum SDK를 선택한 것에 맞추어 OpenGL 3.0 사용 설정을 합니다.

 

 

2. 이미지 그리기

 

 

먼저 그리고자 하는 대상을 선택 하겠습니다.  세모, 네모가 제일 간단한데 그중에 좀더 간단할것 같아 보이는 세모를 정의해 보겠습니다.

 

Google에 있는 설명을 그대로 이용해서 작성을 하려고 하는데 사이트에는 설명을 위해 프로그램을 조각내서 이곳 저곳에 설명을 해놓았는데 한번 붙여서 구현을 해봅니다.

 

원점을 기준으로 삼각형의 좌표상 위치를 설정합니다.  구글 사이트에 있는 삼각형 정의입니다.

 

 

static float triangleCoords[] = {   // in counterclockwise order:
       
0.0f,  0.622008459f, 0.0f, // top
       
-0.5f, -0.311004243f, 0.0f, // bottom left
       
0.5f, -0.311004243f, 0.0f  // bottom right
};

 

상단은 y 축만 값을 가지면 되고 좌우측 좌표 점 값으로 삼각형을 정의합니다.

 

 

public Triangle() {
   
// initialize vertex byte buffer for shape coordinates
   
ByteBuffer bb = ByteBuffer.allocateDirect(
           
// (number of coordinate values * 4 bytes per float)
           
triangleCoords.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(triangleCoords);
   
// set the buffer to read the first coordinate
   
vertexBuffer.position(0);

 

좌표점을 이용하여 삼각형 꼭지점을 정의합니다.

꼭지점을 이용하여 삼각형을 그립니다.

 

public void draw() {
   
// Add program to OpenGL ES environment
   
GLES30.glUseProgram(mProgram);

   
// get handle to vertex shader's vPosition member
   
positionHandle = GLES30.glGetAttribLocation(mProgram, "vPosition");

   
// Enable a handle to the triangle vertices
   
GLES30.glEnableVertexAttribArray(positionHandle);

   
// Prepare the triangle coordinate data
   
GLES30.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
           
GLES30.GL_FLOAT, false,
           
vertexStride, vertexBuffer);

   
// get handle to fragment shader's vColor member
   
colorHandle = GLES30.glGetUniformLocation(mProgram, "vColor");

   
// Set color for drawing the triangle
   
GLES30.glUniform4fv(colorHandle, 1, color, 0);

   
// Draw the triangle
   
GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, vertexCount);

   
// Disable vertex array
   
GLES30.glDisableVertexAttribArray(positionHandle);
}

 

Google에서는 OpenGL ES 2.0으로 소스를 설명을 하고 있는데 3.0으로 변경을 해주었습니다.

 

 

3. 도형 렌더링

 

 

삼각형을 그리긴 했지만 Surface에 표현하는 작업들이 남았습니다.

위에서 설명한 2개의 기본 클래스인 Surface를 정의하고 Rendering 을 하는 두개의 프로그램이 추가되어야 합니다.

먼저 Renderer 파일을 생성합니다.

 

Google에 있는대로 MyGLRenderer.java 파일을 생성하고

 

 

필수 메소드를 추가해 줍니다.

 

 

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
   
// Set the background frame color
   
GLES30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
   
// initialize a triangle
   
mTriangle = new Triangle();
}

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
    GLES30.glViewport(
0, 0, width, height);
}

@Override
public void onDrawFrame(GL10 gl) {
   
// Redraw background color
   
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
}

 

추가한 메소드에 코드를 추가합니다.

 

onSurfaceCreated() - 뷰의 OpenGL ES 환경을 설정하기 위해 한 번 호출됩니다.

                    초기값을 설정해야 하면 여기에 정의합니다.

onDrawFrame() - 뷰를 다시 그릴 때마다 호출됩니다.

onSurfaceChanged() - 기기의 화면 방향이 변경되는 경우와 같이 뷰의 도형이 변경되면 호출됩니다.

 

이제 Surface 파일을 작성합니다.

 

 

 

MyGLSurfaceView.java 파일을 생성하고 GLSurfaceView로부터 상속을 받습니다.

그리고 생성자를 추가하는데 첫번째 생성자를 선택 합니다.

 

private final MyGLRenderer renderer;
public
MyGLSurfaceView(Context context) {
   
super(context);

   
// Create an OpenGL ES 2.0 context
   
setEGLContextClientVersion(2);

   
renderer = new MyGLRenderer();

   
// Set the Renderer for drawing on the GLSurfaceView
   
setRenderer(renderer);
}

 

소스는 간단합니다.

생성한 삼격형 렌더링을 SurfaceView에 넣어주면 됩니다.

 

 

4. 프로젝트 실행

 

 

이제 모든 작업이 완료되었으니 MainActivity에서 생성시 layout에 지금까지의 작업을 실행하기만 하면 되겠네요.

 

 

@Override
   
protected void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        // Create a GLSurfaceView instance and set it
        // as the ContentView for this Activity.
       
gLView = new MyGLSurfaceView(this);
       
setContentView(gLView);
   
}

 

MainActivity 작업이 제일 간단 합니다.

Android layout을 사용하지 않고 OpenGL에서 생성하여 사용한다고 설명하였듯이 새로 생성한 SerfaceView를 추가해주면 끝입니다.

 

 

 

구글에 있는 소스가 너무 나뉘어있고 이곳저곳에서 생략된 표시도 있어서 작동을 하기는 할까? 하는 걱정이 있었는데 위에 있는 소스를 실행하면 좀 단순 하지만 결과가 나옵니다.

 

 

 

 

Layout이나 Gradle에 추가한 작업은 없고 그냥 3개의 신규 파일이 추가되고 2개의 파일(AndroidManifest.xml, MainActivity.java)에 몇 줄 소스 작업만 진행하였습니다.

 

 

5. 전체 소스

 

AndroidManifest.xml

 

<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
   
xmlns:tools="http://schemas.android.com/tools">

    <uses-feature
android:glEsVersion="0x00030000" android:required="true" />

    <application
       
android:allowBackup="true"
       
android:dataExtractionRules="@xml/data_extraction_rules"
       
android:fullBackupContent="@xml/backup_rules"
       
android:icon="@mipmap/ic_launcher"
       
android:label="@string/app_name"
       
android:roundIcon="@mipmap/ic_launcher_round"
       
android:supportsRtl="true"
       
android:theme="@style/Theme.OpenGLTriangle"
       
tools:targetApi="31">
        <activity
           
android:name=".MainActivity"
            
android:exported="true">
            <intent-filter>
                <action
android:name="android.intent.action.MAIN" />

                <category
android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

 

 

MainActivity.java

 

package com.copycoding.opengltriangle;

import
androidx.appcompat.app.AppCompatActivity;

import
android.opengl.GLSurfaceView;
import
android.os.Bundle;

public class
MainActivity extends AppCompatActivity {
   
private GLSurfaceView gLView;

   
@Override
   
protected void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        // Create a GLSurfaceView instance and set it
        // as the ContentView for this Activity.
       
gLView = new MyGLSurfaceView(this);
       
setContentView(gLView);
   
}
}

 

 

MyGLRenderer.java

 

package com.copycoding.opengltriangle;

import
android.opengl.GLES30;
import
android.opengl.GLSurfaceView;

import
javax.microedition.khronos.egl.EGLConfig;
import
javax.microedition.khronos.opengles.GL10;

public class
MyGLRenderer implements GLSurfaceView.Renderer {

   
private Triangle mTriangle;
   
@Override
   
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
       
// Set the background frame color
       
GLES30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
       
// initialize a triangle
       
mTriangle = new Triangle();
   
}

   
@Override
   
public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES30.glViewport(
0, 0, width, height);
   
}

   
@Override
   
public void onDrawFrame(GL10 gl) {
       
// Redraw background color
       
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);

       
mTriangle.draw();
   
}
   
public static int loadShader(int type, String shaderCode){

       
// create a vertex shader type (GLES20.GL_VERTEX_SHADER)
        // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
       
int shader = GLES30.glCreateShader(type);

       
// add the source code to the shader and compile it
       
GLES30.glShaderSource(shader, shaderCode);
       
GLES30.glCompileShader(shader);

        return
shader;
   
}
}

 

 

MyGLSurfaceView.java

 

package com.copycoding.opengltriangle;

import
android.content.Context;
import
android.opengl.GLSurfaceView;

public class
MyGLSurfaceView extends GLSurfaceView {

   
private final MyGLRenderer renderer;
    public
MyGLSurfaceView(Context context) {
       
super(context);

       
// Create an OpenGL ES 2.0 context
       
setEGLContextClientVersion(2);

       
renderer = new MyGLRenderer();

       
// Set the Renderer for drawing on the GLSurfaceView
       
setRenderer(renderer);
   
}
}

 

 

Triangle.java

 

package com.copycoding.opengltriangle;

import
android.opengl.GLES30;

import
java.nio.ByteBuffer;
import
java.nio.ByteOrder;
import
java.nio.FloatBuffer;

public class
Triangle {



   
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;" +
                   
"}";

    private
FloatBuffer vertexBuffer;

   
// number of coordinates per vertex in this array
   
static final int COORDS_PER_VERTEX = 3;
    static float
triangleCoords[] = {   // in counterclockwise order:
           
0.0f,  0.622008459f, 0.0f, // top
           
-0.5f, -0.311004243f, 0.0f, // bottom left
           
0.5f, -0.311004243f, 0.0f  // bottom right
   
};

   
// Set color with red, green, blue and alpha (opacity) values
   
float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };

    private final int
mProgram;

    private int
positionHandle;
    private int
colorHandle;

    private final int
vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
    private final int
vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex


   
public Triangle() {
       
// initialize vertex byte buffer for shape coordinates
       
ByteBuffer bb = ByteBuffer.allocateDirect(
               
// (number of coordinate values * 4 bytes per float)
               
triangleCoords.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(triangleCoords);
       
// set the buffer to read the first coordinate
       
vertexBuffer.position(0);

        int
vertexShader = MyGLRenderer.loadShader(GLES30.GL_VERTEX_SHADER,
               
vertexShaderCode);
        int
fragmentShader = MyGLRenderer.loadShader(GLES30.GL_FRAGMENT_SHADER,
               
fragmentShaderCode);

       
// create empty OpenGL ES Program
       
mProgram = GLES30.glCreateProgram();

       
// add the vertex shader to program
       
GLES30.glAttachShader(mProgram, vertexShader);

       
// add the fragment shader to program
       
GLES30.glAttachShader(mProgram, fragmentShader);

       
// creates OpenGL ES program executables
       
GLES30.glLinkProgram(mProgram);
   
}

   
public void draw() {
       
// Add program to OpenGL ES environment
       
GLES30.glUseProgram(mProgram);

       
// get handle to vertex shader's vPosition member
       
positionHandle = GLES30.glGetAttribLocation(mProgram, "vPosition");

       
// Enable a handle to the triangle vertices
       
GLES30.glEnableVertexAttribArray(positionHandle);

       
// Prepare the triangle coordinate data
       
GLES30.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
               
GLES30.GL_FLOAT, false,
               
vertexStride, vertexBuffer);

       
// get handle to fragment shader's vColor member
       
colorHandle = GLES30.glGetUniformLocation(mProgram, "vColor");

       
// Set color for drawing the triangle
       
GLES30.glUniform4fv(colorHandle, 1, color, 0);

       
// Draw the triangle
       
GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, vertexCount);

       
// Disable vertex array
       
GLES30.glDisableVertexAttribArray(positionHandle);
   
}

}

 

 

Google 사이트에 있는 소스를 복붙하다보니 띄어쓰기 줄맞춤은 좀보기 흉하네요.

Android에서 OpenGL을 어떻게 사용하는가에 대한 흐름을 대충 알것 같습니다.

몇개 더 작업을 하면서 학습을 해봐야 겠네요.

 

- copy coding -

 


1234···17

+ Recent posts