3Dプログラミング講座・その33:WEBGPUテストと無限透視射影



通常透視射影



無限透視射影



無限透視射影+フォンシェーディング



無限透視射影+フォンシェーディング+斜め遠方移動(1,0,-5)





通常透視射影+斜め遠方移動(1,0,-5)





無限透視射影では、遠方の距離が非線形に圧縮されるため、通常透視射影と
比較して遠方の物体の画面上の位置が外側に残りやすく、方角の違いを視覚的に保持しやすい。
見かけ上は視野角が狭いように見える場合があるが、距離感を完全には失わずに
遠方の方角情報を保持するという、通常透視射影と平行射影の中間的な性質を持つ。

通常透視射影は、距離に応じた縮小を強く表現するため、遠方になるほど画面上の位置変化も圧縮される。
そのため、「距離感を強く表現する」方向性を持つ。

無限透視射影は、通常透視射影と平行射影の中間に位置するように、距離情報を非線形に圧縮しながら、遠方における方角の違いをできるだけ保持する方向性を持つ。

平行射影は、距離による縮小を行わないため、距離感をほぼ捨てる代わりに、方角や平行関係を最大限に保持する方向性を持つ。



//テスト結果は良好です方角比/z絶対値と無限透視変換前後のそれぞれの比にも注目してください
var v00 = new N6LVector([1, 15, 10, -50],true);
var v01 = FrustumInf(v00);
var v02 = InvFrustumInf(v01, v00.x[3]);
console.log(v00.Str()); //true,4,1,15,10,-50
console.log(v01.Str()); //true,4,1,0.6110382101210349,0.4073588067473566,-50
console.log(v02.Str()); //true,4,1,15,10,-50
var v10 = new N6LVector([1, -200, 500, -250],true);
var v11 = FrustumInf(v10);
var v12 = InvFrustumInf(v11, v10.x[3]);
console.log(v10.Str()); //true,4,1,-200,500,-250
console.log(v11.Str()); //true,4,1,-1.6566538523413499,4.141634630853375,-250
console.log(v12.Str()); //true,4,1,-200,500,-250



WebGPU 立方体描画コード解説

このページでは、WebGPU を使って「線で立方体を描き、上面だけを塗りつぶす」コードの仕組みを解説します。 線描画と面描画を 別パイプライン に分けることで、軽量かつ柔軟な 3D 表現を実現しています。

透視射影などの詳しい説明は以下のリンクを参考にしてください。

その16:透視射影公式テスト

1. 全体構造

このコードは以下の 3 つの要素で構成されています。

WebGPU は1回のdrawで同じprimitive topologyを混在させられないので、今回の実装では パイプラインを 2 つに分けるのがポイントです。

2. WGSL シェーダー

線描画用(緑色)


@vertex
fn vs_main(@location(0) inPos : vec2) -> @builtin(position) vec4 {
  return vec4(inPos, 0.0, 1.0);
}

@fragment
fn fs_main() -> @location(0) vec4 {
  return vec4(0.0, 1.0, 0.0, 1.0); // 線の色(緑)
}

上面塗りつぶし用(薄い黄緑)


@vertex
fn vs_main(@location(0) inPos : vec2) -> @builtin(position) vec4 {
  return vec4(inPos, 0.0, 1.0);
}

@fragment
fn fs_main() -> @location(0) vec4 {
  return vec4(0.7, 1.0, 0.7, 1.0); // 上面の色(薄い黄緑)
}

3. パイプラインの作成

線描画は line-list、面描画は triangle-list を使います。


// 三角形パイプライン(上面塗り)
trianglePipeline = device.createRenderPipeline({
  primitive: { topology: 'triangle-list' },
  ...
});

// 線パイプライン(輪郭線)
linePipeline = device.createRenderPipeline({
  primitive: { topology: 'line-list' },
  ...
});

4. 頂点データの生成

立方体の輪郭線は 12 本あり、各線は 2 頂点で構成されます。


const edges = [
  [4,5], [5,6], [6,7], [7,4], // 上面
  [0,1], [1,2], [2,3], [3,0], // 下面
  [0,4], [1,5], [2,6], [3,7], // 側面
];

これを 24 頂点の配列にして GPU に送ります。

5. 上面の塗りつぶし(三角形 2 枚)

WebGPU は四角形を直接描けないため、上面を三角形 2 枚に分割します。


// 三角形1 (4,5,6)
// 三角形2 (4,6,7)

これを trianglePipeline で描画します。

6. 輪郭線の描画

線描画は linePipeline に切り替えて行います。


pass.setPipeline(linePipeline);

for (var i = 0; i < edges.length; i++) {
  pass.draw(2, 1, i * 2, 0);
}

7. 回転・投影処理

頂点は以下の順で変換されます。

  1. 回転(X, Y, Z)
  2. Z 方向へ平行移動
  3. 透視投影(frustum)
  4. 画面座標 → NDC(-1~+1)へ正規化

これにより、立方体がゆっくり回転するアニメーションになります。

8. タイマーによるループ

N6LTimerMan を使って 50ms ごとに GLoop を呼び出し、 アニメーションを継続します。

まとめ

このコードは WebGPU の基本構造を押さえつつ、 線と面を別パイプラインで描き分けることで、 軽量で柔軟な 3D 表現を実現しています。

特に以下の点がポイントです。

WebGPU の学習用としても非常に良い構成になっています。




無限遠透視射影(FrustumInf)と無限対数軸 ToLogAxis の概要

本稿で扱う 無限遠透視射影 FrustumInf は、一般的な WebGPU の透視投影行列ではなく、 独自の非線形変換 ToLogAxis() を利用して、 無限遠方向を有限の座標範囲へ圧縮して表現する射影方式です。

通常の透視射影では、距離が大きくなるほど物体は画面中央方向へ急速に収束します。 これに対して FrustumInf では、奥行き方向を非線形に圧縮することで、 遠方の物体についても位置や方向の情報を有限領域内に残すことを目指します。


1. 通常の透視射影

通常の透視射影では、奥行き z に応じて xy をおおむね 1 / |z| に比例して縮小します。 単純化すると、次のように表せます。

x' = x / |z|
y' = y / |z|

そのため、物体が遠方へ移動するほど画面上の大きさは小さくなり、 さらに斜め方向へ移動していても、十分遠方になると画面中央付近へ収束します。

これは通常の透視表現として非常に自然な性質ですが、 非常に遠い物体を連続的に扱う用途では、遠方の位置情報が画面上で極端に圧縮されます。

2. 無限対数軸 ToLogAxis

そこで使用するのが ToLogAxis() です。 これは通常の距離軸を非線形に変換し、 有限距離から無限遠までを有限範囲へ圧縮するための座標変換です。

function ToLogAxis(x) {
    var c = Range / Math.abs(x);

    if (isNaN(c) || !isFinite(c)) {
        return 0.0;
    }

    var v = Range * Math.pow(1.0 / Base, c);

    if (isNaN(v) || !isFinite(v)) {
        v = 0.0;
    }

    if (x < 0.0) v *= -1.0;
    return v;
}

現在の設定では、

Range = 1.0;
Base  = 8.0;

となっています。

Base を大きくすると近距離の変化が緩やかになり、小さくすると近距離での変化が急になります。
現在の Base=8.0 は、比較的遠方まで滑らかに圧縮するための設定です。

この変換の重要な性質は、

となることです。

直感的な意味:
ToLogAxis は、距離が無限に増加しても座標値が無限に増え続けないように、 無限遠を有限値 Range に近づけていくための「ものさし」として働きます。

3. FrustumInf の仕組み

FrustumInf では、まず元の z 座標を ToLogAxis() に通します。 実際のコードでは次のようになっています。

function FrustumInf0(v) {
    var VecWK2 = v.clone().Homogeneous();
    var VecWK  = new N6LVector(4);

    VecWK.x[0] = 1.0;

    VecWK.x[1] =
        (Range - ToLogAxis(-VecWK2.x[3]))
        * VecWK2.x[1] / Range;

    VecWK.x[2] =
        (Range - ToLogAxis(-VecWK2.x[3]))
        * VecWK2.x[2] / Range;

    VecWK.x[3] = VecWK2.x[3];

    return VecWK.SetHomo(true);
}

ここで重要なのが、

scale = Range - ToLogAxis(-z)

という距離依存係数です。 そして X、Y は同じ係数によって縮小されます。

x' = scale × x / Range
y' = scale × y / Range

つまり、通常透視射影のように単純な 1 / |z| を使用するのではなく、 ToLogAxis によって圧縮された距離を利用して X、Y を縮小する構造になっています。

3.1 遠方になるとどうなるか

z が負方向へ遠ざかる場合、 -z は正方向へ大きくなります。 すると、

ToLogAxis(-z) → Range

となるため、

Range - ToLogAxis(-z) → 0

となります。

したがって遠方へ行くほど X、Y の値は小さくなりますが、 その縮小量は通常の 1 / |z| とは異なる非線形なものになります。

これが FrustumInf による 「遠方を圧縮しながら有限領域に残す」 という特徴につながります。


4. 無限透視射影における方角情報の保持

今回の WebGPU テストでは、単純に遠方へ移動させるだけではなく、 斜め方向へ遠方移動させています。

for (var i = 0; i < Vrp.length; i++) {
    Vrp[i][2] -= (40 + Vzpos);
    Vrp[i][0] += (Vzpos / 5);
}

つまり、物体は Z 方向へ遠ざかると同時に X 方向にも移動します。 概念的には、

(1, 0, -5)

に近い方向へ連続的に移動していることになります。

この状態で通常透視射影と無限透視射影を比較すると、 遠方における画面上の位置に明確な違いが現れます。

4.1 通常透視射影

4.2 無限透視射影

したがって、今回の WebGPU デモで観察された 「通常透視では遠方物体が中央寄りになり、無限透視では斜め方向の位置が比較的残る」 という違いは、単なる表示上の偶然ではなく、 両者の距離による縮小方式の違いから生じています。


5. 方角比による数値検証

今回のテストで特に重要なのが、 変換前後の X:Y の方角比です。

5.1 テスト1

var v00 = new N6LVector([1, 15, 10, -50], true);
var v01 = FrustumInf(v00);
var v02 = InvFrustumInf(v01, v00.x[3]);

変換前:

x = 15
y = 10
z = -50

x / |z| = 15 / 50 = 0.30
y / |z| = 10 / 50 = 0.20

x / y = 15 / 10 = 1.5

FrustumInf 後:

x' = 0.6110382101210349
y' = 0.4073588067473566
z' = -50

ここで、

x' / y'
= 0.6110382101210349 / 0.4073588067473566
= 1.5

となります。

したがって、

x / y = x' / y'

が成立し、X:Y の方角比が保存されていることが確認できます。

さらに変換後の奥行きに対する比を見ると、

x' / |z'| = 0.0122207642
y' / |z'| = 0.0081471761

となります。 この2つの比の比率も、

0.0122207642 / 0.0081471761 = 1.5

となります。

5.2 テスト2

var v10 = new N6LVector([1, -200, 500, -250], true);
var v11 = FrustumInf(v10);
var v12 = InvFrustumInf(v11, v10.x[3]);

変換前:

x = -200
y = 500
z = -250

x / |z| = -200 / 250 = -0.8
y / |z| = 500 / 250 = 2.0

x / y = -200 / 500 = -0.4

FrustumInf 後:

x' = -1.6566538523413499
y' =  4.141634630853375
z' = -250

この場合も、

x' / y'
=
-1.6566538523413499 / 4.141634630853375
= -0.4

となります。

つまり、こちらの大きな座標値でも X:Y の方角比は維持されています。


6. なぜ方角比が保存されるのか

これは FrustumInf の計算式そのものから説明できます。

x' = S(z) × x
y' = S(z) × y

ここで S(z) は Z のみに依存する同一の縮小係数です。 したがって、

x' / y'
= {S(z) × x} / {S(z) × y}
= x / y

となります。

重要:
FrustumInf は X と Y を別々の係数で変形しているのではなく、 同じ距離依存係数で X と Y を同時に縮小しているため、 同一奥行きにおける方角比 X:Y は保存されます。

したがって、「遠方の距離を圧縮すること」と 「左右・上下方向の方角情報を維持すること」を分離して扱える点が、 この方式の重要な特徴です。


7. 逆変換 InvFrustumInf による検証

さらに今回のテストでは、FrustumInf の後に InvFrustumInf() を適用しています。

v00
 ↓
FrustumInf
 ↓
v01
 ↓
InvFrustumInf
 ↓
v02

結果は、

[1, 15, 10, -50]
→ [1, 0.6110382101, 0.4073588067, -50]
→ [1, 15, 10, -50]

となっています。

2つ目のテストでも、

[1, -200, 500, -250]
→ [1, -1.6566538523, 4.1416346309, -250]
→ [1, -200, 500, -250]

となっています。

これは今回の実装において、 FrustumInf と InvFrustumInf が対応する可逆変換として機能している ことを示しています。


8. 通常透視・無限透視・平行射影の違い

今回の実験結果を、距離感と方角情報という観点から整理すると次のようになります。

したがって、無限透視射影を単純に 「通常透視と平行射影の中間」 と考えるよりも、 「奥行きを非線形に圧縮しながら、方向情報を保持する別の射影方式」 と捉える方が適切です。


9. WebGPU による遠方描画テスト

今回の WebGPU デモでは、同じ立方体を複数の方式で描画しています。

特に斜め遠方移動を加えることで、 単純な「物体が小さくなる」という違いだけではなく、 遠方へ移動した物体がどの方向へ収束していくのか を比較できるようになっています。

これは無限透視射影の特徴を視覚的に確認するうえで重要なテストです。


10. まとめ

今回の実験で確認できた核心:
無限遠透視射影では、遠方の距離を圧縮して有限領域に収めながら、 X と Y に同じ縮小係数を適用することで、 遠方物体の方角比 X:Y を維持したまま描画できる



コード全文


var TMan = new N6LTimerMan();  //タイマーマネージャー
var p=[];
var pp=[];
var time;
var adapter;
var device;
var canvas;
var context;
var format;
var linePipeline;
var trianglePipeline;
var vertexBufferTri;
var vertexBufferWire;
var vertexCount = 0;

var p2=[];
var pp2=[];
var time2;
var adapter2;
var device2;
var canvas2;
var context2;
var format2;
var linePipeline2;
var trianglePipeline2;
var vertexBufferTri2;
var vertexBufferWire2;
var vertexCount2 = 0;
// Range と Base は外側で設定しておく
var Range = 1.0;
var Base  = 8.0;

var p3=[];
var pp3=[];
var time3;
var adapter3;
var device3;
var canvas3;
var context3;
var format3;
var linePipeline3;
var trianglePipeline3;
var vertexBufferTri3;
var vertexBufferWire3;
var vertexCount3 = 0;
var depthTexture3;
var depthTextureView3;

var p4=[];
var pp4=[];
var time4;
var adapter4;
var device4;
var canvas4;
var context4;
var format4;
var linePipeline4;
var trianglePipeline4;
var vertexBufferTri4;
var vertexBufferWire4;
var vertexCount4 = 0;
var depthTexture4;
var depthTextureView4;
var zpos4 = 0;

var p5=[];
var pp5=[];
var time5;
var adapter5;
var device5;
var canvas5;
var context5;
var format5;
var linePipeline5;
var trianglePipeline5;
var vertexBufferTri5;
var vertexBufferWire5;
var vertexCount5 = 0;
var depthTexture5;
var depthTextureView5;
var zpos5 = 0;

const shaderCode = `
@vertex
fn vs_main(@location(0) inPos : vec2) -> @builtin(position) vec4 {
  return vec4(inPos, 0.0, 1.0);
}

@fragment
fn fs_main() -> @location(0) vec4 {
  return vec4(0.0, 1.0, 0.0, 1.0);
}
`;
const triangleshaderCode = `
@vertex
fn vs_main(@location(0) inPos : vec2) -> @builtin(position) vec4 {
  return vec4(inPos, 0.0, 1.0);
}

@fragment
fn fs_main() -> @location(0) vec4 {
  return vec4(0.7, 1.0, 0.7, 1.0); // ★ 薄い黄緑
}
`;

const phongshaderCode = `
const Range : f32 = ${Range};

struct VertexInput {
    @location(0) position : vec3, // 面側と同じく position を受け取る
};

struct VertexOutput {
    @builtin(position) pos : vec4,
};

@vertex
fn vs_main(input : VertexInput) -> VertexOutput {
    var out : VertexOutput;
    
    // 面側と完全に同じロジックで深度を計算・クランプする
    // (もし面と線が完全に同一個所で重なってZファイティングが起きる場合は、
    //   ここで depth にわずかなオフセット(例: -0.001)を足すと線を少し手前に浮かせられます)
    // 奥(z / Range が 1.0 に近づく)に向かって、2乗で急速にオフセットが 0 に消えていく
    var factor = 1.0 - input.position.z / Range;
    var depth = (-input.position.z) / Range - 0.000002 * (factor * factor * factor * factor);
    out.pos = vec4(input.position.xy, clamp(depth, 0.0, 1.0), 1.0);
    return out;
}

@fragment
fn fs_main() -> @location(0) vec4 {
    // ワイヤーフレームの色(例:黒や好みの色)
    return vec4(0.0, 1.0, 0.0, 1.0);
}
`;

const trianglephongshaderCode = `
const Range : f32 = ${Range};

struct VertexInput {
    @location(0) position : vec3,
    @location(1) normal   : vec3,
    @location(2) faceIndex : f32,   // ★追加
};

struct VertexOutput {
    @builtin(position) pos : vec4,
    @location(0) normal : vec3,
    @location(1) faceIndex : f32,   // ★追加
};

@vertex
fn vs_main(input : VertexInput) -> VertexOutput {
    var out : VertexOutput;
    var depth = (-input.position.z) / Range;
    out.pos = vec4(input.position.xy, clamp(depth, 0.0, 1.0), 1.0);
    out.normal = input.normal;
    out.faceIndex = input.faceIndex;   // ★追加
    return out;
}

@fragment
fn fs_main(
    @location(0) normal : vec3,
    @location(1) faceIndex : f32
) -> @location(0) vec4 {

    // ★ 上面だけピンク
    if (faceIndex == 1.0) {
        return vec4(1.0, 0.7, 0.9, 1.0);
    }

    // ★ 通常のフォンシェーディング
    var N = normalize(normal);
    var L = normalize(vec3(0.3, 0.5, -1.0));
    var diff = max(dot(N, L), 0.0);

    var ambient = 0.2;
    var brightness = ambient + (1.0 - ambient) * diff;

    var color = vec3(0.7, 1.0, 0.7) * brightness;
    return vec4(color, 1.0);
}
`;

function makeFace(indices, zoom, sx, sy) {
  const v = [];
  for (var i of indices) v.push(norm(pp[i], zoom, sx, sy));
  v.push(norm(pp[indices[0]], zoom, sx, sy)); // 閉じる
  return v;
}

function GLoop(id, sw = 1) {
  var Vp=p;
  var Vrp = [];
  var Vpp=pp;
  var Vtime=time;
  var Vadapter=adapter;
  var Vdevice=device;
  var Vcanvas=canvas;
  var Vcontext=context;
  var Vformat=format;
  var VlinePipeline=linePipeline;
  var VtrianglePipeline=trianglePipeline;
  var VvertexBufferTri=vertexBufferTri;
  var VvertexBufferWire=vertexBufferWire;
  var VvertexCount = 0;
  var Vzpos = 0;

  if(sw === 2) {
    Vp=p2;
    Vrp = [];
    Vpp=pp2;
    Vadapter=adapter2;
    Vdevice=device2;
    Vcanvas=canvas2;
    Vcontext=context2;
    Vformat=format2;
    VlinePipeline=linePipeline2;
    VtrianglePipeline=trianglePipeline2;
    VvertexBufferTri=vertexBufferTri2;
    VvertexBufferWire=vertexBufferWire2;
    VvertexCount = 0;
    time2 += Math.PI / 180;
    Vtime=time2;
  }
  else if(sw === 3) {
    Vp=p3;
    Vrp = [];
    Vpp=pp3;
    Vadapter=adapter3;
    Vdevice=device3;
    Vcanvas=canvas3;
    Vcontext=context3;
    Vformat=format3;
    VlinePipeline=linePipeline3;
    VtrianglePipeline=trianglePipeline3;
    VvertexBufferTri=vertexBufferTri3;
    VvertexBufferWire=vertexBufferWire3;
    VvertexCount = 0;
    time3 += Math.PI / 180;
    Vtime=time3;
  }
  else if(sw === 4) {
    Vp=p4;
    Vrp = [];
    Vpp=pp4;
    Vadapter=adapter4;
    Vdevice=device4;
    Vcanvas=canvas4;
    Vcontext=context4;
    Vformat=format4;
    VlinePipeline=linePipeline4;
    VtrianglePipeline=trianglePipeline4;
    VvertexBufferTri=vertexBufferTri4;
    VvertexBufferWire=vertexBufferWire4;
    VvertexCount = 0;
    time4 += Math.PI / 180;
    Vtime=time4;
    zpos4 += 5;
    if(20000 < zpos4) zpos4 = 0; 
    Vzpos = zpos4;
  }
  else if(sw === 5) {
    Vp=p5;
    Vrp = [];
    Vpp=pp5;
    Vadapter=adapter5;
    Vdevice=device5;
    Vcanvas=canvas5;
    Vcontext=context5;
    Vformat=format5;
    VlinePipeline=linePipeline5;
    VtrianglePipeline=trianglePipeline5;
    VvertexBufferTri=vertexBufferTri5;
    VvertexBufferWire=vertexBufferWire5;
    VvertexCount = 0;
    time5 += Math.PI / 180;
    Vtime=time5;
    zpos5 += 5;
    if(20000 < zpos5) zpos5 = 0; 
    Vzpos = zpos5;
  }
  else {
    time += Math.PI / 180;
    Vtime=time;
  }

  Vpp = [];

  for (var i = 0; i < Vp.length; i++) Vrp[i] = rot([0,0,-1], 1 * Vtime, Vp[i]);
  for (var i = 0; i < Vrp.length; i++) Vrp[i] = rot([0,1,0], 2 * Vtime, Vrp[i]);
  for (var i = 0; i < Vrp.length; i++) Vrp[i] = rot([1,0,0], 3 * Vtime, Vrp[i]);
  for (var i = 0; i < Vrp.length; i++) { Vrp[i][2] -= (40 + Vzpos); Vrp[i][0] += (Vzpos / 5); }

  if(sw === 1 || sw === 5) {
    for (var i = 0; i < Vrp.length; i++) Vpp[i] = frustum(-1, Vrp[i]);
  }
  else {
    for (var i = 0; i < Vrp.length; i++) {
      var VecWK = new N6LVector([1.0,Vrp[i][0],Vrp[i][1],Vrp[i][2]],true);
      VecWK = FrustumInf(VecWK);
      Vpp[i] = [];
      for (var j = 0; j < 3; j++) Vpp[i][j] = VecWK.x[j + 1];
    }
  }

  const Vsx = 600, Vsy = 600;
  const Vzoom = 250;

  const Vedges = [
    [4,5], [5,6], [6,7], [7,4],
    [0,1], [1,2], [2,3], [3,0],
    [0,4], [1,5], [2,6], [3,7],
  ];

  // ★ 24頂点の大きな配列を作る
  const Vverts = [];
  if(sw === 3 || sw === 4){
    for (const [a, b] of Vedges) {
      var v1 = norm(Vpp[a], Vzoom, Vsx, Vsy);
      var v2 = norm(Vpp[b], Vzoom, Vsx, Vsy);

      // 両端の距離がほぼ0(0ドット)の場合の共通ガード
      var dx = v2[0] - v1[0];
      var dy = v2[1] - v1[1];
      if (dx * dx + dy * dy < 1e-12) {
        v2[0] += 1.0; // 1ドット以上の幅を持たせる
      }

      Vverts.push(v1[0], v1[1], ToLogAxis(Vpp[a][2]));
      Vverts.push(v2[0], v2[1], ToLogAxis(Vpp[b][2]));
    }
  }
  else {
    for (const [a, b] of Vedges) {
      var v1 = norm(Vpp[a], Vzoom, Vsx, Vsy);
      var v2 = norm(Vpp[b], Vzoom, Vsx, Vsy);

      // 両端の距離がほぼ0(0ドット)の場合の共通ガード
      var dx = v2[0] - v1[0];
      var dy = v2[1] - v1[1];
      if (dx * dx + dy * dy < 1e-12) {
        v2[0] += 1.0; // 1ドット以上の幅を持たせる
      }

      Vverts.push(v1[0], v1[1]);
      Vverts.push(v2[0], v2[1]);
    }
  }

  const Vdata = new Float32Array(Vverts);

  // ★ ワイヤーフレームの頂点を書き込む
  Vdevice.queue.writeBuffer(VvertexBufferWire, 0, Vdata.buffer);

  // ★ 描画パス開始
  const Vencoder = Vdevice.createCommandEncoder();
  const VtextureView = Vcontext.getCurrentTexture().createView();

  var Vpass;
  if(sw === 3 || sw === 4){
    var dtv;
    switch(sw){
    case 3: dtv = depthTextureView3; break;
    case 4: dtv = depthTextureView4; break;
    }
    Vpass = Vencoder.beginRenderPass({
      colorAttachments: [{
        view: VtextureView,
        loadOp: 'clear',
        storeOp: 'store',
        clearValue: { r: 0, g: 0, b: 0, a: 1 },
      }],
      depthStencilAttachment: {
        view: dtv,
        depthClearValue: 1.0,
        depthLoadOp: 'clear',
        depthStoreOp: 'store',
      },
    });
  }
  else {
    Vpass = Vencoder.beginRenderPass({
      colorAttachments: [{
        view: VtextureView,
        loadOp: 'clear',
        storeOp: 'store',
        clearValue: { r: 0, g: 0, b: 0, a: 1 },
      }],
    });
  }

  // ★ 三角形を描く(上面 + 下面 + 側面)
//  p3 = [[10,10,10],[-10,10,10],[-10,-10,10],[10,-10,10],
//       [10,10,-10],[-10,10,-10],[-10,-10,-10],[10,-10,-10]];
  {
    if(sw === 3 || sw === 4) {
        // 元の面法線(立方体のローカル座標)
        const baseNormals = {
          top:    [ 0,  0,  1 ],
          bottom: [ 0,  0, -1 ],
          front:  [ 0, -1,  0 ],
          back:   [ 0,  1,  0 ],
          right:  [ 1,  0,  0 ],
          left:   [-1,  0,  0 ],
        };
        var N1 = rotateNormal(baseNormals.top,    Vtime);
        var N2 = rotateNormal(baseNormals.bottom, Vtime);
        var N3 = rotateNormal(baseNormals.front,  Vtime);
        var N4 = rotateNormal(baseNormals.right,  Vtime);
        var N5 = rotateNormal(baseNormals.back,   Vtime);
        var N6 = rotateNormal(baseNormals.left,   Vtime);

        const Vtverts = [];

        var v0 = norm(Vpp[0], Vzoom, Vsx, Vsy);
        var v1 = norm(Vpp[1], Vzoom, Vsx, Vsy);
        var v2 = norm(Vpp[2], Vzoom, Vsx, Vsy);
        var v3 = norm(Vpp[3], Vzoom, Vsx, Vsy);
        var v4 = norm(Vpp[4], Vzoom, Vsx, Vsy);
        var v5 = norm(Vpp[5], Vzoom, Vsx, Vsy);
        var v6 = norm(Vpp[6], Vzoom, Vsx, Vsy);
        var v7 = norm(Vpp[7], Vzoom, Vsx, Vsy);

        var ax0 = ToLogAxis(Vpp[0][2]);
        var ax1 = ToLogAxis(Vpp[1][2]);
        var ax2 = ToLogAxis(Vpp[2][2]);
        var ax3 = ToLogAxis(Vpp[3][2]);
        var ax4 = ToLogAxis(Vpp[4][2]);
        var ax5 = ToLogAxis(Vpp[5][2]);
        var ax6 = ToLogAxis(Vpp[6][2]);
        var ax7 = ToLogAxis(Vpp[7][2]);



        // 上面 (4,5,6,7) - 巻き順を反転 (CCW)

        Vtverts.push(
          v4[0], v4[1], ax4, N1[0], N1[1], N1[2], 1.0,
          v5[0], v5[1], ax5, N1[0], N1[1], N1[2], 1.0,
          v6[0], v6[1], ax6, N1[0], N1[1], N1[2], 1.0,
        );
        Vtverts.push(
          v4[0], v4[1], ax4, N1[0], N1[1], N1[2], 1.0,
          v6[0], v6[1], ax6, N1[0], N1[1], N1[2], 1.0,
          v7[0], v7[1], ax7, N1[0], N1[1], N1[2], 1.0,
        );

        // 下面 (0,1,2,3) - 巻き順を反転 (CCW)

        Vtverts.push(
          v0[0], v0[1], ax0, N2[0], N2[1], N2[2], 2.0,
          v2[0], v2[1], ax2, N2[0], N2[1], N2[2], 2.0,
          v1[0], v1[1], ax1, N2[0], N2[1], N2[2], 2.0,
        );
        Vtverts.push(
          v0[0], v0[1], ax0, N2[0], N2[1], N2[2], 2.0,
          v3[0], v3[1], ax3, N2[0], N2[1], N2[2], 2.0,
          v2[0], v2[1], ax2, N2[0], N2[1], N2[2], 2.0,
        );


        // ★ 側面の追加

        // 前面 (0,1,5,4)
        Vtverts.push(
          v0[0], v0[1], ax0, N3[0], N3[1], N3[2], 3.0,
          v1[0], v1[1], ax1, N3[0], N3[1], N3[2], 3.0,
          v5[0], v5[1], ax5, N3[0], N3[1], N3[2], 3.0,
        );
        Vtverts.push(
          v0[0], v0[1], ax0, N3[0], N3[1], N3[2], 3.0,
          v5[0], v5[1], ax5, N3[0], N3[1], N3[2], 3.0,
          v4[0], v4[1], ax4, N3[0], N3[1], N3[2], 3.0,
        );

        // 右面 (1,2,6,5)
        Vtverts.push(
          v1[0], v1[1], ax1, N4[0], N4[1], N4[2], 4.0,
          v2[0], v2[1], ax2, N4[0], N4[1], N4[2], 4.0,
          v6[0], v6[1], ax6, N4[0], N4[1], N4[2], 4.0,
        );
        Vtverts.push(
          v1[0], v1[1], ax1, N4[0], N4[1], N4[2], 4.0,
          v6[0], v6[1], ax6, N4[0], N4[1], N4[2], 4.0,
          v5[0], v5[1], ax5, N4[0], N4[1], N4[2], 4.0,
        );

        // 後面 (2,3,7,6)
        Vtverts.push(
          v2[0], v2[1], ax2, N5[0], N5[1], N5[2], 5.0,
          v3[0], v3[1], ax3, N5[0], N5[1], N5[2], 5.0,
          v7[0], v7[1], ax7, N5[0], N5[1], N5[2], 5.0,
        );
        Vtverts.push(
          v2[0], v2[1], ax2, N5[0], N5[1], N5[2], 5.0,
          v7[0], v7[1], ax7, N5[0], N5[1], N5[2], 5.0,
          v6[0], v6[1], ax6, N5[0], N5[1], N5[2], 5.0,
        );

        // 左面 (3,0,4,7)
        Vtverts.push(
          v3[0], v3[1], ax3, N6[0], N6[1], N6[2], 6.0,
          v0[0], v0[1], ax0, N6[0], N6[1], N6[2], 6.0,
          v4[0], v4[1], ax4, N6[0], N6[1], N6[2], 6.0,
        );
        Vtverts.push(
          v3[0], v3[1], ax3, N6[0], N6[1], N6[2], 6.0,
          v4[0], v4[1], ax4, N6[0], N6[1], N6[2], 6.0,
          v7[0], v7[1], ax7, N6[0], N6[1], N6[2], 6.0,
        );

        const Vtdata = new Float32Array(Vtverts);
        Vdevice.queue.writeBuffer(VvertexBufferTri, 0, Vtdata.buffer);

        Vpass.setPipeline(VtrianglePipeline);
        Vpass.setVertexBuffer(0, VvertexBufferTri);

        Vpass.draw(12 * 3, 1, 0, 0);
    }
    else {
        const Vtverts = [];

        var v4 = norm(Vpp[4], Vzoom, Vsx, Vsy);
        var v5 = norm(Vpp[5], Vzoom, Vsx, Vsy);
        var v6 = norm(Vpp[6], Vzoom, Vsx, Vsy);
        var v7 = norm(Vpp[7], Vzoom, Vsx, Vsy);

        Vtverts.push(v4[0], v4[1], v6[0], v6[1], v5[0], v5[1]);
        Vtverts.push(v4[0], v4[1], v7[0], v7[1], v6[0], v6[1]);

        const Vtdata = new Float32Array(Vtverts);
        Vdevice.queue.writeBuffer(VvertexBufferTri, 0, Vtdata.buffer);

        Vpass.setPipeline(VtrianglePipeline);
        Vpass.setVertexBuffer(0, VvertexBufferTri);

        Vpass.draw(2 * 3, 1, 0, 0);
    }
  }
  // ★ 線パイプラインに切り替え
  Vpass.setPipeline(VlinePipeline);

  // ★ 24頂点分の線データを書き込む
  Vdevice.queue.writeBuffer(VvertexBufferWire, 0, Vdata.buffer);
  Vpass.setVertexBuffer(0, VvertexBufferWire);

  // ★ 12本の線を描く
  for (var i = 0; i < Vedges.length; i++) {
    Vpass.draw(2, 1, i * 2, 0);
  }

  Vpass.end();
  Vdevice.queue.submit([Vencoder.finish()]);

  TMan.timer[id].setalerm(() => GLoop(id,sw), 50);
}
// 回転後の法線を作るヘルパ
function rotateNormal(n, time) {
  var v = n;
  v = rot([0,0,-1], 1 * time, v);
  v = rot([0,1,0], 2 * time, v);
  v = rot([1,0,0], 3 * time, v);
  return v;
}
function norm(pt, zoom, sx, sy) {
  const x = pt[0] * zoom + sx / 2;
  const y = pt[1] * zoom + sy / 2;
  // WebGPU は左上原点ではなく NDC(-1~+1)
  const nx = (x / sx) * 2 - 1;
  const ny = -((y / sy) * 2 - 1); // Y軸反転
  return [nx, ny];
}
function rot(a,th,p) {
  var c = Math.cos(th),s = Math.sin(th);
  return [(c+a[0]*a[0]*(1-c))*p[0]+(a[0]*a[1]*(1-c)-a[2]*s)*p[1]+(a[0]*a[2]*(1-c)+a[1]*s)*p[2],
          (a[1]*a[0]*(1-c)+a[2]*s)*p[0]+(c+a[1]*a[1]*(1-c))*p[1]+(a[1]*a[2]*(1-c)-a[0]*s)*p[2],
          (a[2]*a[0]*(1-c)-a[1]*s)*p[0]+(a[2]*a[1]*(1-c)+a[0]*s)*p[1]+(c+a[2]*a[2]*(1-c))*p[2]];
}
function frustum(n,p) {
  var z = p[2];
  return [(n/z)*p[0], (n/z)*p[1]];
}

// 無限対数軸 → 通常軸
function FromLogAxis(x) {
    if (isNaN(x) || Range <= Math.abs(x)) {
        return Infinity;
    }

    var v = Range / (Math.log(Math.abs(x) / Range) / Math.log(1.0 / Base));

    if (isNaN(v) || !isFinite(v)) {
        v = Infinity;
    }

    if (x < 0.0) v *= -1.0;
    return v;
}

// 通常軸 → 無限対数軸
function ToLogAxis(x) {
    var c = Range / Math.abs(x);

    if (isNaN(c) || !isFinite(c)) {
        return 0.0;
    }

    var v = Range * Math.pow(1.0 / Base, c);

    if (isNaN(v) || !isFinite(v)) {
        v = 0.0;
    }

    if (x < 0.0) v *= -1.0;
    return v;
}

// 無限遠透視射影(Range スケール)
function FrustumInf0(v) {
    var VecWK2 = v.clone().Homogeneous();
    var VecWK  = new N6LVector(4);

    VecWK.x[0] = 1.0;
    VecWK.x[1] = (Range - ToLogAxis(-VecWK2.x[3])) * VecWK2.x[1] / Range;
    VecWK.x[2] = (Range - ToLogAxis(-VecWK2.x[3])) * VecWK2.x[2] / Range;
    VecWK.x[3] = VecWK2.x[3];
    return VecWK.SetHomo(true);
}

// 逆無限遠透視射影(Range スケール)
function InvFrustumInf0(v, z) {
    var VecWK2 = v.clone().Homogeneous();
    var VecWK  = new N6LVector(4);

    VecWK.x[0] = 1.0;
    VecWK.x[1] = Range * VecWK2.x[1] / (Range - ToLogAxis(-z));
    VecWK.x[2] = Range * VecWK2.x[2] / (Range - ToLogAxis(-z));
    VecWK.x[3] = z / Range;
    return VecWK.SetHomo(true);
}

// 無限遠透視射影(正規化)
function FrustumInf(v) {
    var VecWK = FrustumInf0(v);
    return VecWK.Div(Range).SetHomo(true);
}

// 逆無限遠透視射影(正規化)
function InvFrustumInf(v, z) {
    var VecWK = InvFrustumInf0(v, z);
    return VecWK.Mul(Range).SetHomo(true);
}
async function init() {
  p = [[10,10,10],[-10,10,10],[-10,-10,10],[10,-10,10],
       [10,10,-10],[-10,10,-10],[-10,-10,-10],[10,-10,-10]];
  time = 0;

  if (!navigator.gpu) throw new Error('WebGPU not available');

  adapter = await navigator.gpu.requestAdapter();
  if (!adapter) throw new Error('No suitable GPU adapter found');

  device = await adapter.requestDevice();

  canvas = document.getElementById('cnv');
  context = canvas.getContext('webgpu');

  format = navigator.gpu.getPreferredCanvasFormat();

  context.configure({
    device,
    format,
    alphaMode: 'opaque',
  });

  const shaderModule = device.createShaderModule({ code: shaderCode });
  const triangleshaderModule = device.createShaderModule({ code: triangleshaderCode });

  trianglePipeline = device.createRenderPipeline({
    layout: 'auto',
    vertex: {
      module: triangleshaderModule,
      entryPoint: 'vs_main',
      buffers: [{
        arrayStride: 2 * 4,
        attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x2' }],
      }],
    },
    fragment: {
      module: triangleshaderModule,
      entryPoint: 'fs_main',
      targets: [{ format }],
    },
    primitive: {
      topology: 'triangle-list',
    },
  });
  linePipeline = device.createRenderPipeline({
    layout: 'auto',
    vertex: {
      module: shaderModule,
      entryPoint: 'vs_main',
      buffers: [{
        arrayStride: 2 * 4,
        attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x2' }],
      }],
    },
    fragment: {
      module: shaderModule,
      entryPoint: 'fs_main',
      targets: [{ format }],
    },
    primitive: {
      topology: 'line-list',
    },
  });
  // 最大頂点数(とりあえず 64 点くらい)
  const maxVertices = 64;
  // 三角形用
  vertexBufferTri  = device.createBuffer({
    size: maxVertices * 2 * 4,
    usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
  });
  // ワイヤ用
  vertexBufferWire = device.createBuffer({
    size: maxVertices * 2 * 4,
    usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
  });
}
async function init2() {
  p2 = [[10,10,10],[-10,10,10],[-10,-10,10],[10,-10,10],
       [10,10,-10],[-10,10,-10],[-10,-10,-10],[10,-10,-10]];
  time2 = 0;

  if (!navigator.gpu) throw new Error('WebGPU not available');

  adapter2 = await navigator.gpu.requestAdapter();
  if (!adapter2) throw new Error('No suitable GPU adapter found');

  device2 = device;

  canvas2 = document.getElementById('cnv2');
  context2 = canvas2.getContext('webgpu');

  format2 = navigator.gpu.getPreferredCanvasFormat();

  context2.configure({
    device: device2,
    format: format2,
    alphaMode: 'opaque',
  });

  const shaderModule2 = device2.createShaderModule({ code: shaderCode });
  const triangleshaderModule2 = device2.createShaderModule({ code: triangleshaderCode });

  trianglePipeline2 = device2.createRenderPipeline({
    layout: 'auto',
    vertex: {
      module: triangleshaderModule2,
      entryPoint: 'vs_main',
      buffers: [{
        arrayStride: 2 * 4,
        attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x2' }],
      }],
    },
    fragment: {
      module: triangleshaderModule2,
      entryPoint: 'fs_main',
      targets: [{format: format2}],
    },
    primitive: {
      topology: 'triangle-list',
    },
  });
  linePipeline2 = device2.createRenderPipeline({
    layout: 'auto',
    vertex: {
      module: shaderModule2,
      entryPoint: 'vs_main',
      buffers: [{
        arrayStride: 2 * 4,
        attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x2' }],
      }],
    },
    fragment: {
      module: shaderModule2,
      entryPoint: 'fs_main',
      targets: [{format: format2}],
    },
    primitive: {
      topology: 'line-list',
    },
  });
  // 最大頂点数(とりあえず 64 点くらい)
  const maxVertices2 = 64;
  // 三角形用
  vertexBufferTri2  = device2.createBuffer({
    size: maxVertices2 * 2 * 4,
    usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
  });
  // ワイヤ用
  vertexBufferWire2 = device2.createBuffer({
    size: maxVertices2 * 2 * 4,
    usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
  });
}
async function init3() {
  p3 = [[10,10,10],[-10,10,10],[-10,-10,10],[10,-10,10],
       [10,10,-10],[-10,10,-10],[-10,-10,-10],[10,-10,-10]];
  time3 = 0;

  if (!navigator.gpu) throw new Error('WebGPU not available');

  adapter3 = await navigator.gpu.requestAdapter();
  if (!adapter3) throw new Error('No suitable GPU adapter found');

  device3 = device;

  canvas3 = document.getElementById('cnv3');
  context3 = canvas3.getContext('webgpu');

  format3 = navigator.gpu.getPreferredCanvasFormat();

  context3.configure({
    device: device3,
    format: format3,
    alphaMode: 'opaque',
  });

  depthTexture3 = device3.createTexture({
    size: { width: 600, height: 600, depthOrArrayLayers: 1 },
    format: 'depth32float',
    usage: GPUTextureUsage.RENDER_ATTACHMENT,
  });
  depthTextureView3 = depthTexture3.createView();


  const shaderModule3 = device3.createShaderModule({ code: phongshaderCode });
  const triangleshaderModule3 = device3.createShaderModule({ code: trianglephongshaderCode });

  trianglePipeline3 = device3.createRenderPipeline({
    layout: 'auto',
    vertex: {
      module: triangleshaderModule3,
      entryPoint: 'vs_main',
      buffers: [{
        arrayStride: 7 * 4, // 例: vec3 position + vec3 normal
        attributes: [
          { shaderLocation: 0, offset: 0,        format: 'float32x3' }, // position
          { shaderLocation: 1, offset: 3 * 4,    format: 'float32x3' }, // normal
          { shaderLocation: 2, offset: 24, format: 'float32'   }, // faceIndex
        ]
      }],
    },
    fragment: {
      module: triangleshaderModule3,
      entryPoint: 'fs_main',
      targets: [{
          format: format3,
          blend: {
              color: {
                  srcFactor: 'one',
                  dstFactor: 'zero',
                  operation: 'add',
              },
              alpha: {
                  srcFactor: 'one',
                  dstFactor: 'zero',
                  operation: 'add',
              },
          },
      }],
    },
    primitive: {
      topology: 'triangle-list',
      cullMode: 'back',
      frontFace: 'ccw',
    },
    depthStencil: {
      format: 'depth32float',
      depthWriteEnabled: true,
      depthCompare: 'less-equal',
    },
  });
  linePipeline3 = device3.createRenderPipeline({
    layout: 'auto',
    vertex: {
      module: shaderModule3,
      entryPoint: 'vs_main',
      buffers: [{
        arrayStride: 3 * 4,
        attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x3' }],
      }],
    },
    fragment: {
      module: shaderModule3,
      entryPoint: 'fs_main',
      targets: [{
          format: format3,
          blend: {
              color: {
                  srcFactor: 'one',
                  dstFactor: 'zero',
                  operation: 'add',
              },
              alpha: {
                  srcFactor: 'one',
                  dstFactor: 'zero',
                  operation: 'add',
              },
          },
      }],
    },
    primitive: {
      topology: 'line-list',
    },
    depthStencil: {
      format: 'depth32float',
      depthWriteEnabled: false,
      depthCompare: 'less-equal',
    },
  });


  // 最大頂点数(とりあえず 64 点くらい)
  const maxVertices3 = 64;
  const maxVertices3T = 128;  // 36 より十分大きく
  const stride3T = 28;        // float32x7 = 28 bytes
  // 三角形用
  vertexBufferTri3  = device3.createBuffer({
    size: maxVertices3T * stride3T,
    usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
  });
  // ワイヤ用
  vertexBufferWire3 = device3.createBuffer({
    size: maxVertices3 * 3 * 4,
    usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
  });

}
async function init4() {
  p4 = [[10,10,10],[-10,10,10],[-10,-10,10],[10,-10,10],
       [10,10,-10],[-10,10,-10],[-10,-10,-10],[10,-10,-10]];
  time4 = 0;

  if (!navigator.gpu) throw new Error('WebGPU not available');

  adapter4 = await navigator.gpu.requestAdapter();
  if (!adapter4) throw new Error('No suitable GPU adapter found');

  device4 = device;

  canvas4 = document.getElementById('cnv4');
  context4 = canvas4.getContext('webgpu');

  format4 = navigator.gpu.getPreferredCanvasFormat();

  context4.configure({
    device: device4,
    format: format4,
    alphaMode: 'opaque',
  });

  depthTexture4 = device4.createTexture({
    size: { width: 600, height: 600, depthOrArrayLayers: 1 },
    format: 'depth32float',
    usage: GPUTextureUsage.RENDER_ATTACHMENT,
  });
  depthTextureView4 = depthTexture4.createView();


  const shaderModule4 = device4.createShaderModule({ code: phongshaderCode });
  const triangleshaderModule4 = device4.createShaderModule({ code: trianglephongshaderCode });

  trianglePipeline4 = device4.createRenderPipeline({
    layout: 'auto',
    vertex: {
      module: triangleshaderModule4,
      entryPoint: 'vs_main',
      buffers: [{
        arrayStride: 7 * 4, // 例: vec3 position + vec3 normal
        attributes: [
          { shaderLocation: 0, offset: 0,        format: 'float32x3' }, // position
          { shaderLocation: 1, offset: 3 * 4,    format: 'float32x3' }, // normal
          { shaderLocation: 2, offset: 24, format: 'float32'   }, // faceIndex
        ]
      }],
    },
    fragment: {
      module: triangleshaderModule4,
      entryPoint: 'fs_main',
      targets: [{
          format: format4,
          blend: {
              color: {
                  srcFactor: 'one',
                  dstFactor: 'zero',
                  operation: 'add',
              },
              alpha: {
                  srcFactor: 'one',
                  dstFactor: 'zero',
                  operation: 'add',
              },
          },
      }],
    },
    primitive: {
      topology: 'triangle-list',
      cullMode: 'back',
      frontFace: 'ccw',
    },
    depthStencil: {
      format: 'depth32float',
      depthWriteEnabled: true,
      depthCompare: 'less-equal',
    },
  });
  linePipeline4 = device4.createRenderPipeline({
    layout: 'auto',
    vertex: {
      module: shaderModule4,
      entryPoint: 'vs_main',
      buffers: [{
        arrayStride: 3 * 4,
        attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x3' }],
      }],
    },
    fragment: {
      module: shaderModule4,
      entryPoint: 'fs_main',
      targets: [{
          format: format4,
          blend: {
              color: {
                  srcFactor: 'one',
                  dstFactor: 'zero',
                  operation: 'add',
              },
              alpha: {
                  srcFactor: 'one',
                  dstFactor: 'zero',
                  operation: 'add',
              },
          },
      }],
    },
    primitive: {
      topology: 'line-list',
    },
    depthStencil: {
      format: 'depth32float',
      depthWriteEnabled: false,
      depthCompare: 'less-equal',
    },
  });


  // 最大頂点数(とりあえず 64 点くらい)
  const maxVertices4 = 64;
  const maxVertices4T = 128;  // 36 より十分大きく
  const stride4T = 28;        // float32x7 = 28 bytes
  // 三角形用
  vertexBufferTri4  = device4.createBuffer({
    size: maxVertices4T * stride4T,
    usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
  });
  // ワイヤ用
  vertexBufferWire4 = device4.createBuffer({
    size: maxVertices4 * 3 * 4,
    usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
  });

}
async function init5() {
  p5 = [[10,10,10],[-10,10,10],[-10,-10,10],[10,-10,10],
       [10,10,-10],[-10,10,-10],[-10,-10,-10],[10,-10,-10]];
  time5 = 0;

  if (!navigator.gpu) throw new Error('WebGPU not available');

  adapter5 = await navigator.gpu.requestAdapter();
  if (!adapter5) throw new Error('No suitable GPU adapter found');

  device5 = await adapter5.requestDevice();

  canvas5 = document.getElementById('cnv5');
  context5 = canvas5.getContext('webgpu');

  format5 = navigator.gpu.getPreferredCanvasFormat();

  context5.configure({
    device: device5,
    format: format5,
    alphaMode: 'opaque',
  });

  const shaderModule5 = device5.createShaderModule({ code: shaderCode });
  const triangleshaderModule5 = device5.createShaderModule({ code: triangleshaderCode });

  trianglePipeline5 = device5.createRenderPipeline({
    layout: 'auto',
    vertex: {
      module: triangleshaderModule5,
      entryPoint: 'vs_main',
      buffers: [{
        arrayStride: 2 * 4,
        attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x2' }],
      }],
    },
    fragment: {
      module: triangleshaderModule5,
      entryPoint: 'fs_main',
      targets: [{ format }],
    },
    primitive: {
      topology: 'triangle-list',
    },
  });
  linePipeline5 = device5.createRenderPipeline({
    layout: 'auto',
    vertex: {
      module: shaderModule5,
      entryPoint: 'vs_main',
      buffers: [{
        arrayStride: 2 * 4,
        attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x2' }],
      }],
    },
    fragment: {
      module: shaderModule5,
      entryPoint: 'fs_main',
      targets: [{ format }],
    },
    primitive: {
      topology: 'line-list',
    },
  });
  // 最大頂点数(とりあえず 64 点くらい)
  const maxVertices5 = 64;
  // 三角形用
  vertexBufferTri5  = device5.createBuffer({
    size: maxVertices5 * 2 * 4,
    usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
  });
  // ワイヤ用
  vertexBufferWire5 = device5.createBuffer({
    size: maxVertices5 * 2 * 4,
    usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
  });
}
function onretry() {
  zpos4 = 0;
  zpos5 = 0;
}
async function enter() {

//テスト結果は良好です方角比/z絶対値と無限透視変換前後のそれぞれの比にも注目してください
var v00 = new N6LVector([1, 15, 10, -50],true);
var v01 = FrustumInf(v00);
var v02 = InvFrustumInf(v01, v00.x[3]);
console.log(v00.Str()); //true,4,1,15,10,-50
console.log(v01.Str()); //true,4,1,0.6110382101210349,0.4073588067473566,-50
console.log(v02.Str()); //true,4,1,15,10,-50
var v10 = new N6LVector([1, -200, 500, -250],true);
var v11 = FrustumInf(v10);
var v12 = InvFrustumInf(v11, v10.x[3]);
console.log(v10.Str()); //true,4,1,-200,500,-250
console.log(v11.Str()); //true,4,1,-1.6566538523413499,4.141634630853375,-250
console.log(v12.Str()); //true,4,1,-200,500,-250

  await init();
  await init2();
  await init3();
  await init4();
  await init5();
  TMan.add();
  TMan.add();
  TMan.add();
  TMan.add();
  TMan.add();
  TMan.timer[0].setalerm(function() { GLoop(0,1); }, 50);
  TMan.timer[1].setalerm(function() { GLoop(1,2); }, 50);
  TMan.timer[2].setalerm(function() { GLoop(2,3); }, 50);
  TMan.timer[3].setalerm(function() { GLoop(3,4); }, 50);
  TMan.timer[4].setalerm(function() { GLoop(4,5); }, 50);
  return true;
}







<< グレゴリオ暦日付計算ライブラリ化 : 無限透視射影と対数距離射影 >>





戻る