1-WebGLAttributesMini 的代码
src\renderers\webgl\WebGLAttributesMini.js
arduino
/**
* 管理 BufferAttribute 与 WebGL buffer 的映射。
* 首次上传用 bufferData,后续仅在 attribute.version 变化时用 bufferSubData 增量更新。
*/
function WebGLAttributesMini( gl ) {
const buffers = new WeakMap();
/** 首次创建 GPU 端 ARRAY_BUFFER 并上传完整数据 */
function createBuffer( attribute, bufferType ) {
const array = attribute.array;
const usage = attribute.usage;
const size = array.byteLength;
const buffer = gl.createBuffer();
gl.bindBuffer( bufferType, buffer );
gl.bufferData( bufferType, array, usage );
let type;
if ( array instanceof Float32Array ) {
type = gl.FLOAT;
}
return {
buffer: buffer,
type: type,
bytesPerElement: array.BYTES_PER_ELEMENT,
version: attribute.version,
size: size
};
}
/** 顶点数据变更时,原地更新已有 buffer(不支持改变 array 字节长度) */
function updateBuffer( buffer, attribute, bufferType ) {
gl.bindBuffer( bufferType, buffer );
// TODO: 支持 updateRanges
gl.bufferSubData( bufferType, 0, attribute.array );
}
/** 获取 attribute 对应的 GPU buffer */
function get( attribute ) {
return buffers.get( attribute );
}
/** 删除 attribute 对应的 GPU buffer */
function remove( attribute ) {
const data = buffers.get( attribute );
if ( data ) {
gl.deleteBuffer( data.buffer );
buffers.delete( attribute );
}
}
/** 更新 attribute 对应的 GPU buffer
* attribute: BufferAttribute
*/
function update( attribute, bufferType ) {
const data = buffers.get( attribute );
console.log('attribute',attribute);
if ( data === undefined ) {
buffers.set( attribute, createBuffer( attribute, bufferType ) );
} else if ( data.version < attribute.version ) {
if ( data.size !== attribute.array.byteLength ) {
throw new Error( 'THREE.WebGLAttributesMini: The size of the buffer attribute's array buffer does not match the original size. Resizing buffer attributes is not supported.' );
}
updateBuffer( data.buffer, attribute, bufferType );
data.version = attribute.version;
}
}
return {
get: get,
remove: remove,
update: update
};
}
export { WebGLAttributesMini };
2-WebGLAttributesMini 的功能定位
WebGLAttributes 负责将CPU里的attribute 数据上传到GPU,也就是创建VBO。
WebGLAttributesMini 亦是如此,只是具体功能做了删减。
其对应的webgl 内容是:
ini
const buffer = gl.createBuffer();
gl.bindBuffer( bufferType, buffer );
gl.bufferData( bufferType, array, usage );
上面的array 就是attribute 数据。
webgl在将CPU里的attribute 数据上传到GPU时,是把attribute 数据深拷贝一份上传的。
所以在gl.bufferData() 后,array 的变化并不会影响GPU 里的数据。
3-WebGLAttributesMini 中的方法详解
构造函数
javascript
function WebGLAttributesMini( gl ) {
const buffers = new WeakMap();
- 工厂函数,接收 WebGL2 上下文
gl - 用
WeakMap缓存:BufferAttribute→ GPU 元数据 - 对外暴露三个方法:
get、remove、update
Buffer 缓存对象结构:
arduino
return {
buffer: buffer,
type: type,
bytesPerElement: array.BYTES_PER_ELEMENT,
version: attribute.version,
size: size
};
| 字段 | 含义 |
|---|---|
buffer |
gl.createBuffer() 得到的 WebGL buffer |
type |
WebGL 数据类型(Mini 版仅 gl.FLOAT) |
bytesPerElement |
每个元素字节数(Float32 为 4) |
version |
上次同步时的 attribute.version |
size |
首次上传时的 array.byteLength |
createBuffer --- 首次上传
ini
function createBuffer( attribute, bufferType ) {
const array = attribute.array;
const usage = attribute.usage;
const size = array.byteLength;
const buffer = gl.createBuffer();
gl.bindBuffer( bufferType, buffer );
gl.bufferData( bufferType, array, usage );
let type;
if ( array instanceof Float32Array ) {
type = gl.FLOAT;
}
return { ... };
}
gl.createBuffer()创建 GPU buffergl.bufferData(...)整包上传 TypedArray- 仅当
array是Float32Array时设置type = gl.FLOAT。完整版还有许多其它类型的type,此处做了简化。
在纯色三角形的绘制中,VBO 主要是用于存储三角形的position 的,如下所示:
ini
const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array( [
- 1.0, - 1.0, 0.0,
1.0, - 1.0, 0.0,
0.0, 1.0, 0.0
] );
geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
updateBuffer --- 数据变更时更新
scss
function updateBuffer( buffer, attribute, bufferType ) {
gl.bindBuffer( bufferType, buffer );
gl.bufferSubData( bufferType, 0, attribute.array );
}
与完整版不同:
- 总是从 offset 0 全量
bufferSubData - 不支持
attribute.updateRanges局部更新 - 没有
onUploadCallback()回调
适合静态或偶尔整包更新的几何体,不适合大量局部改动的场景。
update --- 将CPU中的attribute 数据更新到GPU
kotlin
function update( attribute, bufferType ) {
const data = buffers.get( attribute );
if ( data === undefined ) {
buffers.set( attribute, createBuffer( attribute, bufferType ) );
} else if ( data.version < attribute.version ) {
if ( data.size !== attribute.array.byteLength ) {
throw new Error( 'THREE.WebGLAttributesMini: ...' );
}
updateBuffer( data.buffer, attribute, bufferType );
data.version = attribute.version;
}
}
逻辑:
| 情况 | 行为 |
|---|---|
| 首次见到 attribute | createBuffer,写入 WeakMap |
attribute.version 变大 |
updateBuffer,同步 version |
| version 未变 | 什么都不做(避免重复上传) |
| array 字节长度变化 | 抛错,不支持 resize |
version 由 BufferAttribute.needsUpdate = true 触发递增,与完整版 Three.js 一致。
Mini 版不处理:
InterleavedBufferAttribute(不会解包到attribute.data)GLBufferAttribute(外部已有 buffer 的情况)ELEMENT_ARRAY_BUFFER索引(Mini 用drawArrays,不用 index buffer)
get / remove
get(attribute) --- 供 WebGLBindingStatesMini 绑定 VAO
src\renderers\webgl\WebGLBindingStatesMini.js
ini
const attribute = attributes.get( geometryAttribute );
const buffer = attribute.buffer;
const type = attribute.type;
const bytesPerElement = attribute.bytesPerElement;
gl.enableVertexAttribArray( 0 );
gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
gl.vertexAttribPointer(
0,
size,
type,
normalized,
size * bytesPerElement,
0
);
remove(attribute) --- 几何体 dispose 时释放 GPU 资源:
src\renderers\webgl\WebGLGeometriesMini.js
ini
function onGeometryDispose( event ) {
const geometry = event.target;
attributes.remove( geometry.index );
for ( const name in geometry.attributes ) {
attributes.remove( geometry.attributes[ name ] );
}
geometry.removeEventListener( 'dispose', onGeometryDispose );
delete geometries[ geometry.id ];
}
4-WebGLAttributesMini 的典型运行时流程
- 场景遍历到
Mesh→objects.update(object)→geometries.update(geometry) - 对每个 attribute 调用
attributes.update(..., ARRAY_BUFFER) - 若
needsUpdate = true,CPU 数据通过bufferSubData同步到 GPU - 绘制前
bindingStates.setup(program, geometry)→attributes.get()取 buffer → 设置vertexAttribPointer bufferRenderer.render(0, position.count)执行drawArrays
总结
WebGLAttributes 会在GPU中创建buffer,然后向buffer 中存储attribute 数据。