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:
- 顶点数据在哪个 Buffer 里
- 每个 attribute(如
position)对应 shader 的哪个 location - 数据格式(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 个 attributematPosOnly的 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.idstate.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.id、program.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 的核心架构思路:
-
按
geometry.id + program.id缓存 VAO 每个 geometry + shader program 组合对应一个 VAO,避免重复创建。 -
按需更新
- 切换 geometry/program 时,才调用
gl.bindVertexArray - 通过比较 attribute 的
version判断是否需要重新vertexAttribPointer - 数据未变则跳过绑定,减少 GPU 状态切换
- 切换 geometry/program 时,才调用
-
Mini 版简化 只处理
position这一个顶点属性(对应 shader 里的in vec3 position),不像完整版那样支持多种 attribute、instancing、index buffer 等。