@mativa I was so hoping that the lack of square textures was the problem. But awesome that you tried already. And then you probably tried both normalized and non-normalized values for uniform vec2 iResolution
in the fragment shader too I’ll bet.
OK here is the slitscan project, pared down a bit. It should compile and run; just add a video to the data folder for it to work on. It runs great on my m1 mini (Monterey), and a Dell laptop on linux Mint 20.2 with Intel integrated graphics (i7-6600U). It runs on my 2015 mbp retina, but only 1 “slice” is drawn (likely the newest one). So I’m not sure why. Also @Jona have a look for how to set up the openGL stuff for the array texture.
main.cpp:
#include "ofMain.h"
#include "ofApp.h"
//========================================================================
int main( ){
ofGLFWWindowSettings settings;
settings.setGLVersion(3,3);
settings.setSize(1920, 1080);
ofCreateWindow(settings);
ofRunApp(new ofApp());
}
ofApp.h:
#pragma once
#include "ofMain.h"
class ofApp : public ofBaseApp{
public:
void setup();
void update();
void draw();
ofVideoPlayer videoPlayer;
ofShader shader;
// stuff for the sampler2DArray
GLuint textureArray; // a handle for the array
GLsizei maxDepth; // max number of textures in textureArray
GLsizei numLevels; // max number of mipmap levels
GLint level; // the mipmap level(level 0, since there is only 1 level)
GLint xoffset; // subsection x
GLint yoffset; // subsection y
GLint zoffset; // the depth (level) index value into textureArray
GLsizei width; // texture width
GLsizei height; // texture height
GLsizei depth; // texture depth
};
ofApp.cpp
#include "ofApp.h"
//--------------------------------------------------------------
void ofApp::setup(){
shader.load("common.vert", "radialSlitScan.frag");
videoPlayer.load("video.MOV");
videoPlayer.play();
videoPlayer.update();
maxDepth = 128;
numLevels = 1; // the number of mipmap levels
level = 0; // level 0, the index, since there is only 1 level
xoffset = 0; // offset for subsection
yoffset = 0; // offset for subsection
zoffset = 0; // this gets incremented
width = static_cast<GLsizei>(videoPlayer.getWidth());
height = static_cast<GLsizei>(videoPlayer.getHeight());
depth = 1; // the texure layer; this does not get incremented
// get a handle for textureArray, then bind the target and allocate it:
glGenTextures(1,&textureArray); // get 1 handle and store the value in textureArray
glBindTexture(GL_TEXTURE_2D_ARRAY, textureArray); // bind the target
glTexStorage3D(GL_TEXTURE_2D_ARRAY, numLevels, GL_RGB8, width, height, maxDepth); // allocate some storage for it
// calls for good practice (?):
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// unbind the target
glBindTexture(GL_TEXTURE_2D_ARRAY,0);
}
//--------------------------------------------------------------
void ofApp::update(){
videoPlayer.update();
/* updating the sampler2DArray each time with all the textures is very slow; so use an indexing scheme in the fragment shader, and make only 1 call to glTexSubImage3D() each cycle to update the oldest texture with the newest frame from the video player */
if(videoPlayer.isFrameNew())
{
glBindTexture(GL_TEXTURE_2D_ARRAY, textureArray);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, level, xoffset, yoffset, zoffset, width, height, depth, GL_RGB, GL_UNSIGNED_BYTE, videoPlayer.getPixels().getData());
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
zoffset += 1;
if(zoffset > maxDepth) {zoffset = 0;}
}
}
//--------------------------------------------------------------
void ofApp::draw(){
// not sure if all of these gl calls are necessary; but it might be good practice to consistently bind/enable and unbind/disable stuff
glActiveTexture(GL_TEXTURE0 + textureArray);
glClientActiveTexture(GL_TEXTURE0 + textureArray);
glEnable(GL_TEXTURE_2D_ARRAY);
glBindTexture(GL_TEXTURE_2D_ARRAY, textureArray);
shader.begin();
shader.setUniformTexture("textureArray", GL_TEXTURE_2D_ARRAY, textureArray, 0);
shader.setUniform2f("resolution", static_cast<float>(width), static_cast<float>(height));
shader.setUniform1f("zoffset", static_cast<float>(zoffset));
shader.setUniform1f("maxDepth", static_cast<float>(maxDepth));
videoPlayer.draw(0.f, 0.f); // some texcoord for the shader
shader.end();
glActiveTexture(GL_TEXTURE0 + textureArray);
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
glDisable(GL_TEXTURE_2D_ARRAY);
glActiveTexture(GL_TEXTURE0);
}
common.vert:
#version 330
uniform mat4 modelViewProjectionMatrix;
in vec4 position;
in vec2 texcoord;
out vec2 vTexCoord;
void main()
{
gl_Position = modelViewProjectionMatrix * position;
vTexCoord = texcoord;
}
radialSlitScan.frag:
#version 330
uniform sampler2DArray textureArray;
uniform vec2 resolution;
uniform float zoffset; // the newest layer in the textures array
uniform float maxDepth; // the max number of layers in the textures array
in vec2 vTexCoord;
out vec4 fragColor;
void main()
{
vec2 tc = vTexCoord / resolution.xy;
// shift and correct for the rectangular nature of the textures
float aspectRatio = resolution.x / resolution.y;
tc = tc * 2.0 - 1.0;
tc.x *= aspectRatio;
tc = tc * 0.5 + 0.5;
float index = 1.0 - distance(vec2(0.5), tc);
index *= maxDepth; // radial tiles
index += zoffset; // the newest texture first
if(index > maxDepth) {index -= maxDepth;}
vec3 color = texture(textureArray, vec3(tc, floor(index))).rgb;
fragColor = vec4(color, 1.0);
}