OpenGL之紋理映射使用mipmap

OpenGL之紋理映射使用mipmap

紋理繪畫多邊形當遠離視點,會出現視覺失真或炫目問題,原因是OpenGL對兩個相鄰的像素進行采樣時,從紋理圖中的一些差別較大的部分進行采樣所產生的,在靜止場景問題並不明顯,若運動時紋理紋理采樣發生改變時炫目問題將會更特出,使用mipmap『紋理鏈』能消除部分失真,因為低解釋度的紋理被應用到遠距離的多邊形上時采樣更加精准.因為紋理一級級縮小時紋理可以保持平滑,使用mipmap『紋理鏈』的另一個好處是較小的紋理更容易保存在顯卡『記憶體』中提高命中率.

OpenGL的gluBuild2DMipmaps()可以全自動的生成mipmap『紋理鏈』.但不仿自已動手生成.

mipmap『紋理鏈』演示程式下載:

  1. 按M鍵可切換『紋理鏈』啟用與禁用,當啟用時炫目失真將大副減小.
  2. 按ESC鍵相機返回原點.
  3. 按方向鍵移動相機

紋理結構定義可梯『OpenGL之紋理映射』OpenGL根據目標圖像的大小選擇紋理圖像,『紋理鏈』索引從0開始到分辨率1*1 結束,所以你要對紋理近行縮小:

 

生成紋理鏈代碼

1.不斷縮小紋理圖,直到分辨率1*1

while (width != 1 && height != 1){

2.獲取索引

index = texture->count;

3.縮小紋理

texture->image[index] = Decrease_Image_Texture(texture->image[index-1], width, height);

4.紋理個數加一

++texture->count;

5.寬與高減一半

width = width / 2;

height = height / 2;

}

紋理圖像的寬和高均縮小一半的函式

image:紋理圖

width,height:寬和高

PBYTE Decrease_Image_Texture(PBYTE image,int width,int height){

int r1,r2,r3,r4, g1,g2,g3,g4, b1,b2,b3,b4 ;

int index,x,y;

//分配縮小紋理記憶體

buffer = (PBYTE)malloc(width/2 * height/2 * 3);

for (y = 0; y < height; y = y + 2) //遍歷Y座標

{

for (x = 0; x < width; x = x + 2)//遍歷X座標

{

index = Get_Index_Texture(x, y, 0, 0, width, height) * 3;// 提取紋理像素

r1 = image[index + 0];

g1 = image[index + 1];

b1 = image[index + 2];

index = Get_Index_Texture(x, y, 1, 0, width, height) * 3;// 提取紋理像素

r2 = image[index + 0];

g2 = image[index + 1];

b2 = image[index + 2];

index = Get_Index_Texture(x, y, 0, 1, width, height) * 3;// 提取紋理像素

r3 = image[index + 0];

g3 = image[index + 1];

b3 = image[index + 2];

index = Get_Index_Texture(x, y, 1, 1, width, height) * 3; //提取紋理像素

r4 = image[index + 0];

g4 = image[index + 1];

b4 = image[index + 2];

index = (y/2 * width/2 + x/2) * 3;

//加權平均計算目標紋理像素

buffer[index + 0] = (r1 + r2 + r3 + r4) / 4;

buffer[index + 1] = (g1 + g2 + g3 + g4) / 4;

buffer[index + 2] = (b1 + b2 + b3 + b4) / 4;

}

}

 

計算紋理像素索引函式

x,y:座標

dx,dy:座標增量

wdith,height:紋理圖的寬和高

int Get_Index_Texture(int x, int y, int dx, int dy, int width, int height) {

if (x + dx >= width) //判斷x座標是否越界

dx = 0;

if (y + dy >= height) //判斷y座標是否越界

dy = 0;

int index = (y + dy) * width + (x + dx);        計算紋理像素索引

return index;           返回索引

}

 

綁定mipmap『紋理鏈』代碼函式

bool Bind_Texture(TEXTURE_PTR texture){

glGenTextures(1, &texture->ID);//生成紋理

glBindTexture(GL_TEXTURE_2D, texture->ID);//綁定紋理

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);// 重複紋理

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);// 重複紋理

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);//放大紋理像素採樣線性插值(加權平均)

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);//縮小紋理紋理鏈採樣使用NEAREST,過濾使用LINEAR

for (int index = 0, v = 1; index < texture->count; ++index, v = v * 2)//遍歷紋理鏈

glTexImage2D(GL_TEXTURE_2D, index, GL_RGB, texture->width / v, texture->height / v, 0, GL_RGB, GL_UNSIGNED_BYTE, texture->image[index]);    //載入紋理

}

 

OpenGL之紋理映射

OpenGL之紋理映射

將紋理映射到3D模型是革命性技術,給人帶來照片般震撼逼真效果,簡單來講紋理映射就是將圖片附著於多邊形之上,這樣的圖片稱之為紋理,你可以將平鋪的地圖映射到球體上從而得到3D地球模型,下面以渲染木箱為例的演示程式:下載

生成紋理對像 簡介
void glGenTextures(GLsizei n,GLuint *textures); 生成紋理並返回紋理『索引』即ID編號
n 紋理個數
textures 數組,返回紋理ID

 

綁定紋理 簡介
void glBindTexture(GLenum target,GLuint texture); 生成紋理之後要進行綁定
Target GL_TEXTURE_1D:1維紋理

GL_TEXTURE_2D:2維紋理

Textures 紋理ID數組

 

過濾紋理 簡介
void glTexParameteri(GLenum target,GLenum pname,GLint param); 綁定之後要設定紋理過濾
target: GL_TEXTURE_1D:1維紋理

GL_TEXTURE_2D:2維紋理

pname: GL_TEXTURE_MIN_FILTER:縮小過濾

GL_TEXTURE_MAG_FILTER:放大過濾

GL_TEXTURE_WRAP_S:紋理S座標

GL_TEXTURE_WRAP_T:紋理T座標

param:

 

GL_REPEAT:重複(平鋪)紋理

GL_CLAMP:夾持紋理

GL_LINEAR:像素採樣線性插值(加權平均)

GL_NEAREST:像素採樣最接近中心紋理

GL_NEAREST_MIPMAP_NEAREST:紋理鏈採樣使用NEAREST,過濾使用NEAREST

GL_NEAREST_MIPMAP_LINEAR:紋理鏈採樣使用NEAREST,過濾使用LINEAR

GL_LINEAR_MIPMAP_NEAREST:紋理鏈採樣使用LINEAR,過濾使用NEAREST

GL_LINEAR_MIPMAP_LINEAR:紋理鏈採樣使用LINEAR,過濾使用LINEAR

 

載入紋理 簡介
void glTexImage2D(GLenum target,GLint   level,GLint internalformat,GLsizei width,GLsizei height,GLint border,GLint format,GLenum  type,const GLvoid *pixels); 設定紋理過濾後需要把紋理載入OpenGL

type:最常用GL_UNSIGNED_BYTE(無符號8Bit)

pixels:紋理數據

target: GL_TEXTURE_1D:1維紋理

GL_TEXTURE_2D:2維紋理

level: 紋理鏈索引,若只有單個紋理則設為0
internalformat: 最常用GL_RGBA或GL_RGB
width: 紋理寬度
height: 紋理高度
border: 紋理是否有變框,0沒有邊框,1有邊框
format: 最常用GL_RGBA或GL_RGB

 

紋理座標與頂點座標 簡介
void glTexCoord2f (GLfloat s, GLfloat t); 設定紋理座標(s,t), s軸紋理的x座標,t軸為紋理的y座標,紋理座標必需在頂點座標之前設定
void glVertex3f(GLfloat x,GLfloat y,GLfloat z); 設定頂點座標,並與紋理座標匹配

 

定義紋理結構 簡介
typedef struct TEXTURE_TYP{ 此結構用於保存紋理位圖信息
int   width; 紋理寬度
int   height; 紋理高度
int   bitCount; 位圖像素的bits (8BIT,16BIT,24BIT,32BIT)
int   size; 紋理數據的大小
PALETTEENTRY paletteentry; 位圖調色板
GLuint          ID; 紋理ID
PBYTE images[32]; mipmap紋理鏈
int   count; 紋理鏈個數
}TEXTURE, *TEXTURE_PTR;

 

 

紋理映射代碼示例

1.啟用深度緩衝測試,可保證多邊形被正確繪製

glEnable(GL_DEPTH_TEST);

2.啟動漸變效果

glShadeModel(GL_SMOOTH);

3.啟動多邊形隱面裁剪(消除隱藏面)如果要穿越實體則無需啟動

glEnable(GL_CULL_FACE);

4.設背面為隱面

glCullFace(GL_BACK);

5.多邊形正面使用逆時針

glFrontFace(GL_CCW);

6.啟用2D紋理映射

glEnable(GL_TEXTURE_2D);

7.載入位圖紋理(.bmp圖檔)( .tga圖檔)

TEXTURE texture;

Load_File_Texture(&texture,path);

8.生成1個紋理

glGenTextures(1, &texture->ID);

9.綁定紋理

glBindTexture(GL_TEXTURE_2D, texture->ID);

10.放大紋理像素採樣最接近中心紋理

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

11.縮小紋理過濾像素採樣最接近中心紋理

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

12.載入紋理

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texture->width, texture->height, 0, GL_RGB, GL_UNSIGNED_BYTE, texture->image[0]);

 

從指定的磁盤路徑載入紋理函式

bool Load_File_Texture(TEXTURE_PTR texture,const char * path)

{

char drive[_MAX_DRIVE] = { 0 };// 驅動器盤符

char dir[_MAX_DIR] = { 0 };    // 目錄

char fname[_MAX_FNAME] = { 0 };// 文件名

char ext[_MAX_EXT] = { 0 };    // 擴展名!

BITMAP_FILE bitmap;

TARGA_FILE  targa;

PCX         pcx;

if (texture == NULL || path == NULL)

return false;

//將路徑名分拆為組件!

_splitpath(path, drive, dir, fname, ext);

if (stricmp(ext, “.bmp”) == 0)// 載入位圖

{

Load_Bitmap(&bitmap, path);// 載入BMP文檔

Load_Bitmap_Texture(texture, &bitmap);// 載入紋理

}

else

if (stricmp(ext, “.tga”) == 0)// 載入位圖

{

Load_Targa(&targa, path); // 載入tga文檔

Load_Targa_Texture(texture, &targa);// 載入紋理

}

else

if (stricmp(ext, “.pcx”) == 0)// 載入位圖

{

Load_PCX(&pcx, path); // 載入PCX文檔

Load_PCX_Texture(texture, &pcx);// 載入紋理

}

return false;

}

激活紋理

void Bind_Texture(TEXTURE_PTR texture){

glEnable(GL_TEXTURE_2D);

glBindTexture(GL_TEXTURE_2D, texture->ID);// 綁定紋理

}

 

Windows10輸入『』

Windows10輸入『』

「」與『』符號在標準的英語鍵盤中你是穩唔到這些按鍵,但可按以下方法輸入:

  1. 按CTRL+SHIFT切換到『微軟注音輸入法』
  2. 鼠標右鍵按『中/英』彈出菜單
  3. 按『輸入法整合器』
  4. 按『符』-『符號查詢『
  5. 『符號類別』選『標點符號』
  6. 選擇你想要的符號

如果在WORD可使用快捷鍵輸入,按住ALT鍵不放然後再按數字鍵,是唔是好方便呢.

字符 快捷鍵
ALT鍵+12302鍵
ALT鍵+12303鍵
ALT鍵+12300鍵
ALT鍵+12301鍵

 

 

Windows之重複的WM_COMMAND Message

Windows之重複的WM_COMMAND Message

近日將WinXP升為Win10,將VC6升為VS2016.按Menu後在WindowProc()會接收到兩個重複的WM_COMMAND Message.而Accelerator更會接收多個WM_COMMAND Message.同樣的代碼在WinXP和VC6重未出現.不斷查看Winmain()事件循環.因為遊戲引擎是實時驅動而非消息驅動的.所以使用PeekMessage ()而非GetMessage()讀取消Message.當改為while(GetMessage (&msg, NULL, 0, 0))消息驅動後問題無在出現.梯來是MSG這個Message沒有被清空道至.調用memset(&msg, 0, sizeof(MSG)); 問題得到完美解決.

主事件循環代碼簡介

1.WINDOWS消息的存儲器

MSG msg;

2.Accelerator加速鍵表的句柄

HACCEL hAccel;

hAccel =::LoadAccelerators(main_instance,”ACCEL”);

2.主事件循環非消息驅動的

while(true) {

3.清空MSG

memset(&msg, 0, sizeof(MSG));

4.從事件對列中獲得消息

PeekMessage(&msg,NULL,0,0,PM_REMOVE);

5.由 PostQuitMessage(0) 發送的WM_QUIT消息,被PeekMessage()檢測到跳出主循環

if(msg.message == WM_QUIT)

break;

6.處理加速鍵表

if(!::TranslateAccelerator(main_window,hAccel,&msg)) {

7.處理和轉換加速鍵.

TranslateMessage(&msg);

3.調用WinProc對消息進行處理,從MSG結構取的參數並傳遞.

DispatchMessage(&msg);

}

}

OpenGL之讀取Targa圖檔-RLE解碼

OpenGL之讀取Targa圖檔-RLE解碼

『Targa』與『Bitmap』同樣支持RLE編碼,單編碼方式有點不同.而且『索引、RGB、灰度』均支持RLE編碼.RLE編碼無非兩種方式『重複』與『不重複』的像素.渲染演示程式下載:

讀取ID 簡介
if(ID>=128)

Len = (BYTE)(ID – 127);

重複像素

LEN像素量

if(ID<128)

Len = BYTE(ID + 1);

不重複的像素

LEN=像素量

RLE解碼的C代碼

image:輸出

data:輸入RLE圖像數據

image_size:image圖像數據長度

pixel_size:像素大細

void Load_RLE_Targa(PBYTE image, PBYTE data, int image_size,int pixel_size)

{

BYTE id;//重複像素ID>=128,不重複的像素ID<128

BYTE length; //像素個數

BYTE pixel[4] = {0,0,0,0};// 像素

int image_index = 0;//圖像索引

int data_index = 0;//RLE索引

while (image_index < image_size)

{

id = data[data_index++];// 讀取ID

if (id >= 128)// 重複的像素

{   // 像素個數

length = (BYTE)(id – 127);//像素個數

memcpy(pixel, &data[data_index], pixel_size); // 讀重複像素

data_index = data_index + pixel_size;//移動RLE索引

while (length > 0)//  重複寫入數據

{

memcpy(image+image_index, pixel, pixel_size);//重複寫入數據

image_index = image_index + pixel_size;//移動圖像索引

–length;// 個數減一

}

}

else

{  // 不重複的像素

length = BYTE(id + 1); // 像素個數

// 拷貝像素

memcpy(image+image_index, data+data_index, length*pixel_size);

image_index = image_index + length * pixel_size;// 移動圖像索引

data_index = data_index + length * pixel_size; //移動RLE索引

}

}

}

OpenGL之讀取Targa圖檔

OpenGL之讀取Targa圖檔

『Targa』是常用於3D紋理的『.tga』圖檔,它與『Bitmap』最大的分別是索引、RGB、灰度均支持RLE編碼,當色彩較單調時壓縮效果明顯. 渲染演示程式下載:

它的文檔結構主要由三部份組成:

Targa圖檔結構 簡介
HEADER header; 頭部
PALETTEENTRY palette[256]; 調色板常用於256色模式,灰度模式和RGB模式均無調色板
PBYTE buffer; 位圖數據

 

頭部結構 簡介
BYTE imageIDLength; 圖像頭部的長度
BYTE colorMayType 調色板類型

0=無

1=使用調色盤

BYTE imageTypeCode 圖像類型

0=無圖像數據

1=索引模式

2= RGB模式

3=灰度模式

9=RLE壓縮索引模式

10=RLE壓縮RGB模式

11=RLE壓縮灰度模式

WORD colorMapOrigin 調色板偏移量
WORD colorMapLength 調色板的長度8bit圖檔這個值為256
BYTE colorMapEntrySize 調色板單個條目的大細

有本書居然寫錯左所占空間大細暈

WORD imageXOrigin 圖像左下角的X軸座標總為0
WORD imageYOrigin 圖像左下角的Y軸座標總為0
WORD imageWidth 圖寬
WORD imageHeight 圖高
BYTE bitCount 像素8BIT,16BIT,24BIT,32BIT
BYTE imageDescriptor 圖像原點的位置

 

調色板結構 簡介
BYTE red; 紅色
BYTE green; 綠色
BYTE blue; 藍色

 

載入並分釋TARGA圖檔

bool Load_Targa(TARGA_FILE_PTR targa, PBYTE data,int size)

{

int index;

PBYTE image;

int   image_size;// 圖像字節的長度

int   pixel_size;// 像素大小

int   pixel_count;// 像素個數(寬*高)

memcpy(&targa->header, data, sizeof(TARGA_HEADER));// 讀取頭部數據

image_size = targa->header.imageWidth * targa->header.imageHeight * targa->header.bitCount / 8;

pixel_size = targa->header.bitCount / 8;

pixel_count = targa->header.imageWidth * targa->header.imageHeight;

targa->buffer = (PBYTE)malloc(image_size);// 根據位圖影像的大小申請空間

if (targa->buffer == NULL)

return false;//出錯返回

if (targa->header.imageTypeCode == TARGA_TYPE_INDEXED ||

targa->header.imageTypeCode == TARGA_TYPE_INDEXED_RLE )// 壓縮索引

image = data + sizeof(TARGA_HEADER) + targa->header.imageIDLength + targa->header.colorMapOrigin + (targa->header.colorMapEntrySize / 8) * targa->header.colorMapLength;

else

image = data + sizeof(TARGA_HEADER) + targa->header.imageIDLength ;

if( targa->header.imageTypeCode == TARGA_TYPE_INDEXED ||// 索引

targa->header.imageTypeCode == TARGA_TYPE_RGB     ||// RGB

targa->header.imageTypeCode == TARGA_TYPE_GRAYSCALE)// 灰度

{   // 讀取位圖的圖像

memcpy(targa->buffer, image, image_size);

}

else

if (targa->header.imageTypeCode == TARGA_TYPE_INDEXED_RLE  ||// 壓縮索引

targa->header.imageTypeCode == TARGA_TYPE_RGB_RLE      ||// 壓縮RGB

targa->header.imageTypeCode == TARGA_TYPE_GRAYSCALE_RLE )// 壓縮灰度

{ // RLE解碼

Load_RLE_Targa(targa->buffer, image, image_size, pixel_size);

}

if (targa->header.bitCount == 8)

{   // 計算調色板的入口地址

PBYTE  palette = data + sizeof(TARGA_HEADER) + targa->header.imageIDLength + targa->header.colorMapOrigin;

// RGBQUAD結構與PALETTEENTRY結構的順序調轉了

for (index = 0; index < targa->header.colorMapLength; index++)

{//掉轉調色板的紅色和綠色

targa->palette[index].red   = palette[index *targa->header.colorMapEntrySize/8 + 2];

targa->palette[index].green = palette[index *targa->header.colorMapEntrySize/8 + 1];

targa->palette[index].blue  = palette[index *targa->header.colorMapEntrySize/8 + 0];

targa->palette[index].flags = PC_NOCOLLAPSE;

}

PBYTE temp_buffer = targa->buffer;

//根據圖像的寬高計算記憶體空間(24BIT)

targa->buffer = (UCHAR *)malloc(targa->header.imageWidth * targa->header.imageHeight * 3);

if (targa->buffer == NULL)

return false;//出錯返回

for (index = 0; index < image_size; index++)

{ // 現在將索引值轉為24位值

int color = temp_buffer[index];

if (targa->header.imageTypeCode == TARGA_TYPE_GRAYSCALE ||

targa->header.imageTypeCode == TARGA_TYPE_GRAYSCALE_RLE)

{// 處理灰度圖像

targa->buffer[index * 3 + 0] = color;

targa->buffer[index * 3 + 1] = color;

targa->buffer[index * 3 + 2] = color;

}

else

{

targa->buffer[index * 3 + 0] = targa->palette[color].red;

targa->buffer[index * 3 + 1] = targa->palette[color].green;

targa->buffer[index * 3 + 2] = targa->palette[color].blue;

}

}

targa->header.bitCount = 24;//最後將位圖位數變為24位

}

else

if (targa->header.bitCount == 16)//RGB555

{   // 根據位圖影像的大小申請空間

PBYTE temp_buffer = targa->buffer;

// 根據位圖影像的大小生請空間(位圖為16位但要生成24位空間來保存)

targa->buffer = (UCHAR *)malloc(targa->header.imageWidth * targa->header.imageHeight * 3);

if (targa->buffer == NULL)

{//分配內存空間失敗

free(temp_buffer); // 釋放資源

return false;//出錯返回

}

for (index = 0; index < pixel_count; ++index)

{

WORD color = (temp_buffer[index2 + 1] << 8) | temp_buffer[index2 + 0];

UCHAR red = (((color) >> 10) & 0x1f);

UCHAR green = (((color) >> 5) & 0x1f);

UCHAR blue = ((color) & 0x1f);

targa->buffer[index * 3 + 0] = (red << 3);

targa->buffer[index * 3 + 1] = (green << 3);

targa->buffer[index * 3 + 2] = (blue << 3);

}

targa->header.bitCount = 24;//最後將位圖位數變為24位

free(temp_buffer); // 釋放資源

}

else

if (targa->header.bitCount == 24)

{

for (index = 0; index < image_size; index = index + 3)

{

UCHAR blue = targa->buffer[index + 0];

UCHAR green = targa->buffer[index + 1];

UCHAR red = targa->buffer[index + 2];

targa->buffer[index + 0] = red ;

targa->buffer[index + 2] = blue;

}

}

else

if (targa->header.bitCount == 32)

{

for (index = 0; index < image_size; index = index + 4)

{

//DWORD color;//32的顏色

UCHAR blue = targa->buffer[index + 0];

UCHAR green = targa->buffer[index + 1];

UCHAR red = targa->buffer[index + 2];

targa->buffer[index + 0] = red;

targa->buffer[index + 2] = blue;

}

}

// 判斷圖像原點是否左下角,否則翻轉圖像

if ((targa->header.imageDescriptor & TARGA_ORIGIN_TOP_LEFT) == TARGA_ORIGIN_TOP_LEFT)

Flip_Targa(targa->buffer, targa->header.imageWidth * pixel_size, targa->header.imageHeight);

return true;//

}

 

將顛倒的圖像翻轉過來

image:指向位圖數據

bytes_per_line:圖像每行所占的字節數

height:圖像的高度

bool Flip_Targa(UCHAR *image, int bytes_per_line, int height)

{

UCHAR *buffer; //用於臨時保存位圖數據.

int index;     //循環計數

//根據位圖影像的大小生請空間

buffer = (UCHAR )malloc(bytes_per_lineheight);

if (buffer == NULL)

return false;

// 位圖拷貝

memcpy(buffer, image, bytes_per_line*height);

// 垂直顛倒圖片

for (index = 0; index < height; index++)

memcpy(&image[((height – 1) – index)bytes_per_line], &buffer[indexbytes_per_line], bytes_per_line);

//釋放臨時空間

free(buffer);

return true;//返回

}

OpenGL之讀取Bitmap圖檔-RLE解碼

OpenGL之讀取Bitmap圖檔-RLE解碼

『Bitmap』若是8Bit圖檔則支持RLE編碼(run length encoding),但網絡上大多解釋器都不支持RLE解碼,實現它非常間單,但若顏色較多很可能壓縮後的尺寸比不壓縮之前還要大. Bitmap文檔的『compression』等於1則使用RLE8編碼: 渲染演示程式下載:

數值 簡介
00 00 本行結尾,座標移至下一行頭部
00 01 位圖結束完成解碼
00 02 x y 第三第四字節分別當前位置的X與Y偏移量
00 len val val val 00 非壓縮不重複數據,此數值長度要以2對齊,不足以0補齊

若數值為:00 03 BB CC DD 00

則解壓後:BB CC DD

len val 壓縮的重複數據

若數值為:04 88

則解壓後:88 88 88 88

BMP-8Bit模式的RLE解碼

image:輸出

data:輸入RLE圖像數據

data_size:RLE圖像數據長度

void Load_RLE8_Bitmap(PBYTE image, PBYTE data, int data_size, int width)

{

BYTE value;

BYTE length;// 像素個數

int image_index = 0;

int data_index = 0;

int x = 0;

int y = 0;

while (data_index < data_size)// 遍歷壓縮後數據

{

if (data[data_index] == 0 && data[data_index + 1] == 0)

{// 本行結尾

data_index = data_index + 2;

x = 0;

++y;

}

else

if (data[data_index] == 0 && data[data_index + 1] == 1)

{// 位圖結尾

data_index = data_index + 2;

return ;

}

else

if (data[data_index] == 0 && data[data_index + 1] == 2)

{// 當前位置的偏移

x = x + data[data_index + 2];

y = y + data[data_index + 3];

data_index = data_index + 4;

}

else

if (data[data_index] == 0)

{// 非壓縮不重複數據

length = data[data_index+1];

image_index = y * width + x;

memcpy(image + image_index, data+data_index+2, length);

x = x + length;

data_index = data_index + length + 2 + length%2;

}

else

if(data[data_index] > 0)

{// 壓縮的重複數據

length = data[data_index];

value = data[data_index + 1];

image_index = y * width + x;

memset(image + image_index, value, length);

x = x + length;

data_index = data_index + 2;// 重複的像素

}

}

}

OpenGL之讀取Bitmap圖檔

OpenGL之讀取Bitmap圖檔

『Bitmap』圖檔之副檔名使用『.bmp』它非常簡單易讀,記得在2005年學DirextX時寫圖檔分析器就是它.缺點不支持壓縮.8Bit(256色)支持RLF壓縮但只有色彩單調時才有效果否則文檔更大. 不要以為256色已經淘汰,通過更換調色板的顏色可以快速更換顏色依然大有用處.渲染演示程式下載:

BMP文檔由四部分組成:

『Bitmap』文檔結構 簡介
FILE_HEADER    file; 圖檔的頭部
INFO_HEADER   info; 圖檔的信息
PALETTEENTRY  palette[256]; 調色板只用於256色.

16Bit、24Bit、32Bit均無調色板

PBYTE         buffer; 圖像數據

要在OpenGL中渲染像素『Pixel』要倒轉排成RGB/RGBA順序

 

FILE_HEADER文檔結構 簡介
WORD  type; ‘MB’ 0x4d42 BMP文檔標記

用於判斷是否BMP文檔

DWORD size; 文檔大小,判斷文檔是否完整
WORD  reserved1; 保留
WORD  reserved2; 保留
DWORD OffBits; 圖像數據的偏移量(文檔的頭部)

 

INFO_HEADER圖檔信息頭部 簡介
DWORD size; 圖檔信息頭部的大小
LONG  width; 圖檔寬度像素『Pixel』
LONG  height; 圖檔高度像素『Pixel』
WORD  planes; 平面量,總為1
WORD  bitCount; 位圖像素尺寸:

8Bit(256色)支持RLF壓縮

16BIT(分為RGB555與RGB565), 24BIT, 32BIT

DWORD compression; 壓縮類型:

0 = RGB

0 = RGB555 0x0RRRRRGGGGGBBBBB

3 = RGB565 0xRRRRRGGGGGGBBBBB

1 = RLE8 (run length encoding)壓縮

2 = RLE4

DWORD sizeImage; 圖檔數據所占空間,若使用RLE壓縮為壓縮後的大細
LONG  XPelsPerMeter; X軸每米像素
LONG  YPelsPerMeter; Y軸每米像素
DWORD ClrUsed; 圖像顏色量
DWORD ClrImportant; 圖像重要顏色量

 

PALETTEENTRY調色板 簡介
BYTE red; 紅色
BYTE green; 綠色
BYTE blue; 藍色
BYTE flags 只用於DirectDraw

PC_EXPLICIT:映射到硬件

PC_NOCOLLAPSE:不要映射

PC_RESERVED:保留

 

載入BMP位圖C代碼

bool Load_Bitmap(BITMAP_FILE_PTR bitmap,PBYTE data,int size)

{

int index;

int line_size;// 圖像每行所占的字節數

int pixel_size ;// 像素大小

PBYTE image;

int width ;// 圖寬

int height;// 圖高

// 讀取頭部數據

memcpy(&bitmap->header, data, sizeof(BITMAP_FILE_HEADER));

// 判斷是否是位圖文件

if (bitmap->header.type != BITMAP_ID)

return false;//出錯返回

// 讀取位圖信息的頭部

memcpy(&bitmap->info, data + sizeof(BITMAP_FILE_HEADER), sizeof(BITMAP_INFO_HEADER));

if (bitmap->info.sizeImage == 0)

{

bitmap->info.sizeImage = size – sizeof(BITMAP_FILE_HEADER) – sizeof(BITMAP_INFO_HEADER);

if (bitmap->info.bitCount == 8)

bitmap->info.sizeImage = bitmap->info.sizeImage – MAX_COLORS_PALETTE * sizeof(BITMAP_PALETTEENTRY);

}

//定位圖像數據

image = data + size – (int)bitmap->info.sizeImage;// 相對於文件尾

line_size = bitmap->info.sizeImage / bitmap->info.height;// 圖像每行所占的字節數

pixel_size = bitmap->info.bitCount / 8;// 像素大小

width = bitmap->info.width;// 圖寬

height = abs(bitmap->info.height);// 圖高

// 讀取位圖8或16,32位圖

if (bitmap->info.bitCount == 8)

{   // 讀取位圖的調色板

PBYTE palette = data + sizeof(BITMAP_FILE_HEADER) + sizeof(BITMAP_INFO_HEADER);

// RGBQUAD結構與PALETTEENTRY結構的順序調轉了

for (index = 0; index < MAX_COLORS_PALETTE; index++)

{//掉轉紅色和綠色

bitmap->palette[index].red   = palette[index * 4 + 2];

bitmap->palette[index].green = palette[index * 4 + 1];

bitmap->palette[index].blue  = palette[index * 4 + 0];

bitmap->palette[index].flags = PC_NOCOLLAPSE;

}

//根據位圖影像的大小生請空間

bitmap->buffer = (UCHAR *)malloc(abs(bitmap->info.width * bitmap->info.height * 3));

if (bitmap->buffer == NULL)

return false;//出錯返回

PBYTE buffer = NULL;

if (bitmap->info.compression == BITMAP_COMPRESSION_RLE8)

{

buffer = (PBYTE)malloc(abs(bitmap->info.width * bitmap->info.height));

Load_RLE8_Bitmap(buffer, image, bitmap->info.sizeImage, bitmap->info.width);// RLE解碼

image = buffer;

}

// 現在將索引值值轉為24BIT

for (int y = 0; y < height; ++y)

{

for (int x = 0; x < width; ++x)

{

index = y * width + x;

int color = image[index];

bitmap->buffer[index * 3 + 0] = bitmap->palette[color].red;

bitmap->buffer[index * 3 + 1] = bitmap->palette[color].green;

bitmap->buffer[index * 3 + 2] = bitmap->palette[color].blue;

}

}

if (bitmap->info.compression == BITMAP_COMPRESSION_RLE8)

free(buffer);

//最後將位圖位數變為24位

bitmap->info.bitCount = 24;

}

else

if (bitmap->info.bitCount == 16)// 讀取16位圖

{  // 以24BIT分配記憶體空間

bitmap->buffer = (UCHAR *)malloc(abs(bitmap->info.width * bitmap->info.height * 3));

if (bitmap->buffer == NULL)

return false;//出錯返回

if (bitmap->info.compression == 3)

{// RGB565

for (int y = 0; y < height; ++y)

{

for (int x = 0; x < width; ++x)

{// 現在將各個16位RGB值轉為32位值

index = y * line_size + x * 2;

WORD color = (image[index + 1] << 8) | image[index + 0];

UCHAR red = ((color >> 11) & 0x1f);

UCHAR green = ((color >> 5) & 0x3f);

UCHAR blue = (color & 0x1f);

index = y * width + x;

bitmap->buffer[index * 3 + 0] = (red << 3);

bitmap->buffer[index * 3 + 1] = (green << 2);

bitmap->buffer[index * 3 + 2] = (blue << 3);

}

}

}

else

{// RGB555

for (int y = 0; y < height; ++y)

{

for (int x = 0; x < width; ++x)

{

index = y * line_size + x * 2;

WORD color = (image[index + 1] << 8) | image[index + 0];

UCHAR red = (((color) >> 10) & 0x1f);

UCHAR green = (((color) >> 5) & 0x1f);

UCHAR blue = ((color) & 0x1f);

index = y * width + x;

bitmap->buffer[index * 3 + 0] = (red << 3);

bitmap->buffer[index * 3 + 1] = (green << 3);

bitmap->buffer[index * 3 + 2] = (blue << 3);

}

}

}

//最後將位16BIT變為24BIT

bitmap->info.bitCount = 24;

}

else

if (bitmap->info.bitCount == 24)// 讀取24BIT圖檔

{   // 根據位圖影像的大小申請空間

bitmap->buffer = (UCHAR *)malloc(bitmap->info.sizeImage);

if (bitmap->buffer == NULL)// 申請內存空間失敗

return false;//出錯返回

// 讀取圖像

for (int y = 0; y < height; ++y)

{

for (int x = 0; x < width; ++x)

{// 轉換GL_RGB模式

index = y * line_size + x * 3;

bitmap->buffer[index + 0] = image[index + 2];//逐個像素地拷貝

bitmap->buffer[index + 1] = image[index + 1];

bitmap->buffer[index + 2] = image[index + 0];

}

}

}

else

if (bitmap->info.bitCount == 32)// 處理32BIT圖檔

{       // 根據位圖影像的大小申請空間

bitmap->buffer = (UCHAR *)malloc(bitmap->info.sizeImage);

if (bitmap->buffer == NULL)//若不能申請空間

return false;//出錯退出

// 像素轉為BGRA 32Bit肯定是4字節對齊

for (index = 0; index < (int)bitmap->info.sizeImage-4; index=index+4)

{

bitmap->buffer[index + 0] = image[index + 2];//逐個像素地拷貝

bitmap->buffer[index + 1] = image[index + 1];

bitmap->buffer[index + 2] = image[index + 0];

bitmap->buffer[index + 3] = image[index + 3];

}

}

else

{

return false;//嚴重文提

}

if (bitmap->info.height < 0)// height為負時表示圖片顛倒

Flip_Bitmap(bitmap->buffer, bitmap->info.width*(bitmap->info.bitCount / 8), bitmap->info.height);

bitmap->info.height = abs(bitmap->info.height);

return true;

}

 

將顛倒的BMP文件翻轉過來

bool Flip_Bitmap(UCHAR *image, int bytes_per_line, int height)

{

UCHAR *buffer; //用於臨時保存位圖數據.

int index;     //循環計數

//分配單行空間

buffer = (UCHAR )malloc(bytes_per_lineheight);

if (buffer == NULL)

return false;

// 單行拷貝

memcpy(buffer, image, bytes_per_line*height);

// 垂直顛倒圖片

for (index = 0; index < height; index++)

memcpy(&image[((height – 1) – index)bytes_per_line], &buffer[indexbytes_per_line], bytes_per_line);

//釋放空間

free(buffer);

return true;//返回

}

OpenGL之繪畫圖像文檔

OpenGL之繪畫圖像文檔

在屏幕上渲染圖檔與模型貼上紋理總會令人興奮,幸好在OpengGL繪畫圖像文檔也並不困難,並且實現左示例程式.下載:

  1. 載入並解釋圖像文檔如『.bmp』『.tga』『.pcx
  2. 設置圖像左下角繪畫在屏幕位置glRasterPos2f(x, y);
  3. 設置圖像的縮放比glPixelZoom(xfactor,yfactor);縮放因子=屏幕尺寸/圖像尺寸
  4. 繪畫圖像glDrawPixels(width,height,format,type,pixels);與『DirectDraw』相比稍複雜點, 『DirectDraw』支持32Bit、16Bit、256色.而OpenGL卻直接支持32Bit、24Bit、256色、但要將像素『Pixel』倒轉排成RGB/RGBA順序
Format 簡介
GL_RGB 24Bit
GL_RGBA 32Bit
GL_COLOR_INDEX 調色板索引

 

type 簡介
GL_UNSIGNED_BYTE 像素『Pixel』分量的尺寸
GL_BITMAP 點陣位圖1位『Bit』1像素『Pixel』

 

常見圖檔格式讀取與分析 RLE解碼
Bitmap文檔 8Bit的bitmap支持RLE編碼
Targa圖檔 索引模式、RGB模式、灰度模式均支持RLE編碼
PCX圖檔 8Bit的PCX支持RLE編碼
PNG圖檔

 

渲染位圖C代碼演示:

  1. glPixelStorei(GL_UNPACK_ALIGNMENT, 4);// 4字節對齊
  2. glRasterPos2f(x, y);// 設定圖像左下角在屏幕的位置
  3. glPixelZoom((float)screen_width/(float)image_width, (float)screen_height / (float)image_height); // 設置全屏縮放
  4. glDrawPixels(image_width,image_height,format,type,image); // 繪畫

Windows之ALT快捷鍵

Windows之ALT快捷鍵

之前一值無法使用ALT鍵,因查找唔到其VK_ALT對應的『VIRTUAL CODE』,近日把WinXP切底拋棄改用Win10,發現很多程式均使用ALT鍵展示或隱藏MENU. 細想VK_ALT即等於VK_MENU,下面是使用ALT鍵展示與隱藏MENU源代碼

1.在resource.h加入

#define IDALT                           1000

2.在resource.rc加入加速表,一定要加入ALT否則MENU會獲得焦點要按量下ALT鍵,另不要用VK_LMENU和VK_RMENU

ACCEL ACCELERATORS

BEGIN

VK_MENU,        IDALT,                  VIRTKEY, ALT, NOINVERT

END

3.在main()修改消息循環

MSG msg;//消息 msg是WINDOWS放置下一個消息的存儲器,

HACCEL hAccel; // 加速鍵表的句柄

hAccel =::LoadAccelerators(main_instance,”ACCEL”); // 加速鍵表的句柄

while(GetMessage(&msg,NULL,0,0)) //從事件對列中獲得消息

{  //由 PostQuitMessage(0) 發送的WM_QUIT消息,被PeekMessage()檢測到

if(msg.message == WM_QUIT)

break;// 跳出主循環

if(!::TranslateAccelerator(main_window,hAccel,&msg))// 處理加速鍵表

{

TranslateMessage(&msg);//處理和轉換加速鍵.

DispatchMessage(&msg);//調用WinProc對消息進行處理,從MSG結構取的參數並傳遞.

}

}

4.在WinProc加入

if(msg == WM_COMMAND)

{ // 處理菜單命令

if (LOWORD(wParam) == IDALT)

{     main_menu = ::LoadMenuA(NULL,”MAIN_MENU”);// 主菜單

if (::GetMenu(main_window) == NULL)

::SetMenu(main_window, main_menu);// 設定菜單

else

::SetMenu(main_window, NULL);// 設定菜單

}

}