오랬 동안 Eclipse를 사용하여 개발을 해왔는데 최근에 IntellJ를 사용할 기회가 되어 설치부터 차근차근 정리를 해보려고 합니다.

 

JetBrains 사이트에 접속하여 설치 프로그램을 다운로드 합니다.

 

https://www.jetbrains.com/ko-kr/idea/download/?section=windows

 

 

다운로드 페이지에 접속해서 처음에 보이는 IntelliJ IDEA Ultimate는 유료 버전으로 30일 평가판이고 아래쪽으로 내려가면 IntelliJ IDEA Community Edition이 보입니다.

 

 

 

[다운로드]를 클릭하여 IntelliJ IDEA Community Edition 설치파일을 다운로드 받습니다.

 

다운받은 설치파일(ideaIC-2023.3.exe)을 실행하여 설치를 위한 설정 작업을 시작합니다.

설정 및 설치 작업은 간단 합니다.

 

 

[Next] 버튼을 클릭합니다.

 

 

설치 위치를 선택하거나 기본 값을 그대로 사용하여 [Next] 버튼을 클릭합니다.

 

 

 

설치 옵션 선택입니다.

 

Create Desktop Shortcut : 바탕화면에 바로가기 생성 여부입니다.

Update PATH variable(restart needed) : 윈도우 환경변수에 자동으로 추가해 줍니다.

Update Context menu : 프로젝트 폴더를 선택해서 바로 작업 가능하도록 설정합니다.

Create Association : 사용 언어와 환경을 선택하는 것으로 복수선택이 가능합니다.

 

필요한 항목을 체크하고 [Next] 버튼을 클릭합니다.

 

 

여기까지 환경 설정 작업이었습니다.

[Install] 버튼을 클릭하여 설치를 진행합니다.

 

 

 

설치가 진행되고 잠시 후 완료됩니다.

 

 

 

컴퓨터를 새로 시작해서 바로 환경설정을 적용하거나 나중에 재부팅을 선택합니다.

일단 나중에 재부팅 하기로 하고 [Finish] 버튼을 클릭하여 설치된 Intellij 프로그램을 실행해 봅니다.

 

 

 

사용전에 라이센스 동의를 해야 합니다.  체크박스에 체크하면 [Continue] 버튼이 활성화됩니다.

[Continue] 버튼을 클릭합니다.

 

 

 

인텔리제이를 사용하면서 사용환경 등 여러가지 정보를 보내달라고 하는데 [Don’t Send]를 클릭해서 거절합니다.

 

 

 

드디어 Intellij를 사용할 수 있는 화면에 도달했습니다.

 

좌측에 환경설정을 할 수 있는 메뉴들이 있는데 필요에 따라 하나씩 설정을 하면 됩니다.

간단하게 한번씩 클릭을 해봅니다.

 

 

 

[Customize]를 클릭하면 우측에서 테마는 검은색 그대로 놔두고 디스플레이가 1920x1080인 경우는 글자가 너무 크게 보여서 폰트는 13.0에서 조금 작게 선택했습니다.

 

 

 

[Plugins]에서 Marketplace에는 설치할 수 있는 플러그인이 있고 Installed에는 기존에 설치되어 있는 Plugin들을 볼 수 있습니다.

 

[Learn]은 사용법에 대한 학습을 할 수 있는 새로운 팝업 창이 나타납니다.

 

다시 좌측메뉴에서 [Projects]를 선택하고 우측에서  [New Project]를 선택해서 신규로 프로젝트를 하나 생성해보도록 하겠습니다.

 

 

 

대부분 알아서 입력 또는 선택하면 되는데 JDK는 기존에 설치한 프로그램이 없으면 바로 다운로드 가능 합니다.

 

 

 

여러 버전 중 하나를 선택하고 설치 위치도 설정해서 [Download] 버튼을 클릭합니다.

 

 

 

바로 설치가 진행됩니다.

 

 

 

[Advanced Settings]를 확장하면 기본값이 설정되어 있는데 필요한 내용으로 수정합니다.

 

[Create] 버튼을 클릭하면 프로젝트가 생성됩니다.

 

 

 

프로젝트가 생성된 모습입니다.

Pom.xml 파일을 보니 너무 심플하네요.  한참을 채워 넣어야 할 것 같습니다.

상단에 있는 실행 버튼을 클릭해서 프로젝트를 실행해 봅니다.

 

 

 

Java도 잘 실행이 되는군요.

 

 

 

선택한 폴더에 프로젝트 파일도 생성되었습니다.

사용해 보면서 하나씩 추가적인 기능 설명도 정리를 해보겠습니다.

 

- 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 -

 


1234567···116

+ Recent posts