13 октября 2012 г.

OpenGL ES 2.0. Урок третий-Двумерные текстуры

Загрузка текстуры из файла.
Создадим класс, который загружает двумерную текстуру из графического файла. Нам достаточно разместить какой-нибудь графический файл в папку ресурсов (например, в res/drawable-hdpi), чтобы система присвоила ему идентификатор. Идентификатор ресурса - это целое число, которое является ссылкой на данный ресурс. Идентификаторы ресурсов хранятся в  файле R.java, который система создает автоматически. Если известно имя графического файла ресурса, например picture.png , можно получить его идентификатор как R.drawable.picture.
Итак, приступим к созданию класса. Я думаю, что из комментариев будет все понятно.
public class Texture {
        //создаем поле для хранения имени текстуры
        private int name;
        // конструктор двумерной текстуры из ресурса
        //передаем в качестве аргументов контекст 
        //и идентификатор ресурса графического файла
        public Texture(Context context, int idpicture) {
                //создаем пустой массив из одного элемента
                //в этот массив OpenGL ES запишет свободный номер текстуры, 
                // который называют именем текстуры
                int []names = new int[1];
                // получаем свободное имя текстуры, которое будет записано в names[0]
                GLES20.glGenTextures(1, names, 0);
                //запомним имя текстуры в локальном поле класса
                name = names[0];
                //теперь мы можем обращаться к текстуре по ее имени name
                //устанавливаем режим выравнивания по байту
                GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
                //делаем текстуру с именем name текущей
                GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, name);
                //устанавливаем фильтры текстуры
                GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
                        GLES20.GL_TEXTURE_MIN_FILTER, 
                        GLES20.GL_LINEAR_MIPMAP_LINEAR);
                GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
                        GLES20.GL_TEXTURE_MAG_FILTER,
                        GLES20.GL_LINEAR);
               //устанавливаем режим повтора изображения 
                //если координаты текстуры вышли за пределы от 0 до 1
                GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
                        GLES20.GL_TEXTURE_WRAP_S,
                        GLES20.GL_REPEAT);
                GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
                        GLES20.GL_TEXTURE_WRAP_T,
                        GLES20.GL_REPEAT);
                // загружаем картинку в Bitmap из ресурса
                Bitmap bitmap = 
                        BitmapFactory.decodeResource(context.getResources(), idpicture);
                //переписываем Bitmap в память видеокарты
                GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
                // удаляем Bitmap из памяти, т.к. картинка уже переписана в видеопамять
                bitmap.recycle();
                // Важный момент ! 
                // Создавать мипмапы нужно только
                // после загрузки текстуры в видеопамять
                GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
        }// конец конструктора двумерной текстуры

        //нам будет нужен метод, который возвращает имя текстуры
        public int getName() {
                return name;
        }
}// конец класса
Создать текстурный объект mTexture для картинки picture.png можно следующим образом:
Texture mTexture=new Texture(context, R.drawable.picture);
а получить его имя можно так:
int  mTextureName=mTexture.getName();


Доступ к текстурам из шейдера.
Теперь поговорим о том, как получить доступ к текстуре из шейдера. Для этого в GLSL существует специальный тип униформы и называется он sampler2D. Сэмплеры можно объявлять только во фрагментном шейдере, например так:
uniform sampler2D u_texture;
Здесь u_texture-это имя униформы.
Теперь нам нужно связать униформу текстуры u_texture с текстурным объектом mTexture. Последовательность действий при этом следующая. Сначала нужно выбрать текущую активную программу:
GLES20.glUseProgram(program_Handle); 
Затем нужно получить ссылку на униформу u_texture:
int u_texture_Handle = GLES20.glGetUniformLocation(program_Handle, "u_texture");
В OpenGL ES 2.0 одновременно может быть загружено до 32 текстур в разные текстурные блоки. Каждый текстурный блок может содержать только одну текстуру. Текстурные блоки обозначают именами GL_TEXTURE0, GL_TEXTURE1, GL_TEXTURE2 и.т.д. Поэтому мы должны с начала выбрать текущий текстурный блок (например блок 0) :
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
Затем в этом блоке сделать активным наш текстурный объект mTexture:
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTexture.getName());
Здесь первый аргумент это тип текстуры GL_TEXTURE_2D, который говорит о том, что текстура является двумерной. Второй аргумент - это имя текстуры.
Окончательно связь текстурного объекта mTexture и униформы u_texture выполняется командой:
GLES20.glUniform1i(u_texture_Handle, 0);
Здесь первый аргумент - это ссылка на униформу u_texture. Второй аргумент - номер текстурного блока (в нашем случае 0). Связь текстурного объекта с униформой установлена.

Нам может потребоваться загрузить несколько текстур для одновременного использования. В этом случае мы должны создать несколько текстурных объектов (mTexture0, mTexture1 и.т.д.) объявить во фрагментном шейдере несколько сэмплеров. Например, так:
uniform sampler2D u_texture0;
uniform sampler2D u_texture1;
и.т.д.
Далее нужно установить связь между объектом mTexture0 и униформой u_texture0, а также между объектом  mTexture1 и униформой u_texture1 и.т.д. 
Для примера рассмотрим случай двух текстур. 
Дополним наш класс Shader, описанный в первом уроке, методом linkTexture, который выполняет связь двух текстур с соответствующими сэмплерами:
public void linkTexture(Texture texture0,Texture texture1){
        // texture0, texture1 - текстурные объекты
        //устанавливаем текущую активную программу
        GLES20.glUseProgram(program_Handle);
        if (texture0 != null){
                //получаем ссылку на униформу u_texture0
                int u_texture0_Handle = 
                               GLES20.glGetUniformLocation(program_Handle, "u_texture0");
                //выбираем текущий текстурный блок GL_TEXTURE0          
                GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
                //в текстурном блоке GL_TEXTURE0
                //делаем активной текстуру с именем texture0.getName()  
                GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture0.getName());
                //выполняем связь между объектом  texture0 и униформой u_texture0
                //в нулевом текстурном блоке
                GLES20.glUniform1i(u_texture0_Handle, 0);
        }
        if (texture1 != null){
                //получаем ссылку на униформу u_texture1
                int u_texture1_Handle =
                                GLES20.glGetUniformLocation(program_Handle, "u_texture1");
                //выбираем текущий текстурный блок GL_TEXTURE1          
                GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
                //в текстурном блоке GL_TEXTURE1
                //делаем активной текстуру с именем texture1.getName()  
                GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture1.getName());
                //выполняем связь между объектом  texture1 и униформой u_texture1
                //в первом текстурном блоке
                GLES20.glUniform1i(u_texture1_Handle, 1);
        }
}//конец метода

Создавать текстурные объекты и связывать их с сэмплерами нужно в методе onSurfaceCreated класса рендерера, потому что при создании экрана они разрушаются.

Получение цвета пикселя из текстуры.
Конечная цель применения текстур в шейдерах - это получение цвета конкретного пикселя из текстуры. Однако, одного сэмплера нам для этого недостаточно, нужно еще знать координаты текстуры. О координатах текстур я подробно  рассказал в статьях по OpenGL ES 1. Поэтому повторяться не буду. Координаты текстуры объявляются в шейдерах как вектор из двух чисел с плавающей точкой, т.е. vec2. Например так:
vec2 texcoord;
Первое число вектора - это координата S, второе - координата T. Можно получить координаты S и T через обращение к вектору texcoord.s, texcoord.t. Зная сэмплер текстуры и текстурные координаты можно легко получить цвет пикселя для данных текстурных координат. Для этого в GLSL существует функция texture2D:
vec4 textureColor=texture2D(u_texture, texcoord);
Первый аргумент этой функции сэмплер, т.е. картинка, второй аргумент - координаты текстуры.
Сразу возникает вопрос - где взять текстурные координаты ?

Первый вариант - рассчитать их на CPU  и передать их в вершинный шейдер как атрибут вершины. Это выглядит так:
Вершинный шейдер
attribute vec3 a_texcoord; //приняли координаты текстуры для вершины
varying vec2 v_texcoord; //подготовили переменную для интерполяции
void main() {
        //отправили координаты текстуры на интерполяцию по пикселям
        v_texcoord=a_texcoord; 
        ...........
}
Фрагментный шейдер
uniform sampler2D u_texture0; //приняли картинку текстуры 
varying vec2 v_texcoord; //приняли координаты текстуры для пикселя после интерполяции
void main() {
         //подсчитали цвет пикселя из текстуры
        vec4 textureColor=texture2D(u_texture0, v_texcoord);
        //записали цвет пикселя из текстуры в системную переменную gl_FragColor
        gl_FragColor = textureColor;
}
Данный подход не имеет никаких преимуществ перед OpenGL ES 1 и используется редко. Зачем считать координаты текстур на центральном процессоре, если на графическом процессоре это можно сделать гораздо быстрее. Поэтому координаты текстур очень часто считают в шейдерах.

Второй вариант - расчет координат текстур в вершинном шейдере.

Вершинный шейдер
varying vec2 v_texcoord; //подготовили переменную для интерполяции
void main() {
         // в расчетах координат текстур могут  участвовать атрибуты и униформы,
         // доступные вершинному шейдеру (координаты вершины, вектор нормали и.т.п.)
         // вычислили координаты текстуры 
         vec2 texcoord=......здесь производятся всякие расчеты.....;
        //отправили координаты текстуры на интерполяцию по пикселям
        v_texcoord=texcoord; 
        ...........
}
Фрагментный шейдер
uniform sampler2D u_texture0; //приняли картинку текстуры 
varying vec2 v_texcoord; //приняли координаты текстуры для пикселя после интерполяции
void main() {
         //подсчитали цвет пикселя из текстуры
        vec4 textureColor=texture2D(u_texture0, v_texcoord);
        //записали цвет пикселя из текстуры в системную переменную gl_FragColor
        gl_FragColor = textureColor;
}

Третий вариант - расчет координат текстур в фрагментном шейдере.

Вершинный шейдер
void main() {
        //здесь мы не вычисляем координаты текстур
        ...............................
}
Фрагментный шейдер
uniform sampler2D u_texture0; //приняли картинку текстуры 
void main() {
        // вычислили координаты текстуры 
         vec2 texcoord=......здесь производятся всякие расчеты.....;
         //подсчитали цвет пикселя из текстуры
        vec4 textureColor=texture2D(u_texture0, texcoord);
        //записали цвет пикселя из текстуры в системную переменную gl_FragColor
        gl_FragColor = textureColor;
}

Второй вариант работает в десятки раз быстрее, чем третий, но при этом качество текстур снижается, т.к. расчеты на пиксель точнее, чем расчеты на вершину. Выбор варианта расчета текстурных координат зависит от сложности расчетов. Координаты текстуры могут зависеть от чего угодно. Например, от координат вершины и вектора нормали. Можно комбинировать расчеты координат текстур в вершинном и фрагментном шейдере в зависимости от поставленной задачи.

Практика. Текстурированный квадрат.
Возьмем за основу код второго урока, в котором мы рисовали освещенный квадрат и попробуем наложить на него две текстуры. В качестве текстур будем использовать две бесшовные картинки, которые я сделал в фотошопе:
picture0.png
picture1.png

Поместим файлы picture0.png и picture1.png в каталог ресурсов res/drawable-hdpi. Теперь внесем в код второго урока небольшие изменения. Добавим в класс MyClassRenderer два локальных поля для хранения текстурных объектов:
private Texture mTexture0, mTexture1;
В методе onSurfaceCreated создадим из картинок текстурные объекты и свяжем их с сэмплерами в фрагментном шейдере:
mTexture0=new Texture(context,R.drawable.picture0);
mTexture1=new Texture(context,R.drawable.picture1);
mShader.linkTexture(mTexture0, mTexture1);
В результате рисунок picture0 будет связан с сэмплером u_texture0, а picture1 с сэмплером u_texture1.
Сначала мы будем использовать только один рисунок picture0, затем перейдем к наложению двух текстур на квадрат. Наш квадрат лежит в плоскости XZ, поэтому координата Y всегда равна нулю. Наверно самый простой способ вычисления текстурных координат S и T- это приравнивание их координатам X и Z:
S=X
T=Z
Сначала попробуем вычислить текстурные координаты в вершинном шейдере и передать их во фрагментный шейдер как varying:

Код вершинного шейдера:
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;
// определяем переменные для передачи 
// координат двух текстур на интерполяцию
varying vec2 v_texcoord0;
varying vec2 v_texcoord1;

void main() {
        v_vertex=a_vertex;
        vec3 n_normal=normalize(a_normal);
        v_normal=n_normal;
        v_color=a_color;
        //вычисляем координаты первой текстуры и отравляем их на интерполяцию
        //пусть координата текстуры S будет равна координате вершины X
        v_texcoord0.s=a_vertex.x;
        //а координата текстуры T будет равна координате вершины Z
        v_texcoord0.t=a_vertex.z;
        gl_Position = u_modelViewProjectionMatrix * vec4(a_vertex,1.0);
}
Код фрагментного шейдера:
precision mediump float;
uniform vec3 u_camera;
uniform vec3 u_lightPosition;
uniform sampler2D u_texture0;
uniform sampler2D u_texture1;
varying vec3 v_vertex;
varying vec3 v_normal;
varying vec4 v_color;
// принимаем координат двух текстур после интерполяции
varying vec2 v_texcoord0;
varying vec2 v_texcoord1;
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);
      //оставим пока квадрат временно без освещения
      //вычисляем цвет пикселя для первой текстуры
      vec4 textureColor0=texture2D(u_texture0, v_texcoord0);
      //и присвоим его системной переменной gl_FragColor
      gl_FragColor =textureColor0;
}
Запустим программу на выполнение и получим результат:





















Придумаем, что нибудь более интересное. Например, пусть координаты текстуры будут пропорциональны квадрату расстояния от центра картинки до вершины. Вычислим в вершинном шейдере квадрат расстояния:
float r = a_vertex.x * a_vertex.x + a_vertex.z * a_vertex.z;
А затем умножим его на координаты вершины с коэффициентом 0.3:
v_texcoord0.s = 0.3 * r * a_vertex.x;
v_texcoord0.t = 0.3 * r * a_vertex.z;

Кстати, это можно записать по другому - в векторном виде:
v_texcoord0 = 0.3 * r * a_vertex.xz;
Получим такой результат:





















Как мы видим изображение не очень сильно изменилось несмотря на нелинейную зависимость от расстояния до центра. Объяснение этому эффекту очень простое. У нас всего четыре вершины. Поэтому координаты текстуры будут рассчитаны только для углов квадрата, а для пикселей внутри квадрата сработает линейная интерполяция. Совсем другой результат мы получим, если будем вычислять координаты текстуры на пиксель в фрагментном шейдере. Перенесем расчет координат текстур во фрагментный шейдер:
Код вершинного шейдера:
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;
uniform sampler2D u_texture0;
uniform sampler2D u_texture1;
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);
      //оставим пока квадрат временно без освещения
      //вычисляем координаты первой текстуры
      float r = v_vertex.x * v_vertex.x + v_vertex.z * v_vertex.z;
      vec2 texcoord0 = 0.3 * r * v_vertex.xz;
      //вычисляем цвет пикселя для первой текстуры
      vec4 textureColor0=texture2D(u_texture0, texcoord0);
      //и присвоим его системной переменной gl_FragColor
      gl_FragColor =textureColor0;
}

Результат будет другим:





















Теперь добавим во фрагментный шейдер координаты второй текстуры:
Код фрагментного шейдера:
precision mediump float;
uniform vec3 u_camera;
uniform vec3 u_lightPosition;
uniform sampler2D u_texture0;
uniform sampler2D u_texture1;
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);
      //оставим пока квадрат временно без освещения
      //вычисляем координаты первой текстуры
      float r = v_vertex.x * v_vertex.x + v_vertex.z * v_vertex.z;
      vec2 texcoord0 = 0.3 * r * v_vertex.xz;
      //вычисляем цвет пикселя для первой текстуры
      vec4 textureColor0=texture2D(u_texture0, texcoord0);

      //вычисляем координаты второй текстуры
      //пусть они будут пропорциональны координатам пикселя
      //подберем коэффициенты так, 
      //чтобы вторая текстура заполнила весь квадрат
      vec2 texcoord1=0.25*(v_vertex.xz-2.0);
      //вычисляем цвет пикселя для второй текстуры
      vec4 textureColor1=texture2D(u_texture1, texcoord1);

      //умножим цвета первой и второй текстур
      gl_FragColor =textureColor0*textureColor1;
}

Получилось так:





















Как-то темновато вышло. Понятно почему. Первый рисунок состоит в основном из красных и зеленых цветов, синего цвета в нем практически нет. Второй рисунок наоборот состоит в основном из синего цвета. Когда мы начинаем умножать цвета двух текстур (красный первой текстуры на красный второй текстуры, зеленый первой на зеленый второй, синей первой на синий второй) получаем результирующий цвет близкий к черному. Кроме умножения существуют всякие другие варианты комбинирования двух цветов:
Смешивание: gl_FragColor =mix(textureColor0, textureColor1, 0.5);
Разница: gl_FragColor =abs(textureColor0-textureColor1);
Смешивание
Разница
Пока писал статью придумал свою комбинацию - разницу с заменой цветов:
gl_FragColor.r=abs (textureColor0.g-textureColor1.b);
gl_FragColor.g=abs (textureColor0.r-textureColor1.b);
gl_FragColor.b=abs (textureColor0.r-textureColor1.g);

Разница с заменой цветов






















Можете придумать свои варианты смешивания цветов текстур. Попробуйте проделать все это в старом OpenGL ES 1.0 без шейдеров :)))

Включаем свет.
До сих пор мы рассматривали текстуры без освещения. Настало время включить свет. В фрагментном шейдере у нас есть цвета двух текстур и яркость освещения, рассчитанные на пиксель. Существует много всяких вариантов смешивания цветов текстур и освещения. Например, мне понравился такой:
gl_FragColor=2.0*(ambient+diffuse)*mix(textureColor0,textureColor1,0.5)+specular*one;
Я смешал пополам цвета двух текстур и умножил их на яркость фонового и диффузного освещения. Зеркальную часть освещения добавил отдельно, чтобы блик был белого цвета. Результат получился такой:





















Вы можете сами провести эксперименты по комбинированию цветов.

111 комментариев:

  1. Отлично))) С этим разобрался! У меня появился один вопрос:
    Как быть если источников света должно быть больше чем 1? к примеру если они должны динамически добавляться и исчезать? В голову приходит идея использовать несколько программ для обработки или динамически генерировать шейдер

    ОтветитьУдалить
    Ответы
    1. Каждый источник света имеет яркость, излучаемый цвет и положение в пространстве. Мы рассчитываем их в классе рендерера, а затем передаем в шейдер как униформы. Во фрагментном шейдере вычисляем цвет пикселя для каждого источника света отдельно. Затем в шейдере складываем цвета от всех источников света и делим на количество источников, чтобы яркость при сложении не зашкаливала. Если источник динамически исчезает,то можно постепенно уменьшить его яркость до нуля.

      Удалить
  2. Появился вопрос: а как сделать скругленные края или полукруглую поверхность?
    Я читал про vbo и ibo но в принципе понял мало из того что там было описано...

    ОтветитьУдалить
    Ответы
    1. vbo и ibo - это область памяти видеокарты, из которой OpenGL берет атрибуты вершин и индексы для правила обхода. Большинство устройств с ОС Android (смартфоны) не имеют выделенной видеопамяти, т.к. память общая для CPU и GPU. Поэтому для Android нет смысла использовать vbo.
      Пример гладкой функции. Гладкую выпуклую функцию можно изобразить так. Наносим прямоугольную сетку на горизонтальную плоскость XZ. Высоту Y определяем так:
      for (j = 1; j<jmax; j++) {
      for (i = 1; i<imax; i++) {
      y[j][i]=(float)Math.exp(-3*((x[i]-x0)*(x[i]-x0)+(z[j]-z0)*(z[j]-z0)));
      }
      }
      где imax, jmax - размеры сетки по осям X и Z.
      x0,z0 - центр выпуклости.
      Далее нужно рассчитать нормаль для каждой вершины сетки и придумать правило обхода. Например, составить индексный массив для обхода вершин сетки зигзагом и использовать GL_TRIANGLE_STRIP. Записать вершины и нормали в буферы и связать их с униформами в шейдере. И наконец для рисования применить команду
      GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, sizeindex, GLES20.GL_UNSIGNED_SHORT, indexBuffer);
      sizeindex-размер индексного массива,
      indexBuffer - буфер для хранения индексов.

      Удалить
    2. То есть при помощи IBO мы создаем VertexBuffer в который при помощи математической функции заносим значения. Чем больше полигонов, тем больше точность фигуры..

      Удалить
    3. IBO здесь не используется. В vertexBuffer записываются координаты вершин из массива, в normalBuffer координаты вектора нормали для каждой вершины. Вектор нормали для каждой вершины можно подсчитать зная любые две соседние вершины. В indexBuffer храниться порядок перечисления вершин для правила GL_TRIANGLE_STRIP. Прочитайте мою статью "OpenGL ES 1. Применение индексов при обходе вершин".
      Разница в том, что в OpenGL ES 2.0 команды glEnableClientState, glVertexPointer не используются. Вместо них vertexBuffer и normalBuffer связываются с соответствующими атрибутами вершинного шейдера.

      Удалить
    4. Опубликовал четвертый урок по рисованию гладких поверхностей http://andmonahov.blogspot.ru/2012/12/opengl-es-20-4.html

      Удалить
  3. спасибо за уроки
    где можно скачать последний урок

    ОтветитьУдалить
    Ответы
    1. Скачиваете код второго урока. Добавляете в него класс Texture из этого урока. В класс Shader добавляете метод linkTexture из этого урока. Добавляете в res/drawable-hdpi любые две картинки. Далее читаете в этом уроке раздел. "Практика. Текстурированный квадрат." и все получится.

      Удалить
    2. Здравствуйте,спасибо за ваши уроки, очень тяжело с литературой по ES не ndk. Не могли бы вы вкратце описать использование BLEND смешивания в openGL ES 2.0 или подтолкнуть к существующим примерам.

      Удалить
    3. смешивание цветов работает также как и в OpenGL ES 1.0
      //включаем смешивание
      GLES20.glEnable(GLES20.GL_BLEND);
      //задаем правило смешивания цветов
      //например для эффекта прозрачности
      GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);

      Удалить
  4. Спасибо большое в 1.0 было все так знакомо что как то пропустил косым взглядом. А ведь мою ошибку там вы указали... я забыл отключить CULL задних граней.

    ОтветитьУдалить
  5. Ах да, вопрос про источники все-таки остался для не ndk версии 2.0 все-таки сложно найти более сложные примеры

    ОтветитьУдалить
    Ответы
    1. Есть сайт с уроками на английском http://www.learnopengles.com/android-lesson-one-getting-started
      Все исходники уроков можно скачать. Назвать их примеры сложными не могу. Ограничиваются в основном треугольниками и кубиками.

      Удалить
  6. Очень познавательные уроки. пока написано все понятно, но лишь шаг в сторону.. Возможно вы сможете подсказать? я пытался по аналогии вашим классам сделать кубическую текстуру, но в итоге ничего не вышло. Может вы заметите что я сделал не так, буду крайне признателен.

    import android.content.Context;
    import android.opengl.GLES20;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.opengl.GLUtils;

    public class CubeTexture {


    private int CubeName;
    private int Top;
    private int Botton;
    private int Right;
    private int Left;
    private int Front;
    private int Back;



    public CubeTexture(Context context, int _idpicture[]) {//find id -> R.drawable.forest

    int []names = new int[1];// temp array for name of texture
    GLES20.glGenTextures(1, names, 0); // generate name for texture
    CubeName = names[0];


    GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); //align - byte

    GLES20.glBindTexture(GLES20.GL_TEXTURE_CUBE_MAP, CubeName);//do our texture as current

    GLES20.glTexParameteri( GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR_MIPMAP_LINEAR);
    GLES20.glTexParameteri( GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR_MIPMAP_LINEAR);




    GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, //if coordinats more interval (0..1) set wraping
    GLES20.GL_TEXTURE_WRAP_S,
    GLES20.GL_REPEAT);
    GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP,
    GLES20.GL_TEXTURE_WRAP_T,
    GLES20.GL_REPEAT);

    Bitmap bitmapPX = BitmapFactory.decodeResource(context.getResources(), _idpicture[0]);// load Bitmap
    Bitmap bitmapNX = BitmapFactory.decodeResource(context.getResources(), _idpicture[1]);
    Bitmap bitmapPY = BitmapFactory.decodeResource(context.getResources(), _idpicture[2]);
    Bitmap bitmapNY = BitmapFactory.decodeResource(context.getResources(), _idpicture[3]);
    Bitmap bitmapPZ = BitmapFactory.decodeResource(context.getResources(), _idpicture[4]);
    Bitmap bitmapNZ = BitmapFactory.decodeResource(context.getResources(), _idpicture[5]);

    GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, bitmapPX, 0);
    GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, bitmapNX, 0);
    GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, bitmapPY, 0);
    GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, bitmapNY, 0);
    GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, bitmapPZ, 0);
    GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, bitmapNZ, 0);


    bitmapPX.recycle();
    bitmapNX.recycle();
    bitmapPY.recycle();
    bitmapNY.recycle();
    bitmapPZ.recycle();
    bitmapNZ.recycle();

    GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_CUBE_MAP);

    }

    public int getCubeName() {
    return CubeName;
    }
    }

    ОтветитьУдалить
  7. Подгружаю другим классом. В принципе если отключаю текстуру серый куб рендерится корректно.

    import java.nio.ByteBuffer;
    import android.content.Context;

    import java.nio.ByteOrder;
    import java.nio.FloatBuffer;
    import java.nio.ShortBuffer;

    import android.opengl.GLES20;
    import android.opengl.Matrix;
    public class SkyBox {

    private FloatBuffer vBuffer;
    private FloatBuffer tBuffer;
    private ShortBuffer ListBuffer;
    private Shader m2Shader;
    private CubeTexture texture;
    private float[] tempM;
    private float[] SymetricM;


    private float[] modelViewM;
    private float[] modelVPM;

    static final int COORDS_PER_VERTEX = 3;
    static final float s = 1.0f;
    static final float vertexArray [] = {
    -s, s, s, s, s, s, s,-s, s, -s,-s, s, // front
    s, s,-s, -s, s,-s, -s,-s,-s, s,-s,-s, // back
    -s, s,-s, s, s,-s, s, s, s, -s, s, s, // top
    s,-s,-s, -s,-s,-s, -s,-s, s, s,-s, s, // bottom
    -s, s,-s, -s, s, s, -s,-s, s, -s,-s,-s, // left
    s, s, s, s, s,-s, s,-s,-s, s,-s, s // right
    };

    static final float TextureArray [] = {
    0.0f,0.0f,1.0f, 1.0f,0.0f,1.0f, 1.0f,1.0f,1.0f, 0.0f,1.0f,1.0f, // front
    0.0f,0.0f,0.0f, 1.0f,0.0f,0.0f, 1.0f,1.0f,0.0f, 0.0f,1.0f,0.0f, // back
    0.0f,0.0f,0.0f, 1.0f,0.0f,0.0f, 1.0f,0.0f,1.0f, 0.0f,0.0f,1.0f, // top
    0.0f,1.0f,0.0f, 1.0f,1.0f,0.0f, 1.0f,1.0f,1.0f, 0.0f,1.0f,1.0f, // bottom
    0.0f,0.0f,0.0f, 0.0f,1.0f,0.0f, 0.0f,1.0f,1.0f, 0.0f,0.0f,1.0f, // left
    1.0f,0.0f,0.0f, 1.0f,1.0f,0.0f, 1.0f,1.0f,1.0f, 1.0f,0.0f,1.0f // right
    };

    static final short drawOrder[] = {
    //top
    0, 3, 1, 1, 3, 2, // front
    4, 7, 5, 5, 7, 6, // back
    8,11, 9, 9,11,10, // top
    12,15,13, 13,15,14, // bottom
    16,19,17, 17,19,18, // left
    20,23,21, 21,23,22 // right
    };




    public SkyBox(Context context) {
    int ID[] = {com.messaging.tetris.R.drawable.cubepx,
    com.messaging.tetris.R.drawable.cubenx,
    com.messaging.tetris.R.drawable.cubepy,
    com.messaging.tetris.R.drawable.cubeny,
    com.messaging.tetris.R.drawable.cubepz,
    com.messaging.tetris.R.drawable.cubenz
    };

    ОтветитьУдалить

  8. tempM=new float[16];
    texture=new CubeTexture(context, ID);
    SymetricM =new float[16];
    Matrix.setIdentityM(SymetricM, 0);

    modelViewM=new float[16];
    modelVPM=new float[16];

    ByteBuffer bvertex = ByteBuffer.allocateDirect(vertexArray.length*4);
    bvertex.order(ByteOrder.nativeOrder());
    vBuffer = bvertex.asFloatBuffer();
    vBuffer.position(0);
    vBuffer.put(vertexArray);
    vBuffer.position(0);

    ByteBuffer tvertex = ByteBuffer.allocateDirect(TextureArray.length*4);
    tvertex.order(ByteOrder.nativeOrder());
    tBuffer = tvertex.asFloatBuffer();
    tBuffer.position(0);
    tBuffer.put(TextureArray);
    tBuffer.position(0);

    ByteBuffer dlb = ByteBuffer.allocateDirect(drawOrder.length * 2);
    dlb.order(ByteOrder.nativeOrder());
    ListBuffer = dlb.asShortBuffer();
    ListBuffer.put(drawOrder);
    ListBuffer.position(0);


    String vShaderCode=
    "uniform mat4 u_modelViewProjectionMatrix;"+//норм

    "attribute vec3 a_cubetexcoord;"+//норм
    "varying vec3 v_cubetexcoord;"+//норм
    "attribute vec3 a_vertex;"+
    "void main() {"+
    "v_cubetexcoord = a_cubetexcoord;"+//норм
    "gl_Position = u_modelViewProjectionMatrix * vec4(a_vertex,1.0);"+
    "}";




    String fShaderCode=
    "precision mediump float;"+
    "varying vec3 v_cubetexcoord;"+
    " uniform samplerCube u_cubetexture;"+
    "void main() {"+
    "vec4 textureColor = textureCube(u_cubetexture, v_cubetexcoord);"+
    "gl_FragColor = textureColor;"+
    "}";

    m2Shader=new Shader(vShaderCode, fShaderCode);

    }


    public void draw(float[] _modelMatrix, float[] _viewMatrix, float[] _projectionMatrix, float[] Camera,float[] LightPosition, float weigth, float angleX)
    {

    Matrix.multiplyMM(modelViewM, 0, _viewMatrix, 0, _modelMatrix, 0);
    Matrix.multiplyMM(modelVPM, 0, _projectionMatrix, 0, modelViewM, 0);


    m2Shader.linkCubeTexture(texture);
    m2Shader.linkCubeTextureBuffer(tBuffer);
    m2Shader.linkVertexBuffer(vBuffer);
    m2Shader.linkModelViewProjectionMatrix(modelVPM);
    m2Shader.useProgram();
    GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length,
    GLES20.GL_UNSIGNED_SHORT, ListBuffer);

    }
    }

    ОтветитьУдалить
    Ответы
    1. Мне нужен полный код проекта. Сбросьте мне на and.monakhov@gmail.com
      Поищу ошибки.

      Удалить
    2. Нашел в коде две ошибки:
      1. В классе SkyBox2 в коде фрагментного шейдера два знака равно
      "gl_FragColor = = textureCube(u_cubetexture, v_cubetexcoord);"+

      2. В классе Shader в методе linkCubeTexture номера текстурных блоков
      в glActiveTexture и glUniform1i не совпадают:
      GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
      GLES20.glBindTexture(GLES20.GL_TEXTURE_CUBE_MAP,
      texture0.getCubeName());
      GLES20.glUniform1i(u_texture0_Handle, 0);

      Нужно сделать так:
      GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
      GLES20.glBindTexture(GLES20.GL_TEXTURE_CUBE_MAP, texture0.getCubeName());
      GLES20.glUniform1i(u_texture0_Handle, 0);
      или так:
      GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
      GLES20.glBindTexture(GLES20.GL_TEXTURE_CUBE_MAP, texture0.getCubeName());
      GLES20.glUniform1i(u_texture0_Handle, 1);

      После этого текстура появляется

      Удалить
  9. Столкнулся со следующей проблемой, мне кажется она связана с текстурами. На эмуляторе и на тестовом Samsung Galaxy Асе DUOS, приложение работает и я прекрасно вижу картинку и все элементы. На Samsung Galaxy S у меня черный экран и только один маленький элемент , текстура которого загружена (оба работают на Андройд 2.3.х). Если смотреть в эклипсе, то на телефоне который работает четко видно что делается сессия OpenGL ES 2.0 и генерируются мимпапы, а на тех что не загружаются основные текстуры я этого не вижу. В связи с этим первый вопрос: "Не знаете ли вы возможные проблемы и их решение". Второй вопрос: "Какого влияние мипмапов, если генерацию я отключаю, то я получаю просто черный экран?".

    ОтветитьУдалить
    Ответы
    1. 1. Видеокарта Samsung Galaxy S способна загружать только текстуры степени двойки, т.е. 128х128, 256х256, 512х512, 512х256, 1024х1024, 1024х512, и.т.п. Других текстур она не понимает. Это особенности Samsung Galaxy S.

      2. Если убрать строку генерации мипмапов GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
      т.е. отказаться от создания мипмапов вообще,
      нужно обязательно убрать мипмап из команды установки фильтра, т.е. вместо
      GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
      GLES20.GL_TEXTURE_MIN_FILTER,
      GLES20.GL_LINEAR_MIPMAP_LINEAR);
      использовать
      LES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
      GLES20.GL_TEXTURE_MIN_FILTER,
      GLES20.GL_LINEAR);

      Удалить
  10. Проверил. Все действительно стало работать.

    ОтветитьУдалить
  11. Еще хотел узнать про один момент. Когда я делал приложения с использованием Canvas там была проблема с градиентами и решалась она через более изощренную фильтрацию в фотошопе. Существует ли аналогичная проблема в Open GL . Приведу в пример оригинал текстуры и скриншот с Samsung Galaxy Tab. https://www.dropbox.com/s/d26bvw30hvaqb9v/Texture1.tiff и оригинал https://www.dropbox.com/s/3bqsfh1jy2losop/Texture1src.tiff . Возможно это результат наличия у меня текстуры только для mdpi и xdpi и это результат преобразования в hdpi .

    ОтветитьУдалить
    Ответы
    1. Это картинка с Canvas или OpenGL ? Если с OpenGL, то нужно больше информации, чтобы сделать какие-либо выводы. Сколько вершин ? Как рассчитываются координаты текстуры ? Какие фильтры текстур используются ?

      Удалить
    2. Это картинка с OpenGL. Объект для текстуры взял из вашего примера.
      Код возможно отличается.
      6 вершин.
      GLES20.glGenTextures(1, textureHandle, 0);

      if (textureHandle[0] != 0)
      {
      final BitmapFactory.Options options = new BitmapFactory.Options();
      options.inScaled = false;

      final Bitmap bitmap = BitmapFactory.decodeResource(res, resourceId, options);

      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);


      GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
      GLES20.GL_TEXTURE_MIN_FILTER,
      GLES20.GL_LINEAR_MIPMAP_LINEAR);
      GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
      GLES20.GL_TEXTURE_MAG_FILTER,
      GLES20.GL_LINEAR);

      GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
      GLES20.GL_REPEAT);
      GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
      GLES20.GL_REPEAT);

      GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

      bitmap.recycle();

      GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
      }

      Координаты текстуры

      0.0f, 0.0f,
      0.0f, 1.0f,
      1.0f, 0.0f,
      0.0f, 1.0f,
      1.0f, 1.0f,
      1.0f, 0.0f

      Удалить
    3. На Galaxy S получается самая качественная картинка с т.з. отображения текстур.

      Удалить
    4. Не могу проверить на mdpi и xdpi, т.к. у меня нет такого аппарата, но на hdpi все выглядит идеально.

      Удалить
    5. Опубликовал четвертый урок по OpenGL ES 2.0. Гладкие поверхности

      Удалить
  12. Андрей, у меня такой вопрос. Использовал объекты для Текстур , с ваших примеров. Не использую мипмапы.
    Использую GLWallpaperService от Jesus Freke .

    Мне в Google Play приходит ошибка:
    java.lang.RuntimeException: eglSwapBuffers failed: EGL_BAD_ALLOC
    at android.opengl.GLSurfaceView$EglHelper.throwEglException(GLSurfaceView.java:1178)
    at android.opengl.GLSurfaceView$EglHelper.swap(GLSurfaceView.java:1136)
    at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1463)
    at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1216)

    Я сам, ее не могу выловить, так как на тех устройствах, что проверяю такого не происходит. Может вы подскажете в какую сторону думать надо?

    ОтветитьУдалить
    Ответы
    1. У меня тоже приходят такие ошибки. Причем всегда от девайсов Sony Ericsson. Пока у меня нет такого устройства для тестирования.

      Удалить
  13. Не могу понять одной вещи. Как нарисовать несколько объектов в разных положениях и с разными текстурами?

    Для каждого объекта на сцене нужно писать свой шейдер?

    ОтветитьУдалить
    Ответы
    1. Шейдерный объект содержит в себе:
      1. Правила обработки одной вершины, заданные в вершинном шейдере.
      2. Правила обработки одного пикселя, заданные во фрагментном шейдере.
      3. Внешние данные, которые одинаковы для всех вершин - это униформы которые передаются в вершинный шейдер.
      4. Внешние данные, которые одинаковы для всех пикселей - это униформы которые передаются во фрагментный шейдер. К ним относятся текстуры, т.е. сэмлеры внешних картинок.
      Сами шейдеры задают только правила обработки вершин и пикселей. Если эти правила не меняются, то шейдеры менять не нужно. Например, если у объекта изменилась только текстура, достаточно к шейдеру прицепить новый сэмплер методом linkTexture. Если у объекта изменилось положение, например его повернули, достаточно передать в вершинный шейдер новую матрицу модели после поворота. Новые координаты вершин после поворота вершинный шейдер пересчитает сам.
      Если же меняются правила, заданные в шейдерах, то нужно их переписывать. Например, если один объект преломляет свет, а другой его отражает, нужно для этих двух объектов создавать разные шейдеры.

      Удалить
  14. Андрей, помогите с кодом. Хочу сделать эффект панорамы "SkyBox", наложил текстуры на куб, отсекаю передние грани, но не знаю как сделать плавный переход между соседними текстурами.

    ОтветитьУдалить
    Ответы
    1. Как я понял, у Вас есть шесть разных картинок, которые Вы загрузили в кубическую текстуру. Получился SkyBox. Но картинки разные и границы между ними четко видны. Нужно сделать переходы между картинками плавными. Я использую другой подход.
      Берем один исходный панорамный снимок и загружаем его в программу Bixorama (File-Import panorama-Photo), устанавливаем режим просмотра View-Horizontal Cross и видим кубическую текстуру в виде креста. Затем жмем File-Export panorama-Single files и получаем шесть отдельных файлов изображений по всем сторонам света, которые затем можно загрузить в кубическую текстуру. При этом переходы между картинками будут плавными.

      Удалить
    2. Андрей спасибо, прокололся на текстуре, хотя загружаю её в программу Pano2VR, переходы плавные. Попробовал наложить другие текстуры, всё отлично работает.

      Удалить
  15. Здравствуйте! Я только начинаю изучать openGL ES 2.0. Недавно столкнулся с проблемой отображения прямоугольных текстур. Сначала взл сэмплы какие то и пробовал вертел ими. И загрузил вместо картинки из сэмпла свою картинку. Она была прямоугольной и попробовал запустить на своем девайсе HTC One V. Все работало. Ну я и принял это за чистую монету. Потом решил установить данное приложение на HTC One X. И я удивился черному экрану. Пытался смотреть всякие параметры viewport. А потом чисто случайно загрузил картинку размером 512x512. И она отобразилась! и вот я щас не понимаю как мне поступать? что нужно сделать, чтобы отображались прямоугольные текстуры на всех устройствах? потому что на HTC One X видеоадаптер Tegra и может из-за этого что то не так.
    Надеюсь на вашу помощь. Заранее благодарен!

    ОтветитьУдалить
    Ответы
    1. Все видеокарты поддерживают текстуры, у которых размер стороны степень двойки, т.е. 128, 256, 512, 1024, и.т.д.
      Но не все видеокарты поддерживают текстуры произвольного размера. Поэтому текстуру нужно подгонять под степень 2.

      Удалить
    2. ну я могу сделать текстуру размером 1024х512?
      или только размером 1024х1024, 512х512?

      Удалить
    3. Совсем не обязательно. Если железо не поддерживает ARB_texture_non_power_of_two, GL_OES_texture_npot можно создать текстуру с метрикой кратной 2 и залить туда изображение произвольной метрики рассчитав при этом текстурные координаты таким образом, чтоб пустое место просто не поместилось на полигон. Всегда так делаю)))

      Удалить
  16. и как быть если картинка, например, размером 1366х768?

    ОтветитьУдалить
  17. Спасибо за Ваши уроки! А вот как можно на плоскость на которою натянута текстура, одновременно добавить к ней кубическую текстуру с отражением окружающего пространства?

    ОтветитьУдалить
    Ответы
    1. В классе Texture cоздадим конструктор кубической текстуры из ресурсов
      public Texture(Context context, int idpicture1, int idpicture2,
      int idpicture3, int idpicture4, int idpicture5, int idpicture6) {
      name=0;
      names = new int[1];
      GLES20.glGenTextures(1, names, 0);
      name = names[0];
      GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
      GLES20.glBindTexture(GLES20.GL_TEXTURE_CUBE_MAP, name);
      // устанавливаем параметры текстуры
      GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP,
      GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR_MIPMAP_LINEAR);
      GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP,
      GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
      GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP,
      GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
      GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP,
      GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
      // загружаем картинки
      Bitmap bitmap1 = BitmapFactory.decodeResource(context.getResources(),idpicture1);
      GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, bitmap1, 0);
      bitmap1.recycle();
      Bitmap bitmap2 = BitmapFactory.decodeResource(context.getResources(),idpicture2);
      GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0,bitmap2, 0);
      bitmap2.recycle();
      Bitmap bitmap3 = BitmapFactory.decodeResource(context.getResources(),idpicture3);
      GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0,bitmap3, 0);
      bitmap3.recycle();
      Bitmap bitmap4 = BitmapFactory.decodeResource(context.getResources(),idpicture4);
      GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0,bitmap4, 0);
      bitmap4.recycle();
      Bitmap bitmap5 = BitmapFactory.decodeResource(context.getResources(),idpicture5);
      GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0,bitmap5, 0);
      bitmap5.recycle();
      Bitmap bitmap6 = BitmapFactory.decodeResource(context.getResources(),idpicture6);
      GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0,bitmap6, 0);
      bitmap6.recycle();
      GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_CUBE_MAP);
      }// конец конструктора кубической текстуры


      В классе Shader добавим метод, передающий кубическую текстуру в шейдер
      public void linkCubeTexture(Texture texture){
      GLES20.glUseProgram(program_Handle);
      int u_texture_Handle = GLES20.glGetUniformLocation(program_Handle, "u_textureCube");
      //для примера разместим кубическую текстуру в текстурном блоке 1
      GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
      GLES20.glBindTexture(GLES20.GL_TEXTURE_CUBE_MAP, texture.getName());
      //здесь 1 - номер текстурного блока
      GLES20.glUniform1i(u_texture_Handle, 1);
      }

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

      precision mediump float;
      uniform vec3 u_camera;
      uniform vec3 u_lightPosition;
      uniform sampler2D u_texture0;
      uniform samplerCube u_textureCube;
      varying vec3 v_vertex;
      varying vec3 v_normal;

      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_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);

      //считаем координаты двумерной текстуры
      float r = v_vertex.x * v_vertex.x + v_vertex.z * v_vertex.z;
      vec2 texcoord0 = 0.3 * r * v_vertex.xz;
      //получаем цвет пикселя из двумерной текстуры
      vec4 textureColor0=texture2D(u_texture0, texcoord0);

      //вычисляем координаты для кубической текстуры
      //это отраженный вектор взгляда (из камеры на вершину)
      vec3 texcoordCube = reflect(-lookvector, n_normal);
      //получаем цвет пикселя из кубической текстуры
      vec4 textureColorCube = textureCube(u_textureCube,texcoordCube);

      //смешиваем цвета двумерной и кубической текстур (без учета освещения)
      //простейший вариант
      gl_FragColor =mix(textureColor0, textureColorCube, 0.5);

      }

      Удалить
  18. Спасибо за пример с кубической текстурой! Буду разбираться с Вашим примером.

    ОтветитьУдалить
  19. Не мог понять что не так, после компиляции появляется темный экран и горизонтальная синяя полоска вверху, текстуры смешанной с кубической не видно.

    ОтветитьУдалить
    Ответы
    1. Мне нужно посмотреть полный код. Вышлите на andmonahov@gmail.com

      Удалить
    2. С такими шейдерами работает
      String vertexShaderCode=
      "uniform mat4 u_modelViewProjectionMatrix;"+
      "attribute vec3 a_vertex;"+
      "attribute vec3 a_normal;"+
      "varying vec3 v_vertex;"+
      "varying vec3 v_normal;"+
      "void main() {"+
      "v_vertex=a_vertex;"+
      "vec3 n_normal=normalize(a_normal);"+
      "v_normal=n_normal;"+
      "gl_Position = u_modelViewProjectionMatrix * vec4(a_vertex,1.0);"+
      "}";


      //записываем код фрагментного шейдера в виде строки
      String fragmentShaderCode=
      "precision mediump float;"+
      "uniform vec3 u_camera;"+
      "uniform vec3 u_lightPosition;"+
      "uniform sampler2D u_texture0;"+
      "uniform samplerCube u_textureCube;"+
      "varying vec3 v_vertex;"+
      "varying vec3 v_normal;"+

      "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);"+

      //считаем координаты двумерной текстуры
      "float r = v_vertex.x * v_vertex.x + v_vertex.z * v_vertex.z;"+
      "vec2 texcoord0 = 0.3 * r * v_vertex.xz;"+
      //получаем цвет пикселя из двумерной текстуры
      "vec4 textureColor0=texture2D(u_texture0, texcoord0);"+

      //вычисляем координаты для кубической текстуры
      //это отраженный вектор взгляда (из камеры на вершину)
      "vec3 texcoordCube = reflect(-lookvector, n_normal);"+
      //получаем цвет пикселя из кубической текстуры
      "vec4 textureColorCube = textureCube(u_textureCube,texcoordCube);"+

      //смешиваем цвета двумерной и кубической текстур (без учета освещения)
      //простейший вариант
      "gl_FragColor =mix(textureColor0, textureColorCube, 0.8);"+

      "}";

      Удалить
    3. линковку текстур переносим в конец метода onSurfaceCreated


      //создадим шейдерный объект
      mShader=new Shader(vertexShaderCode, fragmentShaderCode);
      //свяжем буфер вершин с атрибутом a_vertex в вершинном шейдере
      mShader.linkVertexBuffer(vertexBuffer);
      //свяжем буфер нормалей с атрибутом a_normal в вершинном шейдере
      mShader.linkNormalBuffer(normalBuffer);
      //связь атрибутов с буферами сохраняется до тех пор,
      //пока не будет уничтожен шейдерный объект

      mTexture0=new Texture(context,R.drawable.picture0);
      mCubeTexture= new Texture(context,R.drawable.icon1,R.drawable.icon2,R.drawable.icon3,R.drawable.icon4,R.drawable.icon5,R.drawable.icon6);

      //линковку текстур переносим в конец метода
      mShader.linkTexture(mTexture0);
      mShader.linkCubeTexture(mCubeTexture);

      Удалить
    4. В первом примере я забыл определить float k_diffuse=0.8;
      Поэтому фрагментный шейдер не компилировался

      Удалить
  20. Спасибо всё получилось!

    ОтветитьУдалить
  21. Делаю все как описано выше, квадрат без текстуры рисуется отлично,но когда включаю текстуры-рисуется черный экран. В чем может быть причина?

    ОтветитьУдалить
    Ответы
    1. Разобрался. Все дело в фильтрах текстур. Поставил GLES20.GL_NEAREST и все заработало!!! От чего это может зависеть? Видеокарта или что-то другое?

      Удалить
    2. Возможно видеокарта не поддерживает линейные фильтры. Попробуйте на другом аппарате.

      Удалить
  22. Здравствуйте уважаемый автор, у меня возникла следующая проблема, в моем проекте возникла необходимость передавать в шейдеры разрешение экрана в пикселях, как его получить не из Activity? Искал, не как не могу найти работающий код...

    ОтветитьУдалить
    Ответы
    1. В классе рендерера есть метод
      public void onSurfaceChanged(GL10 unused, int width, int height)
      В него передается width и height - ширина и высота экрана в пикселях

      Удалить
  23. При использовании onSurfaceChanged, перед появлением текстуры наблюдается кратковременное появление коричнего фона, именно в попытке решить данную проблему ищу другой способ, так как если фиксированно задавать разрешение в onCreate, все нормально. Может я просто что то не так делаю?

    ОтветитьУдалить
    Ответы
    1. Почему фон коричневый, а не другого цвета ?
      Попробуйте перенести передачу разрешения экрана в шейдер из метода onSurfaceCreated в метод onSurfaceChanged.

      Удалить
    2. Спасибо! Передача параметров из onSurfaceChanged решила данную проблему!

      Удалить
  24. Здравствуйте! Пытаюсь соединить 2 ваших урока в 1. (Двумерные текстуры и гладкие поверхности) Чтобы одновременно выводилась плоскость с текстурой и сетка из урока "Гладкие поверхности". При этом "гладкие поверхности" создаю отдельным классом который должен выводиться в рендере. Методы в новом классе Greed полностью скопированы из Вашего урока. Так вот, вопрос в следующем:
    Какие особенности я должен учитывать в объединении этих уроков?
    А точнее: должна ли быть какая-то разница в юниформах, атрибутах шейдеров? Должны ли отличаться переменные в шейдерах?

    ОтветитьУдалить
    Ответы
    1. Прочитайте в качестве примера "OpenGL ES 2.0. Урок 5. Шейдер преломления света". В нем на гладкую поверхность накладывается текстура.

      Удалить
  25. Здравствуйте! Пробую выполнить ваши уроки - возникли небольшие трудности. Просьба подсказать - в чем может быть проблема с отображением текстуры. Экран черный. Ошибок нет т.к. на другом устройстве все отлично работает. Первый и второй урок также работают. Изменение параметра в фильтре не помогает.
    / / Установка фильтрации
    GLES20.glTexParameteri (GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
    GLES20.glTexParameteri (GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);

    Хотя ваши обои с текстурой на google play - "Surface Wave Free" - отлично работают.
    Телефон JY-G3.

    ОтветитьУдалить
  26. "Surface Wave Free" - обоина древняя и писалась на Open GL ES 1.0. Поэтому она будет работать на чем угодно. Другие мои обои с маркета "Colorful wave free" у Вас работают нормально или тоже выдают черный экран ?

    ОтветитьУдалить
  27. "Colorful Wave Free" - при предосмотре видно как работает - при установке - сбрасывается.

    ОтветитьУдалить
  28. Поставил галочку - настройка задержки и ваши обои заработали.

    ОтветитьУдалить
    Ответы
    1. Китайский чудо-аппарат :) Настройка задержки просто делает паузу между кадрами SystemClock.sleep(количество миллисекунд)
      Попробуйте установить паузу в конец метода onDrawFrame

      Удалить
  29. Еще один интересный факт - пятый урок с текстурой отлично работает.

    ОтветитьУдалить
    Ответы
    1. У меня появились смутные подозрения, что в китайском чуде работает только один текстурный блок. Т.е. одну текстуру на объект наложить можно, а две сразу - нельзя.

      Удалить
    2. Применил строку
      mShader.linkTexture(mTexture0, null);
      Все осталось по прежнему. Черный экран.

      Удалить
    3. Будем думать дальше. Переместите текстуры из папки res/drawable-hdpi в res/drawable-nodpi и удалите R.java, чтобы он создался заново

      Удалить
    4. Не помогло.

      Удалить
    5. Пришлите мне Ваш код на andmonahov@gmail.com

      Удалить
    6. Прошу извинить. Еще раз все проверил, по новой выполнил урок. И о чудо - заработало.
      Большой спасибо за помощь. Ваши уроки требуют терпения и напористости - с первого раза так просто не сдаются. Еще раз спасибо.

      Удалить
    7. Подскажите пожалуйста, как наиболее рационально отрисовать массив спрайтов(прямоугольники с текстурами)? Нужно вызывать на каждый спрайт glDrawArrays или есть методы оптимизации данной процедуры?

      Удалить
    8. Нужно пытаться там где это возможно рисовать с помощью одной команды glDrawArrays. Многократные вызовы glDrawArrays при большом количестве вершин (несколько тысяч) тормозят программу.

      Удалить
  30. А как это сделать, если у спрайтов разные текстуры? Как в шейдере переключать текстуру?

    ОтветитьУдалить
    Ответы
    1. Например так
      uniform sampler2D u_texture0;
      uniform sampler2D u_texture1;
      uniform float arg;

      void main() {
      vec4 color0=texture2D(u_texture0, texcoord);
      vec4 color1=texture2D(u_texture1, texcoord);
      gl_FragColor = mix(color0, color1, arg);
      }
      Если arg=0, то gl_FragColor=color0
      Если arg=1, то gl_FragColor=color1
      Меняя юниформу arg можно легко переключать текстуру

      Удалить
    2. arg может быть не только юниформой, но любой рассчитанной переменной типа float, т.е. может также вычисляться внутри шейдера.

      Удалить
    3. Спасибо большое за ответ!
      Но все равно не понятно как во время выполнения glDrawArrays, я смогу отправить в шейдер униформу. В данном случае выбрана будет текстура по arg, а arg будет задан перед выполнением glDrawArrays. Соответственно в одном glDrawArrays одна текстура.

      Удалить
    4. И ещё, возможна ли в шейдере такая конструкция:
      static float a = 0;
      ...
      ...
      ...
      a++;

      Удалить
    5. извиняюсь, a+=0.25;
      и считать количество вершин в вершинном шейдере

      Удалить
    6. Потом передать целую часть от 'а' во фрагментный шейдер.
      А sampler2D передать через массив:
      uniform sampler2D u_texture[8];
      varying int index;

      void main() {
      gl_FragColor = texture2D(u_texture[index], texcoord);
      }

      Удалить
    7. Присвойте каждой вершине переменную arg и передавйте его в вершинный шейдер как атрибут. Далее отправьте этот атрибут на интерполяцию как varying во фрагментый шейдер. Если у вершины arg=0 она будет нарисована первой текстурой, если arg=1, то второй текстурой при одном вызове glDrawArrays

      Удалить
    8. Вершинный шейдер выполняется для каждой вершины отдельно. Вершины обрабатываются шейдером параллельно. Шейдер ничего не знает о существовании других вершин. Поэтому считать вершины в шейдере не получится.

      Удалить
    9. Тогда через атрибуты передавать индекс слота.
      Спасибо большое за объяснение!
      Если из массива выбирать по 8 спрайтов, то это улучшит производительность? Ведь нам все время придется создавать новый буфер атрибутов.

      Удалить
    10. Из своей практики. Добавление лишнего атрибута в вершинный шейдер не влияет заметно на скорость отрисовки.
      А текстура у Вас меняется для одной вершины с течением времени ? Т.е. индекс слота для конкретной вершины непостоянный ?

      Удалить
    11. Кстати, такой конструкции varying int index быть не может. varying должна быть типа float.

      Удалить
    12. Для конкретной вершины индекс слота не меняется. Т.е. на каждый спрайт своя конкретная текстура.
      Много пишут про атласы текстур. Атласы, как я понимаю, все эти вопросы снимают. Достаточно использовать 1 слот и только менять текстурные координаты. Но, в специфике моей задачи, текстур большое количество и они чуть ли ни каждый день могут меняться. Поэтому до релиза атласы не подходят. Что можете посоветовать?
      Может при запуске программы рендерить в текстуру, которую потом и использовать как атлас. Но тут сложность с алгоритмом упаковки текстур, т.к. размеры текстур совершенно разные.

      Удалить
    13. Попробуйте преобразовать индекс слота (varying) в целое число и используйте переключение текстур внутри шейдера.

      Удалить
    14. Спасибо большое, буду экспериментировать.

      Удалить
  31. Подскажите пожалуйста, как мне координаты экрана перевести в координаты opengl? Задача вроде простая, нужно таскать объект по экрану и при этом отрисовывать его средствами opengl.

    есть стандартные довольно таки шейдеры:

    static const char gVertexShader[] =
    "attribute vec4 vPosition;\n"
    "varying vec2 v_texcoord;\n"
    "uniform mat4 uMatrix; \n"
    "uniform vec2 uShift;\n"
    "void main() {\n"
    "v_texcoord.s = vPosition.x * 2.0;"
    "v_texcoord.t = vPosition.y * 2.0;"
    "gl_Position = uMatrix * vec4(vPosition.x +uShift.x, vPosition.y +uShift.y, 0.5, vPosition.w);\n"
    "}\n";

    static const char gFragmentShader[] =
    "precision mediump float;\n"
    "uniform sampler2D uTexture;\n"
    "varying vec2 v_texcoord;"
    "void main() {\n"
    "vec4 textureColor = texture2D(uTexture,v_texcoord);\n"
    " gl_FragColor = textureColor;\n"
    "}\n";

    где uShift координаты смещения относительно начального положения объекта.

    изначально объект находится в
    const GLfloat gTriangleVertices[] = {
    0.0f, 0.0f,
    0.0f, 0.5f,
    0.5f, 0.0f,
    0.5f, 0.0f,
    0.5f, 0.5f,
    0.0f, 0.5f
    };

    матрица, которая отправляется в шейдер:

    GLfloat* TextureManager::generateMVPMatrix(int w, int h){
    float near = 1.0, far = -1.0;
    float left = 0.0, right = 1.0f / (float) h * (float) w, bottom = 1.0, top = 0.0;
    GLfloat* matrix = new GLfloat[16];

    // First Column
    matrix[0] = 2.0 / (right - left);
    matrix[1] = 0.0;
    matrix[2] = 0.0;
    matrix[3] = 0.0;

    // Second Column
    matrix[4] = 0.0;
    matrix[5] = 2.0 / (top - bottom);
    matrix[6] = 0.0;
    matrix[7] = 0.0;

    // Third Column
    matrix[8] = 0.0;
    matrix[9] = 0.0;
    matrix[10] = -2.0 / (far - near);
    matrix[11] = 0.0;

    // Fourth Column
    matrix[12] = -(right + left) / (right - left);
    matrix[13] = -(top + bottom) / (top - bottom);
    matrix[14] = -(far + near) / (far - near);
    matrix[15] = 1;

    return matrix;
    }

    я пробовал координаты смещения считать так:

    shiftX = (x-width/2) / (width/2);
    shiftY = (height/2-y) / (height/2)

    но так работает не корректно если в шейдере на матрицу умножаю, а если на матрицу не умножаю в шейдере то работает правильно но объект сплющен некрасиво.

    ОтветитьУдалить
  32. Всё это в 2D кстати. х, у - экранные координаты из onTouchEvent, width, height - реальная ширина и высота экрана, т.е. 960х540 на моём устройстве.

    ОтветитьУдалить
    Ответы
    1. Сначала для 2D-графики нужно создать ортогональную матрицу проекции. Создадим такой метод:
      void loadOrthoMatrix (float [] matrix, float left, float right, float bottom, float top, float near, float far){
      float r_l = right - left;
      float t_b = top - bottom;
      float f_n = far - near;
      float tx = - (right + left) / (right - left);
      float ty = - (top + bottom) / (top - bottom);
      float tz = - (far + near) / (far - near);

      matrix[0] = 2.0f / r_l;
      matrix[1] = 0.0f;
      matrix[2] = 0.0f;
      matrix[3] = tx;

      matrix[4] = 0.0f;
      matrix[5] = 2.0f / t_b;
      matrix[6] = 0.0f;
      matrix[7] = ty;

      matrix[8] = 0.0f;
      matrix[9] = 0.0f;
      matrix[10] = 2.0f / f_n;
      matrix[11] = tz;

      matrix[12] = 0.0f;
      matrix[13] = 0.0f;
      matrix[14] = 0.0f;
      matrix[15] = 1.0f;
      }
      Объявим в классе рендерера следующие поля:
      private float screenWidth, screenHeight, scale, touchX, touchY;
      где
      screenWidth, screenHeight - ширина и высота экрана в пикселях,
      scale - коэффициент масштаба,
      touchX, touchY - координаты точки касания экрана, переведенные в OpenGL.

      Метод onSurfaceChanged будет выглядеть так
      public void onSurfaceChanged(GL10 unused, int width, int height) {
      // устанавливаем Viewport
      GLES20.glViewport(0, 0, width, height);
      //запоминаем ширину и высоту экрана как поля класса
      screenWidth=(float)width;
      screenHeight=(float)height;
      //устанавливаем поле - коэффициент масштаба
      scale=2f; //для примера

      float ratio = (float) height / width;
      float left=-scale;
      float right=scale;
      float bottom=-scale*ratio;
      float top=scale*ratio;
      float near=-1f;
      float far=1f;
      //создадим прямоугольник, заполняющий экран по границам
      //координаты вершины 1
      float x1_border=-scale;
      float y1_border=scale*ratio;
      //координаты вершины 2
      float x2_border=-scale;
      float y2_border=-scale*ratio;
      //координаты вершины 3
      float x3_border=scale;
      float y3_border=scale*ratio;
      //координаты вершины 4
      float x4_border=scale;
      float y4_border=-scale*ratio;

      // получаем ортгональную матрицу проекции
      loadOrthoMatrix(projectionMatrix, left, right, bottom, top, near, far);
      // матрица проекции изменилась, поэтому нужно пересчитать матрицу модели-вида-проекции
      Matrix.multiplyMM(modelViewProjectionMatrix, 0, projectionMatrix, 0, modelViewMatrix, 0);
      //запишем координаты всех вершин в единый массив
      float vertexArray [] = {
      x1_border,y1_border, 0f,
      x2_border,y2_border, 0f,
      x3_border,y3_border, 0f,
      x4_border,y4_border, 0f
      };
      vertexBuffer.position(0);
      //перепишем координаты вершин из массива в буфер
      vertexBuffer.put(vertexArray);
      vertexBuffer.position(0);
      //

      ...... //здесь создается шейдер
      //
      shader.linkModelViewProjectionMatrix(modelViewProjectionMatrix);
      shader.linkVertexBuffer(vertexBuffer);



      }
      В методе onSurfaceChanged для примера я определил координаты прямоугольника, заполняющего экран по границам, на случай если нужно залить весь экран какой-нибудь текстурой. В общем случае этого можно не делать.

      Установим камеру
      Matrix.setLookAtM(viewMatrix, 0, 0, 0, 0, 0, 0, -1, 0, 1, 0);

      Метод, который преобразует экранные координаты точки касания в координаты OpenGL
      public void onTouchEvent(MotionEvent touchEvent) {
      //координаты точки касания в писелях
      float screenX=(float)touchEvent.getX();
      float screenY=(float)touchEvent.getY();
      //соотношение высоты экрана к ширине
      float ratio=screenHeight/screenWidth;
      //получаем координаты точки касания в OpenGL
      touchX=(2f*screenX/screenWidth-1f)*scale;
      touchY=(1f-2f*screenY/screenHeight)*scale*ratio;

      }

      Удалить
    2. Сделаю пояснения.
      Пусть центр OpenGL-координат находится в центре экрана.
      Величина scale определяет максимальную OpenGL-координату по оси X (т.е. по горизонтали). В моем примере scale=2, поэтому общая ширина экрана будет четыре условных единицы, по две в каждую сторону от центра вдоль оси X.
      Максимальная координата Y равна scale*ratio.

      Удалить
    3. спасибо, а сам шейдер остаётся таким же как у меня был? Если да, то получается мы будем умножать на CPU :
      // получаем ортгональную матрицу проекции
      loadOrthoMatrix(projectionMatrix, left, right, bottom, top, near, far);
      // матрица проекции изменилась, поэтому нужно пересчитать матрицу модели-вида-проекции
      Matrix.multiplyMM(modelViewProjectionMatrix, 0, projectionMatrix, 0, modelViewMatrix, 0);

      и ещё раз на ту же матрицу на GPU:

      static const char gVertexShader[] =
      "attribute vec4 vPosition;\n"
      "varying vec2 v_texcoord;\n"
      "uniform mat4 uMatrix; \n"
      "uniform vec2 uShift;\n"
      "void main() {\n"
      "v_texcoord.s = vPosition.x * 2.0;"
      "v_texcoord.t = vPosition.y * 2.0;"
      "gl_Position = uMatrix * vec4(vPosition.x +uShift.x, vPosition.y +uShift.y, 0.5, vPosition.w);\n"
      "}\n";

      Удалить
    4. матрица модели-вида-проекции создается один раз на CPU и передается в шейдер как юниформа. В шейдере матрица модели-вида-проекции умножается на координаты вершины:
      static const char gVertexShader[] =
      "attribute vec4 vPosition;\n"
      "varying vec2 v_texcoord;\n"
      "uniform mat4 uModelViewProjectionMatrix; \n"
      "uniform vec2 uShift;\n"
      "void main() {\n"
      "v_texcoord.s = vPosition.x * 2.0;"
      "v_texcoord.t = vPosition.y * 2.0;"
      "gl_Position = uModelViewProjectionMatrix * vec4(vPosition.x +uShift.x, vPosition.y +uShift.y, 0.0, 1.0);\n"
      "}\n";

      Удалить
  33. Андрей, здравствуйте!
    Я мучаюсь вопросом, подскажите плиз - я пишу прогу для iPad под OPEN GL ES 2.0, я использую текстуры. Могу ли я надеяться что сервер ОпенГЛ не затрет данные моих текстур в случае, к примеру, сворачиванию моего приложения и старту другого приложения, которое тоже использует ОпенГл и текстуры?

    ОтветитьУдалить
  34. Вопрос а есть ли возможность пердавать неопределенное кол-во элементов?
    не так
    uniform sampler2D u_texture[8];
    а к примеру так
    uniform sampler2D u_texture[];

    ОтветитьУдалить
    Ответы
    1. Можно, но не все видеокарточки это поддерживают. Лучше отдельно передавать размерность массива
      uniform int l;
      uniform sampler2D u_texture[l];

      Удалить
  35. помогите пожалуйста... есть такие шейдеры (обобщенно)

    uniform mat4 u_mvpMatrix;"
    attribute vec3 a_vertex;"
    attribute vec4 a_color;
    varying vec3 v_vertex;
    varying vec4 v_color;
    varying vec2 v_textcoord;
    void main() {
    v_vertex = a_vertex;
    v_color = a_color;
    v_textcoord.s = a_vertex.x;
    v_textcoord.t = a_vertex.y;
    gl_Position = u_mvpMatrix * vec4(a_vertex, 1.0);
    }


    precision mediump float;
    uniform sampler2D u_texture;
    varying vec3 v_vertex;
    varying vec4 v_color;
    varying vec2 v_textcoord;
    void main() {
    gl_FragColor = v_color;
    if (v_color != NULL) {
    gl_FragColor = v_color;
    } else {
    gl_FragColor = texture2D(u_texture, v_textcoord);
    }
    }

    это как бы уневенсальный шейдер. есть передается цвет, то отображается переданный цвет, а если текстура - то цвет берется из текстуры. только проблема в том, что проверка в фрагментальном шейдере на NULL не работает. можите подсказать, как привильно реализовать?
    спасибо.

    ОтветитьУдалить
    Ответы
    1. в фрагментальном шейдере лишняя строка после "void main() { ". извинете...

      Удалить
    2. в GLSL нет такого значения как NULL. Использование if в фрагментном шейдере приведет к снижению производительности в два раза. В этом я уже убедился на практике. Вы решаете - брать ли цвет из текстуры или из интерполированного значения v_color. Фрагментый шейдер выпоняется для каждого пикселя. Если у Вас миллион пикселей - сравнение if будет выполнено миллион раз. Проще написать два шейдера - один для использованя текстуры, другой без использования текстуры и переключать их на CPU. В этом случает вы выполните сравнение if только один раз на кадр, а не миллион.

      Удалить
    3. Про производительность все понял.
      Скажите, на будущее, возможно в GLSL как небудь проверить, не сравнивая с null, пустая ли переменная (varying)?

      Удалить
  36. А у меня вопрос каким образом реализуется скелетная анимация? Возможно есть какие то техники или решения для этого?
    Ко мне в голову пришло только управление при помощи обьеденения точек в группы.
    К примеру если в самом простом варианте рука сосотои из двух прямоугольников: то каждый из них имеет по 8 точек га каждый
    и в сумме 12 (т.к. по одной из сторон каждого прямоугольника будут общими и будут образовывать локоть)
    То я получаю 3 группы точек:
    Локоть:4 точки
    Плече:4 точки
    Запястье 4 точки

    То при движение запястья я двигаю сразу 4 точки. + надо учитывать длину от запястья до локтя(она должна быть постоянной), так и от локтя до плеча. и вслучае необходимости смещать локоть тоже.

    ОтветитьУдалить
  37. Такой вопрос: как в шейдер передавать и обраьатывать текстурные координаты и индексные буферы. Всю жизнь работал только с opengl 1.0. Поэтому шейдеры штука крайне непривычная

    ОтветитьУдалить
  38. Здравствуйте. Спасибо за простое и четкое обьяснение.

    У меня один вопрос. Вы писали что у OpenGl 32 слота для текстуры. я так понял это для одновременного использования рисуя один обьект. А если у меня 10 обьектов и у каждого одна текстура, нормальная ли это практика все текстуры передавать через нулевой слот? Или было бы быстрее использовать для каждого отдельный слот?

    ОтветитьУдалить