银河系的图片百度一下找一张俯视图的视角即可,只要图片上有的。这里我们将使用贴图来实现银河的效果。
基础代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
}
canvas {
width: 100%;
height: 100%
}
</style>
</head>
<body>
</body>
<script src="./three.js">
</script>
<script src="./OrbitControls.js">
</script>
<script>
const vshader = `
void main() {
gl_PointSize=1.0;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position , 1.0 );
}`
const fshader = `
void main()
{
gl_FragColor = vec4( 1.0,1.0,1.0 ,1.0);
}`
// gl_FragCoord 左下角为(0,0) 右上角 如果屏幕是1000,1000 则为(1000,1000) 是一个vec4
// mix(a,b,c) = a*c+b*(1-c);
// uv 左下角为(0,0) 右上角(1,1)
// position 中心为0,0 右上为1,1
// clamp(a x y) 返回中间大小的值 例如 clamp(5 1 4) 返回的是4 。 -3 1 2返回1 返回三个数的中间数,x必须比y小
// step(a, b);当b > a时, 返回1;当b < a时,返回0。
//smoothstep(edg0, edg1, x); edg0<edg1时,edg0左边缘,edg1右边缘,使x在edg0和edg1区间内进行平滑处理。返回值在[0, 1]区间内,当x > edg1时,返回1,当x < edg0时,返回0,当x在edg0和edg1之间时,返回x; edg0>edg1时情况相反
// length(a); 返回向量a的长度;
// fract(a) 返回小数部分,原理 fract(x)=x-floor(x);
// dat(a,b) 返回 a.x*b.x+a.y*b.y;
// mod (x,y) x - y * floor(x/y);
// normalize 得到长度为1的向量
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
1,
100000
);
camera.position.y = 10;
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const clock = new THREE.Clock();
const length = 40000;
var position = new Float32Array(length * 3)
const geometry = new THREE.BufferGeometry()
for (let i = 0; i < length; i += 1) {
const i3 = i * 3
position[i3] = (Math.random() - 0.5) * 3
position[i3 + 1] = (Math.random() - 0.5) * 3
position[i3 + 2] = (Math.random() - 0.5) * 3
}
geometry.setAttribute('position', new THREE.BufferAttribute(position, 3))
const uniforms = {
u_tex: { value: new THREE.TextureLoader().load("./1.jpg") },
u_time: { value: 0.0 }
};
const material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: vshader,
fragmentShader: fshader,
side: THREE.DoubleSide,
});
const ball = new THREE.Points(geometry, material);
scene.add(ball);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
onWindowResize();
animate();
function onWindowResize(event) {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
uniforms.u_time.value += clock.getDelta();
renderer.render(scene, camera);
}
</script>
</html>
首先创建4w个粒子,接下来把整体的形状变成一个圆盘。
let spherical = new THREE.Spherical();
const vec3 = new THREE.Vector3();
const r=3;
for (let i = 0; i < length; i += 1) {
spherical.set(
r * Math.random(),
Math.PI * 2 * (Math.random() - 0.5),
Math.PI * 2 * Math.random()
)
vec3.setFromSpherical(spherical);
position[i*3] = vec3.x
position[i*3 + 1] =vec3.y*0.1
position[i*3 + 2] = vec3.z
}
接下来就是用shader给圆盘上色了。
const vshader = `
varying vec3 pos;
void main() {
pos=position;
gl_PointSize=1.0;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position , 1.0 );
}`
const fshader = `
varying vec3 pos;
uniform sampler2D u_tex;
uniform float u_r;
void main()
{
vec2 uv=pos.xz/u_r+vec2(0.5);
vec3 color = texture2D(u_tex,uv ).rgb;
gl_FragColor = vec4( color ,1.0);
}`
const uniforms = {
u_tex: { value: new THREE.TextureLoader().load("./1.jpg") },
u_r:{value:r*2},
u_time:{value: 0.0}
};
关键代码是 vec2 uv=pos.xz/u_r+vec2(0.5); 将position的坐标位置转换成uv坐标,从而取得点对应的纹理颜色。
然后加一点点细节。
const fshader = `
varying vec3 pos;
uniform sampler2D u_tex;
uniform float u_r;
void main()
{
vec2 uv=pos.xz/u_r+vec2(0.5);
vec3 color = texture2D(u_tex,uv ).rgb;
float brightness =length(color);
if(brightness<1.0){
color =color*brightness*2.0;
}
gl_FragColor = vec4( color , 1);
}`
让暗的地方更暗,亮的地方越亮。
最后加入旋转动起来。
const vshader = `
varying vec3 pos;
uniform float u_time;
void main() {
mat3 rotate = mat3(
cos(u_time),0.0, sin(u_time),
0.0,1.0,0.0,
-sin(u_time),0, cos(u_time)
);
pos=position;
gl_PointSize=1.0;
gl_Position = projectionMatrix * modelViewMatrix * vec4( rotate*position , 1.0 );
}`
for (let i = 0; i < 100000; i += 1) {
spherical.set(
r * Math.random(),
Math.PI * 2 * (Math.random() - 0.5),
Math.PI * 2 * Math.random()
)
vec3.setFromSpherical(spherical);
position[i*3] = vec3.x
position[i*3 + 1] =vec3.y*0.1
position[i*3 + 2] = vec3.z
}
最后为了让效果更好看我调成了10w个点,看看最后的效果 。 除了调点的数量也可以调整vec3.y*0.1 这个0.1越小效果越好,还有就是gl_PointSize=1.0;往大了调,反正就这几个参数,慢慢调找到你想到的效果吧。
所有代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
}
canvas {
width: 100%;
height: 100%
}
</style>
</head>
<body>
</body>
<script src="./three.js">
</script>
<script src="./OrbitControls.js">
</script>
<script>
const vshader = `
varying vec3 pos;
uniform float u_time;
void main() {
mat3 rotate = mat3(
cos(u_time),0.0, sin(u_time),
0.0,1.0,0.0,
-sin(u_time),0, cos(u_time)
);
pos=position;
gl_PointSize=1.0;
gl_Position = projectionMatrix * modelViewMatrix * vec4( rotate*position , 1.0 );
}`
const fshader = `
varying vec3 pos;
uniform sampler2D u_tex;
uniform float u_r;
void main()
{
vec2 uv=pos.xz/u_r+vec2(0.5);
vec3 color = texture2D(u_tex,uv ).rgb;
float brightness =length(color);
if(brightness<1.0){
color =color*brightness*2.0;
}
gl_FragColor = vec4( color , 1);
}`
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
1,
100000
);
camera.position.y = 10;
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const clock = new THREE.Clock();
// const geometry = new THREE.PlaneGeometry(2,2,30,30);
let spherical = new THREE.Spherical();
const vec3 = new THREE.Vector3();
const r=3;
var position= new Float32Array(100000* 3)
const geometry = new THREE.BufferGeometry()
for (let i = 0; i < 100000; i += 1) {
spherical.set(
r * Math.random(),
Math.PI * 2 * (Math.random() - 0.5),
Math.PI * 2 * Math.random()
)
vec3.setFromSpherical(spherical);
position[i*3] = vec3.x
position[i*3 + 1] =vec3.y*0.03
position[i*3 + 2] = vec3.z
}
geometry.setAttribute('position', new THREE.BufferAttribute(position, 3))
console.log(geometry);
const uniforms = {
u_tex: { value: new THREE.TextureLoader().load("./1.jpg") },
u_r:{value:r*2},
u_time:{value: 0.0}
};
const material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: vshader,
fragmentShader: fshader,
side: THREE.DoubleSide,
});
const ball = new THREE.Points(geometry, material);
// ball.scale.set(200,200,200);
// ball.translateZ(-3000)
scene.add(ball);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
onWindowResize();
animate();
function onWindowResize(event) {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
uniforms.u_time.value += clock.getDelta();
renderer.render(scene, camera);
}
</script>
</html>