]> git.tdb.fi Git - libs/demoscene.git/blobdiff - source/filmgrain.cpp
Add a film grain postprocessor
[libs/demoscene.git] / source / filmgrain.cpp
diff --git a/source/filmgrain.cpp b/source/filmgrain.cpp
new file mode 100644 (file)
index 0000000..151daf5
--- /dev/null
@@ -0,0 +1,156 @@
+#include <random>
+#include <msp/gl/renderer.h>
+#include <msp/gl/texture2d.h>
+#include "filmgrain.h"
+#include "resources.h"
+
+using namespace std;
+
+namespace Msp {
+namespace DemoScene {
+
+FilmGrain::FilmGrain(unsigned s, unsigned l):
+       mesh(get_fullscreen_quad()),
+       shprog(Resources::get_builtins().get<GL::Program>("filmgrain.glsl")),
+       size(s),
+       layers(l),
+       current_layer(0)
+{
+       grain.set_min_filter(GL::LINEAR);
+       grain.set_mag_filter(GL::LINEAR);
+       grain.set_wrap(GL::REPEAT);
+       grain.storage(GL::R8, size, size, layers, 1);
+
+       generate_grain();
+
+       texturing.attach(1, grain);
+       shdata.uniform("grain", 1);
+
+       set_coarseness(2.0f);
+       set_strength(0.1f);
+}
+
+void FilmGrain::generate_grain()
+{
+       UInt8 *data = new UInt8[size*size*layers];
+
+       unsigned noise_size = size/2;
+       UInt8 *noise = new UInt8[noise_size*noise_size];
+       Int16 *dbuf = new Int16[3*noise_size*noise_size];
+       minstd_rand random;
+
+       for(unsigned i=0; i<layers; ++i)
+       {
+               for(unsigned j=0; j<noise_size*noise_size; ++j)
+                       noise[j] = random();
+
+               bicubic2x(noise, data+i*size*size, dbuf);
+       }
+
+       delete[] noise;
+       delete[] dbuf;
+
+       grain.image(0, GL::RED, GL::UNSIGNED_BYTE, data);
+       delete[] data;
+}
+
+void FilmGrain::bicubic2x(const UInt8 *src, UInt8 *target, Int16 *dbuf)
+{
+       unsigned src_size = size/2;
+
+       // dx and dy will have an implicit scaling factor of 2
+       Int16 *dx = dbuf;
+       Int16 *dy = dbuf+src_size*src_size;
+       for(unsigned y=0; y<src_size; ++y)
+               for(unsigned x=0; x<src_size; ++x)
+               {
+                       unsigned i = x+y*src_size;
+                       dx[i] = src[(x+1)%src_size + y*src_size] - src[(x+src_size-1)%src_size + y*src_size];
+                       dy[i] = src[x + (y+1)%src_size*src_size] - src[x + (y+src_size-1)%src_size*src_size];
+               }
+
+       // dxy will have an implicit scaling factor of 4
+       Int16 *dxy = dbuf+2*src_size*src_size;
+       for(unsigned y=0; y<src_size; ++y)
+               for(unsigned x=0; x<src_size; ++x)
+                       dxy[x+y*src_size] = dy[(x+1)%src_size + y*src_size] - dy[(x+src_size-1)%src_size + y*src_size];
+
+       for(unsigned y=0; y<src_size; ++y)
+       {
+               unsigned y2 = (y+1)%src_size;
+
+               for(unsigned x=0; x<src_size; ++x)
+               {
+                       unsigned x2 = (x+1)%src_size;
+
+                       unsigned i00 = x+y*src_size;
+                       unsigned i10 = x2+y*src_size;
+                       unsigned i01 = x+y2*src_size;
+                       unsigned i11 = x2+y2*src_size;
+
+                       // Cubic hermite at t=0.5: 0.5*p0 + 0.125*m0 + 0.5*p1 - 0.125*m1
+                       unsigned i = x*2+y*2*size;
+                       target[i] = src[i00];
+                       target[i+1] = clamp(((src[i00]+src[i10])*8+dx[i00]-dx[i10])/16);
+                       target[i+size] = clamp(((src[i00]+src[i01])*8+dy[i00]-dy[i01])/16);
+                       // dyh0 and dyh1 will have an implicit scaling factor of 32
+                       Int16 dyh0 = (dy[i00]+dy[i10])*8+dxy[i00]-dxy[i10];
+                       Int16 dyh1 = (dy[i01]+dy[i11])*8+dxy[i01]-dxy[i11];
+                       target[i+1+size] = clamp(((src[i00]+src[i10]+src[i01]+src[i11])*64+dyh0-dyh1)/256);
+               }
+       }
+}
+
+UInt8 FilmGrain::clamp(int v)
+{
+       return min(max(v, 0), 255);
+}
+
+void FilmGrain::set_coarseness(float c)
+{
+       shdata.uniform("coarseness", c/2.0f);
+}
+
+void FilmGrain::set_strength(float s)
+{
+       shdata.uniform("strength", s);
+}
+
+void FilmGrain::render(GL::Renderer &renderer, const GL::Texture2D &color, const GL::Texture2D &)
+{
+       current_layer = (current_layer+1)%layers;
+       shdata.uniform("grain_layer", static_cast<int>(current_layer));
+       texturing.attach(0, color);
+       renderer.set_texturing(&texturing);
+       renderer.set_shader_program(&shprog, &shdata);
+       mesh.draw(renderer);
+}
+
+
+FilmGrain::Template::Template():
+       size(256),
+       layers(16),
+       coarseness(2.0f),
+       strength(0.1f)
+{ }
+
+FilmGrain *FilmGrain::Template::create(unsigned, unsigned) const
+{
+       FilmGrain *grain = new FilmGrain(size, layers);
+       grain->set_coarseness(coarseness);
+       grain->set_strength(strength);
+       return grain;
+}
+
+
+FilmGrain::Template::Loader::Loader(Template &t):
+       DerivedObjectLoader<Template, PostProcessor::Template::Loader>(t)
+{
+       add("coarseness", &Template::coarseness);
+       add("layers", &Template::layers);
+       add("size", &Template::size);
+       add("strength", &Template::strength);
+}
+
+} // namespace DemoScene
+} // namespace Msp