import * as THREE from "three";// --- 1. 定义 Uniforms 的 TypeScript 接口 ---// 这有助于在 TS 中获得代码提示interface BendSpiraUniforms {
uTexture: { value: THREE.Texture | null };
uBendAmount: { value: number };
uVelocity: { value: number };
uOpacity: { value: number };
}// --- 2. 自定义材质类 ---export class BendSpiraMaterial extends THREE.ShaderMaterial {
constructor(parameters?: THREE.ShaderMaterialParameters) {
const uniforms: BendSpiraUniforms = {
uTexture: { value: null }, // 定义纹理插槽
uBendAmount: { value: 3.0 }, // 默认弯曲程度
uVelocity: { value: 0.0 }, // 默认速度(倾斜感)
uOpacity: { value: 1.0 }, // 基础透明度
// --- Vertex Shader (顶点着色器):处理几何体变形 ---
uniform float uBendAmount;
uniform float uVelocity;
// 根据 UV 的 x 坐标(0~1)计算一个弧度。
// (uv.x - 0.5) 将范围移至 -0.5 ~ 0.5,让中心点不弯曲。
float angle = (uv.x - 0.5) * 0.4;
// 使用余弦函数计算 Z 轴的偏移量,实现拱形弯曲。
// 乘 uBendAmount 控制弯曲深度。
pos.z += -cos(angle * 3.14159) * uBendAmount + uBendAmount;
// 根据 UV 的 x 坐标和当前速度,让 Y 轴发生偏移。
// 这会产生一种物体由于惯性产生的上下拉伸/倾斜感。
pos.y -= uv.x * uVelocity;
vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);
// 将深度值传递给片元着色器(注意:在 View Space 中 Z 是负值,所以取反)
gl_Position = projectionMatrix * mvPosition;
// --- Fragment Shader (片元着色器):处理颜色和透明度 ---
const fragmentShader = `
precision mediump float;
uniform sampler2D uTexture;
// 【核心逻辑 3:深度雾化 (Depth Fade)】
// 使用 smoothstep 根据顶点深度计算一个透明度系数。
// 160.0 是远处完全透明的距离,45.0 是近处完全不透明的距离。
float depthOpacity = smoothstep(160.0, 45.0, vDepth);
vec4 texColor = texture2D(uTexture, vUv);
// 混合所有透明度因素:纹理自身的 A 通道 * 深度淡出 * 基础透明度
float finalOpacity = texColor.a * depthOpacity * uOpacity;
gl_FragColor = vec4(texColor.rgb, finalOpacity);
// 调用父类 ShaderMaterial 的构造函数
...parameters, // 允许外部传入 transparent, side 等参数
vertexShader: vertexShader,
fragmentShader: fragmentShader,
// 外部可以通过 params 覆盖以下默认值,但通常建议在这里设定关键状态
transparent: true, // 必须开启透明
side: THREE.DoubleSide, // 两面可见
depthWrite: false, // 关键:解决透明物体排序闪烁问题
// --- 3. 添加快捷访问器 (Getters/Setters) ---
// 这样你可以直接 material.bendAmount = 2.0,而不是去动 uniforms
get map(): THREE.Texture | null {
return this.uniforms.uTexture.value;
set map(value: THREE.Texture | null) {
this.uniforms.uTexture.value = value;
get bendAmount(): number {
return this.uniforms.uBendAmount.value;
set bendAmount(value: number) {
this.uniforms.uBendAmount.value = value;
get velocity(): number {
return this.uniforms.uVelocity.value;
set velocity(value: number) {
this.uniforms.uVelocity.value = value;
return this.uniforms.uOpacity.value;
set opacity(value: number) {
this.uniforms.uOpacity.value = value;