2013-04-05 145 views
11

我一直在“黑客”我年轻时最喜欢的游戏之一,我设法能够拦截OpenGL调用并使用opengl32.dll封装注入C++代码。我已经能够为此提出一些很酷的用途,但是我目前的目标是在这个旧游戏中实现后处理glsl着色器,使其看起来更现代一些。该游戏最初是在90年代后期发布的,因此它对OpenGL及其渲染代码的使用是有限的/原始的/过时的。注入帧缓冲区

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

很难找到与我正在做的事情相关的资源,并检查原始的opengl调用(我一直试图通过阅读单帧游戏的opengl轨迹来确定插入代码的位置),所以我一直没有能够把我的头围绕我做错了什么。

我试过把代码放在很多地方,但是目前我在游戏后调用wglSwapBuffers()绑定了framebuffer,我在下一次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 ... 

Here是单个帧的gl轨迹。

它接近800行,但训练有素的opengl眼睛应该能够看到什么时候拨打电话。我删除了几百行我最近了解的绘图调用。

+0

也许尝试通过glReadPixels更新纹理,而不是只是为了查看其余代码是否正确? – 2013-04-07 13:06:48

回答

0

据我所知,你试图更新老游戏的图形方面,但是你在原始代码中遇到了限制,无法改造你需要的部分。

尽管我没有相关的游戏编码经验,但当我有一个应用程序尝试改造时,我会反汇编应用程序以研究它的结构,并有选择地应用内存补丁来重定向某些功能以及我提供的代码片段。

我会预加载一个共享库,它会用条件/无条件的跳转/调用指令和有选择的NOP'ing来覆盖程序内存的某些部分,这样我的代码就成为程序的一个组成部分。它绝对不是以任何方式优雅,但它允许我迅速解决原代码中的缺陷。

2

我不完全确定你想要做什么。运行游戏来呈现给你fbo?渲染顶部的东西? 要开始,请尝试挂钩所有wgl和gl调用。 http://research.microsoft.com/en-us/projects/detours/是一个很好的开始。

但是再次阅读你的文章,我想你已经知道了。这将有助于知道你想要做什么。

  • 渲染覆盖:胡克SwapBuffers,做你的东西有
  • 捕捉:同样,钩SwapBuffers
  • 纹理渲染和做后期效果:胡克BindFramebuffer。但有一个问题:帧缓冲区0是启动时的默认值,所以不使用它的代码不会调用BindFramebuffer来启动,只是将其设置为0.尝试挂钩glClear或任何呼叫先来。

请随时向我提供更多详情。听起来像一个有趣的教育项目,我很乐意帮助:)

我只是在发布之前再次阅读您的文章,并且您的问题可能会更容易。作为一个Windows程序,您的目标假定fbo 0在开始渲染帧时被绑定。只要做到这一点。在完成之前,它会切换回fbo 0.不要做推/流行的事情。只要确保你的fbo尺寸相同(窗口更大也可以),它期望的。你需要钩住BindFramebuffer。每当你的游戏调用绑定0,你需要绑定你的。但是没有必要像触摸视口那样做其他任何事情,游戏必须照顾到这一点。当你最终渲染你的swapbuffers钩子时,只要确保恢复任何你改变的东西。

另外你真的不需要DrawBuffers调用。它混淆了我的痣,可能也是你的目标。 :)你的目标是GL 1.2代码,你正在做3 ... 4 ...

1

从我的经验来看,你尝试做的事情对于无遮影的GL1.x应用程序来说非常有可能采用GL拦截方法。

由于我快速入侵,您现在可以忘记FBO,只需在原始应用程序交换缓冲区时使用glCopyTexSubImage(),即可将渲染内容复制到纹理中,并为您的后处理功能编写文本。

有些事情你应该照顾,做这样的GL注射方法时:

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

我可以向你保证,将纹理注入到一些旧的GL应用程序中的确是可能的,就像我自己做的那样。与Quake3和其他时代的着名游戏工作得非常好。我目前的做法没有看到obviuos错误。我有几件事情:你也应该拦截glDrawBuffer(),并将应用程序将要做的任何绘制到GL_BACK(假设这里是双缓冲应用程序)到你的颜色附件。你发布的日志并没有追踪这个功能,所以游戏在启动时可能会设置一次。我也不明白你为什么使用glFlush。这对任何事情都不是一个好的标记。如果游戏使用双缓冲,则SwapBuffers将是您可以获得的“帧”的最佳近似值。只需在创建上下文之后直接插入你自己的init对象创建内容(你也可以拦截wglCreateCintext()和朋友),并且 已经设置了对FBO的渲染。在SwapBuffers上,只需使用着色器将纹理的内容绘制到真实的后台缓冲区,执行真正的缓冲区交换,然后将渲染恢复到FBO设置。

我不知道你是否已经知道它,但是开源项目Chromium确实为OpenGL拦截提供了一个很好的框架。虽然这可能是分布式集群渲染的重点,但它也可以在全局设置中工作,并允许您编写SPU(流处理单元),您可以在其中简单地指定要挂接的任何GL函数的C函数。这个项目已经过时了,而且从5年左右开始就没有积极的开发,因为它被设计的GL流操作类型不适用于更现代化的可编程管道,但它仍然可以与良好的旧GL1一起使用。 x固定功能管道...