/* Copyright (C) 1997-2001 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // r_bloom.c: 2D lighting post process effect #include "r_local.h" /* ============================================================================== LIGHT BLOOMS ============================================================================== */ static float Diamond8x[8][8] = { 0.0f, 0.0f, 0.0f, 0.1f, 0.1f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.2f, 0.3f, 0.3f, 0.2f, 0.0f, 0.0f, 0.0f, 0.2f, 0.4f, 0.6f, 0.6f, 0.4f, 0.2f, 0.0f, 0.1f, 0.3f, 0.6f, 0.9f, 0.9f, 0.6f, 0.3f, 0.1f, 0.1f, 0.3f, 0.6f, 0.9f, 0.9f, 0.6f, 0.3f, 0.1f, 0.0f, 0.2f, 0.4f, 0.6f, 0.6f, 0.4f, 0.2f, 0.0f, 0.0f, 0.0f, 0.2f, 0.3f, 0.3f, 0.2f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.1f, 0.1f, 0.0f, 0.0f, 0.0f }; static float Diamond6x[6][6] = { 0.0f, 0.0f, 0.1f, 0.1f, 0.0f, 0.0f, 0.0f, 0.3f, 0.5f, 0.5f, 0.3f, 0.0f, 0.1f, 0.5f, 0.9f, 0.9f, 0.5f, 0.1f, 0.1f, 0.5f, 0.9f, 0.9f, 0.5f, 0.1f, 0.0f, 0.3f, 0.5f, 0.5f, 0.3f, 0.0f, 0.0f, 0.0f, 0.1f, 0.1f, 0.0f, 0.0f }; static float Diamond4x[4][4] = { 0.3f, 0.4f, 0.4f, 0.3f, 0.4f, 0.9f, 0.9f, 0.4f, 0.4f, 0.9f, 0.9f, 0.4f, 0.3f, 0.4f, 0.4f, 0.3f }; static int BLOOM_SIZE; cvar_t *r_bloom_alpha; cvar_t *r_bloom_diamond_size; cvar_t *r_bloom_intensity; cvar_t *r_bloom_threshold; cvar_t *r_bloom_darken; cvar_t *r_bloom_sample_size; cvar_t *r_bloom_fast_sample; image_t *r_bloomscreentexture; image_t *r_bloomeffecttexture; image_t *r_bloombackuptexture; image_t *r_bloomdownsamplingtexture; static int r_screendownsamplingtexture_size; static int screen_texture_width, screen_texture_height; //static int r_screenbackuptexture_size; static int r_screenbackuptexture_width, r_screenbackuptexture_height; // current refdef size: static int curView_x; static int curView_y; static int curView_width; static int curView_height; // texture coordinates of screen data inside screentexture static float screenText_tcw; static float screenText_tch; static int sample_width; static int sample_height; // texture coordinates of adjusted textures static float sampleText_tcw; static float sampleText_tch; // this macro is in sample size workspace coordinates #define R_Bloom_DrawStart \ rb_vertex = rb_index = 0; #define R_Bloom_DrawFinish \ RB_DrawArrays (); \ rb_vertex = rb_index = 0; #define R_Bloom_SamplePass( xpos, ypos, r, g, b, a ) \ indexArray[rb_index++] = rb_vertex+0; \ indexArray[rb_index++] = rb_vertex+1; \ indexArray[rb_index++] = rb_vertex+2; \ indexArray[rb_index++] = rb_vertex+0; \ indexArray[rb_index++] = rb_vertex+2; \ indexArray[rb_index++] = rb_vertex+3; \ VA_SetElem2(texCoordArray[0][rb_vertex], 0, sampleText_tch); \ VA_SetElem3(vertexArray[rb_vertex], xpos, ypos, 0); \ VA_SetElem4(colorArray[rb_vertex], r, g, b, a); \ rb_vertex++; \ VA_SetElem2(texCoordArray[0][rb_vertex], 0, 0); \ VA_SetElem3(vertexArray[rb_vertex], xpos, ypos+sample_height, 0); \ VA_SetElem4(colorArray[rb_vertex], r, g, b, a); \ rb_vertex++; \ VA_SetElem2(texCoordArray[0][rb_vertex], sampleText_tcw, 0); \ VA_SetElem3(vertexArray[rb_vertex], xpos+sample_width, ypos+sample_height, 0); \ VA_SetElem4(colorArray[rb_vertex], r, g, b, a); \ rb_vertex++; \ VA_SetElem2(texCoordArray[0][rb_vertex], sampleText_tcw, sampleText_tch); \ VA_SetElem3(vertexArray[rb_vertex], xpos+sample_width, ypos, 0); \ VA_SetElem4(colorArray[rb_vertex], r, g, b, a); \ rb_vertex++; #define R_Bloom_Quad( x, y, width, height, textwidth, textheight, r, g, b, a ) \ rb_vertex = rb_index = 0; \ indexArray[rb_index++] = rb_vertex+0; \ indexArray[rb_index++] = rb_vertex+1; \ indexArray[rb_index++] = rb_vertex+2; \ indexArray[rb_index++] = rb_vertex+0; \ indexArray[rb_index++] = rb_vertex+2; \ indexArray[rb_index++] = rb_vertex+3; \ VA_SetElem2(texCoordArray[0][rb_vertex], 0, textheight); \ VA_SetElem3(vertexArray[rb_vertex], x, y, 0); \ VA_SetElem4(colorArray[rb_vertex], r, g, b, a); \ rb_vertex++; \ VA_SetElem2(texCoordArray[0][rb_vertex], 0, 0); \ VA_SetElem3(vertexArray[rb_vertex], x, y+height, 0); \ VA_SetElem4(colorArray[rb_vertex], r, g, b, a); \ rb_vertex++; \ VA_SetElem2(texCoordArray[0][rb_vertex], textwidth, 0); \ VA_SetElem3(vertexArray[rb_vertex], x+width, y+height, 0); \ VA_SetElem4(colorArray[rb_vertex], r, g, b, a); \ rb_vertex++; \ VA_SetElem2(texCoordArray[0][rb_vertex], textwidth, textheight); \ VA_SetElem3(vertexArray[rb_vertex], x+width, y, 0); \ VA_SetElem4(colorArray[rb_vertex], r, g, b, a); \ rb_vertex++; \ RB_DrawArrays (); \ rb_vertex = rb_index = 0; /* ================= R_Bloom_InitBackUpTexture ================= */ void R_Bloom_InitBackUpTexture (int width, int height) { byte *data; data = malloc( width * height * 4 ); memset( data, 0, width * height * 4 ); // r_screenbackuptexture_size = width; r_screenbackuptexture_width = width; r_screenbackuptexture_height = height; r_bloombackuptexture = R_LoadPic ("***r_bloombackuptexture***", (byte *)data, width, height, it_pic, 3); free ( data ); } /* ================= R_Bloom_InitEffectTexture ================= */ void R_Bloom_InitEffectTexture (void) { byte *data; float bloomsizecheck; if ( r_bloom_sample_size->integer < 32 ) Cvar_SetValue ("r_bloom_sample_size", 32); // make sure bloom size is a power of 2 BLOOM_SIZE = r_bloom_sample_size->integer; bloomsizecheck = (float)BLOOM_SIZE; while(bloomsizecheck > 1.0f) bloomsizecheck /= 2.0f; if ( bloomsizecheck != 1.0f ) { BLOOM_SIZE = 32; while( BLOOM_SIZE < r_bloom_sample_size->integer ) BLOOM_SIZE *= 2; } // make sure bloom size doesn't have stupid values if ( BLOOM_SIZE > screen_texture_width || BLOOM_SIZE > screen_texture_height ) BLOOM_SIZE = min( screen_texture_width, screen_texture_height ); if ( BLOOM_SIZE != r_bloom_sample_size->integer ) Cvar_SetValue ("r_bloom_sample_size", BLOOM_SIZE); // Knightmare- catch too large bloom size while ( ((BLOOM_SIZE * 2) > vid.height) || ((BLOOM_SIZE * 2) > vid.width) ) BLOOM_SIZE /= 2; data = malloc( BLOOM_SIZE * BLOOM_SIZE * 4 ); memset( data, 0, BLOOM_SIZE * BLOOM_SIZE * 4 ); r_bloomeffecttexture = R_LoadPic ("***r_bloomeffecttexture***", (byte *)data, BLOOM_SIZE, BLOOM_SIZE, it_pic, 3); free ( data ); } /* ================= R_Bloom_InitTextures ================= */ void R_Bloom_InitTextures (void) { byte *data; int size; // find closer power of 2 to screen size for (screen_texture_width = 1; screen_texture_width < vid.width; screen_texture_width *= 2); for (screen_texture_height = 1; screen_texture_height < vid.height; screen_texture_height *= 2); // disable blooms if we can't handle a texture of that size /* if ( screen_texture_width > glConfig.maxTextureSize || screen_texture_height > glConfig.maxTextureSize ) { screen_texture_width = screen_texture_height = 0; Cvar_SetValue ("r_bloom", 0); Com_Printf( "WARNING: 'R_InitBloomScreenTexture' too high resolution for Light Bloom. Effect disabled\n" ); return; }*/ //init the screen texture size = screen_texture_width * screen_texture_height * 4; data = malloc( size ); memset( data, 255, size ); r_bloomscreentexture = R_LoadPic ("***r_bloomscreentexture***", (byte *)data, screen_texture_width, screen_texture_height, it_pic, 3); free ( data ); // validate bloom size and init the bloom effect texture R_Bloom_InitEffectTexture (); // if screensize is more than 2x the bloom effect texture, set up for stepped downsampling r_bloomdownsamplingtexture = NULL; r_screendownsamplingtexture_size = 0; if ( vid.width > (BLOOM_SIZE * 2) && !r_bloom_fast_sample->integer ) { r_screendownsamplingtexture_size = (int)(BLOOM_SIZE * 2); data = malloc( r_screendownsamplingtexture_size * r_screendownsamplingtexture_size * 4 ); memset( data, 0, r_screendownsamplingtexture_size * r_screendownsamplingtexture_size * 4 ); r_bloomdownsamplingtexture = R_LoadPic ("***r_bloomdownsamplingtexture***", (byte *)data, r_screendownsamplingtexture_size, r_screendownsamplingtexture_size, it_pic, 3); free ( data ); } // Init the screen backup texture if ( r_screendownsamplingtexture_size ) R_Bloom_InitBackUpTexture( r_screendownsamplingtexture_size, r_screendownsamplingtexture_size ); else R_Bloom_InitBackUpTexture( BLOOM_SIZE, BLOOM_SIZE ); } /* ================= R_InitBloomTextures ================= */ void R_InitBloomTextures (void) { // r_bloom = Cvar_Get( "r_bloom", "0", CVAR_ARCHIVE ); r_bloom_alpha = Cvar_Get( "r_bloom_alpha", "0.25", CVAR_ARCHIVE ); // was 0.33 r_bloom_diamond_size = Cvar_Get( "r_bloom_diamond_size", "8", CVAR_ARCHIVE ); r_bloom_intensity = Cvar_Get( "r_bloom_intensity", "2.5", CVAR_ARCHIVE ); // was 0.6 r_bloom_threshold = Cvar_Get( "r_bloom_threshold", "0.68", CVAR_ARCHIVE ); // was 0.08 r_bloom_darken = Cvar_Get( "r_bloom_darken", "5", CVAR_ARCHIVE ); // was 4 r_bloom_sample_size = Cvar_Get( "r_bloom_sample_size", "256", CVAR_ARCHIVE ); // was 128 r_bloom_fast_sample = Cvar_Get( "r_bloom_fast_sample", "0", CVAR_ARCHIVE ); BLOOM_SIZE = 0; if (!r_bloom->integer) return; R_Bloom_InitTextures (); } /* ================= R_Bloom_DrawEffect ================= */ void R_Bloom_DrawEffect (void) { GL_Bind(r_bloomeffecttexture->texnum); GL_Enable(GL_BLEND); GL_BlendFunc(GL_ONE, GL_ONE); GL_TexEnv(GL_MODULATE); rb_vertex = rb_index = 0; indexArray[rb_index++] = rb_vertex+0; indexArray[rb_index++] = rb_vertex+1; indexArray[rb_index++] = rb_vertex+2; indexArray[rb_index++] = rb_vertex+0; indexArray[rb_index++] = rb_vertex+2; indexArray[rb_index++] = rb_vertex+3; VA_SetElem2(texCoordArray[0][rb_vertex], 0, sampleText_tch); VA_SetElem3(vertexArray[rb_vertex], curView_x, curView_y, 0); VA_SetElem4(colorArray[rb_vertex], r_bloom_alpha->value, r_bloom_alpha->value, r_bloom_alpha->value, 1.0f); rb_vertex++; VA_SetElem2(texCoordArray[0][rb_vertex], 0, 0); VA_SetElem3(vertexArray[rb_vertex], curView_x, curView_y + curView_height, 0); VA_SetElem4(colorArray[rb_vertex], r_bloom_alpha->value, r_bloom_alpha->value, r_bloom_alpha->value, 1.0f); rb_vertex++; VA_SetElem2(texCoordArray[0][rb_vertex], sampleText_tcw, 0); VA_SetElem3(vertexArray[rb_vertex], curView_x + curView_width, curView_y + curView_height, 0); VA_SetElem4(colorArray[rb_vertex], r_bloom_alpha->value, r_bloom_alpha->value, r_bloom_alpha->value, 1.0f); rb_vertex++; VA_SetElem2(texCoordArray[0][rb_vertex], sampleText_tcw, sampleText_tch); VA_SetElem3(vertexArray[rb_vertex], curView_x + curView_width, curView_y, 0); VA_SetElem4(colorArray[rb_vertex], r_bloom_alpha->value, r_bloom_alpha->value, r_bloom_alpha->value, 1.0f); rb_vertex++; RB_DrawArrays (); rb_vertex = rb_index = 0; GL_Disable (GL_BLEND); } #if 0 /* ================= R_Bloom_GeneratexCross - alternative bluring method ================= */ void R_Bloom_GeneratexCross (void) { int i; static int BLOOM_BLUR_RADIUS = 8; // static float BLOOM_BLUR_INTENSITY = 2.5f; float BLOOM_BLUR_INTENSITY; static float intensity; static float range; // set up sample size workspace qglViewport( 0, 0, sample_width, sample_height ); qglMatrixMode( GL_PROJECTION ); qglLoadIdentity (); qglOrtho(0, sample_width, sample_height, 0, -10, 100); qglMatrixMode( GL_MODELVIEW ); qglLoadIdentity (); // copy small scene into r_bloomeffecttexture GL_Bind(0, r_bloomeffecttexture); qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, sample_width, sample_height); // start modifying the small scene corner GL_Enable(GL_BLEND); // darkening passes if (r_bloom_darken->integer) { GL_BlendFunc(GL_DST_COLOR, GL_ZERO); GL_TexEnv(GL_MODULATE); R_Bloom_DrawStart for (i=0; iinteger; i++) { R_Bloom_SamplePass( 0, 0, 1.0f, 1.0f, 1.0f, 1.0f ); } R_Bloom_DrawFinish qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, sample_width, sample_height); } // bluring passes if ( BLOOM_BLUR_RADIUS ) { GL_BlendFunc(GL_ONE, GL_ONE); range = (float)BLOOM_BLUR_RADIUS; BLOOM_BLUR_INTENSITY = r_bloom_intensity->value; // diagonal-cross draw 4 passes to add initial smooth R_Bloom_DrawStart R_Bloom_SamplePass( 1, 1, 0.5f, 0.5f, 0.5f, 1.0 ); R_Bloom_SamplePass( -1, 1, 0.5f, 0.5f, 0.5f, 1.0 ); R_Bloom_SamplePass( -1, -1, 0.5f, 0.5f, 0.5f, 1.0 ); R_Bloom_SamplePass( 1, -1, 0.5f, 0.5f, 0.5f, 1.0 ); R_Bloom_DrawFinish qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, sample_width, sample_height); R_Bloom_DrawStart for (i=-(BLOOM_BLUR_RADIUS+1);itexnum); qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, sample_width, sample_height); // start modifying the small scene corner GL_Enable(GL_BLEND); // darkening passes if ( r_bloom_darken->integer ) { GL_BlendFunc(GL_DST_COLOR, GL_ZERO); GL_TexEnv(GL_MODULATE); R_Bloom_DrawStart for (i=0; iinteger; i++) { R_Bloom_SamplePass( 0, 0, 1.0f, 1.0f, 1.0f, 1.0f ); } R_Bloom_DrawFinish qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, sample_width, sample_height); } // bluring passes // GL_BlendFunc(GL_ONE, GL_ONE); GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR); if ( r_bloom_diamond_size->integer > 7 || r_bloom_diamond_size->integer <= 3) { if ( r_bloom_diamond_size->integer != 8 ) Cvar_SetValue( "r_bloom_diamond_size", 8 ); R_Bloom_DrawStart for (i=0; iinteger; i++) { for (j=0; jinteger; j++) { intensity = r_bloom_intensity->value * 0.3 * Diamond8x[i][j]; if ( intensity < r_bloom_threshold->value ) continue; R_Bloom_SamplePass( i-4, j-4, intensity, intensity, intensity, 1.0 ); } } R_Bloom_DrawFinish } else if ( r_bloom_diamond_size->integer > 5 ) { if ( r_bloom_diamond_size->integer != 6 ) Cvar_SetValue( "r_bloom_diamond_size", 6 ); R_Bloom_DrawStart for (i=0; iinteger; i++) { for (j=0; jinteger; j++) { intensity = r_bloom_intensity->value * 0.5 * Diamond6x[i][j]; if ( intensity < r_bloom_threshold->value ) continue; R_Bloom_SamplePass( i-3, j-3, intensity, intensity, intensity, 1.0 ); } } R_Bloom_DrawFinish } else if ( r_bloom_diamond_size->integer > 3 ) { if ( r_bloom_diamond_size->integer != 4 ) Cvar_SetValue( "r_bloom_diamond_size", 4 ); R_Bloom_DrawStart for (i=0; iinteger; i++) { for (j=0; jinteger; j++) { intensity = r_bloom_intensity->value * 0.8f * Diamond4x[i][j]; if ( intensity < r_bloom_threshold->value ) continue; R_Bloom_SamplePass( i-2, j-2, intensity, intensity, intensity, 1.0 ); } } R_Bloom_DrawFinish } qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, sample_width, sample_height); // restore full screen workspace qglViewport( 0, 0, vid.width, vid.height ); qglMatrixMode( GL_PROJECTION ); qglLoadIdentity (); qglOrtho(0, vid.width, vid.height, 0, -10, 100); qglMatrixMode( GL_MODELVIEW ); qglLoadIdentity (); } /* ================= R_Bloom_DownsampleView ================= */ void R_Bloom_DownsampleView (void) { GL_Disable( GL_BLEND ); // stepped downsample if ( r_screendownsamplingtexture_size ) { int midsample_width = r_screendownsamplingtexture_size * sampleText_tcw; int midsample_height = r_screendownsamplingtexture_size * sampleText_tch; // copy the screen and draw resized GL_Bind(r_bloomscreentexture->texnum); qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, curView_x, vid.height - (curView_y + curView_height), curView_width, curView_height); R_Bloom_Quad( 0, vid.height-midsample_height, midsample_width, midsample_height, screenText_tcw, screenText_tch, 1.0f, 1.0f, 1.0f, 1.0f ); // now copy into Downsampling (mid-sized) texture GL_Bind(r_bloomdownsamplingtexture->texnum); qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, midsample_width, midsample_height); // now draw again in bloom size R_Bloom_Quad( 0, vid.height-sample_height, sample_width, sample_height, sampleText_tcw, sampleText_tch, 0.5f, 0.5f, 0.5f, 1.0f ); // now blend the big screen texture into the bloom generation space (hoping it adds some blur) GL_Enable( GL_BLEND ); GL_BlendFunc(GL_ONE, GL_ONE); GL_Bind(r_bloomscreentexture->texnum); R_Bloom_Quad( 0, vid.height-sample_height, sample_width, sample_height, screenText_tcw, screenText_tch, 0.5f, 0.5f, 0.5f, 1.0f ); GL_Disable( GL_BLEND ); } else // downsample simple { GL_Bind(r_bloomscreentexture->texnum); qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, curView_x, vid.height - (curView_y + curView_height), curView_width, curView_height); R_Bloom_Quad( 0, vid.height-sample_height, sample_width, sample_height, screenText_tcw, screenText_tch, 1.0f, 1.0f, 1.0f, 1.0f ); } } /* ================= R_BloomBlend ================= */ void R_SetupGL (void); void R_BloomBlend ( refdef_t *fd ) { if ( (fd->rdflags & RDF_NOWORLDMODEL) || !r_bloom->integer || r_showtris->integer ) return; if ( !BLOOM_SIZE ) R_Bloom_InitTextures(); if ( screen_texture_width < BLOOM_SIZE || screen_texture_height < BLOOM_SIZE ) return; // set up full screen workspace qglViewport ( 0, 0, vid.width, vid.height ); GL_TexEnv (GL_REPLACE); // Knightmare added GL_Disable (GL_DEPTH_TEST); qglMatrixMode (GL_PROJECTION); qglLoadIdentity (); qglOrtho(0, vid.width, vid.height, 0, -10, 100); qglMatrixMode (GL_MODELVIEW); qglLoadIdentity (); GL_Disable (GL_CULL_FACE); GL_Disable (GL_BLEND); GL_EnableTexture (0); // set up current sizes curView_x = fd->x; curView_y = fd->y; curView_width = fd->width; curView_height = fd->height; screenText_tcw = ((float)fd->width / (float)screen_texture_width); screenText_tch = ((float)fd->height / (float)screen_texture_height); if ( fd->height > fd->width ) { sampleText_tcw = ((float)fd->width / (float)fd->height); sampleText_tch = 1.0f; } else { sampleText_tcw = 1.0f; sampleText_tch = ((float)fd->height / (float)fd->width); } sample_width = BLOOM_SIZE * sampleText_tcw; sample_height = BLOOM_SIZE * sampleText_tch; // copy the screen space we'll use to work into the backup texture GL_Bind(r_bloombackuptexture->texnum); // qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, r_screenbackuptexture_size * sampleText_tcw, r_screenbackuptexture_size * sampleText_tch); qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, r_screenbackuptexture_width, r_screenbackuptexture_height); // create the bloom image R_Bloom_DownsampleView(); R_Bloom_GeneratexDiamonds(); // R_Bloom_GeneratexCross(); // restore the screen-backup to the screen GL_Disable(GL_BLEND); GL_Bind(r_bloombackuptexture->texnum); /* R_Bloom_Quad( 0, vid.height - (r_screenbackuptexture_size * sampleText_tch), r_screenbackuptexture_size * sampleText_tcw, r_screenbackuptexture_size * sampleText_tch, sampleText_tcw, sampleText_tch, 1, 1, 1, 1 ); */ R_Bloom_Quad( 0, vid.height - r_screenbackuptexture_height, r_screenbackuptexture_width, r_screenbackuptexture_height, 1.0, 1.0, 1, 1, 1, 1 ); R_Bloom_DrawEffect(); // Knightmare added R_SetupGL (); GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GL_EnableTexture (0); // qglColor4f(1,1,1,1); }