서버 배너
서버 프로필
쉽게 배우는 게임 해킹
매니저리버서멤버 400

참스 편 무료 맛보기

조회수 140 수정일 2025.08.28 10:33:47

D3D9에서 월핵 같은 기능을 구현할 때 핵심적으로 다루는 것이 바로 Z-Buffer(Depth Buffer) 입니다.
Z-Buffer는 3D 그래픽에서 어떤 픽셀이 화면에 보일지(가려지는지)를 결정하는 데 쓰입니다. 

 

20250828102622_b3706283c401aef8b668a6a2e762e907_fu46.png

 

Z-Buffer(Depth Buffer)의 개념

  • Z-Buffer는 화면의 각 픽셀이 카메라로부터 얼마나 떨어져 있는지(깊이 값, Z 값)를 저장하는 버퍼입니다.
  • 기본 구조는 렌더링 해상도와 동일한 2차원 배열이며, 각 픽셀마다 깊이 값이 들어갑니다.
  • 일반적으로 0.0f는 카메라에 가장 가까움, 1.0f는 가장 멀리 있는 값으로 정규화되어 저장됩니다.

 

렌더링 과정에서의 Z-Buffer 동작

  1. 삼각형을 그릴 때: 각 픽셀에 해당하는 깊이 값(Z)이 계산됩니다.
  2. 깊이 비교: 새로 그리려는 픽셀의 깊이(Z_new)와 Z-Buffer에 이미 저장된 깊이(Z_old)를 비교합니다.
    • Z_new < Z_old → 새 픽셀이 더 가까움 → 픽셀 색상 갱신 + Z-Buffer 갱신
    • Z_new >= Z_old → 기존 픽셀이 더 가까움 → 그리기 무시 (즉, 뒤에 있는 물체는 가려짐)
  3. 이렇게 해서 앞에 있는 물체가 보이고, 뒤에 있는 물체는 가려지는 효과가 생깁니다.

     

월핵과 Z-Buffer의 관계

 

월핵은 본래 벽 뒤에 있는 적 모델도 보이게 하는 기능의 핵 입니다. 이를 위해서는 Z-Buffer의 깊이 테스트(Depth Test)를 우회하거나 조작해야 합니다.

  • Z-Buffer Write 비활성화:

    device->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);

    → 깊이 값을 갱신하지 않으므로, 뒤쪽에 있는 모델도 화면에 덮어써짐.

  • Depth Test 우회:

    device->SetRenderState(D3DRS_ZFUNC, D3DCMP_ALWAYS);

    → 깊이 비교를 항상 통과시켜 벽 뒤의 모델도 그려짐.

  • 두 번 렌더링 기법 (Chams 등과 결합):
    1. Z-Buffer를 무시하고 모델을 밝은 색(예: 빨강, 파랑)으로 먼저 렌더 → 벽 뒤 실루엣 표시
    2. 정상 Z-Test로 다시 렌더링 → 벽 앞에 있을 경우 정상 색으로 표시

정리

  • Z-Buffer는 3D 공간에서 가려짐(occlusion)을 구현하는 핵심 버퍼.
  • 픽셀마다 깊이 값을 저장하고, 가까운 픽셀만 화면에 남깁니다.
  • 월핵은 이 과정을 무시하거나 조작해서 벽 뒤의 오브젝트도 강제로 화면에 보이게 만듭니다.
  • Direct3D9에서는 D3DRS_ZENABLE, D3DRS_ZWRITEENABLE, D3DRS_ZFUNC 같은 RenderState 설정을 건드려 구현합니다.

 

 

아래는 위의 원리로 구현했을 때 어떤식으로 월핵이 동작하는지 보여주는 코드입니다.
이것은 내가 게임의 개발자일 때 적용하는 방법이며, 실제 온라인게임이나 특정 게임에 적용하기 위해서는 DLL 인젝션을 통한 d3d vTable 후킹이 필요합니다.

다음 강의부터 차근차근 설명 드리도록 하겠습니다.


#include <d3d9.h>
#include <d3dx9.h>
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")

HWND                     g_hwnd = nullptr;
LPDIRECT3D9              g_d3d = nullptr;
LPDIRECT3DDEVICE9        g_dev = nullptr;
LPD3DXMESH               g_teapot = nullptr;
LPDIRECT3DVERTEXBUFFER9  g_wallVB = nullptr;

struct V { float x,y,z; DWORD c; float u,v; };
#define FVF (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1)

LRESULT CALLBACK WndProc(HWND h, UINT m, WPARAM w, LPARAM l) {
    if(m == WM_DESTROY){ PostQuitMessage(0); return 0; }
    return DefWindowProcW(h, m, w, l);
}

bool InitD3D(HWND hwnd){
    g_d3d = Direct3DCreate9(D3D_SDK_VERSION);
    if(!g_d3d) return false;

    D3DPRESENT_PARAMETERS pp = {};
    pp.Windowed = TRUE;
    pp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    pp.BackBufferFormat = D3DFMT_UNKNOWN;
    pp.EnableAutoDepthStencil = TRUE;
    pp.AutoDepthStencilFormat = D3DFMT_D24S8; // Z-Stencil

    if(FAILED(g_d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd,
        D3DCREATE_HARDWARE_VERTEXPROCESSING, &pp, &g_dev))) return false;

    // 기본 상태
    g_dev->SetRenderState(D3DRS_ZENABLE, TRUE);
    g_dev->SetRenderState(D3DRS_LIGHTING, FALSE);
    g_dev->SetFVF(FVF);

 
    D3DXCreateTeapot(g_dev, &g_teapot, nullptr);

    // "벽"을 카메라 앞에 두는 큰 사각형(정사영 전제)
    g_dev->CreateVertexBuffer(6*sizeof(V), 0, FVF, D3DPOOL_MANAGED, &g_wallVB, nullptr);
    V* v = nullptr; g_wallVB->Lock(0,0,(void**)&v,0);
    float z = 5.0f; // 카메라 앞쪽 z
    DWORD gray = D3DCOLOR_ARGB(255, 180, 180, 180);
    // 두 삼각형으로 직사각형 구성 (대략 화면 가득)
    v[0] = {-10.f, -10.f, z, gray, 0,0};
    v[1] = {-10.f,  10.f, z, gray, 0,1};
    v[2] = { 10.f,  10.f, z, gray, 1,1};
    v[3] = {-10.f, -10.f, z, gray, 0,0};
    v[4] = { 10.f,  10.f, z, gray, 1,1};
    v[5] = { 10.f, -10.f, z, gray, 1,0};
    g_wallVB->Unlock();

    // 카메라 / 투영
    D3DXMATRIX Vw, Pr;
    D3DXVECTOR3 eye(0,0,-10), at(0,0,0), up(0,1,0);
    D3DXMatrixLookAtLH(&Vw, &eye, &at, &up);
    D3DXMatrixPerspectiveFovLH(&Pr, D3DX_PI/4, 16.f/9.f, 0.1f, 100.f);
    g_dev->SetTransform(D3DTS_VIEW, &Vw);
    g_dev->SetTransform(D3DTS_PROJECTION, &Pr);

    return true;
}

void DrawWall(){
    g_dev->SetStreamSource(0, g_wallVB, 0, sizeof(V));
    g_dev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);
}

void DrawTeapot(DWORD color, const D3DXVECTOR3& pos, float rotY){
    D3DXMATRIX T,R,S;
    D3DXMatrixTranslation(&T, pos.x, pos.y, pos.z);
    D3DXMatrixRotationY(&R, rotY);
    D3DXMatrixScaling(&S, 1.0f, 1.0f, 1.0f);
    D3DXMATRIX W = S * R * T;
    g_dev->SetTransform(D3DTS_WORLD, &W);

    D3DMATERIAL9 m = {}; 
    m.Diffuse = m.Ambient = D3DXCOLOR(
        ((color>>16)&0xFF)/255.f, ((color>>8)&0xFF)/255.f, (color&0xFF)/255.f, 1.0f);
    g_dev->SetMaterial(&m);

    g_teapot->DrawSubset(0);
}

void Render(){
    static float t = 0.f; t += 0.01f;
    g_dev->Clear(0,0, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(30,30,40), 1.0f, 0);
    if(SUCCEEDED(g_dev->BeginScene())){
        // 1) "벽"을 정상 Z-Test로 먼저 렌더 (깊이 버퍼에 값 기록)
        g_dev->SetRenderState(D3DRS_ZWRITEENABLE, TRUE);
        g_dev->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL);
        DrawWall();


        IDirect3DStateBlock9* sb = nullptr;
        g_dev->CreateStateBlock(D3DSBT_ALL, &sb);

        // 2) 티팟 1패스: 벽 뒤 실루엣용(월핵/Chams 패스)
        //    깊이 비교 항상 통과 + 깊이 기록 금지 → 벽에 가려져도 그려짐
        g_dev->SetRenderState(D3DRS_ZFUNC, D3DCMP_ALWAYS);
        g_dev->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
        DrawTeapot(D3DCOLOR_XRGB(255, 64, 64), D3DXVECTOR3(0, -1, 8), t); // 붉은 실루엣

        // 3) 티팟 2패스: 정상 렌더(앞에 있으면 원래 색으로 보이게)
        sb->Apply(); // Z 상태 복구 (LESSEQUAL, ZWRITEENABLE=TRUE 등)
        DrawTeapot(D3DCOLOR_XRGB(200, 200, 255), D3DXVECTOR3(0, -1, 8), t);

        if(sb) sb->Release();
        g_dev->EndScene();
    }
    g_dev->Present(nullptr,nullptr,nullptr,nullptr);
}

void Cleanup(){
    if(g_wallVB) { g_wallVB->Release(); g_wallVB=nullptr; }
    if(g_teapot){ g_teapot->Release(); g_teapot=nullptr; }
    if(g_dev){ g_dev->Release(); g_dev=nullptr; }
    if(g_d3d){ g_d3d->Release(); g_d3d=nullptr; }
}

int APIENTRY wWinMain(HINSTANCE h, HINSTANCE, LPWSTR, int){
    WNDCLASSW wc = { CS_OWNDC, WndProc, 0,0, GetModuleHandleW(nullptr), nullptr, LoadCursor(nullptr, IDC_ARROW),
                     (HBRUSH)(COLOR_WINDOW+1), nullptr, L"D3D9WH" };
    RegisterClassW(&wc);
    g_hwnd = CreateWindowW(L"D3D9WH", L"D3D9 Depth/Wallhack Demo (Educational)",
                           WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 1280, 720,
                           nullptr, nullptr, wc.hInstance, nullptr);
    ShowWindow(g_hwnd, SW_SHOWDEFAULT);

    if(!InitD3D(g_hwnd)) return 0;

    MSG msg = {};
    while(msg.message != WM_QUIT){
        if(PeekMessage(&msg, nullptr, 0,0, PM_REMOVE)){
            TranslateMessage(&msg); DispatchMessage(&msg);
        }else{
            Render();
        }
    }
    Cleanup();
    return 0;
}

댓글0

강의 맛보기 (무료)

게시글 리스트
제목작성자작성일조회
공지08-28141
공지01-29843