threejs shader 入门(六) Galaxy 银河星系效果。

926 阅读1分钟

6.gif

1.png

1.jpg 银河系的图片百度一下找一张俯视图的视角即可,只要图片上有的。这里我们将使用贴图来实现银河的效果。

基础代码

<!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>

2.png

首先创建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
    }

3.gif 接下来就是用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}
    };

4.gif

关键代码是 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);
    }`

5.gif 让暗的地方更暗,亮的地方越亮。

最后加入旋转动起来。

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;往大了调,反正就这几个参数,慢慢调找到你想到的效果吧。 6.gif

所有代码

<!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>