基本显示原理如下:
一.顯示器的工作原理
目前在個人計算機上廣泛使用的是採用陰極射線管(CRT)的光柵掃瞄顯示器,我們在屏幕上所看到的顏色是由電子槍發出的電子束打在CRT屏幕背面的螢光層上的點形成的,通過控制點的亮度可以產生不同的顏色。電子束不斷地從左到右、從上到下掃瞄整個屏幕,使屏幕顯示出圖案,電子束以大約每秒70次的速率在屏幕上重畫這一圖案,這個過程稱為顯示刷新或屏幕刷新,具體的掃瞄頻率依賴於所用的顯示適配器(又稱為顯示卡)。電子束從屏幕的左上角開始向右掃瞄,到達屏幕的右邊緣後,電子束被關閉(水平斷開),接著它又迅速地返回到屏幕的左邊緣(水平回掃)開始進行下一行水平方向的掃瞄,在完成全部的水平方向的掃瞄後,電子束在屏幕的右下角結束,此時電子束被關閉(垂直斷開),接著又迅速地返回到屏幕的左上角(垂直回掃),開始下一屏掃瞄。電子束就是這樣週而復始地掃瞄整個屏幕。顯示器在兩種方式下工作:文本方式和圖形方式,電腦遊戲一般在圖形方式下進行。
二.顯示器的坐標系統
計算機屏幕上的坐標與我們通常使用的直角坐標系不同,坐標原點(0,0)在屏幕的左上角,向右是水平方向的坐標,向下是垂直方向的坐標,且坐標沒有負值。
三.顯示卡的結構
顯示器上的顯示卡負責將圖形顯示在屏幕上。顯示存儲器中存放著在屏幕上顯示的圖像數據,顯示卡硬件不停地將顯存中的內容顯示在屏幕上。顯示存儲器實際上是安裝在顯示卡上的一塊或幾塊大規模集成電路,其容量有1M、2M、4M、8M等,在DOS下我們可以訪問的內存只有1MB空間(這就是DOS的局限性所在),地址從00000H到FFFFFH,這段內存根據用途又分為不同的塊,系統分配給圖形緩衝區(顯示存儲器)的地址在A0000H到BFFFFH之間,大小為128KB,其中,VGA佔用了A0000H到AFFFFH段,共64KB,這段地址是內存映射地址,供我們訪問顯示存儲器用。在VGA 13H圖形模式下,顯示內存使用A0000H到AF9FFH的一段線性內存空間,每個字節表示一個點,對應屏幕上的一個像點,320*200的屏幕分辨率共需要64000個字節,剛好64KB,因為一個字節可以表示的最大整數值為256,所以每個像點就可以表示256種顏色。
四.設置視頻模式
畫圖以前必須使屏幕工作在圖形方式,這就要設置屏幕的視頻模式。設置視頻模式有許多種方法,其中調用視頻BIOS功能是最簡單的一種,通過調用BIOS中斷0x10的服務程序,可以很方便地設置屏幕模式。調用方法是將值0放入ah寄存器,顯示模式放入al寄存器中,然後調用int86()函數。
設置視頻模式的函數:
void SetVideoMode(int mode)
{
union REGS r;
r.h.ah=0;
r.h.al=mode;
int86(0x10,&r,&r);
}
其中mode是視頻模式。
五.在屏幕上畫點
由於顯示存儲器是線性排列的,每個像點用一個字節來表示,所以對像點尋址非常容易,像點在顯示內存中的偏移地址可由這個公式確定:y*320+x,其中,y是像點在垂直方向的坐標,x是水平方向的坐標,320是屏幕的寬度。有了像點的偏移地址,然後加上顯示內存的首地址即可得到像點在顯示內存中的絕對地址。只要將表示點的顏色值放到這個地址處,就可以在屏幕上畫點了。
首先建立一個指針VideoBufferPtr,使它指向顯示內存的首地址:
char far *VideoBufferPtr=( char far *)0xa0000000;
將這個指針加上像點的偏移地址,像點的最終地址就確定了,它等於:
VideoBufferPtr+y*320+x;
把顏色值color寫到這個地址:
*(VideoBufferPtr+y*320+x)=color;
畫點的函數:
void DrawPoint(int x,int y,unsigned char color)
{
*(VideoBufferPtr+y*320+x)=color;
}
六.顯示字符
遊戲經常在屏幕上打印出顯示遊戲狀態的文本,使遊戲者瞭解當前遊戲的狀態,這是遊戲與遊戲者進行交互的必不可少的。然而在圖形模式下顯示文本與在文本模式下有很大區別,在圖形模式下文本必須使用基於畫位圖的方法來顯示。這裡介紹一種簡便的方法:利用計算機只讀存儲器中的ASCII字體數據。在計算機只讀存儲器(ROM)中固化有ASCII字體數據,這可在基址F000:FA6E上找到。我們只需瞭解數據是如何存儲的,然後再得出存取該數據的算法,就可以用任意顏色在屏幕上用畫位圖的方法把字符畫出來。在ROM中,字符按照ASCII字符編碼的順序放置,由於每個字符為8*8的點陣,所以每個字符佔據8個字節的存儲空間,要找出某個字符,我們只需將這個字符的ASCII碼與8相乘,然後將結果加到基址F000:FA6E上。由於字符用畫位圖的方法來顯示,因此我們可以隨意給它們指定顏色(前景或背景顏色)。在圖形模式下,我們只需要兩個函數就能夠打印文本:一個用於顯示單個字符,另一個用來顯示字符串。
定義一個遠指針指向ROM字符集的開始位置:
char far *RomCharPtr=(char far *)0xf000fa6e;
下面是在屏幕上打印字符和字符串的函數:
1.打印字符的函數。
void PrintChar(int cx,int cy,char c,unsigned char Fcolor,unsigned char Bcolor,int flag)
{
int offset,x,y;
char far *TempPtr;
unsigned char bit_mask;
TempPtr=RomCharPtr+(c<<3);
offset=(cy<<8)+(cy<<6)+cx;
for(y=0;y<8;y++)
{
bit_mask=0x80;
for(x=0;x<8;x++)
{
if((*TempPtr&bit_mask))
*(VideoBufferPtr+offset+x)=Fcolor;
else if(flag==1)
*(VideoBufferPtr+offset+x)=Bcolor;
bit_mask=(bit_mask>>1);
}
offset+=320;
TempPtr++;
}
}
說明:
cx,cy 是字符在屏幕上的坐標。
c 字符的ASCII碼。
Fcolor,Bcolor 分別是字符的前景和背景顏色。
flag 打印標誌,當flag=1時顯示字符的背景色,否則打印的字符具有透明效果。
2.打印字符串的函數。
void PrintString(int x,int y,char *string,unsigned char Fcolor,unsigned char Bcolor,int flag)
{
int index;
for(index=0;string[index]!=0;index++)
PrintChar(x+(index<<3),y,string[index],Fcolor,Bcolor,flag);
}
說明:
1.x,y 是字符串在屏幕上的坐標。
2.*string 字符串指針。
3.Fcolor,Bcolor 分別是字符串的前景和背景顏色。
4.flag 打印標誌,當flag=1時顯示字符串的背景色,否則打印的字符串具有透明效果。
七.設置顏色寄存器
我們知道VGA顯示卡具有顯示256種顏色的能力,每種顏色能夠用一個0-255之間的數值來表示,那麼這些數值與我們在屏幕上實際見到的顏色之間有什麼關係呢?其實這些數值只是VGA顯示卡上的顏色寄存器的索引值,顏色寄存器裡才保存了屏幕上顏色的真實值。VGA顯示卡上有一個包含256個單元的顏色寄存器(又稱為調色板),每個單元由三部份組成,這三部份分別代表顏色中的紅、綠、藍三種成份(顯示器就是用這三種成份來組成任何我們所看到的顏色),用三個字節表示,顏色寄存器一共有768個字節(3*256=768)。當我們要在屏幕上顯示某種顏色時,顯示卡硬件就根據顏色的索引值在顏色寄存器中查找,找到後再從相應單元中取出顏色值顯示在屏幕上,這個過程與畫家使用調色板相似,顏色寄存器相當於調色板,顏色寄存器中的單元相當於調色板上的色格,在色格中裝有預先調好的顏色,當畫家需要用某種顏色作畫時,就從裝有那種顏色的色格中把顏色取出來。例如,我們要顯示顏色索引值為30的顏色,顯示卡硬件就去查找顏色寄存器的第30單元,30單元位於距顏色寄存器首址3*30=90處(因為每個單元有三個字節),然後取出90處記錄有紅、綠、藍三種成份的三個字節作為在屏幕上顯示的色彩信號。但是實際上每個字節只用了六位來表示顏色,其它兩位沒用,這六位表示的數的值域為0-63,所以每種顏色(紅、綠、藍)成份具有64種亮度的表現能力,三種顏色成份組合共可以產生64*64*64=262,144種顏色(VGA 13H模式從這262,144種顏色中取出256種在同一屏幕上顯示)。我們可以通過事先設置顏色寄存器的值來使用我們自己的顏色。
設置顏色寄存器有多種方法,如調用BIOS功能,但是這種方法速度比較慢,遊戲設計中通常採用直接訪問VGA顯示卡的I/O端口的方法來快速設置顏色寄存器,我們只需訪問四個I/O端口就可以完成設置顏色寄存器的工作。這四個端口分別是:
0x3c6、0x3c7、0x3c8和0x3c9。
端口0x3c6稱為調色板屏蔽寄存器,用來屏蔽所要求的調色板寄存器的位,如果你在這個寄存器中放入0xff,你就可以通過調色板索引寄存器0x3c7和0x3c8(一個用於讀,一個用於寫)訪問任何你希望訪問的顏色寄存器,端口0x3c9稱為調色板數據寄存器,紅、綠、藍三種成份就是通過它進行讀寫(顏色值要讀或寫三次)。
我們定義一個結構來方便處理顏色寄存器:
typedef struct RGB_COLOR
{
unsigned char red;
unsigned char green;
unsigned char blue;
}RGBColor,*RGBColorPtr;
結構中的red、green和blue變量用來保存顏色的紅、綠、藍三種成份。
設置顏色寄存器值的函數:
void SetPaletteRegister(int index,RGBColorPtr color)
{
outportb(0x3c6,0xff);
outportb(0x3c8,index);
outportb(0x3c9,color->red);
outportb(0x3c9,color->green);
outportb(0x3c9,color->blue);
}
獲取顏色寄存器值的函數:
void GetPaletteRegister(int index,RGBColorPtr color)
{
outportb(0x3c6,0xff);
outportb(0x3c7,index);
color->red=inportb(0x3c9);
color->green=inportb(0x3c9);
color->blue=inportb(0x3c9);
}
八.在屏幕上畫位圖
計算機繪製圖像通常採用一種稱為位映射圖(BITMAP)的圖形處理方法進行,位映射圖是一個矩形的點陣結構(二維矩陣),顯示在屏幕上時,對應屏幕上一個矩形區域,組成位圖的數據儲存在內存中一段連續的區間。我們比較常見的位圖文件有:BMP、PCX、GIF、JPG等。位圖通常存儲在外部文件中,使用以前必須將其從磁盤文件調入內存。下面介紹將一個256色PCX圖形文件讀入內存的方法:
1.定義PCX文件頭結構:
typedef struct PCX_HEADER
{
char menufactrue; /* 廠家標識編號 0x0a */
char version; /* 文件版本編號 */
char packing_type; /* 壓縮模式 */
char bits_per_pixel; /* 每點佔用的位數 */
int minx; /* 最小X坐標值 */
int miny; /* 最小Y坐標值 */
int maxx; /* 最大X坐標值 */
int maxy; /* 最大Y坐標值 */
int hres; /* 水平分辨率 */
int vres; /* 垂直分辨率 */
char palette[48]; /* 顏色調色板 */
char unused; /* 未使用 */
char bit_plance; /* 位平面個數 */
int bytes; /* 單一水平線佔用的字節數 */
int palette_type; /* 調色板類型 */
char unused2[58]; /* 未使用 */
}PCXHeader,*PCXHeaderPtr;
2.定義用來存放PCX圖像數據的結構:
typedef struct PCX_PICTURE
{
int width;
int height;
char far *buffer;
RGBColor palette[256];
}PCXPicture,*PCXPicturePtr;
3.初始化圖像數據的函數:
int InitPCX(PCXPicturePtr image,int w,int h)
{
unsigned size=w*h;
image->width=w;
image->height=h;
image->buffer=(char far *)farmalloc(size);
if(image->buffer==NULL) return 0;
return 1;
}
4.從外部文件讀入數據的函數:
int LoadPCX(char *filename,PCXPicturePtr image,int flag)
{
FILE *fp;
unsigned num_bytes,count,size;
int index;
unsigned char data;
PCXHeader PcxHeader;
size=image->width*image->height;
if((fp=fopen(filename,"rb"))==NULL)
return 0;
fread(&PcxHeader,sizeof(PCXHeader),1,fp);
count=0;
while(count<=size)
{
data=fgetc(fp);
if(data>=192&&data<=255)
{
num_bytes=data-192;
data=fgetc(fp);
while(num_bytes-->0)
{
*(image->buffer+count)=data;
++count;
}
}
else
{
*(image->buffer+count)=data;
++count;
}
}
fseek(fp,-768L,SEEK_END);
for(index=0;index<256;index++)
{
image->palette[index].red=((fgetc(fp))>>2);
image->palette[index].green=((fgetc(fp))>>2);
image->palette[index].blue=((fgetc(fp))>>2);
}
fclose(fp);
if(flag==1)
for(index=0;index<256;index++)
SetPaletteRegister(index,(RGBColorPtr)&image->palette[index]);
return 1;
}
其中參數flag用來指明調入文件的同時是否設置顏色寄存器(flag=1設置)。
5.畫位圖的函數:
void DrawImage(int x,int y,int width,int height,char far *image)
{
int i,j;
for(i=0;i
if(*image!=0&&(x+j)>=0&&(x+j)<320&&(y+i)>=0&&(y+i)<200)
DrawPoint(x+j,y+i,*image);
image++;
}
}
x,y是圖像在屏幕上的左上角坐標,width,height是圖像的寬度和高度,image是指向內存中圖像的指針。
我們對if(*image!=0&&(x+j)>=0&&(x+j)<320&&(y+i)>=0&&(y+i)<200)語句進行一下分析:
*image!=0用來檢查所畫的顏色值是否是透明色,如果是,則不畫出來,這樣我們就可以畫出有透明效果的圖像,即透過圖像可以看到背景,透明色通常取值0,也可以用其他的顏色值表示。
(x+j)>=0&&(x+j)<320&&(y+i)>=0&&(y+i)<200語句用來判斷所畫點的坐標是否超出屏幕顯示的範圍,這樣可以畫出具有裁剪效果的圖像,如圖像的一部份在屏幕外。
這個函數並不是最快的,因為它要執行width X height次判斷,更快的函數請看VGA13H函數庫中的繪圖函數。
VGA顯示系統
顯示硬件基礎
進行TC256色圖形編程時,是直接與顯卡硬件在打交道。那麼,就讓我們從顯卡談起吧。
顯示卡(adapter)是一塊插在PC主機的電路版。一般顯示卡由寄存器、存儲器(顯示RAM和ROM BIOS)、控制電路三大部分組成。隨著PC機的發展,PC機的顯示系統經歷了由CGA、EGA、VGA、SVGA到現在的VESA系列的發展過程。
在早期的顯示系統中,CGA、EGA等顯示卡配置的顯示器是嚴格配套的,一種顯卡只與一種顯示器匹配使用。隨著技術的發展,出現了可變頻的顯示器,它使用自動跟蹤技術,同一顯示器可以適應CGA、EGA、VGA等各種顯示卡。這使得我們用TC操作顯示卡模擬VESA工作模式成為可能。
顏色和灰度是衡量顯示系統的又一重要參數。最初的彩色顯示系統CGA只能顯示固定的16種顏色,到EGA時可以使用64種顏色種的16種顏色,而發展到VGA時可以使用256K種顏色中的16種顏色,現在的顯示卡甚至可以支持到32位真彩色並使用所有顏色。顏色與灰度的發展受顯示內存的限制。在16色模式中,一個像素點只需要log2 16=4位內存即半個字節;而在256色模式中,一個像素點則要占log2 256=8位即一個字節;在16位真彩色模式下,一個像素要佔用整整兩個字節。所以,在CGA高分辨率方式下,只能同時使用兩種顏色,因為640*200*1/8 = 16000(byte)接近16k。而到了VGA,實現640*480分辨率16種顏色時,由於一個像素有16種顏色,最少需要占4位,因此,需要內存640*480*1/8 = 153600(byte)≒ 150k。
此外,RAM的擴大還受到PC機中留作顯示用的地址空間的限制,一般不超過64K。因為,當使用大於64K的地址空間時,通過PC機訪問顯示緩衝區的控制就變得較為複雜了(使用換頁機制)。
現在的顯卡可以兼容多種顯示模式,因此,顯卡在固化的ROM BIOS中為每一種模式分配了一個代號。用戶在使用PC機編程時,直接輸入該代號,再通過顯示卡的BIOS調用即可使用此種模式。
通常,顯示模式號<0x14的是標準模式,其它的為非標準模式,非標準模式因顯示卡的不同而不同。
1.下表列舉了所有的標準字符模式:<點擊此處>
2.下表列舉了所有的標準圖形模式:<點擊此處>
3.非標準字符模式:
這類字符模式一般與某種顯卡有關,其模式號由某類生產廠家自行確定。這類顯示模式的行列變化範圍都比較大,但均超出25行*80列的範圍。
4.非標準圖形模式:
非標準的圖形模式也是變化多樣的,但每個卡的特點主要體現在這些圖形模式中。下表介紹了常見的非標準圖形模式:<點擊此處>
顯示緩衝區與顏色定義
PC機的顯示卡上的RAM存儲器中的數據按照顯示器顯示格式進行存儲。我們知道 ,計算機只能以二進制方式存儲數據,每位有兩種狀態(0與1)。對於單色顯示器,內存中只需存放一張表,表中每一位對應屏幕上一個像素點,該位為1則表示該點是亮點。而對於彩色顯卡來,要表示屏幕上像素點的信息,僅用一為就不夠了。對於顯示16色的顯示模式,就需4位定義一種顏色。到了EGA顯示卡時,已經開始有了顯示卡兼容性。在VGA模式時由於顏色較多,分辨率提高帶來點數的增加,所需要的顯存增大,從而出現了一種以「彩色位平面」的存儲結構來表示顏色信息。CGA400、CEGA、CH、VGA均採用彩色位平面的存儲結構。一般VGA的基本配置有256K的顯示緩衝區(BANK A)。當支持16種以下的顏色時,使用存儲位平面結構,此時256K的顯示緩衝區被分位4個64K的存儲位平面,屏幕上的一個點,由存儲位平面的各一位組合後表示,最多表示16種顏色。當支持256種顏色時,要使用線性內存結構,4個存儲位平麵線性鏈接,形成256K的線性內存(Linear system)。
在使用16色和256色模式顯示時,需要一張顏色表,以將顯存的數據「翻譯」為屏幕上的點信息。所謂顏色表,就是我們通常所指的「調色板」。當顯示器要顯示屏幕上的一個點時,先由顯示卡將顯存中的數據讀出,然後對照顏色表,得到一組RGB顏色信息,然後調整顯示器的射線管,在屏幕相應位置顯示一個點。當顯存中所有的點都顯示後,就得到我們所看到的圖像。用戶還可以根據自己顯示的需要,更改顏色表的RGB對應值,得到我們自定義的顏色。
在顯示模式到了真彩色級別時,由於顯示內存中存儲的已經是像素點的RGB信息,因此調色板就變得沒有意義。因此,在真彩色模式中,不再需要調色板。
視頻BIOS ROM
顯示卡都有自己的專用視頻BIOS支持。此BIOS是視頻控制程序,固化在ROM中,成為顯卡的一部分,顯示卡上的視頻BIOS功能要比PC的視頻BIOS強大的多,它除了支持CGA、EGA、VGA等各標準顯示模式外,還支持各種專用模式和針對顯存的專用操作,可以獨立於CPU單獨處理顯存圖像,比如2D變換、3D環境計算等等非常複雜的運算。一般VGA視頻BIOS的入口地址在C000H——CFFFFH之間。
BMP是bitmap的縮寫形式,bitmap顧名思義,就是位圖也即Windows位圖。它一般由4部分組成:文件頭信息塊、圖像描述信息塊、顏色表(在真彩色模式無顏色表)和圖像數據區組成。在系統中以BMP為擴展名保存。
打開Windows的畫圖程序,在保存圖像時,可以看到三個選項:2色位圖(黑白)、16色位圖、256色位圖和24位位圖。這是最普通的生成位圖的工具,在這裡講解的BMP位圖形式,主要就是指用畫圖生成的位圖(當然,也可以用其它工具軟件生成)。
現在講解BMP的4個組成部分:
1.文件頭信息塊
0000-0001:文件標識,為字母ASCII碼「BM」。
0002-0005:文件大小。
0006-0009:保留,每字節以「00」填寫。
000A-000D:記錄圖像數據區的起始位置。各字節的信息依次含義為:文件頭信息塊大小,圖像描述信息塊的大小,圖像顏色表的大小,保留(為01)。
2.圖像描述信息塊
000E-0011:圖像描述信息塊的大小,常為28H。
0012-0015:圖像寬度。
0016-0019:圖像高度。
001A-001B:圖像的plane總數(恆為1)。
001C-001D:記錄像素的位數,很重要的數值,圖像的顏色數由該值決定。
001E-0021:數據壓縮方式(數值位0:不壓縮;1:8位壓縮;2:4位壓縮)。
0022-0025:圖像區數據的大小。
0026-0029:水平每米有多少像素,在設備無關位圖(.DIB)中,每字節以00H填寫。
002A-002D:垂直每米有多少像素,在設備無關位圖(.DIB)中,每字節以00H填寫。
002E-0031:此圖像所用的顏色數,如值為0,表示所有顏色一樣重要。
3.顏色表
顏色表的大小根據所使用的顏色模式而定:2色圖像為8字節;16色圖像位64字節;256色圖像為1024字節。其中,每4字節表示一種顏色,並以B(藍色)、G(綠色)、R(紅色)、alpha(32位位圖的透明度值,一般不需要)。即首先4字節表示顏色號0的顏色,接下來表示顏色號1的顏色,依此類推。
4.圖像數據區
顏色表接下來位為位圖文件的圖像數據區,在此部分記錄著每點像素對應的顏色號,其記錄方式也隨顏色模式而定,既2色圖像每點占1位(8位為1字節);16色圖像每點占4位(半字節);256色圖像每點占8位(1字節);真彩色圖像每點占24位(3字節)。所以,整個數據區的大小也會隨之變化。究其規律而言,可的出如下計算公式:圖像數據信息大小=(圖像寬度*圖像高度*記錄像素的位數)/8。
然而,未壓縮的圖像信息區的大小。除了真彩色模式外,其餘的均大於或等於數據信息的大小。這是為什麼呢?原因有兩個:
1.BMP文件記錄一行圖像是以字節為單位的。因此,就不存在一個字節中的數據位信息表示的點在不同的兩行中。也就是說,設顯示模式位16色,在每個字節分配兩個點信息時,如果圖像的寬度位奇數,那麼最後一個像素點的信息將獨佔一個字節,這個字節的後4位將沒有意義。接下來的一個字節將開始記錄下一行的信息。
2.為了顯示的方便,除了真彩色外,其他的每中顏色模式的行字節數要用數據「00」補齊為4的整數倍。如果顯示模式為16色,當圖像寬為19時,存儲時每行則要補充4-(19/2+1)%4=2個字節(加1是因為裡面有一個像素點要獨佔了一字節)。如果顯示模式為256色,當圖像寬為19時,每行也要補充4-19%4=1個字節。
還有一點我要申明,當屏幕初始化為16或256色模式時,一定要設置調色板或修正顏色值,否則無法得到正確的圖像顏色。
置256色圖形模式
要使用256色圖形模式,使用TC的界面模式是不行的,因為它為VGA16色的文本模式,要使用256圖形模式,則要調用顯卡BIOS進行圖形界面初始化。具體是調用10H的顯示中斷,將功能號AH置為0,子功能號AL置為要使用的模式號,以調用IBM兼容顯卡的320×200的256色模式為列,程序如下。
#include "dos.h"
void init256(int Vmode)
{ union REGS r;
r.h.ah=0;
r.h.al=Vmode;
int86(0x10,&r,&r);
}
main()
{ init256(0x13);
getch();
init256(0x3);
}
運行後,屏幕進行了一次閃爍,實際上,這是屏幕顯示模式切換引起的。切換模式後,屏幕一片黑暗,因為切換模式後,未對屏幕進行任何操作。接下來,我將演示在256色模式下對顯卡的操作。
訪問顯存
初始化得到了256色圖形模式的屏幕,接下來要做的就是向屏幕顯示圖像了。在顯示圖像之前,我要介紹一個很重要的far指針。也許你在16色VGAHI模式裡用過,那就是內存中的圖形模式顯存映像指針0xa0000000,說它是映像指針,是因為顯示器只與顯示卡直接打交道,所以要更改屏幕內容還需更改顯存內容。因此,內存中提供一塊64k的區域,通過顯示中斷可以使它映射為顯存的不同區域,更改該內存中的值即更改了相應映射區域的顯存。讓我們先試一下下面的程序:(注意:運行時,最好將內存模式設置為large,否則可能出錯,以後我不再提示)
#include "dos.h"
main()
{ long i;
char far *p=MK_FP(0xa000,0000);
union REGS r;
r.x.ax=0x13;
int86(0x10,&r,&r);
for(i=0;i<320*200L;i++)*(p+i)=i/320;
getch();
r.x.ax=0x3;
int86(0x10,&r,&r);
}
看見了什麼,你會驚詫地發現:多麼豐富的色彩!DOS模式下的256色顯示實現了!
顯示卡換頁
前面將過由於內存只提供64k的內存進行顯存操作,在320×200的256模式下尚可訪問所有的屏幕像素點。但是在320×200以上的256模式下就不行了,因為在此模式下,比如在640×480的256模式下,存儲一屏所有的像素點需要640×480byte=300k的存儲單元。比如使用以下程序:
#include "dos.h"
main()
{ long i;
char far *p=0xa0000000;
union REGS r;
r.x.ax=0x5f;/*TNT2、GeForce2的640×480的256色模式均使用該模式號*/
int86(0x10,&r,&r);
for(i=0;i<640*400L;i++)*(p+i)=(i/640)%256;
getch();
r.x.ax=0x3;/*恢復為16色文本模式*/
int86(0x10,&r,&r);
}
VESA虛擬屏幕編程
--------------------------------------------------------------------------------
本文已刊載於《計算機世界》1999年6月21日E23版,但這裡所公佈的比《計算機世界》更全面,請不要錯過機會。
摘 要 本文提供了基於虛擬屏幕方案的VESA顯示驅動程序(800x600x16位增強色),包括一個對屏幕進行全屏刷新速度測試的示例程序。
關鍵詞 VESA標準 顯示驅動程序 虛擬屏幕 遊戲編程
開發環境 Watcom C++10.0、32位DOS平台
--------------------------------------------------------------------------------
一、關於虛擬屏幕
虛擬屏幕技術是當今電腦遊戲編程中採用的一項先進技術,但此其編程內幕卻未見在電腦或軟件期刊、網頁等媒體上有多少透露。本人不揣冒昧,願將自己實踐的結果公諸於世,供廣大遊戲編程愛好者參考。
所謂虛擬屏幕,就是在內存中分配一塊內存,並將其當成顯存進行繪圖操作,當繪圖完成時,就將其複製到顯存中。
使用虛擬屏幕技術有什麼好處呢?
一、操作方便,速度快。在遊戲中,有大量的圖塊存取操作。但在高清晰度下,顯存是分頁操作的,特別是行內分頁的顯示模式下進行圖塊存取更是困難。由於虛擬屏幕的物理位置是在內存中的,所以不存在分頁問題,而且在操作速度上要比顯存快上二倍多。
二、虛擬屏幕大小不受顯示分辨率的影響。在內存中,可以創建任意大小的虛擬屏幕,在顯示時只拷貝一部分到顯存即可。
三、有利於播放動畫。在內存中,我們可以創建多個虛擬屏幕,各存入一幀圖像,然後按順序進行顯示,即可以實現動畫的快速播放。
四、卷軸動畫。所謂卷軸動畫,即創建幾層虛擬屏幕,一層疊加一層。《真侍魂》就是利用這一技術的代表作遊戲。
二、虛擬屏幕技術的適用範圍
虛擬屏幕的應用主要有三種:
一、大屏幕。常見的大型遊戲中,比如《仙劍奇俠傳》、《紅色警戒》都是先將整個地圖創建成一個超大的虛擬屏幕,在虛擬屏幕上進行操作。顯示時就將要顯示的一部分拷貝到顯存中顯示出來。而只將提示信息、菜單等操作在真實屏幕中進行。
二、多層屏幕卷軸動畫。這一方面也主要應用於遊戲中。著名的2D格鬥遊戲《街霸》、《真侍魂》和國產RPG《江湖》就充分的利用了這一技術,在《真侍魂》中,程序創建了八層虛擬屏幕,分別是遠背景(雲,月,星星等)、近背景、裁判、第一層可砍景物(柳生場地右邊的竹子)、人物、動物(狗,鷹,猴)和氣功(旋風,火等)、第二層可砍景物(柳生場地左邊的竹子)、血槽和氣量表。對八個層次的虛擬屏幕分別進行操作,然後進行組合,組合時通過不同的移動速度就可以產生很強的立體感(遠景移動快;近景移動慢)。
三、多個屏幕。這一技術在MacOS、Windows(2.0及更高版本)、OS/2等圖形界面操作系統中沒有不使用的。拿常見的Windows來說,Windows為每個窗口建立一個虛擬屏幕,等有了窗口刷新的消息時,才將虛擬屏幕中的內容在真實屏幕上顯示出來。
三、關於VESA、32位DOS平台和Watcom C++
VESA是當今普遍使用的顯示卡編程接口,可以說在市場上已經沒有了不支持VESA標準的顯示卡。所以,我們在編寫顯示程序時只需編寫一個VESA顯示程序即可獲得最大的兼容性。
在16位實模式的DOS平台下,能夠直接操作的內存只有640k,而現在的內存16MB已是相當普及了。如果使用EMS或XMS,則要進行二次數據存取,將大大影響操作速度。倘若在編程時採用DPMI 32位保護模式技術進行編程,雖然可以越過640k基本內存的限制,但由於程序仍然是16位的,讓CPU在32位保護模式下運行16位程序,運行速度將會變的相當慢。然而,使用Watcom C/C++ 32位模式進行編程,則可以直接實現由16位到32位、640k到4GB的飛躍。當今的一些著名遊戲都是使用Watcom C/C++進行編寫的,例如《金庸群俠傳》、《阿貓阿狗》、《紅色警戒》等等。
四、編程注意事項
由於Watcom C/C++編出的程序是32位的,所以在編程時同Borland C/C++有所不同。要注意以下幾點:
一、數據長度不同。在Borland C/C++3.1中,int數據是16位的。而在Watcom C/C++中,int數據則是32位的。
二,內存地址不同。32位保護模式下的內存(包括顯存在內)都是經過重新影射過的。同實模式下的內存地址不同。如果使用實模式的顯存地址進行讀寫,那後果則是無法預料的。所以在進行顯存讀寫操作時,必須對顯示地址進行轉換之後才能使用。
三、中斷調用方式不同。在32位保護模式下,不能使用int86、int86x進行中斷調用。要使用int386、int386x進行中斷調用。
五、程序的特點
這是一套為開發遊戲而編寫的及其商業化的顯示驅動程序。其顯示速度超快無比。在Pentium 133、800x600x16位增強色模式、中文Windows98的DOS窗口下,全屏刷新速度高達每秒40屏左右,最高時可達每秒45屏。若是在純DOS方式下速度更快。而DirectX6.0中的DirectDraw6.0只能達到每秒鐘27屏左右。這主要是由於Windows不允許軟件直接操作硬件造成的,用DirectX編寫的遊戲,窗口的虛擬屏幕中其實根本就沒有寫入任何東西。DirectX越過了Windows的兼管,直接向顯存中寫入數據來實現快速顯示。這種手法真是讓人好笑,DirectX讓Windows程序又回到了DOS狀態。因為DOS程序從來都是直接操作硬件的,所以速度要比Windows程序快的多。
說它商業化,不僅是因為它速度快,主要是因為它是基於遊戲需要,完全為2D遊戲所設計編寫的。所以只提供了幾個簡單的函數:畫點(原本也沒有)、畫水平線、畫垂直線、畫矩形、畫塊;而沒有提供畫線、畫圓等函數。由於篇幅限制,省略了圖形變換、顯示中英文字符、顯示窗口、圖塊操作等函數。(如若需要可向作者索取)
六、程序使用方法
本套顯示驅動程序有二個文件,頭文件Svga.h和源程序文件Svga.cpp。使用時將Svga.h連入程序,編譯時連Svga.cpp一起編譯即可。在程序中,首先要像DirectDraw一樣建立一個控制指針,再通過此指針進行圖形操作,要顯示時再執行Redraw()函數進行屏幕刷新。
七、源程序
示例程序TEST.CPP
#include"svga.h" //加入本顯示驅動程序
#include
#include
uvar8 test() //屏幕刷新速度測試函數
{
time_t t1,t2; //二個時間記載變量
uvar16 i;
VIEW vga; //創建控制指針
for(i=0;i<512;i++)
vga.HLine(0,i,800,RGB(255,i/2,0)); //畫水平線
t1=time(NULL); //記載第一個時間
for(i=0;i<256;i++) //屏幕刷新256次
vga.Redraw(); //調用屏幕刷新函數
t2=time(NULL); //記載第二個時間
return(t2-t1); //返回兩個時間差
} //函數退出時,控制指針自動關閉圖形模式
void main()
{
printf("256 screen/ %d second/n",test()); //顯示刷新256屏所用的秒數
}
源程序Svga.h
typedef signed char var8; // 8位有符號變量
typedef unsigned char uvar8; // 8位無符號變量
typedef signed short var16; //16位有符號變量
typedef unsigned short uvar16; //16位無符號變量
typedef signed int var32; //32位有符號變量
typedef unsigned int uvar32; //32位無符號變量
#define Yes 0
#define No 1
#include
#define RGB(r,g,b) (((r)/8)*2048+((g)/4)*32+(b)/8) //三原色轉換式
class VIEW //顯示類
{
uvar16 color; //當前操作顏色
void SetVideo(uvar16); //初始化顯示模式
void VideoPage(uvar8); //顯存跳頁函數
protected:
public:
uvar16 *video,*_video; //虛擬屏幕內存指針
VIEW(); //本類構造函數
~VIEW(); //本類析構函數
void Redraw(); //根據虛擬屏幕刷新真實屏幕
void SetColor(uvar8 r,uvar8 g,uvar8 b) //根據3原色設置顏色
{color=RGB(r,g,b);}
void SetColor(uvar16 c) //直接設置顏色
{color=c;}
void Cls(); //設顏色為0並清屏幕
void Clear(); //按當前顏色清屏幕
void Clear(uvar16); //按指定顏色清屏
void Clear(uvar8,uvar8,uvar8); //按指定3原色清屏
void PutPixel(uvar16,uvar16); //畫點
void PutPixel(uvar16,uvar16,uvar16); //指定顏色畫點
void HLine(uvar16,uvar16,uvar16); //畫橫線
void HLine(uvar16,uvar16,uvar16,uvar16); //指定顏色畫橫線
void VLine(uvar16,uvar16,uvar16); //畫豎線
void VLine(uvar16,uvar16,uvar16,uvar16); //指定顏色畫豎線
void Rectangle(uvar16,uvar16,uvar16,uvar16); //畫空心矩形
void Rectangle(uvar16,uvar16,uvar16,uvar16,uvar16); //指定顏色畫空心矩形
void Bar(uvar16,uvar16,uvar16,uvar16); //畫實心矩形
void Bar(uvar16,uvar16,uvar16,uvar16,uvar16); //指定顏色畫實心矩形
};
源程序Svga.cpp
#include"svga.h"
#include
#include
#include
VIEW::VIEW() //本類構造函數,自動執行
{
SetVideo(0x114); //初始化圖形顯式模式
_video=(uvar16 *)malloc(800*601*sizeof(uvar16)); //為虛擬屏幕分配內存
video=_video;Cls(); //創建後備指針並清屏
}
VIEW::~VIEW() //本類析構函數,自動執行
{
free(_video); //釋放虛擬屏幕所用內存
SetVideo(3); //初始化屏幕為文本狀態
}
void VIEW::SetVideo(uvar16 mode) //設置顯示模式,mode為顯示模式
{
union REGS i;
if(mode>=0x100) //如果是VESA顯示模式
{
i.w.bx=mode;
i.w.ax=0x4F02;
int386(0x10,&i,&i);
return;
}
else //如果是標準顯示模式
{
i.w.ax=mode;
int386(0x10,&i,&i);
return;
}
}
void VIEW::VideoPage(uvar8 pn) //選擇顯存頁函數,pn為頁號
{
union REGS i;
i.w.bx=0;
i.w.dx=pn;
//如果您的顯卡VESA標準版本較低,一頁是4k而不是64k的,將會顯示不正常,請將上一行改為:
//i.w.dx=pn*16;
i.w.ax=0x4F05;
int386(0x10,&i,&i);
return;
}
void VIEW::Redraw() //屏幕刷新函數
{
uvar8 i;
uvar16 *VIDEO=(uvar16 *)(0xA000<<4); //顯存指針
video=_video; //恢復虛擬屏幕指針
for(i=0;i<14;i++)
{
VideoPage(i); //選擇顯存頁面
memcpy(VIDEO,video,65536); //從虛擬屏幕拷貝64k數據到顯存
video+=32768; //虛擬屏幕指針後移
}
VideoPage(14); //選擇最後一頁
memcpy(VIDEO,video,42496); //寫入最後一頁數據
video=_video; //恢復虛擬屏幕指針
}
inline void VIEW::Cls() //將顏色置0(黑色)並清屏
//內置式函數
{
memset(_video,color=0,960000); //將整個虛擬屏幕全部寫入0
}
inline void VIEW::Clear() //按當前顏色清屏,內置式函數
{
Bar(0,0,799,599); //在整個屏幕內畫塊
}
inline void VIEW::Clear(uvar16 c) //指定顏色並清屏,內置式函數
// c為16位顏色代碼
{
Bar(0,0,799,599,c); //按指定顏色在整個屏幕內畫塊
}
void VIEW::Clear(uvar8 r,uvar8 g,uvar8 b) //指定三原色清屏
// r=紅色量,g=綠色量,b=藍色量,範圍均為0-255
{
SetColor(r,g,b); //按三原色設置顏色
Bar(0,0,799,599); //在整個屏幕內畫塊
}
void VIEW::PutPixel(uvar16 x,uvar16 y) //用當前顏色畫點函數
// x=橫坐標,y=縱坐標
{
if(x>799||y>599)return; //判斷坐標是否出界
video=_video+y*800+x; //移動虛擬屏幕指針
*video=color; //寫入顏色代碼
video=_video; //恢復虛擬屏幕指針
}
void VIEW::PutPixel(uvar16 x,uvar16 y,uvar16 c) //按指定顏色畫點
// x=橫坐標,y=縱坐標,c=16位顏色代碼
{
if(x>799||y>599)return; //判斷坐標是否出界
video=_video+y*800+x; //移動虛擬屏幕指針
*video=color=c; //寫入顏色代碼並改變當前顏色
video=_video; //恢復虛擬屏幕指針
}
void VIEW::HLine(uvar16 x,uvar16 y,uvar16 n) //畫水平線
// x=橫坐標,y=縱坐標,n=水平線長度
{
if(x>799||y>599)return; //判斷坐標是否出界
if(x+n>800)n=800-x; //判斷水平線是否出界
video=_video+y*800+x; //移動虛擬屏幕指針
for(uvar16 i=0;i
}
void VIEW::HLine(uvar16 x,uvar16 y,uvar16 n,uvar16 c) //按指定顏色畫水平線
// x=橫坐標,y=縱坐標,n=水平線長度,c=16位顏色代碼
{
if(x>799||y>599)return; //判斷坐標是否出界
if(x+n>800)n=800-x; //判斷水平線是否出界
video=_video+y*800+x; //移動虛擬屏幕指針
color=c; //改變當前顏色代碼
for(uvar16 i=0;i
}
void VIEW::VLine(uvar16 x,uvar16 y,uvar16 n) //畫垂直線函數
// x=橫坐標,y=縱坐標,n=垂直線長度
{
if(x>799||y>599)return; //判斷坐標是否出界
if(y+n>600)n=600-y; //判斷垂直線是否出界
video=_video+y*800+x; //移動虛擬屏幕指針
for(uvar16 i=0;i
*video=color; //寫入顏色代碼
video+=800; //移動虛擬屏幕指針
}
video=_video; //恢復虛擬屏幕指針
}
void VIEW::VLine(uvar16 x,uvar16 y,uvar16 n,uvar16 c) //按指定顏色畫垂直線
// x=橫坐標,y=縱坐標,n=垂直線長度,c=16位顏色代碼
{
if(x>799||y>599)return; //判斷坐標是否出界
if(y+n>600)n=600-y; //判斷垂直線是否出界
video=_video+y*800+x; //移動虛擬屏幕指針
color=c; //改變當前使用顏色
for(uvar16 i=0;i
*video=color; //寫入顏色代碼
video+=800; //移動虛擬屏幕指針
}
video=_video; //恢復虛擬屏幕指針
}
void VIEW::Rectangle(uvar16 x,uvar16 y,uvar16 x1,uvar16 y1) //畫矩形框
// x=左上角橫坐標,y=左上角縱坐標,x1=右下角橫坐標,y1=右下角縱坐標
{
HLine(x,y ,x1-x+1); //畫上線
HLine(x,y1,x1-x+1); //畫下線
VLine(x,y+1,y1-y-1); //畫左線
VLine(x1,y+1,y1-y-1); //畫右線
}
void VIEW::Rectangle(uvar16 x,uvar16 y,uvar16 x1,uvar16 y1,uvar16 c)//按指定顏色畫矩形框
// x=左上角橫坐標,y=左上角縱坐標,x1=右下角橫坐標,y1=右下角縱坐標,c=16位顏色代碼
{
HLine(x,y ,x1-x+1,c); //按指定顏色畫上線
HLine(x,y1,x1-x+1); //畫下線
VLine(x,y+1,y1-y-1); //畫左線
VLine(x1,y+1,y1-y-1); //畫右線
}
void VIEW::Bar(uvar16 x,uvar16 y,uvar16 x1,uvar16 y1) //畫塊
// x=左上角橫坐標,y=左上角縱坐標,x1=右下角橫坐標,y1=右下角縱坐標
{
for(uvar16 i=y;i<=y1;i++)HLine(x,i,x1-x+1); //循環畫水平線
}
void VIEW::Bar(uvar16 x,uvar16 y,uvar16 x1,uvar16 y1,uvar16 c) //按指定顏色畫塊
// x=左上角橫坐標,y=左上角縱坐標,x1=右下角橫坐標,y1=右下角縱坐標,c=16位顏色代碼
{
color=c; //改變當前操作顏色
for(uvar16 i=y;i<=y1;i++)HLine(x,i,x1-x+1); //循環畫水平線
}
示例程序編譯命令: WCL386 -fe=TEST -ox SVGA.CPP TEST.CPP
以上可查看svgalib实现