Skip to content

Instantly share code, notes, and snippets.

@sunmeat
Last active November 4, 2025 09:01
Show Gist options
  • Save sunmeat/e81b39ff4af3783368e3c582308d87ab to your computer and use it in GitHub Desktop.
Save sunmeat/e81b39ff4af3783368e3c582308d87ab to your computer and use it in GitHub Desktop.
android opengl minimal code
MainActivity.java:
package site.sunmeat.opengles3d;
import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.opengl.GLES20;
import android.opengl.Matrix;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import android.util.Log;
class TriangleRenderer implements GLSurfaceView.Renderer {
private static final String TAG = "TriangleRenderer";
private FloatBuffer vertexBuffer;
private FloatBuffer colorBuffer;
private int program;
private int positionHandle;
private int colorHandle;
private int mvpMatrixHandle;
private final float[] mvpMatrix = new float[16];
private final float[] scratch = new float[16];
private final long startTime = System.currentTimeMillis();
// координати та кольори
private final float[] triangleCoords = {
0.0f, 0.933f, 0.0f,
-0.75f, -0.466f, 0.0f,
0.75f, -0.466f, 0.0f
};
private final float[] colors = {
1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f
};
private float speedX, speedY, speedZ; // випадкові швидкості в град/сек
@Override
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
// встановлення кольору очищення екрану
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// відключення тесту глибини
GLES20.glDisable(GLES20.GL_DEPTH_TEST);
// ініціалізація буферів
ByteBuffer bb = ByteBuffer.allocateDirect(triangleCoords.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(triangleCoords);
vertexBuffer.position(0);
bb = ByteBuffer.allocateDirect(colors.length * 4);
bb.order(ByteOrder.nativeOrder());
colorBuffer = bb.asFloatBuffer();
colorBuffer.put(colors);
colorBuffer.position(0);
String vertexShaderCode = "uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" +
"attribute vec4 vColor;" +
"varying vec4 vFragmentColor;" +
"void main() {" +
" gl_Position = uMVPMatrix * vPosition;" +
" vFragmentColor = vColor;" +
"}";
// компіляція вершинного шейдера
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
if (vertexShader == 0) {
Log.e(TAG, "Vertex shader compilation failed");
return;
}
String fragmentShaderCode = "precision mediump float;" +
"varying vec4 vFragmentColor;" +
"void main() {" +
" gl_FragColor = vFragmentColor;" +
"}";
// компіляція фрагментного шейдера
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
if (fragmentShader == 0) {
Log.e(TAG, "Fragment shader compilation failed");
return;
}
// створення програми шейдерів
program = GLES20.glCreateProgram();
if (program == 0) {
Log.e(TAG, "Failed to create program");
return;
}
GLES20.glAttachShader(program, vertexShader);
GLES20.glAttachShader(program, fragmentShader);
GLES20.glLinkProgram(program);
// перевірка лінкування програми
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] != GLES20.GL_TRUE) {
Log.e(TAG, "Лінкування програми не вдалося: " + GLES20.glGetProgramInfoLog(program));
GLES20.glDeleteProgram(program);
program = 0;
return;
}
// отримання хендлів атрибутів
positionHandle = GLES20.glGetAttribLocation(program, "vPosition");
colorHandle = GLES20.glGetAttribLocation(program, "vColor");
mvpMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix");
// логування помилок хендлів
if (positionHandle == -1) Log.e(TAG, "Не вдалося отримати: vPosition");
if (colorHandle == -1) Log.e(TAG, "Не вдалося отримати: vColor");
if (mvpMatrixHandle == -1) Log.e(TAG, "Не вдалося отримати: uMVPMatrix");
// видалення шейдерів
GLES20.glDeleteShader(vertexShader);
GLES20.glDeleteShader(fragmentShader);
// ініціалізація випадкових швидкостей (від -180 до +180 град/сек)
speedX = (float) (Math.random() * 360 - 180);
speedY = (float) (Math.random() * 360 - 180);
speedZ = (float) (Math.random() * 360 - 180);
}
@Override
public void onDrawFrame(GL10 unused) {
// перевірка валідності програми
if (program == 0) {
Log.e(TAG, "Шось не працює, малювання зупинено");
return;
}
// очищення буфера кольорів
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// активація програми
GLES20.glUseProgram(program);
// активація атрибутів вершин
GLES20.glEnableVertexAttribArray(positionHandle);
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 12, vertexBuffer);
GLES20.glEnableVertexAttribArray(colorHandle);
GLES20.glVertexAttribPointer(colorHandle, 4, GLES20.GL_FLOAT, false, 16, colorBuffer);
// матриці проекції та виду (без змін)
float[] projectionMatrix = new float[16];
float[] viewMatrix = new float[16];
Matrix.orthoM(projectionMatrix, 0, -1, 1, -1, 1, -5, 5);
Matrix.setLookAtM(viewMatrix, 0, 0, 0, 3, 0f, 0f, 0f, 0f, 1, 0);
// модельна матриця
float[] modelMatrix = new float[16];
Matrix.setIdentityM(modelMatrix, 0);
// випадкове обертання навколо x, y, z
long currentTime = System.currentTimeMillis();
// обчислення часу в секундах
float deltaTime = (currentTime - startTime) / 1000.0f;
// обчислення кутів обертання
float angleX = (speedX * deltaTime) % 360.0f;
float angleY = (speedY * deltaTime) % 360.0f;
float angleZ = (speedZ * deltaTime) % 360.0f;
// застосування обертань (порядок: спочатку z, потім y, потім x — стандарт для евлерових кутів)
Matrix.rotateM(modelMatrix, 0, angleX, 1, 0, 0); // обертання навколо x
Matrix.rotateM(modelMatrix, 0, angleY, 0, 1, 0); // обертання навколо y
Matrix.rotateM(modelMatrix, 0, angleZ, 0, 0, 1); // обертання навколо z
// комбінована матриця mvp
Matrix.multiplyMM(scratch, 0, projectionMatrix, 0, viewMatrix, 0);
Matrix.multiplyMM(mvpMatrix, 0, scratch, 0, modelMatrix, 0);
// передача матриці в шейдер
GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, mvpMatrix, 0);
// малювання трикутника
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
// деактивація атрибутів
GLES20.glDisableVertexAttribArray(positionHandle);
GLES20.glDisableVertexAttribArray(colorHandle);
}
@Override
public void onSurfaceChanged(GL10 unused, int width, int height) {
// налаштування viewport
GLES20.glViewport(0, 0, width, height);
}
// завантаження та компіляція шейдера
private int loadShader(int type, String shaderCode) {
int shader = GLES20.glCreateShader(type);
if (shader != 0) {
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
int[] compileStatus = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
// перевірка статусу компіляції
if (compileStatus[0] == 0) {
Log.e(TAG, "Компляція шейдерів невдала: " + GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
return 0;
}
} else {
Log.e(TAG, "Не вдалося створити шейдер");
}
return shader;
}
}
public class MainActivity extends Activity {
private GLSurfaceView glSurfaceView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
glSurfaceView = findViewById(R.id.gl_surface_view);
// використання OpenGL ES 2.0
// https://www.khronos.org/opengles/
glSurfaceView.setEGLContextClientVersion(2);
// встановлення рендерера
glSurfaceView.setRenderer(new TriangleRenderer());
// безперервний рендеринг для анімації
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
@Override
protected void onPause() {
super.onPause();
// пауза surface view
glSurfaceView.onPause();
}
@Override
protected void onResume() {
super.onResume();
// відновлення surface view
glSurfaceView.onResume();
}
}
==============================================================================================
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.opengl.GLSurfaceView
android:id="@+id/gl_surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment