2024. 5. 3. 22:58
목표
- OpenGL 기본개념 알기
- Android에서 OpenGL로 삼각형 그리기
OpenGL ES란?
OpenGL ES는 3차원 컴퓨터 그래픽스 API인 OpenGL(Open Graphic Library)의 임베디드 시스템을 위한 버전이다. ES가 Embaedded System을 의미한다.
OpenGL은 다양한 API를 제공하며 해당 API들을 통해 점, 선, 삼각형, 사각형, 빛 등을 화면에 그릴 수 있다. 즉, OpenGL은 화면에 뭔가 그릴 수 있도록 도와주는 API들로 이루어진 라이브러리다.
비슷한 API로 Windows에는 DirectX가 있다. 차이점은 OpenGL은 크로스 플랫폼을 지원한다.
Vertex
- 3차원 그래픽의 가장 원자적인 요소
- 정점의 정보를 가지고 있는 data structure
- Position을 가지고 있고 2D라면 x,y로 3D라면 x,y,z로 됨
Polygon
- vertex를 연결해서 만든 면을 polygon이라고 한다.
- 최소 단위는 vertex이지만 면의 최소단위는 triangle
- 보통은 삼각형, 초창기에는 삼각형과 사각형의 싸움이었지만 대부분 삼각형을 이어서 표현
좌표계
- Normalized Device Coordinates (NDC)
- OpenGL에서는 정사각형의 균일 좌표계를 가정하며, 대체로 정사각형이 아닌 화면에서 완전히 정사각형인 것처럼 좌표를 그립니다.
- vertex shader에서 여러분의 정점 좌표들이 처리가 되면 정점들이 normalized device coordinates 내부에 들어가게 됩니다. normailized device coordinates는 x, y, z 값이 모두 -1.0와 1.0 사이에 있는 작은 공간입니다. 이 영역 밖에있는 모든 좌표들은 폐기되고 여러분의 화면에 보이지 않게 됩니다. 아래에서 normalized device coordinates에 명시된 삼각형을 볼 수 있습니다(z 축은 무시)
- 위의 그림에서는 OpenGL 프레임에 사용하는 것으로 간주된 균일 좌표계를 왼쪽에 표시하고, 이 좌표가 가로 모드 방향으로 일반 기기 화면에 실제로 매핑되는 방식을 오른쪽에 보여줍니다. 이 문제를 해결하려면 OpenGL 투영 모드와 카메라 보기를 적용하여 좌표를 변환함으로써 화면에서 그래픽 객체의 비율을 정확하게 맞출 수 있습니다.
- android view에서는 왼쪽 위가 0,0이지만 openGL에서는 가운데가 0,0이다.
그리는 방향
- 3개 이상의 3차원 점 조합(OpenGL의 꼭짓점이라고 함)에는 앞면과 뒷면이 있습니다.
- 답은 권선, 즉 도형의 점을 정의하는 방향과 관련이 있습니다.
- 기본적으로 OpenGL에서는 시계 반대 방향으로 그린 면이 앞면입니다.
- 도형의 앞면이 어떤 면인지를 아는 것이 왜 중요한가요? 그 이유는 일반적으로 사용되는 면 선별이라고 하는 OpenGL의 기능과 관련됩니다. 면 선별은 렌더링 파이프라인에서 도형의 뒷면을 무시(계산하거나 그리지 않음)하도록 하여 시간과 메모리를 절약하고 처리 주기를 단축하는 Open GL 환경의 한 옵션입니다.
OpenGL Rendering Pipeline
- 삼각형을 화면에 나타내기 위해 여러가지 과정을 거쳐 처리를 하게 되고 이러한 과정들을 렌더링 파이프라인이라고 합니다. 그리고 이 파이프라인을 이용하려면 셰이더(Shader)라는 서브루틴(프로그램)을 이용해야합니다.
- 컴퓨터 과학에서 파이프라인(영어: pipeline)은 한 데이터 처리 단계의 출력이 다음 단계의 입력으로 이어지는 형태로 연결된 구조를 가리킨다. 이렇게 연결된 데이터 처리 단계는 한 여러 단계가 서로 동시에, 또는 병렬적으로 수행될 수 있어 효율성의 향상을 꾀할 수 있다. 각 단계 사이의 입출력을 중계하기 위해 버퍼가 사용될 수 있다. (출처 : https://ko.wikipedia.org/wiki/%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8_(%EC%BB%B4%ED%93%A8%ED%8C%85))
Shader
- 셰이더는 그래픽 처리 장치 (GPU)에 데이터를 그리는 방법을 알려줍니다. 셰이더에는 두 가지 유형이 있으며 화면에 무엇인가를 그릴 수 있으려면 두 가지를 모두 정의해야합니다.
- OpenGL ES 2.0부터는 셰이더를 사용하여 프로그래밍 가능한 API를 추가했습니다. 이때부터는 각 정점이 화면에 제어하는 버텍스 쉐이더와 각 프레그먼트가 그려지는 방식을 제어하는 프레그먼트 쉐이더가 추가되었습니다.
GLSL
- OpenGL Shading Language. GLslang로도 알려져 있다
- C 언어를 기초로 한, 상위 레벨 셰이딩 언어이다.
- 어셈블리 언어나 하드웨어에 의존한 언어를 사용하지 않고, 개발자가 그래픽스 파이프라인을 직접 제어할 수 있는 언어이다.
uniform mat4 uMVPMatrix;
attribute vec4 vPosition;
void main() {
gl_Position = uMVPMatrix * vPosition;
}
- attributes - vertex array를 사용하여 제공되는 vertex data
- uniforms - shader에서 사용되는 상수 데이터
Vertex Shader
- 각 정점의 최종 위치를 생성하며 정점마다 한번씩 실행됩니다. 일단 최종 위치가 알려지면, OpenGL은 보이는 정점 집합을 취해 점, 선 및 삼각형으로 만듭니다.
Fragment Shader
- 점, 선 또는 삼각형의 각 프레그먼트 최종 색상을 생성하며 프레그먼트마다 한 번 실행됩니다. 프래그먼트는 컴퓨터 화면의 픽셀과 유사한 단일 색상의 작고 직사각형 인 영역입니다.
- Fragment Shader의 주목적은 GPU에게 최종적인 프레그먼트의 색이 무엇이 되어야 하는지 알려주는 것이다. 기본 요소의 모든 프레그먼트에 대해 한 번씩 호출되므로 삼각형이 1000개의 프레그먼트로 매핑되면 Fragment Shader는 1000번 호출된다.
Program
- Shape를 그리기 위해 사용되는 shader를 포함하는 OpenGL ES 객체
Primitive
Primitive Assembly
- primitive : 점, 선, 삼각형 등 기본 도형들, OpenGL이 무엇을 그려야하는 지 알려준다.
- vertex들의 위치정보를 이용하여 삼각형 같은 primitive를 만들어 낸다. vertex들을 서로 연결할때 일정한 순서에 따라 연결이 된다.
- 아래의 draw mode등을 선택하면 그것에 맞게 그려준다.
Primitive Processing (Clipping)
- 앞에서 생성한 primitive가 View Volume 내부에 포함되는지 태스트한다. 이 태스트를 통과하지 못한 primitive는 이후 단계에서 무시된다. 이 태스트를 Clipping이라고 부른다. 쉽게 얘기해서 3차원 공간상의 물체를 카메라로 찍었을때 보이지 않는 부분들을 제외시키는 것이다.
- 클립영역은 일반적으로 렌더링 퍼포먼스향상을 위해 정의된다. 잘 정의된 클립은 렌더러(프로그램)이 사용자가 보지못하는 픽셀과 관련된 계산을 무시함으로써 에너지와 시간을 절약하게 해준다.
- 앞으로 그려질 픽셀은 클립영역안에 있어야한다. 마찬가지로 그려질 필요가 없는 픽셀은 클립영역 바깥에 있어야 한다. 추가적으로, 이러한 픽셀 들을 '클립됐다'라고 한다.
Rasterizer
- Primitive를 fragment 단위로 쪼갬
- Primitive를 화면에 보여주기 위해서는 Primitive의 형태를 화면의 출력 기본단위인 pixel로 근사화해야 한다. primitive가 나누진 각각의 작은 유닛을 fragment라고 부른다.
- Fragment는 primitive보다는 작고 pixel보다는 크지만 유사한 크기의 단일 색상의 작고 직사각형 인 영역입니다.
안드로이드에서의 OpenGL ES 환경 빌드
안드로이드 버전별로 지원목록
- 안드로이드 1.0 이상 : OpenGL ES 1.0과 1.1
- 안드로이드 2.0(API Level 8) 이상 : OpenGL ES 2.0
- 안드로이드 4.3(API Level 18) 이상 : OpenGL ES 3.0
- 안드로이드 5.0(API Level 21) 이상 : OpenGL ES 3.1
- OpenGL ES 1.x API 호출과 OpenGL ES 2.0 메서드를 혼합하지 마세요! 두 API는 서로 호환되지 않으므로 이 둘을 함께 사용하면 문제만 발생합니다.
- 주의: 기기에서 OpenGL ES 3.0 API를 지원하려면 기기 제조업체에서 제공하는 이 그래픽 파이프라인이 구현되어 있어야 합니다.
- OpenGL ES 3.x API는 2.0 API와 역호환되므로 애플리케이션에서 OpenGL ES를 더욱 유연하게 구현할 수 있습니다.
- OpenGL ES 2.0 API를 manifest에서 요구사항으로 선언하면 이 API 버전을 기본으로 사용하고, 런타임 시 3.x API를 사용할 수 있는지 확인한 다음, 기기에서 지원하는 경우 OpenGL ES 3.x 기능을 사용할 수 있습니다.
AndroidManifest.xml
<!-- 시스템에게 OpenGL ES 2.0이 필요하다는 것을 알려줍니다. -->
<!-- 이 선언을 추가하면 Google Play에서 OpenGL ES 2.0을 지원하지 않는 기기에 애플리케이션이 설치되지 못하게 제한합니다. -->
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
<!-- 시스템에게 OpenGL ES 3.0이 필요하다는 것을 알려줍니다. -->
<uses-feature android:glEsVersion="0x00030000" android:required="true" />
<!-- 시스템에게 OpenGL ES 3.1이 필요하다는 것을 알려줍니다. -->
<uses-feature android:glEsVersion="0x00030001" android:required="true" />
GLSurfaceView
- GLSurfaceView는 OpenGL ES 그래픽을 그릴 수 있는 특수 뷰입니다.
- 객체의 실제 그리기는 이 뷰에 설정한 GLSurfaceView.Renderer에서 제어됩니다.
- 터치 이벤트 캡쳐를 위해서는 GLSurfaceView를 실제로 이 객체의 코드는 매우 간단하므로 상속을 건너뛰고 수정되지 않은 GLSurfaceView 인스턴스를 생성하려고 할 수 있지만, 그러면 안 됩니다. 터치 이벤트를 캡처하려면 이 클래스를 확장해야 합니다. 이 내용은 터치 이벤트에 응답 학습 과정에 설명되어 있습니다.
import android.content.Context
import android.opengl.GLSurfaceView
class MyGLSurfaceView(context: Context) : GLSurfaceView(context) {
private val renderer: MyGLRenderer
init {
// Create an OpenGL ES 2.0 context
setEGLContextClientVersion(2)
// Render the view only when there is a change in the drawing data
renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY
renderer = MyGLRenderer()
// Set the Renderer for drawing on the GLSurfaceView
setRenderer(renderer)
}
}
GLSurfaceView.Renderer
- 이 클래스는 연결된 GLSurfaceView에 그려지는 항목을 제어합니다.
- onSurfaceCreated() - 뷰의 OpenGL ES 환경을 설정하기 위해 한 번 호출합니다.
- onDrawFrame() - 뷰를 다시 그릴 때마다 호출합니다.
- onSurfaceChanged() - 예를 들어 기기의 화면 방향이 변경될 때와 같이 뷰의 도형이 변경되면 호출합니다.
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10
import android.opengl.GLES20
import android.opengl.GLSurfaceView
class MyGLRenderer : GLSurfaceView.Renderer {
override fun onSurfaceCreated(unused: GL10, config: EGLConfig) {
// Set the background frame color
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f)
}
override fun onDrawFrame(unused: GL10) {
// Redraw background color
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
}
override fun onSurfaceChanged(unused: GL10, width: Int, height: Int) {
GLES20.glViewport(0, 0, width, height)
}
}
StateMachine
- OpenGL은 그리기에 필요한 여러 가지 정보들을 상태 머신(State Machine)에 저장한다.
- 상태 머신이란 상태를 저장하는 장소이며 그리기에 영향을 미치는 여러 변수값들의 집합이다.
- 상태 머신은 전역적이며 영속적인 저장소이므로 한번 지정해 놓은 상태 변수는 다른 값으로 바꾸기 전에는 계속 유효하다.
- 그래서 같은 값이라면 이전 값을 계속 사용할 수 있으며 매번 새로 지정할 필요가 없다.
- 색깔만 바꿔 세개의 선을 그리려면, 똑같은 명령어를 약간의 수정만하여 여러번 반복해서 써야만 하지만 상태머신을 통해 현재 설정된 스타일, 두께 색상 등의 값을 시스템 테이블에서 찾아 자동으로 적용하게 된다. 물론 프로그램이 처음 시작될 때에는 디폴트값이 들어있다.
- State를 변경하는 함수, State를 가져오는 함수등으로 구성되어 있다.
GLThread
- Renderer를 주기적으로 호출한다.
- GLSurfaceView의 일관적인 업데이트를 위한 메카니즘
- 일반적인 UI 처리 방식과 동일
- 사용자의 비동기적인 요구에 대응하기 위해 이벤트 큐를 유지하며 사용자의 요구는 이벤트 큐에 일단 저장되었다가 적절한 시점에 일괄적으로 처리
https://android.googlesource.com/platform/frameworks/base/+/master/opengl/java/android/opengl/GLSu
public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback2 {
...
private GLThread mGLThread;
...
public void setRenderer(Renderer renderer) {
mRenderer = renderer;
mGLThread = new GLThread(mThisWeakRef);
mGLThread.start();
}
public void queueEvent(Runnable r) {
mGLThread.queueEvent(r);
}
...
static class GLThread extends Thread {
...
private ArrayList<Runnable> mEventQueue = new ArrayList<Runnable>();
...
GLThread(WeakReference<GLSurfaceView> glSurfaceViewWeakRef) {
super();
mWidth = 0;
mHeight = 0;
mRequestRender = true;
mRenderMode = RENDERMODE_CONTINUOUSLY;
mWantRenderNotification = false;
mGLSurfaceViewWeakRef = glSurfaceViewWeakRef;
}
@Override
public void run() {
while(true) {
...
view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);
...
view.mRenderer.onSurfaceChanged(gl, w, h);
...
view.mRenderer.onDrawFrame(gl);
...
}
}
}
}
삼각형 그리기
- OpenGL ES를 사용하면 3차원 공간에서 좌표를 사용하여 그린 객체를 정의할 수 있습니다.
- 삼각형을 그리려면 먼저 좌표를 정의해야 합니다.
- OpenGL에서는 일반적으로 부동 소수점 숫자로 된 꼭짓점 배열을 좌표로 정의합니다.
- 효율성을 극대화하려면 이 좌표를 ByteBuffer에 쓴 다음 OpenGL ES 그래픽 파이프라인에 전달하여 처리하게 합니다.
- 기본적으로 OpenGL ES는 좌표계에서 [0,0,0](X,Y,Z)은 GLSurfaceView 프레임의 중심을 지정하고, [1,1,0]은 프레임의 오른쪽 상단을 지정하며, [-1,-1,0]은 프레임의 왼쪽 하단을 지정한다고 간주합니다.
Vertex 정의
// number of coordinates per vertex in this array
const val COORDS_PER_VERTEX = 3
var triangleCoords = floatArrayOf( // in counterclockwise order:
0.0f, 0.622008459f, 0.0f, // top
-0.5f, -0.311004243f, 0.0f, // bottom left
0.5f, -0.311004243f, 0.0f // bottom right
)
class Triangle {
// Set color with red, green, blue and alpha (opacity) values
val color = floatArrayOf(0.63671875f, 0.76953125f, 0.22265625f, 1.0f)
private var vertexBuffer: FloatBuffer =
// (number of coordinate values * 4 bytes per float)
ByteBuffer.allocateDirect(triangleCoords.size * 4).run {
// use the device hardware's native byte order
order(ByteOrder.nativeOrder())
// create a floating point buffer from the ByteBuffer
asFloatBuffer().apply {
// add the coordinates to the FloatBuffer
put(triangleCoords)
// set the buffer to read the first coordinate
position(0)
}
}
}
Initialize a triangle
class MyGLRenderer : GLSurfaceView.Renderer {
...
private lateinit var mTriangle: Triangle
override fun onSurfaceCreated(unused: GL10, config: EGLConfig) {
...
// initialize a triangle
mTriangle = Triangle()
}
...
}
Draw a triangle
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;" +
"}"
...
}
- GLSL Shader 코드를 컴파일하고 Programe 객체를 만든다.
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)
}
}
-----------------------------------------------------------------
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)
}
}
}
-----------------------------------------------------------------
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)
}
}
-------------------------------------------------------------------
override fun onDrawFrame(unused: GL10) {
...
triangle.draw()
}
Wrong Result
- 삼각형이 약간 찌그러져 있으며 기기의 화면 방향을 변경하면 도형이 변경됩니다.
- 도형이 비뚤어지는 이유는 객체의 꼭짓점이 GLSurfaceView가 표시되는 화면 영역의 비율에 맞게 수정되지 않았기 때문입니다.
- 다음 학습 과정에서 투영과 카메라 보기를 사용하여 이 문제점을 수정할 수 있습니다.
MVP Matrix 적용하기
- Model : 화면에 나타내고자 하는 객체, 삼각형 혹은 구
- View : Camera 객체, 즉 Model 객체를 바라보는 대상
- Projection : 투영, 카메라가 모델을 볼 수 있는 공간
- 점선 모양의 사각뿔 모양이 Frustum(절두체)라고 하는 Projection이다.
- Perspective Projection : 이 프로젝션은 사람의 눈이나 카메라가 세상을 바라 보는 것처럼 원근법을 적용하여 객체를 표현합니다.
- Orthographic Projection : 이 프로젝션은 절두체 안에 있는 모든 오브젝트들을 원근과 왜곡 없이 표현합니다. 최종적으로 렌더링 되는 결과물을 보면 프로젝션내의 모든 오브젝트들을 그대로 압축해서 보여주는 느낌이 듭니다.
MVP Matrix 연산 방법
"MVPMatrix = Projection * View * Model"
- Model : 삼각형은 원점에 존재하므로 Model 행렬은 따로 연산할 필요 없이 단위행렬로 지정
- View(Camera) : 원점에 있으면 삼각형을 보기 힘드므로 z축으로 일정거리만큼 떼기
- Projection : Perspective
Projection Matrix 계산
- 이 변환은 그린 객체가 표시되는 GLSurfaceView의 너비와 높이에 기반하여 객체의 좌표를 조정합니다. 일반적으로 투영 변환은 렌더기의 onSurfaceChanged() 메서드에서 OpenGL 뷰의 비율이 설정되거나 변경될 때만 계산해야 합니다.
// vPMatrix is an abbreviation for "Model View Projection Matrix"
private val vPMatrix = FloatArray(16)
private val projectionMatrix = FloatArray(16)
private val viewMatrix = FloatArray(16)
override fun onSurfaceChanged(unused: GL10, width: Int, height: Int) {
GLES20.glViewport(0, 0, width, height)
val ratio: Float = width.toFloat() / height.toFloat()
// this projection matrix is applied to object coordinates
// in the onDrawFrame() method
Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1f, 1f, 3f, 7f)
}
View Matrix 계산
- 이 변환에서는 가상 카메라 위치를 기준으로 그릴 객체의 좌표를 조정합니다.
- OpenGL ES에서는 실제 카메라 객체를 정의하는 것이 아니라, 그린 객체의 표시를 변환하여 카메라를 시뮬레이션하는 유틸리티 메서드를 제공한다는 점을 기억하는 것이 중요합니다.
- 카메라 뷰 변환은 GLSurfaceView를 설정할 때 한 번만 계산하거나 사용자 작업 또는 애플리케이션의 기능에 기반하여 동적으로 변경할 수 있습니다.
- 렌더기에 그리기 프로세스의 일부로 카메라 보기 변환을 추가하면 그린 객체의 변환 프로세스가 완료됩니다.
override fun onDrawFrame(unused: GL10) {
...
// Set the camera position (View matrix)
Matrix.setLookAtM(viewMatrix, 0, 0f, 0f, -3f, 0f, 0f, 0f, 0f, 1.0f, 0.0f)
// Calculate the projection and view transformation
Matrix.multiplyMM(vPMatrix, 0, projectionMatrix, 0, viewMatrix, 0)
// Draw shape
triangle.draw(vPMatrix)
- Camera up : camera의 위가 어디 인지를 표현한다. 0, 1, 0이면 의도한 삼각형이 보이지만 0, -1, 0이면 뒤집혀 보인다.
MVP matrix 적용
class Triangle {
private val vertexShaderCode =
// This matrix member variable provides a hook to manipulate
// the coordinates of the objects that use this vertex shader
"uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" +
"void main() {" +
// the matrix must be included as a modifier of gl_Position
// Note that the uMVPMatrix factor *must be first* in order
// for the matrix multiplication product to be correct.
" gl_Position = uMVPMatrix * vPosition;" +
"}"
// Use to access and set the view transformation
private var vPMatrixHandle: Int = 0
...
}
------------------------------------------------------------------------
fun draw(mvpMatrix: FloatArray) { // pass in the calculated transformation matrix
// get handle to shape's transformation matrix
vPMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix")
// Pass the projection and view transformation to the shader
GLES20.glUniformMatrix4fv(vPMatrixHandle, 1, false, mvpMatrix, 0)
// Draw the triangle
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount)
// Disable vertex array
GLES20.glDisableVertexAttribArray(positionHandle)
}
Correct Result
'Mobile > Android' 카테고리의 다른 글
Dependency Injection과 Hilt 맛보기 (0) | 2024.05.03 |
---|---|
안드로이드 SQLite 속도 향상! (0) | 2013.03.26 |
Error generating final archive: Debug certificate expired on ... 문제해결법 (0) | 2013.03.13 |
xml에서 include로 포함한 뷰들의 inflate여부 (0) | 2012.11.13 |
JNIEnv* 와 jclass (0) | 2012.11.13 |