3dmol
Version:
JavaScript/TypeScript molecular visualization library
1,652 lines (1,382 loc) • 71.9 kB
text/typescript
/**
* Simplified webGL renderer
*/
import { Camera } from "./Camera";
import { DoubleSide, BackSide } from "./constants/Sides";
import {
UnsignedByteType,
RGBAFormat,
NearestFilter,
} from "./constants/TextureConstants";
import { Light } from "./core";
import { Color } from "../colors";
import {
MeshOutlineMaterial,
SphereImposterOutlineMaterial,
StickImposterOutlineMaterial,
} from "./materials";
import { Matrix4, Vector3, Matrix3 } from "./math";
import { Mesh, Line, Sprite } from "./objects";
import { ShaderLib, ShaderUtils } from "./shaders";
import { SpritePlugin } from "./SpritePlugin";
// share a single offscreen renderer across all Renderers
var _offscreen_singleton = null;
var _gl_singleton = null;
export class Renderer {
row: any;
col: any;
rows: any;
cols: any;
context = null;
devicePixelRatio = 1.0; //set in setSize
domElement: HTMLCanvasElement;
// scene graph
sortObjects = true;
autoUpdateObjects = true;
autoUpdateScene = true;
// info
info = {
memory: {
programs: 0,
geometries: 0,
textures: 0,
},
render: {
calls: 0,
vertices: 0,
faces: 0,
points: 0,
},
};
// webgl rednering context
private _gl: WebGLRenderingContext | WebGL2RenderingContext;
private _offscreen: OffscreenCanvas = null;
private _bitmap: ImageBitmapRenderingContext = null;
// internal properties
private _programs = [];
private _programs_counter = 0;
private _webglversion = 1;
// internal state cache
private _currentProgram = null;
private _currentMaterialId = -1;
private _currentGeometryGroupHash = null;
private _currentCamera = null;
private _geometryGroupCounter = 0;
// GL state cache
private _oldDoubleSided = -1 as number | boolean;
private _oldFlipSided = -1 as number | boolean;
private _oldDepthTest = -1;
private _oldDepthWrite = -1;
private _oldPolygonOffset = null;
private _oldLineWidth = null;
private _viewportWidth = 0;
private _viewportHeight = 0;
private _currentWidth = 0;
private _currentHeight = 0;
private _enabledAttributes = {};
// camera matrices cache
private _vector3 = new Vector3();
private _worldInverse = new Matrix4();
private _projInverse = new Matrix4();
private _textureMatrix = new Matrix4();
private _fullProjModelMatrix = new Matrix4();
private _fullProjModelMatrixInv = new Matrix4();
// light arrays cach
private _direction = new Vector3();
private _lightsNeedUpdate = true;
private _lights = {
ambient: [0, 0, 0],
directional: {
length: 0,
colors: [],
positions: [],
},
point: {
length: 0,
colors: [],
positions: [],
distances: [],
},
spot: {
length: 0,
colors: [],
positions: [],
distances: [],
directions: [],
anglesCos: [],
exponents: [],
},
hemi: {
length: 0,
skyColors: [],
groundColors: [],
positions: [],
},
};
sprites = new SpritePlugin();
//screensshader related variables
private _screenshader = null;
private _AOshader = null;
private _blurshader = null;
private _vertexattribpos = null;
private _aovertexattribpos = null;
private _blurvertexattribpos = null;
private _screenQuadVBO = null;
//framebuffer variables
private _fb = null;
private _targetTexture = null;
private _depthTexture = null;
private _shadingTexture = null;
private _scratchTexture = null;
private _canvas: any;
private _precision: any;
private _alpha: any;
private _premultipliedAlpha: any;
private _antialias: any;
private _upscale: boolean | null = null;
private _preserveDrawingBuffer: any;
private _clearColor: Color;
private _clearAlpha: any;
private _outlineMaterial: MeshOutlineMaterial;
private _outlineSphereImposterMaterial: SphereImposterOutlineMaterial;
private _outlineStickImposterMaterial: StickImposterOutlineMaterial;
private _outlineEnabled: boolean;
private _AOEnabled: boolean;
private _AOstrength: number = 1.0;
private _AOradius: number = 5.0;
private _extInstanced: any;
private _extFragDepth: ReturnType<WebGL2RenderingContext["getExtension"]>;
private _extFloatLinear: ReturnType<WebGL2RenderingContext["getExtension"]>;
private _extColorBufferFloat: ReturnType<WebGL2RenderingContext["getExtension"]>;
private SHADE_TEXTURE: number = 3;
constructor(parameters) {
parameters = parameters || {};
this.row = parameters.row;
this.col = parameters.col;
this.rows = parameters.rows;
this.cols = parameters.cols;
this._canvas =
parameters.canvas !== undefined
? parameters.canvas
: document.createElement("canvas");
this._precision =
parameters.precision !== undefined ? parameters.precision : "highp";
this._alpha = parameters.alpha !== undefined ? parameters.alpha : true;
this._premultipliedAlpha =
parameters.premultipliedAlpha !== undefined
? parameters.premultipliedAlpha
: true;
this._antialias = parameters.antialias !== undefined ? parameters.antialias : false;
this._upscale = parameters.upscale !== undefined ? parameters.upscale : this._antialias;
this._preserveDrawingBuffer =
parameters.preserveDrawingBuffer !== undefined
? parameters.preserveDrawingBuffer
: false;
this._clearColor =
parameters.clearColor !== undefined
? new Color(parameters.clearColor)
: new Color(0x000000);
this._clearAlpha =
parameters.clearAlpha !== undefined ? parameters.clearAlpha : 0;
this._outlineMaterial = new MeshOutlineMaterial(parameters.outline);
this._outlineSphereImposterMaterial = new SphereImposterOutlineMaterial(
parameters.outline
);
this._outlineStickImposterMaterial = new StickImposterOutlineMaterial(
parameters.outline
);
this._outlineEnabled = !!parameters.outline;
this._AOEnabled = !!parameters.ambientOcclusion;
if (parameters.ambientOcclusion && typeof (parameters.ambientOcclusion.strength) !== 'undefined') {
this._AOstrength = parseFloat(parameters.ambientOcclusion.strength);
}
if (this._AOstrength == 0) {
this._AOEnabled = false;
}
if (parameters.ambientOcclusion && typeof (parameters.ambientOcclusion.radius) !== 'undefined') {
this._AOradius = parseFloat(parameters.ambientOcclusion.radius);
}
this.domElement = this._canvas;
this._canvas.id = parameters.id;
if (parameters.containerWidth == 0 || parameters.containerHeight == 0) {
return; //start lost
}
this.initGL();
this.setDefaultGLState();
this.context = this._gl;
if (this.isWebGL1()) {
this._extInstanced = this._gl.getExtension("ANGLE_instanced_arrays");
} else { //no longer an extension, wrap
this._extInstanced = {
vertexAttribDivisorANGLE: (this._gl as WebGL2RenderingContext).vertexAttribDivisor.bind(this._gl),
drawElementsInstancedANGLE: (this._gl as WebGL2RenderingContext).drawElementsInstanced.bind(this._gl),
};
}
this._extFragDepth = this._gl.getExtension("EXT_frag_depth");
this._extFloatLinear = this._gl.getExtension("OES_texture_float_linear");
this._extColorBufferFloat = this._gl.getExtension("EXT_color_buffer_float");
this.sprites.init(this);
}
// API
supportedExtensions() {
return {
supportsAIA: Boolean(this._extInstanced),
supportsImposters: Boolean(this._extFragDepth) || !this.isWebGL1(),
regen: false
};
}
getContext() {
return this._gl;
}
getCanvas() {
return this._canvas;
}
isLost() {
return this._gl == null || this._gl.isContextLost();
}
getPrecision() {
return this._precision;
}
setClearColorHex(hex, alpha) {
this._clearColor.setHex(hex);
this._clearAlpha = alpha;
if (!this.isLost()) {
this._gl.clearColor(this._clearColor.r, this._clearColor.g, this._clearColor.b, this._clearAlpha);
}
}
enableOutline(parameters) {
this._outlineMaterial = new MeshOutlineMaterial(parameters);
this._outlineSphereImposterMaterial = new SphereImposterOutlineMaterial(
parameters
);
this._outlineStickImposterMaterial = new StickImposterOutlineMaterial(
parameters
);
this._outlineEnabled = true;
}
disableOutline() {
this._outlineEnabled = false;
}
enableAmbientOcclusion(parameters) {
if (parameters) {
if (parameters.strength) this._AOstrength = parameters.strength;
if (parameters.scale) this._AOradius = parameters.scale;
}
this._AOEnabled = this._AOstrength > 0;
}
disableAmbientOcclusion() {
this._AOEnabled = false;
}
setViewport() {
if (this._offscreen) {
//set viewport is called before every render, so setup offscreen size here
this._offscreen.width = this._canvas.width;
this._offscreen.height = this._canvas.height;
}
if (
this.rows != undefined &&
this.cols != undefined &&
this.row != undefined &&
this.col != undefined
) {
//note that drawingBuffer may be smaller than the requested width
//for large canvases
var wid = this._gl.drawingBufferWidth / this.cols;
var hei = this._gl.drawingBufferHeight / this.rows;
this._viewportWidth = wid;
this._viewportHeight = hei;
if (!this.isLost()) {
this._gl.enable(this._gl.SCISSOR_TEST);
this._gl.scissor(wid * this.col, hei * this.row, wid, hei);
this._gl.viewport(wid * this.col, hei * this.row, wid, hei);
}
}
}
setSize(width, height) {
//zooming (in the browser) changes the pixel ratio and width/height
this.devicePixelRatio =
window.devicePixelRatio !== undefined ? window.devicePixelRatio : 1;
//with antialiasing on, render at double rsolution to eliminate jaggies
//don't do it with high resolution displays
if (this._upscale && this.devicePixelRatio < 2.0) this.devicePixelRatio = 2.0;
this._canvas.width = width * this.devicePixelRatio;
this._canvas.height = height * this.devicePixelRatio;
this._canvas.style.width = width + "px";
this._canvas.style.height = height + "px";
if (
this.rows != undefined &&
this.cols != undefined &&
this.row != undefined &&
this.col != undefined
) {
var wid = width / this.cols;
var hei = height / this.rows;
this._viewportWidth = wid * this.devicePixelRatio;
this._viewportHeight = hei * this.devicePixelRatio;
this._viewportWidth = this._gl.drawingBufferWidth /this.cols;
this._viewportHeight = this._gl.drawingBufferHeight/this.rows;
this.setViewport();
} else {
this._viewportWidth = this._canvas.width;
this._viewportHeight = this._canvas.height;
if (!this.isLost()) {
this._gl.viewport(0, 0, this._gl.drawingBufferWidth, this._gl.drawingBufferHeight);
}
}
this.initFrameBuffer();
}
clear(color, depth, stencil) {
var bits = 0;
if (color === undefined || color) bits |= this._gl.COLOR_BUFFER_BIT;
if (depth === undefined || depth) bits |= this._gl.DEPTH_BUFFER_BIT;
if (stencil === undefined || stencil) bits |= this._gl.STENCIL_BUFFER_BIT;
this._gl.clear(bits);
}
setMaterialFaces(material, reflected) {
var doubleSided = material.side === DoubleSide;
var flipSided = material.side === BackSide;
if (!material.imposter)
// Ignore reflection with imposters
flipSided = reflected ? !flipSided : flipSided;
if (this._oldDoubleSided !== doubleSided) {
if (doubleSided) {
this._gl.disable(this._gl.CULL_FACE);
} else {
this._gl.enable(this._gl.CULL_FACE);
}
this._oldDoubleSided = doubleSided;
}
if (this._oldFlipSided !== flipSided) {
if (flipSided) {
this._gl.frontFace(this._gl.CW);
} else {
this._gl.frontFace(this._gl.CCW);
}
this._oldFlipSided = flipSided;
}
this._gl.cullFace(this._gl.BACK);
}
setDepthTest(depthTest) {
if (this._oldDepthTest !== depthTest) {
if (depthTest) {
this._gl.enable(this._gl.DEPTH_TEST);
} else {
this._gl.disable(this._gl.DEPTH_TEST);
}
this._oldDepthTest = depthTest;
}
}
setDepthWrite(depthWrite) {
if (this._oldDepthWrite !== depthWrite) {
this._gl.depthMask(depthWrite);
this._oldDepthWrite = depthWrite;
}
}
setBlending(blending) {
if (!blending) {
this._gl.disable(this._gl.BLEND);
} else {
this._gl.enable(this._gl.BLEND);
this._gl.blendEquationSeparate(this._gl.FUNC_ADD, this._gl.FUNC_ADD);
this._gl.blendFuncSeparate(
this._gl.SRC_ALPHA,
this._gl.ONE_MINUS_SRC_ALPHA,
this._gl.ONE,
this._gl.ONE_MINUS_SRC_ALPHA
);
}
}
// TODO: need to set up shader attributes and uniforms as attributes on
// material object after attaching prgm
// We need to attach appropriate uniform variables to material after shaders
// have been chosen
initMaterial(material, lights, fog, objects) {
material.addEventListener("dispose", this.onMaterialDispose.bind(this));
var parameters, shaderID;
shaderID = material.shaderID;
if (shaderID) {
var shader = ShaderLib[shaderID];
material.vertexShader = shader.vertexShader;
material.fragmentShader = shader.fragmentShader;
material.uniforms = ShaderUtils.clone(shader.uniforms);
// TODO: set material uniforms to shader uniform variables
if (material.shaded) {
material.makeShaded(this.SHADE_TEXTURE);
}
}
parameters = {
wireframe: material.wireframe,
fragdepth: material.imposter,
volumetric: material.volumetric,
shaded: material.shaded
};
material.program = this.buildProgram(
material.fragmentShader,
material.vertexShader,
material.uniforms,
parameters
);
}
renderBuffer(camera, lights, fog, material, geometryGroup, object) {
if (!material.visible) return;
var program, attributes;
// Sets up proper vertex and fragment shaders and attaches them to webGL
// program
// Also sets appropriate uniform variables
program = this.setProgram(camera, lights, fog, material, object, this);
if (!program) return;
attributes = program.attributes;
var updateBuffers = false,
wireframeBit = material.wireframe ? 1 : 0,
geometryGroupHash =
geometryGroup.id * 0xffffff + program.id * 2 + wireframeBit;
if (geometryGroupHash !== this._currentGeometryGroupHash) {
this._currentGeometryGroupHash = geometryGroupHash;
updateBuffers = true;
}
// rebind shader attributes to appropriate (and already initialized) gl
// buffers
if (updateBuffers) {
this.disableAttributes();
// Vertices
if (attributes.position >= 0) {
this._gl.bindBuffer(this._gl.ARRAY_BUFFER, geometryGroup.__webglVertexBuffer);
this.enableAttribute(attributes.position);
this._gl.vertexAttribPointer(attributes.position, 3, this._gl.FLOAT, false, 0, 0);
}
// Colors
if (attributes.color >= 0) {
this._gl.bindBuffer(this._gl.ARRAY_BUFFER, geometryGroup.__webglColorBuffer);
this.enableAttribute(attributes.color);
this._gl.vertexAttribPointer(attributes.color, 3, this._gl.FLOAT, false, 0, 0);
}
// Normals
if (attributes.normal >= 0) {
this._gl.bindBuffer(this._gl.ARRAY_BUFFER, geometryGroup.__webglNormalBuffer);
this.enableAttribute(attributes.normal);
this._gl.vertexAttribPointer(attributes.normal, 3, this._gl.FLOAT, false, 0, 0);
}
// Offsets (Instanced only)
if (attributes.offset >= 0) {
this._gl.bindBuffer(this._gl.ARRAY_BUFFER, geometryGroup.__webglOffsetBuffer);
this.enableAttribute(attributes.offset);
this._gl.vertexAttribPointer(attributes.offset, 3, this._gl.FLOAT, false, 0, 0);
}
// Radii (Instanced only)
if (attributes.radius >= 0) {
this._gl.bindBuffer(this._gl.ARRAY_BUFFER, geometryGroup.__webglRadiusBuffer);
this.enableAttribute(attributes.radius);
this._gl.vertexAttribPointer(attributes.radius, 1, this._gl.FLOAT, false, 0, 0);
}
}
// Render
var faceCount, lineCount;
// lambert shaders - draw triangles
// TODO: make sure geometryGroup's face count is setup correctly
if (object instanceof Mesh) {
if (material.shaderID === "instanced") {
var sphereGeometryGroup = material.sphere.geometryGroups[0];
if (updateBuffers) {
this._gl.bindBuffer(this._gl.ARRAY_BUFFER, geometryGroup.__webglVertexBuffer);
this._gl.bufferData(
this._gl.ARRAY_BUFFER,
sphereGeometryGroup.vertexArray,
this._gl.STATIC_DRAW
);
this._gl.bindBuffer(this._gl.ARRAY_BUFFER, geometryGroup.__webglNormalBuffer);
this._gl.bufferData(
this._gl.ARRAY_BUFFER,
sphereGeometryGroup.normalArray,
this._gl.STATIC_DRAW
);
this._gl.bindBuffer(
this._gl.ELEMENT_ARRAY_BUFFER,
geometryGroup.__webglFaceBuffer
);
this._gl.bufferData(
this._gl.ELEMENT_ARRAY_BUFFER,
sphereGeometryGroup.faceArray,
this._gl.STATIC_DRAW
);
}
faceCount = sphereGeometryGroup.faceidx;
this._extInstanced.vertexAttribDivisorANGLE(attributes.offset, 1);
this._extInstanced.vertexAttribDivisorANGLE(attributes.radius, 1);
this._extInstanced.vertexAttribDivisorANGLE(attributes.color, 1);
this._extInstanced.drawElementsInstancedANGLE(
this._gl.TRIANGLES,
faceCount,
this._gl.UNSIGNED_SHORT,
0,
geometryGroup.radiusArray.length
);
this._extInstanced.vertexAttribDivisorANGLE(attributes.offset, 0);
this._extInstanced.vertexAttribDivisorANGLE(attributes.radius, 0);
this._extInstanced.vertexAttribDivisorANGLE(attributes.color, 0);
} else if (material.wireframe) {
lineCount = geometryGroup.lineidx;
this.setLineWidth(material.wireframeLinewidth);
if (updateBuffers)
this._gl.bindBuffer(
this._gl.ELEMENT_ARRAY_BUFFER,
geometryGroup.__webglLineBuffer
);
this._gl.drawElements(this._gl.LINES, lineCount, this._gl.UNSIGNED_SHORT, 0);
} else {
faceCount = geometryGroup.faceidx;
if (updateBuffers)
this._gl.bindBuffer(
this._gl.ELEMENT_ARRAY_BUFFER,
geometryGroup.__webglFaceBuffer
);
this._gl.drawElements(this._gl.TRIANGLES, faceCount, this._gl.UNSIGNED_SHORT, 0);
}
this.info.render.calls++;
this.info.render.vertices += faceCount;
this.info.render.faces += faceCount / 3;
}
// basic shaders - draw lines
else if (object instanceof Line) {
lineCount = geometryGroup.vertices;
this.setLineWidth(material.linewidth);
this._gl.drawArrays(this._gl.LINES, 0, lineCount);
this.info.render.calls++;
}
}
/* clear out the shading textures */
clearShading() {
this._gl.framebufferTexture2D(
this._gl.FRAMEBUFFER,
this._gl.DEPTH_ATTACHMENT,
this._gl.TEXTURE_2D,
this._shadingTexture,
0
);
this.clear(false, true, false);
this._gl.framebufferTexture2D(
this._gl.FRAMEBUFFER,
this._gl.DEPTH_ATTACHMENT,
this._gl.TEXTURE_2D,
this._depthTexture,
0
);
}
/* Setup the shading buffer to reflect desired shading (ambient occlusion) values.
Only the matching object with materialType are considered. */
setShading(scene, camera, materialType) {
//identify all matching objects
let lights = scene.__lights;
let fog = scene.fog;
let renderList = [];
for (let i = 0, il = scene.__webglObjects.length; i < il; i++) {
let webglObject = scene.__webglObjects[i];
if (webglObject.render && webglObject[materialType]) {
renderList.push(webglObject);
}
}
if (renderList.length == 0) return;
//setup shading texture as depth buffer
this._gl.framebufferTexture2D(
this._gl.FRAMEBUFFER,
this._gl.DEPTH_ATTACHMENT,
this._gl.TEXTURE_2D,
this._shadingTexture,
0
);
this._gl.framebufferTexture2D(
this._gl.FRAMEBUFFER,
this._gl.COLOR_ATTACHMENT0,
this._gl.TEXTURE_2D,
null, //don't write colors (can we do this?)
0
);
//calculate depth map
this.renderObjects(scene.__webglObjects, true, materialType + "Depth",
camera, lights, fog, false);
//detach so we can read and attach scratch
this._gl.framebufferTexture2D(
this._gl.FRAMEBUFFER,
this._gl.DEPTH_ATTACHMENT,
this._gl.TEXTURE_2D,
this._scratchTexture,
0
);
this.clear(false, true, false);
//perform AO calculation from depth map to scratch buffer
// set screen shader and use it
this._gl.useProgram(this._AOshader);
this._currentProgram = this._AOshader;
// disable depth test
this.setDepthTest(-1);
this.setDepthWrite(-1);
let p_uniforms = this._AOshader.uniforms;
this._gl.uniform1f(p_uniforms.total_strength, this._AOstrength);
this._gl.uniform1f(p_uniforms.radius, this._AOradius);
//setup full projection matrix from model to screen and inverted
//use first object
this._fullProjModelMatrix = new Matrix4();
this._fullProjModelMatrixInv = new Matrix4();
let object = renderList[0].object;
this._fullProjModelMatrix.multiplyMatrices(camera.projectionMatrix, object._modelViewMatrix);
this._fullProjModelMatrixInv.getInverse(this._fullProjModelMatrix);
this._gl.uniformMatrix4fv(
p_uniforms.projectionMatrix,
false,
this._fullProjModelMatrix.elements
);
this._gl.uniformMatrix4fv(p_uniforms.projinv, false, this._fullProjModelMatrixInv.elements);
// bind vertexarray buffer and texture
this._gl.bindBuffer(this._gl.ARRAY_BUFFER, this._screenQuadVBO);
this._gl.enableVertexAttribArray(this._aovertexattribpos);
this._gl.vertexAttribPointer(this._aovertexattribpos, 2, this._gl.FLOAT, false, 0, 0);
this._gl.activeTexture(this._gl.TEXTURE0);
this._gl.bindTexture(this._gl.TEXTURE_2D, this._shadingTexture);
// Draw 6 vertexes => 2 triangles:
this._gl.drawArrays(this._gl.TRIANGLES, 0, 6);
//perform blur from scratch to shading
this._gl.framebufferTexture2D(
this._gl.FRAMEBUFFER,
this._gl.DEPTH_ATTACHMENT,
this._gl.TEXTURE_2D,
this._shadingTexture,
0
);
this.clear(false, true, false);
this._gl.useProgram(this._blurshader);
this._currentProgram = this._blurshader;
this.setDepthTest(-1);
this.setDepthWrite(-1);
this._gl.bindBuffer(this._gl.ARRAY_BUFFER, this._screenQuadVBO);
this._gl.enableVertexAttribArray(this._blurvertexattribpos);
this._gl.vertexAttribPointer(this._blurvertexattribpos, 2, this._gl.FLOAT, false, 0, 0);
this._gl.activeTexture(this._gl.TEXTURE0);
this._gl.bindTexture(this._gl.TEXTURE_2D, this._scratchTexture);
// Draw 6 vertexes => 2 triangles:
this._gl.drawArrays(this._gl.TRIANGLES, 0, 6);
//restore original depth, color
this._gl.framebufferTexture2D(
this._gl.FRAMEBUFFER,
this._gl.COLOR_ATTACHMENT0,
this._gl.TEXTURE_2D,
this._targetTexture,
0
);
this._gl.framebufferTexture2D(
this._gl.FRAMEBUFFER,
this._gl.DEPTH_ATTACHMENT,
this._gl.TEXTURE_2D,
this._depthTexture,
0
);
}
render(scene, camera) {
if (camera instanceof Camera === false) {
console.error("Renderer.render: camera is not an instance of Camera.");
return;
}
var i,
il,
webglObject,
object,
renderList,
lights = scene.__lights,
fog = scene.fog;
// reset caching for this frame
this._currentMaterialId = -1;
this._lightsNeedUpdate = true;
// update scene graph
if (this.autoUpdateScene) scene.updateMatrixWorld();
// update camera matrices
// Pretty sure camera's parent is always going to be undefined for our
// purposes...
if (camera.parent === undefined) camera.updateMatrixWorld();
camera.matrixWorldInverse.getInverse(camera.matrixWorld);
if (this.isLost()) {
return;
}
// update WebGL objects
if (this.autoUpdateObjects) this.initWebGLObjects(scene);
this.info.render.calls = 0;
this.info.render.vertices = 0;
this.info.render.faces = 0;
this.info.render.points = 0;
this._currentWidth = this._viewportWidth;
this._currentHeight = this._viewportHeight;
this.setViewport();
this.setFrameBuffer();
this._gl.clearColor(this._clearColor.r, this._clearColor.g, this._clearColor.b, this._clearAlpha);
this.clear(true, true, true);
// set matrices for regular objects (frustum culled)
renderList = scene.__webglObjects;
let hasvolumetric = false;
let hasAO = this._AOEnabled;
for (i = 0, il = renderList.length; i < il; i++) {
webglObject = renderList[i];
object = webglObject.object;
webglObject.render = false;
if (object.visible) {
this.setupMatrices(object, camera);
this.unrollBufferMaterial(webglObject);
webglObject.render = true;
if (webglObject.volumetric) hasvolumetric = true;
if (webglObject.hasAO) hasAO = true;
}
}
// set matrices for immediate objects
// opaque pass (front-to-back order)
this.setBlending(false);
if (hasAO) {
this.setShading(scene, camera, "opaque");
}
this.renderObjects(scene.__webglObjects, true, "opaque", camera, lights, fog, false);
if (hasAO) {
this.clearShading();
}
// Render embedded labels (sprites)
this.renderSprites(scene, camera, false);
// prime depth buffer with transparent objects
this.renderObjects(scene.__webglObjects, true, "transparentDepth", camera, lights, fog, true);
// transparent pass (back-to-front order)
this.renderObjects(scene.__webglObjects, false, "transparent", camera, lights, fog, true);
//volumetric is separate
if (hasvolumetric && this._fb) {
//disconnect depth texture from framebuffer so we can read it
this._gl.framebufferTexture2D(
this._gl.FRAMEBUFFER,
this._gl.DEPTH_ATTACHMENT,
this._gl.TEXTURE_2D,
null,
0
);
this.renderObjects(scene.__webglObjects, false, "volumetric",
camera, lights, fog, true);
}
this.renderFrameBuffertoScreen();
this.setDepthTest(true);
this.setDepthWrite(true);
// Render floating labels (sprites)
this.renderSprites(scene, camera, true);
//if using offscreen render, copy final image
if (this._bitmap) {
const bitmap = this._offscreen.transferToImageBitmap();
this._bitmap.transferFromImageBitmap(bitmap);
bitmap.close();
}
}
//setup framebuffer for drawing into, assumes buffers already allocated
setFrameBuffer() {
if (this.isWebGL1() || !this._fb) return;
let width = this._viewportWidth;
let height = this._viewportHeight;
//when using framebuffer, always draw from origin, will shift the viewport when we render
this._gl.enable(this._gl.SCISSOR_TEST);
this._gl.scissor(0, 0, width, height);
this._gl.viewport(0, 0, width, height);
//color texture
this._gl.bindTexture(this._gl.TEXTURE_2D, this._targetTexture);
this._gl.texImage2D(
this._gl.TEXTURE_2D,
0,
this._gl.RGBA,
width,
height,
0,
this._gl.RGBA,
this._gl.UNSIGNED_BYTE,
null
);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
//depth texture
this._gl.bindTexture(this._gl.TEXTURE_2D, this._depthTexture);
this._gl.texImage2D(
this._gl.TEXTURE_2D,
0,
(this._gl as WebGL2RenderingContext).DEPTH_COMPONENT32F,
width,
height,
0,
this._gl.DEPTH_COMPONENT,
this._gl.FLOAT,
null
);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.NEAREST);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.NEAREST);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
//shading texture - for AO and maybe eventually shadows? I don't like shadows
if (this._shadingTexture) {
//for whatever reason, chrome seems to require this manual memory management
//for these textures, at least in the auto tests webpage where many viewers are being created/destroyed
this._gl.deleteTexture(this._shadingTexture);
this._shadingTexture = this._gl.createTexture();
this._gl.bindTexture(this._gl.TEXTURE_2D, this._shadingTexture);
this._gl.texImage2D(
this._gl.TEXTURE_2D,
0,
(this._gl as WebGL2RenderingContext).DEPTH_COMPONENT32F,
width,
height,
0,
this._gl.DEPTH_COMPONENT,
this._gl.FLOAT,
null
);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.NEAREST);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.NEAREST);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
//scratch texture, needed by AO to do blur
this._gl.deleteTexture(this._scratchTexture);
this._scratchTexture = this._gl.createTexture();
this._gl.bindTexture(this._gl.TEXTURE_2D, this._scratchTexture);
this._gl.texImage2D(
this._gl.TEXTURE_2D,
0,
(this._gl as WebGL2RenderingContext).DEPTH_COMPONENT32F,
width,
height,
0,
this._gl.DEPTH_COMPONENT,
this._gl.FLOAT,
null
);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.NEAREST);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.NEAREST);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
}
//bind fb
this._gl.bindFramebuffer(this._gl.FRAMEBUFFER, this._fb);
this._gl.framebufferTexture2D(
this._gl.FRAMEBUFFER,
this._gl.COLOR_ATTACHMENT0,
this._gl.TEXTURE_2D,
this._targetTexture,
0
);
this._gl.framebufferTexture2D(
this._gl.FRAMEBUFFER,
this._gl.DEPTH_ATTACHMENT,
this._gl.TEXTURE_2D,
this._shadingTexture,
0
);
this._gl.clearDepth(1);
this._gl.clear(this._gl.DEPTH_BUFFER_BIT); //shading all ones
this._gl.framebufferTexture2D(
this._gl.FRAMEBUFFER,
this._gl.DEPTH_ATTACHMENT,
this._gl.TEXTURE_2D,
this._depthTexture,
0
);
}
//allocate buffers for framebuffer, needs to be called with every resize
initFrameBuffer() {
// only needed/works with webgl2
if (this.isWebGL1()) return;
let width = this._viewportWidth;
let height = this._viewportHeight;
//when using framebuffer, always draw from origin, will shift the viewport when we render
this._gl.enable(this._gl.SCISSOR_TEST);
this._gl.scissor(0, 0, width, height);
this._gl.viewport(0, 0, width, height);
//create textures and frame buffer, will be initialized in setFrameBuffer
this._targetTexture = this._gl.createTexture();
this._depthTexture = this._gl.createTexture();
this._shadingTexture = this._gl.createTexture();
this._scratchTexture = this._gl.createTexture();
this._fb = this._gl.createFramebuffer();
// build screenshader
var screenshader = this._antialias ? ShaderLib.screenaa : ShaderLib.screen;
this._screenshader = this.buildProgram(
screenshader.fragmentShader,
screenshader.vertexShader,
screenshader.uniforms,
{}
);
this._vertexattribpos = this._gl.getAttribLocation(this._screenshader, "vertexPosition");
// create the vertex array and attrib array for the full screenquad
var verts = [
// First triangle:
1.0, 1.0, -1.0, 1.0, -1.0, -1.0,
// Second triangle:
-1.0, -1.0, 1.0, -1.0, 1.0, 1.0,
];
this._screenQuadVBO = this._gl.createBuffer();
this._gl.bindBuffer(this._gl.ARRAY_BUFFER, this._screenQuadVBO);
this._gl.bufferData(this._gl.ARRAY_BUFFER, new Float32Array(verts), this._gl.STATIC_DRAW);
// build aoshader
let aoshader = ShaderLib.ssao;
this._AOshader = this.buildProgram(
aoshader.fragmentShader,
aoshader.vertexShader,
aoshader.uniforms,
{}
);
this._aovertexattribpos = this._gl.getAttribLocation(this._AOshader, "vertexPosition");
// create the vertex array and attrib array for the full screenquad
//blur shader
let bshader = ShaderLib.blur;
this._blurshader = this.buildProgram(
bshader.fragmentShader,
bshader.vertexShader,
bshader.uniforms,
{}
);
this._blurvertexattribpos = this._gl.getAttribLocation(this._blurshader, "vertexPosition");
}
renderFrameBuffertoScreen() {
// only needed/works with webgl2
if (this.isWebGL1() || this._fb === null) return;
this.setViewport(); //draw texture in correct viewport
// bind default framebuffer
this._gl.bindFramebuffer(this._gl.FRAMEBUFFER, null);
this._gl.clear(this._gl.COLOR_BUFFER_BIT | this._gl.DEPTH_BUFFER_BIT);
this._gl.frontFace(this._gl.CCW);
this._gl.cullFace(this._gl.BACK);
// set screen shader and use it
this._gl.useProgram(this._screenshader);
this._currentProgram = this._screenshader;
// disable depth test
this.setDepthTest(-1);
this.setDepthWrite(-1);
// bind vertexarray buffer and texture
this._gl.bindBuffer(this._gl.ARRAY_BUFFER, this._screenQuadVBO);
this._gl.enableVertexAttribArray(this._vertexattribpos);
this._gl.vertexAttribPointer(this._vertexattribpos, 2, this._gl.FLOAT, false, 0, 0);
this._gl.activeTexture(this._gl.TEXTURE0);
this._gl.bindTexture(this._gl.TEXTURE_2D, this._targetTexture);
// Draw 6 vertexes => 2 triangles:
this._gl.drawArrays(this._gl.TRIANGLES, 0, 6);
}
initWebGLObjects(scene) {
if (!scene.__webglObjects) {
scene.__webglObjects = [];
scene.__webglObjectsImmediate = [];
scene.__webglSprites = [];
scene.__webglFlares = [];
}
// Add objects; this sets up buffers for each geometryGroup
if (scene.__objectsAdded.length) {
while (scene.__objectsAdded.length) {
this.addObject(scene.__objectsAdded[0], scene);
scene.__objectsAdded.splice(0, 1);
}
// Force buffer update during render
// Hackish fix for initial cartoon-render-then-transparent-surface
// bug
this._currentGeometryGroupHash = -1;
}
while (scene.__objectsRemoved.length) {
this.removeObject(scene.__objectsRemoved[0], scene);
scene.__objectsRemoved.splice(0, 1);
}
// update must be called after objects adding / removal
// This sends typed arrays to GL buffers for each geometryGroup
for (var o = 0, ol = scene.__webglObjects.length; o < ol; o++) {
this.updateObject(scene.__webglObjects[o].object);
}
}
getYRatio() {
if (this.rows !== undefined && this.row !== undefined) return this.rows;
return 1;
}
getXRatio() {
if (this.cols !== undefined && this.col !== undefined) return this.cols;
return 1;
}
getAspect(width, height) {
if (width == undefined || height == undefined) {
width = this._canvas.width;
height = this._canvas.height;
}
var aspect = width / height;
if (
this.rows != undefined &&
this.cols != undefined &&
this.row != undefined &&
this.col != undefined
) {
var wid = width / this.cols;
var hei = height / this.rows;
aspect = wid / hei;
}
return aspect;
}
setTexture(texture, slot, is3D) {
if (texture.needsUpdate) {
if (!texture.__webglInit) {
texture.__webglInit = true;
texture.addEventListener("dispose", this.onTextureDispose.bind(this));
texture.__webglTexture = this._gl.createTexture();
this.info.memory.textures++;
}
this._gl.activeTexture(this._gl.TEXTURE0 + slot);
var gltextureType = is3D ? (this._gl as WebGL2RenderingContext).TEXTURE_3D : this._gl.TEXTURE_2D;
this._gl.bindTexture(gltextureType, texture.__webglTexture);
this._gl.pixelStorei(this._gl.UNPACK_FLIP_Y_WEBGL, texture.flipY);
this._gl.pixelStorei(
this._gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL,
texture.premultiplyAlpha
);
this._gl.pixelStorei(this._gl.UNPACK_ALIGNMENT, texture.unpackAlignment);
this._gl.pixelStorei(this._gl.PACK_ALIGNMENT, texture.unpackAlignment);
var glFormat = this.paramToGL(texture.format),
glType = this.paramToGL(texture.type);
if (!is3D) {
var image = texture.image;
var width = image.width; //might not be defined
var height = image.height;
if (typeof width === "undefined") {
//if no width,
width = image.length;
if (glFormat == this._gl.RGBA) {
width /= 4; //each element takes up 4 bytes
}
height = 1;
}
this.setTextureParameters(this._gl.TEXTURE_2D, texture);
if (!this.isWebGL1()) {
//webgl2
this._gl.texImage2D(
this._gl.TEXTURE_2D,
0,
glFormat,
width,
height,
0,
glFormat,
glType,
texture.image
);
} else {
this._gl.texImage2D(
this._gl.TEXTURE_2D,
0,
glFormat,
glFormat,
glType,
texture.image
);
}
} else {
//3D
this.setTextureParameters((this._gl as WebGL2RenderingContext).TEXTURE_3D, texture);
(this._gl as WebGL2RenderingContext).texImage3D(
(this._gl as WebGL2RenderingContext).TEXTURE_3D,
0,
(this._gl as WebGL2RenderingContext).R32F,
texture.image.size.z,
texture.image.size.y,
texture.image.size.x,
0,
(this._gl as WebGL2RenderingContext).RED,
this._gl.FLOAT,
texture.image.data
);
}
texture.needsUpdate = false;
if (texture.onUpdate) texture.onUpdate();
} else {
this._gl.activeTexture(this._gl.TEXTURE0 + slot);
if (is3D) this._gl.bindTexture((this._gl as WebGL2RenderingContext).TEXTURE_3D, texture.__webglTexture);
else this._gl.bindTexture(this._gl.TEXTURE_2D, texture.__webglTexture);
}
}
supportsVolumetric() {
return !this.isWebGL1();
}
private enableAttribute(attribute) {
if (!this._enabledAttributes[attribute]) {
this._gl.enableVertexAttribArray(attribute);
this._enabledAttributes[attribute] = true;
}
}
private disableAttributes() {
for (let attribute in this._enabledAttributes) {
if (this._enabledAttributes[attribute]) {
this._gl.disableVertexAttribArray(attribute as any);
this._enabledAttributes[attribute] = false;
}
}
}
private setPolygonOffset(polygonOffset, offsetFactor, offsetUnits) {
if (this._oldPolygonOffset !== polygonOffset) {
if (polygonOffset) this._gl.enable(this._gl.POLYGON_OFFSET_FILL);
else this._gl.disable(this._gl.POLYGON_OFFSET_FILL);
}
}
private setLineWidth(width: number) {
if (width !== this._oldLineWidth) {
this._gl.lineWidth(width);
this._oldLineWidth = width;
}
}
private deallocateGeometry(geometry) {
geometry.__webglInit = undefined;
if (geometry.__webglVertexBuffer !== undefined)
this._gl.deleteBuffer(geometry.__webglVertexBuffer);
if (geometry.__webglColorBuffer !== undefined)
this._gl.deleteBuffer(geometry.__webglColorBuffer);
if (geometry.geometryGroups !== undefined) {
for (var g = 0, gl = geometry.groups; g < gl; g++) {
var geometryGroup = geometry.geometryGroups[g];
if (geometryGroup.__webglVertexBuffer !== undefined)
this._gl.deleteBuffer(geometryGroup.__webglVertexBuffer);
if (geometryGroup.__webglColorBuffer !== undefined)
this._gl.deleteBuffer(geometryGroup.__webglColorBuffer);
if (geometryGroup.__webglNormalBuffer !== undefined)
this._gl.deleteBuffer(geometryGroup.__webglNormalBuffer);
if (geometryGroup.__webglFaceBuffer !== undefined)
this._gl.deleteBuffer(geometryGroup.__webglFaceBuffer);
if (geometryGroup.__webglLineBuffer !== undefined)
this._gl.deleteBuffer(geometryGroup.__webglLineBuffer);
}
}
}
private deallocateMaterial(material) {
var program = material.program;
if (program === undefined) return;
material.program = undefined;
// only deallocate GL program if this was the last use of shared program
// assumed there is only single copy of any program in the _programs
// list
// (that's how it's constructed)
var i, il, programInfo;
var deleteProgram = false;
for (i = 0, il = this._programs.length; i < il; i++) {
programInfo = this._programs[i];
if (programInfo.program === program) {
programInfo.usedTimes--;
if (programInfo.usedTimes === 0) {
deleteProgram = true;
}
break;
}
}
if (deleteProgram === true) {
// avoid using array.splice, this is costlier than creating new
// array from scratch
var newPrograms = [];
for (i = 0, il = this._programs.length; i < il; i++) {
programInfo = this._programs[i];
if (programInfo.program !== program) {
newPrograms.push(programInfo);
}
}
this._programs = newPrograms;
this._gl.deleteProgram(program);
this.info.memory.programs--;
}
}
private deallocateTexture(texture) {
if (texture.image && texture.image.__webglTextureCube) {
// cube texture
this._gl.deleteTexture(texture.image.__webglTextureCube);
} else {
// 2D texture
if (!texture.__webglInit) return;
texture.__webglInit = false;
this._gl.deleteTexture(texture.__webglTexture);
}
}
private onGeometryDispose(event) {
var geometry = event.target;
geometry.removeEventListener("dispose", this.onGeometryDispose);
this.deallocateGeometry(geometry);
this.info.memory.geometries--;
}
private onTextureDispose(event) {
var texture = event.target;
texture.removeEventListener("dispose", this.onTextureDispose);
this.deallocateTexture(texture);
this.info.memory.textures--;
}
private onMaterialDispose(event) {
var material = event.target;
material.removeEventListener("dispose", this.onMaterialDispose);
this.deallocateMaterial(material);
}
// Compile and return shader
private getShader(type, str) {
var shader;
if (!this.isWebGL1() && !str.startsWith("#version")) {
//convert webgl1 to webgl2, unless a version is already explicit
str = str.replace(/gl_FragDepthEXT/g, "gl_FragDepth");
if (type == "fragment") {
str = str.replace(/varying/g, "in");
} else {
str = str.replace(/varying/g, "out");
}
str = str.replace(/attribute/g, "in");
str = str.replace(/texture2D/g, "texture");
str = str.replace(/\/\/DEFINEFRAGCOLOR/g, "out vec4 glFragColor;");
str = str.replace(/gl_FragColor/g, "glFragColor");
str = "#version 300 es\n" + str;
}
if (type === "fragment") shader = this._gl.createShader(this._gl.FRAGMENT_SHADER);
else if (type === "vertex") shader = this._gl.createShader(this._gl.VERTEX_SHADER);
if (shader == null) return null;
this._gl.shaderSource(shader, str);
this._gl.compileShader(shader);
if (!this._gl.getShaderParameter(shader, this._gl.COMPILE_STATUS)) {
console.error(this._gl.getShaderInfoLog(shader));
console.error("could not initialize shader");
return null;
}
return shader;
}
// Compile appropriate shaders (if necessary) from source code and attach to
// gl program.
private buildProgram(fragmentShader, vertexShader, uniforms, parameters) {
var p, pl, program, code;
var chunks = [];
chunks.push(fragmentShader);
chunks.push(vertexShader);
for (p in parameters) {
chunks.push(p);
chunks.push(parameters[p]);
}
code = chunks.join();
// check if program has already been compiled
for (p = 0, pl = this._programs.length; p < pl; p++) {
var programInfo = this._programs[p];
if (programInfo.code === code) {
programInfo.usedTimes++;
return programInfo.program;
}
}
// check if program requires webgl2
if (this.isWebGL1()) {
if (parameters.volumetric)
throw new Error(
"Volumetric rendering requires webgl2 which is not supported by your hardware."
);
}
// Set up new program and compile shaders
program = this._gl.createProgram();
if (program == null) return null;
// set up precision
var precision = this._precision;
var prefix = "precision " + precision + " float;";
var prefix_vertex = [
parameters.volumetric ? "#version 300 es" : "",
prefix,
].join("\n");
var prefix_fragment = [
parameters.volumetric ? "#version 300 es" : "",
parameters.fragdepth && this.isWebGL1()
? "#extension GL_EXT_frag_depth: enable"
: "",
parameters.shaded ? "#define SHADED 1" : "",
parameters.wireframe ? "#define WIREFRAME 1" : "",
prefix,
].join("\n");
var glFragmentShader = this.getShader(
"fragment",
prefix_fragment + fragmentShader
);
var glVertexShader = this.getShader("vertex", prefix_vertex + vertexShader);
if (glVertexShader != null) this._gl.attachShader(program, glVertexShader);
if (glFragmentShader != null) this._gl.attachShader(program, glFragmentShader);
this._gl.linkProgram(program);
if (!this._gl.getProgramParameter(program, this._gl.LINK_STATUS))
console.error("Could not initialize shader");
// gather and cache uniform variables and attributes
program.uniforms = {};
program.attributes = {};
var identifiers, u, i;
// uniform vars
identifiers = [
"viewMatrix",
"modelViewMatrix",
"projectionMatrix",
"normalMatrix",
"vWidth",
"vHeight"
];
// custom uniform vars
for (u in uniforms) identifiers.push(u);
for (i = 0; i < identifiers.length; i++) {
var uniformVar = identifiers[i];
program.uniforms[uniformVar] = this._gl.getUniformLocation(
program,
uniformVar
);
}
// attributes
identifiers = [
"position",
"normal",
"color",
"lineDistance",
"offset",
"radius",
];
/*
* for (a in attributes) identifiers.push(a);
*/
for (i = 0; i < identifiers.length; i++) {
var attributeVar = identifiers[i];
program.attributes[attributeVar] = this._gl.getAttribLocation(
program,
attributeVar
);
}
program.id = this._programs_counter++;
this._programs.push({
program: program,
code: code,
usedTimes: 1,
});
this.info.memory.programs = this._programs.length;
return program;
}
private setProgram(camera, lights, fog, material, object, renderer) {
if (material.needsUpdate) {
if (material.program) this.deallocateMaterial(material);
this.initMaterial(material, lights, fog, object);
material.needsUpdate = false;
}
if (material.program == null) return null;
var refreshMaterial = false;
// p_uniforms: uniformVarName => uniformLocation
// m_uniforms: uniformVarName => uniformJsVal
var program = material.program,
p_uniforms = program.uniforms,
m_uniforms = material.uniforms;
if (program != this._currentProgram) {
this._gl.useProgram(program);
this._currentProgram = program;
refreshMaterial = true;
}
if (material.id != this._currentMaterialId) {
this._currentMaterialId = material.id;
refreshMaterial = true;
}
if (camera != this._currentCamera) {
this._currentCamera = camera;
refreshMaterial = true;
}
if (p_uniforms.projectionMatrix) {
this._gl.uniformMatrix4fv(
p_uniforms.projectionMatrix,
false,
camera.projectionMatrix.elements
);
}
if (p_uniforms.modelViewMatrix) {
this._gl.uniformMatrix4fv(
p_uniforms.modelViewMatrix,
false,
object._modelViewMatrix.elements
);
}
if (p_uniforms.normalMatrix) {
this._gl.uniformMatrix3fv(
p_uniforms.normalMatrix,
false,