Проблема определения RGB компонент в 16-битном режиме (OpenGL, glReadPixels)
главная страница статьи файлы о сайте ссылки
Проблема определения RGB компонент
в 16-битном режиме (OpenGL, glReadPixels)

Георгий Мошкин
tmtlib@narod.ru

Проблема правильного определения RGB компонент
в 16-битном режиме. Необходимо точно определить
значение цвета, который выведен
Условия:


glShadeModel(GL_FLAT);
glHint(GL_POLYGON_SMOOTH_HINT, GL_FASTEST);

В 32bpp режиме всё работает нормально.
какой RGB цвет выведен на экран, такой и считывается:
пишем 0,100,0 считываем 0,100,0
пишем 5,4,5 считываем 5,4,5
пишем 230,240,250 считываем 230,240,250

А вот в 16-битном режиме эти значения искажаются:
0,100,0 превращается в 0,101,0
5,4,5 превращается в 8,4,8
230,240,250 превращается в 239, 243, 255
и т.д..

Вывожу треугольник цвета R,G,B (0,100,0)


glColor3ub(0,100,0);

glBegin (GL_TRIANGLES);
glVertex2f (0, 0);
glVertex2f (1, 0);
glVertex2f (1, 1);
glEnd;

Считываю цвет


pix : Array [0..2] of GLUbyte;

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels (X, Y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, @pix);

И вывожу сообщение с компонентами цвета


ShowMessage(IntToStr(pix[0])+', '+IntToStr(pix[1])+', '+IntToStr(pix[2]));

Цвет оказывается неправильным.

Встаёт вопрос как это исправить? Решение следующее:

Вопрос разрешился следующим образом:

r,g,b:GLInt;

glGetIntegerv (GL_RED_BITS, @r);
glGetIntegerv (GL_GREEN_BITS, @g);
glGetIntegerv (GL_BLUE_BITS, @b);

Таким образом мы получаем текущий цветовой режим (сколько битов на цвет)
565, 888 и т.п.

565 <-> r=5 g=6 b=5

на основе этих данных (количество бит r,g,b) генерирую другие цвета с использованием
функций shr shl.

Например, для пяти бит синего цвета


for i:=0 to 31 do
ShowMessage(IntToStr( i shl 3 ));

(так как 11111 = 31)

Короче говоря, на вход glColor3ub нужно подавать компоненты, преобразованные из r,g,b бит в 8 бит. Для режима 565:
для r: 0-31 shl 3
для g: 0-63 shl 2
для b: 0-31 shl 3

Прочитанные данные из glReadPixels нужно перевести обратно в 5,6 и 5 бит соответственно компонентам pix[0], pix[1] и pix[2].

Мне же не нужны реальные цвета на экране. Я хочу по записанному цвету определить номер объекта. То есть что записал, то и считал. Это вспомогательные цвета, которые человек никогда не увидит (сначала пишется в задний буфер "цветная" картинка, потом затирается реальной картинкой).
Например, я вывел 100 треугольников разных цветов. То есть у каждого треугольника свой цвет. Нажимаю мышкой на экране в координате (x,y), потом делаю glreadpixels в этой точке и по цвету узнаю какой это был треугольник.
Для Radiosity это можно тоже использовать: номер патча(n) записывать в красный цвет (R=n), текстурные координаты(u,v) - в синий и зелёный (G=i, B=v). Хотя, если полигонов больше 256, то можно взять часть бит из другого цвета.

Может кому пригодится:
1) Определяем количество бит, используемых под каждый цвет с помощью glGetIntegerv (смотри выше). Количество бит записываем R,G,B
[code]
r,g,b:GLInt;

glGetIntegerv (GL_RED_BITS, @r);
glGetIntegerv (GL_GREEN_BITS, @g);
glGetIntegerv (GL_BLUE_BITS, @b);

[/code]

2) Определяем количество оттенков, которые можно обеспечить этими битами. Например, для 16-битного режима 5,6,5 (R=5, G=6, B=5):
Красный цвет (R) имеет 2^5 = 32 оттенка (rn=32)
Зелёный цвет (G) имеет 2^6 = 64 оттенка (rn=64)
Синий цвет (B) имеет 2^5 = 32 оттенка (rn=32)
[code]
uses Math;

rn:=round(power(2,r));
gn:=round(power(2,g));
bn:=round(power(2,b));

[/code]

3) При выводе цвета используем только оттенки от rn,gn,bn (0..rn-1,0..gn-1,0..bn-1).
Например, вывод всех доступных оттенков цветов:
[code]
for i:=0 to rn-1 do
for j:=0 to gn-1 do
for k:=0 to bn-1 do
begin
glColor3ub(i shl (8-r), j shl (8-g), k shl (8-b)); // SHL-ом смещаем биты влево
glBegin (GL_POINTS);
glVertex2f(xcoord(i,j,k),ycoord(i,j,k));
glEnd;
end;

[/code]

В строчке с glColor3ub мы преобразуем r,g,b бит в 8,8,8 бит:
[code]
i shl (8-r), j shl (8-g), k shl (8-b)
[/code]

Например, самый яркий цвет режима "r,g,b 5,6,5" будет 31,63,31.
В битах это будет:
31=11111
63=111111
31=11111

После shl на 8-5=3, 8-6=2, 8-5=3:
11111000
11111100
11111000

4) При чтении цвета достаточно сместить оттенок обратно вправо SHR-ом:
[code]
glReadPixels (X, Y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, @pix);
ShowMessage(IntToStr(pix[0] shr (8-r))+', '
+IntToStr(pix[1] shr (8-g))+', '
+IntToStr(pix[2] shr (8-b)));

[/code]

Считанный
11111000 -> 11111
11111100 -> 111111
11111000 -> 11111

Итак, лишние биты справа просто не используются =)
Если режим 8,8,8, то оттенки будут меняться от 0 до 255 (0..255,0..255,0.255) и
функции shl/shr не понадобятся. Так и будет, потому что 8-8=0 и смещения будут
нулевые : shl 0 / shr 0. В 16-битных режимах мы узнаём количество бит на цвет, а
затем и количество оттенков. И действуем по следующей схеме:

номер оттенка N бит -> преобразуем в 8 бит -> выводим на экран
считываем с экрана 8 бит -> преобразуем в N бит -> получили то, что и записали

По сути дела мы же и записали 5 бит, просто сместили их влево. А биты справа нас не интересует. На экран всё равно выведен 8-битный цвет, у которого есть 5 бит, а остальные 3 бита нас не волнуют. После считывания мы берём только 5 бит, которые записали. Для этого смещаем SHR-ом вправо, чтобы не было проблем. В итоге из 8 бит остаётсё только 5 (для режима 5,6,5).