返回

WebGPU 模拟飞鸟,学习自然界中的生命运动形式!

前端

之前在文章中,我们简单讨论了GPU流水线的知识,我们直接在GPU上来完成我们认为比较耗费CPU的计算任务,然后利用GPU的图形处理能力,直接将结果呈现在屏幕上,而这其中所谓的计算指的是什么?如果利用三角形的形状,能不能表示不同的数据呢?如果我们将复杂的计算结果映射为屏幕上的三角形网格,那么这些三角形能否按照我们所希望的方式产生运动呢?答案是可以的。本篇文章就详细说明一下如何用WebGPU来模拟自然界中飞鸟的运动方式。

实现起来,我们需要用到WebGPU和wgsl,了解GPU流水线的基础知识,同时了解wgsl这个语言的语法。首先我们来观察一下鸟群在自然界是如何运动的。在对运动轨迹做抽象时,有很多种方法,我们采取的方法是计算前后的方向和最近的邻居的位置,这样就会有一种趋利避害的感觉。

效果如下:

[图片]

这里将所有鸟群的运动当做一个大计算任务,交由GPU来完成,而这就需要我们重构计算任务,因为我们之前实现的计算任务都是针对各个三角形单独执行的,而非依赖三角形的邻居。

第一个任务是根据三角形索引和索引对应的邻居位置,得到相对于自己的位移向量。

fn calculateOffset(
    index: u32,
    neighbors: &[[f32; 4]],
    config: &Config,
) -> Vec3 {
    let neighbor_positions = neighbors[index as usize].to_vec();

    let mut alignment = Vec3::ZERO;
    let mut cohesion = Vec3::ZERO;
    let mut separation = Vec3::ZERO;

    let num_neighbors = neighbor_positions.len();
    for pos in neighbor_positions {
        let neighbor_vec = Vec3::from_slice(&pos[0..3]);
        let diff_vec = neighbor_vec - position(index, config);

        // calculate alignment
        alignment += diff_vec.normalize();

        // calculate cohesion
        cohesion += neighbor_vec;

        // calculate separation
        separation += diff_vec / diff_vec.length();
    }

    alignment /= num_neighbors as f32;
    cohesion = (cohesion / num_neighbors as f32) - position(index, config);
    separation /= num_neighbors as f32;

    alignment * config.alignment_weight
        + cohesion * config.cohesion_weight
        + separation * config.separation_weight
}

接着将邻居们的影响力加到三角形上,变成三角形的方向。

fn updateDirection(index: u32, config: &Config, offsets: &[Vec3]) {
    let current_direction =
        &mut vertex_positions[index as usize][3..6].to_vec().cast::<f32>();
    let target_direction =
        calculateOffset(index, &offsets, config).normalize().cast::<f32>();
    *current_direction =
        lerp(current_direction, &target_direction, config.lerp_amount);
}

最后将方向乘上一个固定速度,就会产生运动效果。

fn updatePosition(index: u32, config: &Config, directions: &[Vec3]) {
    let speed = Vec3::new(config.speed, config.speed, config.speed);
    let dir = &directions[index as usize].cast::<f32>();
    let pos = position(index, config);
    let new_pos = pos + speed * dir;
    vertex_positions[index as usize][0..3] = new_pos.cast::<i32>();
}

更多细节可以参考:

https://webglfundamentals.org/webgl/lessons/webgl-compute-shader-intro.html
https://observablehq.com/@fil/gpu-compute-bird-boids-flock-simulation

以上。