今天,我们来实现一个「透明能量罩」球体效果。
它既具有半透明材质的质感,又具备可调光晕与噪声纹理的动态效果,非常适合用于科技感 UI、游戏护盾、空间标识等场景。
效果参考如下:
代码实现
界面搭建
首先创建 Three.js 的基础场景,包括渲染器、摄像机和动画循环。
import * as THREE from "three";
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.z = 5;
const animate = function () {
requestAnimationFrame(animate);
renderer.render(scene, camera);
};
animate();
创建控制器
使用 OrbitControls
方便我们从各个角度观察球体:
import { OrbitControls } from "three/examples/jsm/Addons.js";
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.25;
controls.enableZoom = false;
controls.enablePan = false;
球体创建
我们创建一个球体并使用自定义的 Shader 材质。由于要控制透明度与噪声扰动,所以不能使用内置的标准材质。
const geometry = new THREE.SphereGeometry(1, 32, 32);
const material = new THREE.ShaderMaterial({
vertexShader: "",
fragmentShader: "",
});
const sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);
- 创建一个半径为 1 的球体,参数
(1, 32, 32)
分别表示:半径、水平分段数、垂直分段数; - 较高的分段数保证曲面光滑,为后续光罩细节提供基础。
着色器编写
我们将创建两个 GLSL 文件 —— vertex.glsl
(顶点着色器)与 fragment.glsl
(片元着色器),分别控制几何体的空间定位与像素渲染逻辑。
同时,在材质中定义两个 uniform
变量:
uColor
:控制球体的主色调;uNoiseTexture
:噪声纹理,模拟不均匀发光。
以下是 Shader 材质的完整配置:
import vertexShader from "./shader/vertex.glsl";
import fragmentShader from "./shader/fragment.glsl";
const material = new THREE.ShaderMaterial({
vertexShader: Vertex,
fragmentShader: Fragment,
uniforms: {
uColor: { value: new THREE.Color(0xff0000) }, // 红色
uNoiseTexture: {
value: null,
},
},
transparent: true,
blending: THREE.AdditiveBlending,
});
ShaderMaterial
:Three.js 提供的自定义着色器材质,你可以完全控制顶点和片元如何渲染;vertexShader
/fragmentShader
:分别是 GLSL 写的顶点与片元着色器源码;uniforms
:传递给着色器的全局变量(只读),比如颜色、纹理等;uColor
:光罩主色,这里设为红色;uNoiseTexture
:噪声纹理,用于扰动像素颜色;
transparent: true
:启用透明度渲染;blending: THREE.AdditiveBlending
:使用加色混合模式,叠加颜色值制造发光感。
噪声纹理加载
加载的图片效果如下
const textureLoader = new THREE.TextureLoader();
textureLoader.load("/noise1.png", (texture) => {
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
material.uniforms.uNoiseTexture.value = texture;
});
顶点着色器(vertex.glsl)
为后续的片元阶段提供两个关键数据:一个是纹理坐标,用于采样噪声纹理;另一个是控制发光强度的变量,用于调整球体表面的明暗分布。
首先,我们需要定义一个 vUv
变量,将每个顶点的 UV 坐标传递到片元着色器中。这样,片元阶段才能正确采样噪声纹理,实现表面纹理扰动的效果。
接着,我们引入一个 vIntensity
变量,用于控制每个像素的发光强度。虽然在这段代码中我们先暂时将它设置为常量 1.8
,但这个值可以根据视角与法线之间的夹角动态调整,实现“背光更亮”的效果。这为之后的光罩模拟提供了方向感的基础。
在准备这些数据的同时,我们还需要将当前顶点的位置从局部坐标变换到世界坐标系下,以便计算摄像机方向。为此,我们使用 modelMatrix * vec4(position, 1.0)
得到世界空间位置 worldPosition
,再用 cameraPosition - worldPosition.xyz
得到当前点指向摄像机的向量。
此外,法线方向也必须转换到世界空间并归一化,保证方向计算的准确性。这一步通过 modelMatrix * vec4(normal, 0.0)
实现,结果存储在 normal
变量中。
最后,我们调用 gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0)
将顶点坐标变换为裁剪空间坐标,完成顶点阶段的常规输出。
varying float vIntensity;
varying vec2 vUv;
void main(){
vec4 worldPosition=modelMatrix*vec4(position,1.);
vec3 normal=normalize(vec3(modelMatrix*vec4(normal,0.)));
vec3 dirToCamera=normalize(cameraPosition-worldPosition.xyz);
vIntensity=1.8;
gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.);
vUv=uv;
}
vUv
:用于在片元着色器中采样纹理;vIntensity
:传递给片元着色器的发光强度;worldPosition
:将模型坐标转换为世界坐标;dirToCamera
:指向摄像机方向的单位向量,用于后续计算视角相关效果(如背光发亮);gl_Position
:顶点最终在屏幕上的位置,必须赋值。
片元着色器(fragment.glsl)
我们从顶点阶段带下 vUv 和 vIntensity,再配合外部传入的噪声纹理 uNoiseTexture 和基色 uColor,把“纹理坐标、局部亮度、整体配色”几条控制线准备好;
用 vUv 在噪声纹理上采样,取得每个像素专属的随机值,给材质增添细腻颗粒感;
把噪声颜色、亮度强度和基色三者直接相乘后输出到 gl_FragColor——一次乘法就把全局色调、局部发光和纹理细节融合在一起,实现既统一又富有层次的发光效果,同时预留了 uTime 接口,后续想做闪烁或流动动画也只需给采样坐标或亮度加上时间偏移即可。
varying float vIntensity;
varying vec2 vUv;
uniform sampler2D uNoiseTexture;
uniform vec3 uColor;
uniform float uTime;
void main(){
vec4 noiseColor=texture2D(uNoiseTexture,vUv);
gl_FragColor=vec4(noiseColor.rgb*vIntensity*uColor,1.);// Set the fragment color to red
}
texture2D(uNoiseTexture, vUv)
:从噪声纹理中采样颜色;noiseColor.rgb * vIntensity * uColor
:将噪声颜色、光照强度与主色叠加,形成最终颜色;gl_FragColor
:GPU 最终输出到屏幕上的颜色值。
代码
https://github.com/calmound/threejs-demo/tree/main/world (opens in a new tab)