学习threejs,使用EffectComposer后期处理组合器(采用RenderPass、UnrealBloomPass、FilmPass渲染通道),实现交互式 3D blob

👨‍⚕️ 主页: gis分享者
👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅!
👨‍⚕️ 收录于专栏:threejs gis工程师


文章目录

  • 一、🍀前言
    • 1.1 ☘️THREE.EffectComposer 后期处理
      • 1.1.1 ☘️代码示例
      • 1.1.2 ☘️构造函数
      • 1.1.3 ☘️属性
      • 1.1.4 ☘️方法
    • 1.2 ☘️THREE.RenderPass
      • 1.2.1 ☘️构造函数
      • 1.2.2 ☘️属性
      • 1.2.3 ☘️方法
    • 1.3 ☘️THREE.UnrealBloomPass
      • 1.3.1 ☘️构造函数
      • 1.3.2 ☘️方法
    • 1.4 ☘️THREE.FilmPass
      • 1.4.1 ☘️构造函数
      • 1.4.2 ☘️属性
      • 1.4.3 ☘️方法
  • 二、🍀使用EffectComposer后期处理组合器(采用RenderPass、UnrealBloomPass、FilmPass渲染通道),实现交互式 3D blob
    • 1. ☘️实现思路
    • 2. ☘️代码样例


一、🍀前言

本文详细介绍如何基于threejs在三维场景中使用EffectComposer后期处理组合器(采用RenderPass、UnrealBloomPass、FilmPass渲染通道),实现交互式 3D blob,亲测可用。希望能帮助到您。一起学习,加油!加油!

1.1 ☘️THREE.EffectComposer 后期处理

THREE.EffectComposer 用于在three.js中实现后期处理效果。该类管理了产生最终视觉效果的后期处理过程链。 后期处理过程根据它们添加/插入的顺序来执行,最后一个过程会被自动渲染到屏幕上。

1.1.1 ☘️代码示例

import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
// 初始化 composer
const composer = new EffectComposer(renderer);
// 创建 RenderPass 并添加到 composer
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
// 添加其他后期处理通道(如模糊)
// composer.addPass(blurPass);
// 在动画循环中渲染
function animate() {composer.render();requestAnimationFrame(animate);
}

1.1.2 ☘️构造函数

EffectComposer( renderer : WebGLRenderer, renderTarget : WebGLRenderTarget )
renderer – 用于渲染场景的渲染器。
renderTarget – (可选)一个预先配置的渲染目标,内部由 EffectComposer 使用。

1.1.3 ☘️属性

.passes : Array
一个用于表示后期处理过程链(包含顺序)的数组。

渲染通道:
BloomPass   该通道会使得明亮区域参入较暗的区域。模拟相机照到过多亮光的情形
DotScreenPass   将一层黑点贴到代表原始图片的屏幕上
FilmPass    通过扫描线和失真模拟电视屏幕
MaskPass    在当前图片上贴一层掩膜,后续通道只会影响被贴的区域
RenderPass  该通道在指定的场景和相机的基础上渲染出一个新的场景
SavePass    执行该通道时,它会将当前渲染步骤的结果复制一份,方便后面使用。这个通道实际应用中作用不大;
ShaderPass  使用该通道你可以传入一个自定义的着色器,用来生成高级的、自定义的后期处理通道
TexturePass 该通道可以将效果组合器的当前状态保存为一个纹理,然后可以在其他EffectCoposer对象中将该纹理作为输入参数

.readBuffer : WebGLRenderTarget
内部读缓冲区的引用。过程一般从该缓冲区读取先前的渲染结果。

.renderer : WebGLRenderer
内部渲染器的引用。

.renderToScreen : Boolean
最终过程是否被渲染到屏幕(默认帧缓冲区)。

.writeBuffer : WebGLRenderTarget
内部写缓冲区的引用。过程常将它们的渲染结果写入该缓冲区。

1.1.4 ☘️方法

.addPass ( pass : Pass ) : undefined
pass – 将被添加到过程链的过程

将传入的过程添加到过程链。

.dispose () : undefined
释放此实例分配的 GPU 相关资源。每当您的应用程序不再使用此实例时调用此方法。

.insertPass ( pass : Pass, index : Integer ) : undefined
pass – 将被插入到过程链的过程。

index – 定义过程链中过程应插入的位置。

将传入的过程插入到过程链中所给定的索引处。

.isLastEnabledPass ( passIndex : Integer ) : Boolean
passIndex – 被用于检查的过程

如果给定索引的过程在过程链中是最后一个启用的过程,则返回true。 由EffectComposer所使用,来决定哪一个过程应当被渲染到屏幕上。

.removePass ( pass : Pass ) : undefined
pass – 要从传递链中删除的传递。

从传递链中删除给定的传递。

.render ( deltaTime : Float ) : undefined
deltaTime – 增量时间值。

执行所有启用的后期处理过程,来产生最终的帧,

.reset ( renderTarget : WebGLRenderTarget ) : undefined
renderTarget – (可选)一个预先配置的渲染目标,内部由 EffectComposer 使用。

重置所有EffectComposer的内部状态。

.setPixelRatio ( pixelRatio : Float ) : undefined
pixelRatio – 设备像素比

设置设备的像素比。该值通常被用于HiDPI设备,以阻止模糊的输出。 因此,该方法语义类似于WebGLRenderer.setPixelRatio()。

.setSize ( width : Integer, height : Integer ) : undefined
width – EffectComposer的宽度。
height – EffectComposer的高度。

考虑设备像素比,重新设置内部渲染缓冲和过程的大小为(width, height)。 因此,该方法语义类似于WebGLRenderer.setSize()。

.swapBuffers () : undefined
交换内部的读/写缓冲。

1.2 ☘️THREE.RenderPass

THREE.RenderPass用于将场景渲染到中间缓冲区,为后续的后期处理效果(如模糊、色调调整等)提供基础。

1.2.1 ☘️构造函数

RenderPass(scene, camera, overrideMaterial, clearColor, clearAlpha)

  • scene THREE.Scene 要渲染的 Three.js 场景对象。
  • camera THREE.Camera 场景对应的相机(如 PerspectiveCamera)。
  • overrideMaterial THREE.Material (可选) 覆盖场景中所有物体的材质(默认 null)。
  • clearColor THREE.Color (可选) 渲染前清除画布的颜色(默认不主动清除)。
  • clearAlpha number (可选) 清除画布的透明度(默认 0)。

1.2.2 ☘️属性

.enabled:boolean
是否启用此通道(默认 true)。设为 false 可跳过渲染。

.clear:boolean
渲染前是否清除画布(默认 true)。若需叠加多个 RenderPass,可设为 false。

.needsSwap:boolean
是否需要在渲染后交换缓冲区(通常保持默认 false)。

1.2.3 ☘️方法

.setSize(width, height)
调整通道的渲染尺寸(通常由 EffectComposer 自动调用)。
width: 画布宽度(像素)。
height: 画布高度(像素)。

1.3 ☘️THREE.UnrealBloomPass

UnrealBloomPass 是 Three.js 中实现高质量泛光效果的后期处理通道,通过模拟类似 Unreal Engine 的泛光效果,为场景中的明亮区域添加柔和的光晕,提升视觉表现力。

1.3.1 ☘️构造函数

new UnrealBloomPass(resolution, strength, radius, threshold)

  • resolution (Vector2): 泛光效果应用的场景分辨率,需与画布尺寸一致。
    示例:new THREE.Vector2(window.innerWidth, window.innerHeight)
  • strength (Number): 泛光强度,默认值 1.0。值越大,光晕越明显。
  • radius (Number): 模糊半径,默认值 0.4。值越大,光晕扩散范围越广。
  • threshold (Number): 泛光阈值,默认值 0.85。仅对亮度高于此值的区域生效。

1.3.2 ☘️方法

  • renderToScreen: 是否直接渲染到屏幕,默认为 false(需通过 EffectComposer 管理)。
  • clearColor: 设置背景清除颜色,默认为透明。

1.4 ☘️THREE.FilmPass

THREE.FilmPass是 Three.js 后期处理模块中的一个特效通道,用于模拟电影胶片效果(如扫描线、颗粒噪声和画面抖动)。适用于复古风格或科幻场景的视觉增强。

1.4.1 ☘️构造函数

FilmPass(
noiseIntensity, // 噪声强度
scanlinesIntensity,// 扫描线强度
scanlinesCount, // 扫描线数量
grayscale // 是否转为灰度
)

1.4.2 ☘️属性

.enabled:boolean
是否启用此通道(默认 true)。设为 false 可临时禁用效果。

.uniforms:object
着色器 uniforms 对象,可直接修改参数(动态调整效果):

filmPass.uniforms.nIntensity.value = 0.8; // 调整噪声强度
filmPass.uniforms.sIntensity.value = 0.5; // 调整扫描线强度
filmPass.uniforms.sCount.value = 1024;    // 调整扫描线密度
filmPass.uniforms.grayscale.value = 1;    // 启用灰度(1 是,0 否)

1.4.3 ☘️方法

.setSize(width, height)
调整通道的渲染尺寸(通常由 EffectComposer 自动调用)。
width: 画布宽度(像素)。
height: 画布高度(像素)。

二、🍀使用EffectComposer后期处理组合器(采用RenderPass、UnrealBloomPass、FilmPass渲染通道),实现交互式 3D blob

1. ☘️实现思路

本例子使用EffectComposer后期处理组合器(采用RenderPass、UnrealBloomPass、FilmPass渲染通道)、THREE.Points点对象等,实现交互式 3D blob。具体代码参考下面代码样例。

2. ☘️代码样例

<!DOCTYPE html>
<html lang="en">
<head><script src="https://cdn.tailwindcss.com"></script><link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>交互式 3D blob</title><style>body {margin: 0;overflow: hidden;font-family: 'Inter', sans-serif;}canvas {display: block;width: 100vw;height: 100vh;}::-webkit-scrollbar {width: 8px;}::-webkit-scrollbar-track {background: rgba(255, 255, 255, 0.1);border-radius: 10px;}::-webkit-scrollbar-thumb {background: rgba(255, 255, 255, 0.3);border-radius: 10px;}::-webkit-scrollbar-thumb:hover {background: rgba(255, 255, 255, 0.5);}select {background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23DDDDDD%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E");background-repeat: no-repeat;background-position: right 0.75rem top 50%;background-size: 0.65em auto;-webkit-appearance: none;-moz-appearance: none;appearance: none;}</style>
</head>
<body>
<div id="ui-container" class="fixed top-5 right-5 z-50 bg-gray-900/70 backdrop-blur-md p-6 rounded-xl shadow-2xl border border-gray-700/50 transition-all duration-300 ease-in-out w-72"><h3 class="text-lg font-semibold text-gray-100 mb-5 tracking-wide uppercase">Dithering Controls</h3><div class="mb-5"><label for="dither-pattern" class="block text-sm font-medium text-gray-300 mb-2">Dither Pattern</label><select id="dither-pattern" class="w-full bg-gray-800/80 border border-gray-700 text-gray-200 text-sm rounded-lg focus:ring-indigo-500 focus:border-indigo-500 block p-2.5 placeholder-gray-400 shadow-sm appearance-none"><option value="0">Bayer Matrix (8x8)</option><option value="1">Halftone Dots</option><option value="2">Line Pattern</option><option value="3">Noise Dithering</option><option value="4">No Dithering</option></select></div><div class="mb-3"><label for="dither-scale" class="block text-sm font-medium text-gray-300 mb-2">Dither Scale</label><select id="dither-scale" class="w-full bg-gray-800/80 border border-gray-700 text-gray-200 text-sm rounded-lg focus:ring-indigo-500 focus:border-indigo-500 block p-2.5 placeholder-gray-400 shadow-sm appearance-none"><option value="1.0">Fine</option><option value="1.5" selected>Medium</option><option value="2.5">Coarse</option><option value="3.5">Very Coarse</option></select></div>
</div>
</body>
<script type="module">import * as THREE from "https://esm.sh/three";import { OrbitControls } from "https://esm.sh/three/examples/jsm/controls/OrbitControls.js";import { EffectComposer } from "https://esm.sh/three/examples/jsm/postprocessing/EffectComposer.js";import { RenderPass } from "https://esm.sh/three/examples/jsm/postprocessing/RenderPass.js";import { UnrealBloomPass } from "https://esm.sh/three/examples/jsm/postprocessing/UnrealBloomPass.js";import { FilmPass } from "https://esm.sh/three/examples/jsm/postprocessing/FilmPass.js";const DITHER_MOTION_SPEED = 2.0;const DITHER_MOTION_AMPLITUDE = 1.5;const BLOB_BASE_RADIUS = 2.0;const BLOB_NOISE_FREQUENCY_VERTEX = 0.75;const BLOB_NOISE_AMPLITUDE_VERTEX = 0.65;const BLOB_NOISE_SPEED_VERTEX = 0.08;const PARTICLE_COUNT = 1200;const STAR_COUNT = 3000;const scene = new THREE.Scene();scene.background = new THREE.Color(0x000000);scene.fog = new THREE.FogExp2(0x000000, 0.025);const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);camera.position.set(0, 0, 5.0);const renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: "high-performance" });renderer.setSize(window.innerWidth, window.innerHeight);renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));renderer.outputColorSpace = THREE.SRGBColorSpace;document.body.appendChild(renderer.domElement);const composer = new EffectComposer(renderer);const renderPass = new RenderPass(scene, camera);composer.addPass(renderPass);const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 0.45, 0.55, 0.75);composer.addPass(bloomPass);const filmPass = new FilmPass(0.20, 0.15, 648, false);composer.addPass(filmPass);const controls = new OrbitControls(camera, renderer.domElement);controls.enableDamping = true;controls.dampingFactor = 0.04;controls.rotateSpeed = 0.20;controls.minDistance = 2.0;controls.maxDistance = 12;controls.enablePan = false;controls.autoRotate = false;const ambientLight = new THREE.AmbientLight(0x606070, 0.6);scene.add(ambientLight);const pointLight1 = new THREE.PointLight(0xffddaa, 0.9, 60);pointLight1.position.set(5, 5, 5);scene.add(pointLight1);const pointLight2 = new THREE.PointLight(0xaaccff, 0.6, 60);pointLight2.position.set(-5, -3, -4);scene.add(pointLight2);const pointLight3 = new THREE.PointLight(0xff8844, 0.75, 60);pointLight3.position.set(0, -5, 3);scene.add(pointLight3);const starGeometry = new THREE.BufferGeometry();const starPositions = [];const starColors = [];const starSizes = [];for (let i = 0; i < STAR_COUNT; i++) {const x = THREE.MathUtils.randFloatSpread(200);const y = THREE.MathUtils.randFloatSpread(200);const z = THREE.MathUtils.randFloatSpread(200);starPositions.push(x, y, z);const color = new THREE.Color();color.setHSL(THREE.MathUtils.randFloat(0.5, 0.7), 0.2, THREE.MathUtils.randFloat(0.3, 0.6));starColors.push(color.r, color.g, color.b);starSizes.push(THREE.MathUtils.randFloat(0.5, 1.5));}starGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starPositions, 3));starGeometry.setAttribute('color', new THREE.Float32BufferAttribute(starColors, 3));starGeometry.setAttribute('size', new THREE.Float32BufferAttribute(starSizes, 1));const starMaterial = new THREE.ShaderMaterial({uniforms: {uTime: { value: 0.0 },},vertexShader: `uniform float uTime;attribute float size;varying vec3 vColor;varying float vAlpha;void main() {vColor = color;vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);gl_PointSize = size * (100.0 / -mvPosition.z) * (sin(position.x * 0.1 + uTime * 0.3) * 0.2 + 0.8);vAlpha = clamp(1.0 - (-mvPosition.z / 150.0), 0.1, 0.8);gl_Position = projectionMatrix * mvPosition;}`,fragmentShader: `uniform float uTime;varying vec3 vColor;varying float vAlpha;void main() {float dist = length(gl_PointCoord - vec2(0.5));if (dist > 0.5) discard;gl_FragColor = vec4(vColor, vAlpha * (0.6 + 0.4 * sin(uTime * 2.0 + gl_FragCoord.x * 0.5)));}`,transparent: true,blending: THREE.AdditiveBlending,depthWrite: false,vertexColors: true});const stars = new THREE.Points(starGeometry, starMaterial);scene.add(stars);const ditherPatternsFunction = `const float bayerMatrix[64] = float[64](0.0/64.0, 32.0/64.0,  8.0/64.0, 40.0/64.0,  2.0/64.0, 34.0/64.0, 10.0/64.0, 42.0/64.0,48.0/64.0, 16.0/64.0, 56.0/64.0, 24.0/64.0, 50.0/64.0, 18.0/64.0, 58.0/64.0, 26.0/64.0,12.0/64.0, 44.0/64.0,  4.0/64.0, 36.0/64.0, 14.0/64.0, 46.0/64.0,  6.0/64.0, 38.0/64.0,60.0/64.0, 28.0/64.0, 52.0/64.0, 20.0/64.0, 62.0/64.0, 30.0/64.0, 54.0/64.0, 22.0/64.0,3.0/64.0, 35.0/64.0, 11.0/64.0, 43.0/64.0,  1.0/64.0, 33.0/64.0,  9.0/64.0, 41.0/64.0,51.0/64.0, 19.0/64.0, 59.0/64.0, 27.0/64.0, 49.0/64.0, 17.0/64.0, 57.0/64.0, 25.0/64.0,15.0/64.0, 47.0/64.0,  7.0/64.0, 39.0/64.0, 13.0/64.0, 45.0/64.0,  5.0/64.0, 37.0/64.0,63.0/64.0, 31.0/64.0, 55.0/64.0, 23.0/64.0, 61.0/64.0, 29.0/64.0, 53.0/64.0, 21.0/64.0);float getBayerValue(vec2 coord) {int x = int(mod(coord.x, 8.0));int y = int(mod(coord.y, 8.0));return bayerMatrix[y * 8 + x];}float getHalftoneValue(vec2 coord, float time) {vec2 c = vec2(0.5);coord = mod(coord * 0.1 + vec2(sin(time*0.1)*0.02, cos(time*0.1)*0.02), 1.0);float d = distance(coord, c);return smoothstep(0.28, 0.29, d);}float getLinePatternValue(vec2 coord, float time) {float lw = 0.35 + sin(time*0.15)*0.1;float p1 = mod(coord.x*0.15+sin(coord.y*0.04+time*0.08)*0.6,1.0);float p2 = mod(coord.y*0.15+cos(coord.x*0.04+time*0.12)*0.6,1.0);return max(smoothstep(0.0,lw,p1)*smoothstep(1.0,1.0-lw,p1), smoothstep(0.0,lw,p2)*smoothstep(1.0,1.0-lw,p2));}float getNoiseDitheringValue(vec2 coord, float time) {return fract(sin(dot(coord + time * 0.05, vec2(12.9898, 78.233))) * 43758.5453);}vec3 ditherMonochrome(vec3 color, vec2 baseScreenPos, float colorLevels, float time,float motionSpeed, float motionAmplitude, int patternType) {float luminance = dot(color, vec3(0.299, 0.587, 0.114));luminance = pow(luminance, 1.2);luminance = (luminance - 0.5) * 6.0 + 0.5;luminance = clamp(luminance, 0.0, 1.0);vec2 ditherScreenPos = baseScreenPos;ditherScreenPos.x += sin(time * motionSpeed * 0.75 + baseScreenPos.y * 0.08) * motionAmplitude;ditherScreenPos.y += cos(time * motionSpeed * 0.55 + baseScreenPos.x * 0.08) * motionAmplitude;float threshold = 0.5;if (patternType == 0) {threshold = getBayerValue(ditherScreenPos);} else if (patternType == 1) {threshold = getHalftoneValue(ditherScreenPos, time);} else if (patternType == 2) {threshold = getLinePatternValue(ditherScreenPos, time);} else if (patternType == 3) {threshold = getNoiseDitheringValue(ditherScreenPos, time);} else if (patternType == 4) {threshold = 0.5;}float ditheredValue = (luminance < threshold) ? 0.0 : 1.0;return vec3(ditheredValue);}
`;const glslRandFunction = `float rand(vec3 co){ return fract(sin(dot(co, vec3(12.9898,78.233,53.543))) * 43758.5453); }float snoise(vec3 p) {vec3 ip = floor(p); vec3 fp = fract(p); fp = fp*fp*(3.0-2.0*fp);float v000=rand(ip+vec3(0,0,0)); float v100=rand(ip+vec3(1,0,0)); float v010=rand(ip+vec3(0,1,0)); float v110=rand(ip+vec3(1,1,0));float v001=rand(ip+vec3(0,0,1)); float v101=rand(ip+vec3(1,0,1)); float v011=rand(ip+vec3(0,1,1)); float v111=rand(ip+vec3(1,1,1));return mix(mix(mix(v000,v100,fp.x),mix(v010,v110,fp.x),fp.y), mix(mix(v001,v101,fp.x),mix(v011,v111,fp.x),fp.y),fp.z);}
`;const blobMaterial = new THREE.ShaderMaterial({uniforms: {uTime: { value: 0 },ditherScale: { value: 1.5 },colorLevels: { value: 2.0 },uDitherMotionSpeed: { value: DITHER_MOTION_SPEED },uDitherMotionAmplitude: { value: DITHER_MOTION_AMPLITUDE },uBaseColor: { value: new THREE.Color(0xffffff) },uFresnelPower: { value: 2.5 },uVertexNoiseFrequency: { value: BLOB_NOISE_FREQUENCY_VERTEX },uVertexNoiseAmplitude: { value: BLOB_NOISE_AMPLITUDE_VERTEX },uVertexNoiseSpeed: { value: BLOB_NOISE_SPEED_VERTEX },uSurfaceNoiseFrequency: { value: 2.8 },uSurfaceNoiseAmplitude: { value: 0.22 },uDitherPattern: { value: 0 },uCoreBrightness: { value: 0.2 }},vertexShader: `uniform float uTime;uniform float uVertexNoiseFrequency;uniform float uVertexNoiseAmplitude;uniform float uVertexNoiseSpeed;varying vec3 vNormal;varying vec3 vViewPosition;varying vec3 vWorldPosition;${glslRandFunction}void main() {vec3 pos = position;float displacement = snoise(pos * uVertexNoiseFrequency + uTime * uVertexNoiseSpeed) * uVertexNoiseAmplitude;displacement += snoise(pos * uVertexNoiseFrequency * 2.2 + uTime * uVertexNoiseSpeed * 1.4) * (uVertexNoiseAmplitude * 0.45);pos += normal * displacement;vec3 offset = vec3(0.01, 0.01, 0.01);float ddx_noise_orig = snoise((position + offset.xyy) * uVertexNoiseFrequency + uTime * uVertexNoiseSpeed) * uVertexNoiseAmplitude;ddx_noise_orig += snoise((position + offset.xyy) * uVertexNoiseFrequency * 2.2 + uTime * uVertexNoiseSpeed * 1.4) * (uVertexNoiseAmplitude * 0.45);vec3 p_ddx = (position + offset.xyy) + normal * ddx_noise_orig;float ddy_noise_orig = snoise((position + offset.yxy) * uVertexNoiseFrequency + uTime * uVertexNoiseSpeed) * uVertexNoiseAmplitude;ddy_noise_orig += snoise((position + offset.yxy) * uVertexNoiseFrequency * 2.2 + uTime * uVertexNoiseSpeed * 1.4) * (uVertexNoiseAmplitude * 0.45);vec3 p_ddy = (position + offset.yxy) + normal * ddy_noise_orig;vec3 tangent = normalize(p_ddx - pos);vec3 bitangent = normalize(p_ddy - pos);vec3 displacedNormal = normalize(cross(tangent, bitangent));if (length(displacedNormal) < 0.1) { displacedNormal = normal; }vNormal = normalize(normalMatrix * displacedNormal);vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);vViewPosition = -mvPosition.xyz;vWorldPosition = (modelMatrix * vec4(pos, 1.0)).xyz;gl_Position = projectionMatrix * mvPosition;}`,fragmentShader: `uniform float uTime;uniform float ditherScale;uniform float colorLevels;uniform float uDitherMotionSpeed;uniform float uDitherMotionAmplitude;uniform vec3 uBaseColor;uniform float uFresnelPower;uniform float uSurfaceNoiseFrequency;uniform float uSurfaceNoiseAmplitude;uniform int uDitherPattern;uniform float uCoreBrightness;varying vec3 vNormal;varying vec3 vViewPosition;varying vec3 vWorldPosition;${ditherPatternsFunction}${glslRandFunction}void main() {vec3 normal = normalize(vNormal);vec3 viewDir = normalize(vViewPosition);float fresnel = pow(1.0 - abs(dot(viewDir, normal)), uFresnelPower);fresnel = smoothstep(0.0, 1.0, fresnel) * 0.6 + 0.4;float rim = pow(1.0 - abs(dot(viewDir, normal)), 10.0);fresnel += rim * 0.25;float surfaceNoise1 = snoise(vWorldPosition * uSurfaceNoiseFrequency + vec3(uTime * 0.1, uTime * 0.06, uTime * 0.08));float surfaceNoise2 = snoise(vWorldPosition * uSurfaceNoiseFrequency * 2.7 + vec3(uTime * 0.15, uTime * 0.1, uTime * -0.04)) * 0.45;float surfaceNoise = (surfaceNoise1 + surfaceNoise2) * 0.5 + 0.5;surfaceNoise = surfaceNoise * uSurfaceNoiseAmplitude + (1.0 - uSurfaceNoiseAmplitude * 0.6);float coreGlow = pow(max(0.0, dot(viewDir, normal)), 2.0) * uCoreBrightness;float intensity = (fresnel + coreGlow) * surfaceNoise;intensity = clamp(intensity, 0.02, 1.0);vec3 finalColor = uBaseColor * intensity;vec2 screenPos = gl_FragCoord.xy / ditherScale;vec3 ditheredOutput = ditherMonochrome(finalColor, screenPos, colorLevels, uTime, uDitherMotionSpeed, uDitherMotionAmplitude, uDitherPattern);gl_FragColor = vec4(ditheredOutput, 1.0);}`,});const blobGeometry = new THREE.SphereGeometry(BLOB_BASE_RADIUS, 128, 128);const morphingBlob = new THREE.Mesh(blobGeometry, blobMaterial);scene.add(morphingBlob);const particleGeometry = new THREE.BufferGeometry();const particlePositions = new Float32Array(PARTICLE_COUNT * 3);const particleSizes = new Float32Array(PARTICLE_COUNT);const particleSpeeds = new Float32Array(PARTICLE_COUNT);for (let i = 0; i < PARTICLE_COUNT; i++) {const radius = BLOB_BASE_RADIUS * 2.5 + Math.random() * BLOB_BASE_RADIUS * 4;const theta = Math.random() * Math.PI * 2;const phi = Math.acos(2 * Math.random() - 1);particlePositions[i * 3] = radius * Math.sin(phi) * Math.cos(theta);particlePositions[i * 3 + 1] = radius * Math.sin(phi) * Math.sin(theta);particlePositions[i * 3 + 2] = radius * Math.cos(phi);particleSizes[i] = Math.random() * 0.06 + 0.015;particleSpeeds[i] = Math.random() * 0.2 + 0.1;}particleGeometry.setAttribute('position', new THREE.BufferAttribute(particlePositions, 3));particleGeometry.setAttribute('size', new THREE.BufferAttribute(particleSizes, 1));particleGeometry.setAttribute('speed', new THREE.BufferAttribute(particleSpeeds, 1));const particleMaterial = new THREE.ShaderMaterial({uniforms: {uTime: { value: 0 },uColor: { value: new THREE.Color(0xddddff) },uBlobBaseRadius: { value: BLOB_BASE_RADIUS }},vertexShader: `uniform float uTime;uniform float uBlobBaseRadius;attribute float size;attribute float speed;varying float vDistance;varying float vParticleAlpha;void main() {vec3 pos = position;float waveX = sin(uTime * (speed * 0.8) + position.y * 0.15) * 0.12;float waveY = cos(uTime * (speed * 1.0) + position.z * 0.20) * 0.12;float waveZ = sin(uTime * (speed * 0.9) + position.x * 0.18) * 0.12;pos += vec3(waveX, waveY, waveZ);vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);vDistance = length(mvPosition.xyz);gl_PointSize = size * (400.0 / -mvPosition.z);vParticleAlpha = smoothstep(uBlobBaseRadius * 6.0, uBlobBaseRadius * 2.0, vDistance);gl_Position = projectionMatrix * mvPosition;}`,fragmentShader: `uniform float uTime;uniform vec3 uColor;varying float vDistance;varying float vParticleAlpha;void main() {float dist = length(gl_PointCoord - vec2(0.5));if (dist > 0.5) discard;float pulse = 0.4 + 0.6 * abs(sin(uTime * (1.0 + mod(vDistance, 1.0) * 0.5) + vDistance * 0.2));float finalAlpha = (1.0 - dist * 2.0) * pulse * vParticleAlpha;finalAlpha = clamp(finalAlpha, 0.0, 0.5);gl_FragColor = vec4(uColor, finalAlpha * 0.4);}`,transparent: true,blending: THREE.AdditiveBlending,depthWrite: false});const particleSystem = new THREE.Points(particleGeometry, particleMaterial);scene.add(particleSystem);const ditherPatternSelect = document.getElementById('dither-pattern');const ditherScaleSelect = document.getElementById('dither-scale');const uiContainer = document.getElementById('ui-container');ditherPatternSelect.addEventListener('change', (e) => {blobMaterial.uniforms.uDitherPattern.value = parseInt(e.target.value);});ditherScaleSelect.addEventListener('change', (e) => {blobMaterial.uniforms.ditherScale.value = parseFloat(e.target.value);});let uiTimeout;const uiAutoHideDelay = 3000;const resetUITimeout = () => {clearTimeout(uiTimeout);uiContainer.classList.remove('opacity-0', 'translate-x-12');uiContainer.classList.add('opacity-100', 'translate-x-0');uiTimeout = setTimeout(() => {uiContainer.classList.remove('opacity-100', 'translate-x-0');uiContainer.classList.add('opacity-0', 'translate-x-12');}, uiAutoHideDelay);};document.addEventListener('mousemove', resetUITimeout);document.addEventListener('click', resetUITimeout);document.addEventListener('touchstart', resetUITimeout);resetUITimeout();window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);composer.setSize(window.innerWidth, window.innerHeight);renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));bloomPass.resolution.set(window.innerWidth, window.innerHeight);});camera.lookAt(scene.position);const clock = new THREE.Clock();function animate() {requestAnimationFrame(animate);const elapsedTime = clock.getElapsedTime();blobMaterial.uniforms.uTime.value = elapsedTime;particleMaterial.uniforms.uTime.value = elapsedTime;starMaterial.uniforms.uTime.value = elapsedTime;morphingBlob.rotation.x += 0.0004;morphingBlob.rotation.y += 0.0007;pointLight1.position.x = Math.sin(elapsedTime * 0.32) * 6;pointLight1.position.z = Math.cos(elapsedTime * 0.32) * 6;pointLight2.position.y = Math.sin(elapsedTime * 0.18) * 4;pointLight2.position.x = Math.cos(elapsedTime * 0.25) * -5;pointLight3.position.z = Math.cos(elapsedTime * 0.40) * 5;pointLight3.position.y = Math.sin(elapsedTime * 0.35) * -4;controls.update();composer.render();}window.onload = () => {animate();};
</script>
</html>

效果如下
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/93680.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/93680.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LLM - windows下的Dify离线部署:从镜像打包到无网环境部署(亲测,包含插件部署)

一、离线部署原理 通过Docker的save/load机制实现镜像的物理介质迁移,配合Docker Compose编排文件的环境适配能力,可在完全断网的环境中快速部署复杂应用。整个过程分为在线环境准备和离线环境还原两个阶段。 二、在线环境操作 1. 环境准备 在线环境:一台可以访问互联网的…

前端学习之后端小白java的一些理论知识(框架)

一、Spring Framework 和 Spring boot的区别 核心定位 Spring Framework&#xff1a;一个全面的Java应用开发框架&#xff0c;提供核心功能如IoC容器、AOP等Spring Boot&#xff1a;Spring Framework的扩展&#xff0c;专注于简化Spring应用的初始搭建和开发过程 配置方式 Spri…

K8S的ingress

一。ingress的介绍对于NodePort和LoadBalance&#xff0c;这两种方法&#xff0c;都有缺点&#xff1a;1.NodePort方式缺点会占用很多集群的端口&#xff0c;当集群服务变多的时候&#xff0c;缺点更加显著2.LB的缺点就是每一个service都需要一个LB&#xff0c;浪费&#xff0c…

实现自己的AI视频监控系统-序章

目录简介视频监控系统是什么&#xff1f;该系列课程你会学到什么&#xff1f;需要准备哪些工具&#xff1f;下期预告简介 在当今快速发展的科技时代&#xff0c;人工智能&#xff08;AI&#xff09;已经深入到我们生活的方方面面。其中&#xff0c;AI视频监控系统作为安防领域…

Pytorch GPU版本安装保姆级教程

本文将介绍在anaconda环境下安装pytorch的详细步骤。 Anaconda安装教程参考Anaconda安装保姆级教程。 目录 一、工具安装 二、创建虚拟环境 三、安装Pytorch CUDA Toolkit安装 Pytorch安装 总结 一、工具安装 点击链接官网codetou.com&#xff0c;下载安装最新版即可&…

重学React(六):脱围机制二

背景&#xff1a; 话不多说&#xff0c;继续学习&#xff0c;现在是Effect时间。 前期回顾&#xff1a; 重学React&#xff08;一&#xff09;&#xff1a;描述UI 重学React&#xff08;二&#xff09;&#xff1a;添加交互 重学React&#xff08;三&#xff09;&#xff1a;状…

【MySQL】索引(B+树详解)

MySQL(五)索引 一、索引的减I/O设计 1.读取量 2.搜索树 2.1方向 2.2有序 3.分多叉 3.1B树 弊端: 3.2B树 3.2.1非叶子-搜索字段 3.2.1.1海量分叉 3.2.1.1.1最大式 3.2.1.1.2最快式 3.2.1.2缓存内存 3.2.1.2.1字段总量小 3.2.1.2.2时间复杂度 3.2.1.3区间搜索向…

GPT-5博士级AI使用教程及国内平替方案

GPT-5博士级AI使用教程及国内平替方案一、GPT-5核心升级&#xff1a;到底强在哪里&#xff1f;1. **统一入口自动思考模式**2. **256K上下文40万汉字记忆**3. **人格系统长期记忆**4. **编程能力史诗级增强**二、注册与访问&#xff1a;国内用户也能免费上车1.官方渠道&#xf…

云计算-多服务集群部署实战指南:从JumpServer到Kafka、ZooKeeper 集群部署实操流程

简介围绕企业级服务部署与集群搭建&#xff0c;基于 OpenStack 私有云平台&#xff0c;介绍了一系列关键服务的实操过程。内容涵盖使用 CentOS7 系统部署 JumpServer 堡垒机并对接 controller 与 compute 节点&#xff0c;构建 RabbitMQ 集群&#xff08;含磁盘节点与内存节点配…

深入剖析Spring IOC容器——原理、源码与实践全解析

&#x1f31f; 你好&#xff0c;我是 励志成为糕手 &#xff01; &#x1f30c; 在代码的宇宙中&#xff0c;我是那个追逐优雅与性能的星际旅人。 ✨ 每一行代码都是我种下的星光&#xff0c;在逻辑的土壤里生长成璀璨的银河&#xff1b; &#x1f6e0;️ 每一个算法都是我绘制…

探秘C语言:数据在内存中的存储机制详解

探秘C语言&#xff1a;数据在内存中的存储机制详解探秘C语言&#xff1a;数据在内存中的存储机制详解一、二进制与进制转换&#xff1a;数据的不同"外衣"1.1基本概念1.2进制转换二、整数在内存中的存储&#xff1a;补码的奥秘原码、反码、补码总结探秘C语言&#xff…

HTML 常用标签介绍

目录 HTML 标签 HTML 常用标签速查表 文档元标签 页面结构与布局 文本内容与排版 链接与媒体 列表与表格 表单与交互 其他功能标签 文本结构标签 文本格式化标签 列表标签 链接与导航标签 媒体标签 容器与结构标签 表格标签 表单标签 元信息与文档标签 脚本…

kafka 冲突解决 kafka安装

目录 解法方法&#xff1a; 一般情况正常可以版本2.0.2 报错&#xff1a; File "<frozen importlib._bootstrap>", line 1050, in _gcd_import File "<frozen importlib._bootstrap>", line 1027, in _find_and_load File "<frozen…

论文阅读 2025-8-9 [DiC, DropKey]

闲来没事&#xff0c;找点近一年的论文看看 1. DiC: Rethinking Conv3x3 Designs in Diffusion Models ✨ 一句话总结&#xff1a;DiC用沙漏架构稀疏跳跃条件门控重构纯Conv3x3扩散模型&#xff0c;在速度碾压Transformer的同时性能反超&#xff0c;为实时生成任务开辟新路径。…

16进制pcm数据转py波形脚本

将16bit的单声道或者双声道的16进制的pcm数据转成波形图片出来分析数据&#xff0c;python脚本如下&#xff1a;import numpy as np import matplotlib.pyplot as plt# 1: 单声道&#xff0c;2&#xff1a;双声道 PCM_CHANNELS 2# 你提供的十六进制数据 hex_str ""…

MySQL的锁:

目录 锁的介绍&#xff1a; 并发事务访问相同数据可以分为以下几种情况&#xff1a; 都是进行读操作&#xff1a; 都是进行写操作&#xff1a; 有读操作也有写操作&#xff1a; 读锁、写锁&#xff1a; 读锁&#xff1a; 写锁&#xff1a; 按照锁粒度分类&#xff1a;…

一道同分排名的SQL题

1 概述遇到这样一道题&#xff1a;(1) 有一张学生课程分数表&#xff0c;字段有&#xff1a;ID、名称、性别、科目、分数。&#xff08;名称换为学号更能标识唯一学生&#xff0c;但名称好阅读&#xff0c;故这里先认为名称可以唯一标识学生。&#xff09;(2) 用一个SQL&#x…

ICCV 2025 | Reverse Convolution and Its Applications to Image Restoration

标题&#xff1a;Reverse Convolution and Its Applications to Image Restoration作者&#xff1a;Xuhong Huang, Shiqi Liu, Kai Zhang, Ying Tai, Jian Yang, Hui Zeng, Lei Zhang单位&#xff1a;Nanjing University, The Hong Kong Polytechnic University, OPPO Research…

mysql启动超时

mysql启动超时&#xff1a; 管理员打开CMD后允许net start MySQL57&#xff0c; 启动超时检查错误日志 MySQL 启动失败的具体原因通常记录在错误日志中。 日志路径&#xff08;根据你的安装方式可能不同&#xff09;&#xff1a; 默认位置&#xff1a;C:\ProgramData\MySQL\MyS…

Flink Stream API 源码走读 - window 和 sum

本文核心观点 核心观点&#xff1a;WindowedStream 是一个"假流"&#xff0c;它比 KeyedStream 更虚&#xff0c;只是一个 API 的过渡器&#xff0c;不是真正意义上的 DataStream&#xff0c;需要调用函数回归。 虚拟化时刻&#xff1a;从真实流到虚拟流 KeyedStream…