WebGL着色器可根据鼠标位置为纹理着色

Nic*_*kyP 2 mouse shader position webgl

我正在尝试使用WebGL产生有趣的效果。在片段着色器中,我有以下一行以黑白方式绘制我的纹理:

gl_FragColor = vec4(vec3(color0.r+color0.g+color0.b)/3.0, color0.a);
Run Code Online (Sandbox Code Playgroud)

color0纹理的颜色在哪里。在着色器中,我还具有一个uniform vec2 u_mouse从我的JavaScript代码传入的内容,作为鼠标在屏幕上的坐标。现在我想要的是能够移动鼠标,并且图像的一部分将在给定的半径内变色,如图所示:

效果图

我的想法是要有一个带有白色圆圈的蒙版,该蒙版会随鼠标移动,但是我不知道随后如何进行图像处理...我也希望动画不像鼠标值之间的插值。

谢谢!

gma*_*man 5

您想将黑白版本与彩色版本混合使用

vec4 bw = vec4(vec3(color0.r + color0.g + color0.b) / 3., color.a);
gl_FragColor = mix(bw, color0, mixAmount);
Run Code Online (Sandbox Code Playgroud)

哪里mix定义为

mix(a, b, l) = a + (b - a) * l
Run Code Online (Sandbox Code Playgroud)

换句话说,如果mixAmount为0,您将得到bw;如果mixAmount为1,您将得到color0。对于介于0和1之间的值,您将混合使用2。

所以现在您只需要一些公式 setting mixAmount

作为一个示例,假设您在画布的相对坐标中传递鼠标,则可以计算与该坐标的距离

uniform vec2 mousePos;  // in pixels where 0,0 is bottom left

...

  float dist = distance(mousePos, gl_FragCoord.xy);
Run Code Online (Sandbox Code Playgroud)

然后,您可以用它来计算mixAmount,例如

uniform float mixRadius;

  float mixAmount = clamp(dist / mixRadius, 0., 1.);
Run Code Online (Sandbox Code Playgroud)

然后您将得到一个渐变色的圆圈,其中心颜色变为边缘的黑色和白色。

如果您希望中央的较大区域成为彩色,则可以传入minRadiusmaxRadius

uniform float minRadius;
uniform float maxRadius;

  float range = maxRadius - minRadius
  float mixAmount = clamp((dist - minRadius) / range, 0., 1.);
Run Code Online (Sandbox Code Playgroud)

或类似的东西

这是一个有效的例子

vec4 bw = vec4(vec3(color0.r + color0.g + color0.b) / 3., color.a);
gl_FragColor = mix(bw, color0, mixAmount);
Run Code Online (Sandbox Code Playgroud)
mix(a, b, l) = a + (b - a) * l
Run Code Online (Sandbox Code Playgroud)
uniform vec2 mousePos;  // in pixels where 0,0 is bottom left

...

  float dist = distance(mousePos, gl_FragCoord.xy);
Run Code Online (Sandbox Code Playgroud)

就像您提到的那样,您还可以传递蒙版纹理。这将使您可以轻松制作其他形状。同样,您只需要一个值mixAmount

所以,像

uniform mat4 maskMatrix;

...

vec2 maskUV = (maskMatrix * vec4(v_texcoord, 0, 1)).xy;
float mixAmount = texture2D(mask, maskUV).a;
Run Code Online (Sandbox Code Playgroud)

通过阅读以下文章,您可以了解如何使用2d或3d矩阵设置该矩阵

uniform float mixRadius;

  float mixAmount = clamp(dist / mixRadius, 0., 1.);
Run Code Online (Sandbox Code Playgroud)
uniform float minRadius;
uniform float maxRadius;

  float range = maxRadius - minRadius
  float mixAmount = clamp((dist - minRadius) / range, 0., 1.);
Run Code Online (Sandbox Code Playgroud)
"use strict";
const vs = `
attribute vec4 position;
attribute vec2 texcoord;

uniform mat4 matrix;

varying vec2 v_texcoord;

void main() {
  gl_Position = matrix * position;
  v_texcoord = texcoord;
}
`;
const fs = `
precision mediump float;

varying vec2 v_texcoord;

uniform sampler2D tex;
uniform vec2 mousePos;
uniform float minRadius;
uniform float maxRadius;

void main() {
  vec4 color0 = texture2D(tex, v_texcoord);
  vec4 bw = vec4(vec3(color0.r + color0.g + color0.b) / 3., color0.a);
  
  float dist = distance(mousePos, gl_FragCoord.xy);
  float range = maxRadius - minRadius;
  float mixAmount = clamp((dist - minRadius) / range, 0., 1.);
  
  gl_FragColor = mix(color0, bw, mixAmount);
}
`;
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const info = document.querySelector("#info");

// compiles shaders, link program, looks up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);

// calls gl.createBuffer, gl.bindBuffer, gl.bufferData for each array
const bufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);

const textureInfo = {
  width: 1,
  height: 1,
};
const texture = twgl.createTexture(gl, {
  src: "http://i.imgur.com/NzBzAdN.jpg",
  crossOrigin: '',
  flipY: true,
}, (err, tex, img) => {
  textureInfo.width = img.width;
  textureInfo.height = img.height;
  render();
});

const mousePos = [0, 0];

function render() {
  twgl.resizeCanvasToDisplaySize(gl.canvas);
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  
  gl.useProgram(programInfo.program);
  
  // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  
  // cover canvas with image  
  const canvasAspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
  const imageAspect = textureInfo.width / textureInfo.height;

  // this assumes we want to fill vertically
  let horizontalDrawAspect = imageAspect / canvasAspect;
  let verticalDrawAspect = 1;
  // does it fill horizontally?
  if (horizontalDrawAspect < 1) {
    // no it does not so scale so we fill horizontally and
    // adjust vertical to match
    verticalDrawAspect /= horizontalDrawAspect;
    horizontalDrawAspect = 1;
  }
  const mat = m4.scaling([horizontalDrawAspect, verticalDrawAspect, 1]);
  
  // calls gl.activeTexture, gl.bindTexture, gl.uniform
  twgl.setUniforms(programInfo, {
    minRadius: 25,
    maxRadius: 100,
    tex: texture,
    matrix: mat,
    mousePos: mousePos,
  });
  
  twgl.drawBufferInfo(gl, bufferInfo);
}
render();

gl.canvas.addEventListener('mousemove', e => {
  const canvas = e.target;
  const rect = canvas.getBoundingClientRect();

  const x = (e.clientX - rect.left) * canvas.width / rect.width;
  const y = (e.clientY - rect.top)  * canvas.height / rect.height;
  mousePos[0] = x;
  mousePos[1] = canvas.height - y - 1;
  
  render();
});

window.addEventListener('resize', render);
Run Code Online (Sandbox Code Playgroud)

注意我使用F带有框架的a来清楚地显示蒙版。还要注意,必须将蒙版的边缘保持为0,因为边缘像素将重复经过蒙版的边界。否则,或者当与蒙版一起使用的纹理坐标为<0或> 1时,需要将着色器修改为使用0。

我还使用矩阵来操纵UV坐标。由于它是矩阵,因此无需更改着色器即可轻松缩放,偏移和/或旋转蒙版。

至于动画,还不清楚您想要哪种动画。如果您想要某种颜色随时间逐渐消失的东西,可以使用此回答中的技术。您将在另一对纹理中绘制蒙版。您可以使用那对纹理作为mixAmount蒙版。通过将一个纹理绘制到另一个纹理中,每帧减去一定量,可以使这些纹理退回到0

gl_FragColor = texture2D(mixTexture, uv).rgba - vec4(0.01);
Run Code Online (Sandbox Code Playgroud)

例如。