Android/OpenGL ES

2) OpenGL ES로 삼각형 그리기

show2888 2021. 9. 28. 14:31
반응형

1. 삼각형 클래스 생성

package com.example.study.opengl.triangle

import android.opengl.GLES30
import com.example.study.opengl.GLES30Renderer
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer

class Triangle {

    // 삼각형의 정점 좌표는 FloatBuffer 객체인 vertexBuffer에 1차원 버퍼의 형태로 들어갈 것입니다.
    private var vertexBuffer: FloatBuffer

    private val mProgram: Int

    // Vertex Shader는 정점의 좌표 데이터를 받아 출력하는 기능을 수행하고
    // Fragment Shader는 색깔 정보를 받아 이를 그대로 출력하는 기능을 수행하고 있습니다.
    private val vertexShaderCode = """#version 300 es                            
layout (location = 0) in vec4 vPosition;   
void main() {                              
   gl_Position = vPosition;                
}                                          
"""

    private val fragmentShaderCode = """#version 300 es                            
precision mediump float;                   
uniform vec4 vColor;                       
out vec4 fragColor;                        
void main() {                              
  fragColor = vColor;                      
}                                          
"""

    private var mPositionHandle = 0
    private var mColorHandle = 0

    private val vertexCount: Int = triangleCoords.size / COORDS_PER_VERTEX
    private val vertexStride = COORDS_PER_VERTEX * 4

    init {
        // 4바이트 float * 삼각형 좌표 배열의 수에 해당하는 만큼 버퍼를 할당
        val bb: ByteBuffer = ByteBuffer.allocateDirect(triangleCoords.size * 4)

        // 디바이스 하드웨어에서 사용하는 byte order를 사용.
        bb.order(ByteOrder.nativeOrder())

        // ByteBuffer를 통해 floating point 버퍼를 생성.
        vertexBuffer = bb.asFloatBuffer()

        // 이 FloatBuffer에 좌표를 추가.
        vertexBuffer.put(triangleCoords)

        // 버퍼 상의 첫 번째 좌표를 읽을 수 있도록 설정.
        vertexBuffer.position(0)

        // 쉐이더 로드 및 컴파일.
        val vertexShader: Int = GLES30Renderer.loadShader(
            GLES30.GL_VERTEX_SHADER,
            vertexShaderCode
        )
        val fragmentShader: Int = GLES30Renderer.loadShader(
            GLES30.GL_FRAGMENT_SHADER,
            fragmentShaderCode
        )

        // 프로그램의 생성.
        // 쉐이더를 담을 일종의 그릇인 프로그램 객체를 생성하고
        // 이 객체에 쉐이더를 붙인 다음 링크를 하면 프로그램을 사용할 준비가 모두 끝나게 됩니다.
        mProgram = GLES30.glCreateProgram()
        GLES30.glAttachShader(mProgram, vertexShader)
        GLES30.glAttachShader(mProgram, fragmentShader)
        GLES30.glLinkProgram(mProgram)
    }

    companion object {
        // 하나의 정점에는 3개의 값(x, y, z)이 있으니 COORDS_PER_VERTEX 값에는 3을 넣어줍니다.
        var COORDS_PER_VERTEX = 3

        // 그 다음은 삼각형의 각 좌표값들을 triangleCoords 배열에 반시계 방향으로 입력해줍니다.
        var triangleCoords = floatArrayOf(
            0.0f, 0.622008459f, 0.0f,      // top
            -0.5f, -0.311004243f, 0.0f,     // bottom left
            0.5f, -0.311004243f, 0.0f       // bottom right
        )
    }

    // 각 원소들은 순서대로 R, G, B, A를 의미하며 0~1.0의 값을 가집니다
    var color = floatArrayOf(0.63671875f, 0.76953125f, 0.22265625f, 1.0f)

    fun draw() {
        // 쉐이더가 포함 된 프로그램을 사용.
        GLES30.glUseProgram(mProgram)

        // 프로그램에 포함 된 쉐이더에서 vPosition 변수에 접근할 수 있는 핸들을 가져온다.
        mPositionHandle = GLES30.glGetAttribLocation(mProgram, "vPosition")
        // 해당 값에 대한 Vertex Attribute Array를 활성화한다.
        GLES30.glEnableVertexAttribArray(mPositionHandle)
        // 활성화된 Vertex Attribute Array가 정점 데이터를 가리키도록 한다. (그래픽 장치로 복사)
        GLES30.glVertexAttribPointer(
            mPositionHandle,
            COORDS_PER_VERTEX,
            GLES30.GL_FLOAT,
            false,
            vertexStride,
            vertexBuffer
        );
        // vColor 변수에 접근할 수 있는 핸들을 가져온다.
        mColorHandle = GLES30.glGetUniformLocation(mProgram, "vColor");
        // 해당 핸들을 이용해 해당 uniform 변수에 값을 할당한다.
        GLES30.glUniform4fv(mColorHandle, 1, color, 0)

        // 위에서 설정한 값들을 바탕으로 렌더링을 수행한다.
        GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, vertexCount)

        // 활성화된 Vertex Attribute Array를 비활성화한다.
        GLES30.glDisableVertexAttribArray(mPositionHandle)
    }

}

 

Renderer)

package com.example.study.opengl;

import android.opengl.GLES30;
import android.opengl.GLSurfaceView;
import android.util.Log;

import com.example.study.opengl.triangle.Triangle;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;

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

public class GLES30Renderer implements GLSurfaceView.Renderer {

    Triangle mTriangle;

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        GLES30.glClearColor(0.0f, 0.0f, 0.2f, 1.0f);

        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) {
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);

        mTriangle.draw();
    }

    public static int loadShader(int type, String shaderCode) {
        // 빈 쉐이더를 생성하고 그 인덱스를 할당.
        int shader = GLES30.glCreateShader(type);

        // *컴파일 결과를 받을 공간을 생성.
        IntBuffer compiled = ByteBuffer.allocateDirect(4).order(ByteOrder.nativeOrder()).asIntBuffer();
        String shaderType;

        // *컴파일 결과를 출력하기 위해 쉐이더를 구분.
        if (type == GLES30.GL_VERTEX_SHADER)
            shaderType = "Vertex";
        else if (type == GLES30.GL_FRAGMENT_SHADER)
            shaderType = "Fragment";
        else
            shaderType = "Unknown";

        // 빈 쉐이더에 소스코드를 할당.
        GLES30.glShaderSource(shader, shaderCode);
        // 쉐이더에 저장 된 소스코드를 컴파일
        GLES30.glCompileShader(shader);

        // *컴파일 결과 오류가 발생했는지를 확인.
        GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, compiled);
        // *컴파일 에러가 발생했을 경우 이를 출력.
        if (compiled.get(0) == 0) {
            GLES30.glGetShaderiv(shader, GLES30.GL_INFO_LOG_LENGTH, compiled);
            if (compiled.get(0) > 1) {
                Log.e("Shader", shaderType + " shader: " + GLES30.glGetShaderInfoLog(shader));
            }
            GLES30.glDeleteShader(shader);
            Log.e("Shader", shaderType + " shader compile error.");
        }

        // 완성된 쉐이더의 인덱스를 리턴.
        return shader;

        // 주의할 점은 실제 쉐이더 객체를 주고 받는 것이 아닌 쉐이더의 인덱스를 이용한다는 것입니다.
        // 이는 OpenGL context의 특징이기도 합니다.
    }
}

 

GLSurfaceView)

package com.example.study.opengl;

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

public class GLE30SurfaceView extends GLSurfaceView {

    private final GLES30Renderer mRenderer;

    public GLE30SurfaceView(Context context) {
        super(context);

        setEGLContextClientVersion(3);
        mRenderer = new GLES30Renderer();

        setRenderer(mRenderer);


    }
}

 

Activity)

import android.opengl.GLSurfaceView
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.study.R

class OpenGLActivity : AppCompatActivity() {

    lateinit var mGLView: GLSurfaceView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mGLView = GLE30SurfaceView(this)

        setContentView(mGLView)

    }
}

 

출처 : https://m.blog.naver.com/yhjeong89/220773795263

반응형

'Android > OpenGL ES' 카테고리의 다른 글

0) Android Graphics architecture  (0) 2021.09.28
1) OpenGL ES란?  (0) 2021.09.27