From 2e3aef939c7df2c0f473d9c26745bd7eb140eb01 Mon Sep 17 00:00:00 2001 From: blobt Date: Sun, 2 May 2021 17:36:44 +0800 Subject: [PATCH] webGLMesh --- src/render/webgl/WebGLMesh.ts | 722 ++++++++++++++++++++++++++++++++++ 1 file changed, 722 insertions(+) diff --git a/src/render/webgl/WebGLMesh.ts b/src/render/webgl/WebGLMesh.ts index 66d79a52..d23f5521 100755 --- a/src/render/webgl/WebGLMesh.ts +++ b/src/render/webgl/WebGLMesh.ts @@ -1,3 +1,725 @@ +import { GLAttribBits, GLAttribState, GLAttribOffsetMap } from "./WebGLAttribState"; +import { vec2, vec3, vec4, mat4 } from "../math"; +import { TypedArrayList } from "../ds/TypedArrayList"; +import { GLProgram } from "./WebGLProgram"; +import { GLTexture } from "./WebGLTexture" + export abstract class GLMeshBase { + /** + * @var WebGL渲染上下文 + */ + public gl: WebGLRenderingContext; + + /** + * @var 7中基本图元 + */ + public drawMode: number + + /** + * @var 顶点属性格式 + */ + protected _attribState: GLAttribBits; + + /** + * @var 顶点属性的stride字节数 + */ + protected _attribStride: number; + + /** + * @var VAO + */ + protected _vao: OES_vertex_array_object; + + /** + * @var VAO对象 + */ + protected _vaoTarget: WebGLVertexArrayObjectOES; + + public constructor(gl: WebGLRenderingContext, attribState: GLAttribBits, drawMode: number = gl.TRIANGLES) { + + this.gl = gl; + + //创建 VAO + let vao: OES_vertex_array_object | null = this.gl.getExtension("OES_vertex_array_object"); + if (vao === null) { + throw new Error("Not support OES_vertex_array_object"); + } + this._vao = vao; + + let vaoTarget: WebGLVertexArrayObjectOES | null = this._vao.createVertexArrayOES(); + if (vaoTarget === null) { + throw new Error("Not support WebGLVertexArrayObjectOES"); + } + this._vaoTarget = vaoTarget; + + this._attribState = attribState; + this._attribStride = GLAttribState.getVertexBytesStride(this._attribState); + this.drawMode = drawMode; + + } + + /** + * 绑定vao + */ + public bind(): void { + this._vao.bindVertexArrayOES(this._vaoTarget); + } + + /** + * 解绑vao + */ + public unbind(): void { + this._vao.bindVertexArrayOES(null); + } + + /** + * @var + */ + public get vertexStride(): number { + return this._attribStride; + } +} + +export class GLStaticMesh extends GLMeshBase { + + /** + * @var 顶点缓冲区 + */ + protected _vbo: WebGLBuffer; + + /** + * @var 顶点数量 + */ + protected _vertCount: number = 0; + + /** + * @var 索引缓冲区 + */ + protected _ibo: WebGLBuffer | null = null; + + /** + * @var 索引数量 + */ + protected _indexCount: number = 0; + + /** + * @var + */ + public mins: vec3; + + /** + * @var + */ + public maxs: vec3; + + public constructor(gl: WebGLRenderingContext, attribState: GLAttribBits, vbo: Float32Array | ArrayBuffer, ibo: Uint16Array | null = null, drawMode: number = gl.TRIANGLES) { + + super(gl, attribState, drawMode); + + //要先绑定vao才能使用vao来管理vbo + this.bind(); + + //创建vbo + let vb: WebGLBuffer | null = gl.createBuffer(); + if (vb === null) { + throw new Error("create vbo failed"); + } + this._vbo = vbo; + + //绑定vbo并载入顶点数据 + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this._vbo); + this.gl.bufferData(this.gl.ARRAY_BUFFER, vbo, this.gl.STATIC_DRAW); + + // 然后计算出交错存储的顶点属性attribOffsetMap相关的值 + let offsetMap: GLAttribOffsetMap = GLAttribState.getInterleaveLayoutAttribOffsetMap(this._attribState); + + //计算顶点数量 + this._vertCount = vbo.byteLength / offsetMap[GLAttribState.ATTRIBSTRIDE]; + + // 使用VAO后,我们只要初始化时设置一次setAttribVertexArrayPointer和setAttribVertexArrayState就行了 + // 当我们后续调用基类的bind方法绑定VAO对象后,VAO会自动处理顶点地址绑定和顶点属性寄存器开启相关操作,这就简化了很多操作 + GLAttribState.setAttribVertexArrayPointer(gl, offsetMap); + GLAttribState.setAttribVertexArrayState(gl, this._attribState); + + this.setIBO(ibo); + + this.unbind(); + + this.mins = new vec3(); + this.maxs = new vec3(); + } + + /** + * 配置ibo + * @param ibo + * @returns + */ + protected setIBO(ibo: Uint16Array | null): void { + if (ibo === null) { + return; + } + + this._ibo = this.gl.createBuffer(); + if (this._ibo === null) { + throw new Error("create ibo failed!"); + } + + //绑定ibo 并赋值 + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this._ibo); + this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, ibo, this.gl.STATIC_DRAW); + + this._indexCount = ibo.length; + } + + /** + * 绘制图形 + */ + public draw(): void { + this.bind(); + if (this._ibo !== null) { + // 如果有IBO,使用drawElements方法绘制静态网格对象 + this.gl.drawElements(this.drawMode, this._indexCount, this.gl.UNSIGNED_SHORT, 0); + } else { + // 如果没有IBO,则使用drawArrays方法绘制静态网格对象 + this.gl.drawArrays(this.drawMode, 0, this._vertCount); + } + this.unbind(); + } + + /** + * 注1:drawElement的offset是以字节为单位的,count是以索引个数为单位的 + * 注2:drawRange内部并没有调用bind和unbind方法,因此要调用drawRange方法的话,必须如下方式: + * mesh.bind(); // 绑定VAO + * mesh.drawRange( 2, 5); // 调用drawRange方法 + * mesh.unbind(); // 解绑VAO + * + * drawRange绘制从offset开始,绘制count个索引 + * @param offset + * @param count + */ + public drawRange(offset: number, count: number) { + if (this._ibo !== null) { + this.gl.drawElements(this.drawMode, count, this.gl.UNSIGNED_SHORT, offset); + } else { + this.gl.drawArrays(this.drawMode, offset, count); + } + } +} + +export class GLIndexedStaticMesh extends GLStaticMesh { + + private _indices: TypedArrayList; + + public constructor(gl: WebGLRenderingContext, attribState: GLAttribBits, vbo: Float32Array | ArrayBuffer, drawMode: number = gl.TRIANGLES) { + super(gl, attribState, vbo, null, drawMode); + this._indices = new TypedArrayList(Uint16Array, 90); + } + + /** + * 添加一个索引 + * @param idx + * @returns + */ + public addIndex(idx: number): GLIndexedStaticMesh { + this._indices.push(idx); + this._indexCount = this._indices.length; + return this; + } + + /** + * 清除所有索引 + * @returns + */ + public clearIndices(): GLIndexedStaticMesh { + this._indices.clear(); + this._indexCount = 0; + return this; + } + + /** + * 设置ibo + * @param ibo + */ + protected setIBO(ibo: Uint16Array | null): void { + this._ibo = this.gl.createBuffer(); + if (this._ibo === null) { + throw new Error("create ibo falied!"); + } + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this._ibo); + } + + /** + * 绘制 + */ + public draw(): void { + this.bind(); + if (this._ibo !== null) { + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this._ibo); + this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, this._indexCount, this.gl.UNSIGNED_SHORT); + this.gl.drawElements(this.drawMode, this._indexCount, this.gl.UNSIGNED_SHORT, 0); + } else { + this.gl.drawArrays(this.drawMode, 0, this._vertCount); + } + this.unbind(); + } + + /** + * + * @param offset 以字节为单位 + * @param count 以索引个数为单位 + */ + public drawRange(offset: number, count: number) { + if (this._ibo !== null) { + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this._ibo); + this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, this._indices.subArray(), this._indexCount); + this.gl.drawElements(this.drawMode, count, this.gl.UNSIGNED_SHORT, offset); + } else { + this.gl.drawArrays(this.drawMode, offset, count); + } + } +} + +export enum EVertexLayout { + INTERLEAVED, + SEQUENCED, + SEPARATED +} + +export class GLMeshBuilder extends GLMeshBase { + + private static SEQUENCED: string = "SEQUENCED"; + private static INTERLEAVED: string = "INVERLEAVED"; + + /** + * @var 顶点在内存或显示中的布局方式 + */ + private _layout: EVertexLayout; + + /* 为了简单,目前只支持顶点位置坐标,纹理坐标,颜色,法线这四种顶点属性格式 */ + + /** + * @var + */ + private _color: vec4 = new vec4([0, 0, 0, 0]); + + /** + * @var + */ + private _texCoord: vec2 = new vec2([0, 0]); + + /** + * @var + */ + private _normal: vec3 = new vec3([0, 0, 1]); + + /** + * @var 是否有颜色 + */ + private _hasColor: boolean; + + /** + * @var 是否有texcoord + */ + private _hasTexcoord: boolean; + + + /** + * @var 是否有法线 + */ + private _hasNormal: boolean; + + /** + * @var 渲染的数据原 + */ + private _lists: { [key: string]: TypedArrayList } = {} + + /** + * 渲染用的VBO + */ + private _buffers: { [key: string]: WebGLBuffer } = {}; + + /** + * 顶点数量 + */ + private _vertCount: number = 0; + + /** + * @var + */ + public program: GLProgram; + + /** + * @var 纹理 + */ + public texture: WebGLTexture | null; + + /** + * @var ibo + */ + private _ibo: WebGLBuffer | null; + + /** + * @var 索引数量 + */ + private _indexCount: number = -1; + + /** + * 设置贴图 + * @param tex + */ + public setTexture(tex: GLTexture): void { + this.texture = tex; + } + + /** + * 设置ibo数据 + * @param data + */ + public setIBO(data: Uint16Array): void { + this._ibo = this.gl.createBuffer(); + if (this._ibo === null) { + throw new Error("create ibo falied!"); + } + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this._ibo); + this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, data, this.gl.STATIC_DRAW); + this._indexCount = data.length; + } + + public constructor(gl: WebGLRenderingContext, state: GLAttribBits, program: GLProgram, texture: GLTexture | null = null, layout: EVertexLayout = EVertexLayout.INTERLEAVED) { + super(gl, state); + + this._hasColor = GLAttribState.hasColor(this._attribState); + this._hasTexcoord = GLAttribState.hasTexCoord_0(this._attribState); + this._hasNormal = GLAttribState.hasNormal(this._attribState); + + this._ibo = null; + + this._layout = layout; + + this.program = program; + this.texture = texture; + + //先绑定vao对象 + this.bind(); + + let buffer: WebGLBuffer | null = this.gl.createBuffer(); + if (buffer === null) { + throw new Error("create webgl buffer failed!"); + } + + if (this._layout === EVertexLayout.INTERLEAVED) { + //interleaved 使用一个arraylist 和一个 顶点缓存,调用的是GLAttribState.getInterleavedLayoutAttribOffsetMap方法 + this._lists[GLMeshBuilder.INTERLEAVED] = new TypedArrayList(Float32Array); + + this._buffers[GLMeshBuilder.INTERLEAVED] = buffer; + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer); + + let map: GLAttribOffsetMap = GLAttribState.getInterleaveLayoutAttribOffsetMap(this._attribState); + GLAttribState.setAttribVertexArrayPointer(this.gl, map); + GLAttribState.setAttribVertexArrayState(this.gl, this._attribState); + } else if (this._layout === EVertexLayout.SEQUENCED) { + //sequenced 使用n个arraylist和一个顶点缓存 + this._lists[GLAttribState.POSITION_NAME] = new TypedArrayList(Float32Array); + + if (this._hasColor) { + this._lists[GLAttribState.COLOR_NAME] = new TypedArrayList(Float32Array); + } + if (this._hasTexcoord) { + this._lists[GLAttribState.TEXCOORD_NAME] = new TypedArrayList(Float32Array); + } + if (this._hasNormal) { + this._lists[GLAttribState.NORMAL_NAME] = new TypedArrayList(Float32Array); + } + + buffer = this.gl.createBuffer(); + if (buffer === null) { + throw new Error("create webgl buffer failed!"); + } + this._buffers[GLMeshBuilder.SEQUENCED] = buffer; + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer); + + //因为是动态的,seqnenced 没法预先设置指针,但是可以预先设置顶点状态 + GLAttribState.setAttribVertexArrayState(this.gl, this._attribState); + } else { + //seperated 使用n个arraylist和n个顶点缓存 + this._lists[GLAttribState.POSITION_NAME] = new TypedArrayList(Float32Array); + this._buffers[GLAttribState.POSITION_NAME] = buffer; + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer); + this.gl.vertexAttribPointer(GLAttribState.POSITION_LOCATION, 3, gl.FLOAT, false, 0, 0); + this.gl.enableVertexAttribArray(GLAttribState.POSITION_LOCATION); + + if (this._hasColor) { + this._lists[GLAttribState.COLOR_NAME] = new TypedArrayList(Float32Array); + buffer = this.gl.createBuffer(); + if (buffer === null) { + throw new Error("create webgl buffer failed!"); + } + this._buffers[GLAttribState.COLOR_NAME] = buffer; + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer); + this.gl.vertexAttribPointer(GLAttribState.COLOR_LOCATION, 4, gl.FLOAT, false, 0, 0); + this.gl.enableVertexAttribArray(GLAttribState.COLOR_LOCATION); + } + + if (this._hasTexcoord) { + this._lists[GLAttribState.TEXCOORD_NAME] = new TypedArrayList(Float32Array); + buffer = this.gl.createBuffer(); + if (buffer === null) { + throw new Error("create webgl buffer failed!"); + } + this._buffers[GLAttribState.TEXCOORD_NAME] = buffer; + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer); + this.gl.vertexAttribPointer(GLAttribState.TEXCOORD_BIT, 2, gl.FLOAT, false, 0, 0); + this.gl.enableVertexAttribArray(GLAttribState.TEXCOORD_BIT); + } + + if (this._hasNormal) { + this._lists[GLAttribState.NORMAL_NAME] = new TypedArrayList(Float32Array); + buffer = this.gl.createBuffer(); + if (buffer === null) { + throw new Error("create webgl buffer failed!"); + } + this._buffers[GLAttribState.NORMAL_NAME] = buffer; + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer); + this.gl.vertexAttribPointer(GLAttribState.NORMAL_LOCATION, 3, gl.FLOAT, false, 0, 0); + this.gl.enableVertexAttribArray(GLAttribState.NORMAL_LOCATION); + } + } + + + this.unbind(); + } + + /** + * 设置颜色 + * @param r + * @param g + * @param b + * @param a + * @returns + */ + public color(r: number, g: number, b: number, a: number = 1.0): GLMeshBuilder { + if (this._hasColor) { + this._color.r = r; + this._color.g = g; + this._color.b = b; + this._color.a = a; + } + return this; + } + + /** + * 设置texcoord + * @param u + * @param v + * @returns + * + */ + public texcoord(u: number, v: number): GLMeshBuilder { + if (this._hasTexcoord) { + this._texCoord.x = u; + this._texCoord.y = v; + } + return this; + } + + /** + * 设置法向 + * @param x + * @param y + * @param z + * @returns + */ + public normal(x: number, y: number, z: number): GLMeshBuilder { + if (this._hasNormal) { + this._normal.x = x; + this._normal.y = y; + this._normal.z = z; + } + return this; + } + + /** + * 设置vertex + * @param x + * @param y + * @param z + * @returns + */ + public vertex(x: number, y: number, z: number): GLMeshBuilder { + if (this._layout === EVertexLayout.INTERLEAVED) { + let list: TypedArrayList = this._lists[GLMeshBuilder.INTERLEAVED]; + list.push(x); + list.push(y); + list.push(z); + + if (this._hasTexcoord) { + list.push(this._texCoord.x); + list.push(this._texCoord.y) + } + + if (this._hasNormal) { + list.push(this._normal.x); + list.push(this._normal.y); + list.push(this._normal.z); + } + + if (this._hasColor) { + list.push(this._color.r); + list.push(this._color.g); + list.push(this._color.b); + list.push(this._color.a); + } + } else { //sequenced separated 都是多个ArrayList + let list: TypedArrayList = this._lists[GLAttribState.POSITION_NAME]; + list.push(x); + list.push(y); + list.push(z); + + if (this._hasTexcoord) { + list = this._lists[GLAttribState.TEXCOORD_NAME]; + list.push(this._texCoord.x); + list.push(this._texCoord.y); + } + + if (this._hasNormal) { + list = this._lists[GLAttribState.NORMAL_NAME]; + list.push(this._normal.x); + list.push(this._normal.y); + list.push(this._normal.z); + } + + if (this._hasColor) { + list = this._lists[GLAttribState.COLOR_NAME]; + list.push(this._color.r); + list.push(this._color.g); + list.push(this._color.b); + list.push(this._color.a); + } + } + + //更新顶点数量 + this._vertCount++; + return this; + } + + /** + * 每次调用上述几个添加顶点属性的方法之前,必须要先调用begin方法,返回this指针,链式操作 + * @param drawMode + */ + public begin(drawMode: number = this.gl.TRIANGLES): GLMeshBuilder { + this.drawMode = drawMode; + this._vertCount = 0; //清空顶点 + + if (this._layout === EVertexLayout.INTERLEAVED) { + let list: TypedArrayList = this._lists[GLMeshBuilder.INTERLEAVED]; + list.clear(); + } else { + let list: TypedArrayList = this._lists[GLMeshBuilder.INTERLEAVED]; + list.clear(); + + if (this._hasTexcoord) { + list = this._lists[GLAttribState.TEXCOORD_NAME]; + list.clear(); + } + + if (this._hasNormal) { + list = this._lists[GLAttribState.NORMAL_NAME]; + list.clear(); + } + + if (this._hasColor) { + list = this._lists[GLAttribState.COLOR_NAME]; + list.clear(); + } + } + return this; + } + + public end(mvp: mat4): void { + this.program.bind(); + + //载入MVPMatrix uniform变量 + this.program.setMatrix4(GLProgram.MVPMatrix, mvp); + + //载入texture + if (this.texture !== null) { + this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture); + this.program.loadSampler(); + } + + //绑定vao + this.bind(); + + if (this._layout === EVertexLayout.INTERLEAVED) { + //获取数据源 + let list: TypedArrayList = this._lists[GLMeshBuilder.INTERLEAVED]; + + //获取vbo + let buffer: WebGLBuffer = this._buffers[GLMeshBuilder.INTERLEAVED]; + + //绑定vbo 上传渲染数据到vbo + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, list.subArray(), this.gl.DYNAMIC_DRAW); + } else if (this._layout === EVertexLayout.SEQUENCED) { + let buffer: WebGLBuffer = this._buffers[GLMeshBuilder.SEQUENCED]; + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, this._attribStride * this._vertCount, this.gl.DYNAMIC_DRAW); + + let list: TypedArrayList = this._lists[GLAttribState.POSITION_NAME]; + this.gl.bufferSubData(this.gl.ARRAY_BUFFER, 0, list.subArray()); + + let map: GLAttribOffsetMap = GLAttribState.getSequenceLayoutAttribOffsetMap(this._attribState, this._vertCount); + if (this._hasTexcoord) { + list = this._lists[GLAttribState.TEXCOORD_NAME]; + this.gl.bufferSubData(this.gl.ARRAY_BUFFER, map[GLAttribState.TEXCOORD_NAME], list.subArray()); + } + if (this._hasNormal) { + list = this._lists[GLAttribState.NORMAL_NAME]; + this.gl.bufferSubData(this.gl.ARRAY_BUFFER, map[GLAttribState.NORMAL_NAME], list.subArray()); + } + if (this._hasColor) { + list = this._lists[GLAttribState.COLOR_NAME]; + this.gl.bufferSubData(this.gl.ARRAY_BUFFER, map[GLAttribState.COLOR_NAME], list.subArray()); + } + //每次都要重新计算和绑定顶点属性的首地址 + GLAttribState.setAttribVertexArrayPointer(this.gl, map); + } else { + let buffer: WebGLBuffer = this._buffers[GLAttribState.POSITION_NAME]; + let list: TypedArrayList = this._lists[GLAttribState.POSITION_NAME]; + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, list.subArray(), this.gl.DYNAMIC_DRAW); + + if (this._hasTexcoord) { + buffer = this._buffers[GLAttribState.TEXCOORD_NAME]; + list = this._lists[GLAttribState.TEXCOORD_NAME]; + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, list.subArray(), this.gl.DYNAMIC_DRAW); + } + + if (this._hasNormal) { + buffer = this._buffers[GLAttribState.NORMAL_NAME]; + list = this._lists[GLAttribState.NORMAL_NAME]; + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, list.subArray(), this.gl.DYNAMIC_DRAW); + } + + if (this._hasColor) { + buffer = this._buffers[GLAttribState.COLOR_NAME]; + list = this._lists[GLAttribState.COLOR_NAME]; + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, list.subArray(), this.gl.DYNAMIC_DRAW); + } + } + + if (this._ibo !== null) { + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this._ibo); + this.gl.drawElements(this.drawMode, this._indexCount, this.gl.UNSIGNED_SHORT, 0); + } else { + this.gl.drawArrays(this.drawMode, 0, this._vertCount); + } + + this.unbind(); + this.program.unbind(); + } } \ No newline at end of file