スクラッチパート7からWebGLエンジン


私はもともといくつかの照明の遮光物で遊ぶためにこれを始めた.照明はかなり大きな話題です.

概要


ライトにかかわるものがたくさんありますので、高いレベルから始めましょう.
  • 光の種類
  • 方向性の
  • ポイント
  • スポットライト(位置と方向)
  • 面積
  • 照明効果の種類
  • アンビエント
  • 散漫な
  • 鏡面反射光
  • PBR/BRDF(現実の物理的性質を持つより現実的な照明)
  • 照明粒度
  • ポリ(フラット)
  • 頂点毎
  • ピクセル当たり
  • 私は本当に専門家ではありませんが、それは私がそれにアプローチしたい方法です.PBRや物理ベースのレンダリングはおそらく私を超えているが、最も一般的なゲームで今日使用されます.実際に私の興味を得るのはいくつかの古いモデルと(実際のAPIと実際には難しいですが、頂点モデルと頂点モデルのようなものを含む)フォーカスされます.

    クリーンアップ-シーンのアニメーション


    最後の部分の最後に私は実際のアニメーションを行うには、ちょうどすべてのポインタの更新でレンダリングと呼ばれるに寄った.場面が操作されている間、私が必要以上にレンダリングしているので、これはパフォーマンスに悪いです.代わりに、より多くの意味を使用するrequestAnimationFrame ブラウザのリフレッシュレートまでフレームを同期させる.
    これは実際にはハードではありません.最初に我々はすべての呼び出しを削除することから始めますthis.render() の唯一の例外でconnectedCallback 描画ループをブートします.私たちは新しい方法を作りますrenderLoop それでrender RequestanimationFrameループで.
    async connectedCallback() {
        this.createShadowDom();
        this.cacheDom();
        this.attachEvents();
        await this.bootGpu();
        this.createCameras();
        this.createMeshes();
        this.createLights();
        await this.loadTextures();
        this.renderLoop();
    }
    renderLoop(){
        requestAnimationFrame(() => {
            this.render();
            this.renderLoop();
        });
    }
    
    なぜメソッドを分割?なぜアニメーションフレームを挿入するかrender ? 主な理由は柔軟性です.render はまだ1フレームに対して責任があります.我々はまた、アニメーションのための現在の時間のような新しいタイミング依存の情報を追加する可能性がありますし、それを入力としてこれを持って意味がありますrender .

    法線


    法線は、表面から離れてポイントするベクトルです.

    我々がちょうど頂点の間で色勾配を使用しているので、我々は本当にこれらについて考える必要はありませんでした、しかし、彼らは照明のようなものを考え出すのに非常に役に立ちます.しかし、照明がメッシュのためにあらゆるピクセルで通常を必要とするかもしれない間、我々はちょうど頂点で通常を得て、彼らの間で内挿します.したがって、上の図のために、私たちは赤い頂点で通常のベクトルを保存するだけです、そして、表面を横切って計算するとき、ピクセル遮光物は私たちに彼らの間で値を与えます.
    値はちょうど3値単位ベクトルは、あなたが行きたい方向に指しています.
    我々は、色とUVSのようにこの情報を加えます.クワッドから始めましょう
    //data.js
    export const quad = {
        positions: [
            -0.5, -0.5, 0,
            0.5, -0.5, 0,
            0.5, 0.5, 0,
            -0.5, 0.5, 0
        ],
        colors: [
            1, 0, 0,
            1, 0, 0,
            1, 0, 0,
            1, 0, 0
        ],
        uvs: [
            0, 1,
            1, 1,
            1, 0,
            0, 0
        ],
        normals: [
            0, 0, -1,
            0, 0, -1,
            0, 0, -1,
            0, 0, -1
        ],
        triangles: [
            0, 1, 2,
            0, 2, 3
        ],
        textureName: "grass"
    }
    
    そして、データが遮光物に行くようにフックします.
    bindNormals(normals) {
        const normalsBuffer = this.context.createBuffer();
        this.context.bindBuffer(this.context.ARRAY_BUFFER, normalsBuffer);
        this.context.bufferData(this.context.ARRAY_BUFFER, normals, this.context.STATIC_DRAW);
        const vertexNormalLocation = this.context.getAttribLocation(this.program, "aVertexNormal");
        this.context.enableVertexAttribArray(vertexNormalLocation);
        this.context.vertexAttribPointer(vertexNormalLocation, 3, this.context.FLOAT, false, 0, 0);
    }
    

    光を加える


    我々は、小さな起動し、光の種類が異なるように構築します.我々は“方向光”を開始したい.これは太陽のように一方向に行く光の無限遠の光源と考えられるタイプの光です.我々は、それが広がるか何か空想について考えるつもりはありません、それはちょうど平行な光ベクトルの純粋なフィールドです、したがって、我々は光の位置さえ必要としません、それはちょうど方向です.また、環境に均一に散乱される光である「拡散」照明から始めます.光が散乱した光が光でなく物体の性質であることを頭の後ろに置いてください.
    私たちは少しの乱雑さを取得し始めているのでlib フォルダー、私はAのようなシーンを構成するものを表す“俳優”と呼ばれる新しいフォルダを使用して起動するつもりですMesh , Camera そして今Light .
    光は今すぐにはなりません.
    export class Light {
        #type;
        #position;
        #direction;
        #color;
    
        constructor(light){
            this.#type = light.type ?? "directional";
            this.#position = light.position ?? [0,0,0];
            this.#direction = light.direction;
            this.#color = light.color ?? [1,1,1,1];
        }
    
        getInfoMatrix(){
            return new Float32Array([
                ...this.#position, 1,
                ...invertVector(this.#direction), 1,
                ...this.#color,
                0, 0, 0, this.#type === "directional" ? 0 : 1
            ])
        }
    }
    
    私は、Aを使うことに決めましたmat4 それが私にすべてを詰め込むことができるので、遮光物にそれを送るために.最初の行は位置、2番目の方向、3番目の色と最終行はいくつかのメタデータです.私たちは場面で多くの明りを持っていません、少なくとも私が行っているアルゴリズムで、我々はちょうど固定名変数としてグローバルユニフォームを使用することができるように、それが非常に高価であるでしょう.
    指摘する価値のあるものは#direction が反転する.これは、我々が正常に、そして、光方向の間でドット製品を使用しているということです、しかし、正しく動作するために、彼らは同じ方向にいる必要があります.
    逆は単純で、各コンポーネントの符号を反転させます.
    //vector.js
    export function invertVector(vec){
        return vec.map(x => -x);
    }
    
    我々はグローバルユニフォームの一部として遮光物にこれを送ります.
    setupGlobalUniforms(){
        const projectionMatrix = this.cameras.default.getProjectionMatrix();
        const projectionLocation = this.context.getUniformLocation(this.program, "uProjectionMatrix");
        this.context.uniformMatrix4fv(projectionLocation, false, projectionMatrix);
        const viewMatrix = this.cameras.default.getViewMatrix();
        const viewLocation = this.context.getUniformLocation(this.program, "uViewMatrix");
        this.context.uniformMatrix4fv(viewLocation, false, viewMatrix);
        const light1Matrix = this.lights[0].getInfoMatrix();
        const light1Location = this.context.getUniformLocation(this.program, "uLight1");
        this.context.uniformMatrix4fv(light1Location, false, light1Matrix);
    }
    
    これで、頂点遮光物の通常のデータにアクセスできます.
    uniform mat4 uProjectionMatrix;
    uniform mat4 uModelMatrix;
    uniform mat4 uViewMatrix;
    
    attribute vec3 aVertexPosition;
    attribute vec3 aVertexColor;
    attribute vec2 aVertexUV;
    attribute vec3 aVertexNormal;
    varying mediump vec4 vColor;
    varying mediump vec2 vUV;
    varying mediump vec3 vNormal;
    void main(){
        gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aVertexPosition, 1.
    0);
        vColor = vec4(aVertexColor, 1.0);
        vUV = aVertexUV;
        vNormal = vec3(uModelMatrix * vec4(aVertexNormal, 1.0));
    }
    
    そして、我々のピクセル遮光物にそれを渡してください.我々はモデルに追加された回転のようなものを説明するためにモデルマトリックスによってそれを回転させる必要があることを心に留めておいてください.次に、光情報を含むフラグメント遮光物に戻ります.
    varying lowp vec4 vColor;
    varying lowp vec2 vUV;
    varying lowp vec3 vNormal;
    uniform lowp mat4 uLight1;
    uniform sampler2D uSampler;
    void main() {
        //gl_FragColor = texture2D(uSampler, vUV);
        gl_FragColor = vColor;
    }
    
    今、我々は光を置くことができます:
    createLights(){
        this.lights = [
            new Light({
                type: "directional",
                direction: [0,0,1],
                color: [1,1,1,1]
            })
        ]
    }
    
    そしてちょうどそれを呼ぶconnectedCallback .

    方向照明


    方向照明は、照明の種類に基づいてあまり変化しない、これは主に物事が一定のフィールドであるので、物事はかなり均等に点灯する傾向があるためです.
    まずいくつかのデバッグを行い、適切な値を得ていることを確認しましょう.
    //fragment shader
    //definitions....
    void main() {
        gl_FragColor = uLight1[1];
    }
    
    私達は青いメッシュを得るべきだ.これは、我々が正のZ方向でそれを置いたので、青いチャンネルが1である、そして、残り(1であるアルファから離れて)が0であると読むとき、0です.

    クール.
    これで実際の光計算ができます.
    void main() {
        mediump float light = dot(vNormal, uLight1[1].xyz);
        gl_FragColor = vec4(light, light, light, 1);
    }
    
    拡散光強度を表す0と1の間のスカラーを生成する正常と光のドット積をとる.最初の顔が負のzに直面しているので、Fullblastを取得します(そして、私たちは強度を使用しているので、モノクロになります).

    それがよく見えるので、我々は色を考慮に入れるためにそれを微調整します:
    //fragment shader
    void main() {
        mediump float light = dot(vNormal, uLight1[1].xyz);
        gl_FragColor = vColor * vec4(light, light, light, 1);
    }
    

    通常の球は光源の方向を中心とした素晴らしい放射状勾配を得る

    ポイントライト


    ポイントライトは感謝して1つだけ多くの操作です.方向の代わりに、我々は、位置と光線は、球状のパターンに移動してそれらを表します.
    それで、我々がするすべては、我々から光を変えていますspot to point そして、それに位置を与えてください.しかし、我々はいくつかのものを頂点遮光物に加える必要があります:
    uniform mat4 uProjectionMatrix;
    uniform mat4 uModelMatrix;
    uniform mat4 uViewMatrix;
    
    attribute vec3 aVertexPosition;
    attribute vec3 aVertexColor;
    attribute vec2 aVertexUV;
    attribute vec3 aVertexNormal;
    varying mediump vec4 vColor;
    varying mediump vec2 vUV;
    varying mediump vec3 vNormal;
    varying mediump vec3 vPosition;
    void main(){
        gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aVertexPosition, 1.0);
        vUV = aVertexUV;
        vColor = vec4(aVertexColor, 1.0);
        vNormal = vec3(uModelMatrix * vec4(aVertexNormal, 1.0));
        vPosition = vec3(uModelMatrix * vec4(aVertexPosition, 1.0));
    }
    
    我々は今、さまざまな位置を作成しているvPosition . これはモデルが動かされたあと、カメラが起こる前に動かされます、そして、それはピクセルのワールドスペース座標を表します.フラグメント遮光物にこれを使用できます.
    varying mediump vec4 vColor;
    varying mediump vec2 vUV;
    varying mediump vec3 vNormal;
    varying mediump vec3 vPosition;
    uniform lowp mat4 uLight1;
    uniform sampler2D uSampler;
    void main() {
        bool isPoint = uLight1[3][3] == 1.0; 
        if(isPoint){
            //point light + color
            mediump vec3 toLight = normalize(uLight1[0].xyz - vPosition);
            mediump float light = dot(normalize(vNormal), toLight);
            gl_FragColor = vColor * vec4(light, light, light, 1);
        } else {
            //directional light + color
            mediump float light = dot(normalize(vNormal), uLight1[1].xyz);
            gl_FragColor = vColor * vec4(light, light, light, 1);
    }
    
    我々はタイプをチェックします、そして、それが点光であるならば、我々は我々の新しいvPosition ベクトルと我々は同じ計算を行います.ここで重要なことはここにも正常化することです.私はその最後の時間をするのを忘れました、しかし、WebGLが彼らがどのように使われるかについて手がかりがないので、補間された値は正常化されません.そうすることの失敗は、あなたが予想するより明るいものまたは薄暗いものを引き起こします.
    Z = - 1.5での点光

    私たちが正常化するのを忘れたならば、z = - 1.5の点光

    Z = 2.0における点光

    光スポットは、私たちが期待しているものに近づくにつれて小さいことがわかります.

    色光


    また、これはかなり簡単に追加することです.それは単にオブジェクトの色によって光の色を掛けるだけです.白い物体を持って[1,1,1] そして、我々は緑の光を投げました[0,1,0] , 私たちはそれが緑でありたい.でも赤い物体があれば[1,0,0] そして、緑の光を投げてください、そして、オブジェクトが戻る緑を反映しないので、それは黒いはずです.白色光を使用すると、オブジェクトのすべての色を保持する必要があります.
    void main() {
        bool isPoint = uLight1[3][3] == 1.0; 
        if(isPoint){
            //point light + color
            mediump vec3 toLight = normalize(uLight1[0].xyz - vPosition);
            mediump float light = dot(normalize(vNormal), toLight);
            gl_FragColor = vColor * uLight1[2] * vec4(light, light, light, 1);
        } else {
            //directional light + color
            mediump float light = dot(normalize(vNormal), uLight1[1].xyz);
            gl_FragColor = vColor * uLight1[2] * vec4(light, light, light, 1);
        }
    }
    
    `
    私たちは光の3番目の(インデックス2)コンポーネントの光の色を格納し、ちょうどそれを掛けます.
    ラベンダー球を[1,0.5,1] :

    イエローキャスト[1,1,0] ライトオンイット

    ここでたくさんカバーし、私もたくさん編集しなければならなかった!次回は別の照明モデルを見ていきたいです.ここでチェックポイントのコードを見つけることができます.https://github.com/ndesmic/geogl/tree/v4