• Thứ Ba, 16/12/2003 16:23 (GMT+7)

    Lập trình tạo hiệu ứng lửa


    L
    ửa trong warcraft 3 trước khi bùng

        Để tạo được những hình ảnh ấn tượng trong các trò chơi (game), bên cạnh việc thiết kế engine, xây dựng mô hình… còn có một yếu tố cũng khá quan trọng đó là các hiệu ứng đặc biệt. Một trong những hiệu ứng hay được dùng nhất là hiệu ứng lửa (fire hay flame). Có một cách tạo lửa không cần dùng thuật toán đặc biệt mà dùng các hình ảnh vẽ sẵn, ví dụ trong game “No one lives forever”, tuy nhiên cách này cho hình ảnh trông không thực. Bạn có thể so sánh với các ngọn lửa được tạo bằng thuật toán trong “Warcraft 3” hay “Unreal Tournament 2003”. Bài viết này giới thiệu một vài ý tưởng cơ bản cho các bạn yêu thích lập trình đồ hoạ về cách tạo lửa. Các đoạn mã ví dụ sử dụng Borland C 3.1 cho DOS hay Visual C 6.0 và thư viện DirectDraw cho Windows. Các đoạn mã được viết chú trọng đến tính dễ hiểu, có thể chưa phải là tốt nhất. Lửa trong Warcraft 3 trước khi bùng

    Bộ Lọc Màu
        Thuật toán đơn giản nhất để tạo lửa dựa trên bộ lọc màu. Mỗi hình ảnh hai chiều đều là một ma trận (mảng 2 chiều) các điểm ảnh. Bộ lọc màu tạo ra một ma trận cùng kích thước với ma trận ban đầu, trong đó màu của mỗi điểm mới dựa trên sự phối trộn màu của những điểm cũ (thường là kế cận điểm đó). Một bộ lọc màu đơn giản thường sử dụng lưới lọc có dạng như sau:
        Với mỗi điểm cần lọc, lưới lọc được đặt chồng lên sao cho điểm đó trùng với ô quy định (ô đánh dấu *). Sau đó, mỗi ô bên dưới lưới lọc được nhân với số tương ứng trên lưới, tính tổng rồi chia cho D và cuối cùng cộng với H.
        Với lưới lọc ở trên, ta có thể tính:
    Mới[i][j]= (a*Cũ[i-1][j]+b*Cũ[i][j-1]+c*Cũ[i][j]+d*Cũ[i][j+1]+e*Cũ[i+1][j]) / D + H
    (Chú ý, ở đây sử dụng cách đánh chỉ số hàng trước, cột sau giống như trong ma trận toán học. Trong chương trình cũng sử dụng cách đánh chỉ số này do cấu trúc vật lí của bộ nhớ đồ hoạ ghi theo từng hàng)
        Trong chế độ bảng màu, mỗi điểm là một số nguyên. Trong chế độ nhiều thành phần màu (thường là R-G-B hoặc R-G-B-A), mỗi thành phần màu được lọc riêng
    .

    Cơ Bản Về Tạo Lửa


          L
    ửa trong warcraft 3

        Lửa luôn được tạo ra từ một nguồn nào đó (đơn giản nhất là đáy dưới hình). Những điểm nguồn được tạo ngẫu nhiên để tạo vẻ tự nhiên cho ngọn lửa, tuy nhiên thường được điều khiển có chừng mực để tạo ra hình dáng ngọn lửa như ý muốn. Sau đó, một bộ lọc màu được áp dụng. Bộ lọc này có tính chất “đùn lên” để ngọn lửa bốc lên. Bộ lọc này có thể lệch về một hướng nào đó để ngọn lửa trông như bị thổi bạt. Giá trị H thường là âm để ngọn lửa tối dần khi lên cao. Dưới đây là một số bộ lọc mẫu:
        - Có thể thực hiện lọc trực tiếp trên một ma trận màu thực. Tuy nhiên, việc này nói chung không đem lại nhiều hiệu quả và làm giảm tốc độ thực thi đáng kể. Tốt nhất nên thực hiện lọc gián tiếp trên ma trận kiểu bảng màu, sau đó đối chứng với bảng màu để ghi kết quả ra. Bảng màu phải có tính chất sáng dần lên, thường là bảng màu tuyến tính (nghĩa là các thành phần màu thực tương ứng là hàm bậc nhất của chỉ số bảng màu).
        - Bảng màu đơn giản nhất chuyển tiếp từ màu trắng sang vàng, rồi từ vàng sang đỏ, cuối cùng từ đỏ tối dần thành đen
    .
    for(count=0;count<64;count++){

      table[count][red]  =count;

      table[count][green]=

      table[count][blue] =0;

      table[count+64][red]  =MAXNUM;

      table[count+64][green]=count;

      table[count+64][blue] =0;

      table[count+64*2][red]  =

      table[count+64*2][green]=MAXNUM;

      table[count+64*2][blue] =count;

      table[count+64*3][red]  =

      table[count+64*3][green]=

      table[count+64*3][blue] =MAXNUM;

      }
        Ở đây, MAXNUM là giá trị cực đại của mỗi thành phần màu. Trong DOS, ở mode13, MAXNUM=63; trong Windows, tuỳ thuộc vào chế độ thiết lập mà MAXNUM của mỗi thành phần màu là khác nhau. Tuy nhiên, ta có thể cho MAXNUM=255, là giá trị lớn nhất có thể (trong chế độ 8/24/32 bit), sau đó dùng một đoạn mã khác tổ hợp các thành phần màu và chuyển về giá trị thích hợp. Trong đoạn chương trình trên, bạn có thể thay đổi thứ tự biến thiên các thành phần để tạo ra màu như kiểu lửa gas
        Dưới đây là một cách tạo màu khác cho ra màu thật hơn:
    int count;

    int hrb=MAXNUM-redBack;

    int hgb=MAXNUM-greenBack;

    int hbb=MAXNUM-blueBack;

    #define CreateComponent(val,r,g,b)\

      {\

        table[val][red]  =(r)*hrb/(63<<2)+redBack;\

        table[val][green]=(g)*hgb/(63<<2)+greenBack;\

        table[val][blue] =(b)*hbb/(63<<2)+blueBack;\

      }

    for(count=0;count<64*2;count++)

    {

      CreateComponent(count,count<<1,count,0);

      CreateComponent(count,0,0,0);

      CreateComponent(count+64,count<<2,count<<1,0);

      CreateComponent(count+64*2,63<<2,(count+63)<<1,0);

      CreateComponent(count+64*3,63<<2,63<<2,count<<2);

      }
        Ở đây (redBack,greenBack,blueBack) là màu nền. Ta sử dụng các thành phần này để chuyển màu trong khoảng (0 .. 63<<2) đến (back .. 255), như được miêu tả trong macro CreateComponent. Thành phần blue được giảm đến 0 giống như cách cũ. Nhưng thành phần green giảm đều trong cả hai phần tư bảng màu, thành phần red chỉ giảm trong một phần tư bảng màu. Kết quả tạo ra những thang màu đỏ vàng tối rất giống màu lửa.
    Mã lọc rất đơn giản, chẳng hạn đoạn mã lọc cho lưới lọc đầu tiên được trình bày dưới đây:

    for(cy=0;cy<199;cy++)

    for(cx=1;cx<319;cx++){

      val=

        (virMem[cy][cx]+virMem[cy+1][cx]+

        virMem[cy+1][cx-1]+virMem[cy+1][cx+1])/4;

      virMem[cy][cx]=val?val-random(2):0;

      }

        Trong đó, virMem là vùng nhớ chứa nội dung ảnh trước khi xuất ra bộ nhớ màn hình, là ma trận hình của ta.

    Nguồn Gập Ghềnh
        Nguồn lửa có thể có hình dáng tuỳ ý. Kiểu như thanh gươm hay dòng chữ bốc cháy. Ta có thể dùng một ma trận nguồn, dựa vào đó để đặt các điểm ban đầu. Hai đoạn mã dưới đây tương ứng tạo một bảng sin rồi sau đó tạo nguồn theo hình sin:

    unsigned char sinTable[320];

    for(count=0;count<320;count++)

      sinTable[count]=(unsigned char)((sin(count*M_PI/100)+1)*25);

    for(count=0;count<320;count++)

      virMem[199-sinTable[count]][count]=random(192)+256-192;


    Lửa tạo bằng phương pháp frank patxi

    for(count=110;count<210;count++)

      virMem[150-sinTable[count-110]][count]=random(256);

    Khối
        Khói vốn bắt nguồn từ lửa. Do vậy trong thực tế khói bốc lên tương tự như lửa. Ta có thể tạo khói bằng cách đơn giản là thay đổi bảng màu và sửa đổi đôi chút mã chương trình, các hằng số sử dụng nếu cần  thiết.Đoạn mã sau tạo ra một thang màu xám, có thể dùng để tạo khói.

    for(count=0;count<256;count++)

      table[count][red] = table[count][green] = table[count][blue] = 

       count*MAXNUM/255;

    Một Phương Pháp Khác
        Thuật toán được trình bày dưới đây (và các biến thể của nó) là thuật toán tạo lửa tốt nhất hiện nay, được công bố lần đầu bởi Frank Jan Sorensen (hay Frank Patxi). Chương trình ở đây có ít nhiều sửa đổi để tạo ra một ấn tượng hơi khác. Tuy nhiên, về cơ bản là cùng một ý tưởng.
        Việc sử dụng bộ lọc làm ngọn lửa có tính đều dần khi bốc lên cao do sự hoà trộn nhiều điểm kế cận. Đặc biệt, việc tạo đốm lửa (văng ra khỏi ngọn lửa) rất khó. Thực sự, nó được dùng để tạo cảm giác như lửa bốc lên từ nham thạch nhiều hơn là lửa đốt.
        Thuật toán của Frank Patxi đốt 1 trong 3 điểm bên trên từ 1 điểm bên dưới, với độ sáng giảm một lượng nhỏ ngẫu nhiên, làm cho các điểm không bị hoà đều vào nhau khi lên cao
    :
    for(cx=xStart;cx<xEnd;cx++)

    for(cy=yMin;cy<=yMax;cy++){

      temp=virMem[cy][cx];

      if(temp<decay)virMem[cy-1][cx]=0;

      else virMem[cy-1][cx+random(3)-1]=temp-random(decay);

      }
        Ở đây, decay là khoảng biến thiên lượng giảm. Tương tự như “H” trong bộ lọc, decay càng lớn thì ngọn lửa càng thấp. Vùng lửa được vẽ ra là (xStart,yMin-1) .. (xEnd-1,yMax). Chú ý là đoạn mã bên trên không đảm bảo tính đối xứng giữa các mép trái và phải khi chạy theo vòng for. Điều này dẫn đến ngọn lửa hơi bị lệch về bên phải. Để khắc phục điều này, ta có thể đảo vòng for của cx sau mỗi lần “burn”. Cách thực hiện chi tiết được chỉ ra trong mã nguồn chương trình sau.
        Nguồn được tạo tại từng điểm (hoặc khoảng điểm) chứ không tạo toàn bộ như trong thuật toán bộ lọc. Vì có thao tác ngẫu nhiên trong việc “burn”, nguồn không nhất thiết phải có sự khác biệt. Thậm chí, bạn có thể đặt nguồn là một điểm sáng cố định. Điều này làm cho ngọn lửa trông như phụt ra từ một khe nứt.
    unsigned char*ptr;

    if(!random(addSourceTime))

      memset(virMem[yMax]+xStart+random(width-fireWidth+1),0xff,fireWidth);

    if(!random(addWaterTime))

      memset(virMem[yMax]+xStart+random(width-waterWidth+1),0,waterWidth);

    for(count=xStart,ptr=&virMem[yMax][xStart]; count<xEnd; count++,ptr++){

      if((temp=*ptr)<minFire)

        *ptr=temp+fireIncrease;

      else{

        temp+=random(fireChange*2+1)-fireChange;

        *ptr=(temp>255)?255:temp;

        }

      }

    for(count=0;count<width/8;count++){

      if(random(200))virMem[yMax][xStart+count]=0;

      if(random(200))virMem[yMax][xEnd-count-1]=0;

      }


     
    Lửa tạo bằng phương pháp frank patxi và kênh mờ

        Thêm một vài điểm tối (0) vào hai bên trái và phải nguồn lửa, sau đó được làm mượt làm cho lửa khum vào và không tạo ra cảm giác “khung gỗ” bao quanh ngọn lửa. Vòng lặp đầu tiên rất đáng quan tâm. Nó cho phép lửa được duy trì sau khi đã tạo ra một nguồn; nguồn được thay đổi một cách mềm mại và tự nhiên.
        Để tránh cảm giác lửa như được phụt ra từ các ống riêng rẽ, ta có thể làm mượt nguồn. Với mỗi điểm nguồn, ta lấy giá trị trung bình của một số điểm kề cận.
    Cuối cùng, để cho ngọn lửa sinh động, việc chọn giá trị cho các hằng số rất có ý nghĩa. Chỉ với một vài thay đổi giá trị hằng số, bạn có thể làm cho lửa trông như được tạo ra từ một vụ cháy lớn, từ một ngọn đuốc hay được nhen lên trong bếp.

    Lửa Mờ
        Trong các game 3D, ngọn lửa không chỉ cháy trên một nền đồng màu. Để tạo cảm giác thực, lửa cần được phối trộn với nền. Việc này đơn giản chỉ là tạo hiệu ứng trên kênh mờ (kênh alpha, dùng để tính độ mờ – đúng hơn là độ hiện rõ – của hình ảnh) của hình ngọn lửa. Vì sử dụng hệ thống màu thực trong DOS khá phức tạp, ở đây chỉ đưa ra ví dụ trong Windows.

    Mã Nguồn
        Chương trình dùng bộ lọc trong DOS là FIRE_PCW.C. Chương trình này cho phép bạn dùng các phím ‘1’ .. ‘4’ để thay đổi bảng màu, ‘a’ .. ‘c’ để thay đổi cách tạo nguồn và ‘A’ .. ‘C’ để thay đổi bộ lọc. Không phải tất cả các cách kết hợp đều cho kết quả đẹp.
    Chương trình thể hiện giải thuật Frank Patxi là “FIRE_FP.C”. Chương trình trong Windows được tổ chức thành dự án FIREW, trình bày phương pháp Frank Patxi và tạo lửa mờ.
    Hi vọng bài viết trên có ích cho các bạn và sẽ có dịp tiếp tục cùng các bạn thảo luận về những phương pháp đồ hoạ hay
    .

    Hồ Song Phi

    ID: A0307_104