使用着色器实现粘性效果(处理3)

时间:2018-04-12 22:15:51

标签: glsl processing shader colormatrix colormatrixfilter

我正在尝试复制一个名为“gooey effect”的网页设计技巧(见实时here)。 这是一种在移动椭圆上应用SVG滤波器以获得类似blob的运动的技术。这个过程很简单:

  • 应用高斯模糊
  • 仅增加Alpha通道的对比度

两者的组合会产生blob效果

最后一步(增加alpha通道对比度)通常通过“彩色矩阵滤镜”完成。

  

颜色矩阵由5列(RGBA +偏移)和4行组成。

     

     

前四列中的值分别为乘以,分别为源红色,绿色,蓝色和alpha值。第五列值已添加(偏移)。

在CSS中,增加alpha通道对比度就像调用SVG过滤器并指定对比度值一样简单(这里是18):

<feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -7" result="goo" />

在处理过程中,它看起来有点复杂。我相信(我可能错了)应用彩色矩阵滤镜的唯一方法是在着色器中创建一个。经过几次尝试后,我想出了这些(非常基本的)用于显色的顶点和片段着色器:

colorvert.glsl

uniform mat4 transform;
attribute vec4 position;
attribute vec4 color;
varying vec4 vertColor;

uniform vec4 o=vec4(0, 0, 0, -9); 
uniform lowp mat4 colorMatrix = mat4(1.0, 0.0, 0.0, 0.0, 
                                     0.0, 1.0, 0.0, 0.0, 
                                     0.0, 0.0, 1.0, 0.0, 
                                     0.0, 0.0, 0.0, 60.0);


void main() {
  gl_Position = transform * position; 
  vertColor = (color * colorMatrix) + o  ;
}

colorfrag.glsl

#ifdef GL_ES
precision mediump float;
precision mediump int;
#endif

varying vec4 vertColor;

void main() {
  gl_FragColor = vertColor;
}

问题:

颜色矩阵部分工作:更改RGB值确实会影响颜色,但更改alpha值(最后一行)不会!

当尝试将着色器与高斯滤镜组合时,即使我将Alpha通道对比度设置为60(如在codepen示例中),绘制的椭圆也会保持模糊:

PShader colmat;

void setup() {
  size(200, 200, P2D);
  colmat = loadShader("colorfrag.glsl", "colorvert.glsl");
}

void draw() {
  background(100);
  shader(colmat);

  noStroke();
  fill(255, 30, 30);
  ellipse(width/2, height/2, 40, 40);
  filter(BLUR,6);
}

当我在@cansik的高斯模糊shader(来自PostFX库)中实现颜色矩阵时,会发生同样的事情。我可以看到颜色在变化而不是α对比度:

blurFrag.glsl

/ Adapted from:
// <a href="http://callumhay.blogspot.com/2010/09/gaussian-blur-shader-glsl.html" target="_blank" rel="nofollow">http://callumhay.blogspot.com/2010/09/gaussian-blur-shader-glsl.html</a>

#ifdef GL_ES
precision mediump float;
precision mediump int;
#endif

#define PROCESSING_TEXTURE_SHADER


uniform sampler2D texture;

uniform vec4 o=vec4(0, 0, 0, 0); 
uniform lowp mat4 colorMatrix = mat4(1, 0.0, 0.0, 0.0, 
                                     0.0, 1, 0.0, 0.0, 
                                     0.0, 0.0, 1, 0.0, 
                                     0, 0.0, 0.0, 60.0); //Alpha contrast set to 60


varying vec2 center;

// The inverse of the texture dimensions along X and Y
uniform vec2 texOffset;

varying vec4 vertColor;
varying vec4 vertTexCoord;

uniform int blurSize;       
uniform int horizontalPass; // 0 or 1 to indicate vertical or horizontal pass
uniform float sigma;        // The sigma value for the gaussian function: higher value means more blur
                            // A good value for 9x9 is around 3 to 5
                            // A good value for 7x7 is around 2.5 to 4
                            // A good value for 5x5 is around 2 to 3.5
                            // ... play around with this based on what you need <span class="Emoticon Emoticon1"><span>:)</span></span>

const float pi = 3.14159265;

void main() {  
  float numBlurPixelsPerSide = float(blurSize / 2); 

  vec2 blurMultiplyVec = 0 < horizontalPass ? vec2(1.0, 0.0) : vec2(0.0, 1.0);

  // Incremental Gaussian Coefficent Calculation (See GPU Gems 3 pp. 877 - 889)
  vec3 incrementalGaussian;
  incrementalGaussian.x = 1.0 / (sqrt(2.0 * pi) * sigma);
  incrementalGaussian.y = exp(-0.5 / (sigma * sigma));
  incrementalGaussian.z = incrementalGaussian.y * incrementalGaussian.y;

  vec4 avgValue = vec4(0.0, 0.0, 0.0, 0.0);
  float coefficientSum = 0.0;

  // Take the central sample first...
  avgValue += texture2D(texture, vertTexCoord.st) * incrementalGaussian.x;
  coefficientSum += incrementalGaussian.x;
  incrementalGaussian.xy *= incrementalGaussian.yz;

  // Go through the remaining 8 vertical samples (4 on each side of the center)
  for (float i = 1.0; i <= numBlurPixelsPerSide; i++) { 
    avgValue += texture2D(texture, vertTexCoord.st - i * texOffset * 
                          blurMultiplyVec) * incrementalGaussian.x;         
    avgValue += texture2D(texture, vertTexCoord.st + i * texOffset * 
                          blurMultiplyVec) * incrementalGaussian.x;         
    coefficientSum += 2.0 * incrementalGaussian.x;
    incrementalGaussian.xy *= incrementalGaussian.yz;
  }
  gl_FragColor = (avgValue / coefficientSum )  * colorMatrix;
}

在主.pde文件中设置glBlendFunc并启用glEnable(GL_BLEND)也无法解决问题。

sketch.pde

import ch.bildspur.postfx.builder.*;
import ch.bildspur.postfx.pass.*;
import ch.bildspur.postfx.*;
import processing.opengl.*;
import com.jogamp.opengl.*;

PostFX fx;

void setup() {
    size(200, 200, P2D);
    fx = new PostFX(this); 
}

void draw() {
    background(100);
    GL gl = ((PJOGL)beginPGL()).gl.getGL();
    gl.glEnable(GL.GL_BLEND);
    gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);
    gl.glDisable(GL.GL_DEPTH_TEST);

    noStroke();
    fill(255, 30, 30);
    ellipse(width/2, height/2, 40, 40);
    fx.render().blur(80, 14).compose();
}

问题:

  • 为什么alpha通道对比不起作用?我怎样才能使它发挥作用?
  • 我实现色彩矩阵的方式有问题吗?
  • 你知道更好的方法来实现这种粘性效果吗?

非常感谢任何帮助!

谢谢

2 个答案:

答案 0 :(得分:4)

来自Processing Forum的@noahbuddy可以找到问题的解决方案,所以我在这里发布。

  

要保留透明度,无论是否使用着色器,请使用屏幕外   缓冲区(PGraphics)。例如,保存透明的PNG图像   背景

     

我从@cansik的模糊着色器中删除了对比矩阵   将它放入一个单独的过滤器中。

<强> blurfrag.glsl

d <- iris
o1=ggplot(d, aes(x=d$Sepal.Length, y=d$Sepal.Width))+geom_smooth(method=lm,alpha=0.25,col='seagreen',lwd=0.1) +ylim(0,8)+xlim(0,8)+
    geom_point(shape=23,fill="black",size=0.2)+theme_bw()+theme(plot.background = element_blank(),panel.grid.major = element_blank()
                                                                ,panel.grid.minor = element_blank()) +labs(x="bla bla",y="bla bla")+
    theme(axis.title.x = element_text(face="bold", size=8),axis.text.x = element_text(size=5))+
    theme(axis.title.y = element_text(face="bold", size=8),axis.text.y = element_text(size=5))+
    theme(plot.title = element_text(lineheight=.8, face="bold",size=8))+theme(
        panel.background = element_rect(fill = "transparent",colour = NA), 
        panel.grid.minor = element_blank(), 
        panel.grid.major = element_blank())
o2=ggplot(d, aes(x=d$Sepal.Length, y=d$Petal.Length))+geom_smooth(method=lm,alpha=0.25,col='seagreen',lwd=0.1) +ylim(0,8)+xlim(0,8)+
    geom_point(shape=23,fill="black",size=0.2)+theme_bw()+theme(plot.background = element_blank(),panel.grid.major = element_blank()
                                                                ,panel.grid.minor = element_blank()) +labs(x="bla bla",y="bla bla")+
    theme(axis.title.x = element_text(face="bold", size=8),axis.text.x = element_text(size=5))+
    theme(axis.title.y = element_text(face="bold", size=8),axis.text.y = element_text(size=5))+
    theme(plot.title = element_text(lineheight=.8, face="bold",size=8))+theme(
        panel.background = element_rect(fill = "transparent",colour = NA), 
        panel.grid.minor = element_blank(), 
        panel.grid.major = element_blank())
png(bg = "transparent")
grid.arrange(o1,o2,ncol=1)
dev.copy(png,"graph.png",width=20,height=15,units="cm",res=800)
dev.off(dev.prev())
dev.off()

<强> colfrag.glsl

// Adapted from:
// <a href="http://callumhay.blogspot.com/2010/09/gaussian-blur-shader-glsl.html" target="_blank" rel="nofollow">http://callumhay.blogspot.com/2010/09/gaussian-blur-shader-glsl.html</a>

#ifdef GL_ES
precision mediump float;
precision mediump int;
#endif


#define PROCESSING_TEXTURE_SHADER

uniform sampler2D texture;

// The inverse of the texture dimensions along X and Y
uniform vec2 texOffset;

varying vec4 vertColor;
varying vec4 vertTexCoord;

uniform int blurSize;       
uniform int horizontalPass; // 0 or 1 to indicate vertical or horizontal pass
uniform float sigma;        // The sigma value for the gaussian function: higher value means more blur
                            // A good value for 9x9 is around 3 to 5
                            // A good value for 7x7 is around 2.5 to 4
                            // A good value for 5x5 is around 2 to 3.5
                            // ... play around with this based on what you need <span class="Emoticon Emoticon1"><span>:)</span></span>

const float pi = 3.14159265;

void main() {  
  float numBlurPixelsPerSide = float(blurSize / 2); 

  vec2 blurMultiplyVec = 0 < horizontalPass ? vec2(1.0, 0.0) : vec2(0.0, 1.0);

  // Incremental Gaussian Coefficent Calculation (See GPU Gems 3 pp. 877 - 889)
  vec3 incrementalGaussian;
  incrementalGaussian.x = 1.0 / (sqrt(2.0 * pi) * sigma);
  incrementalGaussian.y = exp(-0.5 / (sigma * sigma));
  incrementalGaussian.z = incrementalGaussian.y * incrementalGaussian.y;

  vec4 avgValue = vec4(0.0, 0.0, 0.0, 0.0);
  float coefficientSum = 0.0;

  // Take the central sample first...
  avgValue += texture2D(texture, vertTexCoord.st) * incrementalGaussian.x;
  coefficientSum += incrementalGaussian.x;
  incrementalGaussian.xy *= incrementalGaussian.yz;

  // Go through the remaining 8 vertical samples (4 on each side of the center)
  for (float i = 1.0; i <= numBlurPixelsPerSide; i++) { 
    avgValue += texture2D(texture, vertTexCoord.st - i * texOffset * 
                          blurMultiplyVec) * incrementalGaussian.x;         
    avgValue += texture2D(texture, vertTexCoord.st + i * texOffset * 
                          blurMultiplyVec) * incrementalGaussian.x;         
    coefficientSum += 2.0 * incrementalGaussian.x;
    incrementalGaussian.xy *= incrementalGaussian.yz;
  }

  gl_FragColor = avgValue / coefficientSum;
}

<强> sketch.pde

#define PROCESSING_TEXTURE_SHADER

uniform sampler2D texture;
varying vec4 vertTexCoord;

uniform vec4 o = vec4(0, 0, 0, -7.0); 
uniform lowp mat4 colorMatrix = mat4(1.0, 0.0, 0.0, 0.0, 
                                     0.0, 1.0, 0.0, 0.0, 
                                     0.0, 0.0, 1.0, 0.0, 
                                     0.0, 0.0, 0.0, 18.0);

void main() {
  vec4 pix = texture2D(texture, vertTexCoord.st);

  vec4 color = (pix * colorMatrix) + o;
  gl_FragColor = color;
}

就我个人而言,我认为最佳点在于某处:

  • 8到11之间的α对比度
  • 在-7和-9之间,用于alpha偏移

    PShader contrast, blurry;
    PGraphics buf;
    
    void setup() {
      size(200, 200, P2D);
      buf = createGraphics(width, height, P2D);
    
      contrast = loadShader("colfrag.glsl");
      blurry = loadShader("blurFrag.glsl");
    
      // Don't forget to set these
      blurry.set("sigma", 4.5);
      blurry.set("blurSize", 9);
    }
    
    void draw() {
      background(100);
    
      buf.beginDraw();
        // Reset transparency
        // Note, the color used here will affect your edges
        // even with zero for alpha
        buf.background(100, 0); // set to match main background
    
        buf.noStroke();
        buf.fill(255, 30, 30);
        buf.ellipse(width/2, height/2, 40, 40);
        buf.ellipse(mouseX, mouseY, 40, 40);
    
        blurry.set("horizontalPass", 1);
        buf.filter(blurry);
        blurry.set("horizontalPass", 0);
        buf.filter(blurry);
      buf.endDraw();
    
      shader(contrast);
      image(buf, 0,0, width,height);
    }
    
  • “西格玛”的第10和第15页

  • 为“blurSize”而言,在30和40之间

    uniform vec4 o = vec4(0, 0, 0, -9.0); 
    uniform lowp mat4 colorMatrix = mat4(1.0, 0.0, 0.0, 0.0, 
                                         0.0, 1.0, 0.0, 0.0, 
                                         0.0, 0.0, 1.0, 0.0, 
                                         1.0, 1.0, 1.0, 11.0);
    

我在使用有符号距离函数和行进方算法之前编码了2d元球,但我发现这个解决方案是最有效的。性能方面我可以在800x600画布上以60 fps的速度显示多达4500个球(在具有Python模式的入门级2012 imac桌面上进行测试)。

答案 1 :(得分:0)

不幸的是,我无法调试确切的问题,但我有一些想法可能会帮助您取得一些进展:

  1. 为了更简单/更便宜的效果,您可以使用dilate filter
  2. 您可以找到其他metaballs shaders on shadertoy并稍微调整一下代码,以便在处理中运行
  3. 例如https://www.shadertoy.com/view/MlcGWn变为:

    // https://www.shadertoy.com/view/MlcGWn
    
    uniform float iTime;
    uniform vec2 iResolution;
    
    vec3 Sphere(vec2 uv, vec2 position, float radius)
    {
        float dist = radius / distance(uv, position);
        return vec3(dist * dist);
    }
    
    void main()
    {
        vec2 uv = 2.0 * vec2(gl_FragCoord.xy - 0.5 * iResolution.xy) / iResolution.y;
    
        vec3 pixel = vec3(0.0, 0.0, 0.0);
    
        vec2 positions[4];
        positions[0] = vec2(sin(iTime * 1.4) * 1.3, cos(iTime * 2.3) * 0.4);
        positions[1] = vec2(sin(iTime * 3.0) * 0.5, cos(iTime * 1.3) * 0.6);
        positions[2] = vec2(sin(iTime * 2.1) * 0.1, cos(iTime * 1.9) * 0.8);
        positions[3] = vec2(sin(iTime * 1.1) * 1.1, cos(iTime * 2.6) * 0.7);
    
        for (int i = 0; i < 4; i++)
            pixel += Sphere(uv, positions[i], 0.22);
    
        pixel = step(1.0, pixel) * pixel;
    
        gl_FragColor = vec4(pixel, 1.0);
    }
    

    并在处理中:

    PShader shader;
    
    void setup(){
      size(900,900,P2D);
    
      shader = loadShader("metaballs.glsl");
      shader.set("iResolution",(float)width/2,(float)height/2);
    }
    void draw(){
      shader.set("iTime", millis() * 0.001);
      shader(shader);
      rect(0,0,width,height);
    }
    

    https://www.shadertoy.com/view/ldtSRX

    // https://www.shadertoy.com/view/ldtSRX
    
    uniform vec2 iResolution;
    uniform vec2 iMouse;
    uniform float iTime;
    
    struct Metaball{
        vec2 pos;
        float r;
        vec3 col;
    };
    
    vec4 calcball( Metaball ball, vec2 uv)
    {
        float dst = ball.r / (pow(abs(uv.x - ball.pos.x), 2.) + pow(abs(uv.y - ball.pos.y), 2.));
        return vec4(ball.col * dst, dst);
    }
    
    vec3 doballs( vec2 uv )
    {
        Metaball mouse;
        mouse.pos = iMouse.xy / iResolution.yy;
        mouse.r = .015;
        mouse.col = vec3(.5);
    
        Metaball mb1, mb2, mb3, mb4;
        mb1.pos = vec2(1.3, .55+.2*sin(iTime*.5)); mb1.r = .05; mb1.col = vec3(0., 1., 0.);
        mb2.pos = vec2(.6, .45); mb2.r = .02; mb2.col = vec3(0., .5, 1.);
        mb3.pos = vec2(.85, .65); mb3.r = .035; mb3.col = vec3(1., .2, 0.);
        mb4.pos = vec2(1.+.5*sin(iTime), .2); mb4.r = .02; mb4.col = vec3(1., 1., 0.);
    
        vec4 ball1 = calcball(mb1, uv);
        vec4 ball2 = calcball(mb2, uv);
        vec4 ball3 = calcball(mb3, uv);
        vec4 ball4 = calcball(mb4, uv);
    
        vec4 subball1 = calcball(mouse, uv);
    
        float res = ball1.a + ball2.a + ball3.a + ball4.a;
        res -= subball1.a;
        float threshold = res >= 1.5 ? 1. : 0.;
    
        vec3 color = (ball1.rgb + ball2.rgb + ball3.rgb + ball4.rgb - subball1.rgb) / res;
        color *= threshold;
        color = clamp(color, 0., 1.);
        return color;
    }
    
    #define ANTIALIAS 1
    void main()
    {
        vec2 uv = gl_FragCoord.xy / iResolution.yy;
    
        vec3 color = doballs(uv);
    
        #ifdef ANTIALIAS
        float uvs = .75 / iResolution.y;
        color *= .5;
        color += doballs(vec2(uv.x + uvs, uv.y))*.125;
        color += doballs(vec2(uv.x - uvs, uv.y))*.125;
        color += doballs(vec2(uv.x, uv.y + uvs))*.125;
        color += doballs(vec2(uv.x, uv.y - uvs))*.125;
    
        #if ANTIALIAS == 2
        color *= .5;
        color += doballs(vec2(uv.x + uvs*.85, uv.y + uvs*.85))*.125;
        color += doballs(vec2(uv.x - uvs*.85, uv.y + uvs*.85))*.125;
        color += doballs(vec2(uv.x - uvs*.85, uv.y - uvs*.85))*.125;
        color += doballs(vec2(uv.x + uvs*.85, uv.y - uvs*.85))*.125;
        #endif
        #endif
    
        gl_FragColor = vec4(color, 1.);
    }
    

    并在处理中:

    PShader shader;
    PVector mouse = new PVector();
    void setup(){
      size(900,900,P2D);
    
      shader = loadShader("metaballs.glsl");
      shader.set("iResolution",(float)width/2,(float)height/2);
    }
    void draw(){
      mouse.set(mouseX,mouseY);
      shader.set("iMouse", mouse);
      shader.set("iTime", millis() * 0.001);
      shader(shader);
      rect(0,0,width,height);
    }