Three.js-着色器(glsl内置函数实现各种效果2)

该博客详细介绍了如何使用Three.js中的着色器和GLSL内置函数来创建各种图形效果,包括绘制圆、圆环、靶子环、波浪环、角度视图、螺旋渐变、雷达扫射、光芒四射、噪声算法实现的效果等,并附带了示例代码和实现效果展示。

Three.js-着色器(glsl内置函数)_HM-hhxx!的博客-优快云博客Threejs中着色器(glsl内置函数)的介绍及使用,利用内置函数实现各种图形及效果。https://blog.youkuaiyun.com/damadashen/article/details/125807156?spm=1001.2014.3001.5501

接上一篇,继续使用着色器及内置函数实现各种图形及效果。

目录

1. 绘制圆

2.绘制圆环

3.靶子环

4.波浪环

5. 根据角度显示视图(atan())

6.根据角度实现螺旋渐变

7.实现雷达扫射效果

8. 光芒四射效果

9.  使用噪声算法实现各种效果

10.噪声算法二-cnoise()的使用

11. 混合颜色


1. 绘制圆

1)白色圆

  float strength = 1.0 - step(0.5,distance(vUv,vec2(0.5))+0.25) ;
  gl_FragColor =vec4(strength,strength,strength,1);

实现效果:

 2)黑色圆

    float strength = step(0.5,distance(vUv,vec2(0.5))+0.25) ;
    gl_FragColor =vec4(strength,strength,strength,1);

 2.绘制圆环

    float strength = step(0.5,distance(vUv,vec2(0.5))+0.35) ;
    strength *= (1.0 - step(0.5,distance(vUv,vec2(0.5))+0.25)) ;
    gl_FragColor =vec4(strength,strength,strength,1);

实现效果: 

 3.靶子环

    float strength = step(0.1,abs(distance(vUv,vec2(0.5))-0.25))   ;
    gl_FragColor =vec4(strength,strength,strength,1);

实现效果:

 4.波浪环

    vec2 waveUv = vec2(
        vUv.x,
        vUv.y+sin(vUv.x*30.0)*0.1
    );
    float strength = 1.0 - step(0.01,abs(distance(waveUv,vec2(0.5))-0.25))   ;
    gl_FragColor =vec4(strength,strength,strength,1);

实现效果:

5. 根据角度显示视图(atan())

 反正切函数

    float angle = atan(vUv.x,vUv.y);
    float strength = angle;
    gl_FragColor =vec4(strength,strength,strength,1);

实现效果:

 6.根据角度实现螺旋渐变

    float angle = atan(vUv.x-0.5,vUv.y-0.5);
    float strength = (angle+3.14)/6.28;
    gl_FragColor =vec4(strength,strength,strength,1);

实现效果:

 7.实现雷达扫射效果

    vec2 rotateUv = rotate(vUv,-uTime*5.0,vec2(0.5));
    float alpha =  1.0 - step(0.5,distance(vUv,vec2(0.5)));
    float angle = atan(rotateUv.x-0.5,rotateUv.y-0.5);
    float strength = (angle+3.14)/6.28;
    gl_FragColor =vec4(strength,strength,strength,alpha);

实现效果:

8. 光芒四射效果

    float angle = atan(vUv.x-0.5,vUv.y-0.5)/(2.0*PI);
    float strength = sin(angle*100.0);
    
    gl_FragColor =vec4(strength,strength,strength,1);

实现效果:

9.  使用噪声算法实现各种效果

在fragment.glsl文件的main()函数前对噪声算法进行声明:

// 噪声函数
float noise (in vec2 _st) {
    vec2 i = floor(_st);
    vec2 f = fract(_st);

    // Four corners in 2D of a tile
    float a = random(i);
    float b = random(i + vec2(1.0, 0.0));
    float c = random(i + vec2(0.0, 1.0));
    float d = random(i + vec2(1.0, 1.0));

    vec2 u = f * f * (3.0 - 2.0 * f);

    return mix(a, b, u.x) +
            (c - a)* u.y * (1.0 - u.x) +
            (d - b) * u.x * u.y;
}

原始noise()使用

    float strength = noise(vUv);
    gl_FragColor =vec4(strength,strength,strength,1);

实现效果:

  

1)实现细碎黑白花效果

在main()函数中调用noise()进行实现:

    float strength = step(0.5,noise(vUv * 100.0)) ;
    gl_FragColor =vec4(strength,strength,strength,1);

实现效果:

 

10.噪声算法二-cnoise()的使用

在main()函数前对cnoise()进行声明:

//	Classic Perlin 2D Noise 
//	by Stefan Gustavson
//
vec4 permute(vec4 x)
{
    return mod(((x*34.0)+1.0)*x, 289.0);
}

vec2 fade(vec2 t)
{
    return t*t*t*(t*(t*6.0-15.0)+10.0);
}

float cnoise(vec2 P)
{
    vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0);
    vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0);
    Pi = mod(Pi, 289.0); // To avoid truncation effects in permutation
    vec4 ix = Pi.xzxz;
    vec4 iy = Pi.yyww;
    vec4 fx = Pf.xzxz;
    vec4 fy = Pf.yyww;
    vec4 i = permute(permute(ix) + iy);
    vec4 gx = 2.0 * fract(i * 0.0243902439) - 1.0; // 1/41 = 0.024...
    vec4 gy = abs(gx) - 0.5;
    vec4 tx = floor(gx + 0.5);
    gx = gx - tx;
    vec2 g00 = vec2(gx.x,gy.x);
    vec2 g10 = vec2(gx.y,gy.y);
    vec2 g01 = vec2(gx.z,gy.z);
    vec2 g11 = vec2(gx.w,gy.w);
    vec4 norm = 1.79284291400159 - 0.85373472095314 * vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11));
    g00 *= norm.x;
    g01 *= norm.y;
    g10 *= norm.z;
    g11 *= norm.w;
    float n00 = dot(g00, vec2(fx.x, fy.x));
    float n10 = dot(g10, vec2(fx.y, fy.y));
    float n01 = dot(g01, vec2(fx.z, fy.z));
    float n11 = dot(g11, vec2(fx.w, fy.w));
    vec2 fade_xy = fade(Pf.xy);
    vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x);
    float n_xy = mix(n_x.x, n_x.y, fade_xy.y);
    return 2.3 * n_xy;
}

1)通过时间设置变化波形

    float strength = step(uScale,cnoise(vUv * 10.0+uTime)) ;
    gl_FragColor =vec4(strength,strength,strength,1);

实现效果:

 2)实现发光路径

    float strength =1.0 - abs(cnoise(vUv * 10.0)) ;
    gl_FragColor =vec4(strength,strength,strength,1);

实现效果:

 3)利用时间实现波纹效果

    float strength = sin(cnoise(vUv * 10.0)*5.0+uTime) ;
    gl_FragColor =vec4(strength,strength,strength,1);

实现效果:

 11. 混合颜色

    vec3 purpleColor = vec3(1.0, 0.0, 1.0);
    vec3 greenColor = vec3(1.0, 1.0, 1.0);
    vec3 uvColor = vec3(vUv,1.0);
    float strength = step(0.9,sin(cnoise(vUv * 10.0)*20.0))  ;
    vec3 mixColor =  mix(greenColor,uvColor,strength);
    gl_FragColor =vec4(mixColor,1.0);

实现效果:

 

var gradientMaterial = new THREE.ShaderMaterial({ uniforms: { time: {value: 0.0}, num: {value: 1.0}, color1: {value: new THREE.Color(newGradBackColor)},//渐变动画背景颜色 color2: {value: new THREE.Color(newGradDownColor)},//渐变动画下沉颜色 state: {value: stateConfig}, // 0=下沉中, 1=显示背景 timer: {value: 0.0} // 背景显示计时 }, vertexShader: "varying vec2 vUv;\n" + "varying vec3 vNormal;\n" + "void main() {\n" + "vUv = uv;\n" + "vNormal = normal;\n" + "gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n" + "}", fragmentShader: "uniform vec3 color1, color2;\n" + "uniform float time, num;\n" + "uniform int state; // 新增:状态标识\n" + "uniform float timer; // 新增:计时器\n" + "varying vec2 vUv;\n" + "varying vec3 vNormal;\n" + "\n" + "void main() {\n" + "// 状态1:显示顶部渐变背景\n" + "if (state == 1) {\n" + "float alpha = 1.2 - vUv.y; // 顶部不透明到底部透明\n" + "gl_FragColor = vec4(color1, alpha);\n" + "}\n" + "// 状态0:下沉动画或常规背景\n" + "else {\n" + "// 顶部/底部渲染渐变背景\n" + "if (abs(vNormal.z) > 0.99 || vUv.y < 0.01) {\n" + "//float gradientFactor = smoothstep(0.0, 1.0, vUv.y);\n" + "//vec3 bgColor = mix(color1, color2, gradientFactor);\n" + "//gl_FragColor = vec4(bgColor, 1.0);\n" + "} \n" + "// 侧面下沉效果\n" + "else {\n" + "float alpha = 1.0 - fract((vUv.y - time) * num);\n" + "vec3 finalColor = mix(color2, color1, alpha);\n" + "gl_FragColor = vec4(finalColor, 0.9);\n" + "}\n" + "}\n" + "}", side: THREE.DoubleSide, transparent: true, // opacity: 0.8, // depthTest: true, // depthWrite: false, // map:textureImageCBMap, }); // gradientMaterial.uniforms.color1.value.setHSL(200 / 360, 0.65, 0.25);//让下沉结束时背景有渐变感 return gradientMaterial; 保持原先的下沉动画,添加渐变背景,该背景不受下沉动画的颜色影响
08-02
<think>我们有一个需求:在Three.js中使用ShaderMaterial实现一个带有下沉动画和渐变背景的材质,其中渐变背景不受下沉动画影响。 分析: 1. 下沉动画:我们可能是指顶点沿着y轴(或某个方向)向下移动,并且可能是随时间变化的。 2. 渐变背景:可能是整个背景有一个颜色渐变(例如从上到下或从中心到边缘的渐变),并且这个背景渐变不受顶点动画的影响,也就是说即使物体下沉,背景渐变仍然保持原来的样子(比如基于屏幕空间或世界空间固定)。 实现思路: - 使用ShaderMaterial同时编写顶点着色器和片元着色器- 顶点着色器实现下沉动画。我们可以通过修改顶点位置来实现,比如在y轴方向减去一个随时间增加的值(或者使用正弦波等函数来模拟上下波动,但这里要求是下沉,所以可能是线性或缓动的向下移动)。 - 片元着色器实现渐变背景。这里的关键是渐变背景不受下沉动画影响,意味着渐变应该基于原始位置(比如世界坐标或屏幕坐标)而不是变形后的位置。 具体步骤: 1. 顶点着色器- 通常,我们会将顶点位置从模型坐标转换到裁剪空间坐标。但在做动画之前,我们需要保存原始的世界坐标(或模型坐标)以便在片元着色器中用于背景渐变。 - 然后,我们对顶点位置应用下沉动画。下沉动画可以是一个随时间在y轴方向上的位移。我们可以通过uniform传入时间。 2. 片元着色器- 我们需要在片元着色器中计算渐变。由于要求渐变背景不受动画影响,所以我们应该使用动画之前的坐标(即原始坐标)来计算渐变。 - 我们可以将原始的世界坐标(或模型坐标)通过varying传递给片元着色器。但是注意,世界坐标是顶点变换到世界空间的位置,而模型坐标是原始顶点位置。这里我们可能需要根据渐变的性质选择。 - 如果我们希望渐变是相对于物体自身(即模型空间)的,那么我们可以使用模型坐标。但通常,背景渐变可能是基于世界坐标的(比如一个固定的天空盒效果),但这里我们是在物体材质上做渐变,所以可能是基于物体自身的坐标。 - 另一种思路:如果希望背景渐变是屏幕空间的(比如整个屏幕的渐变),那么我们可以使用屏幕坐标(gl_FragCoord.xy)来计算。但注意,这样整个物体上的渐变会随着物体在屏幕上的位置变化而变化,而不是物体自身的坐标。 根据需求描述,渐变背景应该是物体的一部分,但不受下沉动画影响。因此,我们使用物体自身的原始坐标(模型坐标)来计算渐变,这样即使物体做了下沉动画,每个点的模型坐标是不变的(因为模型坐标是相对于模型中心的,不会因为动画而改变)。 但是,注意:在顶点着色器中,我们通常只能得到模型坐标(即position),而经过动画修改后,我们传递给片元着色器的坐标是修改后的。所以我们需要在修改之前将原始模型坐标通过一个varying变量传递给片元着色器。 步骤: 顶点着色器: attribute vec3 position; // 原始模型坐标 uniform mat4 modelViewMatrix; uniform mat4 projectionMatrix; uniform float time; // 时间,用于动画 varying vec3 vOriginalPosition; // 将原始模型坐标传递给片元着色器 void main() { vOriginalPosition = position; // 保存原始模型坐标 // 计算下沉动画:这里我们简单地将y坐标减去一个随时间增加的值 float sinkAmount = time; // 假设时间单位是秒,我们可以乘以一个速度系数 vec3 animatedPosition = position; animatedPosition.y -= sinkAmount; // 随时间下沉 // 将修改后的位置转换为裁剪空间 vec4 mvPosition = modelViewMatrix * vec4(animatedPosition, 1.0); gl_Position = projectionMatrix * mvPosition; } 片元着色器: varying vec3 vOriginalPosition; // 来自顶点着色器的原始模型坐标 void main() { // 使用vOriginalPosition来计算渐变 // 例如,我们做一个从顶部到底部的渐变(在模型空间中,假设y轴上下) // 计算模型空间中y方向上的相对位置(归一化到0~1) float gradient = (vOriginalPosition.y - minY) / (maxY - minY); // 但是注意,我们不知道模型的具体大小,所以我们可以使用模型坐标的范围(如果模型是对称的,我们可以用绝对值或者归一化到[0,1]) // 另一种方法是使用模型坐标的y值直接混合两种颜色 // 假设模型坐标的范围大致在[-1,1](或者我们可以通过归一化) // 这里我们简单地将y坐标从-1到1映射到0~1 float t = (vOriginalPosition.y + 1.0) / 2.0; // 假设模型坐标范围在-1到1之间 // 定义两种颜色 vec3 color1 = vec3(1.0, 0.0, 0.0); // 红色 vec3 color2 = vec3(0.0, 0.0, 1.0); // 蓝色 // 混合 vec3 finalColor = mix(color1, color2, t); gl_FragColor = vec4(finalColor, 1.0); } 但是,上面的归一化假设模型坐标在[-1,1]之间,如果模型不是在这个范围,那么渐变效果可能不如预期。我们可以通过传入模型的最小和最大y值来归一化,但这样就需要额外的uniform。 另一种方法:我们可以在创建几何体时确保其顶点坐标在某个范围内,或者使用一个缩放因子。 注意:如果模型有多个物体,我们希望每个物体独立渐变,那么使用模型坐标是合适的。 另外,下沉动画的修改:上面的下沉动画是线性下沉,而且会一直下沉下去。我们可以根据需求调整动画方式,比如使用正弦函数模拟上下波动,或者设置一个最大下沉深度。 改进动画:我们可以使用一个周期函数,或者使用缓动函数,但需求是下沉,所以我们可以设计为从0开始下沉,然后可能到达某个深度后停止?或者循环?这里我们假设用户希望持续下沉,所以线性增加。 在Three.js中创建ShaderMaterial: 我们需要在JavaScript中设置uniforms,包括time,并且每帧更新time。 代码结构: 1. 创建ShaderMaterial,包含vertexShader和fragmentShader字符串。 2. 定义uniforms,包括time(初始值为0)。 3. 在动画循环中更新time。 注意:如果希望多个物体使用同一个材质,并且动画同步,那么time可以由外部传入;如果每个物体独立,那么每个物体可以有自己的时间。 下面是一个简化的示例代码: 首先,顶点着色器和片元着色器的代码字符串: ```javascript const vertexShader = ` uniform float time; varying vec3 vOriginalPosition; void main() { vOriginalPosition = position; // 下沉动画:每秒下沉1个单位(可以根据需要调整) float sinkAmount = time * 0.5; // 速度系数 vec3 animatedPosition = position; animatedPosition.y -= sinkAmount; gl_Position = projectionMatrix * modelViewMatrix * vec4(animatedPosition, 1.0); } `; const fragmentShader = ` varying vec3 vOriginalPosition; void main() { // 归一化y坐标:假设模型高度范围在-1到1之间(例如一个半径为1的球体) // 如果模型不是这个范围,需要调整归一化方式 float t = (vOriginalPosition.y + 1.0) / 2.0; // 将y从[-1,1]映射到[0,1] // 定义两种颜色 vec3 color1 = vec3(1.0, 0.0, 0.0); // 红色 vec3 color2 = vec3(0.0, 0.0, 1.0); // 蓝色 vec3 color = mix(color1, color2, t); gl_FragColor = vec4(color, 1.0); } `; ``` 然后创建材质: ```javascript const material = new THREE.ShaderMaterial({ uniforms: { time: { value: 0 } }, vertexShader: vertexShader, fragmentShader: fragmentShader }); ``` 在动画循环中更新time: ```javascript const clock = new THREE.Clock(); function animate() { requestAnimationFrame(animate); const elapsedTime = clock.getElapsedTime(); material.uniforms.time.value = elapsedTime; renderer.render(scene, camera); } animate(); ``` 注意:上面的归一化假设模型坐标在[-1,1]之间。如果模型不是(比如是一个平面几何体,其顶点坐标在-10到10之间),那么归一化就会出错。因此,我们需要根据模型的实际情况调整归一化。 解决方案1:在片元着色器中,我们可以传入模型的最小y和最大y,然后归一化: 在顶点着色器中,我们仍然传递原始位置。 在片元着色器中: ```glsl uniform float minY; uniform float maxY; varying vec3 vOriginalPosition; void main() { float t = (vOriginalPosition.y - minY) / (maxY - minY); // ... 混合颜色 } ``` 然后在创建材质时,根据模型的包围盒设置minY和maxY。 但是,注意:每个模型的最小最大y可能不同,所以如果材质是共享的,那么就不能这样。因此,我们可以考虑在顶点着色器中将模型坐标归一化到[0,1](需要知道整个模型的范围),或者使用世界坐标?但世界坐标会随着物体位置变化而变化。 根据需求,渐变背景不受下沉动画影响,但可能受物体位置和旋转影响?需求没有明确,但通常我们可能希望渐变是相对于物体自身的,所以使用模型坐标并且归一化到自身大小即可。 如果模型在创建时我们已知其范围,我们可以计算最小最大y,然后作为uniform传入。 另一种方法:在顶点着色器中进行归一化,然后传递给片元着色器。但这样需要知道整个模型的范围,而顶点着色器处理的是单个顶点,不知道整个模型的范围。所以只能通过uniform传入。 因此,我们改进: 在JavaScript中,我们可以计算几何体的包围盒,然后得到minY和maxY。 例如: ```javascript const geometry = new THREE.BoxGeometry(10, 20, 10); // 假设一个长方体 geometry.computeBoundingBox(); const minY = geometry.boundingBox.min.y; const maxY = geometry.boundingBox.max.y; ``` 然后创建材质时: ```javascript const material = new THREE.ShaderMaterial({ uniforms: { time: { value: 0 }, minY: { value: minY }, maxY: { value: maxY } }, vertexShader: vertexShader, fragmentShader: fragmentShader }); ``` 修改片元着色器: ```glsl uniform float minY; uniform float maxY; varying vec3 vOriginalPosition; void main() { float t = (vOriginalPosition.y - minY) / (maxY - minY); vec3 color1 = vec3(1.0, 0.0, 0.0); vec3 color2 = vec3(0.0, 0.0, 1.0); vec3 color = mix(color1, color2, t); gl_FragColor = vec4(color, 1.0); } ``` 这样,无论模型大小,都可以正确归一化。 但是,注意:如果模型有多个部分,并且每个部分使用不同的材质,那么每个材质需要传入自己的minY和maxY。 另外,如果模型发生了缩放,那么模型坐标乘以模型矩阵后,在世界坐标中会变化。但是这里我们使用的是模型坐标(即原始顶点坐标),所以不受缩放影响?因为模型坐标是原始数据,而缩放是在模型矩阵中体现的,在顶点着色器中我们使用modelViewMatrix来变换顶点位置,但原始坐标vOriginalPosition没有乘以任何矩阵,所以它始终是原始坐标。 因此,即使物体被缩放,我们传入的minY和maxY也是原始模型坐标的范围,所以归一化仍然正确。 最后,关于下沉动画:我们目前是线性下沉,可能希望有一个更自然的效果,比如缓动。我们可以使用一个二次函数或者指数函数来调整下沉量。 例如,我们可以使用: ```glsl float sinkAmount = time * time * 0.1; // 加速度下沉 ``` 或者使用正弦函数模拟波浪: ```glsl float sinkAmount = sin(time) * 0.5; // 上下波动,但这样就不是持续下沉了 ``` 持续下沉可以这样: ```glsl float sinkAmount = - (1.0 - exp(-time)); // 指数衰减到最大深度1.0 ``` 根据需求调整。 总结: 我们通过顶点着色器修改y坐标实现下沉,同时将原始模型坐标传递给片元着色器用于计算渐变。渐变使用原始坐标,因此不受下沉动画影响。 注意:如果模型有多个物体,并且我们希望每个物体独立计算渐变(即每个物体内部从自己的底部到顶部渐变),那么上述方法是正确的。如果希望整个场景有一个统一的渐变背景(比如天空盒),那么应该使用屏幕空间渐变,这需要不同的方法(例如使用后期处理或者背景图)。 但是,根据需求描述,我们是在物体的材质上实现渐变背景,所以上述方法符合要求。 下面提供完整代码示例(假设有一个立方体):</think>在 Three.js 中使用 `ShaderMaterial` 实现下沉动画和独立渐变背景的完整解决方案如下: ### 核心思路 1. **下沉动画**:在顶点着色器中修改顶点 y 坐标 2. **渐变背景**:在片元着色器中使用屏幕坐标计算 3. **动画分离**:使用不同坐标系确保动画不影响背景 ### 完整代码实现 ```javascript import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; // 场景初始化 const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // 自定义着色器材质 const material = new THREE.ShaderMaterial({ uniforms: { time: { value: 0 }, resolution: { value: new THREE.Vector2() }, // 渐变颜色配置 topColor: { value: new THREE.Color(0x3498db) }, // 蓝色 bottomColor: { value: new THREE.Color(0xe74c3c) } // 红色 }, vertexShader: ` uniform float time; varying vec2 vUv; varying vec3 vPosition; void main() { vUv = uv; vPosition = position; // 下沉动画:y坐标随时间减少 float sinkHeight = position.y - sin(time) * 0.5; // 保持原始模型变换 vec4 modelPosition = modelMatrix * vec4(position.x, sinkHeight, position.z, 1.0); gl_Position = projectionMatrix * viewMatrix * modelPosition; } `, fragmentShader: ` uniform vec2 resolution; uniform vec3 topColor; uniform vec3 bottomColor; varying vec2 vUv; varying vec3 vPosition; void main() { // 基于屏幕坐标的垂直渐变 float gradientFactor = gl_FragCoord.y / resolution.y; vec3 bgColor = mix(bottomColor, topColor, gradientFactor); // 物体颜色(此处使用简单颜色,可替换为纹理) vec3 objectColor = vec3(0.9, 0.9, 0.9); // 最终混合输出 gl_FragColor = vec4(bgColor, 1.0); } ` }); // 创建立方体测试对象 const geometry = new THREE.BoxGeometry(2, 2, 2); const cube = new THREE.Mesh(geometry, material); scene.add(cube); // 添加光源 const light = new THREE.DirectionalLight(0xffffff, 1); light.position.set(5, 5, 5); scene.add(light); // 相机位置 camera.position.z = 5; // 控制器 const controls = new OrbitControls(camera, renderer.domElement); // 动画循环 function animate() { requestAnimationFrame(animate); // 更新时间 material.uniforms.time.value = performance.now() / 1000; material.uniforms.resolution.value.set( window.innerWidth, window.innerHeight ); // 旋转立方体(可选) cube.rotation.x += 0.01; cube.rotation.y += 0.01; renderer.render(scene, camera); } // 响应窗口大小变化 window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); animate(); ``` ### 关键实现说明 1. **动画分离原理**: - 下沉动画使用模型坐标(`position.y`)计算 - 渐变背景使用屏幕坐标(`gl_FragCoord`)计算 - 两者使用不同的坐标系,互不影响[^1] 2. **顶点着色器**: ```glsl float sinkHeight = position.y - sin(time) * 0.5; // 正弦波下沉动画 ``` 使用 `sin(time)` 创建平滑的上下浮动效果 3. **片元着色器**: ```glsl float gradientFactor = gl_FragCoord.y / resolution.y; vec3 bgColor = mix(bottomColor, topColor, gradientFactor); ``` `gl_FragCoord` 获取屏幕空间坐标,实现基于屏幕位置的渐变 4. **Uniform 参数**: - `time`:控制动画进度 - `resolution`:传入屏幕尺寸 - `topColor/bottomColor`:可自定义渐变颜色 ### 优化建议 1. **添加纹理**:在片元着色器中混合纹理和渐变 ```glsl vec4 textureColor = texture2D(map, vUv); gl_FragColor = mix(vec4(bgColor, 1.0), textureColor, textureColor.a); ``` 2. **高级动画**:使用噪声函数创建更复杂的下沉模式 ```glsl float noise = cnoise(vec3(position.xz, time)); // 柏林噪声 float sinkHeight = position.y - noise * 0.8; ``` 3. **性能优化**:对静态物体禁用矩阵自动更新 ```javascript cube.matrixAutoUpdate = false; cube.updateMatrix(); ``` [^1]: 基于Three.js坐标系原理,模型坐标与屏幕坐标独立计算。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HM-hhxx!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值