Skip to main content

10. 进阶实战:三大常见 WebGL 效果

掌握了基础语法和渲染管线后,我们来看三个最常见的“花哨”效果是如何实现的。 这些效果是面试和实际项目中出现频率最高的。

1. 菲涅尔效果 (Fresnel Effect)

现象:物体边缘发光,或者边缘反光更强(像肥皂泡、水珠、丝绸)。 原理:根据“视线方向”和“法线方向”的夹角来改变颜色。视线越垂直于表面,颜色越深;视线越平行于表面(边缘),颜色越亮。

需要同时配合 Vertex Shader 和 Fragment Shader。

// --- 顶点着色器 (Vertex Shader) ---
attribute vec3 position;
attribute vec3 normal;

uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform mat3 normalMatrix; // 专门用于变换法线的矩阵

varying vec3 vNormal;
varying vec3 vViewPosition;

void main() {
// 1. 计算变换后的法线
vNormal = normalize(normalMatrix * normal);

// 2. 计算视图空间中的位置 (View Position)
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);

// 3. 视线向量 = 眼睛位置(0,0,0) - 物体位置
// 在视图空间中,相机永远在原点 (0,0,0)
// 所以 vector(Camera - Position) 等价于 -mvPosition
vViewPosition = -mvPosition.xyz;

gl_Position = projectionMatrix * mvPosition;
}

// --- 片段着色器 (Fragment Shader) ---
precision mediump float;

varying vec3 vNormal;
varying vec3 vViewPosition;

void main() {
// 1. 计算视线方向 (View Direction)
// 必须重新归一化,因为插值过程中长度会变
vec3 viewDir = normalize(vViewPosition);

// 2. 计算法线方向 (Normal Direction)
vec3 normal = normalize(vNormal);

// 3. 计算点积 (Dot Product)
// dot = 1.0 (正对) -> 边缘强度 = 0.0
// dot = 0.0 (边缘) -> 边缘强度 = 1.0
float dotProduct = dot(viewDir, normal);

// 4. 反转并增强效果
// clamp 确保值在 0.0 到 1.0 之间,abs 防止背面算出负数
float fresnel = 1.0 - clamp(abs(dotProduct), 0.0, 1.0);

// 用指数函数 (pow) 让边缘更锐利
// 指数越高,边缘光越细
fresnel = pow(fresnel, 3.0);

// 5. 混合颜色
vec3 baseColor = vec3(0.0, 0.0, 1.0); // 蓝色中心
vec3 glowColor = vec3(0.0, 1.0, 1.0); // 青色边缘

gl_FragColor = vec4(mix(baseColor, glowColor, fresnel), 1.0);
}

2. 纹理流动 (Texture Scrolling)

现象:火焰燃烧、水流流动、传送带。 原理:在采样纹理时,动态修改 UV 坐标。

uniform float uTime;
uniform sampler2D uTexture;
varying vec2 vUv;

void main() {
// 1. 修改 UV
vec2 flowUv = vUv;

// 让纹理沿着 X 轴移动
flowUv.x -= uTime * 0.5;

// 让纹理产生波浪扭曲 (高级技巧)
flowUv.y += sin(flowUv.x * 10.0 + uTime) * 0.1;

// 2. 采样
vec4 color = texture2D(uTexture, flowUv);

gl_FragColor = color;
}

3. 噪声 (Noise)

现象:云彩、地形、大理石纹路、溶解效果。 原理:GLSL 没有内置随机函数。我们需要一个“伪随机”函数,输入相同的坐标,永远返回相同的(看起来随机的)值。

最简单的 2D 伪随机函数(直接背诵即可):

// 输入 uv,返回 0.0 ~ 1.0 的随机数
float random (vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
}

// 简单的噪声溶解效果
void main() {
float noiseVal = random(vUv); // 生成噪点

// 随时间变化的阈值
float threshold = sin(uTime) * 0.5 + 0.5;

// 如果噪点值小于阈值,丢弃像素 (透明)
if (noiseVal < threshold) {
discard;
}

gl_FragColor = vec4(1.0);
}

💡 总结

这三个效果代表了 Shader 编程的三个方向:

  1. 光照方向 (菲涅尔):利用 dot(view, normal)
  2. UV 方向 (流动):利用 uv + time
  3. 算法方向 (噪声):利用数学公式生成图案。

📝 编程作业

难度:⭐⭐⭐ 困难 | 预计时间:120 分钟(每个效果 40 分钟)

任务要求

实现三个经典的 WebGL 效果,这是面试和实际项目中最常见的。

效果 1:菲涅尔效果 ⭐⭐

要求

  • 创建一个 3D 物体(球体或复杂模型)
  • 实现边缘发光效果
  • 边缘颜色和中心颜色可配置

代码提示

// 片段着色器
varying vec3 vNormal;
varying vec3 vViewPosition;

void main() {
vec3 viewDir = normalize(vViewPosition);
vec3 normal = normalize(vNormal);

float fresnel = 1.0 - clamp(abs(dot(viewDir, normal)), 0.0, 1.0);
fresnel = pow(fresnel, 3.0);

vec3 baseColor = vec3(0.0, 0.0, 1.0);
vec3 glowColor = vec3(0.0, 1.0, 1.0);

gl_FragColor = vec4(mix(baseColor, glowColor, fresnel), 1.0);
}

效果 2:纹理流动 ⭐⭐

要求

  • 加载一张纹理图片
  • 实现纹理的滚动动画
  • 可选:添加波浪扭曲效果

代码提示

uniform float uTime;
uniform sampler2D uTexture;
varying vec2 vUv;

void main() {
vec2 flowUv = vUv;
flowUv.x -= uTime * 0.5; // 水平滚动

// 可选:波浪扭曲
flowUv.y += sin(flowUv.x * 10.0 + uTime) * 0.1;

gl_FragColor = texture2D(uTexture, flowUv);
}

效果 3:噪声溶解 ⭐⭐⭐

要求

  • 实现基础的噪声函数
  • 创建溶解效果(物体逐渐消失)
  • 添加边缘发光(溶解边缘)

代码提示

// 噪声函数
float random(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
}

uniform float uTime;
varying vec2 vUv;

void main() {
float noiseVal = random(vUv);
float threshold = sin(uTime) * 0.5 + 0.5;

if (noiseVal < threshold) {
discard; // 丢弃像素
}

// 边缘发光
float edge = smoothstep(threshold - 0.1, threshold, noiseVal);
vec3 glowColor = vec3(1.0, 0.5, 0.0);

gl_FragColor = vec4(glowColor * edge, 1.0);
}

检查清单

  • 菲涅尔效果边缘清晰可见
  • 纹理流动平滑自然
  • 溶解效果随时间变化
  • 所有效果参数可通过 uniform 调整
  • 代码结构清晰,有注释

扩展挑战

  • 🌟 将三个效果组合在一起
  • 🌟 添加 GUI 控制面板(如 dat.GUI)
  • 🌟 实现更复杂的噪声函数(如 Perlin Noise)

参考资源