有没有办法纯粹在着色器中应用正弦波失真效果?

Posted

技术标签:

【中文标题】有没有办法纯粹在着色器中应用正弦波失真效果?【英文标题】:Is there a way to apply a sinewave distortion effect purely in shaders? 【发布时间】:2011-11-01 18:37:32 【问题描述】:

2D 图像的正弦波失真是一种经典的视觉效果:拍摄 2D 图像并通过根据正弦波移动像素将其沿 X 或 Y 轴扭曲。它最终看起来像这样:

我已经看到了一些代码示例,使用 OpenGL 执行此操作的标准方法似乎是,对于尺寸为 (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

当然,这涉及到客户端的大量工作。有什么方法可以绘制单个四边形并将失真工作卸载到带有着色器的 GPU 上?只有顶点和片段着色器;我使用的是 OpenGL 2,所以没有可用的几何着色器。

我知道我可以使用片段着色器来采样被正弦波偏移的纹理坐标,但是将它们放置在由四边形定义的原始框之外的位置会很棘手,我不希望有输出像示例图片中一样被剪裁。有没有办法解决这个问题?

【问题讨论】:

它需要是四边形还是可以使用其他几何形状? 渲染一个更高的四边形2 * max_amplitude 并丢弃当前未被sin() 处理的像素?这样你就可以到达你原来的四边形“外面”。 【参考方案1】:

是的,这可以使用着色器来完成。使用顶点着色器,您可以在网格上应用正弦失真。片段着色器可以调制纹理坐标,但不能调制目标像素位置;片段着色器是收集器,不能做数据分散

更新

纹理坐标调制的工作示例:

#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] =  
    /*  X     Y     Z   Nx   Ny   Nz    S    T */
    -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;

片段着色器部分是这样的:

#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);
;

更新——“扩展”的着色器:

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);
;

【讨论】:

如果我只有四个顶点且没有几何着色器,如何在网格上应用失真? @MasonWheeler:在只有 4 个顶点的情况下,顶点着色器无法解决问题。但是您仍然可以使用片段着色器来调制纹理坐标。 里面的 CMYK 东西是干什么用的? @genpfault:这只是两个纹理。均为 2×2 像素。一个有一个红色像素、一个绿色像素、一个蓝色像素和一个白色像素(即 RGB 纹理),另一个包含一个青色像素、一个洋红色像素、一个黄色像素和一个黑色像素(即 CMYK 纹理) .您可以在完整代码粘贴中的着色器源代码之后立即看到纹理日期。 啊,我以为你是在尝试在某处做一些时髦的色彩空间转换,而不是仅仅拥有一些小的内联样本纹理。这更有意义。【参考方案2】:

对于给定的输入四边形渲染一个四边形2 * max_amplitude 更高(可能带有顶点着色器?),并在您的像素着色器中丢弃当前未被sin()'d 的像素。

这样你就可以到达你原来的四边形“外面”。

【讨论】:

嗯...所以如果我使用顶点着色器根据正弦波失真的幅度向上缩放输出四边形的大小,那么我在片段着色器中得到的输入坐标将是在扭曲的空间中,我必须计算正弦波公式 backwards 以找到正确的原始纹素进行采样。对吗?

以上是关于有没有办法纯粹在着色器中应用正弦波失真效果?的主要内容,如果未能解决你的问题,请参考以下文章

你能改变金属着色器中采样器的边界吗?

片段着色器中的OpenGL点精灵旋转

pow(0, 2.2) 在 hlsl 像素着色器中给出 1?

meshlab 有没有办法只将着色器应用于一层?

OpenGL:在着色器中执行 TexCoord 计算,不好的做法?

在片段着色器中丢失纹理定义