13

我从小就一直在“破解”我最喜欢的游戏之一,并且我设法能够拦截 OpenGL 调用并使用 opengl32.dll 包装器注入 C++ 代码。我已经为此想出了许多很酷的用途,但我目前的目标是在这个旧游戏中实现后处理 glsl 着色器,让它看起来更现代一点。该游戏最初是在 90 年代后期发布的,因此它对 OpenGL 的使用及其渲染代码是有限的/原始的/过时的。

我已经能够通过注入代码来初始化和使用 glsl 着色器程序成功地生成顶点和片段着色器,但我的问题在于产生帧缓冲区/将我的场景渲染到纹理以作为采样器发送到我的片段着色器。据我所知,我制作的纹理通常都是黑色的。

很难找到与我正在做的事情和检查原始 opengl 调用相关的资源(我一直试图通过阅读单帧游戏的 opengl 跟踪来确定在哪里插入代码),所以我没有能够把我的头绕在我做错的事情上。

我尝试将代码放在很多地方,但目前,我在游戏调用后绑定帧缓冲区,wglSwapBuffers()并且在下一次调用之前立即解除绑定glFlush(),但我不确定是否设置了多个视口,或矩阵运算,或在这些时间之间创建的任何其他东西都会以某种方式扰乱帧缓冲区。

这是初始化帧缓冲区的代码:

void initFB()
{
  /* Texture */
  glActiveTexture(GL_TEXTURE0);
  glGenTextures(1, &fbo_texture);
  glBindTexture(GL_TEXTURE_2D, fbo_texture);

  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1920, 1080, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

  glBindTexture(GL_TEXTURE_2D, 0);

    /* Depth buffer */
  glGenRenderbuffers(1, &fbo_depth);
  glBindRenderbuffer(GL_RENDERBUFFER, fbo_depth);
  glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, 1920, 1080);  
  glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fbo_depth);
  glBindRenderbuffer(GL_RENDERBUFFER, 0);

  /* Framebuffer to link everything together */
  glGenFramebuffers(1, &fbo);
  glBindFramebuffer(GL_FRAMEBUFFER, fbo);

  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo_texture, 0);
  glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fbo_depth);

  GLenum status;
  if ((status = glCheckFramebufferStatus(GL_FRAMEBUFFER)) != GL_FRAMEBUFFER_COMPLETE) {
    fprintf(stderr, "glCheckFramebufferStatus: error %p", status);
  }

  glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

这是之后调用的代码wglSwapBuffers

GLenum fboBuff[] =  { GL_COLOR_ATTACHMENT0 };
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glDrawBuffers(1, fboBuff);
glPushAttrib(GL_VIEWPORT_BIT | GL_ENABLE_BIT);

这是之前调用的代码glFlush()

glBindFramebuffer(GL_FRAMEBUFFER, 0);
glPopAttrib(); // Restore our glEnable and glViewport states  

... Draw an Overlay containing with fbo texture binded ...

是单帧的 gl 轨迹。

它有近 800 行,但训练有素的 opengl 眼睛应该能够看到什么时候发出的呼叫。我删除了数百行我至少理解的绘图调用。

4

3 回答 3

2

我不完全确定你想做什么。运行游戏渲染给你fbo?在上面渲染东西?首先,尝试挂钩所有 wgl 和 gl 调用。http://research.microsoft.com/en-us/projects/detours/是一个很好的起点。

但是再次阅读您的帖子,我想您已经知道了。知道你想做什么会有所帮助。

  • 渲染叠加层:Hook SwapBuffers,在那里做你的事情
  • 捕获:再次挂钩 SwapBuffers
  • 渲染到纹理并做后期效果:Hook BindFramebuffer。但是有一个问题:帧缓冲区 0 是启动时的默认值,因此不使用它的代码将不会调用 BindFramebuffer 来启动,只会将其设置回 0。尝试挂钩 glClear 或任何首先出现的调用。

欢迎私信我了解更多详情。听起来像是一个有趣的教育项目,我很乐意帮助:)

我只是在发布之前再次阅读了您的帖子,您的问题可能会容易得多。作为 Windows 程序,您的目标假定 fbo 0 在开始渲染帧时已绑定。就这样做吧。在它完成之前,它会切换回 fbo 0。不要做 push/pop 的事情。只需确保您的 fbo 与预期的大小相同(在 Windows 上更大也可以)。不过,您需要挂钩 BindFramebuffer。每当您的游戏调用 bind 0 时,您都需要绑定您的。但是没有必要做任何其他事情,比如触摸视口,游戏已经处理好了。当您最终在您的 swapBuffers 挂钩中渲染时,只需确保恢复您更改的任何内容。

此外,您真的不需要 DrawBuffers 调用。它把我的痣弄糊涂了,可能也把你的目标弄糊涂了。:) 您的目标执行 GL 1.2 代码,您正在执行 3...4...

于 2013-06-16T07:25:53.993 回答
1

根据我的经验,对于无着色器 GL1.x 应用程序的 GL 拦截方法,您尝试做的事情是完全可能的。

正如我快速破解的那样,您现在可以忘记 FBO,并glCopyTexSubImage()在原始应用程序交换缓冲区时使用,将渲染的内容复制到纹理中并为您的后处理函数发送文本。

在执行这种 GL 注入方法时,您应该注意一些事项:

  • 可能有不同的 GL 上下文(尽管这对于 CAD 应用程序比典型游戏更有可能)
  • 旧的 GL 允许用户直接管理对象 ID - 无需调用需要的glGen*函数(Nice 应用程序仍然这样做)。如果您自己创建新对象,原始应用程序将不知道并且可能会重复使用这些 ID。如果你想做到这一点,你将不得不透明地重新映射对象 ID(可能使用一些哈希表)——这是很多工作,因为涉及到许多 GL 函数。另一个邪恶的技巧是自己使用一些不太可能的对象 ID。
  • GL 是一个状态机,所以你应该非常小心地恢复游戏所期望的状态(尽可能)。OTOH 手,您还应该准备好原始应用程序在您获得更改以拦截它时可能设置了任何 GL 状态 - 所以您可能需要重置很多东西。

我可以向您保证,确实可以将渲染纹理注入到一些旧的 GL 应用程序中,就像我自己做的那样。与 Quake3 和其他当时著名的游戏配合得非常好。我没有看到您当前的方法有明显错误。不过我有几件事:您还应该拦截glDrawBuffer()应用程序将要执行的任何绘图GL_BACK(假设这里是双缓冲应用程序)到您的颜色附件。您发布的日志没有跟踪此功能,因此游戏可能在启动时设置了一次。

我也不明白你为什么用glFlush. 它不是特别适合任何事物的好标记。如果游戏使用双缓冲,SwapBuffers 将是您可以获得的“帧”的最佳近似值。只需在上下文创建后直接插入您自己的初始化对象创建内容(您也可以拦截wglCreateCintext()和朋友),并且已经设置了对 FBO 的渲染。在 上SwapBuffers,只需使用着色器将纹理的内容绘制到真实的后台缓冲区,进行真实的缓冲区交换,然后将渲染恢复为 FBO 设置。

我不知道你是否已经意识到这一点,但是开源项目Chromium确实为 OpenGL 拦截提供了一个很好的 fframework。虽然这可能集中在分布式集群渲染上,但它也可以在本地设置中工作,并允许您编写 SPU(流处理单元),您可以在其中简单地为您想要挂钩的任何 GL 函数指定 C 函数. 该项目已经过时并且在 5 年左右的时间里没有积极开发,因为它设计的那种 GL 流操作不适用于更现代的可编程管道,但它仍然可以很好地与旧的 GL1 一起使用。 x 固定功能流水线...

于 2013-06-16T18:28:26.593 回答
0

据我了解,您正在尝试更新旧游戏的图形方面,但您在原始代码中遇到限制,阻止您改装所需的部件。

虽然我没有相关的游戏编码经验,但是当我有一个我正在尝试改造的应用程序时,我会拆解应用程序来研究它的结构,并有选择地应用内存中的补丁来重定向某些功能和代码段到我提供的替代品。

我将预加载一个共享库,该库将使用条件/无条件跳转/调用指令覆盖程序内存的某些部分,并有选择地 NOP'ing,这样我的代码就成为程序的一个组成部分。它绝对不是优雅的,但它让我能够快速解决原始代码中的缺陷。

于 2013-04-16T02:51:19.463 回答