Vala プログラミング

WebGPU プログラミング

おなが@京都先端科学大

物理シミュレーション 剛体の衝突 1

物理シミュレーションの勉強のため、剛体の衝突についてまとめてみました。
剛体衝突の物理シミュレーションは、主に以下の3つの方法で行われています。
1 Force Based
2 Position Based(Constraint)
3 Position Based(ShapeMatching)
衝突のシミュレーションを行うには、衝突検出処理と衝突応答処理が必要に
なりますが、これらは後者に相当します。

1番目の Force Based は、従来からのニュートン運動方程式を数値積分して
いく方法です。衝突の際の、反発力や摩擦力を考慮して計算します。
2番目の Position Based(Constraint) は 2006年に考案された方法です。(下の
参考文献 Position Based Dynamics) 剛体の衝突を拘束(Constraint)と見なし、
その拘束条件を満たすように拘束方程式を解いて、衝突後の剛体の位置(Position)
を知る方法です。衝突の場合は、速度についての拘束条件を用います。
3番目の Position Based(ShapeMatching) は 2005年に出てきた方法です。
(下の参考文献 Meshless Deformations Based on Shape Matching、2番目と
同じグループが考案) これは、剛体を頂点座標で表し、衝突した際の頂点の
移動点を、剛体のもとの形状を保つように頂点を移動させる方法です。

これら3つの方法を理解するために、数学ライブラリーと衝突検出処理には
同じアルゴリズムを使い、Javascript で実装してみました。

物理シミュレーションの理解に役立つ参考文献、サイトです。
1 酒井幸市著「WebGLによる 物理シミュレーション」( 工学社 )
2 藤澤 誠著「CGのための物理シミュレーションの基礎」( マイナビ )
3 特定非営利活動法人natural science
  http://www.natural-science.or.jp/
  コンピュータ・シミュレーション講座、仮想物理実験室ページの下部にある
  記事リストに様々な現象の実装が有ります。
4 物理学の見つけ方
  https://physics-htfi.github.io/

など。

衝突応答処理に関する参考文献、サイトです。
1 Detailed Rigid Body Simulation with Extended Position Based Dynamics
  https://matthias-research.github.io/pages/publications/publications.html

2 Modeling and Solving Constraints
  https://ubm-twvideo01.s3.amazonaws.com/o1/vault/gdc09/slides/04-GDC09_Catto_Erin_Solver.pdf

3 物理エンジンの作り方
  http://cedec.cesa.or.jp/2008/archives/file/pg07.pdf
  物理エンジンの作り方 その2
  http://cedec.cesa.or.jp/2009/ssn_archive/pdf/sep3rd/PG75.pdf
4 A Survey on Position Based Dynamics, 2017
  https://matthias-research.github.io/pages/publications/publications.html
5 Position Based Dynamics
  https://matthias-research.github.io/pages/publications/publications.html
6 Meshless Deformations Based on Shape Matching
  https://matthias-research.github.io/pages/publications/publications.html
など。

実装用に参照した文献、サイトです。
ForceBased
酒井幸市著「WebGLによる 物理シミュレーション」( 工学社 )
第4章 剛体の衝突 (参考コードは書籍のページからダウンロード可)

PositionBased(Constraint)
2D Rigid Body Physics Engine
https://github.com/Sopiro/Physics
(実際に参照したのは、古いバージョン: 2021/11/06 ダウンロード
このバージョンの game.js )

PositionBased(ShapeMatching)
Physical animation with shape matching
Claudio Esperança
https://observablehq.com/@esperanc/physical-animation-with-shape-matching
右上にある […] アイコンの Export からコードをダウンロードできる。
69eb2ec167e70941@1046.js がメインのプログラムである。

数学ライブラリーと衝突検出処理には、ShapeMatching で参照したプログラムを使用しています。衝突検出の方法として、主にSAT(Separating Axis Theorem) と GJK(Gilbert-Johnson-Keerthi)法がありますが、ここでは、GJK法を使用します。また、衝突時のめり込みの長さや方向を知るため、EPA(Expanding Polytope Algorithm)法を使用します。

数学ライブラリー(math.js)

define( function() {
function clamp(value, min, max) {
    if (value < min)
        return min;
    else if (value > max)
        return max;
    else
        return value;
}

class Vector2 {
    constructor(x = 0, y = 0) {
        this.x = x;
        this.y = y;
    }
    clear() {
        this.x = 0;
        this.y = 0;
    }
    copy() {
        return new Vector2(this.x, this.y);
    }
    toFixed() {
        this.x = Math.round(this.x * 1e9) / 1e9;
        this.y = Math.round(this.y * 1e9) / 1e9;
    }
    fixed() {
        return new Vector2(Math.round(this.x * 1e9) / 1e9, Math.round(this.y * 1e9) / 1e9);
    }
    invert() {
        this.x *= -1;
        this.y *= -1;
    }
    inverted() {
        return new Vector2(this.x * -1, this.y * -1);
    }
    normalize() {
        const len = this.getLength();
        if (len != 0) {
            this.x /= len;
            this.y /= len;
        }
    }
    normalized() {
        const len = this.getLength();
        if (len != 0)
            return this.divS(len);
        else
            return this;
    }
    getLength() {
        return Math.sqrt(this.x * this.x + this.y * this.y);
    }
    dot(v) {
        return this.x * v.x + this.y * v.y;
    }
    cross(v) {
        return this.x * v.y - this.y * v.x;
    }
    addV(v) {
        return new Vector2(this.x + v.x, this.y + v.y);
    }
    subV(v) {
        return new Vector2(this.x - v.x, this.y - v.y);
    }
    divS(v) {
        return new Vector2(this.x / v, this.y / v);
    }
    mulS(v) {
        return new Vector2(this.x * v, this.y * v);
    }
    equals(v) {
        return this.x == v.x && this.y == v.y;
    }
    unNaN() {
        if (isNaN(this.x) || isNaN(this.y)) {
            this.x = 0;
            this.y = 0;
        }
    }
}

class Vector3 {
    constructor(x = 0, y = 0, z = 0) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
    clear() {
        this.x = 0;
        this.y = 0;
        this.z = 0;
    }
    copy() {
        return new Vector3(this.x, this.y, this.z);
    }
    toFixed() {
        this.x = Math.round(this.x * 1e9) / 1e9;
        this.y = Math.round(this.y * 1e9) / 1e9;
        this.z = Math.round(this.z * 1e9) / 1e9;
    }
    fixed() {
        return new Vector3(Math.round(this.x * 1e9) / 1e9, Math.round(this.y * 1e9) / 1e9, Math.round(this.z * 1e9) / 1e9);
    }
    normalize() {
        const len = this.getLength();
        this.x /= len;
        this.y /= len;
        this.z /= len;
    }
    normalized() {
        const len = this.getLength();
        if (len != 0)
            return this.divS(len);
        else
            return this;
    }
    invert() {
        this.x *= -1;
        this.y *= -1;
        this.z *= -1;
    }
    inverted() {
        return new Vector3(this.x * -1, this.y * -1, this.z * -1);
    }
    getLength() {
        return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
    }
    dot(v) {
        return this.x * v.x + this.y * v.y + this.z * v.z;
    }
    cross(v) {
        return new Vector3(this.y * v.z - this.z * v.y, this.z * v.x - this.x * v.z, this.x * v.y - this.y * v.x);
    }
    add(v) {
        return new Vector3(this.x + v.x, this.y + v.y, this.z + v.z);
    }
    sub(v) {
        return new Vector3(this.x - v.x, this.y - v.y, this.z - v.z);
    }
    divS(v) {
        return new Vector3(this.x / v, this.y / v, this.z / v);
    }
    divXYZ(x, y, z) {
        return new Vector3(this.x / x, this.y / y, this.z / z);
    }
    mulS(v) {
        return new Vector3(this.x * v, this.y * v, this.z * v);
    }
    mulXYZ(x, y, z) {
        return new Vector3(this.x * x, this.y * y, this.z * z);
    }
    equals(v) {
        return this.x == v.x && this.y == v.y && this.z == v.z;
    }
    unNaN() {
        if (isNaN(this.x) || isNaN(this.y) || isNaN(this.z)) {
            this.x = 0;
            this.y = 0;
            this.z = 0;
        }
    }
}

class Vector4 {
    constructor(x = 0, y = 0, z = 0, w = 0) {
        this.x = x;
        this.y = y;
        this.z = z;
        this.w = w;
    }
    clear() {
        this.x = 0;
        this.y = 0;
        this.z = 0;
        this.w = 0;
    }
    copy() {
        return new Vector4(this.x, this.y, this.z, this.w);
    }
    toFixed() {
        this.x = Math.round(this.x * 1e9) / 1e9;
        this.y = Math.round(this.y * 1e9) / 1e9;
        this.z = Math.round(this.z * 1e9) / 1e9;
        this.w = Math.round(this.w * 1e9) / 1e9;
    }
    fixed() {
        return new Vector4(Math.round(this.x * 1e9) / 1e9, Math.round(this.y * 1e9) / 1e9, Math.round(this.z * 1e9) / 1e9, Math.round(this.w * 1e9) / 1e9);
    }
    normalize() {
        const len = this.getLength();
        this.x /= len;
        this.y /= len;
        this.z /= len;
        this.w /= len;
    }
    normalized() {
        const len = this.getLength();
        if (len != 0)
            return this.divS(len);
        else
            return this;
    }
    invert() {
        this.x *= -1;
        this.y *= -1;
        this.z *= -1;
        this.w *= -1;
    }
    inverted() {
        return new Vector4(this.x * -1, this.y * -1, this.z * -1, this.w * -1);
    }
    getLength() {
        return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w);
    }
    dot(v) {
        return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w;
    }
    add(v) {
        return new Vector4(this.x + v.x, this.y + v.y, this.z + v.z, this.w + v.w);
    }
    sub(v) {
        return new Vector4(this.x - v.x, this.y - v.y, this.z - v.z, this.w - v.w);
    }
    divS(v) {
        return new Vector4(this.x / v, this.y / v, this.z / v, this.w / v);
    }
    divXYZW(x, y, z, w) {
        return new Vector4(this.x / x, this.y / y, this.z / z, this.w / w);
    }
    mulS(v) {
        return new Vector4(this.x * v, this.y * v, this.z * v, this.w * v);
    }
    mulXYZW(x, y, z, w) {
        return new Vector4(this.x * x, this.y * y, this.z * z, this.w * w);
    }
    equals(v) {
        return this.x == v.x && this.y == v.y && this.z == v.z && this.w == v.w;
    }
    unNaN() {
        if (isNaN(this.x) || isNaN(this.y) || isNaN(this.z) || isNaN(this.w)) {
            this.x = 0;
            this.y = 0;
            this.z = 0;
            this.w = 0;
        }
    }
}

class Matrix3 {
    constructor() {
        this.m00 = 1;
        this.m01 = 0;
        this.m02 = 0;
        this.m10 = 0;
        this.m11 = 1;
        this.m12 = 0;
        this.m20 = 0;
        this.m21 = 0;
        this.m22 = 1;
    }
    loadIdentity() {
        this.m00 = 1;
        this.m01 = 0;
        this.m02 = 0;
        this.m10 = 0;
        this.m11 = 1;
        this.m12 = 0;
        this.m20 = 0;
        this.m21 = 0;
        this.m22 = 1;
    }
    copy() {
        let res = new Matrix3();
        res.m00 = this.m00;
        res.m01 = this.m01;
        res.m02 = this.m02;
        res.m10 = this.m10;
        res.m11 = this.m11;
        res.m12 = this.m12;
        res.m20 = this.m20;
        res.m21 = this.m21;
        res.m22 = this.m22;
        return res;
    }
    fromValues(a, b, c, d, e, f, g, h, i) {
        let res = new Matrix3();
        res.m00 = a;
        res.m01 = b;
        res.m02 = c;
        res.m10 = d;
        res.m11 = e;
        res.m12 = f;
        res.m20 = g;
        res.m21 = h;
        res.m22 = i;
        return res;
    }
    mulMatrix(right) {
        let res = new Matrix3();
        res.m00 = this.m00 * right.m00 + this.m01 * right.m10 + this.m02 * right.m20;
        res.m01 = this.m00 * right.m01 + this.m01 * right.m11 + this.m02 * right.m21;
        res.m02 = this.m00 * right.m02 + this.m01 * right.m12 + this.m02 * right.m22;
        res.m10 = this.m10 * right.m00 + this.m11 * right.m10 + this.m12 * right.m20;
        res.m11 = this.m10 * right.m01 + this.m11 * right.m11 + this.m12 * right.m21;
        res.m12 = this.m10 * right.m02 + this.m11 * right.m12 + this.m12 * right.m22;
        res.m20 = this.m20 * right.m00 + this.m21 * right.m10 + this.m22 * right.m20;
        res.m21 = this.m20 * right.m01 + this.m21 * right.m11 + this.m22 * right.m21;
        res.m22 = this.m20 * right.m02 + this.m21 * right.m12 + this.m22 * right.m22;
        return res;
    }
    mulVector(right, z) {
        let res = new Vector2(0, 0);
        if (z == undefined)
            z = 1;
        res.x = this.m00 * right.x + this.m01 * right.y + this.m02 * z;
        res.y = this.m10 * right.x + this.m11 * right.y + this.m12 * z;
        return res;
    }
    mulVectors(right, z) {
        let res = [];
        for (let i = 0; i < right.length; i++)
            res.push(this.mulVector(right[i], 1));
        return res;
    }
    scale(x, y) {
        if (y == undefined)
            y = x;
        let scale = new Matrix3();
        scale.m00 = x;
        scale.m11 = y;
        return this.mulMatrix(scale);
    }
    rotate(r) {
        const sin = Math.sin(r);
        const cos = Math.cos(r);
        let res = new Matrix3();
        res.m00 = cos;
        res.m01 = -sin;
        res.m10 = sin;
        res.m11 = cos;
        return this.mulMatrix(res);
    }
    translate(x, y) {
        let res = new Matrix3();
        res.m02 = x;
        res.m12 = y;
        return this.mulMatrix(res);
    }
}

class Matrix4 {
    constructor() {
        this.m00 = 1;
        this.m01 = 0;
        this.m02 = 0;
        this.m03 = 0;
        this.m10 = 0;
        this.m11 = 1;
        this.m12 = 0;
        this.m13 = 0;
        this.m20 = 0;
        this.m21 = 0;
        this.m22 = 1;
        this.m23 = 0;
        this.m30 = 0;
        this.m31 = 0;
        this.m32 = 0;
        this.m33 = 1;
    }
    loadIdentity() {
        this.m00 = 1;
        this.m01 = 0;
        this.m02 = 0;
        this.m03 = 0;
        this.m10 = 0;
        this.m11 = 1;
        this.m12 = 0;
        this.m13 = 0;
        this.m20 = 0;
        this.m21 = 0;
        this.m22 = 1;
        this.m23 = 0;
        this.m30 = 0;
        this.m31 = 0;
        this.m32 = 0;
        this.m33 = 1;
    }
    copy() {
        let res = new Matrix4();
        res.m00 = this.m00;
        res.m01 = this.m01;
        res.m02 = this.m02;
        res.m03 = this.m03;
        res.m10 = this.m10;
        res.m11 = this.m11;
        res.m12 = this.m12;
        res.m13 = this.m13;
        res.m20 = this.m20;
        res.m21 = this.m21;
        res.m22 = this.m22;
        res.m23 = this.m23;
        res.m20 = this.m30;
        res.m31 = this.m31;
        res.m32 = this.m32;
        res.m33 = this.m33;
        return res;
    }
    fromAxis(vx, vy, vz) {
        let res = new Matrix4();
        res.m00 = vx.x;
        res.m01 = vy.x;
        res.m02 = vz.x;
        res.m10 = vx.y;
        res.m11 = vy.y;
        res.m12 = vz.y;
        res.m20 = vx.z;
        res.m21 = vy.z;
        res.m22 = vz.z;
        return res;
    }
    mulMatrix(right) {
        let res = new Matrix4();
        res.m00 = this.m00 * right.m00 + this.m01 * right.m10 + this.m02 * right.m20 + this.m03 * right.m30;
        res.m01 = this.m00 * right.m01 + this.m01 * right.m11 + this.m02 * right.m21 + this.m03 * right.m31;
        res.m02 = this.m00 * right.m02 + this.m01 * right.m12 + this.m02 * right.m22 + this.m03 * right.m32;
        res.m03 = this.m00 * right.m03 + this.m01 * right.m13 + this.m02 * right.m23 + this.m03 * right.m33;
        res.m10 = this.m10 * right.m00 + this.m11 * right.m10 + this.m12 * right.m20 + this.m13 * right.m30;
        res.m11 = this.m10 * right.m01 + this.m11 * right.m11 + this.m12 * right.m21 + this.m13 * right.m31;
        res.m12 = this.m10 * right.m02 + this.m11 * right.m12 + this.m12 * right.m22 + this.m13 * right.m32;
        res.m13 = this.m10 * right.m03 + this.m11 * right.m13 + this.m12 * right.m23 + this.m13 * right.m33;
        res.m20 = this.m20 * right.m00 + this.m21 * right.m10 + this.m22 * right.m20 + this.m23 * right.m30;
        res.m21 = this.m20 * right.m01 + this.m21 * right.m11 + this.m22 * right.m21 + this.m23 * right.m31;
        res.m22 = this.m20 * right.m02 + this.m21 * right.m12 + this.m22 * right.m22 + this.m23 * right.m32;
        res.m23 = this.m20 * right.m03 + this.m21 * right.m13 + this.m22 * right.m23 + this.m23 * right.m33;
        res.m30 = this.m30 * right.m00 + this.m31 * right.m10 + this.m32 * right.m20 + this.m33 * right.m30;
        res.m31 = this.m30 * right.m01 + this.m31 * right.m11 + this.m32 * right.m21 + this.m33 * right.m31;
        res.m32 = this.m30 * right.m02 + this.m31 * right.m12 + this.m32 * right.m22 + this.m33 * right.m32;
        res.m33 = this.m30 * right.m03 + this.m31 * right.m13 + this.m32 * right.m23 + this.m33 * right.m33;
        return res;
    }
    mulVector(right, w) {
        let res = new Vector3(0, 0, 0);
        if (w == undefined)
            w = 1;
        res.x = this.m00 * right.x + this.m01 * right.y + this.m02 * right.z + this.m03 * w;
        res.y = this.m10 * right.x + this.m11 * right.y + this.m12 * right.z + this.m13 * w;
        res.z = this.m20 * right.x + this.m21 * right.y + this.m22 * right.z + this.m23 * w;
        return res;
    }
    mulVectors(right, z) {
        let res = [];
        for (let i = 0; i < right.length; i++)
            res.push(this.mulVector(right[i], 1));
        return res;
    }
    scale(x, y, z) {
        if (y == undefined && z == undefined) {
            y = x;
            z = x;
        }
        let scale = new Matrix4();
        scale.m00 = x;
        scale.m11 = y;
        scale.m22 = z;
        return this.mulMatrix(scale);
    }
    rotate(x, y, z) {
        const sinX = Math.sin(x);
        const cosX = Math.cos(x);
        const sinY = Math.sin(y);
        const cosY = Math.cos(y);
        const sinZ = Math.sin(z);
        const cosZ = Math.cos(z);
        let res = new Matrix4();
        res.m00 = cosY * cosZ;
        res.m01 = -cosY * sinZ;
        res.m02 = sinY;
        res.m03 = 0;
        res.m10 = sinX * sinY * cosZ + cosX * sinZ;
        res.m11 = -sinX * sinY * sinZ + cosX * cosZ;
        res.m12 = -sinX * cosY;
        res.m13 = 0;
        res.m20 = -cosX * sinY * cosZ + sinX * sinZ;
        res.m21 = cosX * sinY * sinZ + sinX * cosZ;
        res.m22 = cosX * cosY;
        res.m23 = 0;
        res.m30 = 0;
        res.m31 = 0;
        res.m32 = 0;
        res.m33 = 1;
        return this.mulMatrix(res);
    }
    translate(x, y, z) {
        let res = new Matrix4();
        res.m03 = x;
        res.m13 = y;
        res.m23 = z;
        return this.mulMatrix(res);
    }
}

return {Vector2: Vector2, Vector3: Vector3, Vector4: Vector4,
    Matrix3: Matrix3, Matrix4: Matrix4, clamp: clamp};
});

衝突検出処理(gjk_epa.js)

define(['./math'], function(math) {
function support(pts, v) {
    //// pts: Array, element Vector2
    //// v: Vector2
    //// return: Vector2
    let maxdot = Number.NEGATIVE_INFINITY;
    let best;
    for (let p of pts) {
        let dot = p.dot(v);
        if (dot > maxdot) [maxdot, best] = [dot, p];
    }
    return best;
}

function support2(pts1, pts2, v) {
    //// pts1, pts2: Array, element Vector2
    //// v: Vector2
    //// return: Vector2
    try {
         return support(pts1, v).subV(support(pts2, v.mulS(-1)));
    } catch (err) {
        throw "BUG:" + err;
    }
}

function tripleProduct(a, b, c) {
    //// a, b, c: Vector2
    //// return: Vector2
    let z = a.x * b.y - a.y * b.x;
    return new math.Vector2(-c.y * z, c.x * z);
}

function closestEdge(polytope) {
    //// polytope: [a, b, c]  a,b,c Vector2

    let npts = polytope.length;
    let dmin = Number.POSITIVE_INFINITY;
    let closest;

    for (let i = 0; i < npts; i++) {
        let [p, q] = [polytope[i], polytope[(i + 1) % npts]];
        //// p, q: Vector2
        let e = q.subV(p);
        let epPerp = tripleProduct(e, p, e);
        let n = epPerp.normalized();
        let dist = n.dot(p);
        if (dist < dmin) [dmin, closest] = [dist, { dist, i, p, q, n }];
    }
    return closest;
}

return {
    gjk: function(pts1, pts2) {
        //// pts1, pts2: Array, element Vector2
        //// return: false or [a, b, c] a,b,c Vector2

        // First point of pts1-pts2
        let a = support2(pts1, pts2, new math.Vector2(1, 1));
      
        // First direction
        let v = a.mulS(-1);
      
        // Second point
        let b = support2(pts1, pts2, v);
        if (b.dot(v) <= 0) return false; // Second point fails
      
        // Second direction
        let ab = b.subV(a);
        v = tripleProduct(ab, a.mulS(-1), ab);
      
        for (;;) {
            // Third point
            let c = support2(pts1, pts2, v);
            if (c.dot(v) <= 0) return false; // Third point fails
      
            let c0 = c.mulS(-1);
            let cb = b.subV(c);
            let ca = a.subV(c);
      
            let cbPerp = tripleProduct(ca, cb, cb);
            let caPerp = tripleProduct(cb, ca, ca);
      
            if (caPerp.dot(c0) > 0) {
                b = c;
                v = caPerp;
            } else if (cbPerp.dot(c0) > 0) {
                a = c;
                v = cbPerp;
            } else return [a, b, c];
        }
    },

    epa: function(pts1, pts2, a, b, c) {
        //// pts1, pts2: Array, element Vector2
        //// a, b, c: Vector2
        //// [a, b, c]: simplex

        let polytope = [a, b, c];
      
        for (;;) {
            let { dist, i, p, q, n } = closestEdge(polytope);
            let r = support2(pts1, pts2, n);
            if (Math.abs(n.dot(r) - dist) < 0.001) return { p, q, dist, n };
            polytope.splice(i + 1, 0, r);
        }
    }
};

});

jsファイルは、それぞれ require/define を使ってモジュール化している。
(次回につづく)

Julia MeshCat SPH 流体シミュレーション (Mesh)

update 2022/03/03
Julia MeshCat SPH 流体シミュレーション (Mesh) の記事のupdate です。
GeometryTypes ライブラリーが GeometryBasics に変更されました。
この変更により、MeshCat でメッシュの表示ができなくなっていました。
この変更に合わせて、Meshing ライブラリーも update され、新たな書式で
表示できるようになっています。
Meshデータの生成は、直接 pov ファイルを生成するように変更しました。
(変更後のプログラムを掲載しています。)

Julia MeshCat ライブラリを用いた、水柱崩壊のシミュレーションです。
流体のシミュレーションには、SPH (Smoothed Particle Hydrodynamics) 法を使用しています。
前回は、PointCloudを用いて、粒子による描画を行いました。
今回は、Marching cubes法を用いて、meshによる描画を行います。
水柱崩壊のシミュレーション(meshによる描画)
f:id:onagat12:20220303141459g:plain
水柱崩壊のシミュレーション(povrayによる描画)
f:id:onagat12:20220303141708g:plain

1. meshの描画
 粒子による描画をmeshによる描画にするには、粒子の集まりの表面にmeshを
張ります。具体的には、粒子の密度と静止状態の密度の差(density - rest_density)
がゼロとなる境界にmeshを張っていきます。
これは、「修士論文 水の実時間アニメーション 天田崇」
 (https://library.naist.jp/mylimedio/dllimedio/showpdf2.cgi/DLPDFR003344_P1-56)の方法を参考にしました。
・meshの生成
 ①distance field 値を計算する。
  陰関数には、粒子の密度を用いる。
 ②distance field 値から、Marching cubes法を用いて、meshのvertexとfaceを生成する。

## MeshCat-sph-mc.jl

using MeshCat
vis = Visualizer()
#open(vis)

using Blink
AtomShell.isinstalled() || AtomShell.install()
open(vis, Window())

using Meshing
using ColorTypes
using CoordinateTransformations
using Rotations
using LinearAlgebra
using LinearAlgebra: norm
using GeometryBasics
using GeometryBasics: Mesh

mutable struct Particle
    position::Point3
    velocity::Point3
    density
    pressure
    force::Point3
end

# weight function
w_rho(r, h) = r < h ? (h^2 - r^2)^3 : 0.0
w_pressure(r,h) = r < h ? (h - r)^2 : 0.0
w_viscosity(r,h) = r < h ? (h- r) : 0.0

# Gravity
accel_g = [0.0, 0.0, -9.8]

# constants
mass = 0.00020543
pressure_stiffness = 1.0
rest_density = 600.0
h = 0.01
l0 = (mass / rest_density)^(1.0 / 3.0)
simscale = 0.004
d = l0 / simscale * 0.87
visc = 0.2
limit = 200.0
radius = 0.004
epsiron = 0.00001
extstiff = 10000.0
extdamp = 256.0

w_rho_coef = 315.0 / (64.0 * pi * h^9)
w_pressure_coef = 45.0 / (pi * h^6)
w_visc_coef = w_pressure_coef

init_min = [ 0.0,  0.0, 0.0]
init_max = [10.0, 20.0, 10.0] 
pos_min = [0.0, 0.0, 0.0]
pos_max = [10.0, 20.0, 20.0]

nx = 5; ny = 5; nz = 8

particle_list = []

for iz = 0:nz+1, iy = 0:ny+1, ix = 0:nx+1
    x = init_min[1] + d*ix; y = init_min[2] + d*iy; z = init_min[3] + d*iz

    position = Point3(x, y, z)
    velocity = Point3(0, 0, 0)
    density = 0.0
    pressure = 0.0
    force = Point3(0.0, 0.0, 0.0)
    push!(particle_list,
       Particle(position, velocity, density, pressure, force))
end
particles = length(particle_list)
   println("number of particles: ", particles)
## number of particles: (nx+2)*(ny+2)*(nz+2)

function Simulation(DT)
   # neghiber_list
    neighbor_list = []
    for i = 1:particles
        position_i = particle_list[i].position
        neighbors = []
        for j = 1:particles
            j == i && continue
            position_j = particle_list[j].position
            distance = norm(position_j - position_i) * simscale
            if distance < h
                push!(neighbors, j)
            end
        end
        push!(neighbor_list, neighbors)
    end

    # density(rho)
    for i = 1:particles
        position_i = particle_list[i].position

        sum = 0.0
        map(neighbor_list[i]) do j
            position_j = particle_list[j].position
            distance = norm(position_j - position_i) * simscale
            sum += w_rho(distance, h)
        end
        # density
        particle_list[i].density = sum * mass * w_rho_coef

        # pressure
        particle_list[i].pressure =
            pressure_stiffness * (particle_list[i].density - rest_density)
    end
    
    # force: pressure gradient, viscosity and adj(force at wall)
    for i = 1:particles
        pressure_gradient = [0.0, 0.0, 0.0]
        visc_term = [0.0, 0.0, 0.0]
        position_i = particle_list[i].position
        
        map(neighbor_list[i]) do j
            position_j = particle_list[j].position
            distance = norm(position_j - position_i) * simscale
            
            diffji =  (position_j - position_i) * simscale
            pweight = w_pressure_coef * w_pressure(distance, h) * diffji / distance
            
            pji = particle_list[j].pressure + particle_list[i].pressure

            rho_i = particle_list[i].density
            rho_j = particle_list[j].density
            pgradient = -0.5 * pji / (rho_i * rho_j) * pweight
            pressure_gradient .+= pgradient

            vweight = w_visc_coef * w_viscosity(distance, h)
            vji = particle_list[j].velocity - particle_list[i].velocity
            vterm = visc  * vji / (rho_i * rho_j) * vweight 
            
            visc_term .+= vterm
        end
        particle_list[i].force = pressure_gradient + visc_term
    end
    
    # adj force
    accel_adj = Point3(0.0, 0.0, 0.0)
    diff_min = Point3(0.0, 0.0, 0.0)
    diff_max = Point3(0.0, 0.0, 0.0)
    map(particle_list) do particle
        accel = particle.force * mass

        speed = norm( accel )^2;
        if speed > limit^2
            accel *= limit / sqrt(speed)
        end
        
        diff_min = 2.0 * radius .- ( particle.position - pos_min ) * simscale
        diff_max = 2.0 * radius .- ( pos_max - particle.position ) * simscale
        
        # Z
        if diff_min[3] > epsiron
            normal = Point3( 0.0, 0.0, 1.0 )
            adj = extstiff * diff_min[3] - extdamp * dot( normal, particle.velocity )
            accel += adj * normal
        end
        if diff_max[3] > epsiron
            normal = Point3( 0.0, 0.0, -1.0 )
            adj = extstiff * diff_max[3] - extdamp * dot( normal, particle.velocity )
            accel += adj * normal
        end

        # X
        if diff_min[1] > epsiron
            normal = Point3( 1.0, 0.0, 0.0 )
            adj = extstiff * diff_min[1] - extdamp * dot( normal, particle.velocity )
            accel += adj * normal
        end
        if diff_max[1] > epsiron
            normal = Point3( -1.0, 0.0, 0.0 )
            adj = extstiff * diff_max[1] - extdamp * dot( normal, particle.velocity )
            accel += adj * normal
        end
        
        # Y
        if diff_min[2] > epsiron
            normal = Point3( 0.0, 1.0, 0.0 )
            adj = extstiff * diff_min[2] - extdamp * dot( normal, particle.velocity )
            accel += adj * normal
        end
        if diff_max[2] > epsiron
            normal = Point3( 0.0, -1.0, 0.0 )
            adj = extstiff * diff_max[2] - extdamp * dot( normal, particle.velocity )
            accel += adj * normal
        end
        
        accel += accel_g

        veloc = particle.velocity
        veloc = veloc .+ accel * DT

        posit = particle.position
        posit = posit .+ veloc * DT / simscale
        particle.position = posit
        particle.velocity = veloc
    end
    positions = map(particle_list) do particle
        particle.position
    end
    positions
end

function create_mesh(nloop)
    println("nloop ", nloop)

    DT = 0.004
    positions = Simulation(DT)

    function sdfunc(v)
        sum = 0.0
        map(positions) do position
            distance = norm(v - position) * simscale
            sum += w_rho(distance, h)
        end
        # density
        density = sum * mass * w_rho_coef
        density - rest_density
    end

    m1 = Mesh(sdfunc, Rect(Vec(-2.,-2,-2),Vec(14.,24,20)),
            MarchingCubes(), samples=(20,20,20))
end

### Mesh settings
color = RGBA{Float32}(0.0, 0.84, 1.0, 0.7)
material = MeshPhongMaterial(color = color)

N = 200

for i = 1:N    
    m = create_mesh(i)

    m_vertices = decompose(Point{3, Float32}, m)
    m_vertices .*= 0.1

    ### display Mesh
    setobject!(vis, m, material)

    trans = Translation(0., -1, 0)
    rot = LinearMap(RotYZ(0.2, -0.2))
    transform = compose(rot, trans)
    settransform!(vis, transform)
    
    #sleep(0.01)
    #Base.prompt("continue?") # for capture
end

2. povrayによる描画
 povファイルは、「粒子法入門 越塚他著」にあるpovファイルを参考にして
います。

meshデータの生成
meshデータ(vertex_vectorsとface_indices)は以下のようにして生成します。

### create pov file
function writeData(i, mesh)
    string1 = ["#include \"colors.inc\"", "#include \"glass.inc\"", "",
    "global_settings{max_trace_level 20}",
    "camera{ location <-2.5, 0.7, -2.5> look_at <0.5, 0.5, -1.5>}",
    "light_source{ <0.5, 1, 0.25> color rgb<1, 1, 1>}",
    "sky_sphere{",
    "   pigment{", "      gradient y",
    "      color_map{",
    "         [0.0 color rgb<1.0,1.0,1.0>*0.5]",
    "         [0.2 color rgb<1.0,1.0,1.0>*0.2]", "      }", "   }", "}", "",
    "plane { y, 0.0",
    "   texture {",
    "      pigment{checker color White color Gray70 scale 0.2}", "   }",
    "   finish { ambient 0.5}", "}", "", "mesh2{"]

    string2 = ["   rotate x*(-90)",
        "   texture { T_Glass3 } interior{ I_Glass ior 1.33 }", "}"]

    filename = string("out", lpad(i,4,"0"), ".pov")
    out = open(filename, "w")
    # strin1 (pre)
    foreach(line -> println(out, line), string1)

    # vertices
    vertices = decompose(Point{3, Float32}, mesh)
    println(out, "vertex_vectors { ", length(vertices), ",")
    foreach(vertex -> println(out, "<", vertex[1], ",", vertex[2], ",", vertex[3], ">"),
        vertices)
    println(out, "}")
    
    # face indices
    ### notice
    ### for i = 1:length(faces)
    ### => Parse Error: Mesh face index out of range.
    ### => "for i = 1:(length(faces) - 1)"
    faces = decompose(TriangleFace{Int}, mesh)
    println(out, "face_indices { ", length(faces) - 1, ",")
    for i = 1:(length(faces) - 1)
        println(out, "<", faces[i][1], ",", faces[i][2], ",", faces[i][3], ">")
    end
    println(out, "}")

    # strin2 (post)
    foreach(line -> println(out, line), string2)

    close(out)
end

このwrireData関数を上のプログラムに追加し、
m_vertices .*= 0.1 の下で、writeData(i, m) を実行します。

WebGPU WGSL 仕様 update

最近のWebGPUとWGSLの仕様 update により、前回プログラムにエラーが出て表示できなくなっていました。WebGPUでは、ワーニングが出ます。

Error : WGSL
E1: a compute shader must include 'workgroup_size' in its attributes
fn main([[builtin(global_invocation_id)]] GlobalInvocationID : vec3<u32>) {
修正
[[stage(compute)]] -> [[stage(compute), workgroup_size(1)]]

E2: expected decoration
var<storage> particle: [[access(read_write)]] Particles;
修正
var<storage, read_write> particle: Particles;

storage buffer に関して、 accessモードの書き方が変更になっています。

Warning : WebGPU
configureSwapChain() is deprecated.
Use configure() instead and call getCurrentTexture() directly on the context.
Note that configure() must also be called if you want to change the size of
the textures returned by getCurrentTexture()
修正
var swapChain = context.configureSwapChain({
 -> context.configure({
swapChain.getCurrentTexture().createView();
 -> context.getCurrentTexture().createView();

WebGPU SPH シミュレーション(3)

最近、W3C が WebGPUとWGSL仕様書の published version を出すようになりました。

(6月7日修正)
WGSL の最近の仕様変更により、4月26日付のプログラム「WebGPU SPH シミュレーション(2)」は実行できなくなりました。
以下のようなエラーとワーニングが出ます。
前回は、compute shader の変更でした。今回は vertex shader と fragment shader
の仕様が整備されています。

vertex shader
Warning:
1 use of deprecated language feature: use an entry point parameter
   instead of a variable in the `in` storage class
     [[location(0)]] var<in> vertexPosition : vec3<f32>;
2 use of deprecated language feature: use an entry point parameter
   instead of a variable in the `in` storage class
     [[location(2)]] var<in> position : vec3<f32>;
3 use of deprecated language feature: use an entry point return value
   instead of a variable in the `out` storage class
     [[builtin(position)]] var<out> Position : vec4<f32>;
Error:
1 unable to determine function return type
     fn main() -> void {
2 expected '}'
     const scale : f32 = 0.005;

fragment shader
Warning:
1 use of deprecated language feature: use an entry point return value
   instead of a variable in the `out` storage class
     [[location(0)]] var<out> outColor : vec4<f32>;
Error:
1 unable to determine function return type
     fn main() -> void {

修正
(前回同様、WGSL のワーニングは、エラーを修正すると表示されなくなります。)

vertex shader
1 Warning1, 2
   [[location(0)]] var<in> vertexPosition : vec3<f32>;
   [[location(2)]] var<in> position : vec3<f32>;
   -> fn main([[location(0)]] vertexPosition: vec3<f32>, [[location(2)]] position: vec3<f32>)
      in変数は、entory point の引数として設定する。
2 Error1(Warning3 と関連)
   [[builtin(position)]] var<out> Position : vec4<f32>;
   fn main() -> void {
   -> fn main() -> [[builtin(position)]] vec4<f32> {

   Position = uniforms.proj_view * scaleMTX * vec4<f32>(vertexPosition, 1.0);
   return;
   -> var Position: vec4<f32> = uniforms.proj_view * scaleMTX *
          vec4<f32>(vertexPosition, 1.0);
      return Position;
3 Error2
   const scale : f32 = 0.005; -> let scale : f32 = 0.005;

fragment shader
1 Error1(Warning1 と関連)
   [[location(0)]] var<out> outColor : vec4<f32>; 
   fn main() ->  void {
   -> fn main() ->  [[location(0)]] vec4<f32> {

   outColor = vec4<f32>(1.0, 0.0, 0.0, 1.0);
   return;
   -> return vec4<f32>(1.0, 0.0, 0.0, 1.0);

プログラム
vertex shader と fragment shader のみ記載

// render shader
const renderShaders = {
    vertex:`
    [[block]] struct Uniforms {
        proj_view : mat4x4<f32>;
    };

    [[binding(0), group(0)]] var<uniform> uniforms : Uniforms;

    [[stage(vertex)]]
    fn main([[location(0)]] vertexPosition: vec3<f32>, [[location(2)]] position: vec3<f32>)
        -> [[builtin(position)]] vec4<f32> {
        let scale : f32 = ${POINT_SIZE};
        var scaleMTX : mat4x4<f32> = mat4x4<f32>(
            vec4<f32>(scale, 0.0,   0.0,   0.0),
            vec4<f32>(0.0,   scale, 0.0,   0.0),
            vec4<f32>(0.0,   0.0,   scale, 0.0),
            vec4<f32>(position, 1.0)
        );

        var Position: vec4<f32> = uniforms.proj_view * scaleMTX * vec4<f32>(vertexPosition, 1.0);
        return Position;
    }`,

    fragment:`
    [[stage(fragment)]]
    fn main() ->  [[location(0)]] vec4<f32> {
        return vec4<f32>(1.0, 0.0, 0.0, 1.0);
    }`
    };

Babylon.js WebGPU Fluid MAC ( Chrome Canary )

Babylon.js WebGPU で流体シミュレーションを行なってみました。

計算には、MAC法を用いています。
以下のサイトと本を参考にしました。
1 「数学とか語学とか楽しいよね」
 【Navier-Stokes方程式MAC法によるNavier-Stokes方程式の離散化
  https://mathlang.hatenablog.com/entry/2018/11/14/001001
 【差分法】MAC法で中心差分を用いてNavier-Stokes方程式を解きました
    C++コード付き
2 「流れ解析(東京工芸大学)」
  http://www.cs.t-kougei.ac.jp/nsim/study/flow.htm
  速度-圧力法でキャビティ流れ
3 酒井幸市著「WebGLによる「流れ」と「波」のシミュレーション」( 工学社 )

上のサイト等には、Navier-Stokes方程式MAC法、差分法について詳しく
書いてありますので、ここでは式等は省略しています。
また、実際のプログラム記述には、サンプルプログラムが大変参考になります。
WebGPUのプログラムより、300行ほど短く書けています。

実行結果
f:id:onagat12:20210529193629g:plain

プログラム
Fluid-MAC-2d.html

<!DOCTYPE html>
<html>
<head>
    <title>Babylon.js WebGPU Fluid MAC 2D</title>
    <script src="https://preview.babylonjs.com/babylon.js"></script>
</head>
<body>
<canvas id="renderCanvas" width="400" height="400"></canvas>

<script>
async function init() {
    const canvas = document.getElementById("renderCanvas");
    const engine = new BABYLON.WebGPUEngine(canvas);
    await engine.initAsync();

    const deltaT = 0.002;
    const Re = 100.0;

    const left0 = [-0.8, -0.8];
    const scale0 = 1.4;

    const NX = 21;
    const NY = 21;
    const DX = 1.0 / (NX - 1);
    const DY = 1.0 / (NY - 1);

    const nGridX = NX + 1;
    const nGridY = NY + 1;
    const gridNum = nGridX * nGridY;
    const velX_nGridX = NX + 2;
    const velX_nGridY = NY + 1;
    const velX_gridNum = velX_nGridX * velX_nGridY;
    const velY_nGridX = NX + 1;
    const velY_nGridY = NY + 2;
    const velY_gridNum = velY_nGridX * velY_nGridY;
    const DX2 = DX * DX;
    const DY2 = DY * DY;
    const C1 = 0.5 * DY2 / (DX2 + DY2);
    const C2 = 0.5 * DX2 / (DX2 + DY2);
    const C3 = 0.5 * DY2 / (1.0 + DY2/DX2);

    const numParticles = (NX-1)*(NY-1);

    var createScene = function () {
        var scene = new BABYLON.Scene(engine);

        var camera = new BABYLON.ArcRotateCamera("camera",
            -Math.PI / 2, Math.PI / 2, 10, BABYLON.Vector3.Zero(), scene);
        camera.setTarget(BABYLON.Vector3.Zero());
        camera.attachControl(canvas, true);

        const sim = new FluidSim(numParticles, scene);

        scene.onBeforeRenderObservable.add(() => {
            sim.update();
        });

        return scene;
    };

    class FluidSim {

        constructor(numParticles, scene) {
            const engine = scene.getEngine();

            this.numParticles = numParticles;

            const pointMesh = BABYLON.MeshBuilder.CreatePlane("plane", { size: 1 }, scene);

            this.mesh = pointMesh;
            pointMesh.forcedInstanceCount = numParticles;

            const mat = new BABYLON.ShaderMaterial("mat", scene, { 
                vertexSource: renderShader.point_vs,
                fragmentSource: renderShader.point_fs,
            }, {
                attributes: ["a_pos", "a_particlePos"]
            });

            pointMesh.material = mat;

            const side = 0.02;
            const vertex = [
                -side, -side,
                 side, -side,
                 side,  side,
                -side,  side
            ];
            const buffSpriteVertex = new BABYLON.VertexBuffer(
                engine,
                vertex, "a_pos", false, false, 2, false
            );
            
            pointMesh.setIndices([0, 1, 2, 2, 3, 0]);
            pointMesh.setVerticesBuffer(buffSpriteVertex);

            const initialParticleData = new Float32Array(numParticles * 4);
            var k = 0;
            for (let j = 1; j < NY; j++)
            for (let i = 1; i < NX; i++) {
                // position
                initialParticleData[4 * k + 0] = i * DX;
                initialParticleData[4 * k + 1] = j * DY;
                // velocity
                initialParticleData[4 * k + 2] = 0.0;
                initialParticleData[4 * k + 3] = 0.0;

                k++;
            }

            this.particleBuffers = new BABYLON.StorageBuffer(
                engine,
                initialParticleData.byteLength,
                BABYLON.Constants.BUFFER_CREATIONFLAG_VERTEX | BABYLON.Constants.BUFFER_CREATIONFLAG_WRITE
            );
            this.particleBuffers.update(initialParticleData);

            var gridXVelocity = new Float32Array( velX_gridNum );
            for (let i = 0; i < gridXVelocity.length; i++) {
                gridXVelocity[i] = 0.0;
            }
            var gridYVelocity = new Float32Array( velY_gridNum );
            for (let i = 0; i < gridYVelocity.length; i++) {
                gridYVelocity[i] = 0.0;
            }

            var gridVelocity = new Float32Array( gridNum * 2 );
            for (let i = 0; i < gridVelocity.length; i++) {
                gridVelocity[i] = 0.0;
            }

            var poisRHS = new Float32Array( gridNum );
            for (let i = 0; i < poisRHS.length; i++) {
                poisRHS[i] = 0.0;
            }

            var gridPres = new Float32Array( gridNum );
            for (let i = 0; i < gridPres.length; i++) {
                gridPres[i] = 0.0;
            }

            this.gridXVelocityBuffers = new BABYLON.StorageBuffer(
                engine,
                gridXVelocity.byteLength,
                BABYLON.Constants.BUFFER_CREATIONFLAG_VERTEX | BABYLON.Constants.BUFFER_CREATIONFLAG_WRITE
            );
            this.gridXVelocityBuffers.update(gridXVelocity);

            this.gridYVelocityBuffers = new BABYLON.StorageBuffer(
                engine,
                gridYVelocity.byteLength,
                BABYLON.Constants.BUFFER_CREATIONFLAG_VERTEX | BABYLON.Constants.BUFFER_CREATIONFLAG_WRITE
            );
            this.gridYVelocityBuffers.update(gridYVelocity);
    
            this.gridVelocityBuffers = new BABYLON.StorageBuffer(
                engine,
                gridVelocity.byteLength,
                BABYLON.Constants.BUFFER_CREATIONFLAG_VERTEX | BABYLON.Constants.BUFFER_CREATIONFLAG_WRITE
            );
            this.gridVelocityBuffers.update(gridVelocity);

            this.poisRHSBuffers = new BABYLON.StorageBuffer(
                engine,
                poisRHS.byteLength,
                BABYLON.Constants.BUFFER_CREATIONFLAG_VERTEX | BABYLON.Constants.BUFFER_CREATIONFLAG_WRITE
            );
            this.poisRHSBuffers.update(poisRHS);

            this.gridPresBuffers = new BABYLON.StorageBuffer(
                engine,
                gridPres.byteLength,
                BABYLON.Constants.BUFFER_CREATIONFLAG_VERTEX | BABYLON.Constants.BUFFER_CREATIONFLAG_WRITE
            );
            this.gridPresBuffers.update(gridPres);

            this.vertexBuffers = new BABYLON.VertexBuffer(
                engine,
                this.particleBuffers.getBuffer(),
                "a_particlePos", false, false, 4, true, 0, 2
            );

            // compute shader settings
            // gridBC
            this.cs_gridBC = new BABYLON.ComputeShader("compute", engine, {
                computeSource: computeShader.gridBC
            }, { 
                bindingsMapping: {
                    "gridXVelocity": { group: 0, binding: 0 },
                    "gridYVelocity": { group: 0, binding: 1 },
                }
            });
            this.cs_gridBC.setStorageBuffer("gridXVelocity", this.gridXVelocityBuffers);
            this.cs_gridBC.setStorageBuffer("gridYVelocity", this.gridYVelocityBuffers);

            // poisRHS
            this.cs_poisRHS = new BABYLON.ComputeShader("compute", engine, {
                computeSource: computeShader.poisRHS
            }, { 
                bindingsMapping: {
                    "gridXVelocity": { group: 0, binding: 0 },
                    "gridYVelocity": { group: 0, binding: 1 },
                    "poisRHS":  { group: 0, binding: 2 },
                }
            });
            this.cs_poisRHS.setStorageBuffer("gridXVelocity", this.gridXVelocityBuffers);
            this.cs_poisRHS.setStorageBuffer("gridYVelocity", this.gridYVelocityBuffers);
            this.cs_poisRHS.setStorageBuffer("poisRHS", this.poisRHSBuffers);

            // presBC
            this.cs_presBC = new BABYLON.ComputeShader("compute", engine, {
                computeSource: computeShader.presBC
            }, { 
                bindingsMapping: {
                    "gridXVelocity": { group: 0, binding: 0 },
                    "gridYVelocity": { group: 0, binding: 1 },
                    "gridPres":  { group: 0, binding: 2 },
                }
            });
            this.cs_presBC.setStorageBuffer("gridXVelocity", this.gridXVelocityBuffers);
            this.cs_presBC.setStorageBuffer("gridYVelocity", this.gridYVelocityBuffers);
            this.cs_presBC.setStorageBuffer("gridPres", this.gridPresBuffers);
            
            // poisson (gridPres)
            this.cs_poisson = new BABYLON.ComputeShader("compute", engine, {
                computeSource: computeShader.poisson
            }, { 
                bindingsMapping: {
                    "poisRHS": { group: 0, binding: 0 },
                    "gridPres": { group: 0, binding: 1 },
                }
            });
            this.cs_poisson.setStorageBuffer("poisRHS", this.poisRHSBuffers);
            this.cs_poisson.setStorageBuffer("gridPres", this.gridPresBuffers);

            // update XVel
            this.cs_updateXVel = new BABYLON.ComputeShader("compute", engine, {
                computeSource: computeShader.updateXVel
            }, { 
                bindingsMapping: {
                    "gridXVelocity": { group: 0, binding: 0 },
                    "gridYVelocity": { group: 0, binding: 1 },
                    "gridPres":  { group: 0, binding: 2 },
                }
            });
            this.cs_updateXVel.setStorageBuffer("gridXVelocity", this.gridXVelocityBuffers);
            this.cs_updateXVel.setStorageBuffer("gridYVelocity", this.gridYVelocityBuffers);
            this.cs_updateXVel.setStorageBuffer("gridPres", this.gridPresBuffers);

            // update YVel
            this.cs_updateYVel = new BABYLON.ComputeShader("compute", engine, {
                computeSource: computeShader.updateYVel
            }, { 
                bindingsMapping: {
                    "gridXVelocity": { group: 0, binding: 0 },
                    "gridYVelocity": { group: 0, binding: 1 },
                    "gridPres":  { group: 0, binding: 2 },
                }
            });
            this.cs_updateYVel.setStorageBuffer("gridXVelocity", this.gridXVelocityBuffers);
            this.cs_updateYVel.setStorageBuffer("gridYVelocity", this.gridYVelocityBuffers);
            this.cs_updateYVel.setStorageBuffer("gridPres", this.gridPresBuffers);

            // gridVelocity
            this.cs_gridVel = new BABYLON.ComputeShader("compute", engine, {
                computeSource: computeShader.gridVelocity
            }, { 
                bindingsMapping: {
                    "gridXVelocity": { group: 0, binding: 0 },
                    "gridYVelocity": { group: 0, binding: 1 },
                    "gridVelocity":  { group: 0, binding: 2 },
                }
            });
            this.cs_gridVel.setStorageBuffer("gridXVelocity", this.gridXVelocityBuffers);
            this.cs_gridVel.setStorageBuffer("gridYVelocity", this.gridYVelocityBuffers);
            this.cs_gridVel.setStorageBuffer("gridVelocity", this.gridVelocityBuffers);

            // getParticleVelocity
            this.cs_getPVel = new BABYLON.ComputeShader("compute", engine, {
                computeSource: computeShader.getParticleVelocity
            }, { 
                bindingsMapping: {
                    "particle": { group: 0, binding: 0 },
                    "gridVelocity": { group: 0, binding: 1 },
                }
            });
            this.cs_getPVel.setStorageBuffer("particle", this.particleBuffers);
            this.cs_getPVel.setStorageBuffer("gridVelocity", this.gridVelocityBuffers);

            // integrate
            this.cs_integrate = new BABYLON.ComputeShader("compute", engine, {
                computeSource: computeShader.integrate
            }, { 
                bindingsMapping: {
                    "particle": { group: 0, binding: 0 },
                }
            });
            this.cs_integrate.setStorageBuffer("particle", this.particleBuffers);
        }

        update() {
            this.cs_gridBC.dispatch(velX_gridNum);
            this.cs_poisRHS.dispatch((NX-1)*(NY-1));
            for (let k = 0; k < 10; k++) {
                this.cs_presBC.dispatch(nGridX);
                this.cs_poisson.dispatch((NX-1)*(NY-1));
            }
            this.cs_updateXVel.dispatch((NX-2)*(NY-1));
            this.cs_updateYVel.dispatch((NX-1)*(NY-2));
            this.cs_gridVel.dispatch(gridNum);
            this.cs_getPVel.dispatch(this.numParticles);
            this.cs_integrate.dispatch(this.numParticles);
            this.mesh.setVerticesBuffer(this.vertexBuffers, false);
        }
    }

    const renderShader = {
    point_vs:`
    attribute vec2 a_pos;
    attribute vec2 a_particlePos;

    const float scale0 = ${scale0};
    const vec2 left0 = vec2(${left0[0]}, ${left0[1]});
    
    void main() {
        vec2 position0 = vec2(
            left0.x + a_particlePos.x * scale0,
            left0.y + a_particlePos.y * scale0
        );
        mat4 scaleMTX = mat4(
            1.0,         0.0,         0.0, 0.0,
            0.0,         1.0,         0.0, 0.0,
            0.0,         0.0,         1.0, 0.0,
            position0.x, position0.y, 0.0, 1.0
        );

        gl_Position = scaleMTX * vec4(a_pos, 0.0, 1.0);
    }`,

    point_fs:`
    void main() {
        gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }`
    };

    const computeShader = {
    gridBC:`
    [[block]] struct GridXVelBuffer {
        gridXVel: array<f32>;
    };
    [[group(0), binding(0)]]
    var<storage> gridXVelocity: [[access(read_write)]] GridXVelBuffer;
    
    [[block]] struct GridYVelBuffer {
        gridYVel: array<f32>;
    };
    [[group(0), binding(1)]]
    var<storage> gridYVelocity: [[access(read_write)]] GridYVelBuffer;
    
    [[stage(compute)]]
    fn main([[builtin(global_invocation_id)]] GlobalInvocationID : vec3<u32>) {
        let velX_nGridX: u32 = ${velX_nGridX}u;
        let velY_nGridX: u32 = ${velY_nGridX}u;
        let NX: u32 = ${NX}u;
        let NY: u32 = ${NY}u;
        let vxwall: f32 = 1.0;

        var index: u32 = GlobalInvocationID.x;
        if (index >= ${velX_gridNum}u) {
            return;
        }
        
        // velX
        var I: u32 = index % velX_nGridX;
        var j: u32 = index / velX_nGridX;
        // velY
        var i: u32 = index % velY_nGridX;
        var J: u32 = index / velY_nGridX;

        // 左
        // XVel
        gridXVelocity.gridXVel[ 1u + j * velX_nGridX ] = 0.0;
        gridXVelocity.gridXVel[ 0u + j * velX_nGridX ] =
            gridXVelocity.gridXVel[ 2u + j * velX_nGridX ];
        // YVel
        gridYVelocity.gridYVel[ 0u + J * velY_nGridX ] =
            -gridYVelocity.gridYVel[ 1u + J * velY_nGridX ];
        gridYVelocity.gridYVel[ 0u + (NY+1u) * velY_nGridX ] =
            -gridYVelocity.gridYVel[ 1u + (NY+1u) * velY_nGridX ];
        
        // 右
        // XVel
        gridXVelocity.gridXVel[ NX + j * velX_nGridX ] = 0.0;
        gridXVelocity.gridXVel[ (NX + 1u) + j * velX_nGridX ] =
            gridXVelocity.gridXVel[ (NX - 1u) + j * velX_nGridX ];
        // YVel
        gridYVelocity.gridYVel[ NX + J * velY_nGridX ] =
            -gridYVelocity.gridYVel[ (NX - 1u) + J * velY_nGridX ];
        gridYVelocity.gridYVel[ NX + (NY + 1u) * velY_nGridX ] =
            -gridYVelocity.gridYVel[ (NX - 1u) + (NY + 1u) * velY_nGridX ];

        // 下
        // YVel
        gridYVelocity.gridYVel[ i + 1u * velY_nGridX ] = 0.0;
        gridYVelocity.gridYVel[ i + 0u * velY_nGridX ] =
            gridYVelocity.gridYVel[ i + 2u * velY_nGridX ];
        // XVel
        gridXVelocity.gridXVel[ I + 0u * velX_nGridX ] =
            -gridXVelocity.gridXVel[ I + 1u * velX_nGridX ];
        gridXVelocity.gridXVel[ (NX + 1u) + 0u * velX_nGridX ] =
            -gridXVelocity.gridXVel[ NX + 0u * velX_nGridX ];
        
        // 上
        // YVel
        gridYVelocity.gridYVel[ i + NY * velY_nGridX ] = 0.0;
        gridYVelocity.gridYVel[ i + (NY + 1u) * velY_nGridX ] =
            gridYVelocity.gridYVel[ i + (NY - 1u) * velY_nGridX ];
        // XVel
        gridXVelocity.gridXVel[ I + NY * velX_nGridX ] =
            2.0 * vxwall - gridXVelocity.gridXVel[ I + (NY - 1u) * velX_nGridX ];
        gridXVelocity.gridXVel[ (NX + 1u) + NY * velX_nGridX ] =
            -gridXVelocity.gridXVel[ NX + NY * velX_nGridX ];
    }`,

    poisRHS:`
    [[block]] struct GridXVelBuffer {
        gridXVel: array<f32>;
    };
    [[group(0), binding(0)]]
    var<storage> gridXVelocity: [[access(read_write)]] GridXVelBuffer;
    
    [[block]] struct GridYVelBuffer {
        gridYVel: array<f32>;
    };
    [[group(0), binding(1)]]
    var<storage> gridYVelocity: [[access(read_write)]] GridYVelBuffer;
    
    [[block]] struct PoisRHSBuffer {
        prhs: array<f32>;
    };
    [[group(0), binding(2)]]
    var<storage> poisRHS: [[access(read_write)]] PoisRHSBuffer;
    
    [[stage(compute)]]
    fn main([[builtin(global_invocation_id)]] GlobalInvocationID : vec3<u32>) {
        let velX_nGridX: u32 = ${velX_nGridX}u;
        let velY_nGridX: u32 = ${velY_nGridX}u;
        let NX: u32 = ${NX}u;
        let NY: u32 = ${NY}u;
        let DX: f32 = ${DX};
        let DY: f32 = ${DY};
        let deltaT: f32 = ${deltaT};

        var index: u32 = GlobalInvocationID.x;
        // dispatch (NX-1)*(NY-1)
        if (index >= (NX - 1u)*(NY - 1u)) {
            return;
        }
        
        // index i, j
        var ii: u32 = index % (NX - 1u);
        var jj: u32 = index / (NX - 1u);
        var i: u32 = ii + 1u;
        var j: u32 = jj + 1u;

        // velX
        var XIndexIJ: u32   = i + j * velX_nGridX;
        var XIndexIPJ: u32  = (i + 1u) + j * velX_nGridX;
        var XIndexIPJP: u32 = (i + 1u) + (j + 1u) * velX_nGridX;
        var XIndexIJP: u32  = i + (j + 1u) * velX_nGridX;
        var XIndexIJM: u32  = i + (j - 1u) * velX_nGridX;
        var XIndexIPJM: u32 = (i + 1u) + (j - 1u) * velX_nGridX;

        // velY
        var YIndexIJ: u32   = i + j * velY_nGridX;
        var YIndexIJP: u32  = i + (j + 1u) * velY_nGridX;
        var YIndexIPJ: u32  = (i + 1u) + j * velY_nGridX;
        var YIndexIPJP: u32 = (i + 1u) + (j + 1u) * velY_nGridX;
        var YIndexIMJ: u32  = (i - 1u) + j * velY_nGridX;
        var YIndexIMJP: u32 = (i - 1u) + (j + 1u) * velY_nGridX;

        var udiv: f32 = (gridXVelocity.gridXVel[XIndexIPJ] - gridXVelocity.gridXVel[XIndexIJ]) / DX;
        var vdiv: f32 = (gridYVelocity.gridYVel[YIndexIJP] - gridYVelocity.gridYVel[YIndexIJ]) / DY;

        var ua: f32 = (gridXVelocity.gridXVel[XIndexIJ] + gridXVelocity.gridXVel[XIndexIPJ]
                + gridXVelocity.gridXVel[XIndexIPJP] + gridXVelocity.gridXVel[XIndexIJP]) / 4.0;
        var ub: f32 = (gridXVelocity.gridXVel[XIndexIJ] + gridXVelocity.gridXVel[XIndexIPJ]
                + gridXVelocity.gridXVel[XIndexIPJM] + gridXVelocity.gridXVel[XIndexIJM]) / 4.0;
        var va: f32 = (gridYVelocity.gridYVel[YIndexIJ] + gridYVelocity.gridYVel[YIndexIJP]
                + gridYVelocity.gridYVel[YIndexIPJP] + gridYVelocity.gridYVel[YIndexIPJ]) / 4.0;
        var vb: f32 = (gridYVelocity.gridYVel[YIndexIJ] + gridYVelocity.gridYVel[YIndexIJP]
                + gridYVelocity.gridYVel[YIndexIMJP] + gridYVelocity.gridYVel[YIndexIMJ]) / 4.0;

        poisRHS.prhs[i + j * (NX + 1u)] = -udiv*udiv - 2.0*(ua - ub)*(va - vb)/DX/DY
            -vdiv*vdiv + (udiv + vdiv) / deltaT;
    }`,

    presBC:`
    [[block]] struct GridXVelBuffer {
        gridXVel: array<f32>;
    };
    [[group(0), binding(0)]]
    var<storage> gridXVelocity: [[access(read_write)]] GridXVelBuffer;
    
    [[block]] struct GridYVelBuffer {
        gridYVel: array<f32>;
    };
    [[group(0), binding(1)]]
    var<storage> gridYVelocity: [[access(read_write)]] GridYVelBuffer;
    
    [[block]] struct GridPres {
        pres: array<f32>;
    };
    [[group(0), binding(2)]]
    var<storage> gridPres: [[access(read_write)]] GridPres;
    
    [[stage(compute)]]
    fn main([[builtin(global_invocation_id)]] GlobalInvocationID : vec3<u32>) {
        let velX_nGridX: u32 = ${velX_nGridX}u;
        let velY_nGridX: u32 = ${velY_nGridX}u;
        let nGridX: u32 = ${nGridX}u;
        let NX: u32 = ${NX}u;
        let NY: u32 = ${NY}u;
        let DX: f32 = ${DX};
        let DY: f32 = ${DY};
        let Re: f32 = f32(${Re});

        var index: u32 = GlobalInvocationID.x;
        // dispatch index i or j , nGridX(= NX + 1 )
        if (index >= nGridX) {
            return;
        }
        
        // 左右 index = j
        var j: u32 = index;
        gridPres.pres[0u + j * nGridX] = gridPres.pres[1u + j * nGridX]
            - 1.0/Re * 2.0 * gridXVelocity.gridXVel[2u + j * velX_nGridX] / DX;

        gridPres.pres[NX + j * nGridX] = gridPres.pres[(NX - 1u) + j * nGridX]
            + 1.0/Re * 2.0 * gridXVelocity.gridXVel[(NX - 1u) + j * velX_nGridX] / DX;

        // 上下 index = i
        var i: u32 = index;
        gridPres.pres[i + 0u * nGridX] = gridPres.pres[i + 1u * nGridX]
            - 1.0/Re * 2.0 * gridYVelocity.gridYVel[i + 2u * velY_nGridX] / DY;
        gridPres.pres[i + NY * nGridX] = gridPres.pres[i + (NY - 1u) * nGridX]
            + 1.0/Re * 2.0 * gridYVelocity.gridYVel[i + (NY - 1u) * velY_nGridX] / DY;
    }`,

    poisson:`
    [[block]] struct PoisRHSBuffer {
        prhs: array<f32>;
    };
    [[group(0), binding(0)]]
    var<storage> poisRHS: [[access(read_write)]] PoisRHSBuffer;

    [[block]] struct GridPres {
        pres: array<f32>;
    };
    [[group(0), binding(1)]]
    var<storage> gridPres: [[access(read_write)]] GridPres;
    
    [[stage(compute)]]
    fn main([[builtin(global_invocation_id)]] GlobalInvocationID : vec3<u32>) {
        let velX_nGridX: u32 = ${velX_nGridX}u;
        let velY_nGridX: u32 = ${velY_nGridX}u;
        let nGridX: u32 = ${nGridX}u;
        let NX: u32 = ${NX}u;
        let NY: u32 = ${NY}u;
        let DX: f32 = ${DX};
        let DY: f32 = ${DY};
        let Re: f32 = f32(${Re});
        let c1: f32 = ${C1};
        let c2: f32 = ${C2};
        let c3: f32 = ${C3};

        var index: u32 = GlobalInvocationID.x;
        // dispatch (NX-1)*(NY-1)
        if (index >= (NX - 1u)*(NY - 1u)) {
            return;
        }
        
        var ii: u32 = index % (NX - 1u);
        var jj: u32 = index / (NX - 1u);
        var i: u32 = ii + 1u;
        var j: u32 = jj + 1u;

        var pp: f32 = c1 * (gridPres.pres[(i + 1u) + j * nGridX] + gridPres.pres[(i - 1u) + j * nGridX])
            + c2 * (gridPres.pres[i + (j + 1u) * nGridX] + gridPres.pres[i + (j - 1u) * nGridX])
            - c3 * poisRHS.prhs[i + j * nGridX];

        gridPres.pres[i + j * nGridX] = pp;
    }`,

    updateXVel:`
    [[block]] struct GridXVelBuffer {
        gridXVel: array<f32>;
    };
    [[group(0), binding(0)]]
    var<storage> gridXVelocity: [[access(read_write)]] GridXVelBuffer;
    
    [[block]] struct GridYVelBuffer {
        gridYVel: array<f32>;
    };
    [[group(0), binding(1)]]
    var<storage> gridYVelocity: [[access(read_write)]] GridYVelBuffer;
    
    [[block]] struct GridPres {
        pres: array<f32>;
    };
    [[group(0), binding(2)]]
    var<storage> gridPres: [[access(read_write)]] GridPres;
    
    [[stage(compute)]]
    fn main([[builtin(global_invocation_id)]] GlobalInvocationID : vec3<u32>) {
        let velX_nGridX: u32 = ${velX_nGridX}u;
        let velY_nGridX: u32 = ${velY_nGridX}u;
        let nGridX: u32 = ${nGridX}u;
        let deltaT: f32 = ${deltaT};
        let NX: u32 = ${NX}u;
        let NY: u32 = ${NY}u;
        let DX: f32 = ${DX};
        let DY: f32 = ${DY};
        let Re: f32 = f32(${Re});

        var index: u32 = GlobalInvocationID.x;
        // dispatch index (NX-2)*(NY-1)
        // XVel: i=2 ~ NX-1, j=1 ~ NY-1
        if (index >= (NX - 2u)*(NY - 1u)) {
            return;
        }
        
        // XVel
        var i: u32 = index % (NX - 2u) + 2u;
        var j: u32 = index / (NX - 2u) + 1u;

        var YIndexIJ: u32   = i + j * velY_nGridX;
        var YIndexIJP: u32  = i + (j + 1u) * velY_nGridX;
        var YIndexIMJ: u32  = (i - 1u) + j * velY_nGridX;
        var YIndexIMJP: u32 = (i - 1u) + (j + 1u) * velY_nGridX;
        //
        var XIndexIJ: u32  = i + j * velX_nGridX;
        var XIndexIPJ: u32 = (i + 1u) + j * velX_nGridX;
        var XIndexIMJ: u32 = (i - 1u) + j * velX_nGridX;
        var XIndexIJP: u32 = i + (j + 1u) * velX_nGridX;
        var XIndexIJM: u32 = i + (j - 1u) * velX_nGridX;

        var vmid: f32 = (gridYVelocity.gridYVel[YIndexIJ] + gridYVelocity.gridYVel[YIndexIJP]
            + gridYVelocity.gridYVel[YIndexIMJP] + gridYVelocity.gridYVel[YIndexIMJ]) / 4.0;
        var uad: f32 = gridXVelocity.gridXVel[XIndexIJ] * (gridXVelocity.gridXVel[XIndexIPJ] - gridXVelocity.gridXVel[XIndexIMJ])/2.0/DX
            + vmid * (gridXVelocity.gridXVel[XIndexIJP] - gridXVelocity.gridXVel[XIndexIJM])/2.0/DY;
        var udif: f32 = (gridXVelocity.gridXVel[XIndexIPJ] - 2.0*gridXVelocity.gridXVel[XIndexIJ] + gridXVelocity.gridXVel[XIndexIMJ])/DX/DX
            + (gridXVelocity.gridXVel[XIndexIJP] - 2.0*gridXVelocity.gridXVel[XIndexIJ] + gridXVelocity.gridXVel[XIndexIJM])/DY/DY;

        gridXVelocity.gridXVel[XIndexIJ] = gridXVelocity.gridXVel[XIndexIJ]
            + deltaT * (-uad
            - (gridPres.pres[i + j * nGridX] - gridPres.pres[(i - 1u) + j * nGridX])/DX
            + 1.0/Re*udif);
    }`,

    updateYVel:`
    [[block]] struct GridXVelBuffer {
        gridXVel: array<f32>;
    };
    [[group(0), binding(0)]]
    var<storage> gridXVelocity: [[access(read_write)]] GridXVelBuffer;
    
    [[block]] struct GridYVelBuffer {
        gridYVel: array<f32>;
    };
    [[group(0), binding(1)]]
    var<storage> gridYVelocity: [[access(read_write)]] GridYVelBuffer;
    
    [[block]] struct GridPres {
        pres: array<f32>;
    };
    [[group(0), binding(2)]]
    var<storage> gridPres: [[access(read_write)]] GridPres;
    
    [[stage(compute)]]
    fn main([[builtin(global_invocation_id)]] GlobalInvocationID : vec3<u32>) {
        let velX_nGridX: u32 = ${velX_nGridX}u;
        let velY_nGridX: u32 = ${velY_nGridX}u;
        let nGridX: u32 = ${nGridX}u;
        let deltaT: f32 = ${deltaT};
        let NX: u32 = ${NX}u;
        let NY: u32 = ${NY}u;
        let DX: f32 = ${DX};
        let DY: f32 = ${DY};
        let Re: f32 = f32(${Re});

        var index: u32 = GlobalInvocationID.x;
        // dispatch index (NX-1)*(NY-2)
        // YVel: i=1 ~ NX-1, j=2 ~ NY-1
        if (index >= (NX - 1u)*(NY - 2u)) {
            return;
        }
        
        // YVel
        var i: u32 = index % (NX - 1u) + 1u;
        var j: u32 = index / (NX - 1u) + 2u;

        var XIndexIJ: u32   = i + j * velX_nGridX;
        var XIndexIPJ: u32  = (i + 1u) + j * velX_nGridX;
        var XIndexIJM: u32  = i + (j - 1u) * velX_nGridX;
        var XIndexIPJM: u32 = (i + 1u) + (j - 1u) * velX_nGridX;

        var YIndexIJ: u32  = i + j * velY_nGridX;
        var YIndexIPJ: u32 = (i + 1u) + j * velY_nGridX;
        var YIndexIMJ: u32 = (i - 1u) + j * velY_nGridX;
        var YIndexIJP: u32 = i + (j + 1u) * velY_nGridX;
        var YIndexIJM: u32 = i + (j - 1u) * velY_nGridX;

        let umid: f32 = (gridXVelocity.gridXVel[XIndexIJ] + gridXVelocity.gridXVel[XIndexIPJ]
            + gridXVelocity.gridXVel[XIndexIPJM] + gridXVelocity.gridXVel[XIndexIJM]) / 4.0;
        let vad: f32 = umid * (gridYVelocity.gridYVel[YIndexIPJ] - gridYVelocity.gridYVel[YIndexIMJ])/2.0/DX
            + gridYVelocity.gridYVel[YIndexIJ]*(gridYVelocity.gridYVel[YIndexIJP] - gridYVelocity.gridYVel[YIndexIJM])/2.0/DY;
        let vdif: f32 = (gridYVelocity.gridYVel[YIndexIPJ] - 2.0*gridYVelocity.gridYVel[YIndexIJ] + gridYVelocity.gridYVel[YIndexIMJ])/DX/DX
                + (gridYVelocity.gridYVel[YIndexIJP] - 2.0*gridYVelocity.gridYVel[YIndexIJ] + gridYVelocity.gridYVel[YIndexIJM])/DY/DY;
        
        gridYVelocity.gridYVel[YIndexIJ] = gridYVelocity.gridYVel[YIndexIJ]
            + deltaT * ( -vad
            - (gridPres.pres[i + j * nGridX] - gridPres.pres[i + (j - 1u) * nGridX])/DY
            + 1.0/Re*vdif);
    }`,

    gridVelocity:`
    [[block]] struct GridXVelBuffer {
        gridXVel: array<f32>;
    };
    [[group(0), binding(0)]]
    var<storage> gridXVelocity: [[access(read_write)]] GridXVelBuffer;
    
    [[block]] struct GridYVelBuffer {
        gridYVel: array<f32>;
    };
    [[group(0), binding(1)]]
    var<storage> gridYVelocity: [[access(read_write)]] GridYVelBuffer;
    
    struct GridVelocity {
        gridVel: vec2<f32>;
    };
    [[block]] struct GridVelocities {
        gridVelocities: array<GridVelocity, ${gridNum}>;
    };
    [[group(0), binding(2)]]
    var<storage> gridVelocity: [[access(read_write)]] GridVelocities;
    
    [[stage(compute)]]
    fn main([[builtin(global_invocation_id)]] GlobalInvocationID : vec3<u32>) {
        let velX_nGridX: u32 = ${velX_nGridX}u;
        let velY_nGridX: u32 = ${velY_nGridX}u;
        let nGridX: u32 = ${nGridX}u;
        let deltaT: f32 = ${deltaT};
        let NX: u32 = ${NX}u;
        let NY: u32 = ${NY}u;
        let DX: f32 = ${DX};
        let DY: f32 = ${DY};
        let Re: f32 = f32(${Re});

        var index: u32 = GlobalInvocationID.x;
        // dispatch index (NX+1)*(NY+1) gridNum
        if (index >= ${gridNum}u) {
            return;
        }
        
        var i: u32 = index % nGridX;
        var j: u32 = index / nGridX;

        var velXIndexIJ: u32  = i + j * velX_nGridX;
        var velXIndexIPJ: u32 = (i + 1u) + j * velX_nGridX;
        var velYIndexIJ: u32  = i + j * velY_nGridX;
        var velYIndexIJP: u32 = i + (j + 1u) * velY_nGridX;

        gridVelocity.gridVelocities[i + j * nGridX ].gridVel.x =
            (gridXVelocity.gridXVel[velXIndexIJ] + gridXVelocity.gridXVel[velXIndexIPJ]) / 2.0;
        gridVelocity.gridVelocities[i + j * nGridX ].gridVel.y =
            (gridYVelocity.gridYVel[velYIndexIJ] + gridYVelocity.gridYVel[velYIndexIJP]) / 2.0;
    }`,

    getParticleVelocity:`
    struct Particle {
        pos: vec2<f32>;
        vel: vec2<f32>;
    };
    [[block]] struct Particles {
        particles: array<Particle, ${numParticles}>;
    };
    [[group(0), binding(0)]]
    var<storage> particle: [[access(read_write)]] Particles;

    struct GridVelocity {
        gridVel: vec2<f32>;
    };
    [[block]] struct GridVelocities {
        gridVelocities: array<GridVelocity, ${gridNum}>;
    };
    [[group(0), binding(1)]]
    var<storage> gridVelocity: [[access(read_write)]] GridVelocities;

    fn getParticleVelocity(position: vec2<f32>) -> vec2<f32> {
        let NX: u32 = ${NX}u;
        let NY: u32 = ${NY}u;
        let DX: f32 = ${DX};
        let DY: f32 = ${DY};
        let nGridX: u32 = ${nGridX}u;

        var I: u32;
        var J: u32;

        I = 0u;
        J = 0u;

        for (var i: u32 = 0u; i < NX; i = i + 1u) {
            if (f32(i) * DX < position.x && f32(i+1u) * DX > position.x) { I = i; }
        }
        for (var j: u32 = 0u; j < NY; j = j +1u) {
            if (f32(j) * DY < position.y && f32(j+1u) * DY > position.y) { J = j; }
        }

        var a: f32 =  position.x / DX - f32(I);
        var b: f32 =  position.y / DY - f32(J);
        
        var gIndexIJ00: u32 = I + J * nGridX;
        var gIndexIJ10: u32 = (I + 1u) + J * nGridX;
        var gIndexIJ01: u32 = I + (J + 1u) * nGridX;
        var gIndexIJ11: u32 = (I + 1u) + (J + 1u) * nGridX;

        var velXIJ00: f32 = gridVelocity.gridVelocities[ gIndexIJ00 ].gridVel.x;
        var velXIJ10: f32 = gridVelocity.gridVelocities[ gIndexIJ10 ].gridVel.x;
        var velXIJ01: f32 = gridVelocity.gridVelocities[ gIndexIJ01 ].gridVel.x;
        var velXIJ11: f32 = gridVelocity.gridVelocities[ gIndexIJ11 ].gridVel.x;
        var velYIJ00: f32 = gridVelocity.gridVelocities[ gIndexIJ00 ].gridVel.y;
        var velYIJ10: f32 = gridVelocity.gridVelocities[ gIndexIJ10 ].gridVel.y;
        var velYIJ01: f32 = gridVelocity.gridVelocities[ gIndexIJ01 ].gridVel.y;
        var velYIJ11: f32 = gridVelocity.gridVelocities[ gIndexIJ11 ].gridVel.y;

        var vel_x: f32 = (1.0 - b) * ( (1.0 - a) * velXIJ00 + a * velXIJ10 )
                        + b * ( (1.0 - a) * velXIJ01 + a * velXIJ11 );
        var vel_y: f32 = (1.0 - b) * ( (1.0 - a) * velYIJ00 + a * velYIJ10 )
                        + b * ( (1.0 - a) * velYIJ01 + a * velYIJ11 );

        var vel: vec2<f32> = vec2<f32>(
            vel_x,
            vel_y
        );
        return vel;
    }

    [[stage(compute)]]
    fn main([[builtin(global_invocation_id)]] GlobalInvocationID : vec3<u32>) {
        var index: u32 = GlobalInvocationID.x;
        if (index >= ${numParticles}u) {
            return;
        }

        var particlePosition: vec2<f32> = particle.particles[index].pos;
        particle.particles[index].vel = getParticleVelocity(particlePosition);
    }`,
    
    integrate:`
    struct Particle {
        pos : vec2<f32>;
        vel : vec2<f32>;
    };
    [[block]] struct Particles {
        particles : array<Particle, ${numParticles}>;
    };
    [[group(0), binding(0)]]
    var<storage> particle : [[access(read_write)]] Particles;

    [[stage(compute)]]
    fn main([[builtin(global_invocation_id)]] GlobalInvocationID : vec3<u32>) {
        let deltaT: f32 = ${deltaT};
        let DX: f32 = ${DX};
        let DY: f32 = ${DY};

        var index : u32 = GlobalInvocationID.x;
        if (index >= ${numParticles}u) {
            return;
        }

        var vPos : vec2<f32> = particle.particles[index].pos;
        var vVel : vec2<f32> = particle.particles[index].vel;

        vPos = vPos + vVel * deltaT;

        if (vPos.x > 1.0) {
            vPos.x = 1.0 - DX/8.0;
        }
        if (vPos.x < 0.0) {
            vPos.x = 0.0 + DX/8.0;
        }

        particle.particles[index].pos = vPos;
    }`
    };

    const scene = createScene();
    engine.runRenderLoop(() => {
        scene.render();
    });
};

init();
</script>
</body>
</html>

Babylon.js WebGPU Instancing ( Chrome Canary )

Babylon.js WebGPU Compute Shaders examples にある Compute_Boids
を参考にして、instancing にトライしました。

実行結果
f:id:onagat12:20210525195101g:plain

プログラム
instancing.html

<!DOCTYPE html>
<html>
<head>
    <title>Babylon.js WebGPU Instancing</title>
    <script src="https://preview.babylonjs.com/babylon.js"></script>
</head>
<body>
<canvas id="renderCanvas" width="400" height="400"></canvas>

<script>
async function init() {
    const canvas = document.getElementById("renderCanvas");
    const engine = new BABYLON.WebGPUEngine(canvas);
    await engine.initAsync();

    const numParticles = 100;

    var createScene = function () {
        var scene = new BABYLON.Scene(engine);

        var camera = new BABYLON.ArcRotateCamera("camera",
            -Math.PI / 2, Math.PI / 2, 10, BABYLON.Vector3.Zero(), scene);
        camera.setTarget(BABYLON.Vector3.Zero());
        camera.attachControl(canvas, true);

        const point = new Point(numParticles, scene);
        //console.log("point numParticles", point.numParticles);

        scene.onBeforeRenderObservable.add(() => {
            point.update();
        });

        return scene;
    };

    class Point {

        constructor(numParticles, scene) {
            const engine = scene.getEngine();

            this.numParticles = numParticles;

            const pointMesh = BABYLON.MeshBuilder.CreatePlane("plane", { size: 1 }, scene);

            this.mesh = pointMesh;
            pointMesh.forcedInstanceCount = numParticles;

            const mat = new BABYLON.ShaderMaterial("mat", scene, { 
                vertexSource: renderShader.point_vs,
                fragmentSource: renderShader.point_fs,
            }, {
                attributes: ["a_pos", "a_particlePos"]
            });

            pointMesh.material = mat;

            const side = 0.02;
            const vertex = [
                -side, -side,
                 side, -side,
                 side,  side,
                -side,  side
            ];
            const buffSpriteVertex = new BABYLON.VertexBuffer(
                engine,
                vertex, "a_pos", false, false, 2, false
            );
            
            pointMesh.setIndices([0, 1, 2, 2, 3, 0]);
            pointMesh.setVerticesBuffer(buffSpriteVertex);

            const initialParticleData = new Float32Array(numParticles * 4);
            for (let i = 0; i < numParticles; ++i) {
                initialParticleData[4 * i + 0] = 2 * (Math.random() - 0.5);
                initialParticleData[4 * i + 1] = 2 * (Math.random() - 0.5);
                initialParticleData[4 * i + 2] = 2 * (Math.random() - 0.5) * 0.1;
                initialParticleData[4 * i + 3] = 2 * (Math.random() - 0.5) * 0.1;
            }

            this.particleBuffers = new BABYLON.StorageBuffer(
                engine,
                initialParticleData.byteLength,
                BABYLON.Constants.BUFFER_CREATIONFLAG_VERTEX | BABYLON.Constants.BUFFER_CREATIONFLAG_WRITE
            );

            this.particleBuffers.update(initialParticleData);

            this.vertexBuffers = new BABYLON.VertexBuffer(
                engine,
                this.particleBuffers.getBuffer(),
                "a_particlePos", false, false, 4, true, 0, 2
            );

            // compute shader
            this.cs = new BABYLON.ComputeShader("compute", engine, { computeSource: computeShader }, {
                bindingsMapping: {
                    "particle": { group: 0, binding: 0 },
                }
            });
            this.cs.setStorageBuffer("particle", this.particleBuffers);
        }

        update() {
            this.cs.dispatch(this.numParticles);
            this.mesh.setVerticesBuffer(this.vertexBuffers, false);
        }
    }

    const renderShader = {
    point_vs:`
    attribute vec2 a_pos;
    attribute vec2 a_particlePos;
    
    void main() {
        vec2 pos = vec2(a_pos.x, a_pos.y);

        gl_Position = vec4(pos + a_particlePos, 0.0, 1.0);
    }`,

    point_fs:`
    void main() {
        gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }`
    };

    const computeShader = `
    struct Particle {
        pos : vec2<f32>;
        vel : vec2<f32>;
    };
    [[block]] struct Particles {
        particles : array<Particle, ${numParticles}>;
    };
    [[group(0), binding(0)]]
    var<storage> particle : [[access(read_write)]] Particles;

    [[stage(compute)]]
    fn main([[builtin(global_invocation_id)]] GlobalInvocationID : vec3<u32>) {
        let deltaT: f32 = 0.04;

        var index : u32 = GlobalInvocationID.x;
        if (index >= ${numParticles}u) {
            return;
        }

        var vPos : vec2<f32> = particle.particles[index].pos;
        var vVel : vec2<f32> = particle.particles[index].vel;

        vPos = vPos + (vVel * deltaT);

        if (abs(vPos.x) >= 1.0) {
            vVel.x = -vVel.x;
        }
        if (abs(vPos.y) >= 1.0) {
            vVel.y = -vVel.y;
        }

        particle.particles[index].pos = vPos;
        particle.particles[index].vel = vVel;
    }
    `;

    const scene = createScene();
    engine.runRenderLoop(() => {
        scene.render();
    });
};

init();
</script>
</body>
</html>

Babylon.js WebGPU Compute Shader ( Chrome Canary )

Babylon.js のWebGPUで Compute Shader が使えるようになりました。
( v5.0.0-alpha で動作します。)

Compute Shaders | Babylon.js Documentation
https://doc.babylonjs.com/advanced_topics/shaders/computeShader
f:id:onagat12:20210522185026j:plain:w400:h180

この画面からSimple compute shaders example を実行しようとすると、
以下の createStorageBuffer に関するエラーが出て実行できません。
f:id:onagat12:20210522184113p:plain:w400:h160

下のサイトを参考にして、実行できるようにトライしてみました。
・Babylon.js で WebGPU を試してみるテスト - Qiita
 https://qiita.com/cx20/items/e46bd22f0d9e47e81df1
・Babylon.jsでWebGPU試してみる : IZUN∀ TEC - livedoor
 http://blog.livedoor.jp/mizuki_izuna/archives/24967180.html

Playground 画面からプログラムをコピーし、html の script 部分にそまま
貼り付けて実行しました。
実行結果
f:id:onagat12:20210522185522j:plain
f:id:onagat12:20210522185617j:plain
(下: マウスで移動した様子)

コンソール画面
f:id:onagat12:20210522185740j:plain
・compute shader の計算結果が表示されています。
・使用しているライブラリのバージョン( v5.0.0-aplha.22 )も表示されます。

babylon.js 等のライブラリは、preview 版を 使用しています。
f:id:onagat12:20210522185823j:plain:w400:h190
(Babylon.js https://github.com/BabylonJS/Babylon.js の readme)

shader には WGSL が使用できます。

プログラム
Simple_compute_shaders.html

<!DOCTYPE html>
<html>
<head>
    <title>Babylon.js WebGPU Simple Compute Shaders(playground)</title>
    <script src="https://preview.babylonjs.com/babylon.max.js"></script>
    <script src="https://preview.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
</head>
<body>
<canvas id="renderCanvas" width="800" height="650"></canvas>

<script>
async function init() {
    const canvas = document.getElementById("renderCanvas");
    const engine = new BABYLON.WebGPUEngine(canvas);
    await engine.initAsync();

    // from playground
    var createScene = function () {
        // This creates a basic Babylon Scene object (non-mesh)
        var scene = new BABYLON.Scene(engine);

        // This creates and positions a free camera (non-mesh)
        var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);

        // This targets the camera to scene origin
        camera.setTarget(BABYLON.Vector3.Zero());

        // This attaches the camera to the canvas
        camera.attachControl(canvas, true);

        // This creates a light, aiming 0,1,0 - to the sky (non-mesh)
        var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);

        // Default intensity is 1. Let's dim the light a small amount
        light.intensity = 0.7;

        // Our built-in 'sphere' shape.
        var sphere = BABYLON.MeshBuilder.CreateSphere("sphere", { diameter: 2, segments: 32 }, scene);

        // Move the sphere upward 1/2 its height
        sphere.position.y = 1;

        // Our built-in 'ground' shape.
        var ground = BABYLON.MeshBuilder.CreateGround("ground", { width: 6, height: 6 }, scene);

        // -------- COMPUTE 1 -------------------------
        //
        const cs1 = new BABYLON.ComputeShader("myCompute", engine, { computeSource: copyTextureComputeShader }, { bindingsMapping:
            {
                "dest": { group: 0, binding: 0 },
                "src": { group: 0, binding: 2 }
            }
        });

        const src = new BABYLON.Texture("textures/ground.jpg", scene);
        const dest = new BABYLON.RawTexture.CreateRGBAStorageTexture(null, 512, 512, scene, false, false);

        cs1.setTexture("src", src);
        cs1.setStorageTexture("dest", dest);

        cs1.dispatchWhenReady(dest.getSize().width, dest.getSize().height, 1).then(() => {
            dest.readPixels().then((data) => {
                //console.log(data);
            });
        });

        const mat = new BABYLON.StandardMaterial("mat", scene);
        mat.diffuseTexture = dest;

        ground.material = mat;

        // -------- COMPUTE 2 -------------------------
        //
        const cs2 = new BABYLON.ComputeShader("myCompute2", engine, { computeSource: clearTextureComputeShader }, { bindingsMapping:
            {
                "tbuf": { group: 0, binding: 0 },
                "params": { group: 0, binding: 1 }
            }
        });

        const dest2 = new BABYLON.RawTexture.CreateRGBAStorageTexture(null, 512, 512, scene, false, false);

        const uBuffer = new BABYLON.UniformBuffer(engine);

        uBuffer.updateColor4("color", new BABYLON.Color3(1, 0.6, 0.8), 1);
        uBuffer.update();

        cs2.setStorageTexture("tbuf", dest2);
        cs2.setUniformBuffer("params", uBuffer);

        cs2.dispatchWhenReady(dest2.getSize().width, dest2.getSize().height, 1);

        const mat2 = new BABYLON.StandardMaterial("mat2", scene);
        mat2.diffuseTexture = dest2;

        sphere.material = mat2;

        // -------- COMPUTE 3 -------------------------
        //
        const cs3 = new BABYLON.ComputeShader("myCompute3", engine, { computeSource: matrixMulComputeShader }, { bindingsMapping:
            {
                "firstMatrix": { group: 0, binding: 0 },
                "secondMatrix": { group: 0, binding: 1 },
                "resultMatrix": { group: 0, binding: 2 }
            }
        });

        const firstMatrix = new Float32Array([
            2 /* rows */, 4 /* columns */,
            1, 2, 3, 4,
            5, 6, 7, 8
        ]);

        const bufferFirstMatrix = new BABYLON.StorageBuffer(engine, firstMatrix.byteLength);
        bufferFirstMatrix.update(firstMatrix);

        const secondMatrix = new Float32Array([
            4 /* rows */, 2 /* columns */,
            1, 2,
            3, 4,
            5, 6,
            7, 8
        ]);

        const bufferSecondMatrix = new BABYLON.StorageBuffer(engine, secondMatrix.byteLength);
        bufferSecondMatrix.update(secondMatrix);

        const bufferResultMatrix = new BABYLON.StorageBuffer(engine, Float32Array.BYTES_PER_ELEMENT * (2 + firstMatrix[0] * secondMatrix[1]));

        cs3.setStorageBuffer("firstMatrix", bufferFirstMatrix);
        cs3.setStorageBuffer("secondMatrix", bufferSecondMatrix);
        cs3.setStorageBuffer("resultMatrix", bufferResultMatrix);

        cs3.dispatchWhenReady(firstMatrix[0], secondMatrix[1]).then(() => {
            bufferResultMatrix.read().then((res) => {
                // we know the result buffer contains floats
                const resFloats = new Float32Array(res.buffer);
                console.log(resFloats);
            });
        });

        return scene;
    };

    const clearTextureComputeShader = `
    [[group(0), binding(0)]] var tbuf : [[access(write)]] texture_storage_2d<rgba8unorm>;

    [[block]] struct Params {
        color : vec4<f32>;
    };
    [[group(0), binding(1)]] var<uniform> params : Params;

    [[stage(compute), workgroup_size(1, 1, 1)]]

    fn main([[builtin(global_invocation_id)]] global_id : vec3<u32>) {
        textureStore(tbuf, vec2<i32>(global_id.xy), params.color);
    }
    `;

    const copyTextureComputeShader = `
    [[group(0), binding(0)]] var dest : [[access(write)]] texture_storage_2d<rgba8unorm>;
    [[group(0), binding(1)]] var samplerSrc : sampler;
    [[group(0), binding(2)]] var src : texture_2d<f32>;

    [[stage(compute), workgroup_size(1, 1, 1)]]

    fn main([[builtin(global_invocation_id)]] global_id : vec3<u32>) {
        let dims : vec2<f32> = vec2<f32>(textureDimensions(src, 0));
        let pix : vec4<f32> = textureSampleLevel(src, samplerSrc, vec2<f32>(global_id.xy) / dims, 0.0);
        textureStore(dest, vec2<i32>(global_id.xy), pix);
    }
    `;

    const matrixMulComputeShader = `
    [[block]] struct Matrix {
      size : vec2<f32>;
      numbers: array<f32>;
    };

    [[group(0), binding(0)]] var<storage> firstMatrix : [[access(read)]] Matrix;
    [[group(0), binding(1)]] var<storage> secondMatrix : [[access(read)]] Matrix;
    [[group(0), binding(2)]] var<storage> resultMatrix : [[access(write)]] Matrix;

    [[stage(compute)]] fn main([[builtin(global_invocation_id)]] global_id : vec3<u32>) {
        resultMatrix.size = vec2<f32>(firstMatrix.size.x, secondMatrix.size.y);

        let resultCell : vec2<u32> = vec2<u32>(global_id.x, global_id.y);
        var result : f32 = 0.0;
        for (var i : u32 = 0u; i < u32(firstMatrix.size.y); i = i + 1u) {
            let a : u32 = i + resultCell.x * u32(firstMatrix.size.y);
            let b : u32 = resultCell.y + i * u32(secondMatrix.size.y);
            result = result + firstMatrix.numbers[a] * secondMatrix.numbers[b];
        }

        let index : u32 = resultCell.y + resultCell.x * u32(secondMatrix.size.y);
        resultMatrix.numbers[index] = result;
    }
    `;

    const scene = createScene();
    engine.runRenderLoop(() => {
        scene.render();
    });
};

init();
</script>
</body>
</html>