MetaHook Plus 是一个GoldSrc引擎(就是的Half-Life、CS1.6的引擎)的客户端插件平台,它可以加载我们自己开发的DLL插件。

 

首先你需要安装一个 Visual Studio 2005 来编译 MetaHook Plus 本体,也可以用来开发我们自己的插件,这里提供一个镜像文件。

注意:MetaHook Plus 本体必须要用 2005  来编译!

ed2k://|file|cs_vs_2005_pro_dvd.iso|2733268992|9DA1C378BAC22E66A73C9E20EC78CCFB|/

 

顺便一提,如果VS2005只用来写C++代码的话,这样就行,不用安装多余的工具:

红色长方形Python 红色长方形图片纯色_红色长方形Python

 

安装完VS之后,你需要下载 MetaHook Plus 的源码

 

把源码解压好,打开里面的 MetaHook.sln 文件,选择 Debug 版本,重新生成解决方案。

红色长方形Python 红色长方形图片纯色_photoshop_02

红色长方形Python 红色长方形图片纯色_加载_03

 

稍后你可以在下方的输出窗口里看到编译成功的字样,查看源码目录,多了一个 Debug 文件夹,里面有编译好的 MetaHook.exe,把它复制到你的游戏目录,比如 CS1.6 的,改名成 cstrike.exe 

红色长方形Python 红色长方形图片纯色_git_04

 

进入 cstrike 文件夹,新建一个 metahook 文件夹,然后在 metahook 文件夹里新建 configs 和 plugins 两个文件夹,在 configs 文件夹里新建一个文本文档,改名为 plugins.lst

红色长方形Python 红色长方形图片纯色_红色长方形Python_05

红色长方形Python 红色长方形图片纯色_photoshop_06

 

至此 MetaHook Plus 已经正确安装,打开 cstrike.exe 可以正常启动游戏。

 

打开 MetaHook Plus 源码文件夹里的 Plugins\FuckWorld 文件夹,这个项目是一个最简单的 MetaHook 插件(下文简称插件)。

打开 FuckWorld.sln,像编译 MetaHook Plus 一样编译项目,把编译好的 FuckWorld.dll 复制到 plugins 文件夹,打开 plugins.lst 文件,加入一行 FuckWorld.dll(注意不要在文件里留下空行),保存文件。

红色长方形Python 红色长方形图片纯色_photoshop_07

 

现在正常启动游戏,并进入游戏,然后打开控制台(按~键),你会发现控制台里多了几行字。这证明你的插件已经正常运行。

红色长方形Python 红色长方形图片纯色_photoshop_08

 

终于可以开始讲怎么在游戏里显示一张图片了。

不要关掉 FuckWorld 项目的VS,在左边窗口里双击打开 exportfuncs.cpp 文件,在 #include <metahook.h> 这一行的下方加入一行 #include <gl/gl.h> 



#include <metahook.h>
#include <gl/gl.h>

cl_enginefunc_t gEngfuncs;



 

然后打开项目属性,在左边的分支里找到 链接器->输入,然后在右边的 附加依赖项 里添加 opengl32.lib,保存。关闭项目属性。

红色长方形Python 红色长方形图片纯色_加载_09

红色长方形Python 红色长方形图片纯色_git_10

 

这样你就可以在插件里使用OpenGL的功能了,什么是OpenGL?这个单词你肯定不陌生,但这里暂时不打算详细介绍。

注意:确保你游戏的视频模式是OpenGL!

 

那么,现在可以正式开始学习怎么显示一张图片了,首先我们改一下 HUD_Redraw 这个函数,把代码改成如下的样子,注意字母大小写一定要对!后面 // 绿色的部分可以不写。



int HUD_Redraw(float time, int intermission)
{
    glDisable(GL_TEXTURE_2D);            // 关闭纹理功能

    glColor4ub(255, 255, 255, 255);      // 设置显示颜色

    glBegin(GL_QUADS);                   // 开始绘制四边形
    glVertex2f(20.0f, 20.0f);            // 四边形第1个点的坐标
    glVertex2f(80.0f, 20.0f);            // 四边形第2个点的坐标
    glVertex2f(80.0f, 80.0f);            // 四边形第3个点的坐标
    glVertex2f(20.0f, 80.0f);            // 四边形第4个点的坐标
    glEnd();                             // 结束四边形绘制

    glEnable(GL_TEXTURE_2D);             // 打开纹理功能

    return gExportfuncs.HUD_Redraw(time, intermission);
}



 

重新编译 FuckWorld,复制DLL,进入游戏,你会发现游戏左上角多了个白色的四边形,这证明我们的代码已经成功运行。

红色长方形Python 红色长方形图片纯色_photoshop_11

 

下面讲解一下关于这个四边形的事,首先是坐标。

红色长方形Python 红色长方形图片纯色_git_12

 

由此可见,游戏窗口的坐标系是这样的

红色长方形Python 红色长方形图片纯色_photoshop_13

 

现在你可以尝试修改 glVertex2f 的代码,改变4个顶点的坐标,让四边形在别的地方显示,或者改变它的大小,或者改变它的形状。

 

更新时间:2017年6月22日16:06:48

看了上面的教程,你应该能在游戏画面中画出一个纯色的(正)方形,但是这并没有多大卵用。接下来就讨论一下怎麽画一张图片,但真正画图片之前,需要先学一点有关图片的知识。

首先我们要从非常底层的视角去认识图片,比如下图这样:

Photoshop:

红色长方形Python 红色长方形图片纯色_红色长方形Python_14

系统绘图工具:

红色长方形Python 红色长方形图片纯色_git_15

把一张普通图片放大很多倍,你会发现图片都是由很多格子组成的,这些格子就叫做像素。(所以一张图片就是由很多像素组成的)

每个像素都有不同的颜色,并且每个像素能显示出255×255×255种颜色。(16,581,375种)

学过初中物理你应该知道,几乎任何可见光的颜色都可以用红绿蓝(Red Green Blue、RGB)三种颜色混合起来得到,红绿蓝三种颜色按照不同的分量混合起来,就能得到多达16多万种颜色。

那么计算机是怎么保存一张图片的呢?

计算机使用一个字节来保存一种颜色的分量,所以一个像素通常需要三个字节来保存(分别保存RGB三种颜色的分量),但一张图片可不止一个像素,而是非常多像素。

一张尺寸为32×32的图片,就包含1024个像素,你可能很快就发现32×32=1024,没错就是这样。为了保存一张尺寸为32×32的图片的所有像素,我们需要一个非常大的数组变量:



BYTE ubData[ 32 * 32 * 3 ];



假设我们已经把这张图片的所有像素保存到这个数组里,那么:



ubData[ 0 ];    // 第1个像素的红色分量(R)
ubData[ 1 ];    // 第1个像素的绿色分量(G)
ubData[ 2 ];    // 第1个像素的蓝色分量(B)

ubData[ 3 ];    // 第2个像素的红色分量(R)
ubData[ 4 ];    // 第2个像素的绿色分量(G)
ubData[ 5 ];    // 第2个像素的蓝色分量(B)

// ...



你可能会问第1个像素到底是图片里哪个地方,答:最左上角的那一个。顺便一提,第2个像素是第1个像素右边的那个。从左往右横向数的。

红色长方形Python 红色长方形图片纯色_加载_16

然后你可能还想知道第2行的第1个像素的成员是哪几个(假设图片大小是32×32,已知每个像素占用3个成员)

像素在数组里是按照横向顺序保存的,保存完第1行的像素,就接着第2行的像素。



uData[ (1 * 32 * 3) + 0 ];    // 第2行第1个像素的红色分量(R)
uData[ (1 * 32 * 3) + 1 ];    // 第2行第1个像素的绿色分量(G)
uData[ (1 * 32 * 3) + 2 ];    // 第2行第1个像素的蓝色分量(B)



详细怎麽获取像素的颜色就不多说了,你只要知道图片是这麽一回事就好。

 

然后是很关键的一步,我们有一堆图片文件(比如TGA格式的),要怎麽把它们的像素弄(加载、读取、载入.. 等等说法都有)到变量里呢?

这其中的步骤是非常复杂的,但是!不用担心,引擎里就有写好的函数,我们只要拿来用就行。

HL引擎有一个叫做 LoadTGA 的函数,可以把TGA图片的像素读取到变量里。

函数原型:



bool LoadTGA(const char *filename, unsigned char *buffer, int bufferSize, int *wide, int *tall);



所以我们只要拿到这个函数的指针就能使用了:



// 3266版本引擎的基址是固定的,所以直接写静态地址就行。
bool (*g_pfnLoadTGA)(const char *filename, unsigned char *buffer, int bufferSize, int *wide, int *tall) = (bool (*)(const char *, unsigned char *, int, int *, int *))0x01D4F8A2;



如果你想直接照搬这个代码,请一定要使用3266版本的引擎,否则会游戏会崩溃。

其它版本引擎就不能这麽写,引擎地址是不固定的。查找其它版本引擎的LoadTGA函数涉及特征码搜索知识,这里不讨论,以后可能会写特征码搜索的讲解。

(什麽?你不知道怎麽看引擎版本号?打开控制台,输入命令 version )

 

加载一个TGA文件我们可以这样写:



// 3266版本引擎的基址是固定的,所以直接写静态地址就行。
bool (*g_pfnLoadTGA)(const char *filename, unsigned char *buffer, int bufferSize, int *wide, int *tall) = (bool (*)(const char *, unsigned char *, int, int *, int *))0x01D4F8A2;

// 因为TGA文件除了RGB三种颜色分量,还有一个Alpha分量,表示像素的透明度,所以每个像素就要占用4个字节(RGBA)。
BYTE ubData[ 1024 * 1024 * 4 ];

// 用来保存加载的TGA的宽度和高度。
int iWidth, iHeight;

void HUD_Init(void)
{
    gExportfuncs.HUD_Init();

    if (g_pfnLoadTGA("gfx/1.tga", ubData, sizeof(ubData), &iWidth, &iHeight) == false)
    {
        // 检查一下有没有加载失败。
        iWidth = 0;
        iHeight = 0;
    }

    if (iWidth <= 0 || iHeight <= 0)
    {
        // 可能是加载失败了。
        return;
    }

    // 宽高都不为0,表示加载成功,我们在控制台输出一个提示。
    gEngfuncs.Con_Printf("Load TGA successfully !\n");
}



 

这样一来我们就已经把 1.tga 的像素读取并且保存到 ubData 里了。然后我们就要考虑怎麽把这些像素画出来了。

在上面的教程里,你已经知道我们需要使用OpenGL提供的函数来绘制东西,当然绘制图片也不例外。

为了让OpenGL能使用刚才加载好的像素数据,我们需要创建一个OpenGL纹理(下文简称纹理),OpenGL纹理能保存许多东西,我们这里只用来绘制一些像素,所以使用2D纹理。

我们可以用 glGenTextures 或者 glBindTexture 来创建出一个新的纹理,但是 glBindTexture 的专职是绑定纹理,而不是创建,所以一般我们用 glGenTextures。

下面的代码创建1个新纹理。



GLuint texid;
glGenTextures(1, &texid);



但是由于HL引擎的种种原因,我们决定用 glBindTexture 来创建出指定ID的纹理。



GLuint texid = 20000;
glBindTexture(GL_TEXTURE_2D, texid);



这样我们就直接创建出了ID为20000的2D纹理。

OpenGL的工作方式类似流水线(状态机),所以同时只能处理一个纹理,为了马上使用刚才创建出来的纹理,我们要把它绑起来,这样接下来的操作就会应用到这个纹理上。



glBindTexture(GL_TEXTURE_2D, texid);



但是你看到我们上面已经有 glBindTexture 了,所以就不用再次绑定了。

 

绑定好纹理之后,我们就要为这个纹理设定好一些必要的参数:

设置纹理缩放过滤方式(OpenGL纹理过滤方式可以在网上搜索到介绍):



glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);



然后我们就要把加载好的那一堆像素传给这个纹理:



glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, iWidth, iHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, ubData);



第一个参数表示纹理类型为2D。对于 glTexImage2D 来说,必须写 GL_TEXTURE_2D。

第二个参数是Mipmap相关的,我们只用来绘制HUD,所以固定写0就行。

第三个参数是OpenGL内部像素格式,决定了像素传到OpenGL里面之后要保存为什么样的格式,我们一般保存为RGBA就行。

第四个参数是图片宽度。

第五个参数是图片高度。

第六个参数固定写0。

第七个参数是像素的格式,也就是 ubData 的格式,根据我们上面教程的介绍,肯定是 RGBA 这样排啦。

第八个参数是像素的数据类型,我们是BYTE的,所以写 GL_UNSIGNED_BYTE。

第九个参数是像素数据的地址,直接写ubData就行。

 

如果成功调用了 glTexImage2D 那麽像素数据就已经成功传到了OpenGL里(OpenGL程序是在显卡里运行的,所以此时像素数据已经被传到了显存里)。

我们已经处理完自己的纹理了,不要忘了擦屁股,解除纹理绑定(我们会在绘制的时候才绑定需要的纹理):



glBindTexture(GL_TEXTURE_2D, 0);



现在我们代码是这样的:



// 纹理ID
GLuint texid = 20000;

void HUD_Init(void)
{
    // ...

    gEngfuncs.Con_Printf("Load TGA successfully !\n");

    // 绑定纹理
    // 绑定一个2D纹理,使用参数:GL_TEXTURE_2D
    // 绑定ID号为20000的纹理(GL会先检查有没有ID为20000的纹理,如果没有就自动创建新的,有就直接绑定)
    glBindTexture(GL_TEXTURE_2D, texid);

    // 设置纹理参数
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    // 上传像素数据到显卡
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, iWidth, iHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, ubData);

    // 处理完自己的纹理,就解除绑定。
    glBindTexture(GL_TEXTURE_2D, 0);
}



 

这样我们就准备好了一个2D纹理,接着就可以开始绘制啦~

 

2017年7月27日00:48:36 更新

 

还记得上面我写了4个坐标绘制出一个纯色的正方形吗?如果忘了就赶紧往回看!

我们用4个点连成一个形状(面),而我们接下来要做的,就是把一张纹理贴到这个“面”上,这种技术叫做“贴图”。

为了使用贴图,我们要先了解一个新的名词,叫做“纹理坐标”。

纹理坐标就是在纹理上建立的坐标,比方说,我有一个纹理,像这样:

红色长方形Python 红色长方形图片纯色_加载_17

然后建立一个坐标系,横向坐标轴叫做S轴,纵向坐标轴叫做T轴。

如图:

红色长方形Python 红色长方形图片纯色_红色长方形Python_18

你可以看到图中有4组括号,格式为(S,T),这就是纹理4个角的坐标。

接下来我们要修改一下绘制函数(HUD_Redraw)来实现贴图功能了。

为了使用纹理功能,我们就要先打开它,使用 glEnable 函数



glEnable(GL_TEXTURE_2D);            // 打开纹理功能



接着我们绑定要用的纹理,使用 glBindTexture 函数,就是上面加载好的那个 texid,所以:



glBindTexture(GL_TEXTURE_2D, texid);    // 绑定用于贴图的纹理



为了让贴图表现出Alpha通道透明效果,我们还要设置一下透明混合功能。

(混合功能暂不做详解,照抄即可)



glEnable(GL_BLEND);                    // 开启透明混合功能
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);    // 设置混合参数



接下来就是我们绘制四边形的代码了,我们要做的就是在调用 glVertex3f 指定四边形坐标的时候,顺便再指定一个纹理坐标,这样 OpenGL 就知道怎麽把纹理贴上去了。



glBegin(GL_QUADS);                    // 开始绘制四边形

glTexCoord2f(0.0f, 0.0f);            // 四边形第1个点的纹理坐标
glVertex2f(20.0f, 20.0f);            // 四边形第1个点的坐标

glTexCoord2f(1.0f, 0.0f);            // 四边形第2个点的纹理坐标
glVertex2f(80.0f, 20.0f);            // 四边形第2个点的坐标

glTexCoord2f(1.0f, 1.0f);            // 四边形第3个点的纹理坐标
glVertex2f(80.0f, 80.0f);            // 四边形第3个点的坐标

glTexCoord2f(0.0f, 1.0f);            // 四边形第4个点的纹理坐标
glVertex2f(20.0f, 80.0f);            // 四边形第4个点的坐标

glEnd();                            // 结束四边形绘制



如果你忘记了四边形的坐标,请翻回去看看!

看上面的代码,我们就是一如既往地绘制一个四边形而已,顺便指定一下纹理坐标,是不是相当于把一张纹理 “铺上去” 一样呢?

如果你能理解成这样,那么恭喜你。

现在完整的代码是这样的:



int HUD_Redraw(float time, int intermission)
{
    // 先绘制原本的HUD,这样我们画的图片就不会被HUD覆盖了。
    gExportfuncs.HUD_Redraw(time, intermission);

    glEnable(GL_TEXTURE_2D);            // 打开纹理功能
    glBindTexture(GL_TEXTURE_2D, texid);    // 绑定用于贴图的纹理

    glEnable(GL_BLEND);                    // 开启透明混合功能
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);    // 设置混合参数

    glColor4ub(255, 255, 255, 255);        // 设置显示颜色

    glBegin(GL_QUADS);                    // 开始绘制四边形

    glTexCoord2f(0.0f, 0.0f);            // 四边形第1个点的纹理坐标
    glVertex2f(20.0f, 20.0f);            // 四边形第1个点的坐标

    glTexCoord2f(1.0f, 0.0f);            // 四边形第2个点的纹理坐标
    glVertex2f(80.0f, 20.0f);            // 四边形第2个点的坐标

    glTexCoord2f(1.0f, 1.0f);            // 四边形第3个点的纹理坐标
    glVertex2f(80.0f, 80.0f);            // 四边形第3个点的坐标

    glTexCoord2f(0.0f, 1.0f);            // 四边形第4个点的纹理坐标
    glVertex2f(20.0f, 80.0f);            // 四边形第4个点的坐标

    glEnd();                            // 结束四边形绘制

    return 1;
}



现在重新编译你的 FuckWorld,复制到 plugins 里,然后打开游戏看看效果。

如果你做对了,你就会看到这样的效果:

红色长方形Python 红色长方形图片纯色_git_19

 

现在我们来做个有趣的实验吧,假如,我们指定的4个坐标,连起来不是一个正方形会怎么样呢?

我稍微修改一下第3个顶点的坐标(右下角那个顶点),把坐标改成(100,100),像这样:



glTexCoord2f(1.0f, 1.0f);            // 四边形第3个点的纹理坐标
glVertex2f(100.0f, 100.0f);            // 四边形第3个点的坐标



你会得到这样的效果:

红色长方形Python 红色长方形图片纯色_加载_20

是不是更能体现出 “贴图” 的效果呢?

你平时看到的形状复杂的模型,就是由大量的几何图形贴上图组成的,绘制过程同样是:指定顶点坐标、指定纹理坐标…

 

可能有同学注意到,既然指定纹理4个角的坐标就能把整个纹理“铺上去”,那反过来,如果不指定4个角的坐标,而是指定纹理中间一部分的4个坐标,是不是可以只铺上纹理的一部分呢?

答案是肯定的!

我们现在重新指定4个纹理坐标,比如这样:

红色长方形Python 红色长方形图片纯色_git_21

如图所示,这4个坐标形成了一个新的区域,这个区域是整个纹理的右下角一部分。

我们把新的纹理坐标写到代码中,看看效果!



glBegin(GL_QUADS);                    // 开始绘制四边形

glTexCoord2f(0.5f, 0.5f);            // 四边形第1个点的纹理坐标
glVertex2f(20.0f, 20.0f);            // 四边形第1个点的坐标

glTexCoord2f(1.0f, 0.5f);            // 四边形第2个点的纹理坐标
glVertex2f(80.0f, 20.0f);            // 四边形第2个点的坐标

glTexCoord2f(1.0f, 1.0f);            // 四边形第3个点的纹理坐标
glVertex2f(80.0f, 80.0f);            // 四边形第3个点的坐标

glTexCoord2f(0.5f, 1.0f);            // 四边形第4个点的纹理坐标
glVertex2f(20.0f, 80.0f);            // 四边形第4个点的坐标

glEnd();                            // 结束四边形绘制



结果如下图:

红色长方形Python 红色长方形图片纯色_红色长方形Python_22

你可以看到,确实只铺上了整个纹理的右下角一部分!

认识了纹理坐标,你就可以做很多骚操作啦!

比方说把一堆小图标拼在一张图片里,然后绘制的时候指定其中一个图标的纹理坐标来绘制~

这样就不需要加载许多个尺寸很小的图片,而只加载一个尺寸大点的图片就行了。(美工制作起来也方便)

 

至此在HUD上绘制一个图片的过程已经详细解释,但是你看上面写的代码只绘制一个图片,我们实际上制作游戏肯定不止要绘制一个图片而已。

为了更方便地绘制图片,我们把上面的代码整理出来,分别放到两个函数里,一个 R_LoadTextureFromTGA 用于加载TGA图片文件并且上传到纹理,一个 R_DrawTextureRect2D 用于绘制一个贴图的长方形。

以下就是整理过的代码:



#include <metahook.h>
#include <gl/gl.h>

cl_enginefunc_t gEngfuncs;

int Initialize(struct cl_enginefuncs_s *pEnginefuncs, int iVersion)
{
    memcpy(&gEngfuncs, pEnginefuncs, sizeof(gEngfuncs));
    return gExportfuncs.Initialize(pEnginefuncs, iVersion);
}

// 3266版本引擎的基址是固定的,所以直接写静态地址就行。
bool (*g_pfnLoadTGA)(const char *filename, unsigned char *buffer, int bufferSize, int *wide, int *tall) = (bool (*)(const char *, unsigned char *, int, int *, int *))0x01D4F8A2;

// 纹理ID
GLuint texid;

// 此函数用于兼容3266版本引擎(仅用于测试)
GLuint R_GenTexture(void)
{
    static GLuint texnum = 20000;
    return texnum++;
}

GLuint R_LoadTextureFromTGA(const char *filename)
{
    // 局部变量不能太大,所以用静态变量
    static BYTE ubData[ 1024 * 1024 * 4 ];
    // 宽度和高度
    int iWidth, iHeight;
    // 纹理ID
    GLuint iTexID;

    if (g_pfnLoadTGA(filename, ubData, sizeof(ubData), &iWidth, &iHeight) == false)
    {
        // 失败返回0
        return 0;
    }

    // 生成一个新纹理
    iTexID = R_GenTexture();

    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, iTexID);

    // 设置纹理参数
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    // 上传像素数据到显卡
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, iWidth, iHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, ubData);

    // 处理完自己的纹理,就解除绑定。
    glBindTexture(GL_TEXTURE_2D, 0);

    // 返回纹理ID
    return iTexID;
}

void R_DrawTextureRect2D(GLuint tex, int x, int y, int width, int height)
{
    glEnable(GL_TEXTURE_2D);            // 打开纹理功能
    glBindTexture(GL_TEXTURE_2D, tex);    // 绑定用于贴图的纹理

    glEnable(GL_BLEND);                    // 开启透明混合功能
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);    // 设置混合参数

    glColor4ub(255, 255, 255, 255);        // 设置显示颜色

    // 我们没有分别指定4个顶点的坐标,而是给出XY和要绘制的四边形的宽度和高度。
    // 所以这4个点的坐标分别这样计算:
    //   左上角(X,Y)
    //   右上角(X+宽度,Y)
    //   右下角(X+宽度,Y+高度)
    //   左下角(X,Y+高度)

    // 如果你觉得难理解,建议自己画个窗口坐标系来看看

    glBegin(GL_QUADS);                    // 开始绘制四边形

    glTexCoord2f(0.0f, 0.0f);            // 四边形第1个点的纹理坐标
    glVertex2f(x, y);                    // 四边形第1个点的坐标

    glTexCoord2f(1.0f, 0.0f);            // 四边形第2个点的纹理坐标
    glVertex2f(x + width, y);            // 四边形第2个点的坐标

    glTexCoord2f(1.0f, 1.0f);            // 四边形第3个点的纹理坐标
    glVertex2f(x + width, y + height);    // 四边形第3个点的坐标

    glTexCoord2f(0.0f, 1.0f);            // 四边形第4个点的纹理坐标
    glVertex2f(x, y + height);            // 四边形第4个点的坐标

    glEnd();                            // 结束四边形绘制
}

void HUD_Init(void)
{
    gExportfuncs.HUD_Init();

    // 加载TGA
    texid = R_LoadTextureFromTGA("gfx/1.tga");
}

int HUD_Redraw(float time, int intermission)
{
    // 先绘制原本的HUD,这样我们画的图片就不会被HUD覆盖了。
    gExportfuncs.HUD_Redraw(time, intermission);

    // 绘制纹理
    R_DrawTextureRect2D(texid, 10, 10, 70, 70);

    R_DrawTextureRect2D(texid, 10, 100, 170, 170);

    return 1;
}

void V_CalcRefdef(struct ref_params_s *pparams)
{
    gExportfuncs.V_CalcRefdef(pparams);
}



你可以看到我加载 1.tga 只调用了一次 R_LoadTextureFromTGA,并且,我调用两次 R_DrawTextureRect2D 就在不同的位置绘制了两个图片,而不需要重复一大堆代码。

红色长方形Python 红色长方形图片纯色_c/c++_23

 

按照这个方向,我相信你能打造出非常方便的函数用来绘制你的HUD。

 

既然是绘制HUD,就不免有些特殊要求,比方说绘制一个半透明的图片(透明度可以控制的),或者让原本的图片变色之类的操作。

要实现这样的效果是简单的,OpenGL 会自动把纹理中的颜色和 glColor 指定的颜色相乘,至于怎麽乘就懒得介绍了~

我们要做的就是稍微修改一下 R_DrawTextureRect2D 函数。



void R_DrawTextureRect2D(GLuint tex, int x, int y, int width, int height, int r, int g, int b, int alpha)
{
    // ...
    
    glColor4ub(r, g, b, alpha);        // 设置显示颜色
    
    // ...
}



然后需要注意一下,glColor4ub 这个函数的参数只接受 0~255 的值,如果想用 0.0~1.0 这样的小数值来表示颜色,还有一个 glColor4f 函数可以用。

这里为了更贴近我们日常使用的 RGB 就采用 glColor4ub 了。

然后我们修改一下绘制时候使用的参数:



int HUD_Redraw(float time, int intermission)
{
    // ...

    // 绘制纹理
    R_DrawTextureRect2D(texid, 10, 10, 70, 70, 255, 255, 100, 255);

    R_DrawTextureRect2D(texid, 10, 100, 170, 170, 255, 255, 255, 100);

    // ...
}



注意参数的变化。

效果如下:

红色长方形Python 红色长方形图片纯色_红色长方形Python_24

 

 

至此,在HUD上绘制图片的基本过程就解释完毕了,你可以看到实际上要写的代码并不多!

祝你成功!