도형 그리기

OpenGL로 그릴 도형을 정의한 후 도형을 그릴 가능성이 높습니다. OpenGL ES 2.0을 사용하여 도형을 그리려면 생각보다 코드가 조금 더 많이 필요합니다. API가 그래픽 렌더링 파이프라인을 세밀하게 제어하기 때문입니다.

이 과정에서는 OpenGL ES 2.0 API를 사용하여 이전 과정에서 정의한 도형을 그리는 방법을 설명합니다.

도형 초기화

그리기 전에 그릴 도형을 초기화하고 로드해야 합니다. 실행 과정에서 프로그램에서 사용하는 도형의 구조 (원래 좌표)가 변경되지 않으면 메모리와 처리 효율성을 위해 렌더기의 onSurfaceCreated() 메서드에서 도형을 초기화해야 합니다.

Kotlin

class MyGLRenderer : GLSurfaceView.Renderer {
    ...
    private lateinit var mTriangle: Triangle
    private lateinit var mSquare: Square

    override fun onSurfaceCreated(unused: GL10, config: EGLConfig) {
        ...
        // initialize a triangle
        mTriangle = Triangle()
        // initialize a square
        mSquare = Square()
    }
    ...
}

Java

public class MyGLRenderer implements GLSurfaceView.Renderer {

    ...
    private Triangle mTriangle;
    private Square   mSquare;

    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        ...
        // initialize a triangle
        mTriangle = new Triangle();
        // initialize a square
        mSquare = new Square();
    }
    ...
}

도형 그리기

OpenGL ES 2.0을 사용하여 정의된 도형을 그리려면 많은 양의 코드가 필요합니다. 그래픽 렌더링 파이프라인에 많은 세부정보를 제공해야 하기 때문입니다. 특히 다음을 정의해야 합니다.

  • 꼭짓점 셰이더 - 도형의 꼭짓점을 렌더링하는 OpenGL ES 그래픽 코드입니다.
  • 프래그먼트 셰이더 - 색상이나 질감으로 도형의 면을 렌더링하는 OpenGL ES 코드입니다.
  • 프로그램 - 하나 이상의 도형을 그리는 데 사용할 셰이더가 포함된 OpenGL ES 객체입니다.

도형을 그리려면 하나 이상의 꼭짓점 셰이더가 있어야 하고 도형의 색상을 지정하려면 하나의 조각 셰이더가 있어야 합니다. 이러한 셰이더는 컴파일한 다음 OpenGL ES 프로그램에 추가해야 합니다. 이 프로그램은 도형을 그리는 데 사용됩니다. 다음은 Triangle 클래스에서 도형을 그리는 데 사용할 수 있는 기본 셰이더를 정의하는 방법을 보여주는 예입니다.

Kotlin

class Triangle {

    private val vertexShaderCode =
            "attribute vec4 vPosition;" +
            "void main() {" +
            "  gl_Position = vPosition;" +
            "}"

    private val fragmentShaderCode =
            "precision mediump float;" +
            "uniform vec4 vColor;" +
            "void main() {" +
            "  gl_FragColor = vColor;" +
            "}"

    ...
}

Java

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

    ...
}

셰이더에는 OpenGL ES 환경에서 사용하기 전에 컴파일해야 하는 GLSL (OpenGL Shading Language) 코드가 포함되어 있습니다. 이 코드를 컴파일하려면 다음과 같이 렌더기 클래스에 유틸리티 메소드를 만들어야 합니다.

Kotlin

fun loadShader(type: Int, shaderCode: String): Int {

    // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
    // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
    return GLES20.glCreateShader(type).also { shader ->

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

Java

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 = GLES20.glCreateShader(type);

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

    return shader;
}

도형을 그리려면 셰이더 코드를 컴파일하고 OpenGL ES 프로그램 객체에 추가한 후 프로그램을 연결해야 합니다. 그린 객체의 생성자에서 이 작업을 실행하므로 한 번만 실행됩니다.

참고: OpenGL ES 셰이더를 컴파일하고 프로그램을 연결하는 작업은 CPU 주기와 처리 시간 측면에서 비용이 많이 들므로 한 번 이상 실행하면 안 됩니다. 런타임 시 셰이더의 콘텐츠를 모르는 경우 한 번만 생성된 다음 나중에 사용할 수 있도록 캐시되도록 코드를 빌드해야 합니다.

Kotlin

class Triangle {
    ...

    private var mProgram: Int

    init {
        ...

        val vertexShader: Int = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode)
        val fragmentShader: Int = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode)

        // create empty OpenGL ES Program
        mProgram = GLES20.glCreateProgram().also {

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

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

            // creates OpenGL ES program executables
            GLES20.glLinkProgram(it)
        }
    }
}

Java

public class Triangle() {
    ...

    private final int mProgram;

    public Triangle() {
        ...

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

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

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

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

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

이제 도형을 그리는 실제 호출을 추가할 준비가 되었습니다. OpenGL ES로 도형을 그리려면 렌더링 파이프라인에 그릴 내용과 그리는 방법을 알려주는 여러 매개변수를 지정해야 합니다. 그리기 옵션은 도형에 따라 다를 수 있으므로 도형 클래스에 자체 그리기 로직을 포함하도록 하는 것이 좋습니다.

도형을 그리는 draw() 메서드를 작성합니다. 이 코드는 도형의 꼭짓점 셰이더와 프래그먼트 셰이더로 위치 및 색상 값을 설정한 다음 그리기 함수를 실행합니다.

Kotlin

private var positionHandle: Int = 0
private var mColorHandle: Int = 0

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

fun draw() {
    // Add program to OpenGL ES environment
    GLES20.glUseProgram(mProgram)

    // get handle to vertex shader's vPosition member
    positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition").also {

        // Enable a handle to the triangle vertices
        GLES20.glEnableVertexAttribArray(it)

        // Prepare the triangle coordinate data
        GLES20.glVertexAttribPointer(
                it,
                COORDS_PER_VERTEX,
                GLES20.GL_FLOAT,
                false,
                vertexStride,
                vertexBuffer
        )

        // get handle to fragment shader's vColor member
        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor").also { colorHandle ->

            // Set color for drawing the triangle
            GLES20.glUniform4fv(colorHandle, 1, color, 0)
        }

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

        // Disable vertex array
        GLES20.glDisableVertexAttribArray(it)
    }
}

Java

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 void draw() {
    // Add program to OpenGL ES environment
    GLES20.glUseProgram(mProgram);

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

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

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

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

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

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

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

이 코드를 모두 배치했으면 이 객체를 그리려면 렌더기의 onDrawFrame() 메서드 내에서 draw() 메서드를 호출하기만 하면 됩니다.

Kotlin

override fun onDrawFrame(unused: GL10) {
    ...

    mTriangle.draw()
}

Java

public void onDrawFrame(GL10 unused) {
    ...

    mTriangle.draw();
}

애플리케이션을 실행하면 다음과 같이 표시되어야 합니다.

그림 1. 투영 또는 카메라 보기 없이 그려진 삼각형.

이 코드 예에는 몇 가지 문제점이 있습니다. 우선, 친구에게 인상을 주지 않을 것입니다. 둘째, 삼각형이 약간 찌그러져 있어 기기의 화면 방향을 변경하면 모양이 바뀝니다. 도형이 비뚤어지는 이유는 객체의 꼭짓점이 GLSurfaceView가 표시되는 화면 영역의 비율에 맞게 수정되지 않았기 때문입니다. 다음 강의에서는 투영과 카메라 뷰를 사용하여 이 문제를 해결할 수 있습니다.

마지막으로 삼각형은 정지되어 있으므로 약간 지루한 느낌을 줍니다. 모션 추가 과정에서는 이 도형을 회전하게 만들고 OpenGL ES 그래픽 파이프라인을 더 흥미롭게 사용해 보겠습니다.