본문 바로가기

ComputerGraphics [고려대학교_한정현]

Android + OpenGLES로 삼각형 그리기

오늘은 OpenGLES 로 삼각형을 그려보도록 하겠습니다.

 

 

[결과물]

 

 

 

[프로젝트 파일]

 

 

[AndroidMenifest.xml]

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

</manifest>

 

 

[MainActivity.java]

 1. 뷰를 생성하고, 셋팅해준다.

package com.samman.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.app.Activity;
import android.os.Bundle;
import android.view.WindowManager;

public class MainActivity extends Activity {
    private MainGLSurfaceView mGLSurfaceView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().setFlags(WindowManager.LayoutParams.FLAGS_CHANGED, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        mGLSurfaceView = new MainGLSurfaceView(this);
       //렌더링 된것이 나타날 곳을 GLSurfaceView 으로 지정한다.
       setContentView(mGLSurfaceView);
    }
}

 

 

 

[MainGLSurfaceView.java]

 MainActivity 에서 지정해줄 GLSurfaceView클래스를 만든다.

package com.samman.myapplication;

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

public class MainGLSurfaceView extends GLSurfaceView {
    private final MainGLRenderer mGLRenderer;
    public MainGLSurfaceView(Context context) {
        super(context);
        //OpenGLES 2.0을 사용하기 위해 EGL 컨텍스트를 설정하는 메서드입니다.
        //EGL (Embedded System Graphics Library)은 안드로이드에서 OpenGL과 같은 그래픽 라이브러리를 사용할 수 있도록 하는 인터페이스입니다.
        setEGLContextClientVersion(2);
        
        //Renderer를 셋팅합니다.
        mGLRenderer = new MainGLRenderer();
        setRenderer(mGLRenderer);
        
        //GLRenderer에서 구현된 onDrawFrame 이라는 함수를 매프레임마다 계속 호출하게 된다.
        setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
    }
}

 

 

[MainGLRenderer.java]

 GLSurfaceView 에서 그리기를 담당해줄 Renderer 를 만든다.

 내부에는 onDrawFrame이라는 프레임을 그리는 메소드가 있다.

package com.samman.myapplication;

import android.opengl.GLES20;
import android.opengl.GLSurfaceView;

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

public class MainGLRenderer implements GLSurfaceView.Renderer {
    Triangle mTriangle;

    //보통  on 을 쓰면 event 를 받는 함수를 나타내죠.
    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        //필요한 OpenGLES 의 객체를 생성한다.
        //삼각형을 그리는 메소드가 포함된 Triangle 클래스를 초기화한다.
        mTriangle = new Triangle();
    }

    @Override
    public void onSurfaceChanged(GL10 gl10, int width, int height) {
        // GLSurfaceView의 크기가 변경될 때 호출됩니다. 이 메서드에서는 변경된 뷰 크기에 맞게 OpenGL ES 뷰포트를 업데이트합니다.
        GLES20.glViewport(0,0,width, height);
    }

    @Override
    //OpenGLES에서 그래픽랜더링 단계중 '프레임 렌더링'을 수행하는 콜백 메소드이다 ( GLSurfaceView.Renderer인터페이스에 포함 ).
    //View 에 포함되는 모든 객체를 그리고, Frame 완료를 알리는 신호를 보낸다.
    //프레임이란 : FPS 는 1초에 몇번의 뷰를 그릴것인가에 대한 Frame 수를 나타내는 것
    //프레임 렌더링이란 : 한 번의 프레임 렌더링이 수행되고, 이것이 뷰의 화면에 업데이트 되기 위한 한 단계일 뿐입니다.
    public void onDrawFrame(GL10 gl10) {
    	//바탕색 셋팅
        GLES20.glClearColor(0.3f,0.3f,0.0f,0.3f); //GLES20은 정적 클래스이고, OpenGL ES 2.0에서 사용할 수 있는 모든 함수를 제공함
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); //뷰의 화면을 초기화 하기전에 glClear 를 통해 버퍼를 지운다

        //삼각형 그리기
        mTriangle.draw();//  ( VertexShader,FragmentShader 컴파일 -> Program에 셋팅 및 링크 -> 쉐이더를 통해 얻은 Vertex,Color Handler로 값 셋팅 -> 그리기 )  

        //OpenGL ES에서는 명령을 큐에 저장하고, 필요할 때마다 실행합니다.
        //그러나 glFlush() 메서드를 호출하면, 현재까지의 그래픽렌더링 명령을 모두 실행하도록 강제합니다.
        GLES20.glFlush(); // 그리라고 하는 명령어이다.

    }
}

 

 

[ Triangle.java ]

 - 삼각형을 그리기 위한 좌표값 배열과 색상 값

 - 좌표들과 색상값을 셋팅하기 위한 VertexShader, FragmentShader 셋팅

 - 쉐이더끼리 링킹하고 결과 값을 얻기위한 Program

 - Program에 엮여있는 Shader 를 통해 얻은 값을 사용하기 위한 Handler 

package com.samman.myapplication;

import android.opengl.GLES20;

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

public class Triangle {
    //삼각형 정점 3개의 좌표
    static float triangleCoords[] = {
            0.0f, 0.5f, 0.0f,   //top
            -0.5f, -0.5f, 0.0f, // bottom left
            0.5f, -0.5f, 0.0f //bottom right
    };
    private float color[] = {0.5f, 0.5f,0.5f, 0.5f};

    //Vertex Shader ( Shader: 정점에 처리할 연산을 정의하는 메소드 )
    private final String mVertexShaderCode = // Vertex로 포현할 position 프로그램 코드
            "attribute vec4 vPosition;"+ // 객체의 위치를 저장하기 위한 vec4 자료형(4개의 int로 이루어진 벡터)
                    "void main(){"+ // 셰이더함수
                    "gl_Position = vPosition;"+ // 현재 vertex의 좌표 값을 저장함
                    "}";


    //Fragment Shader ( 픽셀에 색상을 넣음 )
    private final String mFragmenetShaderCode  =
            "precision mediump float;\n" +
                    "uniform vec4 vColor;\n" +
                    "void main() {\n" +
                    "   gl_FragColor = vColor;\n" +
                    "}\n";


    //정점을 이루는 좌표의 갯수
    static final int COORDS_PER_VERTEX = 3;
    //정점(꼭지점) 갯수
    private final int vertextCount = triangleCoords.length /COORDS_PER_VERTEX;
    //정점을 이루는 크기(Bytes)( 정점의 좌표값들은 보통 Float 으로 표현됨 )
    private final int vertexStride = COORDS_PER_VERTEX * 4;

    private final int mProgram;

    private FloatBuffer mVertexBuffer;
    private int mPositionHandle;
    private int mColorHandle;

    public Triangle() {
        //버퍼 셋팅
        ByteBuffer bb = ByteBuffer.allocateDirect(triangleCoords.length * 4); //바이트버퍼 할당: 정점 3개 * 각 정점의 좌표 3개 * 4Bytes
        bb.order(ByteOrder.nativeOrder()); //리틀엔디언,빅엔디언 셋팅
        mVertexBuffer = bb.asFloatBuffer(); //FloatBuffer로 변환
        mVertexBuffer.put(triangleCoords);  //Float으로 이루어진 삼각형좌표 배열을 삽입
        mVertexBuffer.position(0); //첫번째 좌표부터 읽도록 positioning

        //Shader 셋팅
        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, mVertexShaderCode);
        int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, mFragmenetShaderCode);

        //프로그램 생성 -> 쉐이더 붙이기 -> 프로그램 링크 ( 쉐이더간의 인터페이스를 생성해주고, 최종 결과물까지 나오도록 내부 파이프라인을 연결해준다. )
        mProgram = GLES20.glCreateProgram();
        GLES20.glAttachShader(mProgram, vertexShader);
        GLES20.glAttachShader(mProgram, fragmentShader);
        GLES20.glLinkProgram(mProgram); // 링크된 프로그램은 GLES20.glUseProgram()을 이용해서 사용된다. (아래참고)
    }

    public void draw(){
        GLES20.glUseProgram(mProgram); // VertexShader, FragmentShader가 셋팅되고 링크된 프로그램을 사용한다.

        //쉐이더내에서 Handler 로 좌표값 셋팅//
        mPositionHandle = GLES20.glGetAttribLocation(mProgram,  "vPosition");// VertexShader를 통해 셋팅된 정점을 읽어들일수 있는 positionHandle을 가져옴
        GLES20.glEnableVertexAttribArray(mPositionHandle); //좌표들을 쓸수 있도록 핸들을 기동시킴
        GLES20.glVertexAttribPointer( //움직이는 PositionHandle이 float형의 VertexBuffer를 타고다니면서 좌표를 얻어냄
                mPositionHandle,
                COORDS_PER_VERTEX,
                GLES20.GL_FLOAT,
                false,
                vertexStride,
                mVertexBuffer);
        //쉐이더내에서 Handler 로 색상 셋팅//
        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor"); // Fragment Shader 를 통해 셋팅된 컬러 값을 읽어들일수 있는 positionHandle을 가져옴
        GLES20.glUniform4fv(mColorHandle, 1, color, 0 ); // 색상을 설정함

        //그리기 및 종료
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertextCount); // 그리기
        GLES20.glDisableVertexAttribArray(mPositionHandle); //PositionHandle 중지함
    }

    //쉐이더들을 처리할(컴파일)할 메소드
    public static int loadShader(int type, String shaderCode){
        int shader = GLES20.glCreateShader(type);
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);
        return shader;
    }
}