Студопедия — OpenGL ES 2.0. Урок третий - Двумерные текстуры
Студопедия Главная Случайная страница Обратная связь

Разделы: Автомобили Астрономия Биология География Дом и сад Другие языки Другое Информатика История Культура Литература Логика Математика Медицина Металлургия Механика Образование Охрана труда Педагогика Политика Право Психология Религия Риторика Социология Спорт Строительство Технология Туризм Физика Философия Финансы Химия Черчение Экология Экономика Электроника

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 в каталог ресурсов 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;
Я смешал пополам цвета двух текстур и умножил их на яркость фонового и диффузного освещения. Зеркальную часть освещения добавил отдельно, чтобы блик был белого цвета. Результат получился такой (рисунок справа):

 

 

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

 







Дата добавления: 2015-10-19; просмотров: 1225. Нарушение авторских прав; Мы поможем в написании вашей работы!



Обзор компонентов Multisim Компоненты – это основа любой схемы, это все элементы, из которых она состоит. Multisim оперирует с двумя категориями...

Композиция из абстрактных геометрических фигур Данная композиция состоит из линий, штриховки, абстрактных геометрических форм...

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

ТЕОРЕТИЧЕСКАЯ МЕХАНИКА Статика является частью теоретической механики, изучающей условия, при ко­торых тело находится под действием заданной системы сил...

Тема: Кинематика поступательного и вращательного движения. 1. Твердое тело начинает вращаться вокруг оси Z с угловой скоростью, проекция которой изменяется со временем 1. Твердое тело начинает вращаться вокруг оси Z с угловой скоростью...

Условия приобретения статуса индивидуального предпринимателя. В соответствии с п. 1 ст. 23 ГК РФ гражданин вправе заниматься предпринимательской деятельностью без образования юридического лица с момента государственной регистрации в качестве индивидуального предпринимателя. Каковы же условия такой регистрации и...

Седалищно-прямокишечная ямка Седалищно-прямокишечная (анальная) ямка, fossa ischiorectalis (ischioanalis) – это парное углубление в области промежности, находящееся по бокам от конечного отдела прямой кишки и седалищных бугров, заполненное жировой клетчаткой, сосудами, нервами и...

Демографияда "Демографиялық жарылыс" дегеніміз не? Демография (грекше демос — халық) — халықтың құрылымын...

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

ЛЕЧЕБНО-ПРОФИЛАКТИЧЕСКОЙ ПОМОЩИ НАСЕЛЕНИЮ В УСЛОВИЯХ ОМС 001. Основными путями развития поликлинической помощи взрослому населению в новых экономических условиях являются все...

Studopedia.info - Студопедия - 2014-2024 год . (0.011 сек.) русская версия | украинская версия