Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
OpenGL ES 1и2.docx
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
4.8 Mб
Скачать

Коды шейдеров.

Объединим полученные знания и напишем коды шейдеров для освещения.

Код вершинного шейдера:

// принимаем матрицу модели-вида-проекции

uniform mat4 u_modelViewProjectionMatrix;

// принимаем координаты вершины

attribute vec3 a_vertex;

// принимаем вектор нормали для вершины

attribute vec3 a_normal;

// принимаем цвет вершины

attribute vec4 a_color;

// определяем переменную для передачи координат вершины на интерполяцию

varying vec3 v_vertex;

// определяем переменную для передачи нормали вершины на интерполяцию

varying vec3 v_normal;

// определяем переменную для передачи цвета вершины на интерполяцию

varying vec4 v_color;

void main() {

        //отправляем координаты вершины на интерполяцию

        v_vertex=a_vertex;

        //нормализуем принятый вектор нормали, т.к. он может быть не нормализован 

        vec3 n_normal=normalize(a_normal);

        // передаем вектор нормали вершины на интерполяцию

        v_normal=n_normal;

        // передаем цвет вершины на интерполяцию

        v_color=a_color;

        // высчитываем координаты вершины в проекции на экран

        gl_Position = u_modelViewProjectionMatrix * vec4(a_vertex,1.0);

};

Код фрагментного шейдера:

precision mediump float;

//принимаем координаты камеры

uniform vec3 u_camera;

//принимаем координаты источника света

uniform vec3 u_lightPosition;

//принимаем координаты пикселя для поверности после интерполяции

varying vec3 v_vertex;

//принимаем вектор нормали для пикселя после интерполяции

varying vec3 v_normal;

//принимаем цвет пикселя после интерполяции

varying vec4 v_color;

void main() {

        //повторно нормализуем нормаль пикселя, т.к. при интерполяции нормализация может нарушиться

        vec3 n_normal=normalize(v_normal);

       //вычисляем единичный вектор, указывающий из пикселя на источник света

        vec3 lightvector = normalize(u_lightPosition - v_vertex);

       //вычисляем единичный вектор, указывающий из пикселя на камеру

       vec3 lookvector = normalize(u_camera - v_vertex);

       //определяем яркость фонового освещения

       float ambient=0.2;

       //определяем коэффициент диффузного освещения

       float k_diffuse=0.8;

       //определяем коэффициент зеркального освещения

       float k_specular=0.4;

       //вычисляем яркость диффузного освещения пикселя

       float diffuse = k_diffuse * max(dot(n_normal, lightvector), 0.0);

       //вычисляем вектор отраженного луча света 

       vec3 reflectvector = reflect(-lightvector, n_normal);

       //вычисляем яркость зеркального освещения пикселя

       float specular = k_specular * pow( max(dot(lookvector,reflectvector),0.0), 40.0 );

      //определяем вектор белого цвета

      vec4 one=vec4(1.0,1.0,1.0,1.0);

      //вычисляем цвет пикселя

      gl_FragColor = (ambient+diffuse+specular)*one;

};

Практика. Освещение плоской поверхности.

OpenGL ES 2.0 поддерживается ОС "Android" начиная с версии 2.2.  

Перейдем к практике. Чтобы подготовить программу к работе с OpenGL ES 2.0 нужно внести изменения в файл манифеста, а именно в секцию uses-feature добавить строку: 

android:glEsVersion="0x00020000". Например, так:

...........................

<uses-sdk     android:minSdkVersion="8"     android:targetSdkVersion="15" /> <uses-feature android:glEsVersion="0x00020000" />     <application         android:icon="@drawable/ic_launcher"         android:label="@string/app_name"         android:theme="@style/AppTheme" > ...........................

Кроме того, в конструкторе собственного класса, расширяющего класс GLSurfaceView нужно обязательно прописать команду setEGLContextClientVersion(2). Пример:

/ /Опишем наш класс MyClassSurfaceView расширяющий GLSurfaceView public class MyClassSurfaceView extends GLSurfaceView{         //создадим ссылку для хранения экземпляра нашего класса рендерера         private MyClassRenderer renderer;         // конструктор         public MyClassSurfaceView(Context context) {                 // вызовем конструктор родительского класса GLSurfaceView                 super(context);                 // установим версию OpenGL ES 2.0                 setEGLContextClientVersion(2);                 // создадим экземпляр нашего класса MyClassRenderer                 renderer = new MyClassRenderer(context);                // запускаем рендерер                setRenderer(renderer);                // установим режим циклического запуска метода onDrawFrame                 setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);                // при этом запускается отдельный поток                // в котором циклически вызывается метод onDrawFrame                // т.е. бесконечно происходит перерисовка кадров         } }

Чтобы не усложнять урок будем рисовать квадрат, расположенный в плоскости XZ:

Поместим источник света над началом координат. У квадрата четыре вершины, поэтому его можно разбить на два треугольника и рисовать при помощи правила обхода GL_TRIANGLE_STRIP. Обход вершин в треугольниках выполняется против часовой стрелки для того, чтобы нормаль вершин была направлена вверх вдоль оси Y:

Напишем рабочий код для рисования квадрата с освещением. Напомню, что класс Shader был описан в первом уроке. Отмечу, что при создании экрана шейдерные объекты разрушаются. Поэтому объект класса Shader должен создаваться в методе onSurfaceCreated. public class MyClassRenderer implements GLSurfaceView.Renderer{         // интерфейс GLSurfaceView.Renderer содержит         // три метода onDrawFrame, onSurfaceChanged, onSurfaceCreated         // которые должны быть переопределены         // текущий контекст         private Context context;         //координаты камеры         private float xСamera, yCamera, zCamera;         //координаты источника света         private float xLightPosition, yLightPosition, zLightPosition;         //матрицы         private float[] modelMatrix;         private float[] viewMatrix;         private float[] modelViewMatrix;         private float[] projectionMatrix;         private float[] modelViewProjectionMatrix;         //буфер для координат вершин         private FloatBuffer vertexBuffer;         //буфер для нормалей вершин         private FloatBuffer normalBuffer;         //буфер для цветов вершин         private FloatBuffer colorBuffer;         //шейдерный объект         private Shader mShader;         //конструктор          public MyClassRenderer(Context context) {                 // запомним контекст он нам понадобится в будущем для загрузки текстур                 this.context=context;                 //координаты точечного источника света                 xLightPosition=0;                 yLightPosition=0.6f;                 zLightPosition=0;                 //матрицы                 modelMatrix=new float[16];                 viewMatrix=new float[16];                 modelViewMatrix=new float[16];                 projectionMatrix=new float[16];                 modelViewProjectionMatrix=new float[16];                 //мы не будем двигать объекты поэтому сбрасываем модельную матрицу на единичную                 Matrix.setIdentityM(modelMatrix, 0);                 //координаты камеры                 xСamera=0.6f;                 yCamera=3.4f;                 zCamera=3f;                 //пусть камера смотрит на начало координат и верх у камеры будет вдоль оси Y                 //зная координаты камеры получаем матрицу вида                 Matrix.setLookAtM(viewMatrix, 0, xСamera, yCamera, zCamera, 0, 0, 0, 0, 1, 0);                 // умножая матрицу вида на матрицу модели получаем матрицу модели-вида                 Matrix.multiplyMM(modelViewMatrix, 0, viewMatrix, 0, modelMatrix, 0);                //координаты вершины 1                 float x1=-2;                 float y1=0;                 float z1=-2;                //координаты вершины 2                 float x2=-2;                 float y2=0;                 float z2=2;                //координаты вершины 3                 float x3=2;                 float y3=0;                 float z3=-2;                //координаты вершины 4                 float x4=2;                 float y4=0;                 float z4=2;                 //запишем координаты всех вершин в единый массив                 float vertexArray [] = {x1,y1,z1, x2,y2,z2, x3,y3,z3, x4,y4,z4};                 //создадим буфер для хранения координат вершин                 ByteBuffer bvertex = ByteBuffer.allocateDirect(vertexArray.length*4);                 bvertex.order(ByteOrder.nativeOrder());                 vertexBuffer = bvertex.asFloatBuffer();                 vertexBuffer.position(0);                 //перепишем координаты вершин из массива в буфер                 vertexBuffer.put(vertexArray);                 vertexBuffer.position(0);                 //вектор нормали перпендикулярен плоскости квадрата                 //и направлен вдоль оси Y                 float nx=0;                 float ny=1;                 float nz=0;                 //нормаль одинакова для всех вершин квадрата,                 //поэтому переписываем координаты вектора нормали в массив 4 раза                 float normalArray [] ={nx, ny, nz,   nx, ny, nz,   nx, ny, nz,   nx, ny, nz};

               //создадим буфер для хранения координат векторов нормали                 ByteBuffer bnormal = ByteBuffer.allocateDirect(normalArray.length*4);                 bnormal.order(ByteOrder.nativeOrder());                 normalBuffer = bnormal.asFloatBuffer();                 normalBuffer.position(0);                //перепишем координаты нормалей из массива в буфер                 normalBuffer.put(normalArray);                 normalBuffer.position(0);                //разукрасим вершины квадрата, зададим цвета для вершин                //цвет первой вершины - красный                 float red1=1;                 float green1=0;                 float blue1=0;                //цвет второй вершины - зеленый                 float red2=0;                 float green2=1;                 float blue2=0;                 //цвет третьей вершины - синий                 float red3=0;                 float green3=0;                 float blue3=1;                 //цвет четвертой вершины - желтый                 float red4=1;                 float green4=1;                 float blue4=0;                //перепишем цвета вершин в массив                //четвертый компонент цвета (альфу) примем равным единице                 float colorArray [] = {                                  red1, green1, blue1, 1,                                 red2, green2, blue2, 1,                                 red3, green3, blue3, 1,                                 red4, green4, blue4, 1,                                 };                 //создадим буфер для хранения цветов вершин                 ByteBuffer bcolor = ByteBuffer.allocateDirect(colorArray.length*4);                 bcolor.order(ByteOrder.nativeOrder());                 colorBuffer = bcolor.asFloatBuffer();                 colorBuffer.position(0);                 //перепишем цвета вершин из массива в буфер                 colorBuffer.put(colorArray);                 colorBuffer.position(0);         }//конец конструктора         //метод, который срабатывает при изменении размеров экрана         //в нем мы получим матрицу проекции и матрицу модели-вида-проекции         public void onSurfaceChanged(GL10 unused, int width, int height) {                 // устанавливаем glViewport                 GLES20.glViewport(0, 0, width, height);                 float ratio = (float) width / height;                 float k=0.055f;                 float left = -k*ratio;                 float right = k*ratio;                 float bottom = -k;                 float top = k;                 float near = 0.1f;                 float far = 10.0f;                 // получаем матрицу проекции                 Matrix.frustumM(projectionMatrix, 0, left, right, bottom, top, near, far);                 // матрица проекции изменилась, поэтому нужно пересчитать матрицу модели-вида-проекции                 Matrix.multiplyMM(modelViewProjectionMatrix, 0, projectionMatrix, 0, modelViewMatrix, 0);         }                   //метод, который срабатывает при создании экрана         //здесь мы создаем шейдерный объект         public void onSurfaceCreated(GL10 unused, EGLConfig config) {                 //включаем тест глубины                 GLES20.glEnable(GLES20.GL_DEPTH_TEST);                 //включаем отсечение невидимых граней                 GLES20.glEnable(GLES20.GL_CULL_FACE);                  //включаем сглаживание текстур, это пригодится в будущем                 GLES20.glHint(GLES20.GL_GENERATE_MIPMAP_HINT, GLES20.GL_NICEST);                 //записываем код вершинного шейдера в виде строки                 String vertexShaderCode=                 "uniform mat4 u_modelViewProjectionMatrix;"+                  "attribute vec3 a_vertex;"+                 "attribute vec3 a_normal;"+                 "attribute vec4 a_color;"+                 "varying vec3 v_vertex;"+                 "varying vec3 v_normal;"+                 "varying vec4 v_color;"+                  "void main() {"+                      "v_vertex=a_vertex;"+                      "vec3 n_normal=normalize(a_normal);"+                      "v_normal=n_normal;"+                      "v_color=a_color;"+                      "gl_Position = u_modelViewProjectionMatrix * vec4(a_vertex,1.0);"+                 "}";                  //записываем код фрагментного шейдера в виде строки                 String fragmentShaderCode=                 "precision mediump float;"+                 "uniform vec3 u_camera;"+                 "uniform vec3 u_lightPosition;"+                  "varying vec3 v_vertex;"+                 "varying vec3 v_normal;"+                 "varying vec4 v_color;"+                 "void main() {"+                      "vec3 n_normal=normalize(v_normal);"+                      "vec3 lightvector = normalize(u_lightPosition - v_vertex);"+                      "vec3 lookvector = normalize(u_camera - v_vertex);"+                      "float ambient=0.2;"+                      "float k_diffuse=0.8;"+                      "float k_specular=0.4;"+                      "float diffuse = k_diffuse * max(dot(n_normal, lightvector), 0.0);"+                      "vec3 reflectvector = reflect(-lightvector, n_normal);"+                      "float specular = k_specular * pow( max(dot(lookvector,reflectvector),0.0), 40.0 );"+                      "vec4 one=vec4(1.0,1.0,1.0,1.0);"+                      "gl_FragColor = (ambient+diffuse+specular)*one;"+                 "}";                  //создадим шейдерный объект                  mShader=new Shader(vertexShaderCode, fragmentShaderCode);                 //свяжем буфер вершин с атрибутом a_vertex в вершинном шейдере                 mShader.linkVertexBuffer(vertexBuffer);                 //свяжем буфер нормалей с атрибутом a_normal в вершинном шейдере                 mShader.linkNormalBuffer(normalBuffer);                 //свяжем буфер цветов с атрибутом a_color в вершинном шейдере                 mShader.linkColorBuffer(colorBuffer);                 //связь атрибутов с буферами сохраняется до тех пор,                 //пока не будет уничтожен шейдерный объект        }         //метод, в котором выполняется рисование кадра         public void onDrawFrame(GL10 unused) {                  //очищаем кадр                 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);               //в отличие от атрибутов связь униформ с внешними параметрами                 //не сохраняется, поэтому перед рисованием каждого кадра нужно связывать униформы заново                 //передаем в шейдерный объект матрицу модели-вида-проекции                  mShader.linkModelViewProjectionMatrix(modelViewProjectionMatrix);                 //передаем в шейдерный объект координаты камеры                 mShader.linkCamera(xСamera, yCamera, zCamera);                //передаем в шейдерный объект координаты источника света                 mShader.linkLightSource(xLightPosition, yLightPosition, zLightPosition);                //делаем шейдерную программу активной                 mShader.useProgram();                 //рисуем квадрат                 GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);                 //последний аргумент в этой команде - это количество вершин =4         } }//конец класса В запустим программу на исполнение и получим следующую картинку:

Изменив код фрагментного шейдера можно выделить отдельно диффузное освещение: gl_FragColor = (ambient+diffuse)*one; и зеркальное освещение: gl_FragColor = (ambient+specular)*one;

Теперь скомбинируем освещение с интерполированными цветами вершин: gl_FragColor = (ambient+diffuse+specular)*v_color;

Получилось слишком тускло. Это происходит потому, что мы умножаем яркость освещения на каждую компоненту вектора цвета пикселя (красную, зеленую и синюю). Произведение двух чисел, каждое из которых меньше единицы, дает число меньшее обоих множителей и поэтому общая яркость цвета уменьшается. Можно комбинировать освещение с цветом по другому. В GLSL есть функция смешивания mix, которая выглядит так:

mix(a,b,k)=a*(1-k)+b*k

где a и b - аргументы которые нужно смешать, а k-коэффициент смешивания, который меняется от 0 до 1. При k=0 результат функции будет равен аргументу a, при k=1 - аргументу b.

Диффузное освещение Зеркальное осв Диффузное и зеркальное осв с учетом цвета вершин

Определим во фрагментном шейдере для освещения свой вектор цвета:

vec4 lightColor = (ambient+diffuse+specular)*one;

и смешаем его наполовину с цветом пикселя:  

gl_FragColor = mix(lightColor, v_color, 0.5);

Результат будет выглядеть так:

Для получения такого качественного освещения в классическом OpenGL ES 1 нам потребовалось бы разбить квадрат на тысячи вершин. В нашем примере потребовалось всего четыре вершины.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]