阿贝的例程学校 第1部分:mode 13h

    技术2022-05-11  194

    [声明]我对阿贝的例程学校(Abe’s Demoschool)和阿贝的智囊(Abe’s bag of tricks)的翻译得到了原作者的同意。任何人如果想使用阿贝的例程学校(Abe’s Demoschool)和阿贝的智囊(Abe’s bag of tricks)做学习交流之外的事情,请与原作者联系:Albert.Veli@systemdesign.af.se

     

    Abe's Demoschool Part 1 : mode 13h

    阿贝的例程学校 1部分:mode 13h

     

    欢迎来到例程学校的第一部分内容。在例程学校中,我将带你领略各种各样的图形特效,包括palette rotation(滚动调色板)、plasma、火焰、sprites(精灵)、3D图形、阴影、卷屏、mode-Xsoundblaster等等所有可以展示的效果。例程学校中的大部分内容都是基于mode 13h或称MCGA-mode。这就立刻引出了第一个问题:

     

    [什么是mode 13h]

    好吧,答案很简单:mode 13hPC机的一种图形模式,大部分游戏和图形演示都基于mode 13h完成。mode 13h的分辨率是水平方向320个像素,垂直方向200个像素。这使得屏幕上一共出现320×200=64000个像素。屏幕上的每个像素可以有256种不同的颜色(当然一个像素一次只能具有一种颜色)。你肯定知道,一个byte8bit)可以取256种不同的值。这意味着mode 13h中的每一个像素都可以用一个byte来表示,而事实也正是如此。

    如上所述,mode 13h拥有320×200=64000个像素即64000bytesMode 13hscreen-memory从地址A000:0000h开始,到A000:FFFFh结束。这总共有65535bytes,从而使屏幕底部还包含了4个不可见的行。

    要往屏幕上写一个像素,须先把段寄存器ES设为A0000h,再计算出该像素的偏移量放入DI,然后向ES:DI写入一个colorbyte(即表示颜色的byte)。偏移量用下式计算:

    偏移量 = 320 × y + x

    其中xx坐标值,yy坐标值。坐标(0,0)位于屏幕的左上角,坐标(319,319)位于屏幕的右下角。下面列出了各个坐标及其相应的偏移量:

           (0,0)       (1,0)       (2,0)       (3,0)                         (319,0)

    0     0            1              2            3                           319        ------x-----à

    1     320        321        322        323                       639        (319,1)

    2     640        641        642        643                       959        (319,2)

    .      .             .              .             .                            .

    .      .             .              .             .                            .

    .      .             .              .             .                            .

    199  63680       63681       63682       63683                      63999

           (0,199)       (1,199)       (2,199)       (3,199)                         (319,199)

    y

     

    mode 13h属于一种线性存储模型(linear memory model),即byte一个接一个的顺序排列。

    要在坐标(3,2)处用颜色1(缺省值是蓝色)写入一个像素,你应该向A000:(320×2+3)=A000:643处写入值1。试试吧,一定行。

    下面是用C写的一个未优化的例子:

    void putpixel(int x,int y,char color)

    {

     int offs;

     offs=320*y + x;           //使用公式计算偏移量

     asm     mov     ax,0a000h //你不能把一个值直接放到ES

     asm     mov     es,ax      //把屏幕地址放入ES

     asm     mov     di,offs    //把计算出来的偏移量放入DI

     asm     mov     al,color   //colorbyte放入AL

     asm     mov     [es:di],al //最后将像素写入屏幕

    }

    在向屏幕写像素之前,你必须已经处于MCGA-mode 13h模式。可以用下面的方法切换到这个mode 13h

        mov     ax,13h  ;把模式代号放入AX

        int     10h      ;使用videointerrupt 10h进入改模式

    可以用下面的方法切换回我们常用的textmode 3

        mov     ax,3    ;mode 3DOS的标准80×25 16色文本模式

        int     10h     ;进入textmode 3

    你可以用C编写一个切换屏幕模式的函数:

    void setmode(int mode)

    {

      asm     mov     ax,mode

      asm     int     10h

    }

    这样,你就可以在main程序开头用setmode(0x0013);切换到mode 13h,再用setmode(3);切换回文本模式。

    要优化putpixel函数,你可以将很慢的乘法替换为位运算和加法。你肯定也知道,向左移1位等同于乘以2,向左移2位等同于乘以4,等等。所使用的技巧是,将320分为几个以2为底的幂之和。320(256+64)y×320(y左移8)+(y左移6)。可以参看使用inline-assembler的优化过的putpixel实现之C代码。

    一个像素具有什么颜色取决于两个因素:你向屏幕写入的颜色值(0255)以及该值在调色板中所代表的颜色。调色板共有256个入口(0255)。调色板中的每一种颜色由3byte表示,分别代表红、绿、蓝三种颜色。每一个byte值都在063之间。值的大小表示了颜色的强度:0表示没有强度,63表示最大强度。这样就有了643次方种可能的颜色。

    [常见的颜色]

          绿             颜色

        ______________________________

        0       0       0       BLACK

        63      0       0       RED

        20      0       0       DARK RED暗红

        0       63      0       GREEN绿

        0       0       63      BLUE

        63      63      0       YELLOW

        63      32      0       ORANGE

        63      0       63      PURPLE

        0       63      63      CYAN

        63      63      63      WHITE

        32      32      32      GRAY

        10      10      10      DARKGRAY暗灰

    MCGA-mode 13h总是以相同的调色板开始(即0表示黑,1表示蓝,等等)。但要自己改变调色板是很容易的。通过向3c8h8c9h端口写入适当信息,我们就可以改变调色板。这并没有看上去那么难。你只要向3c8h端口写入你希望改变的起始颜色的索引值,再向3c9h端口写入红、绿、蓝的值就可以了。向3c9h写入了这三个colorbyte后,索引会自动递增。

    如果调色板被存放在一个数组pal3×256bytes长),我们假设0代表黑,1代表蓝,2代表灰,那么pal将会像这样开始:0,0,00,0,6332,32,32;……

    下面是一个C函数,它根据数组pal的值来设置调色板:

    void setpal(char*pal)

    {

     int i;

     outp(0x3C8,0);         //从颜色值0开始

     for(i=0;i<256*3;i++)

     outp(0x3C9,pal[i]);    //将调色板数据送到3c9h端口

    }

    控制调色板最常用的方法就是在特定的程序里使用数组pal,而把调色板的信息存到磁盘的一个文件中。这个调色板文件很容易辨认,因为它的大小总是3×256=768bytes。在程序中你只需要从此磁盘加载调色板文件并将信息放入数组,再用上面讲到的setpal函数设置调色板。

    在汇编语言中,你可以使用一个非常快的指令rep outsb来设置调色板。rep outsb 指令从DS:SIbyte传到DX值指示的端口,传送的byte数目由CX的值指定。每传一个byteSI就增加1

    下面是设置整个调色板的汇编程序:

    mov     dx,03c8h      ;将端口号放到DX

        xor     al,al          ;AX清零

        out     dx,al          ;0传到3c8h端口

        lds     si,pal         ;DS:SI指向pal

        mov     cx,3*256       ;将要传送的byte数目存放在CX

        inc     dx              ;DX = 3c9h

        rep     outsb          ;pal3c9h端口传送768bytes

    一种常见的图形效果就是不断的循环改变数组pal并调用setpal(pal)以形成颜色的循环变化。你可以在其它图形特效里使用这种技术来制造静态plasma效果。首先设置数组pal,使其包含一些好看颜色之间平滑的循环变化;然后使用sin或者cos函数在屏幕上画出优美的图案;最后不断的滚动调色板,使其不停的循环即可。

    画出的图案并不必须是plasma picture,你可以画任意图案上去,看看当调色板滚动的时候会产生什么效果。要使数组pal循环滚动,先将第一个颜色(即前三个byte)保存起来,然后将数组中所有的颜色值向前移动一个位置(即3byte),最后再将尾部空出来的位置设为刚才保存的颜色。

    滚动调色板的C代吗如下:

    (实际上就是每次将调色板中第一个颜色滚动到最末尾的位置)

    void rotpal(char*pal,int first, int last)

    {

      char r,g,b;

      int i;

      r=pal[first*3 + 0];    //将第一个颜色的三个值分别赋给r,g,b

      g=pal[first*3 + 1];

      b=pal[first*3 + 2];

      for(i=first*3;i<(last+1)*3;i++) //将所有颜色向前移动一个位置

        pal[i]=pal[i+3];

     pal[last*3+0]=r;       //将最末尾设置为保存的原第一个颜色

     pal[last*3+1]=g;

     pal[last*3+2]=b;

     wtsync();      //等待电子束扫完当前帧

     setpal(pal);   //根据滚动过的pal设置调色板

    }

    你并不一定非要将第一个和最末个颜色作为函数参数。但这样做的确有些好处。在例程中,我使用了一个拥有多于256种颜色的输组,每次滚动整个调色板时,都没有改变值为0的颜色,我只设置前256个颜色。我不让值为0的颜色参与滚动是因为,我不希望让背景颜色也改变。你可以让其也参加滚动试一试,这样就能明白我的意思了。

    在每次改变调色板之前总应该调用wtsync函数,以避免因为与电子束不同步而产生的所谓“雪花”效果。wtsync等待垂直回溯,即电子束扫完一帧后跳回初始位置以便开始扫下一帧的时刻。

    也许你会说本次的例程只能算是半个plasma特效——好吧,在例程学校以后的部分里,我会给你演示真正的plasma特效。

    就到此为止,去看看例程的C源代码吧。

    [例程在http://www.mds.mdh.se/föreningar/small/abe/abedemo1.zip下载。]


    最新回复(0)