10-mini版bindingStates

源码:github.com/buglas/thre...

1-WebGLBindingStatesMini的代码

src\renderers\webgl\WebGLBindingStatesMini.js

ini 复制代码
/**
 * 管理 VAO(Vertex Array Object)与顶点属性绑定。
 * 按 geometry.id + program.id 缓存 VAO,仅在 attribute version 变化时重新绑定。
 */
function WebGLBindingStatesMini( gl, attributes ) {
    // 缓存 VAO 与顶点属性:bindingStates[ geometryId ][ programId ] → { object: VAO, attributes: {...} }
    const bindingStates = {};
    // 当前绑定的 VAO
    let currentState = null;
    /**
     * 根据geometry和program 绑定 VAO 
     * 切换 geometry和program 时才 bindVertexArray;数据未变则跳过 vertexAttribPointer。
     */
    function setup( program, geometry ) {
        const state = getBindingState( geometry, program );
        if ( currentState !== state ) {
            currentState = state;
            gl.bindVertexArray( state.object );
        }
        if ( needsUpdate( state, geometry, program ) ) {
            setupVertexAttributes( program, geometry );
            saveCache( state, geometry, program );
        }
    }
    /** 获取 geometry/program 对应的 VAO */
    function getBindingState( geometry, program ) {
        let geometryMap = bindingStates[ geometry.id ];
        if ( geometryMap === undefined ) {
            geometryMap = {};
            bindingStates[ geometry.id ] = geometryMap;
        }
        let state = geometryMap[ program.id ];
        if ( state === undefined ) {
            state = {
                geometry: null,
                program: null,
                object: gl.createVertexArray(),
                attributes: {},
            };
            geometryMap[ program.id ] = state;
        }
        return state;
    }
    /** 比较缓存的 geometry/program id 与各 attribute 的 version */
    function needsUpdate( state, geometry, program ) {
        if ( state.geometry !== geometry.id || state.program !== program.id ) {
            return true;
        }
        // 比较 geometry 的 attributes 的 version
        const geometryAttributes = geometry.attributes;
        for ( const name in geometryAttributes ) {
            const attribute = geometryAttributes[ name ];
            const cached = state.attributes[ name ];
            if ( cached === undefined || cached.version !== attribute.version ) {
                return true;
            }
        }
        return false;
    }
    /** 保存缓存的 geometry/program id 与各 attribute 的 version */
    function saveCache( state, geometry, program ) {
        state.geometry = geometry.id;
        state.program = program.id;
        const geometryAttributes = geometry.attributes;
        for ( const name in geometryAttributes ) {
            const attribute = geometryAttributes[ name ];
            let cached = state.attributes[ name ];
            if ( cached === undefined ) {
                cached = {};
                state.attributes[ name ] = cached;
            }
            cached.version = attribute.version;
        }
    }
​
    /**
     * 将 geometry 的 position 等属性绑定到 shader 对应的 attribute location。
     * Mini 版固定使用 location 0(对应 prefix 中 in vec3 position)。
     */
    function setupVertexAttributes( program, geometry ) {
        const programAttribute = program.getAttributes().position;
        const geometryAttribute = geometry.attributes.position;
        const normalized = geometryAttribute.normalized;
        const size = geometryAttribute.itemSize;
        const attribute = attributes.get( geometryAttribute );
        const buffer = attribute.buffer;
        const type = attribute.type;
        const bytesPerElement = attribute.bytesPerElement;
        gl.enableVertexAttribArray( programAttribute.location );
        gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
        gl.vertexAttribPointer(
            programAttribute.location,
            size,
            type,
            normalized,
            size * bytesPerElement,
            0
        );
    }
    /** program 销毁时释放其关联的所有 VAO */
    function releaseStatesOfProgram( program ) {
        for ( const geometryId in bindingStates ) {
            const geometryMap = bindingStates[ geometryId ];
            const state = geometryMap[ program.id ];
            if ( state !== undefined ) {
                if ( currentState === state ) {
                    currentState = null;
                }
                gl.deleteVertexArray( state.object );
                delete geometryMap[ program.id ];
            }
        }
    }
    /** geometry 销毁时释放其关联的所有 VAO */
    function releaseStatesOfGeometry( geometry ) {
        const geometryMap = bindingStates[ geometry.id ];
        if ( geometryMap === undefined ) return;
        for ( const programId in geometryMap ) {
            const state = geometryMap[ programId ];
            if ( state !== undefined ) {
                if ( currentState === state ) {
                    currentState = null;
                }
                gl.deleteVertexArray( state.object );
            }
        }
        delete bindingStates[ geometry.id ];
    }
    return {
        setup: setup,
        releaseStatesOfProgram: releaseStatesOfProgram,
        releaseStatesOfGeometry: releaseStatesOfGeometry,
    };
}
​
export { WebGLBindingStatesMini };

2-WebGLBindingStatesMini 的功能定位

WebGLBindingStates 的主要作用是告诉shader program 如何从VBO 中读取数据。

WebGLBindingStates 的核心工作是:在每次 draw 前,把 Geometry 的顶点数据 正确挂到 Shader Program 的 attribute 变量 上,并通过 VAO(Vertex Array Object) 缓存配置,避免重复调用昂贵的 WebGL API。

WebGLBindingStates 在WebGL 渲染管线中的位置:

scss 复制代码
Geometry (position/normal/uv...)
        ↓
WebGLAttributes (Buffer → GPU)
        ↓
WebGLBindingStates 
        ↓
WebGLProgram (shader attribute location)
        ↓
gl.drawArrays / gl.drawElements

WebGLBindingStatesMini亦是如此,只是具体功能做了删减。

其对应的webgl 内容是:

ini 复制代码
// 创建 VAO(Vertex Array Object):缓存「position 怎么读」的配置,绘制时 bind 即可恢复
const vao = gl.createVertexArray();
gl.bindVertexArray( vao );
const a_Position = gl.getAttribLocation( program, 'position' );
gl.vertexAttribPointer( a_Position, 3, gl.FLOAT, false, 3 * 4, 0 );
gl.enableVertexAttribArray( a_Position );
gl.bindVertexArray( null );

WebGL 绘制前需要告诉 GPU:

  1. 顶点数据在哪个 Buffer 里
  2. 每个 attribute(如 position)对应 shader 的哪个 location
  3. 数据格式(float/int、几个分量、是否归一化、stride、offset)

这些配置可以缓存在 VAO 里。切换 geometry 或 program 时,只要 bindVertexArray 就能恢复之前的绑定,避免每帧重复 vertexAttribPointer

3-WebGLBindingStatesMini 代码详解

1-构造函数

ini 复制代码
function WebGLBindingStatesMini( gl, attributes ) {
    // 缓存 VAO 与attribute 数据:bindingStates[ geometryId ][ programId ] → { object: VAO,attributes: {...} }
    const bindingStates = {};
    // 当前绑定的 VAO
    let currentState = null;

2.setup(program, geometry)

根据geometry和program 绑定 VAO。

scss 复制代码
function setup( program, geometry ) {
    const state = getBindingState( geometry, program );
    if ( currentState !== state ) {
        currentState = state;
        gl.bindVertexArray( state.object );
    }
    if ( needsUpdate( state, geometry, program ) ) {
        setupVertexAttributes( program, geometry );
        saveCache( state, geometry, program );
    }
}

切换 geometry或program 时才 bindVertexArray;

当VAO 需要更新时,即needsUpdate( state, geometry, program ),才会更新并缓冲VAO。

3. getBindingState(geometry, program)

查找、创建、缓存VAO。

ini 复制代码
function getBindingState( geometry, program ) {
    let geometryMap = bindingStates[ geometry.id ];
    if ( geometryMap === undefined ) {
        geometryMap = {};
        bindingStates[ geometry.id ] = geometryMap;
    }
    let state = geometryMap[ program.id ];
    if ( state === undefined ) {
        state = {
            geometry: null,
            program: null,
            object: gl.createVertexArray(),
            attributes: {},
        };
        geometryMap[ program.id ] = state;
    }
    return state;
}

两层 Map:bindingStates[geometry.id][program.id]

首次命中时, gl.createVertexArray() 并写入缓存。

为何按 geometry + program 缓存?

同一 geometry 在不同 shader program 中, state 可能不同。

举个例子。

下面自定义了2个材质,它们共用一个BufferGeometry,会产生2种shader program。

csharp 复制代码
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', positionAttr);
geometry.setAttribute('normal', normalAttr);
geometry.setAttribute('customData', customAttr);
​
const matFull = new THREE.ShaderMaterial({
  vertexShader: `
    attribute vec3 position;
    attribute vec3 normal;
    attribute float customData;
    void main() { /* 用三个 attribute */ }
  `
});
​
const matPosOnly = new THREE.ShaderMaterial({
  vertexShader: `
    attribute vec3 position;
    void main() { /* 只用 position */ }
  `
});
  • matFull 的 program:VAO 里 enable 3 个 attribute
  • matPosOnly 的 program:VAO 里只 enable 1 个 attribute

若共用一个 VAO,切换 program 后 shader 会从错误的 location 读数据,或读到未绑定的 attribute。

4. needsUpdate(state, geometry, program)

判断VAO是否需要更新。

ini 复制代码
function needsUpdate( state, geometry, program ) {
    if ( state.geometry !== geometry.id || state.program !== program.id ) {
        return true;
    }
    // 比较 geometry 的 attributes 的 version
    const geometryAttributes = geometry.attributes;
    for ( const name in geometryAttributes ) {
        const attribute = geometryAttributes[ name ];
        const cached = state.attributes[ name ];
        if ( cached === undefined || cached.version !== attribute.version ) {
            return true;
        }
    }
    return false;
}

更新VAO的条件:

  • state.geometry !== geometry.id
  • state.program !== program.id
  • 任一 attribute 的 cached.version !== attribute.version

attribute.version 是BufferAttribute 对象的version。

BufferAttribute.version 默认为了0,在BufferAttribute.needsUpdate = true 时,BufferAttribute.version 会递增。

arduino 复制代码
set needsUpdate( value ) {
    if ( value === true ) this.version ++;
}

当BufferAttribute 数据发生改变时,需要手动设置needsUpdate 为true。

ini 复制代码
const position=new THREE.BufferAttribute( vertices, 3 )
...
position.applyMatrix4( matrix );
position.needsUpdate = true;

5. setupVertexAttributes(program, geometry)

绑定shader program 中的attribute,设置attribute 从VBO 中读取数据的方式。

ini 复制代码
function setupVertexAttributes( program, geometry ) {
    const programAttribute = program.getAttributes().position;
    const geometryAttribute = geometry.attributes.position;
    const normalized = geometryAttribute.normalized;
    const size = geometryAttribute.itemSize;
    const attribute = attributes.get( geometryAttribute );
    const buffer = attribute.buffer;
    const type = attribute.type;
    const bytesPerElement = attribute.bytesPerElement;
    gl.enableVertexAttribArray( programAttribute.location );
    gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
    gl.vertexAttribPointer(
        programAttribute.location,
        size,
        type,
        normalized,
        size * bytesPerElement,
        0
    );
}

6. saveCache()

绑定attribute后,把 geometry.idprogram.id 和各 attribute 的 version 写入 state,供下次 needsUpdate 比较。

ini 复制代码
function saveCache( state, geometry, program ) {
    state.geometry = geometry.id;
    state.program = program.id;
    const geometryAttributes = geometry.attributes;
    for ( const name in geometryAttributes ) {
        const attribute = geometryAttributes[ name ];
        let cached = state.attributes[ name ];
        if ( cached === undefined ) {
            cached = {};
            state.attributes[ name ] = cached;
        }
        cached.version = attribute.version;
    }
}

6. releaseStatesOfProgram(program) --- 资源释放

遍历所有 geometry 下该 program 的 VAO,gl.deleteVertexArray() 并删缓存。完整版在 program 销毁时会调用;

ini 复制代码
/** program 销毁时释放其关联的所有 VAO */
function releaseStatesOfProgram( program ) {
    for ( const geometryId in bindingStates ) {
        const geometryMap = bindingStates[ geometryId ];
        const state = geometryMap[ program.id ];
        if ( state !== undefined ) {
            if ( currentState === state ) {
                currentState = null;
            }
            gl.deleteVertexArray( state.object );
            delete geometryMap[ program.id ];
        }
    }
}

7.releaseStatesOfGeometry( geometry )

geometry 销毁时释放其关联的所有 VAO。

ini 复制代码
function releaseStatesOfGeometry( geometry ) {
    const geometryMap = bindingStates[ geometry.id ];
    if ( geometryMap === undefined ) return;
    for ( const programId in geometryMap ) {
        const state = geometryMap[ programId ];
        if ( state !== undefined ) {
            if ( currentState === state ) {
                currentState = null;
            }
            gl.deleteVertexArray( state.object );
        }
    }
    delete bindingStates[ geometry.id ];
}

4-与完整版 WebGLBindingStates.js 的对比

维度 Mini 版 完整版
缓存键 geometry.id + program.id geometry + object + program + wireframe
索引绘制 无(仅 drawArrays 支持 ELEMENT_ARRAY_BUFFER
Instancing / Morph / 多 Pass
生命周期 releaseStatesOfProgram releaseStatesOfGeometry/Object/Program

Mini 版刻意保留 VAO 缓存 + 脏检查的核心思路,去掉复杂分支,便于理解 Three.js 如何把 JavaScript 侧的 BufferGeometry 映射到 WebGL 绘制状态。

总结

WebGLBindingStatesMini 的核心职责是:管理 VAO(Vertex Array Object)与顶点属性绑定。

WebGLBindingStatesMini 的核心架构思路:

  1. geometry.id + program.id 缓存 VAO 每个 geometry + shader program 组合对应一个 VAO,避免重复创建。

  2. 按需更新

    • 切换 geometry/program 时,才调用 gl.bindVertexArray
    • 通过比较 attribute 的 version 判断是否需要重新 vertexAttribPointer
    • 数据未变则跳过绑定,减少 GPU 状态切换
  3. Mini 版简化 只处理 position 这一个顶点属性(对应 shader 里的 in vec3 position),不像完整版那样支持多种 attribute、instancing、index buffer 等。

相关推荐
李伟_Li慢慢1 小时前
08-BufferAttribute
前端·three.js
李伟_Li慢慢1 小时前
09-mini版WebGLAttributes
前端·three.js
李伟_Li慢慢1 小时前
11-mini版WebGLGeometries
前端·three.js
李伟_Li慢慢1 小时前
18-mini版WebGLRenderList
前端·three.js
李伟_Li慢慢1 小时前
15-mini版WebGLMaterials
前端·three.js
ji_shuke1 小时前
Vue3 前端批量打印 PDF/图片踩坑记:跨域、合并打印、对话框闪退与按钮一直 loading
前端·pdf·状态模式·pdf打印
李伟_Li慢慢1 小时前
06-mini版WebGLState
前端·three.js
李伟_Li慢慢1 小时前
04-three.js的渲染流程概览
前端·three.js
李伟_Li慢慢1 小时前
07-WebGLProperties
前端·three.js