FPS 游戏实现D3D透视
FPS游戏可以说一直都比较热门,典型的代表有反恐精英,穿越火线,绝地求生等,基本上只要是FPS游戏都会有透视挂的存在,而透视挂还分为很多种类型,常见的有D3D透视,方框透视,还有一些比较高端的显卡透视,本教程将学习D3D透视的实现原理,并通过DLL注入的方式实现透视。
反恐精英下载地址:链接:https://pan.baidu.com/s/1U4-E9-xNIoHOyLg5aP_l7w 提取码:yupq
DX9 SDK精简版:链接:https://pan.baidu.com/s/1SUufWoizbpZL1ki85J1zbA 提取码:u1ak
Direct3D 透视是一种主流的透视方式,因为现如今大部分游戏都会使用Dx9图形接口,那么我们该如何实现D3D透视?
在D3D中普遍会使用深度缓存区(Depth Buffer)
来进行消隐处理,通过使用Z轴深度缓存即可实现将人物被遮挡的部分不被显示出来,而我们的目的就是要让它强制显示出来,D3D的核心功能主要集成在COM组件中,只要Hook其中EndScence(), DrawPrimitive(),DrawIndexedPrimitive()
函数就可以感知游戏的绘图操作,然后通过调用SetRenderState()
渲染函数,改变其中的渲染参数即可实现不同的透视效果。
为了确保能够正常的编译代码,请自行配置好 Direct3D 9 SDK 和 VS 系列开发环境,过程中使用了 x64dbg,DBGview工具,我这里还是使用CS起源作为演示对象吧,电脑上没别的游戏。
SetWindowHookEx 全局注入
SetWindowHookEx 函数可以将一个Dll强行插入到系统的每个进程里,因为是全局注入,所以该方法可注入到具有保护的游戏中,首先我们需要创建一个Dll工程 hook.cpp
然后将SetHook方法导出,在DllMain中进行了判断,如果窗口句柄为valve001则弹出一个消息框,其他进程直接跳过,即可实现指定进程注入。
#include <windows.h>
HHOOK global_hook;
LRESULT CALLBACK MyProc(int nCode, WPARAM wParam, LPARAM lParam)
{
return CallNextHookEx(global_hook, nCode, wParam, lParam);
}
extern "C" __declspec(dllexport) void SetHook()
{
global_hook = SetWindowsHookEx(WH_CBT, MyProc, GetModuleHandle(TEXT("hook.dll")), 0);
}
extern "C" __declspec(dllexport) void UnHook()
{
if (global_hook) UnhookWindowsHookEx(global_hook);
}
bool APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid)
{
HWND hwnd = FindWindowW(L"valve001",NULL);
DWORD pid;
GetWindowThreadProcessId(hwnd, &pid);
if (GetCurrentProcessId() == pid)
{
MessageBox(hwnd, TEXT("inject"), 0, 0);
}
return true;
}
调用代码如下,注意必须将上方编译好的hook.dll与下方工程放到同一个目录下,通过LoadLibrary函数获取到模块句柄,然后通过GetProcAddress获取到导出函数地址,并通过函数指针调用。
#include <windows.h>
int main()
{
HMODULE hMod = LoadLibrary(TEXT("hook.dll"));
typedef void(*pSetHook)(void);
pSetHook SetHook = (pSetHook)GetProcAddress(hMod, "SetHook");
SetHook();
while (1)
{
Sleep(1000);
}
return 0;
}
计算 DrawIndexedPrimitive 偏移
我们需要找到 DrawIndexedPrimitive 这个渲染函数并 Hook 这个函数,但 DrawIndexedPrimitive 函数与其他普通API函数不同,由于 DirectX 的功能都是以COM组件的形式提供的类函数,所以普通的Hook无法搞它,我这里的思路是,自己编写一个D3D绘图案例,在源码中找到 DrawIndexedPrimitive 函数并设置好断点,通过VS调试单步执行找到函数的所在模块的地址,并与d3d9.dll的基址相减得到相对偏移地址。
#include <windows.h>
#include<tchar.h>
#include<d3d9.h>
#pragma comment( lib, "d3d9.lib")
#define null NULL
#define RETURN return
LPDIRECT3D9 g_pD3D = NULL;
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;
LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;
struct CUSTOMVERTEX
{
float x, y, z, rhw;
DWORD color;
};
#define FVF ( D3DFVF_XYZRHW | D3DFVF_DIFFUSE )
HRESULT InitD3D(HWND hWnd)
{
g_pD3D = Direct3DCreate9(D3D_SDK_VERSION);
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = TRUE;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING,&d3dpp, &g_pd3dDevice);
return S_OK;
}
HRESULT InitVB()
{
CUSTOMVERTEX v[] =
{
100, 000, 0, 1, 0xffff0000,
300, 50, 0, 1, 0xff00ff00,
500, 400, 0, 1, 0xff0000ff
};
g_pd3dDevice->CreateVertexBuffer(3 * sizeof(v), 0, FVF, D3DPOOL_DEFAULT, &g_pVB, 0);
void* vb;
g_pVB->Lock(0, 0, (void**)&vb, 0);
memcpy(vb, v, sizeof(v));
g_pVB->Unlock();
return S_OK;
}
void Render()
{
g_pd3dDevice->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(255, 255, 0), 1, 0);
g_pd3dDevice->BeginScene();
g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(CUSTOMVERTEX));
g_pd3dDevice->SetFVF(FVF);
//g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 10);
g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 4, 0, 4);
g_pd3dDevice->EndScene();
g_pd3dDevice->Present(0, 0, 0, 0);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
message == WM_CLOSE ? PostQuitMessage(0) : 0;
return DefWindowProc(hWnd, message, wParam, lParam);
}
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE, PTSTR, int)
{
wchar_t cn[] = L"ClassName";
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = cn;
RegisterClass(&wc);
HWND hWnd = CreateWindow(cn, 0, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, SW_SHOW);
InitD3D(hWnd);
InitVB();
MSG msg;
ZeroMemory(&msg, sizeof(msg));
while (msg.message != WM_QUIT)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
Render();
}
}
return 0;
}
首先我们直接在VS中运行自己的工程(这样的例子有很多),然后在源代码中找到 DrawIndexedPrimitive
并下一个【F9】断点,然后直接运行程序,发现程序断下后直接按下【Alt + 8】切到反汇编窗口。
函数调用:g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 4, 0, 4);
00D01853 8B F4 mov esi,esp
00D01855 6A 04 push 4
00D01857 6A 00 push 0
00D01859 6A 04 push 4
00D0185B 6A 00 push 0
00D0185D 6A 00 push 0
00D0185F 6A 04 push 4
00D01861 A1 44 91 D0 00 mov eax,dword ptr ds:[00D09144h]
00D01866 8B 08 mov ecx,dword ptr [eax]
00D01868 8B 15 44 91 D0 00 mov edx,dword ptr ds:[0D09144h]
00D0186E 52 push edx
00D0186F 8B 81 48 01 00 00 mov eax,dword ptr [ecx+148h]
00D01875 FF D0 call eax
00D01877 3B F4 cmp esi,esp
00D01879 E8 EF F8 FF FF call __RTC_CheckEsp (0D0116Dh)
上方的代码就是你在VS中看到的代码片段,该代码片段就是调用 DrawIndexedPrimitive
函数的初始化工作,可以明显的看出压栈了6条数据,最后调用了 call eax
我们直接在单步【F9】走到00D01875地址处并按下【F11】进入到CALL的内部,可看到以下代码片段,我们需要记下片段中的 6185CD20
这个地址。
6185CD20 8B FF mov edi,edi
6185CD22 55 push ebp
6185CD23 8B EC mov ebp,esp
6185CD25 6A FF push 0FFFFFFFFh
6185CD27 68 C8 49 87 61 push 618749C8h
6185CD2C 64 A1 00 00 00 00 mov eax,dword ptr fs:[00000000h]
6185CD32 50 push eax
6185CD33 83 EC 20 sub esp,20h
6185CD36 53 push ebx
6185CD37 56 push esi
6185CD38 57 push edi
6185CD39 A1 70 62 95 61 mov eax,dword ptr ds:[61956270h]
上方的起始地址 6185CD20 经常会变化,所以我们需要找到当前 d3d9.dll 模块的基址,通过X64DBG获取到的基址是61800000
通过当前地址减去模块基址 6185CD20 - 61800000
得到相对偏移地址5CD20
,此时我们就可以通过 d3d9.dll + 5CD20 来动态的计算出这个变化的地址,编程实现的代码片段如下:
#include <windows.h>
HHOOK global_hook;
LRESULT CALLBACK MyProc(int nCode, WPARAM wParam, LPARAM lParam)
{
return CallNextHookEx(global_hook, nCode, wParam, lParam);
}
extern "C" __declspec(dllexport) void SetHook()
{
global_hook = SetWindowsHookEx(WH_CBT, MyProc, GetModuleHandle(TEXT("hook.dll")), 0);
}
ULONG_PTR GetDrawIndexedPrimitiveAddr()
{
HANDLE handle = GetModuleHandle(TEXT("d3d9.dll"));
if (handle == INVALID_HANDLE_VALUE) return NULL;
return(ULONG_PTR)handle + 0x5cd20;
}
bool APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid)
{
HWND hwnd = FindWindowW(L"Valve001", NULL);
DWORD pid;
GetWindowThreadProcessId(hwnd, &pid);
if (GetCurrentProcessId() == pid)
{
ULONG_PTR temp = GetDrawIndexedPrimitiveAddr();
MessageBox(hwnd, (LPCWSTR)temp,0,0);
}
return true;
}
将这个DLL注入到游戏,即可获取到模块基址,此处编码问题显示有问题,不过已经可以获取到了。
劫持 DrawIndexedPrimitive 函数
劫持 DrawIndexedPrimitive 函数就可以感知绘图操作,其实这里就是 API Hook 首先我们使用 VirtualProtect()
函数将我们需要填充的内存设置为可读写可执行权限,接着直接使用 jmp (远跳转)
指令替换掉系统领空中的 DrawIndexedPrimitive 函数的前5个字节,然后让其跳转到我们的 hook.dll
模块中的 MyDrawIndexedPrimitive
执行我们自己的绘图过程,执行完毕以后直接通过 Transfer_DrawIndexedPrimitive
中转函数跳转回程序领空中,即可完成 D3D的函数劫持。
我们需要Hook该函数,并跳转到我们自己的函数中,为了保证调用堆栈的平衡,我们需要确保自己的函数参数应和系统函数参数相等,如下是DrawIndexedPrimitive函数的原型定义。
STDMETHOD(DrawIndexedPrimitive)(
THIS_ D3DPRIMITIVETYPE,
INT BaseVertexIndex,
UINT MinVertexIndex,
UINT NumVertices,
UINT startIndex,
UINT primCount
)
从上方的定义上,可以看出一共传递了6个参数,这里需要注意,由于该函数是类函数,在调用时需要传递自身指针(pdevice ->DrawIndexedPrimitive()),所以我们还需要加上一个自身指针,完整声明应该如下:
pdevice = LPDIRECT3DDEVICE9 pDevice
HRESULT DrawIndexedPrimitive(
[in] LPDIRECT3DDEVICE9 pDevice, // 设备指针
[in] D3DPRIMITIVETYPE Type, // 图元类型
[in] INT BaseVertexIndex, // 起始顶点索引
[in] UINT MinIndex, // 最小顶点索引
[in] UINT NumVertices, // 顶点数量
[in] UINT StartIndex, // 起始索引
[in] UINT PrimitiveCount // 图元数量
)
上方我们既然知道了声明方式,那么我们就可以制作自己的中转函数Transfer_DrawIndexedPrimitive
以及自己的MyDrawIndexedPrimitive
函数了,代码片段如下,需要注意调用约定:
__declspec(naked) HRESULT __stdcall Transfer_DrawIndexedPrimitive(LPDIRECT3DDEVICE9 m_pDevice,
D3DPRIMITIVETYPE type,
INT BaseVertexIndex,
UINT MinVertexIndex,
UINT NumVertices,
UINT startIndex,
UINT primCount)
{
__asm{
mov edi, edi
push ebp
mov ebp, esp
mov eax, jump
jmp eax
}
}
HRESULT __stdcall MyDrawIndexedPrimitive(LPDIRECT3DDEVICE9 m_pDevice,
D3DPRIMITIVETYPE type,
INT BaseVertexIndex,
UINT MinVertexIndex,
UINT NumVertices,
UINT startIndex,
UINT primCount)
{
OutputDebugStringA("执行我自己的函数,中转函数\r\n");
return Transfer_DrawIndexedPrimitive(m_pDevice, type, BaseVertexIndex, MinVertexIndex, NumVertices, startIndex, primCount);
}
接着公布下Hook函数的代码,下方我们通过内联汇编进行了跳转的链接,构成了一个完整的Hook链。
bool HookDrawIndexedPrimitive()
{
ULONG_PTR address = GetDrawIndexedPrimitiveAddr();
DWORD oldProtect = 0;
if (VirtualProtect((LPVOID)address, 5, PAGE_EXECUTE_READWRITE, &oldProtect)) // 设置内存保护方式为可读写
{
DWORD value = (DWORD)MyDrawIndexedPrimitive - address - 5; // 计算出需要跳转字节
jump = address + 5; // 计算下一个跳转字节
__asm
{
mov eax, address
mov byte ptr[eax],0xe9 // 填充为 jmp
add eax,1 // 指针递增
mov ebx,value // 中转
mov dword ptr[eax],ebx // 赋值跳转地址(远跳转)
}
VirtualProtect((LPVOID)address, 5, oldProtect, &oldProtect); // 恢复内存保护方式
}
return true;
}
最终完整代码如下所示:
#include <windows.h>
#include <d3d9.h>
#pragma comment(lib, "d3d9.lib")
HHOOK global_hook;
DWORD jump = 0;
LRESULT CALLBACK MyProc(int nCode, WPARAM wParam, LPARAM lParam)
{
return CallNextHookEx(global_hook, nCode, wParam, lParam);
}
extern "C" __declspec(dllexport) void SetHook()
{
global_hook = SetWindowsHookEx(WH_CBT, MyProc, GetModuleHandle(TEXT("hook.dll")), 0);
}
__declspec(naked) HRESULT __stdcall Transfer_DrawIndexedPrimitive(LPDIRECT3DDEVICE9 m_pDevice,
D3DPRIMITIVETYPE type,
INT BaseVertexIndex,
UINT MinVertexIndex,
UINT NumVertices,
UINT startIndex,
UINT primCount)
{// 中转函数,执行被我们填充后的指令片段,并跳转到原始指令的后面继续执行
__asm{
mov edi, edi
push ebp
mov ebp, esp
mov eax, jump
jmp eax
}
}
HRESULT __stdcall MyDrawIndexedPrimitive(LPDIRECT3DDEVICE9 m_pDevice,
D3DPRIMITIVETYPE type,
INT BaseVertexIndex,
UINT MinVertexIndex,
UINT NumVertices,
UINT startIndex,
UINT primCount)
{// 在此函数中DIY添加功能(例如:绘制菜单)
OutputDebugStringA("执行我自己的函数,中转函数\r\n");
return Transfer_DrawIndexedPrimitive(m_pDevice, type, BaseVertexIndex, MinVertexIndex, NumVertices, startIndex, primCount);
}
ULONG_PTR GetDrawIndexedPrimitiveAddr()
{
HANDLE handle = GetModuleHandle(TEXT("d3d9.dll")); // 获得d3d9.dll模块基址
if (handle == INVALID_HANDLE_VALUE) return NULL;
return(ULONG_PTR)handle + 0x5cd20; // 相加偏移
}
bool HookDrawIndexedPrimitive()
{
ULONG_PTR address = GetDrawIndexedPrimitiveAddr();
DWORD oldProtect = 0;
if (VirtualProtect((LPVOID)address, 5, PAGE_EXECUTE_READWRITE, &oldProtect)) // 设置内存保护方式为可读写
{
DWORD value = (DWORD)MyDrawIndexedPrimitive - address - 5; // 计算出需要跳转字节
jump = address + 5; // 计算下一个跳转字节
__asm
{
mov eax, address
mov byte ptr[eax],0xe9 // 填充为 jmp
add eax,1 // 指针递增
mov ebx,value // 中转
mov dword ptr[eax],ebx // 赋值跳转地址(远跳转)
}
VirtualProtect((LPVOID)address, 5, oldProtect, &oldProtect); // 恢复内存保护方式
}
return true;
}
bool APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid)
{
HWND hwnd = FindWindowW(L"valve001", NULL);
DWORD pid;
GetWindowThreadProcessId(hwnd, &pid);
if (GetCurrentProcessId() == pid)
{
HookDrawIndexedPrimitive();
}
return true;
}
将代码编译为 hook.dll 并使用前面提到过的SetWindowHook方法注入游戏,注入后发现已经成功劫持,并且游戏没有崩溃说明我们的Hook中转正常,如果出现错误多半是代码没有衔接完整。
我们通过X64DBG附加游戏进程,可以观察到模块已经注入成功了,我们将 d3d9.dll + 5cd20 = 5B50CD20
X64DBG直接跟一下这个地址,观察我们写入的情况,发现一个远指针(远跳转)
在 jmp hook.5D391122 地址处继续跟进,既可以看到我们自己的中转函数了。
找人物模型ID号
简单的模型过滤:
HRESULT __stdcall MyDrawIndexedPrimitive(LPDIRECT3DDEVICE9 m_pDevice, D3DPRIMITIVETYPE type, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT startIndex, UINT primCount)
{
HRESULT Result = S_OK;
IDirect3DVertexBuffer9 *pStreamData = NULL;
UINT iOffsetInBytes, iStride;
if (m_pDevice->GetStreamSource(0, &pStreamData, &iOffsetInBytes, &iStride) == D3D_OK) pStreamData->Release(); // 得到模型来源
if (iStride == 200) // 得到来源为200的时候,才会渲染
{
Result = Transfer_DrawIndexedPrimitive(m_pDevice, type, BaseVertexIndex, MinVertexIndex, NumVertices, startIndex, primCount);
}
return Result;
}
添加虚拟键位: 创建并添加虚拟键位,按下上光标键模型序号加2,按下下光标键模型序号减2,进入游戏以后按下上光标键,观察游戏的反应,如果人物消失了,就是我们要找的人物ID号。
WNDPROC Global_OldProc = NULL;
DWORD Fvalue = 0;
HRESULT __stdcall MyDrawIndexedPrimitive(LPDIRECT3DDEVICE9 m_pDevice, D3DPRIMITIVETYPE type, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT startIndex, UINT primCount)
{
HRESULT Result = S_FALSE;
IDirect3DVertexBuffer9 *pStreamData = NULL;
UINT iOffsetInBytes, iStride;
if (m_pDevice->GetStreamSource(0, &pStreamData, &iOffsetInBytes, &iStride) == D3D_OK) pStreamData->Release(); // 得到模型来源
if (iStride != Fvalue) // 当来源不等于Fvalue时,就渲染,否则直接去除
{
Result = Transfer_DrawIndexedPrimitive(m_pDevice, type, BaseVertexIndex, MinVertexIndex, NumVertices, startIndex, primCount);
}
return Result;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (uMsg == WM_KEYDOWN)
{
if (wParam = VK_UP) // 按下上光标将,我们让模型编号加2
{
Fvalue += 2;
}
if (wParam == VK_DOWN) // 按下下光标键,我们让我们让模型编号减2
{
Fvalue -= 2;
}
}
return CallWindowProc(Global_OldProc, hwnd, uMsg, wParam, lParam); // 全局热键回调函数
}
bool APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid)
{
HWND hwnd = FindWindowW(L"valve001", NULL);
DWORD pid;
GetWindowThreadProcessId(hwnd, &pid);
if (GetCurrentProcessId() == pid)
{
HookDrawIndexedPrimitive();
Global_OldProc = (WNDPROC)SetWindowLong(hwnd, GWL_WNDPROC, (LONG)WindowProc); // 注册全局热键
}
return true;
}
关闭Z轴缓冲: 通过 GetStreamSource
函数获取到模型的来源,通过判断来源来禁用相应模型的Z轴缓冲,实现透视。
HRESULT __stdcall MyDrawIndexedPrimitive(LPDIRECT3DDEVICE9 m_pDevice, D3DPRIMITIVETYPE type, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT startIndex, UINT primCount)
{
IDirect3DVertexBuffer9 *pStreamData = NULL;
UINT iOffsetInBytes, iStride;
if (m_pDevice->GetStreamSource(0, &pStreamData, &iOffsetInBytes, &iStride) == D3D_OK) pStreamData->Release(); // 得到模型来源
if (iStride == 4) // 得到来源为200的时候,才会关闭Z轴(此处为敌人ID)
{
m_pDevice->SetRenderState(D3DRS_ZENABLE, FALSE); // 关闭Z轴缓冲
}
return Transfer_DrawIndexedPrimitive(m_pDevice, type, BaseVertexIndex, MinVertexIndex, NumVertices, startIndex, primCount);
}
如下截图:我直接禁用了全部的模型Z轴,实现了地图全透的效果
老实说,这款游戏我并没有找到人物的ID(一般也不玩CS),利用上面的方法排查就能找到,找到后替换上方的敌人ID即可完成针对人物的透视,这里懒得试了。
写教程不易,转载请加出处,谢谢 !!
FPS 游戏实现D3D透视的更多相关文章
- FPS 游戏实现GDI透视
FPS游戏可以说一直都比较热门,典型的代表有反恐精英,穿越火线,绝地求生等,基本上只要是FPS游戏都会有透视挂的存在,而透视挂还分为很多种类型,常见的有D3D透视,方框透视,还有一些比较高端的显卡透视 ...
- FPS游戏逆向-方框透视(三角函数)
本套课程主要学习FPS类游戏安全 由于FPS类游戏本身的特性问题,可能产生一些通用的游戏安全问题 在通过逆向与正向对FPS类游戏分析之后,找到其可能出现的不安全点 才能更好的保护游戏不被外部力量侵犯 ...
- FPS游戏方框透视基本原理
计算机图形学基础 计算机图形学最基础的目标就是把建模时构建好的3D物体显示在2D屏幕上,这就涉及到物体在不同坐标系中坐标的转换.(物体/建模坐标系 ---------> 屏幕坐标系) 坐标系 在 ...
- 怎样在Android开发中FPS游戏实现的两种方式比较
怎样在Android开发中FPS游戏实现的两种方式比较 如何用Android平台开发FPS游戏,其实现过程有哪些方法,这些方法又有哪些不同的地方呢?首先让我们先了解下什么是FPS 英文名:FPS (F ...
- FPS游戏服务器设计的问题 【转】
一.追溯 去gameloft笔试,有一个题目是说: 叫你去设计一个FPS(第一人称射击游戏),你是要用TCP呢还是要用UDP,说明理由 . 二.学习 这是两篇网上找到的文章,写非常不错. 当时笔试的时 ...
- 在FPS游戏中,玩家对音画同步感知的量化与评估
前言 在游戏测试中,音画同步测试是个难点(所谓游戏音画同步:游戏中,音效与画面的同步程度),现在一般采用人工主观判断的方式测试,但这会带来2个问题: 无法准确量化,针对同一场景的多次测试结果可能会相反 ...
- Unreal Engine 4 系列教程 Part 10:制作简单FPS游戏
.katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > ...
- (转)FPS游戏服务器设计的问题
FPS游戏服务器设计的问题出处:http://www.byteedu.com/thread-20-1-1.html一.追溯 去gameloft笔试,有一个题目是说: 叫你去设计一个FPS(第一人称射击 ...
- 关于FPS游戏的设计问题
第一个想到的问题: 首先以unity的FPSCharactorController为例,这里规定,相机的方向中心一定是瞄准的方向中心.设置身体的扭曲朝向相机方向,这样身体可以弯腰.你们看图中,我让玩家 ...
随机推荐
- SuperWebSocket实现服务端和WebSocket4Net实现客户端
SuperWebSocket实现服务端和WebSocket4Net实现客户端具体实现如下: SuperWebSocket实现服务端 注:本作者是基于vs2019 enterprise版本,所有项目均为 ...
- win10 'make' 不是内部或外部命令
win10 解决“ 'g++' 不是内部或外部命令,也不是可运行的程序或批处理文件”的问题 https://www.jianshu.com/p/9bffbaf12bed windows下提示make不 ...
- CMU Database Systems - Parallel Execution
并发执行,主要为了增大吞吐,降低延迟,提高数据库的可用性 先区分一组概念,parallel和distributed的区别 总的来说,parallel是指在物理上很近的节点,比如本机的多个线程或进程,不 ...
- face morhper
图像变形背后的想法很简单.给定两个图像,我们想通过将图像和混合来创建中间图像.图像的混合和由参数控制的是在0和1之间().当为0时,变形看起来像,而当为1 时,变形看起来像.天真的,您可以在每个像素上 ...
- python : takes 0 positional arguments but 1 was given
def 的要加self, https://blog.csdn.net/u010269790/article/details/78834410
- centos6环境远程执行shell脚本报错not a valid identifier的问题处理
# 通过jenkins的apache用户rsync同步php代码到远程服务器报错如下: SSH: EXEC: STDOUT/STDERR from command [/bin/sh /usr/loca ...
- 008-SpringBoot发布WAR启动报错:Error assembling WAR: webxml attribute is required
一.Spring Boot发布war包流程: 1.修改web model的pom.xml <packaging>war</packaging> SpringBoot默认发布的都 ...
- java-mybaits-014-数据库缓存设计【querycache、mybatis一级缓存、二级缓存】
一.概述 一般来说,可以在5个方面进行缓存的设计: 1.最底层可以配置的是数据库自带的query cache, 2.mybatis的一级缓存,默认情况下都处于开启状态,只能使用自带的Perpetual ...
- OpenBLAS编译 Release x64 Win10 vs2015
>------ 已启动生成: 项目: ZERO_CHECK, 配置: Release x64 ------ > Checking Build System > CMake does ...
- Linux记录-mysql服务管理shell实现
#!/bin/bash menu() { echo "---欢迎使用mysql管理服务程序---" echo "# 1.启动服务" echo "# 2 ...