[Three.js / glsl] Use shader to write fireflies in the night sky

alva
中文
The reference course is this old man's three.js tutorial very interesting, you can buy it and have a look
If you don’t know enough about the book of shaders is a very good primer

When it comes to making "fireflies", one would definitely think of using "particles".

Create particles with buffer geometry

First define a buffer geometry, and then create a BufferAttribute to store the location information of each firefly;

const firefliesGeometry = new THREE.BufferGeometry()
const firefliesCount = 30 //创建萤火虫的个数
const positionArray = new Float32Array(firefliesCount * 3)

for(let i = 0; i < firefliesCount; i++)
{
    positionArray[i * 3 + 0] = Math.random() * 4
    positionArray[i * 3 + 1] = Math.random() * 4
    positionArray[i * 3 + 2] = Math.random() * 4
}

firefliesGeometry.setAttribute('position', new THREE.BufferAttribute(positionArray, 3))

Let's give it a pointsMaterial first to see where these points are;

const firefliesMaterial = new THREE.PointsMaterial({ size: 0.1, sizeAttenuation: true })

const fireflies = new THREE.Points(firefliesGeometry, firefliesMaterial)
scene.add(fireflies)

Seeing the small dots in the air, I found that the completely random position is a bit strange, so I changed the code for generating the position:

for(let i = 0; i < firefliesCount; i++)
{
    positionArray[i * 3 + 0] = (Math.random() - 0.5) * 4
    positionArray[i * 3 + 1] = Math.random() * 1.5
    positionArray[i * 3 + 2] = (Math.random() - 0.5) * 4
}

The basics are there, and then there is the magic moment of the shader.

Custom shader material: vertex shader

The first is the vertex shader, which is used to determine the position attributes of the object's nodes.

The vertex shader's purpose is to position the vertices of the geometry. The idea is to send the vertices positions, the mesh transformations (like its position, rotation, and scale), the camera information (like its position, rotation, and field of view). Then, the GPU will follow the instructions in the vertex shader to process all of this information in order to project the vertices on a 2D space that will become our render —in other words, our canvas.

This code has almost no meaning, the highlight is fragment shader .
But there is one place to pay attention to, that is, gl_PointSize needs to change according to the screen resolution, so you need to pass in a uniform from the js file: window.devicePixelRatio .

const firefliesMaterial = new THREE.ShaderMaterial({
    uniforms:
    {
        uPixelRatio: { value: Math.min(window.devicePixelRatio, 2) },
        uSize: { value: 100 },
        uTime: { value: 0 },
    },
    blending: THREE.AdditiveBlending, //叠加模式也很重要
    vertexShader: firefliesVertexShader,
    fragmentShader: firefliesFragmentShader
})

At the same time, it is necessary to monitor the resize event and update the resolution in real time:

window.addEventListener('resize', () =>
{
    // ...

    // Update fireflies
    firefliesMaterial.uniforms.uPixelRatio.value = Math.min(window.devicePixelRatio, 2)
})

vertex.glsl

uniform float uPixelRatio;
uniform float uSize; // 粒子大小
uniform float uTime;

void main()
{
    vec4 modelPosition = modelMatrix * vec4(position, 1.0);
    vec4 viewPosition = viewMatrix * modelPosition;
    vec4 projectionPosition = projectionMatrix * viewPosition;

    gl_Position = projectionPosition;
    gl_PointSize = uSize * uPixelRatio; //每一个点的渲染尺寸
    gl_PointSize *= (1.0 / - viewPosition.z); //近大远小
    
    // 萤火虫的浮动
    vec4 modelPosition = modelMatrix * vec4(position, 1.0); 
    modelPosition.y += sin(uTime);
}

If you want the fireflies to float, you need to update uTime outside

const clock = new THREE.Clock()

const tick = () =>
{
    const elapsedTime = clock.getElapsedTime()

    // Update materials
    firefliesMaterial.uniforms.uTime.value = elapsedTime

    // ...
}

Custom shader material: fragment shader

Our goal is to create a pattern with a gradient in the center, which sounds simple (and indeed

void main()
{
    float distanceToCenter = distance(gl_PointCoord, vec2(0.5)); // 计算每一个像素点到中心的距离
    float strength = 0.05 / distanceToCenter - 0.1; // 中心大,周围小

    gl_FragColor = vec4(1.0, 1.0, 1.0, strength); // 将 strength 作为 alpha
}

voila.

Recommended reading

jam3-webgl-shader-threejs
book of shaders

阅读 2.5k

兔子洞精选
图比较好看。

乌龟爬兔子洞。

71 声望
18 粉丝
0 条评论

乌龟爬兔子洞。

71 声望
18 粉丝
文章目录
宣传栏