使用着色器实现粘性效果(处理 3)
Posted
技术标签:
【中文标题】使用着色器实现粘性效果(处理 3)【英文标题】:Implementing a gooey effect with a shader (Processing 3) 【发布时间】:2018-04-12 22:15:51 【问题描述】:我正在尝试复制一种称为“gooey effect”的网页设计技巧(现场观看here)。 这是一种在移动椭圆上应用 SVG 过滤器以获得类似斑点的运动的技术。这个过程相当简单:
应用高斯模糊 仅增加 Alpha 通道的对比度两者结合产生斑点效果
最后一步(增加 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 库)中实现颜色矩阵时,也会发生同样的事情。我可以看到颜色在变化,但不是 alpha 对比度:
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 通道对比度不起作用?我怎样才能让它发挥作用? 我实现颜色矩阵的方式有问题吗? 您知道实现这种粘性效果的更好方法吗?任何帮助将不胜感激!
谢谢
【问题讨论】:
这仅解决了最后一个问题,但另一种选择是使用元球。 感谢您的建议,我已经编写了元球。我想尝试一种新的(更快的)方法。 好的!我以前没有使用过元球,但它们在待办事项列表中。刚刚添加,谢谢! @solub 我不熟悉处理或您正在使用的包,但是:OpenGL 中的 Alpha 通道仅在启用GL_BLEND
且仅在 glBlendFunc
时使用使用 Alpha 通道,如果使用源 Alpha,那么在创建上下文的像素格式时,您还必须有 Alpha 缓冲区。所以我敢打赌,你的模糊根本没有使用 alpha。使用不同的着色器可能意味着不同的输入/输出布局和制服,因此您必须将 CPU 端代码与其匹配,否则着色器将无法正常工作。顺便说一句,也许您可以为此使用 Stencil 以某种方式将模糊图像变为实心图像
@solub 混合无法在片段着色器中启用。您必须设置glBlendFunc
并启用glEnable(GL_BLEND)
【参考方案1】:
来自处理论坛的@noahbuddy 可以找到问题的解决方案,所以我将其发布在这里。
要保持透明度,无论是否使用着色器,请使用离屏 缓冲区(PGraphics)。例如,将 PNG 图像保存为透明 背景。
我从 @cansik 的模糊着色器中删除了对比度矩阵,而是 将其放入单独的过滤器中。
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;
// 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;
colfrag.glsl
#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;
sketch.pde
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);
我个人认为甜蜜点在某处:
Alpha 对比度介于 8 和 11 之间alpha 偏移在 -7 和 -9 之间
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);
“sigma”在 10 到 15 岁之间
“blurSize”在 30 到 40 之间
blurry.set("sigma", 14.5)
blurry.set("blurSize", 35)
在使用有符号距离函数和行进平方算法之前,我已经对 2d 元球进行了编码,但我发现这种解决方案是最有效的解决方案。性能方面,我可以在 800x600 画布上以 60 fps 的速度显示多达 4500 个球(在具有 Python 模式的入门级 2012 imac 桌面上进行了测试)。
【讨论】:
【参考方案2】:很遗憾,我无法调试确切的问题,但我有几个想法希望可以帮助您取得一些进展:
-
要获得更简单/更便宜的效果,您可以使用dilate filter
您可以找到其他 metaballs shaders on shadertoy 并稍微调整代码以便您可以在 Processing 中运行它
例如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);
【讨论】:
您能分享这种方法真是太好了。处理论坛上的某个人刚刚帮我调试了这个问题,所以我会发布他的解决方案并接受它作为答案,因为它专门解决了这个问题。以上是关于使用着色器实现粘性效果(处理 3)的主要内容,如果未能解决你的问题,请参考以下文章
使用shader着色器程序创建扩散光圈效果(three.js实战10)
DirectX11 With Windows SDK--17 利用几何着色器实现公告板效果
WebGL入门(四十)-通过切换着色器实现一个页面同时展示多个立方体
Android 安装包优化Tint 着色器 ( 简介 | 布局文件中的 Tint 着色器基本用法 | 代码中使用 Tint 着色器添加颜色效果 )