#qt #opengl #textures
#qt #opengl #Текстуры
Вопрос:
У меня огромная проблема с отображением текстур в OpenGL, в основном с прозрачностью. Я пытаюсь воспроизвести многослойный фон, сначала отобразив простую текстуру, а затем более подробную текстуру (например, первая будет синей, а вторая будет содержать горы и прочее). У меня это почти работает, но у меня странный результат, и я не уверен, как это исправить.
Я в основном хочу, чтобы черный цвет в моей второй текстуре не отображался. У меня это работает, но коричневый цвет моих гор, кажется, сливается с первой фоновой текстурой (или добавляется к ней). Мои коричневые горы отображаются как бледно-красные. Моя функция glBlendFunc в настоящее время находится в GL_ONE, но я попытался с помощью GL_ONE_MINUS_SRC_ALPHA ничего не изменить; черный цвет все еще там, горы все еще коричневые.
Я пробовал изображение в трех разных форматах без каких-либо различий (BMP, JPG и PNG).
Ниже приведен код, который у меня есть для обеих текстур:
ТЕКСТУРА 1:
if (buf.load("images/background-layer1.png"))
{
tex1 = QGLWidget::convertToGLFormat( buf );
glPushAttrib(GL_CURRENT_BIT | GL_DEPTH_BUFFER_BIT | GL_ENABLE_BIT);
glGenTextures(1, amp;texture[0]);
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, 4, tex1.width(), tex1.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, tex1.bits());
glEnable(GL_TEXTURE_2D);
glBegin(GL_QUADS);
glTexCoord2f(0, 0);
glVertex2d(m_pBottomLeft.x, m_pBottomLeft.y); //Bottom Left
glTexCoord2f(1, 0);
glVertex2d(m_pBottomRight.x, m_pBottomRight.y); //Bottom Right
glTexCoord2f(1, 1);
glVertex2d(m_pTopRight.x, m_pTopRight.y); //Top Right
glTexCoord2f(0, 1);
glVertex2d(m_pTopLeft.x, m_pTopLeft.y); //Top Left
glEnd();
glDisable(GL_TEXTURE_2D);
glPopAttrib();
}
ТЕКСТУРА 2:
if (buf2.load("images/background-layer2.png"))
{
tex2 = QGLWidget::convertToGLFormat( buf2 );
glPushAttrib(GL_CURRENT_BIT | GL_DEPTH_BUFFER_BIT | GL_ENABLE_BIT);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
glEnable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
glGenTextures(2, amp;texture[1]);
glBindTexture(GL_TEXTURE_2D, texture[1]);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex2.width(), tex2.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, tex2.bits());
glBegin(GL_QUADS);
glColor4f(1, 1, 1, 1);
glTexCoord2f(0, 0);
glVertex2d(m_pBottomLeft.x, m_pBottomLeft.y); //Bottom Left
glTexCoord2f(1, 0);
glVertex2d(m_pBottomRight.x, m_pBottomRight.y); //Bottom Right
glTexCoord2f(1, 1);
glVertex2d(m_pTopRight.x, m_pTopRight.y); //Top Right
glTexCoord2f(0, 1);
glVertex2d(m_pTopLeft.x, m_pTopLeft.y); //Top Left
glEnd();
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glDisable(GL_TEXTURE_2D);
glPopAttrib();
}
Скриншот:
Комментарии:
1. Содержит ли ваша текстура mountain допустимый альфа-канал?
2. Это будет звучать чертовски глупо, но как я могу это выяснить или добавить?
3. Ой, обширный вопрос… альфа-канал позволяет сделать части вашего изображения прозрачными, как вы хотели бы для своих черных частей. Вы можете использовать программное обеспечение для редактирования изображений, чтобы проверить / создать его. Смотрите GIMP, например: docs.gimp.org/en/gimp-using-web-transparency.html
4. Ну, я понимаю, что это значит, но если я хочу, чтобы черные цвета моего изображения были прозрачными, зачем мне сохранять прозрачность? Почему бы не сделать мои прозрачные области черными?
5. Хорошо, по какой-то причине я подумал, что вам обязательно нужно заменить какой-то цвет прозрачностью в OpenGL. То, что я сделал, это сохранил изображение должным образом, с истинными альфа / прозрачными областями. Затем я изменил режим на GL_ONE_MINUS_SRC_ALPHA! Это так здорово. Спасибо rotoglup :)!
Ответ №1:
У вас уже есть свое решение, но, эй, можно обойтись без альфа-канала и сделать определенный цвет прозрачным. Во время участия в Global Game Jam (игровое соревнование с ограничением в 48 часов) нам нужно было быстро создать множество спрайтов для разных объектов, не используя никаких сложных инструментов.
В итоге мы использовали цифровую камеру и Windows paint (mspaint). Мы установили правило, согласно которому верхний левый угол изображения всегда должен содержать прозрачный цвет (таким образом, прозрачный цвет может быть практически любого цвета, выбранного художником). Когда изображение было загружено, альфа-канал был настроен соответственно появлению прозрачного цвета. Хотя это сработало хорошо, все же часть прозрачного цвета просочилась в изображение (благодаря фильтрации текстур).
/**
* @brief a simple raster image with fixed RGBA8 storage
*
* The image data are always RGBA8. Alpha is stored in the most significant byte,
* followed by red, green and blue with decreasing significance.
*
* The storage is very simple, each 32 bits in the buffer contains a single pixel,
* the first pixel is in top left corner, there is no scanline padding.
*/
struct TBmp {
char n_former_bpp; /**< @brief former bpp, before conversion to RGBA8 */
bool b_grayscale; /**< @brief grayscale flag (if set, the bitmap is assumed
to contain grayscale image, stored as RGBA8) */
bool b_alpha; /**< @brief alpha channel flag (if set, the alpha channel is significant;
otherwise it's expected to be 0xff in all image pixels) */
int n_width; /**< @brief image width, in pixels */
int n_height; /**< @brief image height, in pixels */
uint32_t *p_buffer; /**< @brief pointer to image data */
};
void TransparentColor_to_Alpha(TBmp *p_sprite, bool b_force_alpha_recalc = false)
{
if(b_force_alpha_recalc || !p_sprite->b_alpha) {
uint32_t n_transparent_color = p_sprite->p_buffer[0] amp; 0xffffff;
// get transparent color from lower left corner
for(int i = 0, n = p_sprite->n_width * p_sprite->n_height; i < n; i) {
uint32_t n_color = p_sprite->p_buffer[i];
if(n_color == n_transparent_color)
;//p_sprite->p_buffer[i] = n_color; // do nothing, color is transparent and alpha is zero
else if((n_color amp; 0xffffff) == n_transparent_color)
p_sprite->p_buffer[i] = n_color amp; 0xffffff; // clear alpha
else
p_sprite->p_buffer[i] = n_color | 0xff000000U; // set alpha
}
// calculate alpha based on transparent color (binary only)
p_sprite->b_alpha = true;
}
// build alpha channel using "transparent color"
}
Чтобы удалить прозрачный цвет с изображения, мы написали дополнительную функцию, которая дублировала бы цвет граничных пикселей, эффективно стирая прозрачный цвет с изображения (это можно сделать, потому что прозрачность теперь находится в альфа-канале).
bool Sprite_FloodEdgeColor(TBmp *p_sprite, int n_max_grow_step_num = 0)
{
{
uint32_t n_transparent_color = p_sprite->p_buffer[0] amp; 0xffffff;
// get transparent color from lower left corner
TBmp *p_clone;
if(!(p_clone = p_sprite->p_Clone()))
return false;
// clone the bitmap
uint32_t *p_buffer = p_sprite->p_buffer;
uint32_t *p_buffer_pong = p_clone->p_buffer;
for(int i = 0; !n_max_grow_step_num || i < n_max_grow_step_num; i) {
bool b_change = false;
for(int y = 0, w = p_sprite->n_width, h = p_sprite->n_height; y < h; y) {
for(int x = 0; x < w; x) {
if(p_buffer[x w * y] == n_transparent_color) {
int n_neigh_rb = 0, n_neigh_g = 0;
int n_neigh_num = 0;
for(int sy = max(1, y) - 1, ey = min(y 1, h - 1); sy <= ey; sy) {
for(int sx = max(1, x) - 1, ex = min(x 1, w - 1); sx <= ex; sx) {
if(sx == x amp;amp; sy == y)
continue; // skip self (it's transparent anyway)
uint32_t n_neigh = p_buffer[sx w * sy];
if(n_neigh != n_transparent_color) {
n_neigh_rb = n_neigh amp; 0xff00ff;
n_neigh_g = n_neigh amp; 0xff00;
n_neigh_num;
}
}
}
// gather neighbour colors
if(n_neigh_num > 2) {
int r = (n_neigh_rb amp; 0xffff0000) / n_neigh_num;
int g = n_neigh_g / n_neigh_num;
int b = (n_neigh_rb amp; 0xffff) / n_neigh_num;
uint32_t n_color = (0xff0000 amp; min(0xff0000, r)) |
(0xff00 amp; min(0xff00, g)) | (0xff amp; min(0xff, b));
// calculate average neighbor color
p_buffer_pong[x w * y] = n_color;
b_change = true;
}
} else
p_buffer_pong[x w * y] = p_buffer[x w * y]; // just copy
}
}
// grow 1px into transparent color
if(b_change || p_buffer != p_sprite->p_buffer)
std::swap(p_buffer, p_buffer_pong);
// swap the buffers ...
if(!b_change)
break;
}
if(p_buffer != p_sprite->p_buffer) {
memcpy(p_sprite->p_buffer, p_buffer,
p_sprite->n_width * p_sprite->n_height * sizeof(uint32_t));
}
// in case the last result is not in
p_clone->Delete();
// cleanup
}
// bleed colors on edge into the transparent space (to enable hifi blending)
return true;
}
Это было почти все, но на снимках объектов, которые мы делали с помощью цифровой камеры, часто были более яркие пиксели по краям, которые особенно мешали смотреть игроку. Итак, мы написали еще одну функцию, которая использовала бы медианный фильтр для удаления ярких пикселей с границы (в то время как остальная часть изображения остается незатронутой).
bool SpriteEdge_MedianFilter(TBmp *p_sprite,
bool b_prefer_darker = true, bool b_5x5_median = true)
{
{
uint32_t n_transparent_color = p_sprite->p_buffer[0] amp; 0xffffff;
// get transparent color from lower left corner
TBmp *p_clone;
if(!(p_clone = p_sprite->p_Clone()))
return false;
// clone the bitmap
uint32_t *p_buffer = p_sprite->p_buffer;
uint32_t *p_buffer_pong = p_clone->p_buffer;
{
const int n_off = (b_5x5_median)? 2 : 1;
const int n_thresh = (b_5x5_median)? 25 : 9;
bool b_change = false;
for(int y = 0, w = p_sprite->n_width, h = p_sprite->n_height; y < h; y) {
for(int x = 0; x < w; x) {
if(p_buffer[x w * y] != n_transparent_color) {
uint32_t p_neigh_color[25];
int n_neigh_num = 0;
for(int sy = max(n_off, y) - n_off,
ey = min(y n_off, h - 1); sy <= ey; sy) {
for(int sx = max(n_off, x) - n_off,
ex = min(x n_off, w - 1); sx <= ex; sx) {
uint32_t n_neigh = p_buffer[sx w * sy];
if(n_neigh != n_transparent_color) {
p_neigh_color[n_neigh_num] = n_neigh;
n_neigh_num;
}
}
}
// gather neighbour colors (including self)
if(n_neigh_num < n_thresh) { // if the pixel is on the edge ...
uint32_t r[25], g[25], b[25];
for(int i = 0; i < n_neigh_num; i) {
r[i] = p_neigh_color[i] amp; 0xff0000;
g[i] = p_neigh_color[i] amp; 0xff00;
b[i] = p_neigh_color[i] amp; 0xff;
}
std::sort(r, r n_neigh_num);
std::sort(g, g n_neigh_num);
std::sort(b, b n_neigh_num);
// calculate median neighbor color
uint32_t n_self = p_buffer[x w * y];
int mr, mg, mb;
if(b_prefer_darker) {
mr = min(r[n_neigh_num / 2], n_self amp; 0xff0000);
mg = min(g[n_neigh_num / 2], n_self amp; 0xff00);
mb = min(b[n_neigh_num / 2], n_self amp; 0xff);
} else {
mr = r[n_neigh_num / 2];
mg = g[n_neigh_num / 2];
mb = b[n_neigh_num / 2];
}
int a = n_self amp; 0xff000000U;
p_buffer_pong[x w * y] = mr | mg | mb | a;
b_change = true;
}
} else
p_buffer_pong[x w * y] = p_buffer[x w * y]; // just copy
}
}
// grow 1px into transparent color
if(b_change || p_buffer != p_sprite->p_buffer)
std::swap(p_buffer, p_buffer_pong);
// swap the buffers ...
}
if(p_buffer != p_sprite->p_buffer) {
memcpy(p_sprite->p_buffer, p_buffer,
p_sprite->n_width * p_sprite->n_height * sizeof(uint32_t));
}
// in case the last result is not in
p_clone->Delete();
// cleanup
}
return true;
}
На самом деле мы написали еще одну функцию, которая размывает непрозрачные части изображения, эффективно уменьшая размер спрайта на выбранное количество пикселей и удаляя проблемные области на случай, если их невозможно удалить с помощью функции медианы. Вот и все, хотя это было написано примерно за час, это практически идеальный инструмент для создания быстрых и грязных спрайтов.
Получите полный исходный код.