【three.js / glsl】 用 shader 写出夜空中的萤火虫

参考课程是这位老哥的 three.js 教程 很是厉害 感兴趣的能够买来看看
若是自己对 shader 还不够了解的话, the book of shaders 是很是好的入门读物

说到作“萤火虫”,确定会想到用“粒子”(particles)。javascript

用 buffer geometry 建立粒子

首先定义一个 buffer geometry, 而后建立一个 BufferAttribute 用于存储每个萤火虫的位置信息;java

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

咱们先给它一个 pointsMaterial 看看这些点都在哪里;git

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

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

看到了空中的小点点,发现彻底的随机位置有点奇怪,因此改改生成位置的代码:github

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
}

基础的小点点有了,接下来就是 shader 的魔法时刻。web

自定义 shader material: vertex shader

首先是 vertex shader,用于决定物体的节点的位置属性。canvas

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.

这段代码几乎没有什么含义,重头戏在 fragment shader 上。
可是有一个地方须要注意一下,即 gl_PointSize 须要根据屏幕分辨率而变化,因此须要从 js 文件中传进去一个 uniform: window.devicePixelRatioless

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
})

同时,要监听 resize 事件,实时更新分辨率:dom

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

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

vertex.glslide

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);
}

想让萤火虫浮动起来,还须要在外面更新 uTimewebgl

const clock = new THREE.Clock()

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

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

    // ...
}

自定义 shader material: fragment shader

咱们的目标是创造一个中心渐变的图案,听起来很简单(确实

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.

推荐阅读

jam3-webgl-shader-threejs
book of shaders

相关文章
相关标签/搜索