Three.js 收藏吃灰系列:一套代码,把你的 GLB 模型变成漫威级全息投影
Three.js 进阶实战:打造“锐利边缘”的赛博朋克特效
![]()
在 Three.js 的 3D 开发中,我们经常需要制作“扫描”、“全息”或者“能量护盾”的效果。最常用的手段是使用 菲涅尔(Fresnel) 效应。
但是,默认的菲涅尔效果往往呈现出一种“软绵绵”的雾化感,中心区域总是有一层洗不掉的朦胧。如果你想要一种硬核、锐利、仅保留物体轮廓的赛博朋克风格(Hard-surface Edge),就需要对 Shader 进行一些特殊的数学运算截断。
今天,我们就来拆解一套**“锐利边缘能量体”**的代码实现。
前排提示:本文涉及的特效逻辑,源自我正在开发的 3D 编辑器项目 Meteor3DEditor。如果你对 Web3D 引擎开发感兴趣,欢迎 Star 和体验!
- GitHub: github.com/nikonikoCW/…
- 官网: meteor3d.cn/
核心难点:如何让菲涅尔变得“锐利”?
普通的菲涅尔公式通常是 1.0 - dot(viewDir, normal)。这会产生一个从边缘向中心平滑过渡的渐变。
为了得到锐利的边缘,我们需要在 Fragment Shader 中做三件事:
- 极高次幂压缩:把边缘压扁。
- 阈值截断:把中心的“雾气”彻底切除。
- 亮度重映射:把剩下的边缘提亮。
1. Shader 代码解析
这是核心的 ShaderMaterial 实现。请注意看 fragmentShader 中的注释部分:
JavaScript
const sharpFresnelShader = {
uniforms: {
color: { value: new THREE.Color(0x00ffff) }, // 赛博青色
opacityMultiplier: { value: 1.0 }
},
vertexShader: `
varying vec3 vWorldNormal;
varying vec3 vViewDirection;
void main() {
// 获取世界坐标系下的法线
vWorldNormal = normalize(mat3(modelMatrix) * normal);
vec4 worldPosition = modelMatrix * vec4(position, 1.0);
// 计算视线方向
vViewDirection = normalize(cameraPosition - worldPosition.xyz);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform vec3 color;
uniform float opacityMultiplier;
varying vec3 vWorldNormal;
varying vec3 vViewDirection;
void main() {
vec3 normal = normalize(vWorldNormal);
vec3 viewDir = normalize(vViewDirection);
// --- 基础菲涅尔 ---
float dotProduct = dot(viewDir, normal);
// 1.0 - abs(dot) 确保双面渲染时背面也能正确发光
float fresnel = 1.0 - abs(dotProduct);
fresnel = clamp(fresnel, 0.0, 1.0);
// --- 关键魔法区域 ---
// 技巧1:使用 pow(6.0) 甚至更高。
// 默认的菲涅尔可能是 pow(2.0),看起来很软。
// 6.0 的指数会让函数曲线极速陡峭,只有最边缘的地方保留数值,其余迅速归零。
fresnel = pow(fresnel, 6.0);
// 技巧2:阈值截断 (Threshold)
// 即使是 pow(6.0),中心正对相机的地方可能还有 0.01 的微弱值。
// 这种微弱值在叠加模式下会导致模型看起来脏脏的。
// 我们减去 0.1,强制把这一部分变成纯黑(透明)。
fresnel = max(fresnel - 0.1, 0.0);
// 技巧3:亮度补偿
// 因为刚才减去了 0.1,最大值只剩 0.9 了,而且整体变暗。
// 乘一个系数把边缘亮度拉爆。
fresnel *= 2.5;
// ------------------
float finalAlpha = fresnel * opacityMultiplier;
// 颜色也乘以 alpha,配合 AdditiveBlending 食用效果更佳
gl_FragColor = vec4(color * finalAlpha, finalAlpha);
}
`
};
2. 材质配置的关键点
Shader 写好了,Material 的配置同样重要。为了达到“全息投影”那种通透感,我们需要关闭深度写入,并开启叠加混合。
JavaScript
this.energyMaterial = new THREE.ShaderMaterial({
uniforms: THREE.UniformsUtils.clone(sharpFresnelShader.uniforms),
vertexShader: sharpFresnelShader.vertexShader,
fragmentShader: sharpFresnelShader.fragmentShader,
transparent: true,
side: THREE.DoubleSide, // 双面渲染:让模型内部的结构线条也能透出来,增加复杂度
depthWrite: false, // 关闭深度写入:这是实现“透明重叠”不穿帮的关键
blending: THREE.AdditiveBlending // 叠加混合:重叠的部分会变亮(变白),模拟光的物理叠加
});
动态系统:上升的能量残影
光有一个静止的边缘是不够帅的。我们需要让它动起来。这里我们实现了一个简单的粒子系统思想:不断克隆模型,向上发射,然后消失。
系统逻辑 (RisingEnergySystem)
这个类负责管理所有的“残影(Ghost)”。
-
Spawn (生成) :每隔几百毫秒,克隆一次目标模型。
-
Update (更新) :
- 将所有残影沿 Y 轴向上移动。
- 随着时间流逝,减小
opacityMultiplier(透明度)。 - 当生命周期结束,从场景移除并销毁内存。
JavaScript
spawn(originPosition, originRotation, scale) {
if (this.sourceGeoGroups.length === 0) return;
const ghostGroup = new THREE.Group();
// 技巧:每次生成时微调颜色,让能量场色彩更丰富
const instanceMat = this.energyMaterial.clone();
instanceMat.uniforms.color.value.setHSL(0.5 + Math.random() * 0.1, 1.0, 0.5);
this.sourceGeoGroups.forEach(template => {
const mesh = template.clone();
mesh.material = instanceMat;
ghostGroup.add(mesh);
});
// ...设置位置、旋转、缩放...
// 稍微向上偏移一点出生点,避免和实体模型完全重叠导致 Z-Fighting 闪烁
ghostGroup.position.y += 0.05;
this.scene.add(ghostGroup);
this.ghosts.push({
mesh: ghostGroup,
materialBtn: instanceMat,
life: 1.0,
speed: 1.5
});
}
为什么会有“扫描”的感觉?
因为我们使用了 AdditiveBlending(叠加混合)。
当新的残影生成时,它离实体模型很近。实体模型本身可能有一个基础亮度,加上残影的亮度,重叠部分会爆亮。随着残影上升,它与本体分离,光线被“拉”开了。这种高亮 -> 分离 -> 消失的过程,视觉上就形成了强烈的能量扫描感。
![效果图占位:建议放一张局部特写,展示边缘重叠变亮的效果]
性能优化小贴士
在代码中,有一个细节处理:
JavaScript
// update 循环中
ghost.materialBtn.dispose(); // 清理内存
由于我们不断地 new ShaderMaterial(或者 clone),这会创建大量的 WebGL Program。如果不手动 dispose,Three.js 可能会保留这些材质引用,导致内存泄漏。虽然在 Demo 级别无所谓,但在生产环境中,建议使用材质池(Material Pool)或者InstancedMesh来复用资源,避免频繁创建销毁。
完整体验与源码
这个特效非常适合用于展示高科技感的 3D 模型,比如无人机、芯片、或者飞船内部结构。
如果你想看完整的运行效果,或者想直接 Copy 源码运行,我已经整理在文章开头了。而如果你想在一个可视化的环境中直接调整这些 Shader 参数,而不用每次都改代码,欢迎尝试我开发的 Web3D 编辑器:
🌌 Meteor3DEditor
Meteor3D 是一个基于 Web 的轻量级 3D 编辑器,旨在简化 Three.js 的场景搭建和特效调试。
- GitHub: github.com/nikonikoCW/…
- 在线官网: meteor3d.cn/
如果你觉得这个特效对你有帮助,欢迎来 GitHub 点个 Star ⭐️,也欢迎提 Issue 交流更多的 Shader 技巧!
总结:
- Pow(6.0) 制造锐利边缘。
- Minus Offset 去除中心雾化。
- AdditiveBlending 制造光的叠加感。
- DoubleSide 增加模型内部细节的透视感。
希望这篇文章能给你的 WebGL 开发带来一点灵感!