diff --git a/Resources/Shaders/PostFilters/TemporalAA.fs b/Resources/Shaders/PostFilters/TemporalAA.fs index e817179bf..02bafb2e0 100644 --- a/Resources/Shaders/PostFilters/TemporalAA.fs +++ b/Resources/Shaders/PostFilters/TemporalAA.fs @@ -20,12 +20,13 @@ uniform sampler2D inputTexture; +uniform sampler2D depthTexture; uniform sampler2D previousTexture; uniform sampler2D processedInputTexture; uniform vec2 inverseVP; +uniform mat4 reprojectionMatrix; varying vec2 texCoord; -varying vec3 reprojectedTexCoord; /* UE4-style temporal AA. Implementation is based on my ShaderToy submission */ // YUV-RGB conversion routine from Hyper3D @@ -49,12 +50,41 @@ vec3 decodePalYuv(vec3 yuv) void main() { - vec4 lastColor = texture2D(previousTexture, reprojectedTexCoord.xy / reprojectedTexCoord.z); - + // ------------------------------------------------------------------------ + // Reprojection + // + // Calulate the Z position of the current pixel. Take the minimum Z value + // of the neighboring pixels to preserve the antialiasing of foreground + // objects. + vec2 off = inverseVP; + float inputZ0 = texture2D(depthTexture, texCoord).x; + float inputZ1 = texture2D(depthTexture, texCoord + vec2(+off.x, 0.0)).x; + float inputZ2 = texture2D(depthTexture, texCoord + vec2(-off.x, 0.0)).x; + float inputZ3 = texture2D(depthTexture, texCoord + vec2(0.0, +off.y)).x; + float inputZ4 = texture2D(depthTexture, texCoord + vec2(0.0, -off.y)).x; + float inputZ5 = texture2D(depthTexture, texCoord + vec2(+off.x, +off.y)).x; + float inputZ6 = texture2D(depthTexture, texCoord + vec2(-off.x, +off.y)).x; + float inputZ7 = texture2D(depthTexture, texCoord + vec2(+off.x, -off.y)).x; + float inputZ8 = texture2D(depthTexture, texCoord + vec2(-off.x, -off.y)).x; + float inputZ = min(min(min(inputZ0, inputZ1), min(inputZ2, inputZ3)), + min(min(inputZ4, inputZ5), min(inputZ6, min(inputZ7, inputZ8)))); + + // Predict where the point was in the previous frame. The Z range [0, 0.1] + // is for a view weapon, so assume no movement in this range. + vec4 reprojectedTexCoord; + if (inputZ < 0.1) { + reprojectedTexCoord.xy = texCoord.xy; + } else { + reprojectedTexCoord = reprojectionMatrix * vec4(texCoord, inputZ, 1.0); + reprojectedTexCoord.xy /= reprojectedTexCoord.w; + } + + vec4 lastColor = texture2D(previousTexture, reprojectedTexCoord.xy); + + // ------------------------------------------------------------------------ vec3 antialiased = lastColor.xyz; float mixRate = min(lastColor.w, 0.5); - vec2 off = inverseVP; vec3 in0 = texture2D(processedInputTexture, texCoord).xyz; antialiased = mix(antialiased, in0, mixRate); diff --git a/Resources/Shaders/PostFilters/TemporalAA.vs b/Resources/Shaders/PostFilters/TemporalAA.vs index a38baa219..4d77072ff 100644 --- a/Resources/Shaders/PostFilters/TemporalAA.vs +++ b/Resources/Shaders/PostFilters/TemporalAA.vs @@ -23,8 +23,6 @@ attribute vec2 positionAttribute; varying vec2 texCoord; -varying vec3 reprojectedTexCoord; -uniform mat4 reprojectionMatrix; void main() { vec2 pos = positionAttribute; @@ -34,9 +32,5 @@ void main() { gl_Position = vec4(scrPos, 0.5, 1.); texCoord = pos; - - reprojectedTexCoord = (reprojectionMatrix * vec4(scrPos, 1., 0.)).xyz; - reprojectedTexCoord.xy *= 0.5; - reprojectedTexCoord.xy += vec2(reprojectedTexCoord.z) * .5; } diff --git a/Sources/Draw/GLTemporalAAFilter.cpp b/Sources/Draw/GLTemporalAAFilter.cpp index 384d52b55..30f18261a 100644 --- a/Sources/Draw/GLTemporalAAFilter.cpp +++ b/Sources/Draw/GLTemporalAAFilter.cpp @@ -36,6 +36,7 @@ namespace spades { namespace draw { GLTemporalAAFilter::GLTemporalAAFilter(GLRenderer *renderer) : renderer(renderer) { prevMatrix = Matrix4::Identity(); + prevViewOrigin = Vector3(0.0f, 0.0f, 0.0f); program = renderer->RegisterProgram("Shaders/PostFilters/TemporalAA.program"); // Preload @@ -62,25 +63,65 @@ namespace spades { IGLDevice *dev = renderer->GetGLDevice(); GLQuadRenderer qr(dev); - // Compute the reprojection matrix + // Calculate the current view-projection matrix. const client::SceneDefinition &def = renderer->GetSceneDef(); Matrix4 newMatrix = Matrix4::Identity(); Vector3 axes[] = {def.viewAxis[0], def.viewAxis[1], def.viewAxis[2]}; - axes[0] /= std::tan(def.fovX * 0.5f); - axes[1] /= std::tan(def.fovY * 0.5f); newMatrix.m[0] = axes[0].x; newMatrix.m[1] = axes[1].x; - newMatrix.m[2] = axes[2].x; + newMatrix.m[2] = -axes[2].x; newMatrix.m[4] = axes[0].y; newMatrix.m[5] = axes[1].y; - newMatrix.m[6] = axes[2].y; + newMatrix.m[6] = -axes[2].y; newMatrix.m[8] = axes[0].z; newMatrix.m[9] = axes[1].z; - newMatrix.m[10] = axes[2].z; + newMatrix.m[10] = -axes[2].z; + + Matrix4 projectionMatrix; + { + // From `GLRenderer::BuildProjectionMatrix` + float near = def.zNear; + float far = def.zFar; + float t = near * std::tan(def.fovY * .5f); + float r = near * std::tan(def.fovX * .5f); + float a = r * 2.f, b = t * 2.f, c = far - near; + Matrix4 &mat = projectionMatrix; + mat.m[0] = near * 2.f / a; + mat.m[1] = 0.f; + mat.m[2] = 0.f; + mat.m[3] = 0.f; + mat.m[4] = 0.f; + mat.m[5] = near * 2.f / b; + mat.m[6] = 0.f; + mat.m[7] = 0.f; + mat.m[8] = 0.f; + mat.m[9] = 0.f; + mat.m[10] = -(far + near) / c; + mat.m[11] = -1.f; + mat.m[12] = 0.f; + mat.m[13] = 0.f; + mat.m[14] = -(far * near * 2.f) / c; + mat.m[15] = 0.f; + } + + newMatrix = projectionMatrix * newMatrix; + + // In `y = newMatrix * x`, the coordinate space `y` belongs to must + // cover the clip region by range `[0, 1]` (like texture coordinates) + // instead of `[-1, 1]` (like OpenGL clip coordinates) + newMatrix = Matrix4::Translate(1.0f, 1.0f, 1.0f) * newMatrix; + newMatrix = Matrix4::Scale(0.5f, 0.5f, 0.5f) * newMatrix; + // Camera translation must be incorporated into the calculation + // separately to avoid numerical errors. (You'd be suprised to see + // how visible the visual artifacts can be.) + Matrix4 translationMatrix = Matrix4::Translate(def.viewOrigin - prevViewOrigin); + + // Compute the reprojection matrix Matrix4 inverseNewMatrix = newMatrix.Inversed(); - Matrix4 diffMatrix = prevMatrix * inverseNewMatrix; + Matrix4 diffMatrix = prevMatrix * translationMatrix * inverseNewMatrix; prevMatrix = newMatrix; + prevViewOrigin = def.viewOrigin; if (!historyBuffer.valid || historyBuffer.width != input.GetWidth() || historyBuffer.height != input.GetHeight()) { @@ -155,10 +196,9 @@ namespace spades { return GLFXAAFilter{renderer}.Filter(input); }() : input; */ - // TODO: take camera translation (not just rotation) into account during reprojection - static GLProgramAttribute positionAttribute("positionAttribute"); static GLProgramUniform inputTexture("inputTexture"); + static GLProgramUniform depthTexture("depthTexture"); static GLProgramUniform previousTexture("previousTexture"); static GLProgramUniform processedInputTexture("processedInputTexture"); static GLProgramUniform reprojectionMatrix("reprojectionMatrix"); @@ -168,6 +208,7 @@ namespace spades { positionAttribute(program); inputTexture(program); + depthTexture(program); previousTexture(program); processedInputTexture(program); reprojectionMatrix(program); @@ -178,6 +219,7 @@ namespace spades { inputTexture.SetValue(0); previousTexture.SetValue(1); processedInputTexture.SetValue(2); + depthTexture.SetValue(3); reprojectionMatrix.SetValue(diffMatrix); inverseVP.SetValue(1.f / input.GetWidth(), 1.f / input.GetHeight()); @@ -190,6 +232,9 @@ namespace spades { dev->BindTexture(IGLDevice::Texture2D, historyBuffer.texture); dev->ActiveTexture(2); dev->BindTexture(IGLDevice::Texture2D, processedInput.GetTexture()); + dev->ActiveTexture(3); + dev->BindTexture(IGLDevice::Texture2D, + renderer->GetFramebufferManager()->GetDepthTexture()); dev->ActiveTexture(0); dev->BindFramebuffer(IGLDevice::Framebuffer, output.GetFramebuffer()); dev->Viewport(0, 0, output.GetWidth(), output.GetHeight()); diff --git a/Sources/Draw/GLTemporalAAFilter.h b/Sources/Draw/GLTemporalAAFilter.h index d1c2677b7..00b3423ae 100644 --- a/Sources/Draw/GLTemporalAAFilter.h +++ b/Sources/Draw/GLTemporalAAFilter.h @@ -45,6 +45,7 @@ namespace spades { } historyBuffer; Matrix4 prevMatrix; + Vector3 prevViewOrigin; std::size_t jitterTableIndex = 0; void DeleteHistoryBuffer();