#author("2023-05-07T09:38:31+09:00","","")
[[GBA開発Wiki]]

#author("2023-05-08T17:16:33+09:00","","")
* ラスタースクロール [#ua2f72eb]
チュートリアルの最初で少し解説しましたVBlankとHBlankについて再度解説していこうと思います。HBlank期間を利用することで特殊な画面効果を実際にやってみましょう。~
チュートリアルの最初の方で少し触れたVBlankとHBlankについて再度解説していこうと思います。HBlank期間を利用することで特殊な画面効果が可能です。~

** 液晶画面 [#e0c4ca8c]
GBAの液晶画面は1秒間に約60回(59.727... hz)更新されます。1回の画面更新では、上のラインから1ラインずつ更新していき、画面を更新する160ライン分の期間と、直接描画は行わない68ライン分のブランク期間(VBlank)があります。通常のフレームごとの処理は画面のチラつき防止のためVBlank期間に行います。~

#ref(1.png,nolink)


画面を更新する1ラインごとの期間のうち、横1ラインを描画する期間が960サイクル、描画を行わない期間が272サイクル(HBlank)あります。1フレームの処理内容で縦ラインが160+68=228ライン、1ラインの処理が960+272=1232サイクルで1フレームあたりのサイクル数が228×1232=280896サイクルとなり、GBAのCPUは16.78MHz=16777216サイクルなので16777216/280896=59.727... hzとなり、ほぼ計算が合うことになります。~
画面を更新する1ラインごとの期間のうち、横1ラインを描画する期間が960サイクル、描画を行わない期間が272サイクル(HBlank)あります。960+272=1232サイクル。縦は160とVBlank68で160+68=228ライン、1フレームあたりのサイクル数は1232×228=280896サイクルとなり、GBAのCPUは16.78MHz=16780000サイクルなので1678000/280896=59.737... hz、ほぼ計算が合うことになります。このことは1秒間のCPUサイクル=1フレーム分と考えることができます。単純なことをいうと60fpsでゲームを作る場合、以下の表に書いてあるとおりVBlank中のCPU使用率30%以内でなければなりません。~

- Horizontal Dimensions (H-Blank)
 The drawing time for each dot is 4 CPU cycles.
 
  Visible     240 dots,  57.221 us,    960 cycles - 78% of h-time
  H-Blanking   68 dots,  16.212 us,    272 cycles - 22% of h-time
  Total       308 dots,  73.433 us,   1232 cycles - ca. 13.620 kHz

- Vertical Dimensions (V-Blank)
  Visible (*) 160 lines, 11.749 ms, 197120 cycles - 70% of v-time
  V-Blanking   68 lines,  4.994 ms,  83776 cycles - 30% of v-time
  Total       228 lines, 16.743 ms, 280896 cycles - ca. 59.737 Hz


** ラスタースクロール [#i8376fbd]
横1ラインの描画時に、横1ライン分のドットの色をBGやスプライト、~
拡大縮小回転、モザイクなどの特殊効果を考慮して描画されるのですが、~
1ラインの描画が終わって次のラインを描画するまでのブランク期間(HBlank)に、~
BGの表示位置などを変更することによって通常の描画では行えないような特殊な画面効果を出すことができます。~
横1ラインの描画は、横1ライン分のドットの色をBGやスプライト、拡大縮小回転、モザイクなどの特殊効果を考慮して描画されます。1ラインの描画が終わって次のラインを描画するまでのブランク期間(HBlank)に、BGの表示位置などを変更することによって通常の描画では行えないような特殊効果を出すことができます。具体的にはHBlank中に横方向のスクロールのレジスタ値を変更します。このことをラスタースクロールと言います。HBlank期間を判定する方法としては、割り込みのHBlank割り込みを使うことができます。また、現在どのラインを描画中なのかREG_VCOUNT(0~227)を取得することも必要です。~

#br
 4000006h - VCOUNT - Vertical Counter (Read only)
 Indicates the currently drawn scanline, values in range from 160..227 indicate 'hidden' scanlines within VBlank area.
 
   Bit   Expl.
   0-7   Current Scanline (LY)      (0..227)                              (R)
   8     Not used (0) / NDS: MSB of Current Scanline (LY.Bit8) (0..262)   (R)
   9-15  Not Used (0)

具体的にはHBlank中に横方向のスクロール値を変更したりします。~
このことを特に''ラスタースクロール''と言いいます。~
** irq.arm.c [#z401f48d]
まず割り込みできるようにIrqInit関数内のREG_IE、REG_DISPSTATにHBlankフラグを登録をします。IrqHandler関数は実際の割り込み中の処理となります。

ラスタースクロール効果を出すには、HBlank中にBGのスクロール値を変更することが必要です。~
HBlank期間を判定する方法としては、割り込みのHBlank割り込みを使うことができます。~
また、現在描画中のライン番号(0~227)を取得することも必要です。~
これは、I/OレジスタのREG_VCOUNT(0x4000006)の値を取得することでできます。~

** ラスタースクロールの例 [#z401f48d]
 #include "lib/gba.h"
 #include "sintbl.h"
 #include "res.h"
 #include "irq.arm.h"
 #include "math.h"
 
 #define SHAKE_CYCLE		4				// 横揺れの周期
 #define SHAKE_CX		64				// 横振れの幅
 
 //---------------------------------------------------------------------------
 volatile s32 angle;						// 横揺れの1周期内の位置
 // main.c
 extern vs32 shakeAngle;		// 横揺れの1周期内の位置
 extern vs32 shakeCycle;		// 横揺れの周期
 extern vs32 shakeCx;		// 横振れの幅
 
 
 //---------------------------------------------------------------------------
 void WaitForVsync(void)
 EWRAM_CODE void IrqInit(void)
 {
 	while (*(volatile u16*)0x4000006 >= 160) {};
 	while (*(volatile u16*)0x4000006 <  160) {};
 	REG_IME = 0;
 
 	INT_VECTOR   = (u32)IrqHandler;
 	REG_IE       = IRQ_VBLANK | IRQ_HBLANK;
 	REG_DISPSTAT = LCDC_VBL | LCDC_HBL;
 
 	REG_IME = 1;
 }
 //---------------------------------------------------------------------------
 void IrqHandler(void)
 IWRAM_CODE void IrqHandler(void)
 {
 	REG_IME  = 0;
 	u16 flag = REG_IF;
 	REG_IF   = flag;
 
 	if(flag & IRQ_HBLANK)
 	{
 		if(REG_VCOUNT < 160)
 		{
 			// 横方向を揺らす
 			REG_BG0HOFS = GetSin(angle * SHAKE_CYCLE + REG_VCOUNT) * SHAKE_CX / 256;
 			REG_BG1HOFS = MathSin(shakeAngle * shakeCycle + REG_VCOUNT) * shakeCx / 256;
 		}
 		else if(REG_VCOUNT == 227)
 		{
 			// 次のフレームのVCOUNT 0用
 			REG_BG0HOFS = GetSin(angle * SHAKE_CYCLE + 0) * SHAKE_CX / 256;
 			REG_BG1HOFS = MathSin(shakeAngle * shakeCycle + 0) * shakeCx / 256;
 		}
 	}
 
 	if(flag & IRQ_VBLANK)
 	{
 		REG_IRQ_WAITFLAGS |= IRQ_VBLANK;
 	}
 
 	REG_IF  = flag;
 	REG_IME = 1;
 }
       
 // 横方向を揺らす
 REG_BG1HOFS = MathSin(shakeAngle * shakeCycle + REG_VCOUNT) * shakeCx / 256;

そのラインの左右のスクロール値を決定している部分です。MathSin(shakeAngle * shakeCycle + REG_VCOUNT)の部分は、現在のフレーム数を元にsinの値を取得します。サインテーブルは360で1周期となっているので、angleにSHAKE_CYCLEをかけて360で1周期になるようにします。縦の1ラインごとにスクロール値を変更する必要がある(これをしないとラスタースクロールにならない)ので、REG_VCOUNTで現在のライン番号を取得してラインごとにスクロール値を変更しています。* shakeCx / 256はゆれ幅の調整部分です。GetSin()で取得する値は-256~256の範囲なので、-SHAKE_CX~SHAKE_CXの範囲になるように調節します。~

** main.c [#b1e26a1e]
 #include "lib/gba.h"
 #include "irq.arm.h"
 #include "bg.h"
 #include "key.h"
 #include "math.h"
 
 //---------------------------------------------------------------------------
 void IrqSetup(void)
 {
 	REG_IME = 0;
 vs32 shakeAngle;		// 横揺れの1周期内の位置
 vs32 shakeCycle;		// 横揺れの周期
 vs32 shakeCx;			// 横振れの幅
 
 	INT_VECTOR   = (u32)IrqHandler;
 	REG_DISPSTAT = LCDC_HBL;
 	REG_IE       = IRQ_HBLANK;
 
 	REG_IME = 1;
 }
 //---------------------------------------------------------------------------
 int main(void)
 {
 	// モード設定
 	SetMode(MODE_0 | BG0_ENABLE);
 	REG_BG0CNT = (BG_SIZE_0 | BG_256_COLOR | CHAR_BASE(0) | MAP_BASE(28)); 
 	REG_WSCNT = 0x4317;
 
 	u16* pal = BG_PALETTE;			// BGパレット
 	u16* chr = CHAR_BASE_ADR(0);	// キャラクタデータ
 	u16* map = MAP_BASE_ADR(28);	// マップデータ
 	shakeAngle =  0;
 	shakeCycle =  4;
 	shakeCx    = 32;
 
 	u32 i;
 	IrqInit();
 	KeyInit();
 	BgInit();
 
 	// パレット設定
 	for(i=0; i<256; i++)
 	{
 		pal[i] = imagePal[i];
 	}
 	u16 cnt, trg;
 
 	// キャラクタ格納
 	for(i=0; i<(240*160)/2; i++)
 	for(;;)
 	{
 		chr[i] = imageTiles[i];
 	}
 		VBlankIntrWait();
 
 	// マップ格納
 	i = 0;
 	u32 x, y;
 	for(y=0; y<20; y++)
 	{
 		for(x=0; x<30; x++)
 		{
 			map[y*32+x] = i++;
 		}
 	}
 		BgAsciiDrawPrintf(1, 1, "cycle = [%3d]", shakeCycle);
 		BgAsciiDrawPrintf(1, 2, "cx    = [%3d]", shakeCx);
 
 	angle = 0;
 	IrqSetup();
 		KeyExec();
 		cnt = KeyGetCnt();
 		trg = KeyGetTrg();
 
 	for(;;)
 	{
 		WaitForVsync();
 		if(trg & KEY_UP)    { shakeAngle=0; shakeCycle--; }
 		if(trg & KEY_DOWN)  { shakeAngle=0; shakeCycle++; }
 		if(cnt & KEY_LEFT)  { shakeAngle=0; shakeCx--;    }
 		if(cnt & KEY_RIGHT) { shakeAngle=0; shakeCx++;    }
 
 		if(++angle == (360 / SHAKE_CYCLE))
 		shakeCycle = MathClamp(shakeCycle, 1, 32);
 		shakeCx    = MathClamp(shakeCx, 1, 64);
 
 
 		if(shakeAngle == Div(360, shakeCycle))
 		{
 			angle = 0;
 			shakeAngle = 0;
 		}
 		shakeAngle++;
 
 	}
 }

ボタン操作できるので動作を見つつトレースして頂ければ幸いです。

 // 横方向を揺らす
 REG_BG0HOFS = GetSin(angle * SHAKE_CYCLE + REG_VCOUNT) * SHAKE_CX / 256;

そのラインの左右のスクロール値を決定している部分です。~

GetSin(angle * SHAKE_CYCLE + REG_VCOUNT)の部分は、現在のフレーム数を元にsinの値を取得します。~
サインテーブルは360で1周期となっているので、angleにSHAKE_CYCLEをかけて360で1周期になるようにします。~
縦の1ラインごとにスクロール値を変更する必要がある(これをしないとラスタースクロールにならない)ので、~
REG_VCOUNTで現在のライン番号を取得してラインごとにスクロール値を変更しています。~
 * SHAKE_CX / 256
はゆれ幅の調整部分です。~
GetSin()で取得する値は-256~256の範囲なので、-SHAKE_CX~SHAKE_CXの範囲になるように調節します。~


*** 動作画面 [#ued344df]
-[[github:https://github.com/akkera102/gbadev-ja/tree/main/tut21%20%E3%83%A9%E3%82%B9%E3%82%BF%E3%83%BC%E3%82%B9%E3%82%AF%E3%83%AD%E3%83%BC%E3%83%AB]]
#ref(2.png,nolink)


** 履歴 [#u3bf3127]
- 2015/10/07
- 2007/09/08


トップ   一覧 検索 最終更新   ヘルプ   最終更新のRSS