반응형
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)
}
}
반응형
'Android > OpenGL ES' 카테고리의 다른 글
0) Android Graphics architecture (0) | 2021.09.28 |
---|---|
1) OpenGL ES란? (0) | 2021.09.27 |