D3D9에서 월핵 같은 기능을 구현할 때 핵심적으로 다루는 것이 바로 Z-Buffer(Depth Buffer) 입니다.
Z-Buffer는 3D 그래픽에서 어떤 픽셀이 화면에 보일지(가려지는지)를 결정하는 데 쓰입니다.
Z-Buffer(Depth Buffer)의 개념
렌더링 과정에서의 Z-Buffer 동작
이렇게 해서 앞에 있는 물체가 보이고, 뒤에 있는 물체는 가려지는 효과가 생깁니다.
월핵과 Z-Buffer의 관계
월핵은 본래 벽 뒤에 있는 적 모델도 보이게 하는 기능의 핵 입니다. 이를 위해서는 Z-Buffer의 깊이 테스트(Depth Test)를 우회하거나 조작해야 합니다.
Z-Buffer Write 비활성화:
device->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
→ 깊이 값을 갱신하지 않으므로, 뒤쪽에 있는 모델도 화면에 덮어써짐.
Depth Test 우회:
device->SetRenderState(D3DRS_ZFUNC, D3DCMP_ALWAYS);
→ 깊이 비교를 항상 통과시켜 벽 뒤의 모델도 그려짐.
정리
아래는 위의 원리로 구현했을 때 어떤식으로 월핵이 동작하는지 보여주는 코드입니다.
이것은 내가 게임의 개발자일 때 적용하는 방법이며, 실제 온라인게임이나 특정 게임에 적용하기 위해서는 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;
}
제목 | 작성자 | 작성일 | 조회 | |
---|---|---|---|---|
공지 | 08-28 | 141 | ||
공지 | 01-29 | 843 |
댓글0