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(); }
애플리케이션을 실행하면 다음과 같이 표시되어야 합니다.
이 코드 예에는 몇 가지 문제점이 있습니다. 우선, 친구에게 인상을 주지 않을 것입니다. 둘째, 삼각형이 약간 찌그러져 있어 기기의 화면 방향을 변경하면 모양이 바뀝니다. 도형이 비뚤어지는 이유는 객체의 꼭짓점이 GLSurfaceView
가 표시되는 화면 영역의 비율에 맞게 수정되지 않았기 때문입니다. 다음 강의에서는 투영과 카메라 뷰를 사용하여 이 문제를 해결할 수 있습니다.
마지막으로 삼각형은 정지되어 있으므로 약간 지루한 느낌을 줍니다. 모션 추가 과정에서는 이 도형을 회전하게 만들고 OpenGL ES 그래픽 파이프라인을 더 흥미롭게 사용해 보겠습니다.