Вивчаємо OpenGL ES2 для Android Урок №2. Створення трикутників

Урок №2. Створення трикутників

Основу коду та ідеї я черпав звідси:
1. Сатия Коматинени, Дейв Маклін, Саїд Хашимі. Android 3 для професіоналів. Створення додатків для планшетних комп'ютерів і смартфонів.: Пер. з англ. – М: ТОВ «І. Д. Вільямс». 2012 – 1024 с.
2. http://www.learnopengles.com/android-lesson-one-getting-started/

На першому уроці (можна подивитися тутhttps://habrahabr.ru/post/278895/ або albatrossgames.blogspot.com/2016/03/opengl-es-2-android-1-opengl.html#more ) ми з вами навчилися заливати екран одним кольором з допомогою OpenGL ES. Прийшла пора малювати трикутники, а точніше, з допомогою трикутників ми намалюємо вітрильник, який буде циклічно рухатися зліва направо.

image1

Чому трикутники? Справа в тому, що в OpenGL є тільки три графічних примітиву: точка, лінія і трикутник. Корпус яхти (трапеція) і море (прямокутник) теж намальовані за допомогою трикутників. Як відомо, крапку в нашому просторі визначають три координати (x, y, z). Так як наш малюнок плоский, то у всіх точок малюнка одна координата по осі 0z (вона перпендикулярна площині екрану і йде на нас) буде дорівнює нулю. Для прикладу я вказав координати по осі 0х і 0у двох крайніх точок великого вітрила (грота).

" alt=«image2»/>

У коді визначення трьох точок грота виглядає так:

// this triangle is white-blue. First sail is mainsail
final float[] triangle1VerticesData = {
// X, Y, Z,
// R, G, B, A
-0.5 f, -0.25 f, 0.0 f,
1.0 f, 1.0 f, 1.0 f, 1.0 f,

0.0 f, -0.25 f, 0.0 f,
0.8 f, 0.8 f, 1.0 f, 1.0 f,

0.0 f, 0.56 f, 0.0 f,
0.8 f, 0.8 f, 1.0 f, 1.0 f};


Як ви бачите, створює масив даних з плаваючою комою. Для кожної точки вказується її координата і колірна гамма. Перша точка у нас біла, тому вагові коефіцієнти у червоного, зеленого і синього однакові і дорівнюють 1, а от дві інші вершини я подсинил для краси. Обхід точок робиться проти годинникової стрілки
Розмірність координат в OpenGL умовна і буде фактично визначатися кількістю пікселів екрану пристрою по ширині і висоті.
Тепер потрібно створити буфер, куди ми перекачаем дані про точках для OpenGL. Пов'язано це з тим, що OpenGL написаний на С-подібному мовою. Тому ми переводимо наші дані в інший вид, зрозумілий для OpenGL, виділяючи пам'ять.

/** How many bytes per float. */
private final int mBytesPerFloat = 4;
// Initialize the buffers.
mTriangle1Vertices = ByteBuffer.allocateDirect(triangle1VerticesData.length * mBytesPerFloat)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mTriangle1Vertices.put(triangle1VerticesData).position(0);

image

Давайте розглянемо кожну частину. По-перше, ми виділили блок машинної пам'яті, використовуючи ByteBuffer.allocateDirect (); ця пам'ять не буде управлятися Складальником сміття (що важливо). Необхідно сказати методом, наскільки великий блок пам'яті повинен бути в байтах. Так як наші вершини зберігаються в масиві у вигляді змінних float і займають 4 байти на float, ми передаємо triangle1VerticesData.length * mBytesPerFloat. (Нагадаю, що private final int mBytesPerFloat = 4;)

Наступна рядок говорить байтовому буферу, як він повинен організовувати свої байти в машинному коді. Коли справа доходить до значень, які охоплюють кілька байтів, таких як 32-розрядні цілі числа, байти можна записувати в різному порядку, наприклад, від найбільш значущого значення до найменш значущого. Це схоже на написання великого числа або зліва направо або справа наліво. Нам це все одно, але важливо те, що ми використовуємо один і той же порядок, що і система. Ми організуємо це, викликаючи order(ByteOrder.nativeOrder()). Нарешті, краще не мати справу з окремими байтами безпосередньо. Ми хочемо працювати з floats, тому викликаємо FloatBuffer (), щоб отримати FloatBuffer, який містить основні байти. Потім копіюємо, починаючи з нульової позиції, дані з пам'яті Dalvik в машинну пам'ять, викликаючи mTriangle1Vertices.put(triangle1VerticesData).position(0);

Пам'ять буде звільнена, коли процес припиняється, тому нам не потрібно турбуватися про це.

Розуміння матриць
Хороший урок для розуміння матриць www.songho.ca/opengl/gl_transform.html

Щоб зрозуміти матриці, потрібно для початку розібратися, як ми «бачимо» об'єкт OpenGL.
Уявіть, що ви тримаєте в руках фотоапарат і хочете сфотографувати наш вітрильник.

image

Нехай наша камера знаходиться у т. До, ця точка називається точкою огляду. Якщо в коді ми не вкажемо точку огляду, камера буде за замовчуванням в т. з координатами (0,0,0).
Подивимося, як задається камери в коді:

@Override
public void onSurfaceCreated(GL10 glUnused, EGLConfig config)
{
// Set the background clear color to gray.
GLES20.glClearColor(0.5 f, 0.5 f, 0.7 f, 1.0 f);

// Position the eye behind the origin.
final float eyeX = 0.0 f;
final float eyeY = 0.0 f;
final float eyeZ = 1.5 f;

// We are looking toward the distance
final float lookX = 0.0 f;
final float lookY = 0.0 f;
final float lookZ = -5.0 f;

// Set up our vector. This is where our head would be pointing were we holding the camera.
final float upX = 0.0 f;
final float upY = 1.0 f;
final float upZ = 0.0 f;

// Set the view matrix. This matrix can be said to represent the camera position.
// NOTE: In OpenGL 1, a ModelView matrix is used, which is a combination of a model and
// view matrix. In OpenGL 2, we can keep track of these matrices separately if we choose.
Matrix.setLookAtM(mViewMatrix, 0, eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ);

На початку, ми встановили колір фону синьо-сірий, аналогічно, як робили в першому занятті.
GLES20.glClearColor(0.5 f, 0.5 f, 0.7 f, 1.0 f);

Потім розмістили камеру, фактично вказали координати т. К. Як ви бачите, камера у нас зрушена по осі 0z на 1,5 одиниці (відстань ОК).
final float eyeX = 0.0 f;
final float eyeY = 0.0 f;
final float eyeZ = 1.5 f;

У наступних рядках коду ми встановлюємо максимальну дальність, на яку бачить камера, це координата т. 02.
final float lookX = 0.0 f;
final float lookY = 0.0 f;
final float lookZ = -5.0 f;

Таким чином, висота нашої піраміди К02 = 10.
Наступні рядки коду визначають, як орієнтована камера або положення up vector. Із-за невдалих перекладів, у різних статтях допущені неточності в цьому питанні. В нашому коді зараз
final float upX = 0.0 f;
final float upY = 1.0 f;
final float upZ = 0.0 f;

Це означає, що камера розміщена зазвичай, так, якби ви поклали її на горизонтальний стіл і дивиться на вітрильник у т. 0.
habrastorage.org/files/cc8/be0/e15/cc8be0e15a014565ba75c5dc3cdd4406.jpg
Тепер уявіть, що з камери виходять три вектори, як з т. О. Поставивши ваговий коефіцієнт final float upY = 1.0 f, ми говоримо OpenGL, що вгору буде спрямована вісь 0У і бачимо картинку, як на початку статті.
Але варто поставити нам ось такі коефіцієнти
final float upX = 1.0 f;
final float upY = 1.0 f;
final float upZ = 0.0 f;

ми побачимо на емуляторі наступне. Наш вітрильник буде дертися вгору по похилій.
" alt=«image»/>
Камера повернулася на 45 градусів проти годинникової стрілки, якщо дивитися на вісь 0z. Зрозуміло, що якщо зробити таку комбінацію
final float upX = 1.0 f;
final float upY = 0.0 f;
final float upZ = 0.0 f;

вгору буде дивитися вісь 0х камери і кораблик буде плисти вертикально вгору.
Всі наші дані ми передаємо методу setLookAtM.
Matrix.setLookAtM(mViewMatrix, 0, eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ);

Видимий об'єм камери

Розглянемо наступний шматок коду.
@Override
public void onSurfaceChanged(GL10 glUnused, int width, int height)
{
// Set the OpenGL viewport to the same size as the surface.
GLES20.glViewport(0, 0, width, height);

// Create a new perspective projection matrix. The height will stay the same
// while the width буде змінюватись as per aspect ratio.
final float ratio = (float) width / height;
final float left = -ratio;
final float right = ratio;
final float bottom = -1.0 f;
final float top = 1.0 f;
final float near = 1.0 f;
final float far = f 10.0;

Matrix.frustumM(mProjectionMatrix, 0, left, right, bottom, top, near, far);
}

Метод onSurfaceChanged дозволяє відпрацювати зміну орієнтації самого пристрою.
Повернемо на емуляторі наш гаджет і бачимо таку картину
image
Не дуже красиво, але в принципі те, що ми намалювали.
Наступний рядок коду встановлює розмір екрану. Спочатку встановлюємо координати нижньої точки лівого кута екрана (0,0), а потім ширину і висоту екрана.
GLES20.glViewport(0, 0, width, height);
Давайте ще раз розглянемо наш малюнок:

image

Обсяг, який бачить наша камера, укладений в обсязі усіченої піраміди (A1,B1,C1,D1, A2,B2,C2,D2).
Спочатку ми знаходимо відношення ширини до висоти пристрою (ratio).
final float ratio = (float) width / height;
Потім визначаємо координату по 0х лівої і правої сторони видимого паралелепіпеда (A1,B1,C1,D1).
final float left = -ratio;
final float right = ratio;
Визначаємо координату по 0у нижньої і верхньої сторони паралелепіпеда (A1,B1,C1,D1).
final float bottom = -1.0 f;
final float top = 1.0 f;
Відстань від камери до передньої сторони (КО1)
final float near = 1.0 f;
Відстань від камери до задньої сторони (КО2)
final float far = f 10.0;
Застосовуємо матричне перетворення
Matrix.frustumM(mProjectionMatrix, 0, left, right, bottom, top, near, far);
Є кілька різних видів матриць, які ми використовуємо:
1. Матриця моделі. Ця матриця використовується для розміщення моделі де-то у «світі». Наприклад, якщо у вас є модель автомобіля, і ви захочете розмістити її на відстані 1000 м на схід, ви будете використовувати матрицю моделі.
2. Матриця виду. Ця матриця являє собою камеру. Якщо ми хочемо подивитися на нашу машину, яка знаходиться в 1000 м на схід, ми повинні пересунути себе на 1000 м на схід. Чи можна залишитися нерухомими, а весь інший світ пересунути на 1000 м на захід. Щоб зробити це, ми будемо використовувати матрицю виду.
3. Матриця проекції. Так як наші екрани є плоскими, то нам потрібно зробити остаточне перетворення в «проект» нашого виду на екрані і отримати 3D-перспективу. Для цього використовують матрицю проекції.

Визначення верхових і фрагментных шейдерів
Навіть найпростіші малюнки в OpenGL ES 2.0 вимагають створення програмних сегментів, які називаються шейдерами. Шейдери формують ядро OpenGL ES 2.0. Вершини обробляються верховими шейдерами і мають справу тільки з точками вершин. Простору між вершинами обробляють з допомогою фрагментных шейдерів, вони маю справу з кожним пікселем на екрані.

Для написання шейдерів використовують мову програмування OpenGL Shading Language (GLSL).
Кожен шейдер в основному складається з введення, виведення і програми. Спочатку ми визначаємо форму, яка представляє собою комбіновану матрицю, що містить всі наші перетворення. Вона постійна для всіх вершин і використовується для проектування їх на екран. Потім ми визначаємо два атрибута для позиції і кольору. Ці атрибути будуть прочитані з буфера, яке ми визначили раніше, вони задають положення і колір кожної вершини. Потім ми визначимо варіювання (зміна кольору), який інтерполює значення для всього трикутника і передасть його у фрагментний шейдер. Коли справа доходить до фрагментного шейдера, то він буде містити интерполированное значення для кожного пікселя.

Скажімо, ми визначили трикутник, у якого вершини будуть червона, зелена і синя, а його розмір буде займати 10 пікселів на екрані. При виконанні фрагментного шейдера, він буде містити різний змінюється колір кожного пікселя. У якійсь точці, що зміна буде червоним, але на півдорозі між червоним і синім, це може бути пурпурний колір.

Розглянемо наш вершинний шейдер
final String vertexShader =
"uniform mat4 u_MVPMatrix; \n" // A constant representing the combined model/view/projection matrix.

+ "attribute vec4 a_Position; \n" // Per-vertex position information we will pass in.
+ "attribute vec4 a_Color; \n" // Per-vertex color information we will pass in.

+ "varying vec4 v_Color; \n" // This will be passed into the fragment shader.

+ "void main() \n" // The entry point for our vertex shader.
+ "{ \n"
+ " v_Color = a_Color; \n" // Pass through the color to the fragment shader.
// It will be interpolated across the triangle.
+ " gl_Position = u_MVPMatrix \n" // gl_Position is a special variable used to store the final position.
+ " * a_Position; \n" // Multiply the vertex by the matrix to get the final point in
+ "} \n"; // normalized screen coordinates.


Через уніформи (uniform) в шейдери передаються зовнішні дані, які можуть бути використані для розрахунків. Уніформи можуть бути використані тільки для читання. Уніформи можуть бути передані як у верховий, так і в фрагментний шейдери. У нашому випадку уніформа одна — це матриця моделі-виду-проекції u_MVPMatrix і передається вона в вершинний шейдер. Ключове слово mat4 означає, що це матриця розміром 4х4 складається з чисел з плаваючою точкою. Уніформи ніяк не пов'язані з конкретною вершиною і є глобальними константами. Для назви уніформ зазвичай використовують префікс u_.
Атрибутів (attribute) — це властивість вершини. У вершини можуть бути різні атрибути. Наприклад, координати положення у просторі, координати вектора нормалі, колір. Крім того, ви можете передавати в вершинний шейдер якісь свої атрибути. Важливо зрозуміти, що атрибут-це властивість вершини і тому він повинен бути заданий для кожної вершини. Атрибути передаються тільки в вершинний шейдер. Атрибути доступні вершинних шейдеру тільки для читання. Не можна визначати атрибути під фрагментном шейдере. В подальшому для зручності будемо позначати атрибути з префіксом a_.

Визначимо вершинном шейдере три атрибути:
attribute vec4 a_Position;
Змінна a_Position – атрибут вершини, який має справу з положенням вершини (координатами), це четырехкомпанентный вектор (vec4).
Атрибут кольору вершини
attribute vec4 a_Color;
Атрибут інтерполяції кольору
varying vec4 v_Color;
Розглянемо код функції main детальніше:
v_Color = a_Color;
Передаємо інформацію про кольорі вершин у фрагментний шейдер.
gl_Position = u_MVPMatrix * a_Position;
Трансформуємо положення вершин з допомогою матриці і записуємо в нову змінну gl_Position.
Системна змінна gl_Position — це чотирьох-компонентний вектор, що визначає координати вершини, спроектовані на площину екрану. Змінна gl_Position обов'язково повинна бути визначена в вершинном шейдере, інакше на екрані ми нічого не побачимо.

Приступимо до розгляду фрагментного шейдера.
final String fragmentShader =
"precision mediump float; \n" // Set the default precision to medium. We don't need as high of a
// precision in the fragment shader.
+ "varying vec4 v_Color; \n" // This is the color from the vertex shader interpolated across the
// triangle per fragment.
+ "void main() \n" // The entry point for our fragment shader.
+ "{ \n"
+ " gl_FragColor = v_Color; \n" // Pass the color directly through the pipeline.
+ "} \n";


Точність за замовчуванням встановлюємо середню, так як вона не потрібна нам високою у випадку фрагментного шейдера. У вершинном шейдере точність за замовчуванням висока.
precision mediump float;
Кінцева мета фрагментного шейдерів — це отримання кольору пікселя. Розрахований колір пікселя обов'язково повинен бути записаний в системну змінну gl_FragColor. У нашому простому прикладі ми не обчислюємо колір пікселя під фрагментном шейдере, а просто присвоюємо значення кольору v_color, отриманого шляхом інтерполяції кольорів вершин:
gl_FragColor = v_color;

Завантаження шейдерів в OpenGL
// Load in the vertex shader.
int vertexShaderHandle = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);

if (vertexShaderHandle != 0)
{
// Pass in the shader source.
GLES20.glShaderSource(vertexShaderHandle, vertexShader);

// Compile the shader.
GLES20.glCompileShader(vertexShaderHandle);

// Get the compilation status.
final int[] compileStatus = new int[1];
GLES20.glGetShaderiv(vertexShaderHandle, GLES20.GL_COMPILE_STATUS, compileStatus, 0);

// If the compilation failed, delete the shader.
if (compileStatus[0] == 0)
{
GLES20.glDeleteShader(vertexShaderHandle);
vertexShaderHandle = 0;
}
}

if (vertexShaderHandle == 0)
{
throw new RuntimeException("Error creating vertex shader.");
}

Зв'язування вершинного і фрагментного шейдерів разом у програмі
// Create a program object and store the handle to it.
int programHandle = GLES20.glCreateProgram();

if (programHandle != 0)
{
// Bind the vertex shader to the program.
GLES20.glAttachShader(programHandle, vertexShaderHandle);

// Bind the fragment shader to the program.
GLES20.glAttachShader(programHandle, fragmentShaderHandle);

// Bind attributes
GLES20.glBindAttribLocation(programHandle, 0, "a_Position");
GLES20.glBindAttribLocation(programHandle, 1, "a_Color");

// Link the two shaders together into a program.
GLES20.glLinkProgram(programHandle);

// Get the link status.
final int[] linkStatus = new int[1];
GLES20.glGetProgramiv(programHandle, GLES20.GL_LINK_STATUS, linkStatus, 0);

// If the link failed, delete the program.
if (linkStatus[0] == 0)
{
GLES20.glDeleteProgram(programHandle);
programHandle = 0;
}
}

if (programHandle == 0)
{
throw new RuntimeException("Error creating program.");
}



Перед тим як використовувати наші верховий і фрагментний шейдери, нам потрібно зв'язати їх разом в програмі. Це те, що з'єднує вихід вершинного шейдера зі входом фрагментного шейдера. Це також те, що дозволяє нам передати вхідні дані з нашої програми і використовувати шейдери, щоб малювати наші фігури.

Ми створюємо новий програмний об'єкт, і якщо це вдалося, ми потім приєднуємо наші шейдери. Ми хочемо передати дані про стан і колір, як атрибути, так що нам потрібно, щоб зв'язати ці атрибути. Потім з'єднаємо шейдери разом.

//New class members
/** This will be used to pass in the transformation matrix. */
private int mMVPMatrixHandle;

/** This will be used to pass in model position information. */
private int mPositionHandle;

/** This will be used to pass in model color information. */
private int mColorHandle;

@Override
public void onSurfaceCreated(GL10 glUnused, EGLConfig config)
{
...

// Set program handles. These will later be used to pass in values to the program.
mMVPMatrixHandle = GLES20.glGetUniformLocation(programHandle, "u_MVPMatrix");
mPositionHandle = GLES20.glGetAttribLocation(programHandle, "a_Position");
mColorHandle = GLES20.glGetAttribLocation(programHandle, "a_Color");

// Tell OpenGL to use this program when rendering.
GLES20.glUseProgram(programHandle);
}


Після того, як ми успішно зв'язали нашу програму, ми закінчимо з парою великих завдань, тепер ми реально можемо використовувати її. Перша задача полягає в отриманні посилань, тому ми можемо передавати дані в програму. Тоді ми говоримо OpenGL використовувати цю програму, коли відбувається малювання. Так як ми використовуємо тільки одну програму в цьому уроці, ми можемо поставити це в onSurfaceCreated () замість onDrawFrame ().

Установка перспективної проекції
// New class members
/** Store the projection matrix. This is used to project the scene onto a 2D viewport. */
private float[] mProjectionMatrix = new float[16];

@Override
public void onSurfaceChanged(GL10 glUnused, int width, int height)
{
// Set the OpenGL viewport to the same size as the surface.
GLES20.glViewport(0, 0, width, height);

// Create a new perspective projection matrix. The height will stay the same
// while the width буде змінюватись as per aspect ratio.
final float ratio = (float) width / height;
final float left = -ratio;
final float right = ratio;
final float bottom = -1.0 f;
final float top = 1.0 f;
final float near = 1.0 f;
final float far = f 10.0;

Matrix.frustumM(mProjectionMatrix, 0, left, right, bottom, top, near, far);
}

Наш метод onSurfaceChanged () викликається принаймні один раз, а також всякий раз, коли наша поверхня змінюється. Так як нам треба скидати нашу матрицю проекції всякий раз, коли на екрані проектування змінилася, то onSurfaceChanged () є ідеальним місцем, щоб зробити це.

Висновок об'єктів на екран
Висновок виробляємо в методі onDrawFrame(GL10 glUnused)
Щоб трикутники рухалися по осі 0х, застосовуємо матрицю переміщення і задаємо збільшення зміщення по х на 0,001 за кожне оновлення поверхні. Як тільки х досягає 1 або правого краю екрана, обнуляем його.

Matrix.translateM(mModelMatrix, 0, x + 0.3 f, 0.0 f, 0.0 f);
drawTriangle(mTriangle2Vertices);
if(x < =1){x = (float) (x + 0.001);}
else {x=0;}
Думаю для другого уроку більш ніж достатньо. Багато питань втрачені, і їхнє розуміння прийде пізніше.

Запуск уроку на Андроїд Студіо.

Я рекомендую використовувати проект попереднього уроку і скопіювати в нього даний код.
Якщо у вас його немає, то створіть проект First Open GL Project
AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.adc2017gmail.firstopenglproject">

<application

android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />

<activity android:name=".FirstOpenGLProjectActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>


FirstOpenGLProjectActivity.java

package com.adc2017gmail.firstopenglproject;


import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ConfigurationInfo;
import android.opengl.GLSurfaceView;
import android.os.Bundle;

public class FirstOpenGLProjectActivity extends Activity
{
/** Hold a reference to our GLSurfaceView */
private GLSurfaceView mGLSurfaceView;

@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);

mGLSurfaceView = new GLSurfaceView(this);

// Check if the system supports OpenGL ES 2.0.
final ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
final boolean supportsEs2 = configurationInfo.reqGlEsVersion >= 0x20000;

if (supportsEs2)
{
// Request an OpenGL ES 2.0 compatible context.
mGLSurfaceView.setEGLContextClientVersion(2);

// Set the renderer to our demo renderer, defined below.
mGLSurfaceView.setRenderer(new LessonOneRenderer());
}
else
{
// This is where you could create an OpenGL ES 1.x compatible
// renderer if you wanted to support both ES 1 and ES 2.
return;
}

setContentView(mGLSurfaceView);
}

@Override
protected void onResume()
{
// The activity must call the GL surface view's onResume() on activity onResume().
super.onResume();
mGLSurfaceView.onResume();
}

@Override
protected void onPause()
{
// The activity must call the GL surface view's onPause() on activity onPause().
super.onPause();
mGLSurfaceView.onPause();
}
}


LessonOneRenderer.java

package com.adc2017gmail.firstopenglproject;

import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

/**
* This class implements our custom renderer. Note that the GL10 parameter passed in is unused for OpenGL ES 2.0
* renderers -- the static class GLES20 is used instead.
*/
public class LessonOneRenderer implements GLSurfaceView.Renderer
{
/**
* Store the model matrix. This matrix is used to move models from object space (where each model can be thought
* of being located at the center of the universe) to world space.
*/
private float[] mModelMatrix = new float[16];

/**
* Store the view matrix. This can be thought of as our camera. This matrix transforms world space to eye space;
* it positions things relative to our eye.
*/
private float[] mViewMatrix = new float[16];

/** Store the projection matrix. This is used to project the scene onto a 2D viewport. */
private float[] mProjectionMatrix = new float[16];

/** Allocate storage for the final combined matrix. This will be passed into the shader program. */
private float[] mMVPMatrix = new float[16];

/** Our Store model data in a float buffer. */
private final FloatBuffer mTriangle1Vertices;
private final FloatBuffer mTriangle2Vertices;
private final FloatBuffer mTriangle3Vertices;
private final FloatBuffer mTriangle4Vertices;
private final FloatBuffer mTriangle5Vertices;
private final FloatBuffer mTriangle6Vertices;


/** This will be used to pass in the transformation matrix. */
private int mMVPMatrixHandle;

/** This will be used to pass in model position information. */
private int mPositionHandle;

/** This will be used to pass in model color information. */
private int mColorHandle;

/** How many bytes per float. */
private final int mBytesPerFloat = 4;

/** How many elements per vertex. */
private final int mStrideBytes = 7 * mBytesPerFloat;

/** Offset of the data position. */
private final int mPositionOffset = 0;

/** Size of the data position in elements. */
private final int mPositionDataSize = 3;

/** Offset of the color data. */
private final int mColorOffset = 3;

/** Size of the color data in elements. */
private final int mColorDataSize = 4;
private float x;


/**
* Initialize the model data.
*/
public LessonOneRenderer()
{
// Define points for equilateral triangles.

// This triangle is white_blue.First sail is mainsail
final float[] triangle1VerticesData = {
// X, Y, Z,
// R, G, B, A
-0.5 f, -0.25 f, 0.0 f,
1.0 f, 1.0 f, 1.0 f, 1.0 f,

0.0 f, -0.25 f, 0.0 f,
0.8 f, 0.8 f, 1.0 f, 1.0 f,

0.0 f, 0.56 f, 0.0 f,
0.8 f, 0.8 f, 1.0 f, 1.0 f};
// This triangle is white_blue..The second is called the jib sail
final float[] triangle2VerticesData = {
// X, Y, Z,
// R, G, B, A
-0.25 f, -0.25 f, 0.0 f,
0.8 f, 0.8 f, 1.0 f, 1.0 f,

0.03 f, -0.25 f, 0.0 f,
1.0 f, 1.0 f, 1.0 f, 1.0 f,

-0.25 f, 0.4 f, 0.0 f,
0.8 f, 0.8 f, 1.0 f, 1.0 f};

// This triangle3 is blue.
final float[] triangle3VerticesData = {
// X, Y, Z,
// R, G, B, A
-1.0 f, -1.5 f, 0.0 f,
0.0 f, 0.0 f, 1.0 f, 1.0 f,

1.0 f, -0.35 f, 0.0 f,
0.0 f, 0.0 f, 1.0 f, 1.0 f,

-1.0 f, -0.35 f, 0.0 f,
0.0 f, 0.0 f, 1.0 f, 1.0 f};


// This triangle4 is blue.
final float[] triangle4VerticesData = {
// X, Y, Z,
// R, G, B, A
-1.0 f, -1.5 f, 0.0 f,
0.0 f, 0.0 f, 1.0 f, 1.0 f,

1.0 f, -1.5 f, 0.0 f,
0.0 f, 0.0 f, 1.0 f, 1.0 f,

1.0 f, -0.35 f, 0.0 f,
0.0 f, 0.0 f, 1.0 f, 1.0 f};

// This triangle5 is brown.
final float[] triangle5VerticesData = {
// X, Y, Z,
// R, G, B, A
-0.4 f, -0.3 f, 0.0 f,
0.7 f, 0.3 f, 0.4 f, 1.0 f,

-0.4 f, -0.4 f, 0.0 f,
0.7 f, 0.3 f, 0.4 f, 1.0 f,

0.3 f, -0.3 f, 0.0 f,
0.7 f, 0.3 f, 0.4 f, 1.0 f};

// This triangle6 is brown.
final float[] triangle6VerticesData = {
// X, Y, Z,
// R, G, B, A
-0.4 f, -0.4 f, 0.0 f,
0.7 f, 0.3 f, 0.4 f, 1.0 f,

0.22 f, -0.4 f, 0.0 f,
0.7 f, 0.3 f, 0.4 f, 1.0 f,

0.3 f, -0.3 f, 0.0 f,
0.7 f, 0.3 f, 0.4 f, 1.0 f};


// Initialize the buffers.
mTriangle1Vertices = ByteBuffer.allocateDirect(triangle1VerticesData.length * mBytesPerFloat)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mTriangle2Vertices = ByteBuffer.allocateDirect(triangle2VerticesData.length * mBytesPerFloat)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mTriangle3Vertices = ByteBuffer.allocateDirect(triangle3VerticesData.length * mBytesPerFloat)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mTriangle4Vertices = ByteBuffer.allocateDirect(triangle4VerticesData.length * mBytesPerFloat)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mTriangle5Vertices = ByteBuffer.allocateDirect(triangle5VerticesData.length * mBytesPerFloat)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mTriangle6Vertices = ByteBuffer.allocateDirect(triangle6VerticesData.length * mBytesPerFloat)
.order(ByteOrder.nativeOrder()).asFloatBuffer();




mTriangle1Vertices.put(triangle1VerticesData).position(0);
mTriangle2Vertices.put(triangle2VerticesData).position(0);
mTriangle3Vertices.put(triangle3VerticesData).position(0);

mTriangle4Vertices.put(triangle4VerticesData).position(0);
mTriangle5Vertices.put(triangle5VerticesData).position(0);
mTriangle6Vertices.put(triangle6VerticesData).position(0);
}

@Override
public void onSurfaceCreated(GL10 glUnused, EGLConfig config)
{
// Set the background clear color to gray.
GLES20.glClearColor(0.5 f, 0.5 f, 0.7 f, 1.0 f);

// Position the eye behind the origin.
final float eyeX = 0.0 f;
final float eyeY = 0.0 f;
final float eyeZ = 1.5 f;

// We are looking toward the distance
final float lookX = 0.0 f;
final float lookY = 0.0 f;
final float lookZ = -5.0 f;

// Set up our vector. This is where our head would be pointing were we holding the camera.
final float upX = 0.0 f;
final float upY = 1.0 f;
final float upZ = 0.0 f;

// Set the view matrix. This matrix can be said to represent the camera position.
// NOTE: In OpenGL 1, a ModelView matrix is used, which is a combination of a model and
// view matrix. In OpenGL 2, we can keep track of these matrices separately if we choose.
Matrix.setLookAtM(mViewMatrix, 0, eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ);

final String vertexShader =
"uniform mat4 u_MVPMatrix; \n" // A constant representing the combined model/view/projection matrix.

+ "attribute vec4 a_Position; \n" // Per-vertex position information we will pass in.
+ "attribute vec4 a_Color; \n" // Per-vertex color information we will pass in.

+ "varying vec4 v_Color; \n" // This will be passed into the fragment shader.

+ "void main() \n" // The entry point for our vertex shader.
+ "{ \n"
+ " v_Color = a_Color; \n" // Pass through the color to the fragment shader.
// It will be interpolated across the triangle.
+ " gl_Position = u_MVPMatrix \n" // gl_Position is a special variable used to store the final position.
+ " * a_Position; \n" // Multiply the vertex by the matrix to get the final point in
+ "} \n"; // normalized screen coordinates.

final String fragmentShader =
"precision mediump float; \n" // Set the default precision to medium. We don't need as high of a
// precision in the fragment shader.
+ "varying vec4 v_Color; \n" // This is the color from the vertex shader interpolated across the
// triangle per fragment.
+ "void main() \n" // The entry point for our fragment shader.
+ "{ \n"
+ " gl_FragColor = v_Color; \n" // Pass the color directly through the pipeline.
+ "} \n";

// Load in the vertex shader.
int vertexShaderHandle = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);

if (vertexShaderHandle != 0)
{
// Pass in the shader source.
GLES20.glShaderSource(vertexShaderHandle, vertexShader);

// Compile the shader.
GLES20.glCompileShader(vertexShaderHandle);

// Get the compilation status.
final int[] compileStatus = new int[1];
GLES20.glGetShaderiv(vertexShaderHandle, GLES20.GL_COMPILE_STATUS, compileStatus, 0);

// If the compilation failed, delete the shader.
if (compileStatus[0] == 0)
{
GLES20.glDeleteShader(vertexShaderHandle);
vertexShaderHandle = 0;
}
}

if (vertexShaderHandle == 0)
{
throw new RuntimeException("Error creating vertex shader.");
}

// Load in the fragment shader shader.
int fragmentShaderHandle = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);

if (fragmentShaderHandle != 0)
{
// Pass in the shader source.
GLES20.glShaderSource(fragmentShaderHandle, fragmentShader);

// Compile the shader.
GLES20.glCompileShader(fragmentShaderHandle);

// Get the compilation status.
final int[] compileStatus = new int[1];
GLES20.glGetShaderiv(fragmentShaderHandle, GLES20.GL_COMPILE_STATUS, compileStatus, 0);

// If the compilation failed, delete the shader.
if (compileStatus[0] == 0)
{
GLES20.glDeleteShader(fragmentShaderHandle);
fragmentShaderHandle = 0;
}
}

if (fragmentShaderHandle == 0)
{
throw new RuntimeException("Error creating fragment shader.");
}

// Create a program object and store the handle to it.
int programHandle = GLES20.glCreateProgram();

if (programHandle != 0)
{
// Bind the vertex shader to the program.
GLES20.glAttachShader(programHandle, vertexShaderHandle);

// Bind the fragment shader to the program.
GLES20.glAttachShader(programHandle, fragmentShaderHandle);

// Bind attributes
GLES20.glBindAttribLocation(programHandle, 0, "a_Position");
GLES20.glBindAttribLocation(programHandle, 1, "a_Color");

// Link the two shaders together into a program.
GLES20.glLinkProgram(programHandle);

// Get the link status.
final int[] linkStatus = new int[1];
GLES20.glGetProgramiv(programHandle, GLES20.GL_LINK_STATUS, linkStatus, 0);

// If the link failed, delete the program.
if (linkStatus[0] == 0)
{
GLES20.glDeleteProgram(programHandle);
programHandle = 0;
}
}

if (programHandle == 0)
{
throw new RuntimeException("Error creating program.");
}

// Set program handles. These will later be used to pass in values to the program.
mMVPMatrixHandle = GLES20.glGetUniformLocation(programHandle, "u_MVPMatrix");
mPositionHandle = GLES20.glGetAttribLocation(programHandle, "a_Position");
mColorHandle = GLES20.glGetAttribLocation(programHandle, "a_Color");

// Tell OpenGL to use this program when rendering.
GLES20.glUseProgram(programHandle);
}

@Override
public void onSurfaceChanged(GL10 glUnused, int width, int height)
{
// Set the OpenGL viewport to the same size as the surface.
GLES20.glViewport(0, 0, width, height);

// Create a new perspective projection matrix. The height will stay the same
// while the width буде змінюватись as per aspect ratio.
final float ratio = (float) width / height;
final float left = -ratio;
final float right = ratio;
final float bottom = -1.0 f;
final float top = 1.0 f;
final float near = 1.0 f;
final float far = f 10.0;

Matrix.frustumM(mProjectionMatrix, 0, left, right, bottom, top, near, far);
}

@Override
public void onDrawFrame(GL10 glUnused)
{
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);

// Draw the triangle facing straight on.
Matrix.setIdentityM(mModelMatrix, 0);
Matrix.translateM(mModelMatrix, 0, x, 0.0 f, 0.0 f);
drawTriangle(mTriangle1Vertices);

// Draw triangle_2.
Matrix.setIdentityM(mModelMatrix, 0);
Matrix.translateM(mModelMatrix, 0, x + 0.3 f, 0.0 f, 0.0 f);
drawTriangle(mTriangle2Vertices);
if(x < =1){x = (float) (x + 0.001);}
else {x=0;}

// Draw triangle_3.
Matrix.setIdentityM(mModelMatrix, 0);
drawTriangle(mTriangle3Vertices);

// Draw triangle_4.
Matrix.setIdentityM(mModelMatrix, 0);
drawTriangle(mTriangle4Vertices);

// Draw triangle_5. Boat
Matrix.setIdentityM(mModelMatrix, 0);
Matrix.translateM(mModelMatrix, 0, x, 0.0 f, 0.0 f);
//Matrix.rotateM(mModelMatrix, 0, 0, 0.0 f, 0.0 f, 1.0 f);
drawTriangle(mTriangle5Vertices);

// Draw triangle_6. Boat
Matrix.setIdentityM(mModelMatrix, 0);
Matrix.translateM(mModelMatrix, 0, x, 0.0 f, 0.0 f);
//Matrix.rotateM(mModelMatrix, 0, 0, 0.0 f, 0.0 f, 1.0 f);
drawTriangle(mTriangle6Vertices);

}

/**
* Draws a triangle from the given vertex data.
*
* @param aTriangleBuffer The buffer containing the vertex data.
*/
private void drawTriangle(final FloatBuffer aTriangleBuffer)
{
// Pass in the position information
aTriangleBuffer.position(mPositionOffset);
GLES20.glVertexAttribPointer(mPositionHandle, mPositionDataSize, GLES20.GL_FLOAT, false,
mStrideBytes, aTriangleBuffer);

GLES20.glEnableVertexAttribArray(mPositionHandle);

// Pass in the color information
aTriangleBuffer.position(mColorOffset);
GLES20.glVertexAttribPointer(mColorHandle, mColorDataSize, GLES20.GL_FLOAT, false,
mStrideBytes, aTriangleBuffer);

GLES20.glEnableVertexAttribArray(mColorHandle);

// This multiplies the view matrix by the model matrix, and stores the result in the matrix MVP
// (which currently contains model * view).
Matrix.multiplyMM(mMVPMatrix, 0, mViewMatrix, 0, mModelMatrix, 0);

// This multiplies the modelview matrix by the projection matrix, and stores the result in the matrix MVP
// (which now contains model * view * projection).
Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0);

GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVPMatrix, 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
}
}


Список джерел

1. Сатия Коматинени, Дейв Маклін, Саїд Хашимі. Android 3 для професіоналів. Створення додатків для планшетних комп'ютерів і смартфонів.: Пер. з англ. – М: ТОВ «І. Д. Вільямс». 2012 – 1024 с.
2. http://www.learnopengles.com/android-lesson-one-getting-started/
3. http://andmonahov.blogspot.com/2012/10/opengl-es-20-1.html
4. https://www.opengl.org/sdk/docs/tutorials/ClockworkCoders/attributes.php
5. https://www.khronos.org/opengles/sdk/docs/reference_cards/OpenGL-ES-2_0-Reference-card.pdf
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.