9个学习着色器的GLSL示例

学习GLSL的基础知识是小菜一碟。然而,使用这些知识来创造效果可能会令人生畏,因为您可能会感到迷失,不确定从哪里开始。如果这听起来很熟悉,那么本教程就是为你准备的。

分辨率、鼠标和时间

在第一个例子中,我们将使用Shader的编辑器。打开它,让我们开始我们的第一个例子。

首先,让我们讨论u_resolution,一个包含画布宽度和高度vec2统一变量。

众所周知,GLSL在0到1的坐标系中运行:

为了实现这一点,我们将当前片段的坐标(gl_FragCoord)除以分辨率(u_resolution)。

GLSL 复制代码
vec2 st = gl_FragCoord.xy / u_resolution.xy;

将产生一个水平渐变,其中0表示黑色,1表示白色。

GLSL 复制代码
#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;

void main() {
  vec2 st = gl_FragCoord.xy / u_resolution.xy;

  vec3 color = vec3(st.x);

  gl_FragColor = vec4(color, 1.);
}

st.x替换为st.y以创建垂直渐变。

GLSL 复制代码
vec3 color = vec3(st.y);

要反转颜色,只需从1中减去该值。

GLSL 复制代码
vec3 color = vec3(1. - st.y);

下一个统一变量是光标的坐标(u_mouse),也用像素表示。我们通过将它们除以分辨率来将其标准化。

GLSL 复制代码
vec2 mousePos = u_mouse.xy / u_resolution.xy;

允许我们使用鼠标位置的x坐标来调整梯度。

GLSL 复制代码
#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform vec2 u_mouse;

void main() {
  vec2 st = gl_FragCoord.xy / u_resolution.xy;
  vec2 mousePos = u_mouse.xy / u_resolution.xy;
  
  vec3 color = vec3(mousePos.x);

  gl_FragColor = vec4(color, 1.);
}

现在让我们让事情变得更有趣一点。我们将创建一个围绕光标的渐变圆圈。

为了实现这一点,我们将根据片段与鼠标位置的距离设置片段的颜色。距离越近,片段越暗,值接近0(黑色)。

为了计算距离,我们将使用预定义的distance()函数。

GLSL 复制代码
#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;

void main() {
  vec2 st = gl_FragCoord.xy/u_resolution.xy;
  vec2 mousePos = u_mouse.xy / u_resolution.xy;
  
  float d = distance(st, mousePos);
  
  vec3 color = vec3(d);

  gl_FragColor = vec4(color, 1.);
}

反转颜色:

GLSL 复制代码
float d = 1. - distance(st, mousePos);

为了控制圆的半径,我们将使用pow()函数。增加指数将减小圆的大小。

GLSL 复制代码
vec3 color = vec3(pow(d, 10.));

第三个统一变量是一个代表时间的浮点数。它是着色器开始运行后的秒数。

GLSL 复制代码
#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;

void main() {
  vec2 st = gl_FragCoord.xy/u_resolution.xy;
  vec2 mousePos = u_mouse.xy / u_resolution.xy;
  
  float d = 1. - distance(st, mousePos);
  
  vec3 color = vec3(pow(d, 10. * u_time));

  gl_FragColor = vec4(color, 1.);
}

这会导致圆消失,接下来,我们将对时间应用sin()函数,该函数返回-1和1之间的值,使圆具有动画效果。

GLSL 复制代码
vec3 color = vec3(pow(d, 10. * sin(u_time)));

请注意,当sin()返回负值时,画布将在较长时间内保持白色。

为了平衡,我们将使用abs()函数来只考虑正值。

GLSL 复制代码
vec3 color = vec3(pow(d, 10. * abs(sin(u_time))));
GLSL 复制代码
vec3 color = vec3(pow(d, 10. * abs(1.2 - sin(u_time))));

圆与环

这里的主要重点是避免值修改,这会导致创建渐变。

例如,如果我想要一个定义好的黑色圆,我需要它的半径内的值正好为0,其余的设置为1,避免任何中间值。

为了实现这一点,我们可以使用GLSL内置函数step()。这个函数比较它的两个输入:如果第二个参数小于第一个,它返回0;如果它大于,它返回1。

GLSL 复制代码
#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;

void main() {
  vec2 st = gl_FragCoord.xy/u_resolution.xy;
  
  float d1 = step(0.5, distance(vec2(0.5), st));
  
  vec3 color = vec3(d1);

  gl_FragColor = vec4(color, 1.);
}

如果当前片段到中心的距离小于0.5,则将其设置为黑色;否则,将其设置为白色。

GLSL 复制代码
float d1 = step(0.5, distance(vec2(0.5), st));

现在,要绘制圆环,我将创建另一个圆。

GLSL 复制代码
float d1 = step(0.44, distance(vec2(0.5), st));
float d2 = step(0.6, 1. - distance(vec2(0.5), st));
vec3 color = vec3(d1 + d2);

现在,用于计算d1step()的参数表示圆环的外半径,而用于计算d2step()的第一个参数表示圆环的内半径。

脉动光

在GLSL中,内置函数fact()返回浮点数的小数部分。

所以,当我在这里应用fact()时,你可以看到我们获得了几乎相同的梯度。如果不是完全相同的话,可以使用st.x时。

GLSL 复制代码
#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;

void main() {
  vec2 st = gl_FragCoord.xy/u_resolution.xy;
  
  vec3 color = vec3(fract(st.x));

  gl_FragColor = vec4(color, 1.);
}

现在,将参数乘以10。你会得到一个重复的渐变图案。

GLSL 复制代码
vec3 color = vec3(fract(10. * st.x));

现在,要将其转换为放射状图案,请将st.x替换为与中心的距离。

GLSL 复制代码
float d = distance(vec2(0.5), st);
    
vec3 color = vec3(fract(10. * d));

若要设置动画,请减去u_time

GLSL 复制代码
#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform float u_time;

void main() {
  vec2 st = gl_FragCoord.xy/u_resolution.xy;
  
  float d = distance(vec2(0.5), st);
  
  vec3 color = vec3(fract(10. * d - u_time));

  gl_FragColor = vec4(color, 1.);
}

此外,将所有内容除以距离,然后再除以40。

GLSL 复制代码
vec3 color = vec3(fract(10. * d - u_time)) / d / 40.;

最后,让我们介绍一些颜色。为此,创建一个包含任意值的vec3变量。然后,您可以尝试使用各种运算符来实现不同的视觉效果。

GLSL 复制代码
#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform float u_time;

void main() {
  vec2 st = gl_FragCoord.xy/u_resolution.xy;
  
  float d = distance(vec2(0.5), st);
  
  vec3 color = vec3(fract(10. * d - u_time)) / d / 40.;
  
  vec3 color2 = vec3(0.1, 0.5, 0.9);

  // Try /, +, and *
  gl_FragColor = vec4(color - color2, 1.);
}

国际象棋

floor()作用是:将浮点数向下舍入为小于或等于原始数字的最近整数。

示例:

GLSL 复制代码
floor(0.7) === 0.
floor(2.2) === 2.
floor(3.9) === 3.

另一方面,mod()计算两个数之间除法运算的余数。

示例:

GLSL 复制代码
mod(6., 2.) === 0.
mod(5., 2.) === 1.
mod(10., 4.) === 2.

现在,当我们对st.x应用floor()函数时,我们仍然得到相同的黑色画布。

GLSL 复制代码
#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;

void main() {
  vec2 st = gl_FragCoord.xy/u_resolution.xy;
  
  float checkerX = floor(st.x);

  vec3 checker = vec3(checkerX);
  
  gl_FragColor = vec4(checker, 1.);
}

要进行明显的更改,请将输入乘以任意数字。

GLSL 复制代码
#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;

void main() {
  vec2 st = gl_FragCoord.xy/u_resolution.xy;
  
  float frequency = 5.;
  
  float checkerX = floor(st.x * frequency);

  vec3 checker = vec3(checkerX);
  
  gl_FragColor = vec4(checker, 1.);
}

完成后,将mod()函数应用于结果。

GLSL 复制代码
vec3 checker = vec3(mod(checkerX, 2.0));

接下来,使用st.y而不是st.x来创建水平线。然后,将它们添加到mod()以形成棋盘模式。

GLSL 复制代码
float checkerX = floor(st.x * frequency);
    
float checkerY = floor(st.y * frequency);

vec3 checker = vec3(mod(checkerX + checkerY, 2.));
GLSl 复制代码
#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform float u_time;

void main() {
  vec2 st = gl_FragCoord.xy/u_resolution.xy;
  
  float frequency = 5.;
  
  float checkerX = floor(st.x * frequency + u_time);
  
  float checkerY = floor(st.y * frequency);

  vec3 checker = vec3(mod(checkerX + checkerY, 2.));
  
  gl_FragColor = vec4(checker, 1.);
}

为了控制速度,我们需要将u_time乘以另一个浮点数。

GLSL 复制代码
float speed = 2.;
    
float checkerX = floor(st.x * frequency + u_time * speed);

旋转和缩放

为了旋转图形,我们需要使用旋转矩阵。

因此,我将创建一个函数。它以旋转角度为参数,并返回一个旋转矩阵。

GLSL 复制代码
mat2 rotate(float angle) {
  return mat2(cos(angle), -sin(angle), sin(angle), cos(angle));
}

现在,当我们将此应用于画布时,您会注意到旋转原点位于(0,0)点。

GLSL 复制代码
#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform float u_time;

mat2 rotate(float angle) {
    return mat2(cos(angle), -sin(angle), sin(angle), cos(angle));
}

void main() {
  vec2 st = gl_FragCoord.xy/u_resolution.xy;
  
  st *= rotate(sin(u_time * 0.1) * 5.);
  
  float frequency = 5.;
  
  float checkerX = floor(st.x * frequency);
  
  float checkerY = floor(st.y * frequency);

  vec3 checker = vec3(mod(checkerX + checkerY, 2.));
  
  vec3 color = vec3(0.423, 0.435, 0.800);
    
  gl_FragColor = vec4(checker + color, 1.);
}

若要使画布的中心成为旋转原点。请在应用旋转之前将坐标系移动一半,然后再将其移回,以调整坐标系。

GLSl 复制代码
st -= vec2(0.5);
st *= rotate(sin(u_time * 0.1) * 5.);
st += vec2(0.5);

同样的原则也适用于缩放对象;我们只需要使用不同的矩阵进行缩放。

GLSL 复制代码
#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform float u_time;

mat2 rotate(float angle) {
    return mat2(cos(angle), -sin(angle), sin(angle), cos(angle));
}

mat2 scale(vec2 scale) {
    return mat2(scale.x, 0., 0., scale.y);
}

void main() {
  vec2 st = gl_FragCoord.xy/u_resolution.xy;
  
  st -= vec2(0.5);
  st *= rotate(sin(u_time * 0.1) * 5.);
  st += vec2(0.5);
  
  st -= vec2(0.5);
  st *= scale(vec2(sin(u_time * 0.1) * 8.));
  st += vec2(0.5);
  
  float frequency = 5.;
  
  float checkerX = floor(st.x * frequency);
  
  float checkerY = floor(st.y * frequency);

  vec3 checker = vec3(mod(checkerX + checkerY, 2.));
  
  vec3 color = vec3(0.423, 0.435, 0.800);
  
  gl_FragColor = vec4(checker + color, 1.);
}

将Shader集成到Three.js应用程序中

Book of Shader's Editor中的正方形表示整个场景。但是,在实际示例中,您可能希望将相同的效果应用于场景中的平面,而不是整个场景本身。

首先,创建一个Three.js应用程序或使用我的Three.js样板

接下来,将以下代码复制并粘贴到main.js和index.html文件中。

main.js:

js 复制代码
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
  45,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);

// Sets orbit control to move the camera around
const orbit = new OrbitControls(camera, renderer.domElement);

// Camera positioning
camera.position.set(6, 8, 14);
orbit.update();

const uniforms = {
  u_time: { value: 0.0 },
  u_resolution: {
    value: new THREE.Vector2(
      window.innerWidth,
      window.innerHeight
    ).multiplyScalar(window.devicePixelRatio),
  },
  u_mouse: { value: new THREE.Vector2(0.0, 0.0) },
};

window.addEventListener('mousemove', function (e) {
  uniforms.u_mouse.value.set(
    e.offsetX / this.window.innerWidth,
    1 - eoffsetnY / this.window.innerHeight
  );
});

const geometry = new THREE.PlaneGeometry(10, 10, 30, 30);
const customMaterial = new THREE.ShaderMaterial({
  vertexShader: document.getElementById('vertexshader').textContent,
  fragmentShader: document.getElementById('fragmentshader').textContent,
  uniforms,
});
const mesh = new THREE.Mesh(geometry, customMaterial);
scene.add(mesh);

const clock = new THREE.Clock();
function animate() {
  uniforms.u_time.value = clock.getElapsedTime();
  renderer.render(scene, camera);
}

renderer.setAnimationLoop(animate);

window.addEventListener('resize', function () {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

index.html:

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Wael Yasmina Three.js boilerplate</title>
    <style>
      body {
        margin: 0;
      }
    </style>
  </head>
  <body>
    <script id="vertexshader" type="vertex">
      void main() {
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
      }
    </script>

    <script id="fragmentshader" type="fragment">
      uniform float u_time;
      uniform vec2 u_resolution;
      uniform vec2 u_mouse;
      void main() {
          gl_FragColor = vec4(1.0);
      }
    </script>
    <script src="/main.js" type="module"></script>
  </body>
</html>

现在,将第一个示例中的着色器复制并粘贴到应用程序的片段着色器中。

js 复制代码
<script id="fragmentshader" type="fragment">
uniform float u_time;
uniform vec2 u_resolution;
uniform vec2 u_mouse;
void main() {
  vec2 st = gl_FragCoord.xy/u_resolution.xy;

  float d = 1. - distance(st, u_mouse);

  vec3 color = vec3(pow(d, 10. * abs(sin(u_time))));

  gl_FragColor = vec4(color, 1.);
}
</script>

现在,您应该看到相同的动画,但偏移量较大。这是因为效果是基于整个场景的大小而不仅仅是平面的大小来应用的。

要解决这个问题,首先将鼠标位置的x坐标乘以窗口的宽高比。

js 复制代码
window.addEventListener('mousemove', function (e) {
  const vpRatio = this.window.innerWidth / this.window.innerHeight;

  uniforms.u_mouse.value.set(
    (e.offsetX / this.window.innerWidth) * vpRatio,

    1 - e.offsetY / this.window.innerHeight
  );
});

下来,在片段着色器中,将画布的x坐标乘以其纵横比。

GLSL 复制代码
uniform float u_time;
uniform vec2 u_resolution;
uniform vec2 u_mouse;
void main() {
  vec2 st = gl_FragCoord.xy/u_resolution.xy;

  st.x *= u_resolution.x / u_resolution.y;

  float d = 1. - distance(st, u_mouse);

  vec3 color = vec3(pow(d, 10. * abs(sin(u_time))));

  gl_FragColor = vec4(color, 1.);
}

完整示例

纹理

在这个例子中,我们将看到如何将纹理映射到圆柱体上。

首先,将图像放置在项目目录的公用文件夹中。

接下来,在main.js文件中,加载纹理并将其传递给uniforms对象。

js 复制代码
const uniforms = {
  u_texture: { value: new THREE.TextureLoader().load('/fries.jpg') },
};
js 复制代码
//const geometry = new THREE.PlaneGeometry(10, 10, 30, 30);
const geometry = new THREE.CylinderGeometry(2, 2, 0, 100);
const customMaterial = new THREE.ShaderMaterial({
  vertexShader: document.getElementById('vertexshader').textContent,
  fragmentShader: document.getElementById('fragmentshader').textContent,
  uniforms,
});
const mesh = new THREE.Mesh(geometry, customMaterial);
scene.add(mesh);

现在,我们将使用texture2D()函数,它有两个参数:图像和纹理坐标。

该函数的目的是从纹理中检索颜色信息,也称为纹理元素。然后,在指定的纹理坐标处对该2D纹理进行采样。

您可以观察到圆柱体现在显示了纹理的一部分,从而创建了一个很酷的效果。但是,如果你想让纹理适合物体的表面,你需要将圆柱体的纹理坐标传递给texture2D()函数,而不是整个场景的坐标。

为了实现这一点,我们需要创建一个可变的变量来将网格的纹理坐标从顶点着色器传输到片段着色器。然后,将预定义uv变量的值赋给它。

js 复制代码
<script id="vertexshader" type="vertex">
  varying vec2 vUv;
  void main() {
    vUv = uv;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }
</script>
js 复制代码
<script id="fragmentshader" type="fragment">
  varying vec2 vUv;

  uniform sampler2D u_texture;
  void main() {
    vec4 texel = texture2D(u_texture, vUv);

    gl_FragColor = texel;
  }
</script>

完整示例

雷达信号

首先,我将演示如何使用此图像创建类似雷达的效果。

首先,我将选择一个任意的颜色,并使用纹理的纹理元素的阿尔法通道。另外,不要忘记加载纹理并将其与sampler2D变量相关联。

GLSL 复制代码
varying vec2 vUv;

uniform sampler2D u_texture;
void main() {

  // u_texture is the black and white image
  vec4 texel = texture2D(u_texture, vUv);

  gl_FragColor = vec4(vec3(0.4, 0.5, 1.0), texel.r);
}

正如你所看到的,我们得到了一个放射状的蓝色渐变,但是透明度不起作用。

若要解决此问题,请将材质的透明属性设置为true

GLSL 复制代码
varying vec2 vUv;

uniform float u_time;

uniform sampler2D u_texture;

mat2 rotate(float angle) {
  return mat2(cos(angle), -sin(angle), sin(angle), cos(angle));
}
void main() {

  vec2 vUv = vUv;

  vUv -= vec2(0.5);
  vUv *= rotate(sin(u_time * 0.1) * 5.);
  vUv += vec2(0.5);

  // u_texture is the black and white image
  vec4 texel = texture2D(u_texture, vUv);

  gl_FragColor = vec4(vec3(0.4, 0.5, 1.0), texel.r);
}

我们可以做的另一件有趣的事情是在另一个纹理上叠加这个效果。

为了实现这一点,我将首先从纹理传递RGB值。

GLSL 复制代码
varying vec2 vUv;

uniform float u_time;

uniform sampler2D u_texture;
uniform sampler2D u_texture2;

mat2 rotate(float angle) {
  return mat2(cos(angle), -sin(angle), sin(angle), cos(angle));
}
void main() {

  vec2 vUv = vUv;

  vUv -= vec2(0.5);
  vUv *= rotate(sin(u_time * 0.1) * 5.);
  vUv += vec2(0.5);

  // u_texture is the black and white image
  vec4 texel = texture2D(u_texture, vUv);

  // The cupcake image
  vec4 texel2 = texture2D(u_texture2, vUv);

  gl_FragColor = vec4(texel2.rgb, texel.r);
}

为了防止第二个纹理旋转,我将创建一个变量来保存旋转之前的原始UV坐标,并将其传递给texture2D()函数。

GLSL 复制代码
varying vec2 vUv;

uniform float u_time;

uniform sampler2D u_texture;
uniform sampler2D u_texture2;

mat2 rotate(float angle) {
  return mat2(cos(angle), -sin(angle), sin(angle), cos(angle));
}
void main() {

  vec2 vUv = vUv;
  vec2 vUv2 = vUv;

  vUv -= vec2(0.5);
  vUv *= rotate(sin(u_time * 0.1) * 5.);
  vUv += vec2(0.5);

  // u_texture is the black and white image
  vec4 texel = texture2D(u_texture, vUv);

  // The cupcake image
  vec4 texel2 = texture2D(u_texture2, vUv2);

  gl_FragColor = vec4(texel2.rgb, texel.r);
}

完整示例

淡入

clamp()函数接受三个输入:数值,最小值和最大值定义的范围。如果第一个输入下降值在该范围内,则函数返回相同的值。如果它小于最小值,则函数返回范围的最小值。相反,如果它大于最大值,则返回范围的最大值。

mix()函数在两个值之间执行线性插值,也称为lerp。简单地说,它返回在这两个值之间转换的值。

也就是说,我们将使用这两个函数在网格上纹理之间创建平滑过渡效果。

首先,准备好两个图像之间的转换。

现在,在uniforms对象中创建一个名为mixRatio的新float属性。此变量将控制过渡效果的量。

接下来,安装lil-gui,这样我们就可以通过接口控制该变量的值。

导入库,创建params对象,并将params对象中mixRatio属性的值链接到uniforms对象中的mixRatio

js 复制代码
import { GUI } from 'lil-gui';

const gui = new GUI();

const params = {
  mixRatio: 0.0,
};

gui.add(params, 'mixRatio', 0.0, 1.0).onChange(function (value) {
  uniforms.mixRatio.value = value;
});

const uniforms = {
  u_texture: { value: new THREE.TextureLoader().load('/burger1.jpg') },
  u_texture2: { value: new THREE.TextureLoader().load('/burger2.jpg') },
  u_transition: { value: new THREE.TextureLoader().load('/transition.png') },
  u_transition2: { value: new THREE.TextureLoader().load('/transition2.png') },
  mixRatio: { value: 0.0 },
};

这是片段着色器。

GLSL 复制代码
varying vec2 vUv;

uniform float u_time;

uniform sampler2D u_texture;
uniform sampler2D u_texture2;

uniform sampler2D u_transition;
uniform sampler2D u_transition2;

uniform float mixRatio;

void main() {

  vec2 vUv = vUv;

  // burger 1 image
  vec4 texel = texture2D(u_texture, vUv);

  // Burger2 image
  vec4 texel2 = texture2D(u_texture2, vUv);

  gl_FragColor = mix(texel, texel2, mixRatio);
}

首先,让我们通过调整mixRatio值来观察对两个纹理的mix()函数的基本调用。

通过这样做,您将看到当mixRatio为0时,只有第一个纹理可见。当它为1时,只有第二个纹理可见

现在,我们将使用另一个纹理来塑造过渡效果。为此,我们将使用一些数学方法,包括前面讨论的clamp()函数,来控制混合的值。

GLSL 复制代码
varying vec2 vUv;

uniform sampler2D u_texture;
uniform sampler2D u_texture2;

uniform sampler2D u_transition;
uniform sampler2D u_transition2;

uniform float mixRatio;

void main() {

  vec2 vUv = vUv;

  // burger 1 image
  vec4 texel = texture2D(u_texture, vUv);

  // Burger 2 image
  vec4 texel2 = texture2D(u_texture2, vUv);

  // transition texture 1
  vec4 transitionTexel = texture2D(u_transition, vUv);

  // transition texture 2
  vec4 transitionTexel2 = texture2D(u_transition2, vUv);

  float r = mixRatio * 1.6 - 0.3;

  // Try transitionTexel2 for another effect
  float mixF = clamp((transitionTexel.r - r) * 3.33, 0.0, 1.0);

  gl_FragColor = mix(texel, texel2, mixF);
}

完整示例

原文:waelyasmina.net/articles/9-...

相关推荐
m0_738820207 分钟前
前端模拟接口工具-json-server
前端·json
姬嘉晗-19期-河北工职大14 分钟前
jQuery总结(思维导图+二维表+问题)
前端·javascript·jquery
逆旅行天涯22 分钟前
【Threejs】从零开始(十)--加载gltf模型和压缩后的模型
前端
王家视频教程图书馆33 分钟前
请求go web后端接口 java安卓端播放视频
android·java·前端
请叫我飞哥@33 分钟前
HTML 基础
前端·html
一点一木1 小时前
🚀 2024年12月 GitHub 十大热门项目排行榜 🔥
前端·人工智能·github
黄毛火烧雪下1 小时前
介绍 Html 和 Html 5 的关系与区别
前端·html
键盘舞者1131 小时前
玩安卓-鸿蒙版 二 首页横幅、搜索、跳转链接功能
前端·鸿蒙·鸿蒙系统
闲人一枚(学习中)1 小时前
前端模块化
前端
云创彦祖1 小时前
vue分辨率适配浏览器缩放
前端·javascript·vue.js