Loading...
Searching...
No Matches
Shader-Demo
Prev Tutorial: Nanovg-Demo
Next Tutorial: Font-Demo
Original author | Amir Hassan (kallaballa) amir@.nosp@m.viel.nosp@m.-zu.o.nosp@m.rg |
Compatibility | OpenCV >= 4.7 |
Renders a mandelbrot fractal zoom. Uses shaders, OpenCV and video editing together.
Downloading...
// 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/v4d/v4d.hpp>
using std::cerr;
using std::endl;
/* Demo parameters */
#ifndef __EMSCRIPTEN__
constexpr long unsigned int WIDTH = 1280;
constexpr long unsigned int HEIGHT = 720;
#else
constexpr long unsigned int WIDTH = 960;
constexpr long unsigned int HEIGHT = 960;
#endif
constexpr bool OFFSCREEN = false;
const unsigned long DIAG = hypot(double(WIDTH), double(HEIGHT));
#ifndef __EMSCRIPTEN__
constexpr const char* OUTPUT_FILENAME = "shader-demo.mkv";
#endif
/* Mandelbrot control parameters */
int glow_kernel_size = std::max(int(DIAG / 200 % 2 == 0 ? DIAG / 200 + 1 : DIAG / 200), 1);
// Red, green, blue and alpha. All from 0.0f to 1.0f
float base_color_val[4] = {0.2, 0.6, 1.0, 1.0};
//contrast boost
int contrast_boost = 15; //0.0-255
//max fractal iterations
int max_iterations = 500;
//center x coordinate
float center_x = -0.119609;
//center y coordinate
float center_y = 0.13262;
float zoom_factor = 1.0;
float current_zoom = 1.0;
float zoom_incr = 0.99;
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 current_zoom_hdl;
GLint resolution_hdl;
/* Shader program handle */
GLuint shader_program_hdl;
/* Object handles */
GLuint VAO;
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 };
//Load objects and buffers
static void load_buffer_data() {
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
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);
glBindVertexArray(0);
}
//mandelbrot shader code adapted from my own project: https://github.com/kallaballa/FractalDive#after
static void load_shader() {
#if !defined(__EMSCRIPTEN__) && !defined(OPENCV_V4D_USE_ES3)
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 current_zoom;
uniform float center_x;
uniform float center_y;
uniform vec2 resolution;
int get_iterations()
{
float pointr = (((gl_FragCoord.x / resolution[1]) - 0.5f) * current_zoom + center_x) * 5.0f;
float pointi = (((gl_FragCoord.y / resolution[1]) - 0.5f) * current_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;
}
void determine_color()
{
int iter = get_iterations();
if (iter < max_iterations) {
float iterations = float(iter) / float(max_iterations);
float cb = float(contrast_boost);
outColor = vec4(base_color[0] * iterations * cb, base_color[1] * iterations * cb, base_color[2] * iterations * cb, base_color[3]);
} else {
outColor = vec4(0,0,0,0);
}
}
void main()
{
determine_color();
})";
shader_program_hdl = cv::v4d::initShader(vert.c_str(), frag.c_str(), "fragColor");
}
//easing function for the bungee zoom
static 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;
}
//Initialize shaders, objects, buffers and uniforms
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
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");
current_zoom_hdl = glGetUniformLocation(shader_program_hdl, "current_zoom");
center_x_hdl = glGetUniformLocation(shader_program_hdl, "center_x");
center_y_hdl = glGetUniformLocation(shader_program_hdl, "center_y");
resolution_hdl = glGetUniformLocation(shader_program_hdl, "resolution");
}
//Render the mandelbrot fractal on top of a video
//bungee zoom
if (current_zoom >= 1) {
zoom_incr = -0.01;
} else if (current_zoom < 2.5e-06) {
zoom_incr = +0.01;
}
glUseProgram(shader_program_hdl);
glUniform4f(base_color_hdl, base_color_val[0], base_color_val[1], base_color_val[2], base_color_val[3]);
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) {
current_zoom += zoom_incr;
glUniform1f(current_zoom_hdl, easeInOutQuint(current_zoom));
} else {
current_zoom = 1.0 / pow(zoom_factor, 5.0f);
glUniform1f(current_zoom_hdl, current_zoom);
}
glUniform2fv(resolution_hdl, 1, res);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}
#ifndef __EMSCRIPTEN__
static cv::UMat dst16;
cv::bitwise_not(src, dst);
cv::resize(dst, resize, cv::Size(), 0.5, 0.5);
cv::resize(resize, blur, src.size());
cv::multiply(dst, blur, dst16, 1, CV_16U);
cv::bitwise_not(dst, dst);
}
#endif
using namespace cv::v4d;
//Setup the GUI using ImGUI.
window->imgui([](ImGuiContext* ctx) {
using namespace ImGui;
SetCurrentContext(ctx);
Begin("Fractal");
Text("Navigation");
SliderInt("Iterations", &max_iterations, 3, 50000);
if(SliderFloat("X", ¢er_x, -1.0f, 1.0f))
manual_navigation = true;
if(SliderFloat("Y", ¢er_y, -1.0f, 1.0f))
manual_navigation = true;
if(SliderFloat("Zoom", &zoom_factor, 1.0f, 100.0f))
manual_navigation = true;
#ifndef __EMSCRIPTEN__
Text("Glow");
SliderInt("Kernel Size", &glow_kernel_size, 1, 127);
#endif
Text("Color");
ColorPicker4("Color", base_color_val);
SliderInt("Contrast boost", &contrast_boost, 1, 255);
End();
});
}
if(!window->capture())
return false;
window->gl(render_scene);
#ifndef __EMSCRIPTEN__
window->fb([](cv::UMat& frameBuffer) {
glow_effect(frameBuffer, frameBuffer, glow_kernel_size);
});
#endif
window->write();
return window->display();
}
#ifndef __EMSCRIPTEN__
int main(int argc, char** argv) {
if (argc != 2) {
cerr << "Usage: shader-demo <video-file>" << endl;
exit(1);
}
#else
int main() {
#endif
try {
if (!OFFSCREEN) {
setup_gui(window);
}
window->printSystemInfo();
window->gl(init_scene);
#ifndef __EMSCRIPTEN__
Source src = makeCaptureSource(argv[1]);
window->setSource(src);
window->setSink(sink);
#else
Source src = makeCaptureSource(WIDTH, HEIGHT, window);
window->setSource(src);
#endif
window->run(iteration);
} catch (std::exception& ex) {
cerr << "Exception: " << ex.what() << endl;
}
return 0;
}
static Scalar_< double > all(double v0)
returns a scalar with all elements set to v0
Definition: mat.hpp:2432
MatSize size
dimensional size of the matrix; accessible in various formats
Definition: mat.hpp:2643
Definition: sink.hpp:19
Definition: source.hpp:20
float fps()
void bitwise_not(InputArray src, OutputArray dst, InputArray mask=noArray())
Inverts every bit of an array.
void divide(InputArray src1, InputArray src2, OutputArray dst, double scale=1, int dtype=-1)
Performs per-element division of two arrays or a scalar by an array.
void multiply(InputArray src1, InputArray src2, OutputArray dst, double scale=1, int dtype=-1)
Calculates the per-element scaled product of two arrays.
void pow(InputArray src, double power, OutputArray dst)
Raises every array element to a power.
void blur(InputArray src, OutputArray dst, Size ksize, Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT)
Blurs an image using the normalized box filter.
void boxFilter(InputArray src, OutputArray dst, int ddepth, Size ksize, Point anchor=Point(-1,-1), bool normalize=true, int borderType=BORDER_DEFAULT)
Blurs an image using the box filter.
void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR)
Resizes an image.
Definition: framebuffercontext.hpp:25
Source makeCaptureSource(const string &inputFilename)
unsigned int initShader(const char *vShader, const char *fShader, const char *outputAttributeName)
Sink makeWriterSink(const string &outputFilename, const float fps, const cv::Size &frameSize)