• 【入门向】使用 MetaHook Plus 绘制 HUD


    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++代码的话,这样就行,不用安装多余的工具:

    安装完VS之后,你需要下载 MetaHook Plus 的源码,地址在这里,下载ZIP就行。

     http://git.oschina.net/Nagist2/MetaHook 

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

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

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

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

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

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

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

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

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

    #include <metahook.h>
    #include <gl/gl.h>
    
    cl_enginefunc_t gEngfuncs;

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

    这样你就可以在插件里使用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,进入游戏,你会发现游戏左上角多了个白色的四边形,这证明我们的代码已经成功运行。

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

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

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

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

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

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

    Photoshop:

    系统绘图工具:

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

    每个像素都有不同的颜色,并且每个像素能显示出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个像素右边的那个。从左往右横向数的。

    然后你可能还想知道第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 !
    ");
    }

    这样一来我们就已经把 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 !
    ");
    
        // 绑定纹理
        // 绑定一个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个点连成一个形状(面),而我们接下来要做的,就是把一张纹理贴到这个“面”上,这种技术叫做“贴图”。

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

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

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

    如图:

    你可以看到图中有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 里,然后打开游戏看看效果。

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

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

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

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

    你会得到这样的效果:

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

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

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

    答案是肯定的!

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

    如图所示,这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();                            // 结束四边形绘制

    结果如下图:

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

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

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

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

    至此在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 就在不同的位置绘制了两个图片,而不需要重复一大堆代码。

    按照这个方向,我相信你能打造出非常方便的函数用来绘制你的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);
    
        // ...
    }

    注意参数的变化。

    效果如下:

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

    祝你成功!

  • 相关阅读:
    Linux程序分析工具介绍—ldd,nm
    Makefile学习(三)[第二版]
    Linux下的tree命令 --Linux下目录树查看
    Makefile学习(二)[第二版]
    Makefile学习(一)[第二版]
    Linux下top命令详解
    Shell编程入门(第二版)(下)
    mysql用命令行导入sql文件
    javascript的onbeforeunload函数在IOS上运行
    mysql如何利用Navicat 导出和导入数据库
  • 原文地址:https://www.cnblogs.com/crsky/p/6440832.html
Copyright © 2020-2023  润新知