Is there a way to apply the sinusoidal distortion effect only in shaders?

Sinusoidal distortion of a 2D image is a classic visual effect: with a 2D image and deforming it along the X or Y axis by shifting the pixels in accordance with a sinusoidal wave. As a result, it looks something like this: Y-axis sinewave distortion example

I saw some code examples for it, and the standard way to do this using OpenGL seems to be an image of dimensions (x, y):

for each column from 0 to X draw a single quad one pixel wide and y pixels high, offset by a sine wave value 

Of course, this is due to a lot of work on the client side. Is there a way to draw one quad and offload the distortion work on the GPU using shaders? Only vertex and fragment shaders; I am using OpenGL 2, so there are no geometric shaders available.

I know that I could use a fragment shader to select texture coordinates that are compensated by a sine wave, but placing them in places outside the original field defined by the quad would be difficult, and I would prefer not to have the output cut off, as in the sample . Is there any way to solve this problem?

+6
source share
2 answers

Yes, this can be done using shaders. Using the vertex shader, you can apply sine distortion to the grid. A fragment shader can modulate the texture coordinate, but not the location of the target pixel; fragment shaders are collectors and cannot perform data scattering.

Update

A working example for modulating a texture coordinate:

 #include <stdlib.h> #include <stdio.h> #include <GL/glew.h> #include <GL/glfw.h> static void pushModelview() { GLenum prev_matrix_mode; glGetIntegerv(GL_MATRIX_MODE, &prev_matrix_mode); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glMatrixMode(prev_matrix_mode); } static void popModelview() { GLenum prev_matrix_mode; glGetIntegerv(GL_MATRIX_MODE, &prev_matrix_mode); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glMatrixMode(prev_matrix_mode); } static const GLchar *vertex_shader_source = "#version 130\n" "void main()" "{" " gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;" " gl_TexCoord[0] = gl_MultiTexCoord0;" " gl_FrontColor = gl_Color;" " gl_BackColor = gl_Color;" "}\0"; GLuint shaderVertex = 0; static const GLchar *fragment_shader_source = "#version 130\n" "uniform sampler2D texCMYK;\n" "uniform sampler2D texRGB;\n" "uniform float T;\n" "const float pi = 3.14159265;\n" "void main()\n" "{\n" " float ts = gl_TexCoord[0].s;\n" " vec2 mod_texcoord = gl_TexCoord[0].st + vec2(0, 0.5*sin(T + 1.5*ts*pi));\n" " gl_FragColor = -texture2D(texCMYK, mod_texcoord) + texture2D(texRGB, gl_TexCoord[0].st);\n" "}\n\0"; GLuint shaderFragment = 0; GLuint shaderProgram = 0; #define TEX_CMYK_WIDTH 2 #define TEX_CMYK_HEIGHT 2 GLubyte textureDataCMYK[TEX_CMYK_WIDTH * TEX_CMYK_HEIGHT][3] = { {0x00, 0xff, 0xff}, {0xff, 0x00, 0xff}, {0xff, 0xff, 0x00}, {0x00, 0x00, 0x00} }; GLuint texCMYK = 0; #define TEX_RGB_WIDTH 2 #define TEX_RGB_HEIGHT 2 GLubyte textureDataRGB[TEX_RGB_WIDTH * TEX_RGB_HEIGHT][3] = { {0x00, 0x00, 0xff}, {0xff, 0xff, 0xff}, {0xff, 0x00, 0x00}, {0x00, 0xff, 0x00} }; GLuint texRGB = 0; GLfloat cube_vertices[][8] = { /* XYZ Nx Ny Nz ST */ {-1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0}, // 0 { 1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0}, // 1 { 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0}, // 2 {-1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0}, // 3 { 1.0, -1.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0}, {-1.0, -1.0, -1.0, 0.0, 0.0, -1.0, 1.0, 0.0}, {-1.0, 1.0, -1.0, 0.0, 0.0, -1.0, 1.0, 1.0}, { 1.0, 1.0, -1.0, 0.0, 0.0, -1.0, 0.0, 1.0}, {-1.0, -1.0, 1.0, -1.0, 0.0, 0.0, 0.0, 0.0}, {-1.0, -1.0, -1.0, -1.0, 0.0, 0.0, 1.0, 0.0}, {-1.0, 1.0, -1.0, -1.0, 0.0, 0.0, 1.0, 1.0}, {-1.0, 1.0, 1.0, -1.0, 0.0, 0.0, 0.0, 1.0}, { 1.0, -1.0, -1.0, 1.0, 0.0, 0.0, 0.0, 0.0}, { 1.0, -1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0}, { 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0}, { 1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 0.0, 1.0}, { 1.0, -1.0, -1.0, 0.0, -1.0, 0.0, 0.0, 0.0}, {-1.0, -1.0, -1.0, 0.0, -1.0, 0.0, 1.0, 0.0}, {-1.0, -1.0, 1.0, 0.0, -1.0, 0.0, 1.0, 1.0}, { 1.0, -1.0, 1.0, 0.0, -1.0, 0.0, 0.0, 1.0}, {-1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0}, { 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0}, { 1.0, 1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 1.0}, {-1.0, 1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 1.0}, }; static void draw_cube(void) { glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glVertexPointer(3, GL_FLOAT, sizeof(GLfloat) * 8, &cube_vertices[0][0]); glNormalPointer(GL_FLOAT, sizeof(GLfloat) * 8, &cube_vertices[0][3]); glTexCoordPointer(2, GL_FLOAT, sizeof(GLfloat) * 8, &cube_vertices[0][6]); glDrawArrays(GL_QUADS, 0, 24); } static void bind_sampler_to_unit_with_texture(GLchar const * const sampler_name, GLuint texture_unit, GLuint texture) { glActiveTexture(GL_TEXTURE0 + texture_unit); glBindTexture(GL_TEXTURE_2D, texture); GLuint loc_sampler = glGetUniformLocation(shaderProgram, sampler_name); glUniform1i(loc_sampler, texture_unit); } static void display(double T) { int window_width, window_height; glfwGetWindowSize(&window_width, &window_height); if( !window_width || !window_height ) return; const float window_aspect = (float)window_width / (float)window_height; glDisable(GL_SCISSOR_TEST); glClearColor(0.5, 0.5, 0.7, 1.0); glClearDepth(1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glEnable(GL_DEPTH_TEST); glViewport(0, 0, window_width, window_height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glFrustum(-window_aspect, window_aspect, -1, 1, 1, 100); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(0, 0, -5); pushModelview(); glRotatef(T * 0.1 * 180, 0., 1., 0.); glRotatef(T * 0.1 * 60, 1., 0., 0.); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glUseProgram(shaderProgram); glUniform1f(glGetUniformLocation(shaderProgram, "T"), T); bind_sampler_to_unit_with_texture("texCMYK", 0, texCMYK); bind_sampler_to_unit_with_texture("texRGB", 1, texRGB); draw_cube(); popModelview(); glfwSwapBuffers(); } static int open_window(void) { #if 0 glfwWindowHint(GLFW_OPENGL_VERSION_MAJOR, 2); glfwWindowHint(GLFW_OPENGL_VERSION_MINOR, 0); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE); #endif if( glfwOpenWindow(0, 0, /* default size */ 8, 8, 8, /* 8 bits per channel */ 8, 24, 8, /* 8 alpha, 24 depth, 8 stencil */ GLFW_WINDOW) != GL_TRUE ) { fputs("Could not open window.\n", stderr); return 0; } if( glewInit() != GLEW_OK ) { fputs("Could not initialize extensions.\n", stderr); return 0; } return 1; } static int check_extensions(void) { if( !GLEW_ARB_vertex_shader || !GLEW_ARB_fragment_shader ) { fputs("Required OpenGL functionality not supported by system.\n", stderr); return 0; } return 1; } static int check_shader_compilation(GLuint shader) { GLint n; glGetShaderiv(shader, GL_COMPILE_STATUS, &n); if( n == GL_FALSE ) { GLchar *info_log; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &n); info_log = malloc(n); glGetShaderInfoLog(shader, n, &n, info_log); fprintf(stderr, "Shader compilation failed: %*s\n", n, info_log); free(info_log); return 0; } return 1; } static int init_resources(void) { glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glGenTextures(1, &texCMYK); glBindTexture(GL_TEXTURE_2D, texCMYK); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, TEX_CMYK_WIDTH, TEX_CMYK_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, textureDataCMYK); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glGenTextures(1, &texRGB); glBindTexture(GL_TEXTURE_2D, texRGB); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, TEX_RGB_WIDTH, TEX_RGB_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, textureDataRGB); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); shaderVertex = glCreateShader(GL_VERTEX_SHADER); glShaderSource(shaderVertex, 1, (const GLchar**)&vertex_shader_source, NULL); glCompileShader(shaderVertex); if( !check_shader_compilation(shaderVertex) ) return 0; shaderFragment = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(shaderFragment, 1, (const GLchar**)&fragment_shader_source, NULL); glCompileShader(shaderFragment); if( !check_shader_compilation(shaderFragment) ) return 0; shaderProgram = glCreateProgram(); glAttachShader(shaderProgram, shaderVertex); glAttachShader(shaderProgram, shaderFragment); glLinkProgram(shaderProgram); return 1; } static void main_loop(void) { glfwSetTime(0); while( glfwGetWindowParam(GLFW_OPENED) == GL_TRUE ) { display(glfwGetTime()); } } int main(int argc, char *argv[]) { if( glfwInit() != GL_TRUE ) { fputs("Could not initialize framework.\n", stderr); return -1; } if( !open_window() ) return -1; if( !check_extensions() ) return -1; if( !init_resources() ) return -1; main_loop(); glfwTerminate(); return 0; } 

Part of the fragment shader is as follows:

 #version 130 uniform sampler2D texCMYK; uniform sampler2D texRGB; uniform float T; const float pi = 3.14159265; void main() { float ts = gl_TexCoord[0].s; vec2 mod_texcoord = gl_TexCoord[0].st + vec2(0, 0.5*sin(T + 1.5*ts*pi)); gl_FragColor = -texture2D(texCMYK, mod_texcoord) + texture2D(texRGB, gl_TexCoord[0].st); }; 

Update - a shader that "expands":

 uniform sampler2D texCMYK; uniform sampler2D texRGB; uniform float T; const float pi = 3.14159265; void main() { float ts = gl_TexCoord[0].s; vec2 mod_texcoord = gl_TexCoord[0].st*vec2(1., 2.) + vec2(0, -0.5 + 0.5*sin(T + 1.5*ts*pi)); if( mod_texcoord.t < 0. || mod_texcoord.t > 1. ) { discard; } gl_FragColor = -texture2D(texCMYK, mod_texcoord) + texture2D(texRGB, gl_TexCoord[0].st); }; 
+5
source

For this input ATV, output quad 2 * max_amplitude higher (perhaps with an vertex shader?), And in your pixel shader, discard pixels that are not currently sin() 'd.

This way you can reach the “outside” of your original quadrant.

+4
source

Source: https://habr.com/ru/post/900549/


All Articles