THREE.js複数のcastShadowを追加した光源エラー原因分析


最近THREE.jsを使ってシーンに30ぐらいのcastShadowの光源を追加しました.そしてコンソールでエラーを報告します.
THREE.WebGLProgram: shader error:  0 35715 false gl.getProgramInfoLog Varyings over maximum register limit
この記事にはこのエラーの原因が記載されています.
varyingの意味
まずVaryings over maximum register limitを見てみます.どういう意味ですか?Varyings変数の登録数が制限を超えていますが、何がVaryingですか?発色器言語は3つの変数タイプを提供します.
  • atribute:外部から頂点着色器に伝達される変数は、一般的に頂点データを転送するために使用されます.
  • uniform:外部から頂点着色器またはパッチ要素着色器に伝達される変数は、定数に類似しています.修正できないものしか使えません.変換行列、材質、照明、色などの情報を伝送するのに一般的です.
  • varying:頂点パッチから情報を送る変数は、転送時にその変数を線形補間するので、varyingという単語はこの変化の意味を表しています.
  • varying変数の個数制限
    上記のvaryingは、着色器言語におけるvarying変数、つまりバリング変数の数が最大限度を超えているということです.どれぐらいのvarying変数を定義できますか?
    このvarying変数の数と具体的な実現に関する情報を検索することにより、このサイトをクリックしてくださいは中でMax Varying Vectorsを検索する.私のパソコンの表示は15です.
    THREE.jsはなぜエラーが発生しましたか?
    エラーを報告したTHREE.jsバージョンは110番で、原因分析を間違えた時に使うのは119バージョンです.
    まず、ソースコードの中でgl.getProgramInfoLogを検索します.大体コードのどの位置が間違っていますか?エラーが発見されたコードはWebGLOgram.jsファイルのWebGLOProgram関数で、この関数の機能は大体programを作成してコンパイルします.
    function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) {
        const gl = renderer.getContext();
        // ...
        const program = gl.createProgram(); //     program
        // ...
        //                    
        const glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl ); //           
        const glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl ); //           
        gl.attachShader( program, glVertexShader ); //  program  
        gl.attachShader( program, glFragmentShader ); //  program  
        // ...
        gl.linkProgram( program );
        // ...           
        const programLog = gl.getProgramInfoLog( program ).trim(); //       
        if (...) { //     
            console.error(... 'gl.getProgramInfoLog' ...) //     
        }
    }
    コードから分かるように、この関数はまずプログララムを作成し、このプログラに頂点とパッチパッチを追加してコンパイルします.コンパイルしたのは何ですか?
    コンパイルはテキストであるべきだと思います.テキストを実行できるコードセグメントにコンパイルします.いったい正しいですか?
    私達は知っていて、頂点のカラーライターと切れの元のカラーライターはカラーライターの言語を使って編纂したので、これは1種のCの言語で、私達の熟知しているjavascriptではありません.上に色器を作成したWebGL ShaderはTHREE.jsパッケージの関数であり、その第三のパラメータはソースの文字列形式である.次にcompileShaderを呼び出してコンパイルする.
    function WebGLShader( gl, type, string ) {
        const shader = gl.createShader( type );
    
        gl.shaderSource( shader, string );
        gl.compileShader( shader );
    
        return shader;
    }
    したがって、上記のエラーは動態的に発生したソースコードに問題がある可能性があります.バリング変数は、頂点ディスペンサーからパッチへデータを転送するために使用されますので、同じバリング変数は二つのカラーライターの中で宣言されますので、私たちは一つだけ分析してもいいです.私が分析したのは、上のvertexGlsl変数です.
    let vertexShader = parameters.vertexShader;
    // ...
    if ( parameters.isRawShaderMaterial ) { //       ,   
    } else { // THREE.js      
        prefixVertex = [...]
    }
    const vertexGlsl = prefixVertex + vertexShader;
    vertexGlslに関連する2つの変数prefixVertexvertexShaderを見つけました.
    prefix Vetexテキスト分析
    エラーはcastShadowを開いてからあるので、castShadowに関するコードがありますか?まずprefixVerstexの中にshowMapEnbaledがあります.ちょっと関係があります.
    parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',
    parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '',
    次にparametersを見て検証してみます.この変数はWebGLOProgramのパラメータです.その後、どこでWebGLOProgramを呼び出したのか探してみます.最後に発見されたのはWebGLOgrams.jsファイルの中のacquire Program関数です.
    function acquireProgram( parameters, cacheKey ) {
        // ...
        program = new WebGLProgram( renderer, cacheKey, parameters, bindingStates );
        // ...
    }
    parametersはacquire Programのパラメータですので、この関数はどこで呼び出されたかを見てください.WebGrenderer.jsの中のinitMaterial関数が呼び出されました.
    function initMaterial( material, scene, object ) {
        // ...
        const shadowsArray = currentRenderState.state.shadowsArray;
        // ...
        const parameters = programCache.getParameters( material, lights.state, shadowsArray, ... );
        // ...
        program = programCache.acquireProgram( parameters, programCacheKey );
        // ...
    }
    検索でprogramCache = が発見されました.
    programCache = new WebGLPrograms( _this, extensions, capabilities, bindingStates );
    したがって、再びWebGLOPrograms.jsファイルに戻ってgetParameters関数を参照してください.
    function getParameters( material, lights, shadows, scene, nClipPlanes, nClipIntersection, object ) {
        // ...
        shadowMapEnabled: renderer.shadowMap.enabled && shadows.length > 0
        // ...
    }
    initMaterialに戻って、getParametersを呼び出したときに入ってきたshadows変数は何ですか?発見はshadowsArrayである.
    const shadowsArray = currentRenderState.state.shadowsArray;
    currentRenderState = renderStates.get( scene, _currentArrayCamera || camera ); //       
    renderStates = new WebGLRenderStates()
    WebGloStatitesの内部でWeakMapを使っています.そのkeyはsceneで、その値はWeakMapで、このmapのkeyはcameraで、valueはWebGrenderStateです.注意先はWebGrenderStatitesです.sがあります.
    renderState = new WebGLRenderState();
    renderStates.set( scene, new WeakMap() );
    renderStates.get( scene ).set( camera, renderState );
    次にWebGrenderStateを見てみます.中にはpush Shadowの方法があります.前のconst shadowsArray = currentRenderState.state.shadowsArray;と対応できると思います.
    function pushShadow( shadowLight ) {
        shadowsArray.push( shadowLight );
    }
    次に、push Shadowがどこで呼び出されたかを見て、WebGrender.jsファイルのcomple方法で呼び出されました.
    this.compile = function ( scene, camera ) {
        //     renderStates      WeakMap,   scene    ,   camera    
        currentRenderState = renderStates.get( scene, camera );
        currentRenderState.init();
    
        //       
        scene.traverse( function ( object ) {
            if ( object.isLight ) { //    
    
                currentRenderState.pushLight( object );
    
                if ( object.castShadow ) { //        
                    currentRenderState.pushShadow( object );
                }
            }
        } );
    
        currentRenderState.setupLights( camera );
    
        const compiled = new WeakMap();
    
        scene.traverse( function ( object ) {
            // ... initMaterial
        } );
    };
    comppileの方法はまずsceneとcameraによって関連renderstateを獲得して、それからシーンの対象を遍歴して、castShadowの光源をshows Arayの中に置きます.後ろから材質を初期化します.材質を初期化する時は、前に述べた頂点着色器と片元着色器をコンパイルします.
    コードセグメントshadowMapEnabled: renderer.shadowMap.enabled && shadows.length > 0に戻ります.シーンにcastShadowの光源を追加したとき、このshows配列の長さは0より大きいです.だからshow MapEnbaledはtrueです.
    prefix Vetexテキストには#define USE_SHADOWMAPが含まれています.
    parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',
    prefix Vetexを分析したところ、関連するのは頂点のカラータイマーコードに#define USE_SHADOWMAPを追加しています.varyingの声明を見ていません.varying声明はvertexShaderテキストにあるはずです.
    vertex Shaderテキスト分析
    WebGLOProgram関数において、vertexShaderはparametersの属性の一つである.
    function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) {
        let vertexShader = parameters.vertexShader;
    }
    同じように、私達はWebGLOgrams.jsファイルを見つけました.get Parameters関数の中のvertexShaderはどうやって得られますか?
    function getParameters( material, lights, shadows, scene, nClipPlanes, nClipIntersection, object ) {
        // ...
        const shaderID = shaderIDs[ material.type ];
        // ...
        let vertexShader, fragmentShader;
        if ( shaderID ) { //        /       
            const shader = ShaderLib[ shaderID ];
    
            vertexShader = shader.vertexShader;
            fragmentShader = shader.fragmentShader;
        }
        // ...
    }
    shardersを見てください
    const shaderIDs = {
        MeshDepthMaterial: 'depth',
        MeshDistanceMaterial: 'distanceRGBA',
        MeshNormalMaterial: 'normal',
        MeshBasicMaterial: 'basic',
        MeshLambertMaterial: 'lambert',
        MeshPhongMaterial: 'phong',
        MeshToonMaterial: 'toon',
        MeshStandardMaterial: 'physical',
        MeshPhysicalMaterial: 'physical',
        MeshMatcapMaterial: 'matcap',
        LineBasicMaterial: 'basic',
        LineDashedMaterial: 'dashed',
        PointsMaterial: 'points',
        ShadowMaterial: 'shadow',
        SpriteMaterial: 'sprite'
    };
    私達は材質がMeshardardMaterialであると仮定して、sharder IDは'physical'であり、その後ShaderLibを見て、ShaderLib.jsファイルで定義されています.
    ShaderLib.physical = {
        // ...
        vertexShader: ShaderChunk.meshphysical_vert,
        // ...
    }
    import meshphysical_vert from './ShaderLib/meshphysical_vert.glsl.js';
    export const ShaderChunk = {
        // ...
        meshphysical_vert: meshphysical_vert,
        // ...
    }
    やっと頭を見つけました.つまりmeshphysicalです.vert.glsl.jsファイルで、showmapに関するコードを見つけました.
    #include 
    sharowmapを見てくださいパーシ_vertex.glsl.jsファイル:
    export default /* glsl */`
    #ifdef USE_SHADOWMAP
    
        #if NUM_DIR_LIGHT_SHADOWS > 0
    
            uniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];
            varying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];
            # ...
        #endif
    
        #if NUM_SPOT_LIGHT_SHADOWS > 0
    
            uniform mat4 spotShadowMatrix[ NUM_SPOT_LIGHT_SHADOWS ];
            varying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHT_SHADOWS ];
            # ...
        #endif
    
        #if NUM_POINT_LIGHT_SHADOWS > 0
    
            uniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];
            varying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];
            # ...
        #endif
    #endif
    `;
    注意先頭の#ifdef USE_SHADOWMAPは、prefixVerstexが最後に何を生成したか覚えていますか?USE_SHADOWMAPじゃないですか?
    #define USE_SHADOWMAP
    このdefineがないと、ifdefはこの判断に失敗してしまい、中間のこの部分のコードに辿り着けなくなります.
    私たちが使用していると仮定して、spotLightは、長いNUM_SPOT_LIGHT_SHADOWSのvec 4配列であると宣言します.この配列の長さはどれぐらいですか?どれぐらいのvarying変数の定員を占用しますか?
    varying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHT_SHADOWS ];
    NUM_SPOT_LIGHT_SHADOWS変数は、castShadowを有効にした光源の数であるべきだと推測します.検証してみますか
    ソースコードからNUM_SPOT_LIGHT_SHADOWSを検索します.WebGLOProgram.jsファイルから関数が見つかります.
    function replaceLightNums( string, parameters ) {
        return string
            // ...
            .replace( /NUM_SPOT_LIGHT_SHADOWS/g, parameters.numSpotLightShadows )
            // ...
    }
    WebGLOProgramの後に、vertexShaderに対してさらなる処理を行います.replaceLightNumsが含まれている.
    vertexShader = replaceLightNums( vertexShader, parameters );
    最後に、parameters.numSpotLightShadowsという変数だけを見る必要があります.
    function getParameters( material, lights, shadows, scene, nClipPlanes, nClipIntersection, object ) {
        // ...
        numSpotLightShadows: lights.spotShadowMap.length,
        // ...
    }
    lightsはちょうど前に解析したcurrentRenderState.state.shadowsArrayです.この変数のspotShadowMap変数はWebGLLightssetup関数に設定されています.
    function setup( lights, shadows, camera ) {
        // ...
        let numSpotShadows = 0;
        // ...
        for ( let i = 0, l = lights.length; i < l; i ++ ) {
            // ...
            if ( light.castShadow ) {
                // ...
                numSpotShadows ++;
                // ...
            }
            // ...
        }
        // ...
        state.spotShadowMap.length = numSpotShadows;
        // ...
    }
    ここで分析したのはほぼ同じです.n個のcastShadowを有効にする光源はn個のvarying変数を占有します.
    頂点着色器はcastShadowの光源占用のvarying以外に他のvarying変数があります.すべてのvarying変数を合わせて15個を超えてはいけません.私の例ではcastShadowの光源は最大で12、13個ぐらいです.
    ソリューション
    では、varyingの制限数を超えるcastShadowを有効にする光源をどのようにシーンに追加しますか?
    検索してみると、WebGLDeferredRendererを使ってもいいという話がありますが、これは数年前から資源のメンテナンスがないので除去されました.
    他の案はまだ見つかっていません.
    もし皆さんに何かいい解決策がありましたら、コメントエリアにメッセージを残してください.勉強します.
    締め括りをつける
    THREE.jsとWebGLはよく分かりません.もし間違いがあれば、コメントエリアでコメントしてください.