首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

VirtuaNES 模擬器源碼深度解析(二):圖形查看器與 PPU 圖案表渲染原理

     編輯:關於C語言

VirtuaNES 模擬器源碼深度解析(二):圖形查看器與 PPU 圖案表渲染原理

前言

VirtuaNES 是經典的 NES (FC) 模擬器,其源碼展示了 PPU (Picture Processing Unit) 圖形處理的核心邏輯。本文深度解析「圖形查看器」功能,揭示 NES 圖案表、調色板、命名表如何組合生成遊戲畫面。

一、功能入口與窗口創建

1. 菜單響應函數


// MainFrame.cpp
WNDCMD CMainFrame::OnViewCommand(WNDCMDPARAM) {
    if (!Emu.IsRunning() || !Nes) return;
    
    switch (uID) {
        case ID_VIEW_PATTERN:  // 圖形查看器
            if (!m_PatternView.m_hWnd)
                m_PatternView.Create(HWND_DESKTOP);
            ::SetWindowPos(m_PatternView.m_hWnd, HWND_NOTOPMOST, 0,0,0,0, SWP_NOMOVE|SWP_NOSIZE);
            break;
        // ... 其他查看器
    }
}

2. CPatternView 核心類

  • Create():創建窗口,初始化位圖資訊頭,啟動定時器
  • OnCreate():響應 WM_CREATE,調用 SetTimer(1, 50, NULL) 每 50ms 刷新
  • OnTimer():核心渲染邏輯,讀取 PPU 記憶體繪製圖案表

二、位圖資訊頭初始化


// Create() 中
m_BitmapHdr.bih.biSize = sizeof(BITMAPINFOHEADER);
m_BitmapHdr.bih.biWidth = 128;      // 圖像寬度
m_BitmapHdr.bih.biHeight = -256;    // 負值表示自上而下 DIB
m_BitmapHdr.bih.biPlanes = 1;
m_BitmapHdr.bih.biBitCount = 8;     // 8位色,256色模式
m_BitmapHdr.bih.biCompression = BI_RGB;
m_BitmapHdr.bih.biClrUsed = 16;     // 實際使用 16 色調色板

三、NES PPU 圖形記憶體架構

1. 三大核心表

表類型大小內容作用
圖案表4KB (兩個各 4KB)256 個 8×8 圖塊角色/背景圖形像素數據(每像素 2 位)
命名表960 字節32×30 圖塊索引背景佈局,每字節指向一個圖塊
屬性表64 字節每 4×4 圖塊共享調色板為圖塊提供高 2 位色彩數據

2. 調色板機制

  • 背景調色板 (BGPAL) + 精靈調色板 (SPPAL),各 16 字節
  • 每字節 = 索引值 (0-255),指向系統 256 色調色板中的一色
  • 畫面同時只能顯示 16+16=32 種顏色(背景 13 + 精靈 13 + 通用背景色 1)

四、OnTimer 核心渲染邏輯深度解析

1. 調色板選擇與映射


// m_SelectPal: 0-7,點擊窗口切換
LPBYTE pPAL = (m_SelectPal < 4) ? 
    &BGPAL[m_SelectPal * 4] : &SPPAL[(m_SelectPal & 3) * 4];

// 將 4 個調色板索引映射到系統 256 色
m_BitmapHdr.rgb[0] = m_Palette[pPAL[0]];
m_BitmapHdr.rgb[1] = m_Palette[pPAL[1]];
m_BitmapHdr.rgb[2] = m_Palette[pPAL[2]];
m_BitmapHdr.rgb[3] = m_Palette[pPAL[3]];

每幅圖案表圖像僅使用 4 種顏色(2 位像素 + 2 位屬性)。

2. 圖案表像素解碼核心算法


// m_lpPattern: 目標像素緩衝區 (128×256)
// PPU_MEM_BANK[0..7]: 8 個 1KB 圖案表銀行指標
for (INT i = 0; i < 8; i++) {
    if (m_lpBank[i] != PPU_MEM_BANK[i] || PPU_MEM_TYPE[i] == BANKTYPE_CRAM) {
        m_lpBank[i] = PPU_MEM_BANK[i];
        LPBYTE lpPtn = PPU_MEM_BANK[i];
        
        // 每銀行 64 個圖塊 (256/4)
        for (INT p = 0; p < 64; p++) {
            // 計算目標圖塊在 128×256 緩衝區中的位置
            LPBYTE lpScn = &m_lpPattern[i*32*128 + (p&15)*8 + (p/16)*8*128];
            
            // 每圖塊 8 行
            for (INT y = 0; y < 8; y++) {
                BYTE chr_l = lpPtn[y];      // 低位平面
                BYTE chr_h = lpPtn[y + 8];  // 高位平面
                
                // 位運算解碼:每字節 8 像素,高低位平面組合成 2 位色值
                lpScn[0] = ((chr_h >> 6) & 2) | ((chr_l >> 7) & 1);
                lpScn[1] = ((chr_h >> 5) & 2) | ((chr_l >> 6) & 1);
                lpScn[2] = ((chr_h >> 4) & 2) | ((chr_l >> 5) & 1);
                lpScn[3] = ((chr_h >> 3) & 2) | ((chr_l >> 4) & 1);
                lpScn[4] = ((chr_h >> 2) & 2) | ((chr_l >> 3) & 1);
                lpScn[5] = ((chr_h >> 1) & 2) | ((chr_l >> 2) & 1);
                lpScn[6] = ((chr_h >> 0) & 2) | ((chr_l >> 1) & 1);
                lpScn[7] = ((chr_h << 1) & 2) | ((chr_l >> 0) & 1);
                
                lpScn += 128;  // 下一行 (圖像寬度 128)
            }
            lpPtn += 16;  // 下一圖塊 (16 字節 = 8低位 + 8高位)
        }
    }
}

3. 像素解碼邏輯圖解

圖案表存儲格式 (16 字節/圖塊):
字節 0-7  : 低位平面 (bit 7-0 → 像素 0-7 低位)
字節 8-15 : 高位平面 (bit 7-0 → 像素 0-7 高位)

像素色值 = (高位 << 1) | 低位  → 0,1,2,3 對應調色板 4 色

解碼優化:原代碼打亂 0-7 順序,實際邏輯為按位順序提取
正確順序應為:
lpScn[0] = 高bit7<<1 | 低bit7
lpScn[1] = 高bit6<<1 | 低bit6
...
lpScn[7] = 高bit0<<1 | 低bit0

五、關鍵數據結構

變量類型含義
PPU_MEM_BANK[12]LPBYTE*前 8 個指向圖案表,後 4 個指向命名/屬性表
PPU_MEM_TYPE[12]int銀行類型標識 (BANKTYPE_CRAM 等)
m_lpBank[8]LPBYTE快取上次圖案表銀行指標,檢測變化
m_lpPatternLPBYTE128×256 像素緩衝區,每字節一像素 (0-3)
m_Palette[1024]BYTE256 色 × 4 字節 (RGBA) 系統調色板

六、總結與延伸

  1. 圖案表分銀行:NES 將 4KB 圖案表分為 8 個 1KB 銀行,靈活映射
  2. 位平面分離:高低位分離存儲是 8 位機時代節省邏輯電路的經典設計
  3. 動態更新:OnTimer 每 50ms 檢測銀行變化,僅重繪變更部分
  4. 下一步:命名表查看器將展示如何根據圖塊索引+屬性表合成完整背景畫面

通過分析 VirtuaNES 圖形查看器,可見 NES PPU 圖形系統的精妙設計:在極其有限的硬體資源下(2KB VRAM + 256 色調色板),通過圖案表、命名表、屬性表、調色板四級間接寻址,實現了豐富的圖形顯示能力。這種「間接寻址 + 位平面分離」的思想,對理解現代 GPU 纹理採樣、瓦片渲染同樣具有啟發意義。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved