diff --git a/source/GLRender/Axis.ts b/source/GLRender/Axis.ts index fe291cc..728a170 100644 --- a/source/GLRender/Axis.ts +++ b/source/GLRender/Axis.ts @@ -1,7 +1,7 @@ import { BasicsShader } from "./BasicShader"; import { DisplayObject } from "./DisplayObject"; -class Axis extends DisplayObject{ +class Axis extends DisplayObject{ /** * 坐标轴数据 @@ -24,6 +24,10 @@ class Axis extends DisplayObject{ this.gl.bufferData(this.gl.ARRAY_BUFFER, Axis.AXIS_VER_DATA, this.gl.STATIC_DRAW); } + public clean(): void { + this.gl.deleteBuffer(this.axisVertexBuffer); + } + /** * 绘制半径 */ @@ -34,39 +38,39 @@ class Axis extends DisplayObject{ /** * 绘制坐标轴 */ - public draw(shader: BasicsShader){ + public draw(){ // 使用程序 - shader.use(); + this.shader.use(); // 绑定缓冲区 this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.axisVertexBuffer); // 指定指针数据 this.gl.vertexAttribPointer( - shader.attribLocate("aPosition"), + this.shader.attribLocate("aPosition"), 3, this.gl.FLOAT, false, 0, 0); // mvp参数传递 - shader.mvp(this.camera.transformMat); + this.shader.mvp(this.camera.transformMat); // 半径传递 - shader.radius([this.r, this.r, this.r]); - shader.position(this.pos); + this.shader.radius([this.r, this.r, this.r]); + this.shader.position(this.pos); - shader.fogColor(this.renderer.fogColor); - shader.fogDensity(this.renderer.fogDensity); + this.shader.fogColor(this.renderer.fogColor); + this.shader.fogDensity(this.renderer.fogDensity); // 绘制 X 轴 - shader.color([1, 0, 0]); + this.shader.color([1, 0, 0]); this.gl.drawArrays(this.gl.LINES, 0, 2); // 绘制 Y 轴 - shader.color([0, 1, 0]); + this.shader.color([0, 1, 0]); this.gl.drawArrays(this.gl.LINES, 2, 2); // 绘制 Z 轴 - shader.color([0, 0, 1]); + this.shader.color([0, 0, 1]); this.gl.drawArrays(this.gl.LINES, 4, 2); } } diff --git a/source/GLRender/BasicCube.ts b/source/GLRender/BasicCube.ts index d47318b..c5eb76d 100644 --- a/source/GLRender/BasicCube.ts +++ b/source/GLRender/BasicCube.ts @@ -1,7 +1,7 @@ import { DisplayObject } from "./DisplayObject"; import { BasicsShader } from "./BasicShader"; -class BaseCube extends DisplayObject{ +class BasicCube extends DisplayObject{ /** * 立方体数据 @@ -28,10 +28,15 @@ class BaseCube extends DisplayObject{ // 绑定缓冲区 this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.cubeVertexBuffer); - this.gl.bufferData(this.gl.ARRAY_BUFFER, BaseCube.CUBE_VER_DATA, this.gl.STATIC_DRAW); + this.gl.bufferData(this.gl.ARRAY_BUFFER, BasicCube.CUBE_VER_DATA, this.gl.STATIC_DRAW); this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.cubeElementBuffer); - this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, BaseCube.CUBE_ELE_DATA, this.gl.STATIC_DRAW); + this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, BasicCube.CUBE_ELE_DATA, this.gl.STATIC_DRAW); + } + + public clean(): void { + this.gl.deleteBuffer(this.cubeElementBuffer); + this.gl.deleteBuffer(this.cubeVertexBuffer); } private cubeVertexBuffer: WebGLBuffer | null = null; @@ -40,7 +45,7 @@ class BaseCube extends DisplayObject{ /** * 绘制半径 */ - private r:[number,number,number] = [1, 1, 1]; + public r:[number,number,number] = [1, 1, 1]; /** * 坐标 @@ -55,10 +60,10 @@ class BaseCube extends DisplayObject{ /** * 绘制立方体 */ - public draw(shader: BasicsShader){ + public draw(){ // 使用程序 - shader.use(); + this.shader.use(); // 绑定缓冲区 this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.cubeVertexBuffer); @@ -66,26 +71,32 @@ class BaseCube extends DisplayObject{ // 指定指针数据 this.gl.vertexAttribPointer( - shader.attribLocate("aPosition"), + this.shader.attribLocate("aPosition"), 3, this.gl.FLOAT, false, 0, 0); // mvp参数传递 - shader.mvp(this.camera.transformMat); + this.shader.mvp(this.camera.transformMat); // 半径传递 - shader.radius(this.r); - shader.position(this.position); + this.shader.radius(this.r); + this.shader.position(this.position); // 指定颜色 - shader.color(this.color); + this.shader.color(this.color); - shader.fogColor(this.renderer.fogColor); - shader.fogDensity(this.renderer.fogDensity); + this.shader.fogColor(this.renderer.fogColor); + this.shader.fogDensity(this.renderer.fogDensity); // 开始绘制 this.gl.drawElements(this.gl.LINES, 24, this.gl.UNSIGNED_SHORT, 0); } + + public isCube: boolean = true; + + public static isCube(object: DisplayObject): object is BasicCube { + return !!(object as BasicCube).isCube; + } } -export default BaseCube; -export { BaseCube }; \ No newline at end of file +export default BasicCube; +export { BasicCube }; \ No newline at end of file diff --git a/source/GLRender/BasicGroup.ts b/source/GLRender/BasicGroup.ts index e974196..4c79403 100644 --- a/source/GLRender/BasicGroup.ts +++ b/source/GLRender/BasicGroup.ts @@ -2,7 +2,7 @@ import { DisplayObject } from "./DisplayObject"; import { GroupShader } from "./GroupShader"; import { ObjectData } from "@Model/Renderer"; -class BasicGroup extends DisplayObject{ +class BasicGroup extends DisplayObject { private pointVertexBuffer: WebGLBuffer | null = null; private pointVecMaxCount: number = 100 * 3; @@ -18,6 +18,10 @@ class BasicGroup extends DisplayObject{ this.gl.bufferData(this.gl.ARRAY_BUFFER, this.pointVecMaxCount, this.gl.DYNAMIC_DRAW); } + public clean(): void { + this.gl.deleteBuffer(this.pointVertexBuffer); + } + /** * 向 GPU 上传数据 */ @@ -43,34 +47,40 @@ class BasicGroup extends DisplayObject{ /** * 绘制立方体 */ - public draw(shader: GroupShader){ + public draw(){ // 使用程序 - shader.use(); + this.shader.use(); // 绑定缓冲区 this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.pointVertexBuffer); // 指定指针数据 this.gl.vertexAttribPointer( - shader.attribLocate("aPosition"), + this.shader.attribLocate("aPosition"), 3, this.gl.FLOAT, false, 0, 0); // mvp参数传递 - shader.mvp(this.camera.transformMat); + this.shader.mvp(this.camera.transformMat); // 半径传递 - shader.radius(this.size); + this.shader.radius(this.size); // 指定颜色 - shader.color(this.color); + this.shader.color(this.color); - shader.fogColor(this.renderer.fogColor); - shader.fogDensity(this.renderer.fogDensity); + this.shader.fogColor(this.renderer.fogColor); + this.shader.fogDensity(this.renderer.fogDensity); // 开始绘制 this.gl.drawArrays(this.gl.POINTS, 0, this.pointVecCount); } + + public isGroup: boolean = true; + + public static isGroup(object: DisplayObject): object is BasicGroup { + return !!(object as BasicGroup).isGroup; + } } export default BasicGroup; diff --git a/source/GLRender/ClassicRenderer.ts b/source/GLRender/ClassicRenderer.ts index 4c51d80..9c247b5 100644 --- a/source/GLRender/ClassicRenderer.ts +++ b/source/GLRender/ClassicRenderer.ts @@ -2,11 +2,19 @@ import { ObjectID, ObjectData, ICommonParam } from "@Model/Renderer"; import { BasicRenderer } from "./BasicRenderer"; import { BasicsShader } from "./BasicShader"; import { Axis } from "./Axis"; -import { BaseCube } from "./BasicCube"; +import { BasicCube } from "./BasicCube"; import { GroupShader } from "./GroupShader"; import { BasicGroup } from "./BasicGroup"; +import DisplayObject from "./DisplayObject"; -interface IClassicRendererParams {} +interface IClassicRendererParams { + point: { + size: number; + } + cube: { + radius: number[]; + } +} class ClassicRenderer extends BasicRenderer<{}, IClassicRendererParams> { @@ -22,14 +30,9 @@ class ClassicRenderer extends BasicRenderer<{}, IClassicRendererParams> { private readonly farFogLine = 2.5; /** - * 点存储池数组 + * 对象储池数组 */ - private groupPool = new Map(); - - /** - * 立方体储池数组 - */ - private cubePool = new Map(); + private objectPool = new Map(); public onLoad(): void { @@ -38,19 +41,7 @@ class ClassicRenderer extends BasicRenderer<{}, IClassicRendererParams> { this.basicShader = new BasicsShader().bindRenderer(this); this.groupShader = new GroupShader().bindRenderer(this); - this.axisObject = new Axis().bindRenderer(this); - - // 测试渲染器 - if (true) { - let cubeObject = new BaseCube().bindRenderer(this); - let basicGroup = new BasicGroup().bindRenderer(this); - - // 生成随机数据测试 - basicGroup.upLoadData(new Array(1000 * 3).fill(0).map(() => (Math.random() - .5) * 2)); - - this.cubePool.set("1", cubeObject); - this.groupPool.set("1", basicGroup); - } + this.axisObject = new Axis().bindRenderer(this).bindShader(this.basicShader); this.canvas.on("mousemove", () => { @@ -99,16 +90,35 @@ class ClassicRenderer extends BasicRenderer<{}, IClassicRendererParams> { this.cleanCanvas(); - // 绘制全部立方体 - this.cubePool.forEach((cube) => { - if (cube.isDraw) cube.draw(this.basicShader); - else cube.drawEmptyFrame ++; + // 绘制全部物体 + // 对象清理实现自动内存释放 + let cleanArray: undefined | Set; + this.objectPool.forEach((object, key) => { + if (object.drawEmptyFrame > this.maxDrawEmptyFrame) { + + // 加入清除列表 + if (!cleanArray) cleanArray = new Set(); + cleanArray.add(key); + + // 释放显存 + object.clean(); + } }); - // 绘制全部点 - this.groupPool.forEach((group) => { - if (group.isDraw) group.draw(this.groupShader); - else group.drawEmptyFrame ++; + if (cleanArray) { + cleanArray.forEach((key) => { + this.objectPool.delete(key); + console.log(`Renderer: Automatically clear objects ${key}`); + }); + } + + // 遍历绘制 + this.objectPool.forEach((object) => { + if (object.isDraw) { + object.draw(); + object.drawEmptyFrame = 0; + } + else object.drawEmptyFrame ++; }); // 右上角绘制坐标轴 @@ -119,19 +129,137 @@ class ClassicRenderer extends BasicRenderer<{}, IClassicRendererParams> { position * this.camera.ratio, position ); - this.axisObject.draw(this.basicShader); + this.axisObject.draw(); } + /** + * 最大不活动帧数 + */ + private maxDrawEmptyFrame = 5000; + clean(id?: ObjectID | ObjectID[]): this { - throw new Error("Method not implemented."); + if (id) { + if (Array.isArray(id)) { + id.forEach((key) => { + let object = this.objectPool.get(key); + if (object) object.isDraw = false; + }); + } else { + let object = this.objectPool.get(id); + if (object) object.isDraw = false; + } + } else { + this.objectPool.forEach((object) => { + object.isDraw = false; + }); + } + return this; } - points(id: ObjectID, position: ObjectData, param?: ICommonParam): this { - throw new Error("Method not implemented."); + points( + id: ObjectID, position?: ObjectData, + param?: Readonly> + ): this { + let object = this.objectPool.get(id); + let group: BasicGroup; + if (object) { + if (BasicGroup.isGroup(object)) { + group = object; + + // 数据上传 + if (position) { + object.upLoadData(position); + } + } else { + throw new Error("Renderer: Use duplicate ObjectID when drawing different types of objects"); + } + } else { + group = new BasicGroup().bindRenderer(this).bindShader(this.groupShader); + + // 数据上传 + group.upLoadData(position ?? []); + + this.objectPool.set(id, group); + console.log(`Renderer: Create new group object with id ${id}`); + } + + // 开启绘制 + group.isDraw = true; + + // 参数传递 + if (param) { + + // 颜色数据 + if (param.color) { + group.color[0] = param.color[0] ?? group.color[0] + group.color[1] = param.color[1] ?? group.color[1] + group.color[2] = param.color[2] ?? group.color[2] + } + + // 半径数据 + if (param.size) { + group.size = param.size; + } + } + + return this; } - cube(id: ObjectID, position: ObjectData, param?: ICommonParam): this { - throw new Error("Method not implemented."); + cube( + id: ObjectID, position?: ObjectData, + param?: Readonly> + ): this { + let object = this.objectPool.get(id); + let cube: BasicCube; + if (object) { + if (BasicCube.isCube(object)) { + cube = object; + + // 坐标数据上传 + if (position) { + cube.position[0] = position[0] ?? cube.position[0]; + cube.position[1] = position[1] ?? cube.position[1]; + cube.position[2] = position[2] ?? cube.position[2]; + } + } else { + throw new Error("Renderer: Use duplicate ObjectID when drawing different types of objects"); + } + } else { + cube = new BasicCube().bindRenderer(this).bindShader(this.basicShader); + + // 数据上传 + if (position) { + cube.position[0] = position[0] ?? cube.position[0]; + cube.position[1] = position[1] ?? cube.position[1]; + cube.position[2] = position[2] ?? cube.position[2]; + } + + this.objectPool.set(id, cube); + console.log(`Renderer: Create new cube object with id ${id}`); + } + + // 开启绘制 + cube.isDraw = true; + + // 参数传递 + if (param) { + + // 颜色数据 + if (param.color) { + cube.color[0] = param.color[0] ?? cube.color[0] + cube.color[1] = param.color[1] ?? cube.color[1] + cube.color[2] = param.color[2] ?? cube.color[2] + } + + // 半径数据 + if (param.radius) { + cube.r[0] = param.radius[0] ?? cube.r[0]; + cube.r[1] = param.radius[1] ?? cube.r[1]; + cube.r[2] = param.radius[2] ?? cube.r[2]; + } + } + + return this; } } diff --git a/source/GLRender/DisplayObject.ts b/source/GLRender/DisplayObject.ts index e2d8cac..39f28d2 100644 --- a/source/GLRender/DisplayObject.ts +++ b/source/GLRender/DisplayObject.ts @@ -1,7 +1,9 @@ import { EventType } from "@Model/Emitter"; import { GLContextObject } from "./GLContext"; +import { GLShader } from "./GLShader"; abstract class DisplayObject< + S extends GLShader = GLShader, E extends Record = {} > extends GLContextObject { @@ -14,6 +16,30 @@ abstract class DisplayObject< * 绘制帧数 */ public drawEmptyFrame: number = 0; + + /** + * 绑定的 shader + */ + protected shader: S = undefined as any; + + /** + * 绑定着色器 + */ + public bindShader(shader: S) { + this.shader = shader; + return this; + } + + /** + * 绘制函数 + */ + abstract draw(): void; + + /** + * 销毁时调用 + * 用于释放显存 + */ + abstract clean(): void; } export { DisplayObject } diff --git a/source/Model/Renderer.ts b/source/Model/Renderer.ts index 9061674..a805681 100644 --- a/source/Model/Renderer.ts +++ b/source/Model/Renderer.ts @@ -30,11 +30,6 @@ interface ICommonParam { * 颜色 */ color?: ObjectData; - - /** - * 半径 - */ - radius?: number; } /** @@ -108,7 +103,7 @@ abstract class AbstractRenderer< * @param id 使用的标识符 * @param position 做标集合 */ - abstract points(id: ObjectID, position: ObjectData, param?: P["points"] & ICommonParam): this; + abstract points(id: ObjectID, position?: ObjectData, param?: Readonly): this; /** * @function cube 绘制立方体 @@ -118,7 +113,7 @@ abstract class AbstractRenderer< * * 注意: 这里的半径指的是立方体重心与立方体任意一面几何中心的距离 */ - abstract cube(id: ObjectID, position: ObjectData, param?: P["cube"] & ICommonParam): this; + abstract cube(id: ObjectID, position?: ObjectData, param?: Readonly): this; } export default AbstractRenderer; diff --git a/source/Page/Laboratory/Laboratory.tsx b/source/Page/Laboratory/Laboratory.tsx index 2e6a783..37f2733 100644 --- a/source/Page/Laboratory/Laboratory.tsx +++ b/source/Page/Laboratory/Laboratory.tsx @@ -28,7 +28,27 @@ class Laboratory extends Component { renderer.onLoad(); - console.log(renderer); + // 测试渲染器 + if (true) { + renderer.points("0"); + renderer.points("1", new Array(100 * 3).fill(0).map(() => (Math.random() - .5) * 2)); + renderer.points("2", new Array(100 * 3).fill(0).map(() => (Math.random() - .5) * 2), { + size: 100, + color: [1, 0, 1] + }); + renderer.points("3", new Array(100 * 3).fill(0).map(() => (Math.random() - .5) * 2), { + size: 80, + color: [0, 1, 1] + }); + renderer.points("2"); + renderer.cube("4"); + renderer.cube("5", new Array(3).fill(0).map(() => (Math.random() - .5) * 2), { + radius: new Array(3).fill(0).map(() => Math.random() * 1.2), + color: [1, 1, 0] + }) + } + + (window as any).renderer = renderer; this.canvasContRef.current.appendChild(renderer.canvas.dom); }