in src/renderers/webgl/programs/RenderProgram.ts [175:531]
constructor(renderer: WebGLRenderer, passes: ShaderPass[]) {
super(renderer, passes);
const canvas = renderer.canvas;
const gl = renderer.gl;
let u_projection: WebGLUniformLocation;
let u_viewport: WebGLUniformLocation;
let u_focal: WebGLUniformLocation;
let u_view: WebGLUniformLocation;
let u_texture: WebGLUniformLocation;
let u_transforms: WebGLUniformLocation;
let u_transformIndices: WebGLUniformLocation;
let u_colorTransforms: WebGLUniformLocation;
let u_colorTransformIndices: WebGLUniformLocation;
let u_outlineThickness: WebGLUniformLocation;
let u_outlineColor: WebGLUniformLocation;
let positionAttribute: number;
let indexAttribute: number;
let transformsTexture: WebGLTexture;
let transformIndicesTexture: WebGLTexture;
let colorTransformsTexture: WebGLTexture;
let colorTransformIndicesTexture: WebGLTexture;
let vertexBuffer: WebGLBuffer;
let indexBuffer: WebGLBuffer;
this._resize = () => {
if (!this._camera) return;
this._camera.data.setSize(canvas.width, canvas.height);
this._camera.update();
u_projection = gl.getUniformLocation(this.program, "projection") as WebGLUniformLocation;
gl.uniformMatrix4fv(u_projection, false, this._camera.data.projectionMatrix.buffer);
u_viewport = gl.getUniformLocation(this.program, "viewport") as WebGLUniformLocation;
gl.uniform2fv(u_viewport, new Float32Array([canvas.width, canvas.height]));
};
const createWorker = () => {
this._worker = new SortWorker();
this._worker.onmessage = (e) => {
if (e.data.depthIndex) {
const { depthIndex } = e.data;
this._depthIndex = depthIndex;
gl.bindBuffer(gl.ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, depthIndex, gl.STATIC_DRAW);
}
};
};
this._initialize = () => {
if (!this._scene || !this._camera) {
console.error("Cannot render without scene and camera");
return;
}
this._resize();
this._scene.addEventListener("objectAdded", handleObjectAdded);
this._scene.addEventListener("objectRemoved", handleObjectRemoved);
for (const object of this._scene.objects) {
if (object instanceof Splat) {
object.addEventListener("objectChanged", handleObjectChanged);
}
}
this._renderData = new RenderData(this._scene);
u_focal = gl.getUniformLocation(this.program, "focal") as WebGLUniformLocation;
gl.uniform2fv(u_focal, new Float32Array([this._camera.data.fx, this._camera.data.fy]));
u_view = gl.getUniformLocation(this.program, "view") as WebGLUniformLocation;
gl.uniformMatrix4fv(u_view, false, this._camera.data.viewMatrix.buffer);
u_outlineThickness = gl.getUniformLocation(this.program, "outlineThickness") as WebGLUniformLocation;
gl.uniform1f(u_outlineThickness, this.outlineThickness);
u_outlineColor = gl.getUniformLocation(this.program, "outlineColor") as WebGLUniformLocation;
gl.uniform4fv(u_outlineColor, new Float32Array(this.outlineColor.flatNorm()));
this._splatTexture = gl.createTexture() as WebGLTexture;
u_texture = gl.getUniformLocation(this.program, "u_texture") as WebGLUniformLocation;
gl.uniform1i(u_texture, 0);
transformsTexture = gl.createTexture() as WebGLTexture;
u_transforms = gl.getUniformLocation(this.program, "u_transforms") as WebGLUniformLocation;
gl.uniform1i(u_transforms, 1);
transformIndicesTexture = gl.createTexture() as WebGLTexture;
u_transformIndices = gl.getUniformLocation(this.program, "u_transformIndices") as WebGLUniformLocation;
gl.uniform1i(u_transformIndices, 2);
colorTransformsTexture = gl.createTexture() as WebGLTexture;
u_colorTransforms = gl.getUniformLocation(this.program, "u_colorTransforms") as WebGLUniformLocation;
gl.uniform1i(u_colorTransforms, 3);
colorTransformIndicesTexture = gl.createTexture() as WebGLTexture;
u_colorTransformIndices = gl.getUniformLocation(
this.program,
"u_colorTransformIndices",
) as WebGLUniformLocation;
gl.uniform1i(u_colorTransformIndices, 4);
vertexBuffer = gl.createBuffer() as WebGLBuffer;
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-2, -2, 2, -2, 2, 2, -2, 2]), gl.STATIC_DRAW);
positionAttribute = gl.getAttribLocation(this.program, "position");
gl.enableVertexAttribArray(positionAttribute);
gl.vertexAttribPointer(positionAttribute, 2, gl.FLOAT, false, 0, 0);
indexBuffer = gl.createBuffer() as WebGLBuffer;
indexAttribute = gl.getAttribLocation(this.program, "index");
gl.enableVertexAttribArray(indexAttribute);
gl.bindBuffer(gl.ARRAY_BUFFER, indexBuffer);
createWorker();
};
const handleObjectAdded = (event: Event) => {
const e = event as ObjectAddedEvent;
if (e.object instanceof Splat) {
e.object.addEventListener("objectChanged", handleObjectChanged);
}
resetSplatData();
};
const handleObjectRemoved = (event: Event) => {
const e = event as ObjectRemovedEvent;
if (e.object instanceof Splat) {
e.object.removeEventListener("objectChanged", handleObjectChanged);
}
resetSplatData();
};
const handleObjectChanged = (event: Event) => {
const e = event as ObjectChangedEvent;
if (e.object instanceof Splat && this._renderData) {
this._renderData.markDirty(e.object);
}
};
const resetSplatData = () => {
this._renderData?.dispose();
this._renderData = new RenderData(this._scene as Scene);
this._worker?.terminate();
createWorker();
};
this._render = () => {
if (!this._scene || !this._camera || !this.renderData) {
console.error("Cannot render without scene and camera");
return;
}
if (this.renderData.needsRebuild) {
this.renderData.rebuild();
}
if (
this.renderData.dataChanged ||
this.renderData.transformsChanged ||
this.renderData.colorTransformsChanged
) {
if (this.renderData.dataChanged) {
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.splatTexture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA32UI,
this.renderData.width,
this.renderData.height,
0,
gl.RGBA_INTEGER,
gl.UNSIGNED_INT,
this.renderData.data,
);
}
if (this.renderData.transformsChanged) {
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, transformsTexture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA32F,
this.renderData.transformsWidth,
this.renderData.transformsHeight,
0,
gl.RGBA,
gl.FLOAT,
this.renderData.transforms,
);
gl.activeTexture(gl.TEXTURE2);
gl.bindTexture(gl.TEXTURE_2D, transformIndicesTexture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.R32UI,
this.renderData.transformIndicesWidth,
this.renderData.transformIndicesHeight,
0,
gl.RED_INTEGER,
gl.UNSIGNED_INT,
this.renderData.transformIndices,
);
}
if (this.renderData.colorTransformsChanged) {
gl.activeTexture(gl.TEXTURE3);
gl.bindTexture(gl.TEXTURE_2D, colorTransformsTexture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA32F,
this.renderData.colorTransformsWidth,
this.renderData.colorTransformsHeight,
0,
gl.RGBA,
gl.FLOAT,
this.renderData.colorTransforms,
);
gl.activeTexture(gl.TEXTURE4);
gl.bindTexture(gl.TEXTURE_2D, colorTransformIndicesTexture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.R32UI,
this.renderData.colorTransformIndicesWidth,
this.renderData.colorTransformIndicesHeight,
0,
gl.RED_INTEGER,
gl.UNSIGNED_INT,
this.renderData.colorTransformIndices,
);
}
const detachedPositions = new Float32Array(this.renderData.positions.slice().buffer);
const detachedTransforms = new Float32Array(this.renderData.transforms.slice().buffer);
const detachedTransformIndices = new Uint32Array(this.renderData.transformIndices.slice().buffer);
this._worker?.postMessage(
{
sortData: {
positions: detachedPositions,
transforms: detachedTransforms,
transformIndices: detachedTransformIndices,
vertexCount: this.renderData.vertexCount,
},
},
[detachedPositions.buffer, detachedTransforms.buffer, detachedTransformIndices.buffer],
);
this.renderData.dataChanged = false;
this.renderData.transformsChanged = false;
this.renderData.colorTransformsChanged = false;
}
this._camera.update();
this._worker?.postMessage({ viewProj: this._camera.data.viewProj.buffer });
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.disable(gl.DEPTH_TEST);
gl.enable(gl.BLEND);
gl.blendFuncSeparate(gl.ONE_MINUS_DST_ALPHA, gl.ONE, gl.ONE_MINUS_DST_ALPHA, gl.ONE);
gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
gl.uniformMatrix4fv(u_projection, false, this._camera.data.projectionMatrix.buffer);
gl.uniformMatrix4fv(u_view, false, this._camera.data.viewMatrix.buffer);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.vertexAttribPointer(positionAttribute, 2, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, this.depthIndex, gl.STATIC_DRAW);
gl.vertexAttribIPointer(indexAttribute, 1, gl.INT, 0, 0);
gl.vertexAttribDivisor(indexAttribute, 1);
gl.drawArraysInstanced(gl.TRIANGLE_FAN, 0, 4, this.depthIndex.length);
};
this._dispose = () => {
if (!this._scene || !this._camera || !this.renderData) {
console.error("Cannot dispose without scene and camera");
return;
}
this._scene.removeEventListener("objectAdded", handleObjectAdded);
this._scene.removeEventListener("objectRemoved", handleObjectRemoved);
for (const object of this._scene.objects) {
if (object instanceof Splat) {
object.removeEventListener("objectChanged", handleObjectChanged);
}
}
this._worker?.terminate();
this.renderData.dispose();
gl.deleteTexture(this.splatTexture);
gl.deleteTexture(transformsTexture);
gl.deleteTexture(transformIndicesTexture);
gl.deleteBuffer(indexBuffer);
gl.deleteBuffer(vertexBuffer);
};
this._setOutlineThickness = (value: number) => {
this._outlineThickness = value;
if (this._initialized) {
gl.uniform1f(u_outlineThickness, value);
}
};
this._setOutlineColor = (value: Color32) => {
this._outlineColor = value;
if (this._initialized) {
gl.uniform4fv(u_outlineColor, new Float32Array(value.flatNorm()));
}
};
}