You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
451 lines
13 KiB
451 lines
13 KiB
// This file is part of OpenCV project. |
|
// It is subject to the license terms in the LICENSE file found in the top-level directory |
|
// of this distribution and at http://opencv.org/license.html. |
|
// Copyright Amir Hassan (kallaballa) <amir@viel-zu.org> |
|
|
|
#include "opencv2/viz2d/viz2d.hpp" |
|
#include "opencv2/viz2d/util.hpp" |
|
|
|
using std::cerr; |
|
using std::endl; |
|
|
|
constexpr long unsigned int WIDTH = 1920; |
|
constexpr long unsigned int HEIGHT = 1080; |
|
constexpr double FPS = 60; |
|
constexpr bool OFFSCREEN = false; |
|
constexpr const char* OUTPUT_FILENAME = "shader-demo.mkv"; |
|
const unsigned long DIAG = hypot(double(WIDTH), double(HEIGHT)); |
|
|
|
int glow_kernel_size = std::max(int(DIAG / 200 % 2 == 0 ? DIAG / 200 + 1 : DIAG / 200), 1); |
|
|
|
/** mandelbrot control parameters **/ |
|
// Red, green, blue and alpha. All from 0.0f to 1.0f |
|
nanogui::Color base_color_val(0.2f, 0.6f, 1.0f, 1.0f); |
|
// Keep alpha separate for the GUI |
|
float alpha = 1.0f; //0.0-1.0 |
|
//contrast boost |
|
int contrast_boost = 15; //0.0-255 |
|
int max_iterations = 500; |
|
float center_x = -0.119609; |
|
float center_y = 0.13262; |
|
float zoom_factor = 1.0; |
|
float zoom = 1.0; |
|
float zoom_incr = 0.99; |
|
long iterations = 0; |
|
bool manual_navigation = false; |
|
|
|
/** GL uniform handles **/ |
|
GLint base_color_hdl; |
|
GLint contrast_boost_hdl; |
|
GLint max_iterations_hdl; |
|
GLint center_x_hdl; |
|
GLint center_y_hdl; |
|
GLint zoom_hdl; |
|
|
|
/** shader and program handle **/ |
|
GLuint shader_program_hdl; |
|
|
|
#ifndef __EMSCRIPTEN__ |
|
//vertex array |
|
GLuint VAO; |
|
#endif |
|
GLuint VBO, EBO; |
|
|
|
// vertex position, color |
|
float vertices[] = |
|
{ |
|
// x y z |
|
-1.0f, -1.0f, -0.0f, |
|
1.0f, 1.0f, -0.0f, |
|
-1.0f, 1.0f, -0.0f, |
|
1.0f, -1.0f, -0.0f |
|
}; |
|
|
|
unsigned int indices[] = |
|
{ |
|
// 2---,1 |
|
// | .' | |
|
// 0'---3 |
|
0, 1, 2, |
|
0, 3, 1 |
|
}; |
|
|
|
void load_buffer_data(){ |
|
|
|
#ifndef __EMSCRIPTEN__ |
|
glGenVertexArrays(1, &VAO); |
|
glBindVertexArray(VAO); |
|
#endif |
|
|
|
glGenBuffers(1, &VBO); |
|
glGenBuffers(1, &EBO); |
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, VBO); |
|
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); |
|
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); |
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); |
|
|
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); |
|
glEnableVertexAttribArray(0); |
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0); |
|
#ifndef __EMSCRIPTEN__ |
|
glBindVertexArray(0); |
|
#endif |
|
} |
|
|
|
//workaround: required with emscripten + nanogui on every iteration before rendering |
|
void rebind_buffers() { |
|
glBindBuffer(GL_ARRAY_BUFFER, VBO); |
|
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); |
|
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); |
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); |
|
|
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); |
|
glEnableVertexAttribArray(0); |
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0); |
|
} |
|
|
|
GLuint init_shader(const char* vShader, const char* fShader, const char* outputAttributeName) { |
|
struct Shader { |
|
GLenum type; |
|
const char* source; |
|
} shaders[2] = { |
|
{ GL_VERTEX_SHADER, vShader }, |
|
{ GL_FRAGMENT_SHADER, fShader } |
|
}; |
|
|
|
GLuint program = glCreateProgram(); |
|
|
|
for ( int i = 0; i < 2; ++i ) { |
|
Shader& s = shaders[i]; |
|
GLuint shader = glCreateShader( s.type ); |
|
glShaderSource( shader, 1, (const GLchar**) &s.source, NULL ); |
|
glCompileShader( shader ); |
|
|
|
GLint compiled; |
|
glGetShaderiv( shader, GL_COMPILE_STATUS, &compiled ); |
|
if ( !compiled ) { |
|
std::cerr << " failed to compile:" << std::endl; |
|
GLint logSize; |
|
glGetShaderiv( shader, GL_INFO_LOG_LENGTH, &logSize ); |
|
char* logMsg = new char[logSize]; |
|
glGetShaderInfoLog( shader, logSize, NULL, logMsg ); |
|
std::cerr << logMsg << std::endl; |
|
delete [] logMsg; |
|
|
|
exit( EXIT_FAILURE ); |
|
} |
|
|
|
glAttachShader( program, shader ); |
|
} |
|
#ifndef VIZ2D_USE_ES3 |
|
/* Link output */ |
|
glBindFragDataLocation(program, 0, outputAttributeName); |
|
#endif |
|
/* link and error check */ |
|
glLinkProgram(program); |
|
|
|
GLint linked; |
|
glGetProgramiv( program, GL_LINK_STATUS, &linked ); |
|
if ( !linked ) { |
|
std::cerr << "Shader program failed to link" << std::endl; |
|
GLint logSize; |
|
glGetProgramiv( program, GL_INFO_LOG_LENGTH, &logSize); |
|
char* logMsg = new char[logSize]; |
|
glGetProgramInfoLog( program, logSize, NULL, logMsg ); |
|
std::cerr << logMsg << std::endl; |
|
delete [] logMsg; |
|
|
|
exit( EXIT_FAILURE ); |
|
} |
|
|
|
/* use program object */ |
|
glUseProgram(program); |
|
|
|
return program; |
|
} |
|
|
|
//mandelbrot shader code adapted from my own project: https://github.com/kallaballa/FractalDive#after |
|
void load_shader(){ |
|
#ifndef __EMSCRIPTEN__ |
|
const string shaderVersion = "330"; |
|
#else |
|
const string shaderVersion = "300 es"; |
|
#endif |
|
|
|
const string vert = " #version " + shaderVersion + R"( |
|
in vec4 position; |
|
|
|
void main() |
|
{ |
|
gl_Position = vec4(position.xyz, 1.0); |
|
})"; |
|
|
|
const string frag = " #version " + shaderVersion + R"( |
|
precision lowp float; |
|
|
|
out vec4 outColor; |
|
|
|
uniform vec4 base_color; |
|
uniform int contrast_boost; |
|
uniform int max_iterations; |
|
uniform float zoom; |
|
uniform float center_x; |
|
uniform float center_y; |
|
|
|
int get_iterations() |
|
{ |
|
float pointr = (((gl_FragCoord.x / 1080.0f) - 0.5f) * zoom + center_x) * 5.0f; |
|
float pointi = (((gl_FragCoord.y / 1080.0f) - 0.5f) * zoom + center_y) * 5.0f; |
|
const float four = 4.0f; |
|
|
|
int iterations = 0; |
|
float zi = 0.0f; |
|
float zr = 0.0f; |
|
float zrsqr = 0.0f; |
|
float zisqr = 0.0f; |
|
|
|
while (iterations < max_iterations && zrsqr + zisqr < four) { |
|
//equals following line as a consequence of binomial expansion: zi = (((zr + zi)*(zr + zi)) - zrsqr) - zisqr |
|
zi = (zr + zr) * zi; |
|
|
|
zi += pointi; |
|
zr = (zrsqr - zisqr) + pointr; |
|
|
|
zrsqr = zr * zr; |
|
zisqr = zi * zi; |
|
++iterations; |
|
} |
|
return iterations; |
|
} |
|
|
|
vec4 return_color() |
|
{ |
|
int iter = get_iterations(); |
|
if (iter == max_iterations) { |
|
return vec4(0.0f, 0.0f, 0.0f, 0.0f); |
|
} |
|
|
|
float iterations = float(iter) / float(max_iterations); |
|
//convert to float |
|
float cb = float(contrast_boost); |
|
|
|
return vec4(base_color[0] * iterations * cb, base_color[1] * iterations * cb, base_color[2] * iterations * cb, base_color[3]); |
|
} |
|
|
|
void main() |
|
{ |
|
outColor = return_color(); |
|
})"; |
|
|
|
cerr << "##### Vertex Shader #####" << endl; |
|
cerr << vert << endl; |
|
|
|
cerr << "##### Fragment Shader #####" << endl; |
|
cerr << frag << endl; |
|
|
|
shader_program_hdl = init_shader(vert.c_str(), frag.c_str(), "fragColor"); |
|
} |
|
|
|
float easeInOutQuint(float x) { |
|
return x < 0.5f ? 16.0f * x * x * x * x * x : 1.0f - std::pow(-2.0f * x + 2.0f, 5.0f) / 2.0f; |
|
} |
|
|
|
void init_scene(const cv::Size& sz) { |
|
load_shader(); |
|
load_buffer_data(); |
|
|
|
base_color_hdl = glGetUniformLocation(shader_program_hdl, "base_color"); |
|
contrast_boost_hdl = glGetUniformLocation(shader_program_hdl, "contrast_boost"); |
|
max_iterations_hdl = glGetUniformLocation(shader_program_hdl, "max_iterations"); |
|
zoom_hdl = glGetUniformLocation(shader_program_hdl, "zoom"); |
|
center_x_hdl = glGetUniformLocation(shader_program_hdl, "center_x"); |
|
center_y_hdl = glGetUniformLocation(shader_program_hdl, "center_y"); |
|
|
|
glViewport(0, 0, WIDTH, HEIGHT); |
|
} |
|
|
|
void render_scene(const cv::Size& sz) { |
|
// glClearColor(1.0f, 1.0f, 1.0f, 1.0f); |
|
// glClear(GL_COLOR_BUFFER_BIT); |
|
if(zoom >= 1) { |
|
zoom_incr = -0.01; |
|
iterations = 0; |
|
} else if(zoom < 2.5e-06) { |
|
zoom_incr = +0.01; |
|
iterations = 0; |
|
} |
|
|
|
glUseProgram(shader_program_hdl); |
|
glUniform4f(base_color_hdl, base_color_val[0], base_color_val[1], base_color_val[2], alpha); |
|
glUniform1i(contrast_boost_hdl, contrast_boost); |
|
glUniform1i(max_iterations_hdl, max_iterations); |
|
glUniform1f(center_y_hdl, center_y); |
|
glUniform1f(center_x_hdl, center_x); |
|
if(!manual_navigation) { |
|
zoom+=zoom_incr; |
|
glUniform1f(zoom_hdl, easeInOutQuint(zoom)); |
|
} else { |
|
zoom = 1.0 / pow(zoom_factor,5.0f); |
|
glUniform1f(zoom_hdl, zoom); |
|
} |
|
|
|
#ifndef __EMSCRIPTEN__ |
|
glBindVertexArray(VAO); |
|
#endif |
|
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); |
|
} |
|
|
|
void glow_effect(const cv::UMat &src, cv::UMat &dst, const int ksize) { |
|
static cv::UMat resize; |
|
static cv::UMat blur; |
|
static cv::UMat dst16; |
|
|
|
cv::bitwise_not(src, dst); |
|
|
|
//Resize for some extra performance |
|
cv::resize(dst, resize, cv::Size(), 0.5, 0.5); |
|
//Cheap blur |
|
cv::boxFilter(resize, resize, -1, cv::Size(ksize, ksize), cv::Point(-1,-1), true, cv::BORDER_REPLICATE); |
|
//Back to original size |
|
cv::resize(resize, blur, src.size()); |
|
|
|
//Multiply the src image with a blurred version of itself |
|
cv::multiply(dst, blur, dst16, 1, CV_16U); |
|
//Normalize and convert back to CV_8U |
|
cv::divide(dst16, cv::Scalar::all(255.0), dst, 1, CV_8U); |
|
|
|
cv::bitwise_not(dst, dst); |
|
} |
|
|
|
cv::Ptr<cv::viz::Viz2D> v2d = cv::viz::Viz2D::make(cv::Size(WIDTH, HEIGHT), cv::Size(WIDTH, HEIGHT), OFFSCREEN, "Shader Demo"); |
|
|
|
void setup_gui(cv::Ptr<cv::viz::Viz2D> v2d) { |
|
v2d->nanogui([](cv::viz::FormHelper& form){ |
|
form.makeDialog(5, 30, "Fractal"); |
|
|
|
form.makeGroup("Navigation"); |
|
form.makeFormVariable("Iterations", max_iterations, 3, 1000000, true, "", "How deeply to calculate the fractal."); |
|
auto* cxVar = form.makeFormVariable("X", center_x, -1.0f, 1.0f, true, "", "The x location from -1.0 to 1.0"); |
|
cxVar->number_format("%.7g"); |
|
cxVar->set_value_increment(0.0000001); |
|
cxVar->set_callback([&, cxVar](const float& value){ |
|
manual_navigation = true; |
|
cxVar->set_value(value); |
|
center_x = value; |
|
}); |
|
|
|
auto* cyVar = form.makeFormVariable("Y", center_y, -1.0f, 1.0f, true, "", "The y location from -1.0 to 1.0"); |
|
cyVar->number_format("%.7g"); |
|
cyVar->set_value_increment(0.0000001); |
|
cyVar->set_callback([&,cyVar](const float &value) { |
|
manual_navigation = true; |
|
cyVar->set_value(value); |
|
center_y = value; |
|
}); |
|
|
|
auto* czVar = form.makeFormVariable("Zoom", zoom_factor, 1.0f, 1000000.0f, true, "", "How much to zoom in on the fractal"); |
|
czVar->set_callback([&,czVar](const float &value) { |
|
manual_navigation = true; |
|
czVar->set_value(value); |
|
zoom_factor = value; |
|
}); |
|
|
|
#ifndef __EMSCRIPTEN__ |
|
form.makeGroup("Glow"); |
|
auto* kernelSize = form.makeFormVariable("Kernel Size", glow_kernel_size, 1, 127, true, "", "Intensity of glow defined by kernel size"); |
|
kernelSize->set_callback([=](const int& k) { |
|
static int lastKernelSize = glow_kernel_size; |
|
|
|
if(k == lastKernelSize) |
|
return; |
|
|
|
if(k <= lastKernelSize) { |
|
glow_kernel_size = std::max(int(k % 2 == 0 ? k - 1 : k), 1); |
|
} else if(k > lastKernelSize) |
|
glow_kernel_size = std::max(int(k % 2 == 0 ? k + 1 : k), 1); |
|
|
|
lastKernelSize = k; |
|
kernelSize->set_value(glow_kernel_size); |
|
}); |
|
#endif |
|
form.makeGroup("Color"); |
|
form.makeColorPicker("Color", base_color_val, "The base color of the fractal visualization",[&](const nanogui::Color &c) { |
|
base_color_val[0] = c[0]; |
|
base_color_val[1] = c[1]; |
|
base_color_val[2] = c[2]; |
|
}); |
|
form.makeFormVariable("Alpha", alpha, 0.0f, 1.0f, true, "", "The opacity of the fractal visualization"); |
|
form.makeFormVariable("Contrast boost", contrast_boost, 1, 255, true, "", "Boost contrast by this factor"); |
|
}); |
|
} |
|
|
|
void iteration() { |
|
//ignore failed capture attempts |
|
v2d->capture(); |
|
|
|
#ifdef __EMSCRIPTEN__ |
|
//required in conjunction with emscripten + nanovg |
|
rebind_buffers(); |
|
#endif |
|
//Render using OpenGL |
|
v2d->gl(render_scene); |
|
|
|
//To slow for WASM but works |
|
#ifndef __EMSCRIPTEN__ |
|
//Aquire the frame buffer for use by OpenCL |
|
v2d->fb([](cv::UMat &frameBuffer) { |
|
//Glow effect (OpenCL) |
|
glow_effect(frameBuffer, frameBuffer, glow_kernel_size); |
|
}); |
|
#endif |
|
|
|
updateFps(v2d, true); |
|
|
|
#ifndef __EMSCRIPTEN__ |
|
v2d->write(); |
|
#endif |
|
//If onscreen rendering is enabled it displays the framebuffer in the native window. Returns false if the window was closed. |
|
if (!v2d->display()) |
|
exit(0); |
|
|
|
++iterations; |
|
} |
|
|
|
int main(int argc, char **argv) { |
|
using namespace cv::viz; |
|
try { |
|
if(argc != 2) { |
|
cerr << "Usage: shader-demo <video-file>" << endl; |
|
exit(1); |
|
} |
|
|
|
printSystemInfo(); |
|
if(!v2d->isOffscreen()) { |
|
setup_gui(v2d); |
|
v2d->setVisible(true); |
|
} |
|
v2d->gl(init_scene); |
|
|
|
#ifndef __EMSCRIPTEN__ |
|
Source src = makeCaptureSource(argv[1]); |
|
v2d->setSource(src); |
|
Sink sink = makeWriterSink(OUTPUT_FILENAME, cv::VideoWriter::fourcc('V', 'P', '9', '0'), FPS, cv::Size(WIDTH, HEIGHT)); |
|
v2d->setSink(sink); |
|
|
|
while(keepRunning()) |
|
iteration(); |
|
#else |
|
Source src = makeCaptureSource(WIDTH, HEIGHT); |
|
v2d->setSource(src); |
|
emscripten_set_main_loop(iteration, -1, true); |
|
#endif |
|
} catch(std::exception& ex) { |
|
cerr << "Exception: " << ex.what() << endl; |
|
} |
|
return 0; |
|
}
|
|
|