mirror of
https://github.com/yquake2/yquake2remaster.git
synced 2024-11-22 12:41:21 +00:00
GL3: Implement underwater postprocess effect using framebuffer object
Can be disabled with gl3_usefbo 0. Mostly this adds an underwater warping effect, like the soft-renderer, and also like the vulkan renderer (the shader is based on the vk one). When this is enabled, the v_blend effect (for fullscreen overlay with one color, like when hit or to make the screen white-ish when under water) is now applied in the shader used for rendering the FBO instead of rendering a fullscreen quad in blendmode.
This commit is contained in:
parent
4db6534ab3
commit
3d619a1441
5 changed files with 240 additions and 10 deletions
|
@ -249,6 +249,27 @@ GL3_Draw_TileClear(int x, int y, int w, int h, char *pic)
|
|||
drawTexturedRectangle(x, y, w, h, x/64.0f, y/64.0f, (x+w)/64.0f, (y+h)/64.0f);
|
||||
}
|
||||
|
||||
void
|
||||
GL3_DrawFrameBufferObject(int x, int y, int w, int h, GLuint fboTexture, const float v_blend[4])
|
||||
{
|
||||
qboolean underwater = (gl3_newrefdef.rdflags & RDF_UNDERWATER) != 0;
|
||||
gl3ShaderInfo_t* shader = underwater ? &gl3state.si2DpostProcessWater
|
||||
: &gl3state.si2DpostProcess;
|
||||
GL3_UseProgram(shader->shaderProgram);
|
||||
GL3_Bind(fboTexture);
|
||||
|
||||
if(underwater && shader->uniLmScalesOrTime != -1)
|
||||
{
|
||||
glUniform1f(shader->uniLmScalesOrTime, gl3_newrefdef.time);
|
||||
}
|
||||
if(shader->uniVblend != -1)
|
||||
{
|
||||
glUniform4fv(shader->uniVblend, 1, v_blend);
|
||||
}
|
||||
|
||||
drawTexturedRectangle(x, y, w, h, 0, 1, 1, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Fills a box of pixels with a single color
|
||||
*/
|
||||
|
|
|
@ -126,6 +126,7 @@ cvar_t *gl_shadows;
|
|||
cvar_t *gl3_debugcontext;
|
||||
cvar_t *gl3_usebigvbo;
|
||||
cvar_t *r_fixsurfsky;
|
||||
cvar_t *gl3_usefbo;
|
||||
|
||||
// Yaw-Pitch-Roll
|
||||
// equivalent to R_z * R_y * R_x where R_x is the trans matrix for rotating around X axis for aroundXdeg
|
||||
|
@ -258,6 +259,8 @@ GL3_Register(void)
|
|||
r_speeds = ri.Cvar_Get("r_speeds", "0", 0);
|
||||
gl_finish = ri.Cvar_Get("gl_finish", "0", CVAR_ARCHIVE);
|
||||
|
||||
gl3_usefbo = ri.Cvar_Get("gl3_usefbo", "1", CVAR_ARCHIVE); // use framebuffer object for postprocess effects (water)
|
||||
|
||||
#if 0 // TODO!
|
||||
//gl_lefthand = ri.Cvar_Get("hand", "0", CVAR_USERINFO | CVAR_ARCHIVE);
|
||||
//gl_farsee = ri.Cvar_Get("gl_farsee", "0", CVAR_LATCH | CVAR_ARCHIVE);
|
||||
|
@ -601,6 +604,11 @@ GL3_Init(void)
|
|||
|
||||
GL3_SurfInit();
|
||||
|
||||
glGenFramebuffers(1, &gl3state.ppFBO);
|
||||
// the rest for the FBO is done dynamically in GL3_RenderView() so it can
|
||||
// take the viewsize into account (enforce that by setting invalid size)
|
||||
gl3state.ppFBtexWidth = gl3state.ppFBtexHeight = -1;
|
||||
|
||||
R_Printf(PRINT_ALL, "\n");
|
||||
return true;
|
||||
}
|
||||
|
@ -623,6 +631,17 @@ GL3_Shutdown(void)
|
|||
GL3_SurfShutdown();
|
||||
GL3_Draw_ShutdownLocal();
|
||||
GL3_ShutdownShaders();
|
||||
|
||||
// free the postprocessing FBO and its renderbuffer and texture
|
||||
if(gl3state.ppFBrbo != 0)
|
||||
glDeleteRenderbuffers(1, &gl3state.ppFBrbo);
|
||||
if(gl3state.ppFBtex != 0)
|
||||
glDeleteTextures(1, &gl3state.ppFBtex);
|
||||
if(gl3state.ppFBO != 0)
|
||||
glDeleteFramebuffers(1, &gl3state.ppFBO);
|
||||
gl3state.ppFBrbo = gl3state.ppFBtex = gl3state.ppFBO = 0;
|
||||
gl3state.ppFBObound = false;
|
||||
gl3state.ppFBtexWidth = gl3state.ppFBtexHeight = -1;
|
||||
}
|
||||
|
||||
/* shutdown OS specific OpenGL stuff like contexts, etc. */
|
||||
|
@ -1302,6 +1321,8 @@ GL3_MYgluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zF
|
|||
return ret;
|
||||
}
|
||||
|
||||
static void GL3_Clear(void);
|
||||
|
||||
static void
|
||||
SetupGL(void)
|
||||
{
|
||||
|
@ -1332,7 +1353,67 @@ SetupGL(void)
|
|||
}
|
||||
#endif // 0
|
||||
|
||||
glViewport(x, y2, w, h);
|
||||
|
||||
|
||||
// set up the FBO accordingly, but only if actually rendering the world
|
||||
// (=> don't use FBO when rendering the playermodel in the player menu)
|
||||
if (gl3_usefbo->value && (gl3_newrefdef.rdflags & RDF_NOWORLDMODEL) == 0 && gl3state.ppFBO != 0)
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, gl3state.ppFBO);
|
||||
gl3state.ppFBObound = true;
|
||||
if(gl3state.ppFBtex == 0)
|
||||
{
|
||||
gl3state.ppFBtexWidth = -1; // make sure we generate the texture storage below
|
||||
glGenTextures(1, &gl3state.ppFBtex);
|
||||
}
|
||||
|
||||
if(gl3state.ppFBrbo == 0)
|
||||
{
|
||||
gl3state.ppFBtexWidth = -1; // make sure we generate the RBO storage below
|
||||
glGenRenderbuffers(1, &gl3state.ppFBrbo);
|
||||
}
|
||||
|
||||
// even if the FBO already has a texture and RBO, the viewport size
|
||||
// might have changed so they need to be regenerated with the correct sizes
|
||||
if(gl3state.ppFBtexWidth != w || gl3state.ppFBtexHeight != h)
|
||||
{
|
||||
gl3state.ppFBtexWidth = w;
|
||||
gl3state.ppFBtexHeight = h;
|
||||
GL3_Bind(gl3state.ppFBtex);
|
||||
// create texture for FBO with size of the viewport
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
GL3_Bind(0);
|
||||
// attach it to currently bound FBO
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl3state.ppFBtex, 0);
|
||||
|
||||
// also create a renderbuffer object so the FBO has a stencil- and depth-buffer
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, gl3state.ppFBrbo);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, w, h);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
// attach it to the FBO
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
|
||||
GL_RENDERBUFFER, gl3state.ppFBrbo);
|
||||
|
||||
GLenum fbState = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
if(fbState != GL_FRAMEBUFFER_COMPLETE)
|
||||
{
|
||||
R_Printf(PRINT_ALL, "GL3 SetupGL(): WARNING: FBO is not complete, status = 0x%x\n", fbState);
|
||||
gl3state.ppFBtexWidth = -1; // to try again next frame; TODO: maybe give up?
|
||||
gl3state.ppFBObound = false;
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
}
|
||||
|
||||
GL3_Clear(); // clear the FBO that's bound now
|
||||
|
||||
glViewport(0, 0, w, h); // this will be moved to the center later, so no x/y offset
|
||||
}
|
||||
else // rendering directly (not to FBO for postprocessing)
|
||||
{
|
||||
glViewport(x, y2, w, h);
|
||||
}
|
||||
|
||||
/* set up projection matrix (eye coordinates -> clip coordinates) */
|
||||
{
|
||||
|
@ -1637,13 +1718,23 @@ GL3_RenderFrame(refdef_t *fd)
|
|||
{
|
||||
GL3_RenderView(fd);
|
||||
GL3_SetLightLevel(NULL);
|
||||
qboolean usedFBO = gl3state.ppFBObound; // if it was/is used this frame
|
||||
if(usedFBO)
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0); // now render to default framebuffer
|
||||
gl3state.ppFBObound = false;
|
||||
}
|
||||
GL3_SetGL2D();
|
||||
|
||||
if(v_blend[3] != 0.0f)
|
||||
int x = (vid.width - gl3_newrefdef.width)/2;
|
||||
int y = (vid.height - gl3_newrefdef.height)/2;
|
||||
if (usedFBO)
|
||||
{
|
||||
// if we're actually drawing the world and using an FBO, render the FBO's texture
|
||||
GL3_DrawFrameBufferObject(x, y, gl3_newrefdef.width, gl3_newrefdef.height, gl3state.ppFBtex, v_blend);
|
||||
}
|
||||
else if(v_blend[3] != 0.0f)
|
||||
{
|
||||
int x = (vid.width - gl3_newrefdef.width)/2;
|
||||
int y = (vid.height - gl3_newrefdef.height)/2;
|
||||
|
||||
GL3_Draw_Flash(v_blend, x, y, gl3_newrefdef.width, gl3_newrefdef.height);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -217,6 +217,87 @@ static const char* fragmentSrc2D = MULTILINE_STRING(#version 150\n
|
|||
}
|
||||
);
|
||||
|
||||
static const char* fragmentSrc2Dpostprocess = MULTILINE_STRING(#version 150\n
|
||||
in vec2 passTexCoord;
|
||||
|
||||
// for UBO shared between all shaders (incl. 2D)
|
||||
// TODO: not needed here, remove?
|
||||
layout (std140) uniform uniCommon
|
||||
{
|
||||
float gamma;
|
||||
float intensity;
|
||||
float intensity2D; // for HUD, menu etc
|
||||
|
||||
vec4 color;
|
||||
};
|
||||
|
||||
uniform sampler2D tex;
|
||||
uniform vec4 v_blend;
|
||||
|
||||
out vec4 outColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
// no gamma or intensity here, it has been applied before
|
||||
// (this is just for postprocessing)
|
||||
vec4 res = texture(tex, passTexCoord);
|
||||
// apply the v_blend, usually blended as a colored quad with:
|
||||
// glBlendEquation(GL_FUNC_ADD); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
res.rgb = v_blend.a * v_blend.rgb + (1.0 - v_blend.a)*res.rgb;
|
||||
outColor = res;
|
||||
}
|
||||
);
|
||||
|
||||
static const char* fragmentSrc2DpostprocessWater = MULTILINE_STRING(#version 150\n
|
||||
in vec2 passTexCoord;
|
||||
|
||||
// for UBO shared between all shaders (incl. 2D)
|
||||
// TODO: not needed here, remove?
|
||||
layout (std140) uniform uniCommon
|
||||
{
|
||||
float gamma;
|
||||
float intensity;
|
||||
float intensity2D; // for HUD, menu etc
|
||||
|
||||
vec4 color;
|
||||
};
|
||||
|
||||
const float PI = 3.14159265358979323846;
|
||||
|
||||
uniform sampler2D tex;
|
||||
|
||||
uniform float time;
|
||||
uniform vec4 v_blend;
|
||||
|
||||
out vec4 outColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 uv = passTexCoord;
|
||||
|
||||
// warping based on vkquake2
|
||||
// here uv is always between 0 and 1 so ignore all that scrWidth and gl_FragCoord stuff
|
||||
//float sx = pc.scale - abs(pc.scrWidth / 2.0 - gl_FragCoord.x) * 2.0 / pc.scrWidth;
|
||||
//float sy = pc.scale - abs(pc.scrHeight / 2.0 - gl_FragCoord.y) * 2.0 / pc.scrHeight;
|
||||
float sx = 1.0 - abs(0.5-uv.x)*2.0;
|
||||
float sy = 1.0 - abs(0.5-uv.y)*2.0;
|
||||
float xShift = 2.0 * time + uv.y * PI * 10;
|
||||
float yShift = 2.0 * time + uv.x * PI * 10;
|
||||
vec2 distortion = vec2(sin(xShift) * sx, sin(yShift) * sy) * 0.00666;
|
||||
|
||||
uv += distortion;
|
||||
uv = clamp(uv, vec2(0.0, 0.0), vec2(1.0, 1.0));
|
||||
|
||||
// no gamma or intensity here, it has been applied before
|
||||
// (this is just for postprocessing)
|
||||
vec4 res = texture(tex, uv);
|
||||
// apply the v_blend, usually blended as a colored quad with:
|
||||
// glBlendEquation(GL_FUNC_ADD); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
res.rgb = v_blend.a * v_blend.rgb + (1.0 - v_blend.a)*res.rgb;
|
||||
outColor = res;
|
||||
}
|
||||
);
|
||||
|
||||
// 2D color only rendering, GL3_Draw_Fill(), GL3_Draw_FadeScreen()
|
||||
static const char* vertexSrc2Dcolor = MULTILINE_STRING(#version 150\n
|
||||
|
||||
|
@ -827,7 +908,8 @@ initShader2D(gl3ShaderInfo_t* shaderInfo, const char* vertSrc, const char* fragS
|
|||
|
||||
//shaderInfo->uniColor = shaderInfo->uniProjMatrix = shaderInfo->uniModelViewMatrix = -1;
|
||||
shaderInfo->shaderProgram = 0;
|
||||
shaderInfo->uniLmScales = -1;
|
||||
shaderInfo->uniLmScalesOrTime = -1;
|
||||
shaderInfo->uniVblend = -1;
|
||||
|
||||
shaders2D[0] = CompileShader(GL_VERTEX_SHADER, vertSrc, NULL);
|
||||
if(shaders2D[0] == 0) return false;
|
||||
|
@ -894,6 +976,18 @@ initShader2D(gl3ShaderInfo_t* shaderInfo, const char* vertSrc, const char* fragS
|
|||
goto err_cleanup;
|
||||
}
|
||||
|
||||
shaderInfo->uniLmScalesOrTime = glGetUniformLocation(prog, "time");
|
||||
if(shaderInfo->uniLmScalesOrTime != -1)
|
||||
{
|
||||
glUniform1f(shaderInfo->uniLmScalesOrTime, 0.0f);
|
||||
}
|
||||
|
||||
shaderInfo->uniVblend = glGetUniformLocation(prog, "v_blend");
|
||||
if(shaderInfo->uniVblend != -1)
|
||||
{
|
||||
glUniform4f(shaderInfo->uniVblend, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
err_cleanup:
|
||||
|
@ -917,7 +1011,8 @@ initShader3D(gl3ShaderInfo_t* shaderInfo, const char* vertSrc, const char* fragS
|
|||
}
|
||||
|
||||
shaderInfo->shaderProgram = 0;
|
||||
shaderInfo->uniLmScales = -1;
|
||||
shaderInfo->uniLmScalesOrTime = -1;
|
||||
shaderInfo->uniVblend = -1;
|
||||
|
||||
shaders3D[0] = CompileShader(GL_VERTEX_SHADER, vertexCommon3D, vertSrc);
|
||||
if(shaders3D[0] == 0) return false;
|
||||
|
@ -1017,7 +1112,7 @@ initShader3D(gl3ShaderInfo_t* shaderInfo, const char* vertSrc, const char* fragS
|
|||
}
|
||||
|
||||
GLint lmScalesLoc = glGetUniformLocation(prog, "lmScales");
|
||||
shaderInfo->uniLmScales = lmScalesLoc;
|
||||
shaderInfo->uniLmScalesOrTime = lmScalesLoc;
|
||||
if(lmScalesLoc != -1)
|
||||
{
|
||||
shaderInfo->lmScales[0] = HMM_Vec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
|
@ -1102,6 +1197,17 @@ static qboolean createShaders(void)
|
|||
return false;
|
||||
}
|
||||
|
||||
if(!initShader2D(&gl3state.si2DpostProcess, vertexSrc2D, fragmentSrc2Dpostprocess))
|
||||
{
|
||||
R_Printf(PRINT_ALL, "WARNING: Failed to create shader program to render framebuffer object!\n");
|
||||
return false;
|
||||
}
|
||||
if(!initShader2D(&gl3state.si2DpostProcessWater, vertexSrc2D, fragmentSrc2DpostprocessWater))
|
||||
{
|
||||
R_Printf(PRINT_ALL, "WARNING: Failed to create shader program to render framebuffer object under water!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* lightmappedFrag = (gl3_colorlight->value == 0.0f)
|
||||
? fragmentSrc3DlmNoColor : fragmentSrc3Dlm;
|
||||
|
||||
|
|
|
@ -332,7 +332,7 @@ UpdateLMscales(const hmm_vec4 lmScales[MAX_LIGHTMAPS_PER_SURFACE], gl3ShaderInfo
|
|||
|
||||
if(hasChanged)
|
||||
{
|
||||
glUniform4fv(si->uniLmScales, MAX_LIGHTMAPS_PER_SURFACE, si->lmScales[0].Elements);
|
||||
glUniform4fv(si->uniLmScalesOrTime, MAX_LIGHTMAPS_PER_SURFACE, si->lmScales[0].Elements);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -116,7 +116,8 @@ typedef struct
|
|||
typedef struct
|
||||
{
|
||||
GLuint shaderProgram;
|
||||
GLint uniLmScales;
|
||||
GLint uniVblend;
|
||||
GLint uniLmScalesOrTime; // for 3D it's lmScales, for 2D underwater PP it's time
|
||||
hmm_vec4 lmScales[4];
|
||||
} gl3ShaderInfo_t;
|
||||
|
||||
|
@ -196,6 +197,13 @@ typedef struct
|
|||
int currentlightmap; // lightmap_textureIDs[currentlightmap] bound to GL_TEXTURE1
|
||||
GLuint currenttmu; // GL_TEXTURE0 or GL_TEXTURE1
|
||||
|
||||
// FBO for postprocess effects (like under-water-warping)
|
||||
GLuint ppFBO;
|
||||
GLuint ppFBtex; // ppFBO's texture for color buffer
|
||||
int ppFBtexWidth, ppFBtexHeight;
|
||||
GLuint ppFBrbo; // ppFBO's renderbuffer object for depth and stencil buffer
|
||||
qboolean ppFBObound; // is it currently bound (rendered to)?
|
||||
|
||||
//float camera_separation;
|
||||
//enum stereo_modes stereo_mode;
|
||||
|
||||
|
@ -208,6 +216,9 @@ typedef struct
|
|||
// NOTE: make sure si2D is always the first shaderInfo (or adapt GL3_ShutdownShaders())
|
||||
gl3ShaderInfo_t si2D; // shader for rendering 2D with textures
|
||||
gl3ShaderInfo_t si2Dcolor; // shader for rendering 2D with flat colors
|
||||
gl3ShaderInfo_t si2DpostProcess; // shader to render postprocess FBO, when *not* underwater
|
||||
gl3ShaderInfo_t si2DpostProcessWater; // shader to apply water-warp postprocess effect
|
||||
|
||||
gl3ShaderInfo_t si3Dlm; // a regular opaque face (e.g. from brush) with lightmap
|
||||
// TODO: lm-only variants for gl_lightmap 1
|
||||
gl3ShaderInfo_t si3Dtrans; // transparent is always w/o lightmap
|
||||
|
@ -393,6 +404,7 @@ extern void GL3_Draw_PicScaled(int x, int y, char *pic, float factor);
|
|||
extern void GL3_Draw_StretchPic(int x, int y, int w, int h, char *pic);
|
||||
extern void GL3_Draw_CharScaled(int x, int y, int num, float scale);
|
||||
extern void GL3_Draw_TileClear(int x, int y, int w, int h, char *pic);
|
||||
extern void GL3_DrawFrameBufferObject(int x, int y, int w, int h, GLuint fboTexture, const float v_blend[4]);
|
||||
extern void GL3_Draw_Fill(int x, int y, int w, int h, int c);
|
||||
extern void GL3_Draw_FadeScreen(void);
|
||||
extern void GL3_Draw_Flash(const float color[4], float x, float y, float w, float h);
|
||||
|
|
Loading…
Reference in a new issue