Skip to content

Instantly share code, notes, and snippets.

@tyhenry
Created November 27, 2018 16:04
Show Gist options
  • Save tyhenry/94638b597d17455db7c6254f5ad5d0f5 to your computer and use it in GitHub Desktop.
Save tyhenry/94638b597d17455db7c6254f5ad5d0f5 to your computer and use it in GitHub Desktop.
LUT shader - oF
#pragma once
#include "ofMain.h"
#include "TextureFilter.h"
namespace TextureFilter {
class Lut3DFilter : public TextureFilterBase {
public:
Lut3DFilter() {}
~Lut3DFilter() {}
bool setup(string cubeFile, int lutSize = 64) {
// Load the LUT
vector<RGB> LUT;
LUTpath = ofToDataPath(cubeFile, true);
LUTsize = lutSize;
ifstream LUTfile(LUTpath.c_str());
// read from file
while (!LUTfile.eof()) {
string LUTline;
getline(LUTfile, LUTline);
if (LUTline.empty()) continue;
RGB line;
if (sscanf(LUTline.c_str(), "%f %f %f", &line.r, &line.g, &line.b) == 3) LUT.push_back(line);
}
if (LUT.size() != (pow(LUTsize, 3.0))) {
LUT.clear();
ofLogError("Lut3DFilter") << "Error loading CUBE file: " << LUTpath << " - LUT size is incorrect.";
return false;
}
// create 3d texture
// Reference from http://content.gpwiki.org/index.php/OpenGL:Tutorials:3D_Textures
glEnable(GL_TEXTURE_3D);
glGenTextures(1, &texture3D);
glBindTexture(GL_TEXTURE_3D, texture3D);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_REPEAT);
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB, LUTsize, LUTsize, LUTsize, 0, GL_RGB,
GL_FLOAT, &LUT[0]);
glBindTexture(GL_TEXTURE_3D, 0);
glDisable(GL_TEXTURE_3D);
ofLogNotice("Lut3DFilter") << "Loaded " << LUTpath << " to 3D texture with size " << LUTsize;
return linkShader();
}
private:
// LUT
GLuint texture3D;
int LUTsize = 64;
string LUTpath;
struct RGB { float r, g, b; };
bool filterSrcToDst(ofFbo* src, ofFbo* dst) override
{
if (!src || !dst) return false;
dst->begin();
ofClear(0);
shader.begin();
ofTexture& tex0 = src->getTexture();
shader.setUniformTexture("u_tex0", tex0, 0);
shader.setUniformTexture("lutTexture", GL_TEXTURE_3D, texture3D, 1);
shader.setUniform1f("lutSize", LUTsize);
plane.draw();
shader.end();
dst->end();
return true;
}
string fragmentShader() override
{
return versionSrc + fragShaderSrc;
}
// LUT 3D FILTER SHADER SOURCE
// fragment shader
const string fragShaderSrc = STRINGIFY(
uniform sampler2D u_tex0;
uniform sampler3D lutTexture;
varying vec2 texCoordVarying;
uniform float lutSize;
void main() {
// Based on "GPU Gems 2 � Chapter 24. Using Lookup Tables to Accelerate Color Transformations";
// More info and credits @ http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter24.html
vec4 rawColor = texture2D(u_tex0, texCoordVarying);
// Compute the 3D LUT lookup scale/offset factor;
vec3 scale = vec3((lutSize - 1.0) / lutSize);
vec3 offset = vec3(1.0 / (2.0 * lutSize));
// ****** Apply 3D LUT color transform! **************;
// This is our dependent texture read; The 3D texture's;
// lookup coordinates are dependent on the;
// previous texture read's result;
vec3 color = texture3D(lutTexture, scale * rawColor.rgb + offset).rgb;
//vec3 color = vec3(texCoordVarying.x,texCoordVarying.y,0.);
gl_FragColor = vec4(color, rawColor.a);
}
);
};
}
#include "TextureFilter.h"
namespace TextureFilter {
void TextureFilterBase::applyFilter(const ofTexture & input, ofFbo & output, int nPasses)
{
allocatePingpongs(input);
setupPlane(input);
ofFbo* src = &pingpong[0];
ofFbo* dst = &pingpong[1];
// init src
src->begin();
ofClear(0);
input.draw(0, 0, src->getWidth(), src->getHeight());
src->end();
// ping pong
for (int i = 0; i < nPasses; i++) {
if (!filterSrcToDst(src, dst)) { // do the shader
ofLogError("TextureFilter") << "unable to run filter at pass: " << i;
break; // break if error
}
if (i == nPasses - 1) break; // last pass don't flip flop
// flipflop
ofFbo* tmp = src;
src = dst;
dst = tmp;
}
// draw to output
allocateFbo(output, input);
output.begin();
ofClear(0);
dst->draw(0, 0, output.getWidth(), output.getHeight());
output.end();
}
bool TextureFilterBase::allocatePingpongs(const ofTexture & input)
{
if (!input.isAllocated()) return false;
bool didIt = false;
for (int i = 0; i < 2; i++) {
didIt = allocateFbo(pingpong[i], input);
}
return didIt;
}
// static function allocates an fbo with texture dims and GL_RGBA
// ---------------------------------------------------------------------------
bool TextureFilterBase::allocateFbo(ofFbo& fbo, const ofTexture& texture) {
if (!texture.isAllocated()) return false;
float w = texture.getWidth();
float h = texture.getHeight();
if (fbo.getWidth() != w || fbo.getHeight() != h || fbo.getTexture().getTextureData().glInternalFormat != GL_RGBA) {
fbo.allocate(w, h, GL_RGBA);
fbo.begin();
ofClear(0);
fbo.end();
return true;
}
return fbo.isAllocated();
}
// set up plane dims + pos to match texture dims
// ---------------------------------------------------------------------------
bool TextureFilterBase::setupPlane(const ofTexture & input)
{
if (!input.isAllocated()) return false;
float w = input.getWidth();
float h = input.getHeight();
if (plane.getWidth() != w || plane.getHeight() != h) { // avoid unnecessary mesh reconstruction
plane.set(w, h, 2, 2); // texture dims and 2x2 grid
}
plane.resetTransform();
plane.setPosition(w * 0.5, h * 0.5, 0); // make sure centered on texture dims
return true;
}
// expands stringified source, making new lines after ';' and '{' and '}'
// ---------------------------------------------------------------------------
string TextureFilterBase::expandLines(const string& src) {
string s = src;
ofStringReplace(s, ";", ";\n");
ofStringReplace(s, "{", "{\n");
ofStringReplace(s, "}", "}\n");
return s;
}
} // end TextureFilter namespace
#pragma once
#include "ofMain.h"
#define STRINGIFY(A) #A
namespace TextureFilter{
class TextureFilterBase {
public:
TextureFilterBase() {}
virtual ~TextureFilterBase() {}
void applyFilter(const ofTexture& input, ofFbo& output, int nPasses); // runs filterSrcToDst() for n passes
const ofTexture& applyFilter(const ofTexture& input, int nPasses) { // stores and returns result
applyFilter(input, result, nPasses);
return result.getTexture();
}
const ofFbo& getResult() { return result; }
virtual void draw(const ofTexture& input, float x, float y, float w, float h, int nPasses) {
applyFilter(input, nPasses);
result.draw(x,y,w,h);
}
virtual void draw(const ofTexture& input, float x, float y, int nPasses) {
applyFilter(input, nPasses);
result.draw(x,y);
}
// links the shader sources
virtual bool linkShader() {
return
(
shader.setupShaderFromSource(GL_VERTEX_SHADER, expandLines(vertexShader())) &&
shader.setupShaderFromSource(GL_FRAGMENT_SHADER, expandLines(fragmentShader())) &&
shader.linkProgram()
);
}
ofShader shader;
protected:
ofFbo pingpong[2], result;
ofPlanePrimitive plane;
bool allocatePingpongs(const ofTexture& input);
bool static allocateFbo(ofFbo& fbo, const ofTexture& texture);
bool setupPlane(const ofTexture& input);
static string expandLines(const string& src); // expands STRINGIFY shader source, adding new lines
// mutable functions:
// runs the shader
virtual bool filterSrcToDst(ofFbo* src, ofFbo* dst)
{
if (!src || !dst) return false;
dst->begin();
ofClear(0);
shader.begin();
ofTexture& tex0 = src->getTexture();
shader.setUniformTexture("u_tex0", tex0, 0);
shader.setUniform2f("u_tex0Resolution", tex0.getWidth(), tex0.getHeight());
plane.draw();
shader.end();
dst->end();
return true;
}
// prepare shader code
virtual string vertexShader() {
return versionSrc + vertShaderSrc;
}
virtual string fragmentShader() {
return versionSrc + expandLines(fragShaderSrc);
}
// DEFAULT SHADER CODE
// GLSL version declaration
const string versionSrc = "#version 120\n";
// vertex shader
const string vertShaderSrc = STRINGIFY(
varying vec2 texCoordVarying;
void main() {
texCoordVarying = gl_MultiTexCoord0.xy;
gl_Position = ftransform();
}
);
// fragment shader
const string fragShaderSrc = STRINGIFY(
uniform sampler2D u_tex0;
uniform vec2 u_tex0Resolution;
varying vec2 texCoordVarying;
void main() {
gl_FragColor = texture2D(u_tex0, texCoordVarying);
}
);
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment