Можно ли сделать десатурацию в OpenGL ES 1.0 и как?

Я хотел бы отобразить цветную текстуру в сером цвете. Сделать это в ES 2.0 с помощью шейдера проще простого, но возможно ли это сделать в ES 1.x?

ОБНОВИТЬ

Благодаря @datenwolf я делаю это так:

GLfloat weights_vector[4] = {0.2126, 0.7152, 0.0722, 1.0};
GLfloat additions_vector[4] = {0.5, 0.5, 0.5, 0.0};

glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);

/* First part */
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_DOT3_RGB);
glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE);
glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_CONSTANT);
glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, weights_vector);

/* Second part */
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_ADD);
glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS);
glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_CONSTANT);
glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, additions_vector);

Первая часть отображается нормально, если я оставлю ее саму по себе, но если я добавлю вторую, она использует «черный» в качестве предыдущего цвета, и поэтому я получаю только серые пиксели. Я делаю это неправильно здесь?

ВТОРОЕ ОБНОВЛЕНИЕ

Если я попытаюсь использовать GL_TEXTURE0 вместо GL_PREVIOUS, я действительно получу тот же результат, что и GL_TEXTURE. Однако, если я использовал GL_TEXTURE1, я получаю даже не серые пиксели, а черные. Я теряюсь здесь...

ТРЕТЬЕ ОБНОВЛЕНИЕ

Сейчас работает вторая часть. Надо было просто использовать название предыдущей текстуры, как предложил @datenwolf!

Однако вывод по-прежнему не был правильным, поскольку он был инвертирован. Я исправил это, добавив:

glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_ONE_MINUS_SRC_COLOR);

Сейчас слишком темно. И я не могу понять это правильно. Пробовал и не получилось:

  • умножение весов на два
  • добавление 0,5
  • модуляция всего изображения на 2
  • умножая веса на 2 и затем вычитая на 0,5

ЧЕТВЕРТОЕ ОБНОВЛЕНИЕ (извините, что так много)

Я начинаю подозревать, что это невозможно из-за смещения и умножения в DOT3_RGB. Правильный способ сделать это в объединителе:

  1. Добавьте 0.5 к входной текстуре
  2. Рассчитать весовые коэффициенты: weight_new = (weight_real + 2.0) / (4.0);
  3. Do a GL_DOT3_RGB

Например, вместо использования:

GLfloat weights_vector[4] = {0.30, 0.59, 0.11, 0.0};

Использовать:

GLfloat weights_vector[4] = {0.575, 0.6475, 0.5275, 0.0};

Это действительно дает почти правильный результат, но часть контраста теряется из-за первого шага и того факта, что числа зажаты в диапазоне [-1.0, 1.0]

Зачем расчеты? Ну, по API:

Расчет точек

Поэтому я не вижу никакого другого способа, отличного от того, который я показал, и из-за ограничения точности. Конечно, я мог бы сначала разделить ввод на 2,0, затем добавить 0,5, сделать точку3, а затем снова умножить на 2, таким образом фактически имея все значения в диапазоне [-1,0, 0,0]. Но я боюсь, что это все равно потеряет точность из-за разделения.

Я подозреваю, что DOT не был предназначен для этой цели, скорее, только для бампмэппинга или чего-то в этом роде. Очень жаль. Надеюсь, я ошибаюсь, но я не понимаю, как.


person Albus Dumbledore    schedule 03.08.2011    source источник
comment
Я немного опоздал к обсуждению, а на вопрос уже дан ответ. Однако я потратил много времени на изучение этого, и на самом деле вы можете сделать это, используя 2 или 3 объединителя текстур. См. мой пост ниже для примера кода, который будет работать.   -  person Snickers    schedule 13.03.2012


Ответы (3)


Это возможно при использовании 2 или 3 объединителей текстур. Скорее всего, вы хотите поддерживать более старое устройство (например, iPhone 3G), которое поддерживает только 2 объединителя текстур. Вы можете определить, сколько объединителей текстур поддерживает ваше устройство, используя следующий код:

int maxTextureUnits;
glGetIntegerv(GL_MAX_TEXTURE_UNITS, &maxTextureUnits);

Основные шаги для рендеринга текстуры в градациях серого без шейдеров:

  1. Разделите все значения RGB в пикселях на 2.
  2. Вычтите 0,5 из всех ваших значений RGB пикселей
  3. Рассчитайте значение GL_DOT3_RGB для каждого значения RGB, используя пользовательские взвешенные значения.

Теперь каждый из этих шагов можно выполнить с помощью средства объединения текстур. Или, в качестве альтернативы, вы можете использовать только два объединителя текстур, заменив шаг 1 пользовательским кодом, который корректирует значения пикселей при считывании текстуры. Если вы решите использовать только два объединителя текстур, вы все равно сможете визуализировать скорректированную текстуру в цвете, вам просто понадобится один объединитель текстур, который удваивает ваши значения RGB перед рендерингом.

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

Причина, по которой мы делим все значения пикселей на 2, заключается в том, что наши значения не фиксируются при переходе от шага 2 к шагу 3. Если бы у нас было значение RGB (0,5, 0,6, 0,7) и мы добавили 0,5 к для каждого из значений RGB наше результирующее значение RGB на шаге 3 будет (1.0, 1.0, 1.0). После того, как уравнение DOT3 вычтет 0,5 из каждого значения, оно будет рассчитывать яркость на основе (0,5, 0,5, 0,5).

Вот пример кода, который будет отображать текстуру в градациях серого с использованием 3 текстурных блоков:

//Enable texture unit 0 to divide RGB values in our texture by 2
glActiveTexture(GL_TEXTURE0);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, m_textureId);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
glClientActiveTexture(GL_TEXTURE0);

//GL_MODULATE is Arg0 * Arg1    
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);

//Configure Arg0
glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);

//Configure Arg1
float multipliers[4] = {.5, .5, .5, 0.0};
glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_CONSTANT);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, (GLfloat*)&multipliers);

//Remember to set your texture coordinates if you need them
//glEnableClientState(GL_TEXTURE_COORD_ARRAY);
//glTexCoordPointer...

//Enable texture unit 1 to increase RGB values by .5
glActiveTexture(GL_TEXTURE1);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, m_textureId);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
glClientActiveTexture(GL_TEXTURE1);

//GL_ADD is Arg0 + Arg1
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_ADD);

//Configure Arg0
glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);

//Configure Arg1
GLfloat additions[4] = {.5, .5, .5, 0.0};
glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_CONSTANT);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, (GLfloat*)&additions);

//Set your texture coordinates if you need them
//glEnableClientState(GL_TEXTURE_COORD_ARRAY);
//glTexCoordPointer...

//Enable texture combiner 2 to get a DOT3_RGB product of your RGB values
glActiveTexture(GL_TEXTURE2);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, m_textureId);
glClientActiveTexture(GL_TEXTURE2);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);

//GL_DOT3_RGB is 4*((Arg0r - 0.5) * (Arg1r - 0.5) + (Arg0g - 0.5) * (Arg1g - 0.5) + (Arg0b - 0.5) * (Arg1b - 0.5))
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_DOT3_RGB);   

//Configure Arg0
glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);

//Configure Arg1
//We want this to adjust our DOT3 by R*0.3 + G*0.59 + B*0.11
//So, our actual adjustment will need to take into consideration
//the fact that OpenGL will subtract .5 from our Arg1
//and we need to also take into consideration that we have divided 
//our RGB values by 2 and we are multiplying the entire
//DOT3 product by 4
//So, for Red adjustment you will get :
//   .65 = (4*(0.3))/2 + 0.5  = (0.3/2) + 0.5
GLfloat weights[4] = {.65, .795, .555, 1.};
glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_CONSTANT);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, (GLfloat*)&weights);

//Set your texture coordinates if you need them
//glEnableClientState(GL_TEXTURE_COORD_ARRAY);
//glTexCoordPointer...

//Render your objects or sprite

//Clean up by disabling your texture combiners or texture units. 
glActiveTexture(GL_TEXTURE2);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glDisable(GL_TEXTURE_2D);

glActiveTexture(GL_TEXTURE1);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glDisable(GL_TEXTURE_2D);

glActiveTexture(GL_TEXTURE0);
glClientActiveTexture(GL_TEXTURE0);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
person Snickers    schedule 13.03.2012

Для этого можно использовать текстурную среду скалярного произведения. Помните, что скалярное произведение векторов равно

dot(v1, v2) = v1[0]*v2[0] + v1[1]*v2[1] + ... + v1[n]*v2[n]

Десатурация достигается путем суммирования каналов с весовым коэффициентом каждого

L{r,g,b} = w_r * R + w_g * G + w_b * B

Но это не что иное, как скалярное произведение цвета с весовым вектором. OpenGL-1.5 имеет текстурную среду, называемую combiner, и эта среда объединителя имеет режим точечный продукт:

GLfloat weighting_vector[4];
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_DOT3_RGB);
glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE);
glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_CONSTANT);
glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, weighting_vector);

ИЗМЕНИТЬ из-за комментария

Вы можете указать операцию с альфа-каналом в объединителе альфа-канала, токен GL_COMBINE_ALPHA. В вашем случае вы просто хотите использовать исходную альфу. Добавление этой конфигурации:

glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE);
glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE);

Что я забыл, так это то, что режим скалярного произведения вводит смещение 0,5. Но это не проблема, так как вам предоставляется как минимум 3 каскада объединителя, поэтому вы завершаете этап GL_SUBSTRACT, вычитая 0,5 каждого канала и умножая свой вес на 2,0, чтобы компенсировать это.

Взгляните на glTexEnv справочную страницу http://www.opengl.org/sdk/docs/man/xhtml/glTexEnv.xml и исходную спецификацию расширения http://www.opengl.org/registry/specs/ARB/texture_env_combine.txt (время, когда это было расширением). Я признаю, что объединители текстур немного сбивают с толку, если вы новичок в них. Исторически они являются предшественниками фрагментных шейдеров; NVidia начала все это с того, что они тогда назвали "объединителями регистров", которые позже стали объединителями текстур.

EDIT2 из-за добавления к вопросу

Вы должны сделать вторую часть на своем собственном этапе объединения (= блок текстуры). Вы переключаете блоки текстур с помощью glActiveTexture. Измените свой код следующим образом:

GLfloat weights_vector[4] = {0.2126, 0.7152, 0.0722, 1.0};
GLfloat additions_vector[4] = {0.5, 0.5, 0.5, 0.0};

glActiveTexture(GL_TEXTURE0);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texID);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);

/* First part */
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_DOT3_RGB);
glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE);
glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_CONSTANT);
glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, weights_vector);

glActiveTexture(GL_TEXTURE1);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texID); // we need some dummy texture active
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);

/* Second part */
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_SUBTRACT);
glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS);
glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_CONSTANT);
glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, additions_vector);

Также я думаю, что вы намереваетесь вычесть, чтобы компенсировать смещение 0,5 вместо добавления 0,5; FTFY.

person datenwolf    schedule 03.08.2011
comment
Большое спасибо! Это действительно работает! Не могли бы вы помочь мне еще немного. Моя реальная цель — также сохранить альфу (т. е. не смешивать ее, а просто заменить), вот так: (R, G, B, A) -> (Gray, Gray, Gray, A), или даже если бы я мог сделать что-то вроде: (R, G, B, A) -> (A, Gray, Gray). - person Albus Dumbledore; 03.08.2011
comment
На самом деле, у меня есть небольшая проблема. Выход серый, но инвертированный. - person Albus Dumbledore; 03.08.2011
comment
Огромное спасибо! Кажется, теперь я почти понял. Сообщает ли glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_*, *) графическому процессору, что запрашивается новый этап? - person Albus Dumbledore; 03.08.2011
comment
Как заставить его использовать вывод предыдущего этапа объединителя в следующем? - person Albus Dumbledore; 03.08.2011
comment
Стадии объединителя текстур сопоставляются с мультитекстурными текстурными модулями. Таким образом, текстурный блок 0 — это этап 0, текстурный модуль 1 — этап 1 и так далее. Существует специальный источник объединителя GL_PREVIOUS, который использует результат объединения предыдущего этапа. т.е. GL_PREVIOUS этапа 1 относится к результату этапа 0. Прошло довольно много времени с тех пор, как я возился с объединителями текстур, поэтому я немного заржавел ;) - person datenwolf; 03.08.2011
comment
Большое спасибо @datenwolf! У меня все еще возникают некоторые проблемы, смотрите обновление. - person Albus Dumbledore; 03.08.2011
comment
К сожалению, это все еще не работает для меня. На этот раз он работает так, как если бы второй части вообще не было, то есть, делаю ли я GL_ADD или GL_SUBTRACT, он дает тот же старый инвертированный результат. - person Albus Dumbledore; 03.08.2011
comment
@Albus Dumbledore: У вас есть где-нибудь скриншоты и оригинальное изображение? - person datenwolf; 03.08.2011
comment
Нужна ли мне только фиктивная текстура name, полученная с помощью glGenTextures(), или мне также нужно, чтобы фиктивная текстура была полностью инициализирована с помощью glTexImage2D()? - person Albus Dumbledore; 04.08.2011
comment
На самом деле вы можете повторно использовать какую-то другую текстуру. Подойдет одна из текстур, использованных на предыдущем этапе. Также это может улучшить производительность, так как GPU не будет очищать свой кеш. - person datenwolf; 04.08.2011
comment
Вот почему вы написали texID в обоих местах. Я думал, что это ошибка :-) Теперь это работает: теперь я использую идентификатор первой текстуры. Моя вина! Спасибо! Однако вычитания на 0,5 все же недостаточно. Смотрите мое обновление через минуту. - person Albus Dumbledore; 04.08.2011

Перед загрузкой текстуры в OpenGL обесцветьте ее с помощью ColorMatrix.

Paint paint = new Paint();
ColorMatrix matrix = new ColorMatrix();
matrix.setSaturation(0);
paint.setColorFilter(new ColorMatrixColorFilter(matrix));

Bitmap bmp = Bitmap.createBitmap(resource.getWidth(), resource.getHeight(), resource.getConfig());
Canvas canvas = new Canvas(bmp);
canvas.drawBitmap(resource, null, paint);
person Will Kru    schedule 03.08.2011
comment
Спасибо, но это специфично для Android, и я думаю, что это вряд ли аппаратное ускорение. - person Albus Dumbledore; 03.08.2011
comment
Дело в том, что шейдеры недоступны в OpenGL ES 1.x. Вы играли с режимами наложения? - person Will Kru; 03.08.2011
comment
Да, это моя точка зрения. Я просмотрел glTexEnv и glBlendFunc, но ни один из них не предоставляет такой функциональности. - person Albus Dumbledore; 03.08.2011
comment
Если использование OpenGL 2.0 для вас не вариант, то, я думаю, вы застряли с решением, подобным приведенному выше. Если у кого-то еще есть блестящая идея.. - person Will Kru; 03.08.2011