threejs(10)-WEBGL と GPU レンダリングの原則 (困難さ) は後で要約できます

1. レンダリングパイプライン

WebGLとは

WebGL (Web Graphics Library) は、プラグインを必要とせずに、互換性のある Web ブラウザーで高性能のインタラクティブな 3D および 2D グラフィックスをレンダリングする JavaScript API です。WebGL は、OpenGL ES 2.0 との一貫性が高く、HTML5 要素内で使用できる API を導入することでこれを実現します。この一貫性により、API はユーザーのデバイスが提供するハードウェア グラフィック アクセラレーションを利用できるようになります。

WebGL開発の歴史

WebGL の開発は 2006 年まで遡ることができます。WebGL は Mozilla 従業員の Vladimir Vkisievich による Canvas 3D 実験プロジェクトから生まれました。Canvas 3D のプロトタイプは 2006 年に初めて実証されました。このテクノロジーは 2007 年に開始されました。FireFox および Opera ブラウザに実装されました。年末。2009 年初頭に、KhronosGroup アライアンスは WebGL ワーキング グループを設立し、最初のメンバーには Apple、Google、Mozilla、Opera などが含まれていました。WebGL 1.0 仕様は 2011 年 3 月にリリースされました。WebGL 2 仕様の開発は 2013 年に始まり、最終的に 2017 年 1 月に完成しました。WebGL 2 仕様は、Firefox 51、Chrome 56、および Opera 43 で初めてサポートされました。

レンダリングパイプライン

Webgl のレンダリングは、基盤となる GPU のレンダリング機能に依存します。したがって、WEBGL レンダリング プロセスは、GPU 内のレンダリング パイプラインと一貫性があります。レンダリング パイプラインの機能は、3D モデルを 2D 画像に変換することです。

初期のレンダリング パイプラインはプログラム可能ではなく、固定レンダリング パイプラインと呼ばれ、詳細な作業プロセスが固定されており、変更する場合は一部のパラメーターを調整する必要がありました。最新の GPU に含まれるレンダリング パイプラインは、プログラム可能なレンダリング パイプラインです。GLSL シェーダ言語をプログラミングすることで、レンダリング ステージの詳細を制御できます。簡単に言うと、シェーダを使用して、キャンバス内の各ピクセルを処理し、Aさまざまなクールなエフェクトを生成できます。
ここに画像の説明を挿入します
ここに画像の説明を挿入します

2. レンダリングプロセスを詳細に説明する

頂点シェーダ

WebGL は GPU を扱います。GPU 上で実行されるコードは 1 組のシェーダーであり、1 つは頂点シェーダー、もう 1 つはフラグメント シェーダーです。シェーダ プログラムが呼び出されるたびに、最初に頂点シェーダが実行され、次にフラグメント シェーダが実行されます。
ここに画像の説明を挿入します
頂点シェーダの仕事は、通常は次の形式でクリップ空間座標値を生成することです。

const vertexshaderSource = `
	attribute vec3 position; 
	void main() {
		gl_Position = vec4(position,1);
	}
`

(頂点) シェーダは頂点ごとに 1 回呼び出されます。各呼び出しでは特別なグローバル変数 gl_Position を設定する必要があります。この変数の値はクリッピング スペース座標値です。ここで何人かの学生が、クリッピング スペースの座標値は何ですかと尋ねました。? ?
実は、以前にも言いましたが、もう一度言います。
クリッピング空間座標とは何ですか? つまり、キャンバスがどれほど大きくても、トリミング座標の座標範囲は常に -1 から 1 です。下の写真を見てください。

ここに画像の説明を挿入します
頂点シェーダーを 1 回実行すると、gl_Position は (-0.5, -0.5, 0, 1) になります。常に Vec4 であることに注意してください。簡単に理解すると、これは x、y、
z、w に対応します。他に何も使用しない場合でも、デフォルト値を設定します。これが画面に転送される 3D モデルと呼ばれるものです。
頂点シェーダに必要なデータは以下の 4 つの方法で取得できます。

  1. 属性 属性 (バッファからデータを読み取る)
  2. ユニフォーム グローバル変数 (通常、オブジェクトに対する全体的な変更、回転、スケーリングを行うために使用されます)
  3. テクスチャ テクスチャ (ピクセルまたはテクスチャからデータを取得)
  4. 変数変数 (頂点シェーダーの変数をフラグメント シェーダーに渡します)

プリミティブのアセンブリとラスター化

プリミティブとは何ですか?
さまざまなグラフィック要素を記述する関数はプリミティブと呼ばれ、幾何学的要素を記述する関数は幾何学プリミティブ (点、線分、または多角形) と呼ばれます。点と線は最も単純な幾何学プリミティブであり、頂点シェーダによって座標が計算された後、それらは結合されたプリミティブに組み立てられます。
一般的な説明: プリミティブは点、線分、または多角形です。
プリミティブアセンブリとは何ですか?
簡単に理解すると、設定した頂点、色、テクスチャ、その他のコンテンツを組み立ててレンダリング可能なポリゴンを作成するプロセスです。
アセンブリのタイプは次によって異なります: 最後に図面で選択した形状のタイプ

gl.drawArrays(gl.TRIANGLES, 0, 3)

三角形の場合、頂点シェーダーが 3 回実行されます

ラスタライズ

ラスタライズとは:
プリミティブを組み立て、ピクセルを計算して塗りつぶし、目に見えない部分を削除し、可視範囲内にない部分を切り出すことによって生成されるポリゴンです。最後に、カラー データを含む可視グラフィックスが生成され、描画されます。
ラスタライズプロセス図:

ここに画像の説明を挿入します

カリングとクリッピング

ここに画像の説明を挿入します
カリング: 日常生活では、不透明なオブジェクトの場合、裏側は観察者には見えません。同様に、webgl では、オブジェクトの裏側を非表示に設定することもできます。これにより、レンダリング プロセス中に、非表示の部分が削除され、描画に参加しなくなります。レンダリングのオーバーヘッドを節約します。

ここに画像の説明を挿入します
クリッピング: 日常生活では、テレビを見たり、物体を観察したりするときに、視覚範囲があり、視覚範囲外のものを見ることはできません。同様に、グラフィックスが生成された後、一部の部分が表示範囲外になる場合があり、その部分はクリップされて描画に参加しません。これを使用してパフォーマンスを向上させます。これが視錐台で、 ■ の範囲内に見えるものだけが描画されます。
ここに画像の説明を挿入します

フラグメントシェーダ

ここに画像の説明を挿入します
ここに画像の説明を挿入します
ラスタライズ後、各ピクセルには色、深度、およびテクスチャ データが含まれます。これはフラグメントと呼ばれます。
ヒント: 各ピクセルの色は、ラスタライズ段階で生成されたフラグメントを受け取るために、フラグメント シェーダの gl_FragColor によって提供されます
。ラスタライズ段階では、各フラグメントの色情報が計算され、この段階でフラグメントが 1 つずつ選択され、処理されたフラグメントが後続のステージに渡されます。フラグメント シェーダーの実行回数は、グラフィックスに含まれるフラグメントの数によって決まります。

フラグメントごとの選択により、
テンプレート テストと深度テストを通じてフラグメントを表示するかどうかが決定されます。テスト プロセス中に、一部の不要なフラグメント コンテンツが破棄され、描画可能な 2 次元イメージが生成されて表示されます。

  • 深度テスト: Z 軸の値をテストするもので、値が小さいフラグメントの内容が値が大きい値をカバーします。(近くの物体が遠くの物体をブロックするという事実と同様です)。
  • テンプレート テスト: 観察者の観察動作をシミュレートし、鏡観察に接続できます。画像に現れるすべてのフラグメントをマークし、最後にマークされたコンテンツのみを描画します。

3. WEBGL は三角形を描画します

ここに画像の説明を挿入します
ここに画像の説明を挿入します

CANVASの初期化

新しい WebGL キャンバスを作成する

<canvas id="webgl" width="500" height="500"></canvas>

WebGL コンテキストを作成します。

const gl = document.getElementById('webgl').getContext('webgl')

シェーダプログラムを作成する

シェーダ プログラムのコードは実際には繰り返されます。以下の図を見て、どのような手順が必要かを確認してください。
ここに画像の説明を挿入します
次に、次のフローチャートに従います。ステップバイステップで実行してみましょう。

シェーダの作成

 const vertexShader = gl.createShader(gl.VERTEX_SHADER)
 const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)

gl.VERTEX_SHADER と gl.FRAGMENT_SHADER は、それぞれ頂点シェーダーとフラグメント シェーダーを表すグローバル変数です。

データソースをバインドする

名前が示すように、データ ソース、つまりシェーダー コードです。
シェーダー コードを記述する方法は数多くあります。

  1. スクリプトタグタイプ notjs を使用して次のように記述します
  2. テンプレート文字列 (私はこの種の推奨事項を好みます)
    まず頂点シェーダーを書きましょう:
const vertexShaderSource = `
    attribute vec4 a_position;
    void main() {
        gl_Position = a_position;
    }
 `

頂点シェーダには main 関数が必要です。これは厳密に型指定された言語です。必ずセミコロンを追加してください。js ではありません。私のシェーダー コードは、vec4 の頂点位置を定義し、それを gl_Position に渡すという非常に単純なコードです
ここでは a_position をこのようにする必要がありますか? ?
これは実際には次のようになります。変数に名前を付けるときは、通常、次のようにプレフィックスを使用して、それが属性であるか、グローバル変数であるか、テクスチャであるかを区別します。

uniform mat4 u_mat;

行列を表します。そうでなくても大丈夫です。ただし、プロフェッショナルとしてバグを避けてください。
次に、フラグメント シェーダーを作成します。

const fragmentShaderSource = `
    void main() {
        gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    }
`

これは実際には非常に簡単に理解でき、各ピクセルの色は赤であり、gl_FragColor は実際には色の表現である rgba に対応します。
データ ソースを取得したら、バインドを開始します。

// 创建着色器
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
//绑定数据源
gl.shaderSource(vertexShader, vertexShaderSource)
gl.shaderSource(fragmentShader, fragmentShaderSource)

とても簡単な答えではないでしょうか?はははは、あなたなら知っているはずです。

シェーダーの背後で行われるいくつかの操作

実際、シェーダーのコンパイル、シェーダーのバインド、シェーダー プログラムの接続、およびシェーダー プログラムの使用はすべて API によって処理されます。詳細については説明せず、コードだけを見ていきます。

// 编译着色器
gl.compileShader(vertexShader)
gl.compileShader(fragmentShader)
// 创建着色器程序
const program = gl.createProgram()
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// 链接 并使用着色器
gl.linkProgram(program)
gl.useProgram(program)

このようにしてシェーダプログラムが作成されました。
ここで誰かが、自分が作成したシェーダーが正しいか間違っているかをどうやって知ることができるのかと再度質問しました。私はただ非常に不注意な人間なのでしょうか?? ? OK、彼が来ました。デバッグ方法:

const success = gl.getProgramParameter(program, gl.LINK_STATUS)
if (success) {
    
    
  gl.useProgram(program)
  return program
}
console.error(gl.getProgramInfoLog(program), 'test---')
gl.deleteProgram(program)

getProgramParameter メソッドは、シェーダーの glsl 言語が正しく記述されているかどうかを判断するために使用され、その後、ログ記録と同様の getProgramInfoLog メソッドを通じて確認できます。

データはバッファに保存されます

シェーダーの場合、必要なのはデータだけですよね?
Attributes 属性は、上記の頂点シェーダーを作成するときに使用され、この変数がバッファーからデータを読み取る必要があることを示しています。次に、データをバッファーに保存します。
まず頂点バッファ オブジェクト (VBO) を作成します。

const buffer = gl.createBuffer()

gl.createBuffer() 関数はバッファを作成し、識別子を返します。次に、このバッファを WebGL にバインドする必要があります。

gl.bindBuffer(gl.ARRAY_BUFFER, buffer)

gl.bindBuffer() 関数は、識別子バッファを現在のバッファに設定し、bindBuffer が別の現在のバッファにバインドされるまで、後続のすべてのデータが現在のバッファに置かれます。
新しい配列を作成し、データをバッファーに保存します。

const data = new Float32Array([0.0, 0.0, -0.3, -0.3, 0.3, -0.3])
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)

JavaScript と WebGL 間の通信はバイナリである必要があり、従来のテキスト形式ではできないため、ここでは ArrayBuffer オブジェクトを使用してデータをバイナリに変換します。頂点データは浮動小数点数であるため、精度をあまり高くする必要はありませんこれは、JavaScript と GPU の間で大量のデータをリアルタイムで交換する効率的な方法です。
gl.STATIC_DRAW は、データ ストレージ領域の使用を指定します。キャッシュ領域の内容は頻繁に使用される可能性がありますが、変更されません
。gl.DYNAMIC_DRAW は、キャッシュ領域の内容が頻繁に使用され、頻繁に変更されることを示します。
gl.STREAM_DRAW は、バッファの内容が頻繁に使用されない可能性があることを示します

バッファからデータを読み取る

GLSL シェーディング プログラムへの唯一の入力は、属性値 a_position です。最初に行う必要があるのは、作成したばかりの GLSL シェーダ プログラムからこのプロパティ値の場所を見つけることです。

const aposlocation = gl.getAttribLocation(program, 'a_position')

次に、前に準備したバッファからデータを取得してシェーダーの属性に渡す方法を WebGL に指示する必要があります。まず、対応する属性を有効にする必要があります

gl.enableVertexAttribArray(aposlocation)

最後に、データがバッファから読み取られ、アクティブ化されたアポストロケーションの位置にバインドされます。

gl.vertexAttribPointer(aposlocation, 2, gl.FLOAT, false, 0, 0)

gl.vertexAttribPointer() 関数には 6 つのパラメータがあります。

  1. 読み取ったデータはどこにバインドされるべきですか?
  2. 毎回キャッシュからフェッチされるデータの数、または各頂点が持つデータのユニット数を示します。値の範囲は 1 ~ 4 です。ここでは毎回2つのデータを取り出すことになりますが、先ほど頂点で宣言した6つのデータはまさに3つの頂点の2次元座標となります。
  3. データ型を示します。オプションのパラメータには、gl.BYTE 符号付き 8 ビット整数、gl.SHORT 符号付き 16 ビット整数、gl.UNSIGNED_BYTE 符号なし 8 ビット整数、gl.UNSIGNED_SHORT 符号なし 16 ビット整数、gl が含まれます。FLOAT32 ビット IEEE標準の浮動小数点数。
  4. 整数値を特定の範囲に正規化するかどうかを示します。このパラメータは、gl.FLOAT 型では無効です。
  5. データがフェッチされるたびに、最後に取得されるまでのビット数が何ビット離れているかを示します。0 は、データがフェッチされるたびに、最後のデータの位置のすぐ隣にあることを示します。間隔は WebGL によって自動的に計算されます。
  6. データが最初にフェッチされるときのオフセットを表します。これはバイト サイズの倍数である必要があります。0 は最初から開始することを意味します。

レンダリング

シェーダ プログラムとデータの準備ができたので、レンダリングを始めます。レンダリングする前に、2D キャンバスと同じアクションを実行してキャンバスをクリアします。

// 清除canvas
gl.clearColor(0, 0, 0, 0)
gl.clear(gl.COLOR_BUFFER_BIT)

それぞれ r、g、b、alpha (赤、緑、青、アルファ) の値に対応する 0、0、0、0 でキャンバスをクリアするため、この例ではキャンバスを透明にします。
三角形の描画を有効にします。

gl.drawArrays(gl.TRIANGLES, 0, 3)

  1. 最初のパラメータは描画のタイプを示します
  2. 2 番目のパラメータは、どの頂点から描画を開始するかを示します。
  3. 3 番目のパラメーターは、描画するポイントの数を示します。バッファーには合計 6 つのデータがあり、毎回 2 つが取得され、合計 3 ポイントになります。
    描画タイプはいくつかあります。画像の表示:
    ここに画像の説明を挿入します
    ここでは、画像が次のとおりであるかどうかを確認します。赤い三角形:

すべてのコード

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      * {
    
    
        margin: 0;
        padding: 0;
      }
      canvas {
    
    
        width: 100vw;
        height: 100vh;
        display: block;
      }
    </style>
  </head>
  <body>
    <canvas></canvas>

    <script>
      // 获取canvas元素
      let canvas = document.querySelector("canvas");
      //   设置canvas的宽高
      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;
      // 获取webgl上下文
      let gl = canvas.getContext("webgl");

      // 创建顶点着色器
      const vShader = gl.createShader(gl.VERTEX_SHADER);

      // 顶点着色器源码
      gl.shaderSource(
        vShader,
        `
        attribute vec4 v_position;
        void main(){
          gl_Position = v_position;
        }
      `
      );
      //   编译顶点着色器
      gl.compileShader(vShader);

      // 创建片元着色器
      const fShader = gl.createShader(gl.FRAGMENT_SHADER);
      // 片元着色器源码
      gl.shaderSource(
        fShader, // vec4-> 参数rgba
        `
          void main(){
            gl_FragColor = vec4(1.0,0.0,0.0,1.0);
          }
        `
      );

      // 编译片元着色器
      gl.compileShader(fShader);

      // 创建着色器程序链接顶点着色器和片元着色器
      const program = gl.createProgram();
      //   添加顶点着色器
      gl.attachShader(program, vShader);
      //   添加片元着色器
      gl.attachShader(program, fShader);
      //   链接着色器程序
      gl.linkProgram(program);
      //   使用着色器程序
      gl.useProgram(program);

      // 创建顶点数据
      const position = gl.getAttribLocation(program, "v_position");
      //   创建缓冲区
      const pBuffer = gl.createBuffer();
      //   绑定缓冲区
      gl.bindBuffer(gl.ARRAY_BUFFER, pBuffer);

      //   设置顶点数据
      gl.bufferData(
        gl.ARRAY_BUFFER,
        new Float32Array([0.0, 0.5, -0.5, -0.5, 0.5, -0.5]),
        gl.STATIC_DRAW // 静态绘制
      );

      // 将顶点数据提供给到atttribute变量
      gl.vertexAttribPointer(
        //告诉attribute变量从哪里获取数据
        position,
        2, // 每次迭代提供2个单位的数据
        gl.FLOAT, // 每个单位的数据类型是32位浮点型
        false, // 不需要归一化数据
        0, // 0 步长
        0 // 从缓冲区的哪个位置开始读取数据
      );

      //   开启attribute变量
      gl.enableVertexAttribArray(position);

      //   绘制
      gl.drawArrays(gl.TRIANGLES, 0, 3);
    </script>
  </body>
</html>

作成したデータはこんな感じです。
キャンバスの幅は500*500です。実際に変換したデータはこんな感じです

0,0  ====>  0,0 
-0.3, -0.3 ====> 175, 325
0.3, -0.3 ====>  325, 325

4. スケーリング行列、一様変数および変動変数

アニメーションのズームインとズームアウトを実行する

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <style>
    * {
    
    
      margin: 0;
      padding: 0;
    }

    canvas {
    
    
      width: 100vw;
      height: 100vh;
      display: block;
    }
  </style>
</head>

<body>
  <canvas id="canvas"></canvas>
  <script>
    const canvasEl = document.querySelector("#canvas");
    canvasEl.width = document.body.clientWidth; // 设置 canvas 画布的宽度
    canvasEl.height = document.body.clientHeight; // 设置 canvas 画布的高度

    const gl = canvasEl.getContext("webgl"); // 获取 WebGL 上下文
    gl.viewport(0,0,canvasEl.width,  canvasEl.height )

    // 创建顶点着色器 语法 gl.createShader(type) 此处 type 为枚举型值为 gl.VERTEX_SHADER 或 gl.FRAGMENT_SHADER 两者中的一个
    const vShader = gl.createShader(gl.VERTEX_SHADER);
    // 编写顶点着色器的 GLSL 代码 语法 gl.shaderSource(shader, source); shader - 用于设置程序代码的 webglShader(着色器对象) source - 包含 GLSL 程序代码的字符串
    gl.shaderSource(
      vShader,
      `
          attribute vec4 a_position;
          uniform mat4 u_Mat;
          varying vec4 v_Color;
          void main() {
            gl_Position = u_Mat * a_Position; // 设置顶点位置
            v_Color = gl_Position;
          }
        `
    );
    gl.compileShader(vShader); // 编译着色器代码

    const fShader = gl.createShader(gl.FRAGMENT_SHADER);

    gl.shaderSource(
      fShader,
      `
          precision mediump float;
          varying vec4 v_Color
          void main() {
            gl_FragColor = v_Color; // 设置片元颜色
          }
        `
    ); // 编写片元着色器代码
    gl.compileShader(fShader); // 编译着色器代码

    // 创建一个程序用于连接顶点着色器和片元着色器
    const program = gl.createProgram();
    gl.attachShader(program, vShader); // 添加顶点着色器
    gl.attachShader(program, fShader); // 添加片元着色器
    gl.linkProgram(program); // 连接 program 中的着色器

    gl.useProgram(program); // 告诉 WebGL 用这个 program 进行渲染

    // //   用于指定uniform变量在 GPU 内存中的位置
    // const color = gl.getUniformLocation(program, "v_Color");
    // // 获取 f_color 变量位置
    // gl.uniform4f(color, 0.93, 0, 0.56, 1); // 设置它的值

    // 获取 v_position 位置
    const pBuffer = gl.createBuffer();

    // 创建一个顶点缓冲对象,返回其 id,用来放三角形顶点数据,
    gl.bindBuffer(gl.ARRAY_BUFFER, pBuffer);
    // 将这个顶点缓冲对象绑定到 gl.ARRAY_BUFFER
    // 后续对 gl.ARRAY_BUFFER 的操作都会映射到这个缓存
    gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array([0, 0.5, 0.5, 0, -0.5, -0.5]), // 三角形的三个顶点
      // 因为会将数据发送到 GPU,为了省去数据解析,这里使用 Float32Array 直接传送数据
      gl.STATIC_DRAW // 表示缓冲区的内容不会经常更改
    );
    // 将顶点数据加入的刚刚创建的缓存对象
    const a_Position = gl.getAttribLocation(program, "a_Position");

    gl.vertexAttribPointer(
      // 告诉 OpenGL 如何从 Buffer 中获取数据
      a_Position, // 顶点属性的索引
      2, // 组成数量,必须是 1,2,3 或 4。我们只提供了 x 和 y
      gl.FLOAT, // 每个元素的数据类型
      false, // 是否归一化到特定的范围,对 FLOAT 类型数据设置无效
      0, // stride 步长 数组中一行长度,0 表示数据是紧密的没有空隙,让 OpenGL 决定具体步长
      0 // offset 字节偏移量,必须是类型的字节长度的倍数。
    );
    gl.enableVertexAttribArray(a_Position);
    // 开启 attribute 变量额,使顶点着色器能够访问缓冲区数据


    const scale = {
    
    
      x: 0.5,
      y: 0.5,
      z: 0.5
    };

    // const mat = new Float32Array([
    //   scale.x, 0.0, 0.0, 0.0,
    //   0.0, scale.x, 0.0, 0.0,
    //   0.0, 0.0, scale.x, 0.0,
    //   0.0, 0.0, 0.0, 1.0,
    // ])
    // const u_Mat = gl.getUniformLocation(program, 'u_Mat');
    // gl.uniformMatrix4fv(u_Mat, false, mat)

    // gl.clearColor(0.0, 0.0, 0.0, 0.0); // 设置清空颜色缓冲时的颜色值
    // gl.clear(gl.COLOR_BUFFER_BIT); // 清空颜色缓冲区,也就是清空画布
    // // 语法 gl.drawArrays(mode, first, count); mode - 指定绘制图元的方式 first - 指定从哪个点开始绘制 count - 指定绘制需要使用到多少个点
    // gl.drawArrays(gl.TRIANGLES, 0, 3);

    function animate() {
    
    
      scale.x -= 0.01;
      const mat = new Float32Array([
        scale.x, 0.0, 0.0, 0.0,
        0.0, scale.x, 0.0, 0.0,
        0.0, 0.0, scale.x, 0.0,
        0.0, 0.0, 0.0, 1.0,
      ])
      const u_Mat = gl.getUniformLocation(program, 'u_Mat');
      gl.uniformMatrix4fv(u_Mat, false, mat);
      gl.drawArrays(gl.TRIANGLES, 0, 3);
      requestAnimationFrame(animate)
    }
    animate()
  </script>
</body>

</html>

5. シェーダglslの基本仕様

フラグメントシェーダーとは何ですか?
私たちはシェーダーをグラフィックスのグーテンベルク印刷機と表現します。なぜ?さらに重要なのは、シェーダーとは何ですか?

ここに画像の説明を挿入します
コンピューター描画の経験がすでにある場合は、希望のイメージを形成するまで、円、次に長方形、線、いくつかの三角形を描画するプロセスをご存知でしょう。このプロセスは、手紙や本を手書きするのと非常に似ており、タスクを次々と実行するための一連の指示です。
シェーダも命令のセットですが、命令は画面上のピクセルごとに 1 回実行されます。これは、作成するコードは、画面上のピクセルの位置に応じて異なる動作をする必要があることを意味します。タイプライターと同じように、プログラムは位置を取得して色を返す関数として機能し、コンパイルすると非常に高速に実行されます。
ここに画像の説明を挿入します

なぜシェーダーはこれほど高速なのでしょうか?

この疑問に答えるために、並列処理の素晴らしさを紹介します。
コンピューターの CPU を大きな産業用パイプとして考えてください。工場の生産ラインのように、すべてのタスクがその中を流れます。一部のタスクは他のタスクよりも大きいため、処理にはより多くの時間と労力が必要になります。より多くの処理能力が必要だと私たちは言います。コンピューターのアーキテクチャにより、ジョブは連続して実行する必要があり、各ジョブは一度に 1 つずつ完了する必要があります。現代のコンピューターには通常、これらのパイプのように機能する 4 つのプロセッサーのグループがあり、タスクを次々に完了してスムーズな動作を維持します。各チューブはスレッドとも呼ばれます。
ここに画像の説明を挿入します
ビデオ ゲームやその他のグラフィック アプリケーションは、他のプログラムよりも多くの処理能力を必要とします。グラフィック コンテンツのため、ピクセルごとに大規模な操作を行う必要があります。画面上のすべてのピクセルを計算する必要があり、3D ゲームではジオメトリと遠近法も計算する必要があります。
パイプラインとタスクのメタファーに戻りましょう。画面上のすべてのピクセルは、単純な小さなタスクを表します。ピクセルごとの個々のタスクは CPU にとって問題ではありませんが、(ここに問題があります) 画面上のすべてのピクセルに対して小さなタスクを実行する必要があります。これは、古い 800x600 画面では 1 フレームあたり 480,000 ピクセルを処理する必要があったことを意味します。これは、1 秒あたり 1,4400,000 回の計算を意味します。はい!これはマイクロプロセッサに過負荷を与えるほどの問題です。1 秒あたり 60 フレームで動作する最新の 2880x1800 Retina ディスプレイでは、1 秒あたり合計 3 億 1,104 万回の計算が実行されます。グラフィックスエンジニアはこの問題をどのように解決するのでしょうか?
ここに画像の説明を挿入します
このとき、並列処理が優れたソリューションになります。大きくて強力なマイクロプロセッサやパイプラインをいくつか用意するよりも、多数の小さなマイクロプロセッサを同時に並行して実行する方が賢明です。これはグラフィック プロセッサ ユニット (GPU) です。

ここに画像の説明を挿入します

小さなマイクロプロセッサをパイプのテーブル、各ピクセルのデータをピンポン玉と考えてください。1 秒あたり 14,400,000 個のピンポン球は、ほぼすべてのパイプを詰まらせる可能性があります。しかし、800x600 のマイクロパイプ テーブルは、1 秒あたり 30 個の 480,000 ピクセル波の受信を問題なく処理できます。これは高解像度でも同じです。並列ハードウェアが多いほど、より大きなストリームを管理できます。
GPU のもう 1 つの「スーパーパワー」は、ハードウェアによって加速される特別な数学関数です。そのため、複雑な数学演算はソフトウェアではなくマイクロチップによって直接解決されます。これは、電気と同じくらい高速な超高速三角関数演算と行列演算を意味します。

GLSLとは何ですか?

GLSL は openGL Shading Language の略で、次の章で説明するように、シェーダ プログラムの特定の標準です。ハードウェアとオペレーティング システムに応じて、他のタイプのシェーダがあります。ここでは、Khronos Group によって規定されている openGL 仕様を使用します。OpenGL の歴史を理解すると、その奇妙な規約のほとんどを理解するのに役立ちます。openglbook.com/ chapter-0-preface-what-is-opengl.html を参照することをお勧めします。

シェーダーが痛みを伴うことで有名なのはなぜですか?

ベンおじさんが「大いなる力には大いなる責任が伴う」と言ったように、並列コンピューティングはこのルールに従っており、GPU の強力なアーキテクチャ設計には独自の制約と制限があります。
並列実行するには、各パイプラインまたはスレッドが他のすべてのスレッドから独立している必要があります。スレッドは他のスレッドの作業に対して盲目であると言います。この制限は、すべてのデータが同じ方向に流れる必要があることを意味します。したがって、別のスレッドの結果をチェックしたり、入力データを変更したり、あるスレッドの結果を別のスレッドに渡したりすることはできません。スレッド間の通信を許可すると、データの整合性が危険にさらされます。
また、GPU は並列マイクロプロセッサ (パイプライン) をビジー状態に保ち、それらが解放されると、処理する新しい情報を受け取ります。スレッドが直前に何をしていたかを知ることは不可能です。オペレーティング システムの UI からボタンを描画し、ゲーム内で空の一部をレンダリングし、電子メールのテキストを表示する可能性があります。各スレッドは盲目であるだけでなく、記憶もありません。位置に基づいて結果をピクセルごとに変更する一般的な関数をコーディングするために必要な抽象化に加えて、ブラインド制約とメモリレス制約により、シェーダーはジュニア プログラマーの間で人気が低くなります。

「Hello world!」は、新しい言語を学ぶ最初の例となることがよくあります。これは非常に単純な 1 行のプログラムです。これは、温かい歓迎であると同時に、プログラミングがもたらす可能性についてのメッセージでもあります。
ただし、GPU の世界では、最初のステップでテキスト行をレンダリングするのは非常に難しいため、代わりに明るいウェルカム カラーを選択します。楽しみましょう!

#ifdef GL_ES
precision mediump float;
#endif

uniform float u_time;

void main() {
    
    
	gl_FragColor = vec4(1.0,0.0,1.0,1.0);
}

この本をオンラインで読んでいる場合、上記のコードは対話型です。コードの任意の部分をクリックまたは変更して探索することができます。GPU アーキテクチャのおかげで、シェーダは迅速にコンパイルおよび更新されるため、変更はすぐに目の前に表示されます。8 行目の値を変更して、何が起こるかを確認してください。
これらの単純なコード行には多くのコンテンツがあるようには見えませんが、これらに基づいていくつかの知識ポイントを推測することができます。

  1. シェーダ言語には、最後にカラー値を返す main 関数があります。これは C 言語とよく似ています。
  2. 最終的なピクセルの色は、プリセットされたグローバル変数 gl_FragColor によって決まります。
  3. この C に似た言語には、変数 (gl_FragColor など)、関数、データ型が組み込まれています。この例では、vec4 (4 成分浮動小数点ベクトル) を導入しました。後ほど、vec3 (3 成分浮動小数点ベクトル) や vec2 (2 成分浮動小数点ベクトル) などのさらに多くの型と、float (単精度浮動小数点型)、int (整数型) などの非常に有名な型について説明します。 bool (ブール値)。
  4. vec4 型を詳しく見ると、これら 4 つの引数がそれぞれ赤、緑、青、透明度のチャネルに応答すると推測できます。同時に、これらの変数が正規化されていることもわかります。これは、それらの値が 0 から 1 であることを意味します。後で、変数間の値のマッピングを容易にするために変数を正規化する方法を学びます。
  5. この例から分かる C 系言語のもう 1 つの非常に重要な機能は、プリプロセッサ マクロです。マクロはプリコンパイルの一部です。マクロを使用すると、グローバル変数を #define (定義) したり、(#ifdef と #endif を使用して) いくつかの基本的な条件付き操作を実行したりできます。すべてのマクロは # で始まります。プリコンパイルはコンパイルの直前に行われ、すべてのコマンドを #defines にコピーし、#ifdef 条件文が定義されているかどうか、および #ifndef 条件文が定義されていないかを確認します。「hello world!」の例では、GL_ES が 2 行目で定義されているかどうかを確認しました。これは通常、モバイルまたはブラウザのコンパイルで使用されます。
  6. シェーダーでは float 型が非常に重要であるため、精度が非常に重要です。精度が低いとレンダリングは速くなりますが、品質が犠牲になります。各浮動小数点値の精度を選択できます。最初の行 (precision middlep float;) では、すべての浮動小数点値を中精度に設定します。ただし、この値を「low」(precision lowp float;) または「high」(precision highp float;) に設定することを選択することもできます。
  7. 最後に、そしておそらく最も重要な詳細は、GLSL 言語仕様では変数が自動的に変換されることを保証していないということです。この文はどういう意味ですか? グラフィックス カード ハードウェアのメーカーはさまざまなグラフィックス カード アクセラレーション方式を採用していますが、最も合理化された言語仕様を備えていることが求められます。したがって、自動キャストは含まれません。「hello world!」の例では、vec4 は単精度浮動小数点まで正確であるため、float 形式を指定する必要があります。ただし、コードに一貫性を持たせ、後でデバッグに多くの時間を費やしたくない場合は、float 値に「.」を追加する習慣を身に付けることが最善です。次のようなコードは正しく実行されない可能性があります。
void main() {
    
    
    gl_FragColor = vec4(1,0,0,1);   // 出错
}

vec4 タイプを構築するには多くの方法がありますので、他の方法を試してください。その方法の 1 つを次に示します。

vec4 color = vec4(vec3(1.0,0.0,1.0),1.0);

この例はあまり魅力的ではありませんが、非常に基本的なものであり、キャンバス上のすべてのピクセルを正確な色に変更しています。次の章では、空間 (画面上のピクセルの位置に基づく) と時間 (ページが読み込まれてからの秒数に基づく) という 2 つの入力ソースを使用してピクセルの色を変更する方法を説明します。

おすすめ

転載: blog.csdn.net/woyebuzhidao321/article/details/134148002