GPU レンダリング パイプラインとシェーダーに関する現地語の概要 -- 1 つの記事で十分です

転載元: https:
//blog.csdn.net/newchenxf/article/details/119803489
とても良く書かれています!強くお勧めします

GPU レンダリング パイプラインとシェーダーに関する現地語の概要 -- 1 つの記事で十分です

記事ディレクトリ

1 はじめに

グラフィックスや画像を作成するには、GPU を理解する必要があります。GPU を
理解するために最も重要なことは、レンダリング パイプライン (またはパイプライン) を理解することです。
レンダリング パイプラインの最も重要な部分はシェーダー、つまりシェーダ。

そこで、この記事では、画像開発の入門チュートリアルとして使用できる GPU レンダリング プロセスとシェーダーについてまとめてみます。

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

パイプライン、組立ラインとも呼ばれます。

組立ラインとは何ですか?

パイプライン化とは、タスクを繰り返し実行するときに、タスクを多数の小さなタスクに分割し、それらの小さなタスクを重複して実行することで全体の動作効率を向上させることを意味します。

例:
ポーターが荷物を降ろすとき、3 人で車から店舗まで移動します。その3人はAさん、Bさん、Cさんです。
A は B に渡し、B は C に渡し、C は店に行きます。
A は、次の移動を開始する前に C がアイテムをストアに入れるのを待つ必要はなく、移動の進行中に C が 2 番目のアイテムの移動を開始できます。

CPU の処理では、実際にはパイプライン テクノロジーが使用されており、命令の実行が命令のフェッチ、デコード、データのフェッチ、計算、結果の書き込みの 5 つの部分に分割されます。その後のプロセスは上記のポーターと同様です。

レンダリング パイプラインは、3 次元シーンの頂点、テクスチャ、およびその他の情報を 2 次元イメージに変換します。この作業はCPU+GPUで完結します。
レンダリング プロセスは通常、次の 3 つのステージに分かれています:
(1) アプリケーション ステージ
(2) ジオメトリ ステージ
(3) ラスター化ステージ

应用阶段由CPU完成,几何阶段 和 光栅化阶段 由GPU完成。

もちろんパイプラインなので3ステージの実行は非同期ということになります。つまり、CPU の実行が完了した後、次の呼び出しを開始する前に GPU が実行を終了する必要はありません。
さらに、GPU によって実行されるジオメトリ ステージとラスタライゼーション ステージもサブタスクに分割され、これらのサブタスクでもパイプライン テクノロジが使用されます。

2.1 CPU と GPU はどのように並行して動作するのですか?

答えは「はい」です命令缓冲区コマンド バッファにはコマンド キューが含まれています。CPU追加コマンド、GPU読み取りコマンド。
CPU は、オブジェクトをレンダリングする必要がある場合、コマンド バッファにコマンドを追加し、レンダリング タスクが完了すると、引き続きバッファからコマンドを取り出して実行します。
ここに画像の説明を挿入します
緑色の [レンダリング モデル] が私たちが話しているものですDraw Call
openGL の例を示します。

GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

オレンジ色の [レンダリング状態の変更] は、データのロード、シェーダーの変更、テクスチャの切り替えなどを行います。openGL の例を挙げます。

GLES20.glUseProgram(program);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);

画像のフレームを生成する際、オブジェクト モデルが複数ある場合、DrawCall は 1 つずつ送信されます。GPU はカラー バッファーに 1 つずつ描画し、それを混合します。GPU は DrawCall を 1 つずつ処理しますが、GPU には内部パイプラインもあります。B を描画する前に A の描画が完了するのを待つ必要はありません。 A が最初の小さなステップを完了している限り、2 番目のステップに入り、B は最初のステップ (後述する頂点シェーダー) を開始できます。

フレームを描画した後、swapBuffer が呼び出され、カラー バッファー内のデータが表示のために画面に送信されます。

3 CPU処理---アプリケーションステージ

主な作業:
(1) ビデオメモリ(GPUメモリ)へのデータロード
(2) レンダリングステータスの設定
(3) Draw Callの呼び出し

3.1 データをビデオメモリにロードする

通常、GPU はメモリに直接アクセスできないため、ビデオ メモリ (グラフィック カード メモリ) と呼ばれる独自のメモリを作成しました。CPU はレンダリングに必要なデータをハードディスクまたはネットワークからメモリにロードし、ビデオ メモリに送信します。
データには大きく分けて、モデルデータ(頂点座標+テクスチャ座標)とテクスチャ画像の2種類があります。法線方向などその他のデータは非常に少ないです。ビデオメモリにロードした後、CPU のメモリ内のデータを削除することができ、たとえば、ビットマップを使用してテクスチャを生成し、GPU にロードした後に再利用することができます。

3.1.1 モデルデータの読み込み例

以下では、Android ビデオの再生とレンダリングの例を使用して、モデル データをロードする方法を説明します。

ビデオは 2 次元の長方形フレーム内に表示されるため、4 つの頂点座標と対応する 4 つのテクスチャ座標のみが必要です。テクスチャはデコードされたビデオ イメージを使用します。

    //顶点数据
    private float[] vertexData = {
            -1f, -1f,
            1f, -1f,
            -1f, 1f,
            1f, 1f
    };
    //纹理坐标数据
    private float[] fragmentData = {
            0f, 0f,
            1f, 0,
            0f, 1f,
            1f, 1f
    };

	public void onCreate() {
		//顶点坐标数据在JVM的内存,复制到系统内存
        vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(vertexData);
        vertexBuffer.position(0);
        //纹理坐标数据在JVM的内存,复制到系统内存
        fragmentBuffer = ByteBuffer.allocateDirect(fragmentData.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(fragmentData);

		//VBO全名顶点缓冲对象(Vertex Buffer Object),他主要的作用就是可以一次性的发送一大批顶点数据到显卡上
 		int [] vbos = new int[1];
        GLES20.glGenBuffers(1, vbos, 0);
        vboId = vbos[0];


		//下面四句代码,把内存的数据复制到显存中
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vboId);
        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vertexData.length * 4 + fragmentData.length * 4, null, GLES20. GL_STATIC_DRAW);
        GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, 0, vertexData.length * 4, vertexBuffer);
        GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, vertexData.length * 4, fragmentData.length * 4, fragmentBuffer);
	}

コードにはすでにコメントが付けられていますが、ここで説明することもできます。

結局のところ、 vertexData は
Java によって定義された変数であり、システム メモリではなく JAVA 仮想マシンのメモリを使用するため、GPU に直接与えることはできません。したがって、まず ByteBuffer を使用して vertexData をシステム メモリにコピーします。


次に、大量の頂点データを一度にグラフィックス カードに送信する頂点バッファ オブジェクト (Vertex Buffer Object) を作成します。次に、glBufferData を呼び出して、データをグラフィックス カード メモリにコピーします。こうすることで、後で GPU が動作し始めたときに、すぐにアクセスできるようになります。

3.1.2 テクスチャの読み込み例

以下は、テクスチャ画像をロードする Android アプリの別の例です。

    public static int createTexture(Bitmap bitmap){
        int[] texture=new int[1];

        //生成纹理
        GLES20.glGenTextures(1,texture,0);
        //生成纹理
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,texture[0]);
        //设置缩小过滤为使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_NEAREST);
        //设置放大过滤为使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);
        //设置环绕方向S,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_CLAMP_TO_EDGE);
        //设置环绕方向T,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_CLAMP_TO_EDGE);

        if(bitmap!=null&&!bitmap.isRecycled()){
            //根据以上指定的参数,生成一个2D纹理,上传到GPU
            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
        }
        return texture[0];
    }

まず、glGenTextures を使用してテクスチャ ID を生成します。この時点ではまだ実際のデータは作成されていません。
次に、テクスチャ プロパティを設定し、texImage2D を呼び出します。これにより、最初にテクスチャ バッファが作成され、次にビットマップがバッファにコピーされます。それで完了です。上記の関数が終了すると、ビットマップの内容が GPU にコピーされ、必要に応じて再利用できます。

強調する必要があるのは、不管是加载顶点还是纹理,都只需要在初始化时,一次性完成onDraw のたびにロードする必要はないということです。そうしないと意味がありません。

3.2 レンダリングステータスの設定

つまり、どの頂点シェーダー/フラグメント
シェーダーを使用するか、光源属性、マテリアル(テクスチャ)など、シーンのメッシュ(モデル)をどのようにレンダリングするかを宣言します。異なるメッシュを描画するときにレンダリング状態を切り替えないと、前後のメッシュは同じレンダリング状態を使用します。

3.3 コールドローコール

DrawCall については前にも触れましたが、実際にはコマンドであることはすでに理解されていると思います。開始側は CPU、受信側は GPU です。
Draw Call が呼び出されると、GPU はレンダリング状態 (マテリアル、テクスチャ、シェーダ) とすべての頂点データに基づいて計算を実行し、GPU パイプラインが実行を開始します。

もう一度強調して、

3.4 概要

最初のステップでは 1 回のロードのみが必要で、描画するたびにロードする必要はありません。2 番目と 3 番目は onDraw ごとに設定する必要があります。
ここでは、例としてビデオや画像を描画するために onDraw 関数も使用します。

 public void onDraw(int textureId)
    {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        GLES20.glClearColor(1f,0f, 0f, 1f);
        //设置渲染状态: program根据具体的shader编译,这里就是指定用哪个shader
        GLES20.glUseProgram(program);
        //设置渲染状态:指定纹理
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
        //设置渲染状态:绑定VBO
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vboId);

        //设置渲染状态:指定顶点坐标在VBO的从0开始,8个数据
        GLES20.glEnableVertexAttribArray(vPosition);
        GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8,
                0);

        //设置渲染状态:指定顶点坐标在VBO的从8*4(float是4个字节)开始,8个数据
        GLES20.glEnableVertexAttribArray(fPosition);
        GLES20.glVertexAttribPointer(fPosition, 2, GLES20.GL_FLOAT, false, 8,
                vertexData.length * 4);

        //调用Draw Call, GPU 管线开始工作
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

        //绘制完成,恢复现场
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
    }

4 GPU 処理 ---- ジオメトリ ステージ

ジオメトリステージとラスタライズステージを組み合わせて絵を描くことができます。
ここに画像の説明を挿入します
もちろん、上の図は完全ではない可能性があり、途中にオプションの手順がいくつかある可能性がありますが、最も重要なものを列挙しただけです。
このうち、青い部分の頂点シェーダーとフラグメント シェーダーは開発者が完全にカスタマイズ可能であり、画像開発を学ぶほとんどの学生が注意を払う必要がある 2 つのステップでもあります。

4.1 頂点データとは何ですか?

基本的な説明から始めましょう。GPU が理解できるデータは点、線、三角形のみです。
これは 1 頂点、2 頂点、3 頂点に相当します。これら 3 つは とも呼ばれます图元点と線は通常 2D シーンで使用されますが、3D シーンでは基本的にオブジェクト モデルに接続された N 個の三角形で構成されます。

下の図はキャラクター モデルの例です。
ここに画像の説明を挿入します
細部を拡大すると、すべて三角形で構成されていることがわかります。三角形の頂点は、頂点座標と呼ばれるものです。
ここに画像の説明を挿入します
すべての三角形に 2 次元画像テクスチャを貼り付けると、完成した画像になります
ここに画像の説明を挿入します
顶点数据,一般会包含顶点坐标 + 纹理坐标。

モデル ファイルには通常、頂点座標、テクスチャ座標、テクスチャ自体 (複数のテクスチャが存在する場合があります) などが含まれます。たとえば、obj サフィックスが 3D max のモデル ファイルを開くと、次のテキスト コンテンツが表示されます。

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

4.2 頂点シェーダー

頂点シェーダーは、GPU の内部パイプラインの最初のステップです。

その処理単位は頂点、つまり です每个顶点,都会调用一次顶点着色器
その主な作業は、座標変換と頂点ごとのライティング、および後続のステージ (フラグメント シェーダーなど) のためのデータの準備です。

坐标转换,即把顶点坐标从模型坐标空间转换到齐次裁切坐标空间。

これは Unity シェーダー コードです。Unity は頂点シェーダー コードとフラグメント シェーダー コードをファイルに配置しますが、Unity エンジンはファイルを解析し、2 つのシェーダー コード セグメントを変換して GPU に渡します。

Shader "Unlit/SimpleUnlitTexturedShader"
{
    Properties
    {
        // 我们已删除对纹理平铺/偏移的支持,
        // 因此请让它们不要显示在材质检视面板中
        [NoScaleOffset] _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            // 使用 "vert" 函数作为顶点着色器
            #pragma vertex vert
            // 使用 "frag" 函数作为像素(片元)着色器
            #pragma fragment frag

            // 顶点着色器输入
            struct appdata
            {
                float4 vertex : POSITION; // 顶点位置
                float2 uv : TEXCOORD0; // 纹理坐标
            };

            // 顶点着色器输出("顶点到片元")
            struct v2f
            {
                float2 uv : TEXCOORD0; // 纹理坐标
                float4 vertex : SV_POSITION; // 裁剪空间位置
            };

            // 顶点着色器
            v2f vert (appdata v)
            {
                v2f o;
                // 将位置转换为裁剪空间
                //(乘以模型*视图*投影矩阵)
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                // 仅传递纹理坐标
                o.uv = v.uv;
                return o;
            }
            
            // 我们将进行采样的纹理
            sampler2D _MainTex;

            // 像素着色器;返回低精度("fixed4" 类型)
            // 颜色("SV_Target" 语义)
            fixed4 frag (v2f i) : SV_Target
            {
                // 对纹理进行采样并将其返回
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

コードには明確にコメントが付けられています。
Appdata は、頂点座標とテクスチャ座標を含む頂点シェーダーの入力です。
通常、頂点シェーダーは頂点座標のみを処理し、空間変換を実行します。テクスチャ座標は通常、フラグメント シェーダに送信されます。

vert 関数は頂点シェーダーのコードです。Unity エンジンは、これを次のような main 関数を持つ GLSL コードに変換し、コンパイルします。

attribute vec4 v_Position;
attribute vec4 f_Position;
uniform mat4 uMVPMatrix;
varying vec2 textureCoordinate;

void main()
{
    gl_Position = uMVPMatrix* v_Position;
    textureCoordinate = f_Position.xy;
}

空間変換コードは非常に単純で、座標に MVP 行列を乗算するだけです。

次に、MVP矩阵に基づいて空間変換の概念を拡張します。

始める前に強調しておきますが、main函数会执行几次?
答案是,有几个顶点,就执行几次,且是并行的!

4.3 座標空間

座標空間とは何ですか? 実際、彼は私たちの生活のどこにでも存在します。たとえば、あなたと友人は美術館の門から100メートル離れた場所で会うことに同意していますが、このとき、あなたが話している場所は美術館の門を原点とした空間です。したがって、座標空間は相対的な概念です。

ゲームの世界でも同じことが言えます。

さまざまな参照オブジェクトに従って、次のように分類できます。模型空间-世界空间-观察空间-裁剪空间-屏幕空间。

3D 世界では、頂点座標は (x, y, z) の 3 つのデータを持ちますが、平行移動/回転/拡大縮小などの変換を容易にするために、w コンポーネントを追加する必要があります。つまり、(x,y,z,
w)、この4次元の座標を同次座標といいます。この知識については、セクション 5 の付録の知識を参照してください。

次に、これらのスペースについて 1 つずつ説明します。

4.3.1 モデル空間

3D Max でキャラクター モデルを作成し、さまざまなソフトウェアに適用できます。
モデルの内部には、例えば心臓に座標原点があり、身体の各部位は心臓を基準とした座標値を持ち、各モデルオブジェクトは独自の独立した座標空間を持ちます
したがって、オブジェクト空間またはローカル空間と呼ぶこともできます

4.3.2 ワールド空間

ゲーム内の地図をスモールワールドとして使用でき、地図の中心を座標原点として使用できます。キャラクター モデル全体は、マップ上に配置されると、マップの中心を基準とした座標値を持ちます。ここはワールド空間です。

モデル空間の座標をワールド空間の座標に変換するには、行列演算を使用します。この行列はモデル行列と呼ばれます。
実際、モデルをマップ上に配置するときに、最初にスケール変更してから、回転または移動することができます。これら 3 つは対応する行列を持っています。詳細については付録を参照してください。

つまり、この行列は、マップ内のオブジェクトのスケーリング、回転、および移動パラメーターによって決定されます。
変換の目的は、マップ上のオブジェクトの特定の頂点の特定の位置を取得することです。

4.3.3 観察空間

とも呼ばれます摄像机空间
展望空間とは、ゲームマップが非常に大きいため、コンテンツの一部しか見ることができないことを意味します。したがって、ゲーム マップで、同じくワールド空間内にあるカメラを定義します。カメラは私たちの目に相当し、カメラが光る場所には、それが私たちの目に映ります。

3D ゲームでは、カメラとユーザー キャラクターが関連付けられることがよくあります。キャラクターオブジェクトが行くところにカメラオブジェクトも移動するので、どこに行っても景色が見えるという効果があります。

したがって、カメラが座標原点として使用されている場合、マップ内のオブジェクトはすべて、カメラを基準とした座標値も持ちます。カメラを原点とした空間が観測空間となる。

ワールド空間の座標を観測空間に変換することも、行列演算によって取得できます。このマトリックスはビュー マトリックスと呼ばれます。

この行列はどのパラメータに依存するのでしょうか?

まず、カメラ自体もワールド座標系に位置しており、カメラがどこを見ているかを考慮しなければ、実際には非常に単純な平行移動行列になります。
つまり、カメラ A の座標が (-3, 0, 0) の場合、そのカメラはワールド原点 O から
(-3, 0, 0) 移動した結果になります。カメラ座標が新しい原点として使用される場合、元のワールド原点 O は、カメラから (3, 0, 0) シフトした結果と等価です。したがって、2つの座標系の変換行列はほぼ平行移動行列となる。

もちろん、実際のカメラの向きは、目が正面を見ているか逆さまを見ているかによっても異なります。逆さまに見るとはどういうことか分かりませんか?下の写真を見てください
ここに画像の説明を挿入します

したがって、このマトリックスは常に 3 つの要因の影響を受けます。
さあ、関数があなたの悩みを解決し、複雑な計算プロセスを忘れさせます。

glm::mat4 CameraMatrix = glm::LookAt(
    cameraPosition, // the position of your camera, in world space
    cameraTarget,   // where you want to look at, in world space
    upVector        // probably glm::vec3(0,1,0), but (0,-1,0) would make you looking upside-down, which can be great too
);

4.3.4 クリッピングスペース

クリッピング スペースとは、カメラが認識できる領域です。この領域が視錐台になります。
これは 6 つのプレーンで構成されており、クリッピング プレーンにもなります。視錐台には 2 つのタイプがあり、2 つの投影法が関係します。1 つは透視投影で、もう 1 つは正投影
です。

この空間内に完全に配置されているプリミティブは保持され、
空間の外側に完全にあるプリミティブは完全に破棄され、
空間の境界と交差するプリミティブはクリップされます。

下図は透視投影の模式図です。キャラクターは完全にビジュアル コーン内にあるため、右下隅にある小さな画像がエンド ユーザーに表示される 2D 画像です。
ここに画像の説明を挿入します
もちろん、写真のNearとFarは表示の都合上、比較的小さな値に設定しています。実際のゲームでは、Near = 0.1、Far =
1000 のように、Near と Far は大きく異なります。これは人間の目で見える範囲と同じで、目に近いものは見えず、遠くのものは見えません。

では、視円錐内に何かがあるかどうかをどうやって判断するのでしょうか?
答えは、射影行列を通じて頂点をクリッピング スペースに変換することです。
この行列は基本的にカメラのパラメータによって決まります。

パラメータ図は次のとおりです。
ここに画像の説明を挿入します

FOV は視野 (Field of View) を表し、
Near と Far はカメラの原点とクリッピング面からの距離、
AspectRatio はクリッピング面のアスペクト比、Far はカメラの原点とクリッピング面からの距離を表します。

観測空間からクリッピング空間への変化行列が射影行列になります。

行列は関数を使用して生成できます。

// Generates a really hard-to-read matrix, but a normal, standard 4x4 matrix nonetheless
glm::mat4 projectionMatrix = glm::perspective(
    glm::radians(FoV), // The vertical Field of View, in radians: the amount of "zoom". Think "camera lens". Usually between 90° (extra wide) and 30° (quite zoomed in)
    AspectRatio,       // Aspect Ratio. Depends on the size of your window. such as 4/3 == 800/600 == 1280/960, sounds familiar
    Near,              // Near clipping plane. Keep as big as possible, or you'll get precision issues.
    Far             // Far clipping plane. Keep as little as possible.
);

具体的な数式生成原理についてはここでは説明しませんので、詳細については、Google で検索するか、https://zhuanlan.zhihu.com/p/104768669 を参照してください。

射影行列には射影という言葉が付いていますが、実際の射影は行いません。代わりに、彼は投影の準備をしていました。投影は、次のステップであるスクリーン空間への変換まで使用されません。

射影行列演算が実行された後、つまりクリッピング空間に到達した後、w の値は必ずしも 0 と 1 である必要はなく、特別な意味を持ちます。具体的には、頂点が視錐台内にある場合、その変換された座標は次を満たさなければなりません。

-w <= x <= w
-w <= y <= w
-w <= z <= w

要件を満たさないものは削除または削減されます。

例:
キャラクターモデルの手の頂点の観測空間への座標は
[9, 8.81, -27.31, 1]であり
、クリッピング空間への変換後の値は
[11.691, 15.311, 23.69, 27.3]となります。
すると、頂点がクリップ空間内に収まり、表示できるようになります。

これまでの説明は透視投影に基づいていました。では、正投影と透視投影の違いは何でしょうか?
透视投影:越远的东西,呈现在屏幕的越小; 正交投影,远和近的同样大小东西,呈现在屏幕上一样大

これが手描きの絵です、あなたは私の言いたいことがわかるでしょう!
ここに画像の説明を挿入します

上記からわかるように、透視投影には 3D 効果がありますが、直交透過には効果がないため、2D ゲームにより適しています。

4.3.5 画面スペース

画面空間は 2 次元空間です。これは私たちがこれから見ようとしている絵でもあり、私たちが知る必要がある最後の空間でもあります(息を吸ってませんでしたか、笑)

頂点をクリッピング空間からスクリーン空間に変換するには、3 次元が 2 次元になるため、 と呼ばれます投影

この投影は主に次の 2 つのことを行います。

まず、同次分割が必要です複雑だと思わないでください。実際には、w コンポーネントを使用して x、y、z コンポーネントを見つけているだけです。Open GL では、このステップで取得された座標は归一化的设备坐标(正規化デバイス座標、NDC) と呼ばれます。
NDC の x、y、z の有効な値の範囲はすべて です负1到1この値以外の点は削除されます。

下図はNDCの模式図です。長さ、幅、高さの範囲が [-1,1] の立方体のように見えます。
ここに画像の説明を挿入します
NDCの存在意義、第2ステップ。

次に、画面座標を取得するために画面にマッピングする必要があります。openGLでは、画面の左下隅のピクセル座標は(0,0)、右上隅は(pixelWidth,pixelHeight)です。
たとえば、正規化された座標が (x, y) で、画面の幅と高さが (w, h) の場合、画面座標は次のようになります。

Sx = x*w + w/2
Sy = y*h + h/2

NDC の z コンポーネントはもう必要ないと言いたいのでしょうか?
いいえ、いいえ、z コンポーネントにも多くの価値があり、深度バッファーとして機能します。たとえば、後に同じ画面座標を持つ 2 つの頂点があり、どちらも透明でない場合は、深度バッファーに依存し、カメラに最も近い頂点が表示されます。

強調する必要があるのは、クリッピング スペースからスクリーン スペースへの変換は最下層によって完了し、コードの実装を必要としないことです。

顶点着色器,只需要把顶点,从模型空间转换到裁减空间即可

フラグメント シェーダーでは、画面空間内のフラグメントの位置を取得できます。

4.4 頂点座標変換の概要

セクション 4.2 に戻り、MVP 行列について説明しましたが、これで頂点をモデル空間からクリッピング空間に変換できる行列を表すことがついにわかりました。
この行列を模型矩阵 * 观察矩阵 * 投影矩阵再度乗算すると得られます!

画像で説明しましょう:
ここに画像の説明を挿入します

これは最終的にセクション 4.2 のコードと一致します。

o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);

このマクロは、UNITY_MATRIX_MVP図の MVP マトリックスです。

モデルの位置/回転/スケールを決定する場合、モデル マトリックスが存在します。カメラの位置と向きが決まると、ビュー
マトリックスが作成されます。カメラのFOV画角、遠近切断面間の距離などが決まると、投影行列が作成されます。
3 つすべてが決定され、それらを掛け合わせると MVP マトリックスが決定されます。

したがって、頂点シェーダーは非常に単純で、固定の MVP マトリックスを乗算するだけです。次に、裁减空间以下の座標を取得します。
次に、GPU がトリミング、NDC 座標の変換、画面マッピングを支援し、ジオメトリ ステージが完全に完了します。

4.5 ビデオ再生用の頂点データの確認

ビデオ再生の例であるセクション 3.1 を振り返ってみましょう。頂点座標は次のとおりです。

    private float[] vertexData = {
            -1f, -1f,
            1f, -1f,
            -1f, 1f,
            1f, 1f
    };

なぜそんなに単純なのでしょうか? 動画再生専用なので画像は長方形です。必要な頂点は 4 つだけです。頂点座標は 2 次元で、8 つの数値が 4 つの頂点を表し、z 値のデフォルトは 0、w 値のデフォルトは 1 です。

完全なデータを自分で書き込む必要がある場合は、

    private float[] vertexData = {
            -1f, -1f, 0f, 1f, 
            1f, -1f,  0f, 1f, 
            -1f, 1f,  0f, 1f, 
            1f, 1f,  0f, 1f, 
    };

したがって、ここでの頂点座標はクリップ空間内の値と同じになります。

つまり、ビデオ再生は 2 次元であるため、ワールド空間や観測空間を気にする必要がなく、これらの空間がなければ、MVP 行列は単位行列になります。

また、w の値が 1 であるため、NDC 下の座標はクリッピング空間と同じになります。

セクション 4.3.5 では、3 次元立方体である NDC 座標について説明しました。
z が考慮されない場合、NDC 座標は 2 次元の正方形になります。

2次元NDCは下図のようになり、
ここに画像の説明を挿入します
動画再生時の頂点座標を基準にすることができます。つまり、頂点は 4 つの値として使用され、1 対 1 で負になることが保証されます。

頂点について話した後は、openGL テクスチャ座標系について話しましょう。テクスチャは 2 次元にすぎません。ビデオ再生でも 3D 世界でも、どちらも 2 次元です。

座標系は次のように定義されます。
ここに画像の説明を挿入します

セクション 3.1 に戻り、頂点データをロードするときに、対応するテクスチャ座標も含めます。

    //纹理坐标数据
    private float[] fragmentData = {
            0f, 0f,
            1f, 0,
            0f, 1f,
            1f, 1f
    };

データを見ると、頂点座標とテクスチャ座標は1対1で対応していることが分かり、例えば頂点座標の左下隅の位置(-1, -1)が左下隅の位置に対応します。テクスチャ座標 (0, 0) の値。

最後に、セクション 3.1 に対応する頂点シェーダー コードを見てみましょう。これも非常に単純です。

attribute vec4 v_Position;
attribute vec4 f_Position;

varying vec2 textureCoordinate;

void main()
{
    gl_Position = v_Position;
    textureCoordinate = f_Position.xy;
}

これはセクション 4.2 とは異なることに注意してください。このセクションはすでに openGL 用に直接コンパイルできるシェーダーです。セクション 4.2 は Unity によってカプセル化されたコード形式です。最後に、Unity エンジンも上記と同様のコードに変換されます。主な機能。

v_Position は外部から入力された頂点座標であり、gl_Positionクリッピング空間の最終座標を表すグローバル組み込み変数です。

上記のシェーダーが MVP マトリックスを使用する必要がある場合は、それも可能ですが、この MVP は単位マトリックスです。単位行列を掛けても、同じ値になります。
今すぐ

gl_Position = mvpMatrix * v_Position;

5 GPU 処理 ----ラスタライズ段階

ラスタライズの主なタスク: 各プリミティブによってカバーされるピクセルを計算し、これらのピクセルに色を付けます。

5.1 トライアングルのセットアップとトラバーサル

三角形の設定:

これはラスタライズ パイプラインの最初の段階です。
このステージでは、三角形メッシュをラスタライズするために必要な情報を計算します。
具体的には、前段の出力は画面上の 2 次元三角グリッドの頂点です。つまり、三角形メッシュの各エッジの 2 つの端点が得られます。しかし、三角グリッド全体のピクセル範囲を取得したい場合は、各エッジのピクセル座標を計算する必要があります。境界ピクセルの座標情報を計算できるようにするには、三角形の境界の表現を取得する必要があります。このような三角形メッシュ表現データを計算するプロセスは、三角形セットアップと呼ばれます。その成果は次の段階に備えることです。

三角形トラバーサル:
三角形トラバーサル フェーズでは、每个像素三角形メッシュで覆われているかどうかを確認します。覆われている場合はフラグメントが生成され、どのピクセルが三角グリッドで覆われているかを見つけるプロセスが行われます三角形遍历三角形トラバーサル ステージでは、前のステージの計算結果に基づいて、どのピクセルが三角形メッシュでカバーされるかを決定し、三角形メッシュの 3 つの頂点の頂点情報を使用して、カバーされた領域全体のピクセルに対する演算を実行します插值以下の図は、トライアングルトラバーサルステージの簡略化された計算プロセスを示しています。
ここに画像の説明を挿入します
ジオメトリステージによって出力された頂点情報に従って、三角形メッシュによってカバーされるピクセル位置が取得されます。対応するピクセルはフラグメントを生成し、フラグメント内の状態は三角形の頂点情報から計算されます。
上の写真のように三角グリッドに合計8つのフラグメントが生成されます。

あなたはきっと疑問に思っているでしょう。绘制一帧,到底会产生多少片元?

動画再生などの単純な 2 次元でのみ使用する場合、三角形は頂点が 4 つだけで画面の四隅に広がり、奥行きがないと言えます。以下に示すように、理解しやすいようにしてください片元数量就是屏幕的宽*高

ここに画像の説明を挿入します
同じ深さを持つ三角形は合計 2 つだけです、つまり、重なっている三角形はありません。

しかし、3D ゲームの場合は、必ずしもそうとは限りません。
ここに画像の説明を挿入します
たとえば、ゲームのこのシーンでは、いくつかの物理モデルがあり、それらのモデルは重なっています。たとえば、石の後ろに家があります。

フレームを描画するには、複数のモデルを描画する必要があり、各モデルで Draw Call を呼び出す必要がある場合があります。描画呼び出しでは、いくつかのフラグメントが生成される場合があります。
すべてのオブジェクトが描画される場合、基本的にすべての画面ピクセル座標がグリッドでカバーされ、一部のピクセル座標では重複するフラグメントが存在します。
たとえば、石の矢印部分のピクセルに対応する三角形メッシュと家の三角形メッシュは、x と y が同じですが、深さ z が異なります。
それで、3D游戏场景,片元数量 >= 屏幕宽 * 高

5.2 フラグメントシェーダ

フラグメント シェーダもカスタマイズ可能なコードであり、主にフラグメントの色付けに使用されます。
ビデオ再生レンダリング用のシェーダー コードから始めましょう。

varying highp vec2 textureCoordinate;// 片元对应的纹理坐标,又顶点着色器的varying变量会传递到这里

uniform sampler2D sTexture;          // 外部传入的图片纹理 即代表整张图片的数据

void main()
{
     gl_FragColor = texture2D(sTexture, textureCoordinate);  // 从纹理中,找到坐标为textureCoordinate的颜色,赋值给gl_FragColor
}

簡単だと思いましたか?テクスチャ座標に基づいてカラーをサンプリングし、それをフラグメントの最終カラーとして gl_FragColor に割り当てるだけです。

これは、最終的な色が完全に制御できることを意味しており、現在、多くのビデオ特殊効果処理はこのフラグメント シェーダーに基づいています。

たとえば、ビデオを再生すると、白黒兄弟の写真が表示されます。

#extension GL_OES_EGL_image_external : require
precision mediump float;
varying vec2 vCoordinate;
uniform samplerExternalOES uTexture;
void main() {
  vec4 color = texture2D(uTexture, vCoordinate);
  float gray = (color.r + color.g + color.b)/3.0;
  gl_FragColor = vec4(gray, gray, gray, 1.0);
}

rgb の単純な平均を作成し、最終的な色は同じ RGB コンポーネントを持ちます (つまり、白と黒の間、グレーは 0 黒、グレーは 255、白、グレーは 2 つの中間、グレー)。黒と白の色。それをフラグメントに割り当てれば、シンプルな白黒フィルターが完成します。

フラグメント シェーダも並行して実行されることに注意してください。フラグメントの数だけ実行するだけです。GPU は非常に強力な並列コンピューティング機能を備えています。

5.3 フラグメントごとの操作

PerFragment オペレーションは openGL で呼ばれるもので、DirectX では出力のマージおよびミキシングの段階になります。

この段階での主なタスク:
(1) テンプレート テスト、深度テストなどを実行して、各フラグメントの可視性を判断します。
(2) フラグメントはテストに合格し、このフラグメントのカラー値が、カラー バッファにすでに格納されているカラーに追加されます ( と呼ばれます) 混合
ここに画像の説明を挿入します

5.3.1 テンプレートのテスト

これはオプションです。テンプレート データはステンシル バッファー (Stencil Buffer) に配置されます。
テンプレートってどういう意味ですか?とてもシンプルで、例えばシーンをどのように描いても、画面の中央の円形の領域だけが見たいので、それ以外の領域は見えません。

テンプレートの例を見てみましょう。
ここに画像の説明を挿入します
フラグメントの座標が表示領域の外にある場合、それらは直接破棄されます。

5.3.2 深度テスト

有効にすると、フラグメントの深度値、つまり Z 値が、深度バッファーにすでに存在する深度値 (存在する場合) と比較されます。比較方法を設定できます。デフォルトでは、フラグメント以上の場合はフラグメントが破棄され、フラグメント以下の場合はフラグメントが保持されます。これも当然で、奥行きが大きいほどカメラから遠ざかり、カメラに近いものに遮られるので表示する必要がありません。

5.3.3 ブレンド

前のテストに合格した場合は、混合段階に入ります。これも設定可能です。

なぜ合併するのか?因为一帧图像生成,需要逐个绘制物体模型,每画一次,就会刷新一次颜色缓冲区。当我们执行某次渲染时,只要不是第一次,那颜色缓冲区就有上一次渲染物体的结果
では、同じ画面座標の位置に対して、前回の色を使うのか、今回の色を使うのか、それとも混ぜて使うのか?これが現段階で検討されている内容です。

半透明のオブジェクトの場合は、カラー バッファーにすでにある色を確認できる必要があるため、ブレンド機能を有効にする必要があります。そのため、透明効果を実現するにはそれらをブレンドする必要があります。

不透明なオブジェクトの場合、開発者はブレンディング機能をオフにすることができます。結局のところ、深度テストに合格している限り、フラグメントは保持されるべきであり、フラグメントの色を直接使用してオブジェクトの色を上書きすることができます。カラーバッファー。

5.3.4 画像出力 - ダブルバッファリング

まず、画面に表示されるのはカラーバッファ内のデータです。

画像のフレームを描画するプロセス中、カラー バッファーは常に上書きされ、混合されます。このプロセスはユーザーに表示されてはいけません。したがって、GPU は問題を解決するためにダブル バッファリング戦略を使用します。
FrontBuffer が描画された後、表示のために画面に渡され、その後 BackBuffer が解放され、次のレンダリングに使用されます。
絵を描けば一目瞭然。
ここに画像の説明を挿入します
画像のフレームを描画した後、eglSwapBuffers を呼び出してバッファを切り替えます。

6 パフォーマンスディスカッション

6.1 パフォーマンスのボトルネックはどこにありますか?

まず結論からお話しましょう。一般在CPU阶段,提交命令的耗时。Draw Call数量越多,越可能造成性能问题。

各描画
呼び出しが呼び出される前に、CPU はデータ、ステータス、コマンドなどの大量のコンテンツを GPU に送信する必要があります。CPU が準備作業を完了してコマンドを送信すると、GPU は動作を開始できます。GPU のレンダリング能力は非常に強力で、100 個の三角形メッシュをレンダリングしても、10,000 個の三角形メッシュをレンダリングしても大きな違いはありません。それで、
GPU渲染速度往往优于CPU提交命令的速度多くの場合、GPU はコマンド バッファーの処理を終了し、アイドル状態で待機していますが、CPU はデータの準備にまだ熱心に取り組んでいます。

これは、たとえば 1,000 個などの大量の txt ファイルがあり、1,000 個を別のフォルダーにコピーすると、非常に遅くなります。圧縮しなくてもtarballパッケージにまとめて別のフォルダに一気にコピーすると早いです。
なぜ?cpy 自体は非常に高速であるため、各 cpy の前に主にメモリの割り当て、メタデータ、ファイル コンテキストの作成などに時間がかかります。

如果地图中有N个物体模型,渲染一帧完整图像,可能就要调用N次Draw Call
、呼び出されるたびに、CPU は多くの準備作業 (レンダリング状態の変更) を実行する必要があるため、CPU に負荷がかかることになります。したがって、ターゲットを絞ったパフォーマンスの最適化が必要です。

6.2 パフォーマンスを最適化する方法

まず、Draw Call の CPU 消費量のおおよそのレベルを見てみましょう。NVIDIA
はかつて GDC で、25,000 バッチ/秒で 1 GHz CPU がいっぱいになり、使用率が 100% に達すると提案しました。

公式:

DrawCall_Num = 25K * CPU_Frame * CPU_Percentage / FPS

DrawCall_Num: DrawCall の数 (サポートされている最大値)
CPU_Frame: CPU 動作周波数 (GHZ 単位)
CPU_Percentage: CPU によって描画呼び出しに割り当てられた時間レート (パーセンテージ)
FPS : 必要なゲーム フレーム レート

たとえば、Qualcomm 820 を使用し、動作周波数が 2GHz で、CPU 時間の 10% をドローコールに割り当て、60 フレームが必要な場合、フレームには最大 83 個のドローコールを含めることができます (25000 2 10%/60 = 83.33)
)、割り当てが 20% CPU 時間の場合、約 167 になります。

したがって、ドローコールの最適化は主に、グラフィカル インターフェイスを呼び出す際の CPU オーバーヘッドを可能な限り解放することです。ドローコールの主なアイデアは次のとおりです每个物体尽量减少渲染次数,多个物体最好一起渲染,或者叫,批处理(batching)

バッチ処理できるオブジェクトの種類は何ですか?
答えは です使用同一种材质的物体

同じマテリアルの物理的性質は、頂点データのみが異なることを意味しますが、使用されるテクスチャ、頂点シェーダー、およびフラグメント シェーダー コードはすべて同じです。

したがって、これらのオブジェクトの頂点データを 1 つにマージし、GPU に送信して、再度 DrawCall を呼び出すことができます。

Unity エンジンは、静态批处理と の2 つのバッチ処理方法をサポートしています动态批处理他のゲームエンジンの処理方法についてはここでは説明しません。

6.2.1 Unityの静的バッチ処理

静的バッチ処理では、モデルの頂点数が制限されず、実行の開始時にモデルが 1 回だけマージされ、動きの開始時に 1 回だけモデルがマージされます。これには、これらの物理を移動できないことが必要です。

利点は、これらを一度マージして作成すれば、その後のレンダリングではマージする必要がないため、パフォーマンスが節約できることです。
欠点は、より多くのメモリを使用する可能性があることです。たとえば、一些物体共享了网格
マップにはモデルを共有する 100 本のツリーがあるなど、静的な処理はありません。これらを静的バッチ処理にすると、100倍の頂点データメモリが必要になります。

6.2.2 Unityの動的バッチ処理

動的バッチ処理では、レンダリングのフレームごとにモデル メッシュを結合する必要があります。

利点は、これらのオブジェクトを移動できることです。
欠点は、マージするたびに、頂点の数が 300 を超えることができないなどの制限があることです (Unity のバージョンによって異なります)。

7 付録の知識

7.1 同次座標とは

同次座標は、N+1 次元を使用して N 次元の座標を表します。
例えば、直交座標系の座標(x,y,z)は、(x,y,z,w)で表される。座標は点またはベクトルにすることができます。

目的:

  1. 区分向量或者点。
  2. 辅助 平移T、旋转R、缩放S这3个最常见的仿射变换

経典からの引用:

同次座標表現はコンピュータ グラフィックスにおける重要な手法の 1 つであり、ベクトルと点を明確に区別できるだけでなく、アフィン幾何変換を容易に行うことができます。
—— FS Hill、JR 『Computer Graphics (OpenGL Edition)』著者

ベクトルと点の区別については、
(1) 通常の座標から同次座標に変換する場合、
(x, y, z) が点の場合は (x, y, z, 1) となり、
( x, y,z) がベクトルの場合、(x,y,z,0) になります。

(2)同次座標から通常の座標に変換する場合、
(x,y,z,1)であれば点であることが分かるので(x,y,z)となり、(x,y,z,1)であれば点であることが分かります
。 y,z,0 ) の場合、それがベクトルであることがわかりますが、それでも (x, y, z) になります。

平行移動、回転、拡大縮小については、ゆっくりお話しましょう。

7.2 翻訳行列

変換行列は最も単純な変換行列です。変換行列は次のようになります。
ここに画像の説明を挿入します
ここで、X、Y、Z は点の変位増分です。たとえば、ベクトル (10, 10, 10, 1)を 10, 10
に沿って 10 単位m n の行列) n tの行列でのみ乗算できます)。
ここに画像の説明を挿入します

7.3 スケーリング行列

スケーリングも非常に簡単で、
ここに画像の説明を挿入します
たとえば、ベクトル (点または方向) を各方向に 2 倍に拡大します。
ここに画像の説明を挿入します

7.4 回転行列

これはもう少し複雑で、X、Y、Z 軸に沿った回転が異なりますが、最終的には行列であることに変わりはありません。ここでは個別に展開しません。ご興味がございましたら、以下をご参照ください。

7.5 組み合わせ変換

以上、回転、平行移動、拡大縮小の操作方法を紹介しました。これらの行列は、次のように乗算することで結合できます。

TransformedVector = TranslationMatrix * RotationMatrix * ScaleMatrix *
OriginalVector;

注意,是先执行缩放,接着旋转,最后才是平移。これが行列の乗算の仕組みであり、3D モデルをマップに配置する一般的な方法でもあります。

3 つの行列を乗算しても 1 つの行列になります。これは結合された変換の行列なので、最終的には次のように書くこともできます。

TransformedVector = TRSMatrix * OriginalVector;

参考

Android OpenGL ES 1. 基本概念
コンピュータ構成原理 – GPU
[Computer Things (8) – グラフィックスと画像レンダリングの原理](http://chuquan.me/2018/08/26/graphics-rending-principle-gpu/
)
[ opengl-tutorial ](http://www.opengl-tutorial.org/cn/beginners-tutorials/tutorial-3-matrices/
)
[Unity ドキュメント]( https://docs.unity3d.com/cn/2019.4/ マニュアル/SL-
VertexFragmentShaderExamples.html)
ラスタライズ段階の設定
Drawcall について

おすすめ

転載: blog.csdn.net/weixin_40301728/article/details/131950662
おすすめ