宇宙
本帖最后由 马黑黑 于 2025-4-30 21:19 编辑 <br /><br /><style>#pa { --state: running; margin: 30px 0; left: calc(50% - 81px); transform: translateX(-50%); width: clamp(600px, 90vw, 1600px); height: auto; aspect-ratio: 16/9; background: url('https://bayimg.com/49690c5dec662f3800fe484eb0f2c75ae1897191.jpg') no-repeat center/cover; box-shadow: 2px 2px 10px rgba(0,0,0,.65); z-index: 1; position: relative; }
#pa canvas { display: block; }
#pa canvas { position: absolute; display: block; z-index: 5; }
#player { position: absolute; left: 20px; top: 20px; z-index: 9; clip-path: circle(45%); transition: filter .7s; cursor: pointer; animation: rot 10s linear infinite var(--state); }
#player:hover { filter: hue-rotate(90deg); }
#btnFs { right: 30px; top: 30px; color: #eee; }
#btnFs:hover { color: red; }
#pa video { position: absolute; width: 100%; height: 100; mask: radial-gradient(circle, transparent, red); opacity: .5; }
@keyframes rot { to { transform: rotate(360deg); } }
</style>
<div id="pa">
<img id="player" src="https://638183.freep.cn/638183/small/mufuz2.jpg" width="10%" title="播放/暂停" />
<audio id="aud" src="https://music.163.com/song/media/outer/url?id=450041137" autoplay loop></audio>
<video src="https://bpic.588ku.com/video_listen/588ku_video/23/08/29/17/08/52/video64edb5a4341d3.mp4" autoplay loop muted></video>
</div>
<script type="module">
import * as THREE from "https://esm.sh/three";
import { OrbitControls } from "https://esm.sh/three/examples/jsm/controls/OrbitControls";
import { FS } from 'https://638183.freep.cn/638183/web/ku/fscreen.js';
const aud = document.querySelector("#aud");
const player = document.querySelector("#player");
const pa = document.querySelector("#pa");
let w = pa.offsetWidth;
let h = pa.offsetHeight;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, w / h, 0.001, 1000);
camera.position.set(0, 3, 24);
camera.lookAt(scene.position);
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(w, h);
renderer.setClearAlpha(0.3);
pa.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
const count1 = 50000;
const count2 = 100000;
const geometry = new THREE.BufferGeometry();
const positions = [];
const sizes = [];
const shifts = [];
for (let i = 0; i < count1 + count2; i++) {
let theta = Math.random() * Math.PI * 2;
let phi = Math.acos(Math.random() * 2 - 1);
let angle = (Math.random() * 0.9 + 0.1) * Math.PI * 0.1;
let strength = Math.random() * 0.9 + 0.1;
shifts.push(theta, phi, angle, strength);
let size = Math.random() * 1.5 + 0.5;
sizes.push(size);
if (i < count1) {
let r = Math.random() * 0.5 + 9.5;
let { x, y, z } = new THREE.Vector3()
.randomDirection()
.multiplyScalar(r);
positions.push(x, y, z);
} else {
let r = 10;
let R = 40;
let rand = Math.pow(Math.random(), 1.5);
let radius = Math.sqrt(R * R * rand + (1 - rand) * r * r);
let { x, y, z }= new THREE.Vector3().setFromCylindricalCoords(
radius,
Math.random() * 2 * Math.PI,
(Math.random() - 0.5) * 2
);
positions.push(x, y, z);
}
}
geometry.setAttribute(
"position",
new THREE.Float32BufferAttribute(positions, 3)
);
geometry.setAttribute("aSize", new THREE.Float32BufferAttribute(sizes, 1));
geometry.setAttribute("aShift", new THREE.Float32BufferAttribute(shifts, 4));
const vertexShader = `
attribute float aSize;
attribute vec4 aShift;
uniform float uTime;
varying vec3 vColor;
const float PI = 3.141592653589793238;
void main() {
float d = length(abs(position) / vec3(40., 10., 40.));
d = clamp(d, 0., 1.);
vec3 color1 = vec3(227., 155., 0.);
vec3 color2 = vec3(100., 50., 255.);
vColor = mix(color1, color2, d) / 255.;
vec3 transformed = position;
float theta = mod(aShift.x + aShift.z * uTime, PI * 2.);
float phi = mod(aShift.y + aShift.z * uTime, PI * 2.);
transformed += vec3(sin(phi) * cos(theta), cos(phi), sin(phi) * sin(theta)) * aShift.w;
vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.0);
gl_PointSize = aSize * 50.0 / -mvPosition.z;
gl_Position = projectionMatrix * mvPosition;
}
`;
const fragmentShader = `
varying vec3 vColor;
void main() {
float d = length(gl_PointCoord.xy - 0.5);
if (d > 0.5) discard;
gl_FragColor = vec4(vColor, smoothstep(0.5, 0.1, d));
}
`;
const material = new THREE.ShaderMaterial({
vertexShader,
fragmentShader,
uniforms: {
uTime: { value: 0 },
},
transparent: true,
blending: THREE.AdditiveBlending,
depthTest: false,
});
const mesh = new THREE.Points(geometry, material);
mesh.rotation.order = "ZYX";
mesh.rotation.z = 0.2;
scene.add(mesh);
let clock = new THREE.Clock();
function render() {
let time = clock.getElapsedTime();
mesh.rotation.y = time * 0.01;
material.uniforms.uTime.value = time;
renderer.render(scene, camera);
controls.update();
requestAnimationFrame(render);
}
function resize() {
w = pa.offsetWidth;
h = pa.offsetHeight;
renderer.setSize(w, h);
camera.aspect = w / h;
camera.updateProjectionMatrix();
}
window.addEventListener("resize", resize);
render();
FS(pa, player);
</script> 本帖最后由 马黑黑 于 2025-4-30 21:20 编辑
帖子代码(基于 three.js 的核心代码作者为 古柳 大佬):
<style>
#pa { --state: running; margin: 30px 0; left: calc(50% - 81px); transform: translateX(-50%); width: clamp(600px, 90vw, 1600px); height: auto; aspect-ratio: 16/9; background: url('https://bayimg.com/49690c5dec662f3800fe484eb0f2c75ae1897191.jpg') no-repeat center/cover; box-shadow: 2px 2px 10px rgba(0,0,0,.65); z-index: 1; position: relative; }
#pa canvas { display: block; }
#pa canvas { position: absolute; display: block; z-index: 5; }
#player { position: absolute; left: 20px; top: 20px; z-index: 9; clip-path: circle(45%); transition: filter .7s; cursor: pointer; animation: rot 10s linear infinite var(--state); }
#player:hover { filter: hue-rotate(90deg); }
#btnFs { right: 30px; top: 30px; color: #eee; }
#btnFs:hover { color: red; }
#pa video { position: absolute; width: 100%; height: 100; mask: radial-gradient(circle, transparent, red); opacity: .5; }
@keyframes rot { to { transform: rotate(360deg); } }
</style>
<div id="pa">
<img id="player" src="https://638183.freep.cn/638183/small/mufuz2.jpg" width="10%" title="播放/暂停" />
<audio id="aud" src="https://music.163.com/song/media/outer/url?id=450041137" autoplay loop></audio>
<video src="https://bpic.588ku.com/video_listen/588ku_video/23/08/29/17/08/52/video64edb5a4341d3.mp4" autoplay loop muted></video>
</div>
<script type="module">
import * as THREE from "https://esm.sh/three";
import { OrbitControls } from "https://esm.sh/three/examples/jsm/controls/OrbitControls";
import { FS } from 'https://638183.freep.cn/638183/web/ku/fscreen.js';
const aud = document.querySelector("#aud");
const player = document.querySelector("#player");
const pa = document.querySelector("#pa");
let w = pa.offsetWidth;
let h = pa.offsetHeight;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, w / h, 0.001, 1000);
camera.position.set(0, 3, 24);
camera.lookAt(scene.position);
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(w, h);
renderer.setClearAlpha(0.3);
pa.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
const count1 = 50000;
const count2 = 100000;
const geometry = new THREE.BufferGeometry();
const positions = [];
const sizes = [];
const shifts = [];
for (let i = 0; i < count1 + count2; i++) {
let theta = Math.random() * Math.PI * 2;
let phi = Math.acos(Math.random() * 2 - 1);
let angle = (Math.random() * 0.9 + 0.1) * Math.PI * 0.1;
let strength = Math.random() * 0.9 + 0.1;
shifts.push(theta, phi, angle, strength);
let size = Math.random() * 1.5 + 0.5;
sizes.push(size);
if (i < count1) {
let r = Math.random() * 0.5 + 9.5;
let { x, y, z } = new THREE.Vector3()
.randomDirection()
.multiplyScalar(r);
positions.push(x, y, z);
} else {
let r = 10;
let R = 40;
let rand = Math.pow(Math.random(), 1.5);
let radius = Math.sqrt(R * R * rand + (1 - rand) * r * r);
let { x, y, z }= new THREE.Vector3().setFromCylindricalCoords(
radius,
Math.random() * 2 * Math.PI,
(Math.random() - 0.5) * 2
);
positions.push(x, y, z);
}
}
geometry.setAttribute(
"position",
new THREE.Float32BufferAttribute(positions, 3)
);
geometry.setAttribute("aSize", new THREE.Float32BufferAttribute(sizes, 1));
geometry.setAttribute("aShift", new THREE.Float32BufferAttribute(shifts, 4));
const vertexShader = `
attribute float aSize;
attribute vec4 aShift;
uniform float uTime;
varying vec3 vColor;
const float PI = 3.141592653589793238;
void main() {
float d = length(abs(position) / vec3(40., 10., 40.));
d = clamp(d, 0., 1.);
vec3 color1 = vec3(227., 155., 0.);
vec3 color2 = vec3(100., 50., 255.);
vColor = mix(color1, color2, d) / 255.;
vec3 transformed = position;
float theta = mod(aShift.x + aShift.z * uTime, PI * 2.);
float phi = mod(aShift.y + aShift.z * uTime, PI * 2.);
transformed += vec3(sin(phi) * cos(theta), cos(phi), sin(phi) * sin(theta)) * aShift.w;
vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.0);
gl_PointSize = aSize * 50.0 / -mvPosition.z;
gl_Position = projectionMatrix * mvPosition;
}
`;
const fragmentShader = `
varying vec3 vColor;
void main() {
float d = length(gl_PointCoord.xy - 0.5);
if (d > 0.5) discard;
gl_FragColor = vec4(vColor, smoothstep(0.5, 0.1, d));
}
`;
const material = new THREE.ShaderMaterial({
vertexShader,
fragmentShader,
uniforms: {
uTime: { value: 0 },
},
transparent: true,
blending: THREE.AdditiveBlending,
depthTest: false,
});
const mesh = new THREE.Points(geometry, material);
mesh.rotation.order = "ZYX";
mesh.rotation.z = 0.2;
scene.add(mesh);
let clock = new THREE.Clock();
function render() {
let time = clock.getElapsedTime();
mesh.rotation.y = time * 0.01;
material.uniforms.uTime.value = time;
renderer.render(scene, camera);
controls.update();
requestAnimationFrame(render);
}
function resize() {
w = pa.offsetWidth;
h = pa.offsetHeight;
renderer.setSize(w, h);
camera.aspect = w / h;
camera.updateProjectionMatrix();
}
window.addEventListener("resize", resize);
render();
FS(pa, player);
</script>
古柳大佬全名 牛衣古柳,他的展示作品地址:
https://codepen.io/prisoner849/pen/RwyzrVj three.js 使用 GLSL 渲染,比较吃显卡。如果帖子呈现的粒子不正常,刷新一下。 帖子的粒子系统在画布上渲染出来,它原本是覆盖一切的,帖子背景不可见。
我启用了 alpha: true,,并给 renderer 对象添加一个设置:
renderer.setClearAlpha(0.3);
这样canvas画布之下的帖子容器才得以呈现。设置 为 0 也行,帖子背景就完全呈现了。 粒子整体可以使用鼠标滑轮调整尺寸 马黑黑 发表于 2025-4-30 12:44
粒子整体可以使用鼠标滑轮调整尺寸
是的,进来就发现了。可以无限变大变小呢。太神奇了{:4_199:} 黑黑又带来了奇妙的效果,背景配得特别好,还让背景得到了显示。
如此浩渺的无限宇宙{:4_199:} 想起黑黑以前带来的太空蜘蛛等好几个大佬的设计,真的很让人开眼界呢{:4_187:} 马黑黑 发表于 2025-4-30 12:44
粒子整体可以使用鼠标滑轮调整尺寸
暂停的时候尺寸也不能调整了{:4_173:} 我看不到画面 开眼界、长见识了,谢谢马老师带来的大佬级分享{:4_191:} 大气磅礴,宇宙之巅,美不胜收{:4_178:} 自己去电脑预览换背景看看效果{:4_205:} 梦江南 发表于 2025-4-30 15:07
我看不到画面
需要浏览器支持,太低版本的浏览器无法支持 three.js 3d 场景的渲染 马黑黑 发表于 2025-4-30 17:00
需要浏览器支持,太低版本的浏览器无法支持 three.js 3d 场景的渲染
现在能看到了,全都是粒子组成的。 梦江南 发表于 2025-4-30 17:45
现在能看到了,全都是粒子组成的。
有背景图、有视频、有音频控制按钮 小辣椒 发表于 2025-4-30 16:36
自己去电脑预览换背景看看效果
这个应该需要本地虚拟服务器程序支持。three.js 有一些功能相对独特 小辣椒 发表于 2025-4-30 16:35
大气磅礴,宇宙之巅,美不胜收
现在加了个视频,还加了全屏按钮、键盘控制 杨帆 发表于 2025-4-30 15:23
开眼界、长见识了,谢谢马老师带来的大佬级分享
{:4_190:}