1 /** @file bloom.cpp
2 *
3 * @authors Copyright (c) 2014-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 *
5 * @par License
6 * GPL: http://www.gnu.org/licenses/gpl.html
7 *
8 * <small>This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2 of the License, or (at your
11 * option) any later version. This program is distributed in the hope that it
12 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
14 * Public License for more details. You should have received a copy of the GNU
15 * General Public License along with this program; if not, see:
16 * http://www.gnu.org/licenses</small>
17 */
18
19 #include "render/fx/bloom.h"
20 #include "clientapp.h"
21 #include "world/clientserverworld.h"
22
23 #include <doomsday/console/var.h>
24 #include <de/Drawable>
25 #include <de/GLTextureFramebuffer>
26 #include <de/WindowTransform>
27
28 using namespace de;
29
30 namespace fx {
31
32 static int bloomEnabled = true;
33 static float bloomIntensity = .65f;
34 static float bloomThreshold = .35f;
35 static float bloomDispersion = 1;
36 static int bloomComplexity = 1;
37
DENG2_PIMPL(Bloom)38 DENG2_PIMPL(Bloom)
39 {
40 typedef GLBufferT<Vertex2Tex> VBuf;
41
42 Drawable bloom;
43 GLTextureFramebuffer workFB;
44 GLUniform uMvpMatrix;
45 GLUniform uTex;
46 GLUniform uColor;
47 GLUniform uBlurStep;
48 GLUniform uWindow;
49 GLUniform uThreshold;
50 GLUniform uIntensity;
51
52 Impl(Public *i)
53 : Base(i)
54 , uMvpMatrix("uMvpMatrix", GLUniform::Mat4)
55 , uTex ("uTex", GLUniform::Sampler2D)
56 , uColor ("uColor", GLUniform::Vec4)
57 , uBlurStep ("uBlurStep", GLUniform::Vec2)
58 , uWindow ("uWindow", GLUniform::Vec4)
59 , uThreshold("uThreshold", GLUniform::Float)
60 , uIntensity("uIntensity", GLUniform::Float)
61 {}
62
63 void glInit()
64 {
65 // Geometry for drawing with: a single quad.
66 VBuf *buf = new VBuf;
67 buf->setVertices(gl::TriangleStrip,
68 VBuf::Builder().makeQuad(
69 Rectanglef(0, 0, 1, 1),
70 Rectanglef(0, 0, 1, 1)),
71 gl::Static);
72 bloom.addBuffer(buf);
73
74 // The work buffer does not need alpha because the result will be additively
75 // blended back to the framebuffer.
76 workFB.setColorFormat(Image::RGB_888);
77 workFB.setSampleCount(1);
78 workFB.glInit();
79
80 ClientApp::shaders().build(bloom.program(), "fx.bloom.horizontal")
81 << uMvpMatrix << uTex << uBlurStep << uWindow << uThreshold << uIntensity;
82
83 bloom.addProgram("vert");
84 ClientApp::shaders().build(bloom.program("vert"), "fx.bloom.vertical")
85 << uMvpMatrix << uTex << uBlurStep << uWindow;
86
87 uMvpMatrix = Matrix4f::ortho(0, 1, 0, 1);
88 }
89
90 void glDeinit()
91 {
92 bloom.clear();
93 workFB.glDeinit();
94 }
95
96 /**
97 * Takes the current rendered frame buffer contents and applies bloom on it.
98 */
99 void draw()
100 {
101 GLFramebuffer &target = GLState::current().target();
102 GLTexture *colorTex = target.attachedTexture(GLFramebuffer::Color);
103
104 //qDebug() << "bloom with" << colorTex;
105
106 // Must have access to the color texture containing the frame buffer contents.
107 if (!colorTex) return;
108
109 // Determine the dimensions of the viewport and the target.
110 //Rectanglef const rectf(0, 0, 1, 1); //= GLState::current().normalizedViewport();
111 Vector2ui const targetSize = colorTex->size(); // (rectf.size() * target.rectInUse().size()).toVector2ui();
112
113 // Quarter resolution is used for better efficiency (without significant loss
114 // of quality).
115 Vector2ui blurSize = (targetSize / 4).max(Vector2ui(1, 1));
116
117 // Update the size of the work buffer if needed. Also ensure linear filtering
118 // is used for better-quality blurring.
119 workFB.resize(blurSize);
120 workFB.colorTexture().setFilter(gl::Linear, gl::Linear, gl::MipNone);
121
122 GLState::push()
123 .setDepthWrite(false) // don't mess with depth information
124 .setDepthTest(false);
125
126 switch (bloomComplexity)
127 {
128 case 1:
129 // Two passes result in a better glow effect: combining multiple Gaussian curves
130 // ensures that the middle peak is higher/sharper.
131 drawBloomPass(*colorTex, .5f, .75f);
132 drawBloomPass(*colorTex, 1.f, 1.f);
133 break;
134
135 default:
136 // Single-pass for HW with slow fill rate.
137 drawBloomPass(*colorTex, 1.f, 1.75f);
138 break;
139 }
140
141 GLState::pop();
142 }
143
144 /**
145 * Draws a bloom pass that takes the contents of the framebuffer, applies blurring
146 * and thresholding, and blends the result additively back to the framebuffer.
147 *
148 * @param rectf Normalized viewport rectangle within the target.
149 * @param targetSize Size of the actual area in pixels (affected by target
150 * active rectangle and viewport).
151 * @param colorTarget Texture containing the frame buffer colors.
152 * @param bloomSize Size factor for the effect: at most 1.0; smaller values
153 * cause more blurring/less quality as the work resolution
154 * reduces.
155 * @param weight Weight factor for intensity.
156 * @param targetOp Blending factor (should be gl::One unless debugging).
157 */
158 void drawBloomPass(//Rectanglef const &rectf, //Vector2ui const &/*targetSize*/,
159 GLTexture &colorTarget, float bloomSize, float weight,
160 gl::Blend targetOp = gl::One)
161 {
162 uThreshold = bloomThreshold * (1 + bloomSize) / 2.f;
163 uIntensity = bloomIntensity * weight;
164
165 // Initialize the work buffer for this pass.
166 workFB.clear(GLFramebuffer::Color);
167
168 // Divert rendering to the work area (full or partial area used).
169 //GLFramebuffer &target = GLState::current().target();
170 Vector2ui const workSize = workFB.size() * bloomSize;
171 GLState::push()
172 .setTarget(workFB)
173 .setViewport(Rectangleui::fromSize(workSize));
174
175 // Normalized active rectangle of the target.
176 /*Vector4f const active(target.activeRectScale(),
177 target.activeRectNormalizedOffset());*/
178
179 /*
180 * Draw step #1: thresholding and horizontal blur.
181 */
182 uTex = colorTarget;
183
184 // Window in the color buffer: area occupied by the viewport. Top needs to
185 // be flipped because the shader uses the bottom left corner as UV origin.
186 // Also need to apply the active rectangle as it affects where the viewport
187 // ends up inside the frame buffer.
188 uWindow = Vector4f(0, 0, 1, 1); /*Vector4f(rectf.left() * active.x + active.z,
189 1 - (rectf.bottom() * active.y + active.w),
190 rectf.width() * active.x,
191 rectf.height() * active.y);*/
192
193 // Spread out or contract the texture sampling of the Gaussian blur kernel.
194 // If dispersion is too large, the quality of the blur will suffer.
195 uBlurStep = Vector2f(bloomDispersion / workFB.size().x,
196 bloomDispersion / workFB.size().y);
197
198 bloom.setProgram(bloom.program()); // horizontal shader
199 bloom.draw();
200
201 GLState::pop();
202
203 workFB.resolveSamples();
204
205 /*
206 * Draw step #2: vertical blur and blending back to the real framebuffer.
207 */
208 GLState::push()
209 .setBlend(true)
210 .setBlendFunc(gl::One, targetOp);
211
212 // Use the work buffer's texture as the source.
213 uTex = workFB.colorTexture();
214 uWindow = Vector4f(0, 1 - bloomSize, bloomSize, bloomSize);
215
216 bloom.setProgram("vert"); // vertical shader
217 bloom.draw();
218
219 GLState::pop();
220 }
221 };
222
Bloom(int console)223 Bloom::Bloom(int console)
224 : ConsoleEffect(console)
225 , d(new Impl(this))
226 {}
227
glInit()228 void Bloom::glInit()
229 {
230 d->glInit();
231 ConsoleEffect::glInit();
232 }
233
glDeinit()234 void Bloom::glDeinit()
235 {
236 ConsoleEffect::glDeinit();
237 d->glDeinit();
238 }
239
draw()240 void Bloom::draw()
241 {
242 if (!ClientApp::world().hasMap())
243 {
244 return;
245 }
246
247 if (!bloomEnabled || bloomIntensity <= 0)
248 {
249 return;
250 }
251
252 d->draw();
253 }
254
consoleRegister()255 void Bloom::consoleRegister()
256 {
257 C_VAR_INT ("rend-bloom", &bloomEnabled, 0, 0, 1);
258 C_VAR_FLOAT("rend-bloom-threshold", &bloomThreshold, 0, 0, 1);
259 C_VAR_FLOAT("rend-bloom-intensity", &bloomIntensity, 0, 0, 10);
260 C_VAR_FLOAT("rend-bloom-dispersion", &bloomDispersion, 0, 0, 3.5f);
261 C_VAR_INT ("rend-bloom-complexity", &bloomComplexity, 0, 0, 1);
262 }
263
isEnabled()264 bool Bloom::isEnabled() // static
265 {
266 return bloomEnabled;
267 }
268
intensity()269 float Bloom::intensity()
270 {
271 return bloomIntensity;
272 }
273
274 } // namespace fx
275