1409 lines
48 KiB
JavaScript
1409 lines
48 KiB
JavaScript
// TODO Resources like shader, texture, geometry reference management
|
|
// Trace and find out which shader, texture, geometry can be destroyed
|
|
import Base from './core/Base';
|
|
import GLInfo from './core/GLInfo';
|
|
import glenum from './core/glenum';
|
|
import vendor from './core/vendor';
|
|
|
|
import Material from './Material';
|
|
import Vector2 from './math/Vector2';
|
|
import ProgramManager from './gpu/ProgramManager';
|
|
|
|
// Light header
|
|
import Shader from './Shader';
|
|
|
|
import prezEssl from './shader/source/prez.glsl.js';
|
|
Shader['import'](prezEssl);
|
|
|
|
import mat4 from './glmatrix/mat4';
|
|
import vec3 from './glmatrix/vec3';
|
|
|
|
var mat4Create = mat4.create;
|
|
|
|
var errorShader = {};
|
|
|
|
function defaultGetMaterial(renderable) {
|
|
return renderable.material;
|
|
}
|
|
function defaultGetUniform(renderable, material, symbol) {
|
|
return material.uniforms[symbol].value;
|
|
}
|
|
function defaultIsMaterialChanged(renderabled, prevRenderable, material, prevMaterial) {
|
|
return material !== prevMaterial;
|
|
}
|
|
function defaultIfRender(renderable) {
|
|
return true;
|
|
}
|
|
|
|
function noop() {}
|
|
|
|
var attributeBufferTypeMap = {
|
|
float: glenum.FLOAT,
|
|
byte: glenum.BYTE,
|
|
ubyte: glenum.UNSIGNED_BYTE,
|
|
short: glenum.SHORT,
|
|
ushort: glenum.UNSIGNED_SHORT
|
|
};
|
|
|
|
function VertexArrayObject(availableAttributes, availableAttributeSymbols, indicesBuffer) {
|
|
this.availableAttributes = availableAttributes;
|
|
this.availableAttributeSymbols = availableAttributeSymbols;
|
|
this.indicesBuffer = indicesBuffer;
|
|
|
|
this.vao = null;
|
|
}
|
|
|
|
function PlaceHolderTexture(renderer) {
|
|
var blankCanvas;
|
|
var webglTexture;
|
|
this.bind = function (renderer) {
|
|
if (!blankCanvas) {
|
|
// TODO Environment not support createCanvas.
|
|
blankCanvas = vendor.createCanvas();
|
|
blankCanvas.width = blankCanvas.height = 1;
|
|
blankCanvas.getContext('2d');
|
|
}
|
|
|
|
var gl = renderer.gl;
|
|
var firstBind = !webglTexture;
|
|
if (firstBind) {
|
|
webglTexture = gl.createTexture();
|
|
}
|
|
gl.bindTexture(gl.TEXTURE_2D, webglTexture);
|
|
if (firstBind) {
|
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, blankCanvas);
|
|
}
|
|
};
|
|
this.unbind = function (renderer) {
|
|
renderer.gl.bindTexture(renderer.gl.TEXTURE_2D, null);
|
|
};
|
|
this.isRenderable = function () {
|
|
return true;
|
|
};
|
|
}
|
|
/**
|
|
* @constructor clay.Renderer
|
|
* @extends clay.core.Base
|
|
*/
|
|
var Renderer = Base.extend(function () {
|
|
return /** @lends clay.Renderer# */ {
|
|
|
|
/**
|
|
* @type {HTMLCanvasElement}
|
|
* @readonly
|
|
*/
|
|
canvas: null,
|
|
|
|
/**
|
|
* Canvas width, set by resize method
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
_width: 100,
|
|
|
|
/**
|
|
* Canvas width, set by resize method
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
_height: 100,
|
|
|
|
/**
|
|
* Device pixel ratio, set by setDevicePixelRatio method
|
|
* Specially for high defination display
|
|
* @see http://www.khronos.org/webgl/wiki/HandlingHighDPI
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
devicePixelRatio: (typeof window !== 'undefined' && window.devicePixelRatio) || 1.0,
|
|
|
|
/**
|
|
* Clear color
|
|
* @type {number[]}
|
|
*/
|
|
clearColor: [0.0, 0.0, 0.0, 0.0],
|
|
|
|
/**
|
|
* Default:
|
|
* _gl.COLOR_BUFFER_BIT | _gl.DEPTH_BUFFER_BIT | _gl.STENCIL_BUFFER_BIT
|
|
* @type {number}
|
|
*/
|
|
clearBit: 17664,
|
|
|
|
// Settings when getting context
|
|
// http://www.khronos.org/registry/webgl/specs/latest/#2.4
|
|
|
|
/**
|
|
* If enable alpha, default true
|
|
* @type {boolean}
|
|
*/
|
|
alpha: true,
|
|
/**
|
|
* If enable depth buffer, default true
|
|
* @type {boolean}
|
|
*/
|
|
depth: true,
|
|
/**
|
|
* If enable stencil buffer, default false
|
|
* @type {boolean}
|
|
*/
|
|
stencil: false,
|
|
/**
|
|
* If enable antialias, default true
|
|
* @type {boolean}
|
|
*/
|
|
antialias: true,
|
|
/**
|
|
* If enable premultiplied alpha, default true
|
|
* @type {boolean}
|
|
*/
|
|
premultipliedAlpha: true,
|
|
/**
|
|
* If preserve drawing buffer, default false
|
|
* @type {boolean}
|
|
*/
|
|
preserveDrawingBuffer: false,
|
|
/**
|
|
* If throw context error, usually turned on in debug mode
|
|
* @type {boolean}
|
|
*/
|
|
throwError: true,
|
|
/**
|
|
* WebGL Context created from given canvas
|
|
* @type {WebGLRenderingContext}
|
|
*/
|
|
gl: null,
|
|
/**
|
|
* Renderer viewport, read-only, can be set by setViewport method
|
|
* @type {Object}
|
|
*/
|
|
viewport: {},
|
|
|
|
/**
|
|
* Max joint number
|
|
* @type {number}
|
|
*/
|
|
maxJointNumber: 20,
|
|
|
|
// Set by FrameBuffer#bind
|
|
__currentFrameBuffer: null,
|
|
|
|
_viewportStack: [],
|
|
_clearStack: [],
|
|
|
|
_sceneRendering: null
|
|
};
|
|
}, function () {
|
|
|
|
if (!this.canvas) {
|
|
this.canvas = vendor.createCanvas();
|
|
}
|
|
var canvas = this.canvas;
|
|
try {
|
|
var opts = {
|
|
alpha: this.alpha,
|
|
depth: this.depth,
|
|
stencil: this.stencil,
|
|
antialias: this.antialias,
|
|
premultipliedAlpha: this.premultipliedAlpha,
|
|
preserveDrawingBuffer: this.preserveDrawingBuffer
|
|
};
|
|
|
|
this.gl = canvas.getContext('webgl', opts)
|
|
|| canvas.getContext('experimental-webgl', opts);
|
|
|
|
if (!this.gl) {
|
|
throw new Error();
|
|
}
|
|
|
|
this._glinfo = new GLInfo(this.gl);
|
|
|
|
if (this.gl.targetRenderer) {
|
|
console.error('Already created a renderer');
|
|
}
|
|
this.gl.targetRenderer = this;
|
|
|
|
this.resize();
|
|
}
|
|
catch (e) {
|
|
throw 'Error creating WebGL Context ' + e;
|
|
}
|
|
|
|
// Init managers
|
|
this._programMgr = new ProgramManager(this);
|
|
|
|
this._placeholderTexture = new PlaceHolderTexture(this);
|
|
},
|
|
/** @lends clay.Renderer.prototype. **/
|
|
{
|
|
/**
|
|
* Resize the canvas
|
|
* @param {number} width
|
|
* @param {number} height
|
|
*/
|
|
resize: function(width, height) {
|
|
var canvas = this.canvas;
|
|
// http://www.khronos.org/webgl/wiki/HandlingHighDPI
|
|
// set the display size of the canvas.
|
|
var dpr = this.devicePixelRatio;
|
|
if (width != null) {
|
|
if (canvas.style) {
|
|
canvas.style.width = width + 'px';
|
|
canvas.style.height = height + 'px';
|
|
}
|
|
// set the size of the drawingBuffer
|
|
canvas.width = width * dpr;
|
|
canvas.height = height * dpr;
|
|
|
|
this._width = width;
|
|
this._height = height;
|
|
}
|
|
else {
|
|
this._width = canvas.width / dpr;
|
|
this._height = canvas.height / dpr;
|
|
}
|
|
|
|
this.setViewport(0, 0, this._width, this._height);
|
|
},
|
|
|
|
/**
|
|
* Get renderer width
|
|
* @return {number}
|
|
*/
|
|
getWidth: function () {
|
|
return this._width;
|
|
},
|
|
|
|
/**
|
|
* Get renderer height
|
|
* @return {number}
|
|
*/
|
|
getHeight: function () {
|
|
return this._height;
|
|
},
|
|
|
|
/**
|
|
* Get viewport aspect,
|
|
* @return {number}
|
|
*/
|
|
getViewportAspect: function () {
|
|
var viewport = this.viewport;
|
|
return viewport.width / viewport.height;
|
|
},
|
|
|
|
/**
|
|
* Set devicePixelRatio
|
|
* @param {number} devicePixelRatio
|
|
*/
|
|
setDevicePixelRatio: function(devicePixelRatio) {
|
|
this.devicePixelRatio = devicePixelRatio;
|
|
this.resize(this._width, this._height);
|
|
},
|
|
|
|
/**
|
|
* Get devicePixelRatio
|
|
* @param {number} devicePixelRatio
|
|
*/
|
|
getDevicePixelRatio: function () {
|
|
return this.devicePixelRatio;
|
|
},
|
|
|
|
/**
|
|
* Get WebGL extension
|
|
* @param {string} name
|
|
* @return {object}
|
|
*/
|
|
getGLExtension: function (name) {
|
|
return this._glinfo.getExtension(name);
|
|
},
|
|
|
|
/**
|
|
* Get WebGL parameter
|
|
* @param {string} name
|
|
* @return {*}
|
|
*/
|
|
getGLParameter: function (name) {
|
|
return this._glinfo.getParameter(name);
|
|
},
|
|
|
|
/**
|
|
* Set rendering viewport
|
|
* @param {number|Object} x
|
|
* @param {number} [y]
|
|
* @param {number} [width]
|
|
* @param {number} [height]
|
|
* @param {number} [devicePixelRatio]
|
|
* Defaultly use the renderere devicePixelRatio
|
|
* It needs to be 1 when setViewport is called by frameBuffer
|
|
*
|
|
* @example
|
|
* setViewport(0,0,width,height,1)
|
|
* setViewport({
|
|
* x: 0,
|
|
* y: 0,
|
|
* width: width,
|
|
* height: height,
|
|
* devicePixelRatio: 1
|
|
* })
|
|
*/
|
|
setViewport: function (x, y, width, height, dpr) {
|
|
|
|
if (typeof x === 'object') {
|
|
var obj = x;
|
|
|
|
x = obj.x;
|
|
y = obj.y;
|
|
width = obj.width;
|
|
height = obj.height;
|
|
dpr = obj.devicePixelRatio;
|
|
}
|
|
dpr = dpr || this.devicePixelRatio;
|
|
|
|
this.gl.viewport(
|
|
x * dpr, y * dpr, width * dpr, height * dpr
|
|
);
|
|
// Use a fresh new object, not write property.
|
|
this.viewport = {
|
|
x: x,
|
|
y: y,
|
|
width: width,
|
|
height: height,
|
|
devicePixelRatio: dpr
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Push current viewport into a stack
|
|
*/
|
|
saveViewport: function () {
|
|
this._viewportStack.push(this.viewport);
|
|
},
|
|
|
|
/**
|
|
* Pop viewport from stack, restore in the renderer
|
|
*/
|
|
restoreViewport: function () {
|
|
if (this._viewportStack.length > 0) {
|
|
this.setViewport(this._viewportStack.pop());
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Push current clear into a stack
|
|
*/
|
|
saveClear: function () {
|
|
this._clearStack.push({
|
|
clearBit: this.clearBit,
|
|
clearColor: this.clearColor
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Pop clear from stack, restore in the renderer
|
|
*/
|
|
restoreClear: function () {
|
|
if (this._clearStack.length > 0) {
|
|
var opt = this._clearStack.pop();
|
|
this.clearColor = opt.clearColor;
|
|
this.clearBit = opt.clearBit;
|
|
}
|
|
},
|
|
|
|
bindSceneRendering: function (scene) {
|
|
this._sceneRendering = scene;
|
|
},
|
|
|
|
/**
|
|
* Render the scene in camera to the screen or binded offline framebuffer
|
|
* @param {clay.Scene} scene
|
|
* @param {clay.Camera} camera
|
|
* @param {boolean} [notUpdateScene] If not call the scene.update methods in the rendering, default true
|
|
* @param {boolean} [preZ] If use preZ optimization, default false
|
|
* @return {IRenderInfo}
|
|
*/
|
|
render: function(scene, camera, notUpdateScene, preZ) {
|
|
var _gl = this.gl;
|
|
|
|
var clearColor = this.clearColor;
|
|
|
|
if (this.clearBit) {
|
|
|
|
// Must set depth and color mask true before clear
|
|
_gl.colorMask(true, true, true, true);
|
|
_gl.depthMask(true);
|
|
var viewport = this.viewport;
|
|
var needsScissor = false;
|
|
var viewportDpr = viewport.devicePixelRatio;
|
|
if (viewport.width !== this._width || viewport.height !== this._height
|
|
|| (viewportDpr && viewportDpr !== this.devicePixelRatio)
|
|
|| viewport.x || viewport.y
|
|
) {
|
|
needsScissor = true;
|
|
// http://stackoverflow.com/questions/11544608/how-to-clear-a-rectangle-area-in-webgl
|
|
// Only clear the viewport
|
|
_gl.enable(_gl.SCISSOR_TEST);
|
|
_gl.scissor(viewport.x * viewportDpr, viewport.y * viewportDpr, viewport.width * viewportDpr, viewport.height * viewportDpr);
|
|
}
|
|
_gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
|
|
_gl.clear(this.clearBit);
|
|
if (needsScissor) {
|
|
_gl.disable(_gl.SCISSOR_TEST);
|
|
}
|
|
}
|
|
|
|
// If the scene have been updated in the prepass like shadow map
|
|
// There is no need to update it again
|
|
if (!notUpdateScene) {
|
|
scene.update(false);
|
|
}
|
|
scene.updateLights();
|
|
|
|
camera = camera || scene.getMainCamera();
|
|
if (!camera) {
|
|
console.error('Can\'t find camera in the scene.');
|
|
return;
|
|
}
|
|
camera.update();
|
|
var renderList = scene.updateRenderList(camera, true);
|
|
|
|
this._sceneRendering = scene;
|
|
|
|
var opaqueList = renderList.opaque;
|
|
var transparentList = renderList.transparent;
|
|
var sceneMaterial = scene.material;
|
|
|
|
scene.trigger('beforerender', this, scene, camera, renderList);
|
|
|
|
// Render pre z
|
|
if (preZ) {
|
|
this.renderPreZ(opaqueList, scene, camera);
|
|
_gl.depthFunc(_gl.LEQUAL);
|
|
}
|
|
else {
|
|
_gl.depthFunc(_gl.LESS);
|
|
}
|
|
|
|
// Update the depth of transparent list.
|
|
var worldViewMat = mat4Create();
|
|
var posViewSpace = vec3.create();
|
|
for (var i = 0; i < transparentList.length; i++) {
|
|
var renderable = transparentList[i];
|
|
mat4.multiplyAffine(worldViewMat, camera.viewMatrix.array, renderable.worldTransform.array);
|
|
vec3.transformMat4(posViewSpace, renderable.position.array, worldViewMat);
|
|
renderable.__depth = posViewSpace[2];
|
|
}
|
|
|
|
// Render opaque list
|
|
this.renderPass(opaqueList, camera, {
|
|
getMaterial: function (renderable) {
|
|
return sceneMaterial || renderable.material;
|
|
},
|
|
sortCompare: this.opaqueSortCompare
|
|
});
|
|
|
|
this.renderPass(transparentList, camera, {
|
|
getMaterial: function (renderable) {
|
|
return sceneMaterial || renderable.material;
|
|
},
|
|
sortCompare: this.transparentSortCompare
|
|
});
|
|
|
|
scene.trigger('afterrender', this, scene, camera, renderList);
|
|
|
|
// Cleanup
|
|
this._sceneRendering = null;
|
|
},
|
|
|
|
getProgram: function (renderable, renderMaterial, scene) {
|
|
renderMaterial = renderMaterial || renderable.material;
|
|
return this._programMgr.getProgram(renderable, renderMaterial, scene);
|
|
},
|
|
|
|
validateProgram: function (program) {
|
|
if (program.__error) {
|
|
var errorMsg = program.__error;
|
|
if (errorShader[program.__uid__]) {
|
|
return;
|
|
}
|
|
errorShader[program.__uid__] = true;
|
|
|
|
if (this.throwError) {
|
|
throw new Error(errorMsg);
|
|
}
|
|
else {
|
|
this.trigger('error', errorMsg);
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
updatePrograms: function (list, scene, passConfig) {
|
|
var getMaterial = (passConfig && passConfig.getMaterial) || defaultGetMaterial;
|
|
scene = scene || null;
|
|
for (var i = 0; i < list.length; i++) {
|
|
var renderable = list[i];
|
|
var renderMaterial = getMaterial.call(this, renderable);
|
|
if (i > 0) {
|
|
var prevRenderable = list[i - 1];
|
|
var prevJointsLen = prevRenderable.joints ? prevRenderable.joints.length : 0;
|
|
var jointsLen = renderable.joints ? renderable.joints.length : 0;
|
|
// Keep program not change if joints, material, lightGroup are same of two renderables.
|
|
if (jointsLen === prevJointsLen
|
|
&& renderable.material === prevRenderable.material
|
|
&& renderable.lightGroup === prevRenderable.lightGroup
|
|
) {
|
|
renderable.__program = prevRenderable.__program;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
var program = this._programMgr.getProgram(renderable, renderMaterial, scene);
|
|
|
|
this.validateProgram(program);
|
|
|
|
renderable.__program = program;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Render a single renderable list in camera in sequence
|
|
* @param {clay.Renderable[]} list List of all renderables.
|
|
* @param {clay.Camera} [camera] Camera provide view matrix and porjection matrix. It can be null.
|
|
* @param {Object} [passConfig]
|
|
* @param {Function} [passConfig.getMaterial] Get renderable material.
|
|
* @param {Function} [passConfig.getUniform] Get material uniform value.
|
|
* @param {Function} [passConfig.isMaterialChanged] If material changed.
|
|
* @param {Function} [passConfig.beforeRender] Before render each renderable.
|
|
* @param {Function} [passConfig.afterRender] After render each renderable
|
|
* @param {Function} [passConfig.ifRender] If render the renderable.
|
|
* @param {Function} [passConfig.sortCompare] Sort compare function.
|
|
* @return {IRenderInfo}
|
|
*/
|
|
renderPass: function(list, camera, passConfig) {
|
|
this.trigger('beforerenderpass', this, list, camera, passConfig);
|
|
|
|
passConfig = passConfig || {};
|
|
passConfig.getMaterial = passConfig.getMaterial || defaultGetMaterial;
|
|
passConfig.getUniform = passConfig.getUniform || defaultGetUniform;
|
|
// PENDING Better solution?
|
|
passConfig.isMaterialChanged = passConfig.isMaterialChanged || defaultIsMaterialChanged;
|
|
passConfig.beforeRender = passConfig.beforeRender || noop;
|
|
passConfig.afterRender = passConfig.afterRender || noop;
|
|
|
|
var ifRenderObject = passConfig.ifRender || defaultIfRender;
|
|
|
|
this.updatePrograms(list, this._sceneRendering, passConfig);
|
|
if (passConfig.sortCompare) {
|
|
list.sort(passConfig.sortCompare);
|
|
}
|
|
|
|
// Some common builtin uniforms
|
|
var viewport = this.viewport;
|
|
var vDpr = viewport.devicePixelRatio;
|
|
var viewportUniform = [
|
|
viewport.x * vDpr, viewport.y * vDpr,
|
|
viewport.width * vDpr, viewport.height * vDpr
|
|
];
|
|
var windowDpr = this.devicePixelRatio;
|
|
var windowSizeUniform = this.__currentFrameBuffer
|
|
? [this.__currentFrameBuffer.getTextureWidth(), this.__currentFrameBuffer.getTextureHeight()]
|
|
: [this._width * windowDpr, this._height * windowDpr];
|
|
// DEPRECATED
|
|
var viewportSizeUniform = [
|
|
viewportUniform[2], viewportUniform[3]
|
|
];
|
|
var time = Date.now();
|
|
|
|
// Calculate view and projection matrix
|
|
if (camera) {
|
|
mat4.copy(matrices.VIEW, camera.viewMatrix.array);
|
|
mat4.copy(matrices.PROJECTION, camera.projectionMatrix.array);
|
|
mat4.copy(matrices.VIEWINVERSE, camera.worldTransform.array);
|
|
}
|
|
else {
|
|
mat4.identity(matrices.VIEW);
|
|
mat4.identity(matrices.PROJECTION);
|
|
mat4.identity(matrices.VIEWINVERSE);
|
|
}
|
|
mat4.multiply(matrices.VIEWPROJECTION, matrices.PROJECTION, matrices.VIEW);
|
|
mat4.invert(matrices.PROJECTIONINVERSE, matrices.PROJECTION);
|
|
mat4.invert(matrices.VIEWPROJECTIONINVERSE, matrices.VIEWPROJECTION);
|
|
|
|
var _gl = this.gl;
|
|
var scene = this._sceneRendering;
|
|
|
|
var prevMaterial;
|
|
var prevProgram;
|
|
var prevRenderable;
|
|
|
|
// Status
|
|
var depthTest, depthMask;
|
|
var culling, cullFace, frontFace;
|
|
var transparent;
|
|
var drawID;
|
|
var currentVAO;
|
|
var materialTakesTextureSlot;
|
|
|
|
// var vaoExt = this.getGLExtension('OES_vertex_array_object');
|
|
// not use vaoExt, some platforms may mess it up.
|
|
var vaoExt = null;
|
|
|
|
for (var i = 0; i < list.length; i++) {
|
|
var renderable = list[i];
|
|
var isSceneNode = renderable.worldTransform != null;
|
|
var worldM;
|
|
|
|
if (!ifRenderObject(renderable)) {
|
|
continue;
|
|
}
|
|
|
|
// Skinned mesh will transformed to joint space. Ignore the mesh transform
|
|
if (isSceneNode) {
|
|
worldM = (renderable.isSkinnedMesh && renderable.isSkinnedMesh())
|
|
// TODO
|
|
? (renderable.offsetMatrix ? renderable.offsetMatrix.array :matrices.IDENTITY)
|
|
: renderable.worldTransform.array;
|
|
}
|
|
var geometry = renderable.geometry;
|
|
var material = passConfig.getMaterial.call(this, renderable);
|
|
|
|
var program = renderable.__program;
|
|
var shader = material.shader;
|
|
|
|
var currentDrawID = geometry.__uid__ + '-' + program.__uid__;
|
|
var drawIDChanged = currentDrawID !== drawID;
|
|
drawID = currentDrawID;
|
|
if (drawIDChanged && vaoExt) {
|
|
// TODO Seems need to be bound to null immediately (or before bind another program?) if vao is changed
|
|
vaoExt.bindVertexArrayOES(null);
|
|
}
|
|
if (isSceneNode) {
|
|
mat4.copy(matrices.WORLD, worldM);
|
|
mat4.multiply(matrices.WORLDVIEWPROJECTION, matrices.VIEWPROJECTION, worldM);
|
|
mat4.multiplyAffine(matrices.WORLDVIEW, matrices.VIEW, worldM);
|
|
if (shader.matrixSemantics.WORLDINVERSE ||
|
|
shader.matrixSemantics.WORLDINVERSETRANSPOSE) {
|
|
mat4.invert(matrices.WORLDINVERSE, worldM);
|
|
}
|
|
if (shader.matrixSemantics.WORLDVIEWINVERSE ||
|
|
shader.matrixSemantics.WORLDVIEWINVERSETRANSPOSE) {
|
|
mat4.invert(matrices.WORLDVIEWINVERSE, matrices.WORLDVIEW);
|
|
}
|
|
if (shader.matrixSemantics.WORLDVIEWPROJECTIONINVERSE ||
|
|
shader.matrixSemantics.WORLDVIEWPROJECTIONINVERSETRANSPOSE) {
|
|
mat4.invert(matrices.WORLDVIEWPROJECTIONINVERSE, matrices.WORLDVIEWPROJECTION);
|
|
}
|
|
}
|
|
|
|
// Before render hook
|
|
renderable.beforeRender && renderable.beforeRender(this);
|
|
passConfig.beforeRender.call(this, renderable, material, prevMaterial);
|
|
|
|
var programChanged = program !== prevProgram;
|
|
if (programChanged) {
|
|
// Set lights number
|
|
program.bind(this);
|
|
// Set some common uniforms
|
|
program.setUniformOfSemantic(_gl, 'VIEWPORT', viewportUniform);
|
|
program.setUniformOfSemantic(_gl, 'WINDOW_SIZE', windowSizeUniform);
|
|
if (camera) {
|
|
program.setUniformOfSemantic(_gl, 'NEAR', camera.near);
|
|
program.setUniformOfSemantic(_gl, 'FAR', camera.far);
|
|
}
|
|
program.setUniformOfSemantic(_gl, 'DEVICEPIXELRATIO', vDpr);
|
|
program.setUniformOfSemantic(_gl, 'TIME', time);
|
|
// DEPRECATED
|
|
program.setUniformOfSemantic(_gl, 'VIEWPORT_SIZE', viewportSizeUniform);
|
|
|
|
// Set lights uniforms
|
|
// TODO needs optimized
|
|
if (scene) {
|
|
scene.setLightUniforms(program, renderable.lightGroup, this);
|
|
}
|
|
}
|
|
else {
|
|
program = prevProgram;
|
|
}
|
|
|
|
// Program changes also needs reset the materials.
|
|
if (programChanged || passConfig.isMaterialChanged(
|
|
renderable, prevRenderable, material, prevMaterial
|
|
)) {
|
|
if (material.depthTest !== depthTest) {
|
|
material.depthTest ? _gl.enable(_gl.DEPTH_TEST) : _gl.disable(_gl.DEPTH_TEST);
|
|
depthTest = material.depthTest;
|
|
}
|
|
if (material.depthMask !== depthMask) {
|
|
_gl.depthMask(material.depthMask);
|
|
depthMask = material.depthMask;
|
|
}
|
|
if (material.transparent !== transparent) {
|
|
material.transparent ? _gl.enable(_gl.BLEND) : _gl.disable(_gl.BLEND);
|
|
transparent = material.transparent;
|
|
}
|
|
// TODO cache blending
|
|
if (material.transparent) {
|
|
if (material.blend) {
|
|
material.blend(_gl);
|
|
}
|
|
else {
|
|
// Default blend function
|
|
_gl.blendEquationSeparate(_gl.FUNC_ADD, _gl.FUNC_ADD);
|
|
_gl.blendFuncSeparate(_gl.SRC_ALPHA, _gl.ONE_MINUS_SRC_ALPHA, _gl.ONE, _gl.ONE_MINUS_SRC_ALPHA);
|
|
}
|
|
}
|
|
|
|
materialTakesTextureSlot = this._bindMaterial(
|
|
renderable, material, program,
|
|
prevRenderable || null, prevMaterial || null, prevProgram || null,
|
|
passConfig.getUniform
|
|
);
|
|
prevMaterial = material;
|
|
}
|
|
|
|
var matrixSemanticKeys = shader.matrixSemanticKeys;
|
|
|
|
if (isSceneNode) {
|
|
for (var k = 0; k < matrixSemanticKeys.length; k++) {
|
|
var semantic = matrixSemanticKeys[k];
|
|
var semanticInfo = shader.matrixSemantics[semantic];
|
|
var matrix = matrices[semantic];
|
|
if (semanticInfo.isTranspose) {
|
|
var matrixNoTranspose = matrices[semanticInfo.semanticNoTranspose];
|
|
mat4.transpose(matrix, matrixNoTranspose);
|
|
}
|
|
program.setUniform(_gl, semanticInfo.type, semanticInfo.symbol, matrix);
|
|
}
|
|
}
|
|
|
|
if (renderable.cullFace !== cullFace) {
|
|
cullFace = renderable.cullFace;
|
|
_gl.cullFace(cullFace);
|
|
}
|
|
if (renderable.frontFace !== frontFace) {
|
|
frontFace = renderable.frontFace;
|
|
_gl.frontFace(frontFace);
|
|
}
|
|
if (renderable.culling !== culling) {
|
|
culling = renderable.culling;
|
|
culling ? _gl.enable(_gl.CULL_FACE) : _gl.disable(_gl.CULL_FACE);
|
|
}
|
|
// TODO Not update skeleton in each renderable.
|
|
this._updateSkeleton(renderable, program, materialTakesTextureSlot);
|
|
if (drawIDChanged) {
|
|
currentVAO = this._bindVAO(vaoExt, shader, geometry, program);
|
|
}
|
|
this._renderObject(renderable, currentVAO, program);
|
|
|
|
// After render hook
|
|
passConfig.afterRender(this, renderable);
|
|
renderable.afterRender && renderable.afterRender(this);
|
|
|
|
prevProgram = program;
|
|
prevRenderable = renderable;
|
|
}
|
|
|
|
// TODO Seems need to be bound to null immediately if vao is changed?
|
|
if (vaoExt) {
|
|
vaoExt.bindVertexArrayOES(null);
|
|
}
|
|
|
|
this.trigger('afterrenderpass', this, list, camera, passConfig);
|
|
},
|
|
|
|
getMaxJointNumber: function () {
|
|
return this.maxJointNumber;
|
|
},
|
|
|
|
_updateSkeleton: function (object, program, slot) {
|
|
var _gl = this.gl;
|
|
var skeleton = object.skeleton;
|
|
// Set pose matrices of skinned mesh
|
|
if (skeleton) {
|
|
// TODO Update before culling.
|
|
skeleton.update();
|
|
if (object.joints.length > this.getMaxJointNumber()) {
|
|
var skinMatricesTexture = skeleton.getSubSkinMatricesTexture(object.__uid__, object.joints);
|
|
program.useTextureSlot(this, skinMatricesTexture, slot);
|
|
program.setUniform(_gl, '1i', 'skinMatricesTexture', slot);
|
|
program.setUniform(_gl, '1f', 'skinMatricesTextureSize', skinMatricesTexture.width);
|
|
}
|
|
else {
|
|
var skinMatricesArray = skeleton.getSubSkinMatrices(object.__uid__, object.joints);
|
|
program.setUniformOfSemantic(_gl, 'SKIN_MATRIX', skinMatricesArray);
|
|
}
|
|
}
|
|
},
|
|
|
|
_renderObject: function (renderable, vao, program) {
|
|
var _gl = this.gl;
|
|
var geometry = renderable.geometry;
|
|
|
|
var glDrawMode = renderable.mode;
|
|
if (glDrawMode == null) {
|
|
glDrawMode = 0x0004;
|
|
}
|
|
|
|
var ext = null;
|
|
var isInstanced = renderable.isInstancedMesh && renderable.isInstancedMesh();
|
|
if (isInstanced) {
|
|
ext = this.getGLExtension('ANGLE_instanced_arrays');
|
|
if (!ext) {
|
|
console.warn('Device not support ANGLE_instanced_arrays extension');
|
|
return;
|
|
}
|
|
}
|
|
|
|
var instancedAttrLocations;
|
|
if (isInstanced) {
|
|
instancedAttrLocations = this._bindInstancedAttributes(renderable, program, ext);
|
|
}
|
|
|
|
if (vao.indicesBuffer) {
|
|
var uintExt = this.getGLExtension('OES_element_index_uint');
|
|
var useUintExt = uintExt && (geometry.indices instanceof Uint32Array);
|
|
var indicesType = useUintExt ? _gl.UNSIGNED_INT : _gl.UNSIGNED_SHORT;
|
|
|
|
if (isInstanced) {
|
|
ext.drawElementsInstancedANGLE(
|
|
glDrawMode, vao.indicesBuffer.count, indicesType, 0, renderable.getInstanceCount()
|
|
);
|
|
}
|
|
else {
|
|
_gl.drawElements(glDrawMode, vao.indicesBuffer.count, indicesType, 0);
|
|
}
|
|
}
|
|
else {
|
|
if (isInstanced) {
|
|
ext.drawArraysInstancedANGLE(glDrawMode, 0, geometry.vertexCount, renderable.getInstanceCount());
|
|
}
|
|
else {
|
|
// FIXME Use vertex number in buffer
|
|
// vertexCount may get the wrong value when geometry forget to mark dirty after update
|
|
_gl.drawArrays(glDrawMode, 0, geometry.vertexCount);
|
|
}
|
|
}
|
|
|
|
if (isInstanced) {
|
|
for (var i = 0; i < instancedAttrLocations.length; i++) {
|
|
_gl.disableVertexAttribArray(instancedAttrLocations[i]);
|
|
}
|
|
}
|
|
},
|
|
|
|
_bindInstancedAttributes: function (renderable, program, ext) {
|
|
var _gl = this.gl;
|
|
var instancedBuffers = renderable.getInstancedAttributesBuffers(this);
|
|
var locations = [];
|
|
|
|
for (var i = 0; i < instancedBuffers.length; i++) {
|
|
var bufferObj = instancedBuffers[i];
|
|
var location = program.getAttribLocation(_gl, bufferObj.symbol);
|
|
if (location < 0) {
|
|
continue;
|
|
}
|
|
|
|
var glType = attributeBufferTypeMap[bufferObj.type] || _gl.FLOAT;;
|
|
_gl.enableVertexAttribArray(location); // TODO
|
|
_gl.bindBuffer(_gl.ARRAY_BUFFER, bufferObj.buffer);
|
|
_gl.vertexAttribPointer(location, bufferObj.size, glType, false, 0, 0);
|
|
ext.vertexAttribDivisorANGLE(location, bufferObj.divisor);
|
|
|
|
locations.push(location);
|
|
}
|
|
|
|
return locations;
|
|
},
|
|
|
|
_bindMaterial: function (renderable, material, program, prevRenderable, prevMaterial, prevProgram, getUniformValue) {
|
|
var _gl = this.gl;
|
|
// PENDING Same texture in different material take different slot?
|
|
|
|
// May use shader of other material if shader code are same
|
|
var sameProgram = prevProgram === program;
|
|
|
|
var currentTextureSlot = program.currentTextureSlot();
|
|
var enabledUniforms = material.getEnabledUniforms();
|
|
var textureUniforms = material.getTextureUniforms();
|
|
var placeholderTexture = this._placeholderTexture;
|
|
|
|
for (var u = 0; u < textureUniforms.length; u++) {
|
|
var symbol = textureUniforms[u];
|
|
var uniformValue = getUniformValue(renderable, material, symbol);
|
|
var uniformType = material.uniforms[symbol].type;
|
|
// Not use `instanceof` to determine if a value is texture in Material#bind.
|
|
// Use type instead, in some case texture may be in different namespaces.
|
|
// TODO Duck type validate.
|
|
if (uniformType === 't' && uniformValue) {
|
|
// Reset slot
|
|
uniformValue.__slot = -1;
|
|
}
|
|
else if (uniformType === 'tv') {
|
|
for (var i = 0; i < uniformValue.length; i++) {
|
|
if (uniformValue[i]) {
|
|
uniformValue[i].__slot = -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
placeholderTexture.__slot = -1;
|
|
|
|
// Set uniforms
|
|
for (var u = 0; u < enabledUniforms.length; u++) {
|
|
var symbol = enabledUniforms[u];
|
|
var uniform = material.uniforms[symbol];
|
|
var uniformValue = getUniformValue(renderable, material, symbol);
|
|
var uniformType = uniform.type;
|
|
var isTexture = uniformType === 't';
|
|
|
|
if (isTexture) {
|
|
if (!uniformValue || !uniformValue.isRenderable()) {
|
|
uniformValue = placeholderTexture;
|
|
}
|
|
}
|
|
// PENDING
|
|
// When binding two materials with the same shader
|
|
// Many uniforms will be be set twice even if they have the same value
|
|
// So add a evaluation to see if the uniform is really needed to be set
|
|
if (prevMaterial && sameProgram) {
|
|
var prevUniformValue = getUniformValue(prevRenderable, prevMaterial, symbol);
|
|
if (isTexture) {
|
|
if (!prevUniformValue || !prevUniformValue.isRenderable()) {
|
|
prevUniformValue = placeholderTexture;
|
|
}
|
|
}
|
|
|
|
if (prevUniformValue === uniformValue) {
|
|
if (isTexture) {
|
|
// Still take the slot to make sure same texture in different materials have same slot.
|
|
program.takeCurrentTextureSlot(this, null);
|
|
}
|
|
else if (uniformType === 'tv' && uniformValue) {
|
|
for (var i = 0; i < uniformValue.length; i++) {
|
|
program.takeCurrentTextureSlot(this, null);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (uniformValue == null) {
|
|
continue;
|
|
}
|
|
else if (isTexture) {
|
|
if (uniformValue.__slot < 0) {
|
|
var slot = program.currentTextureSlot();
|
|
var res = program.setUniform(_gl, '1i', symbol, slot);
|
|
if (res) { // Texture uniform is enabled
|
|
program.takeCurrentTextureSlot(this, uniformValue);
|
|
uniformValue.__slot = slot;
|
|
}
|
|
}
|
|
// Multiple uniform use same texture..
|
|
else {
|
|
program.setUniform(_gl, '1i', symbol, uniformValue.__slot);
|
|
}
|
|
}
|
|
else if (Array.isArray(uniformValue)) {
|
|
if (uniformValue.length === 0) {
|
|
continue;
|
|
}
|
|
// Texture Array
|
|
if (uniformType === 'tv') {
|
|
if (!program.hasUniform(symbol)) {
|
|
continue;
|
|
}
|
|
|
|
var arr = [];
|
|
for (var i = 0; i < uniformValue.length; i++) {
|
|
var texture = uniformValue[i];
|
|
|
|
if (texture.__slot < 0) {
|
|
var slot = program.currentTextureSlot();
|
|
arr.push(slot);
|
|
program.takeCurrentTextureSlot(this, texture);
|
|
texture.__slot = slot;
|
|
}
|
|
else {
|
|
arr.push(texture.__slot);
|
|
}
|
|
}
|
|
|
|
program.setUniform(_gl, '1iv', symbol, arr);
|
|
}
|
|
else {
|
|
program.setUniform(_gl, uniform.type, symbol, uniformValue);
|
|
}
|
|
}
|
|
else{
|
|
program.setUniform(_gl, uniform.type, symbol, uniformValue);
|
|
}
|
|
}
|
|
var newSlot = program.currentTextureSlot();
|
|
// Texture slot maybe used out of material.
|
|
program.resetTextureSlot(currentTextureSlot);
|
|
return newSlot;
|
|
},
|
|
|
|
_bindVAO: function (vaoExt, shader, geometry, program) {
|
|
var isStatic = !geometry.dynamic;
|
|
var _gl = this.gl;
|
|
|
|
var vaoId = this.__uid__ + '-' + program.__uid__;
|
|
var vao = geometry.__vaoCache[vaoId];
|
|
if (!vao) {
|
|
var chunks = geometry.getBufferChunks(this);
|
|
if (!chunks || !chunks.length) { // Empty mesh
|
|
return;
|
|
}
|
|
var chunk = chunks[0];
|
|
var attributeBuffers = chunk.attributeBuffers;
|
|
var indicesBuffer = chunk.indicesBuffer;
|
|
|
|
var availableAttributes = [];
|
|
var availableAttributeSymbols = [];
|
|
for (var a = 0; a < attributeBuffers.length; a++) {
|
|
var attributeBufferInfo = attributeBuffers[a];
|
|
var name = attributeBufferInfo.name;
|
|
var semantic = attributeBufferInfo.semantic;
|
|
var symbol;
|
|
if (semantic) {
|
|
var semanticInfo = shader.attributeSemantics[semantic];
|
|
symbol = semanticInfo && semanticInfo.symbol;
|
|
}
|
|
else {
|
|
symbol = name;
|
|
}
|
|
if (symbol && program.attributes[symbol]) {
|
|
availableAttributes.push(attributeBufferInfo);
|
|
availableAttributeSymbols.push(symbol);
|
|
}
|
|
}
|
|
|
|
vao = new VertexArrayObject(
|
|
availableAttributes,
|
|
availableAttributeSymbols,
|
|
indicesBuffer
|
|
);
|
|
|
|
if (isStatic) {
|
|
geometry.__vaoCache[vaoId] = vao;
|
|
}
|
|
}
|
|
|
|
var needsBindAttributes = true;
|
|
|
|
// Create vertex object array cost a lot
|
|
// So we don't use it on the dynamic object
|
|
if (vaoExt && isStatic) {
|
|
// Use vertex array object
|
|
// http://blog.tojicode.com/2012/10/oesvertexarrayobject-extension.html
|
|
if (vao.vao == null) {
|
|
vao.vao = vaoExt.createVertexArrayOES();
|
|
}
|
|
else {
|
|
needsBindAttributes = false;
|
|
}
|
|
vaoExt.bindVertexArrayOES(vao.vao);
|
|
}
|
|
|
|
var availableAttributes = vao.availableAttributes;
|
|
var indicesBuffer = vao.indicesBuffer;
|
|
|
|
if (needsBindAttributes) {
|
|
var locationList = program.enableAttributes(this, vao.availableAttributeSymbols, (vaoExt && isStatic && vao));
|
|
// Setting attributes;
|
|
for (var a = 0; a < availableAttributes.length; a++) {
|
|
var location = locationList[a];
|
|
if (location === -1) {
|
|
continue;
|
|
}
|
|
var attributeBufferInfo = availableAttributes[a];
|
|
var buffer = attributeBufferInfo.buffer;
|
|
var size = attributeBufferInfo.size;
|
|
var glType = attributeBufferTypeMap[attributeBufferInfo.type] || _gl.FLOAT;
|
|
|
|
_gl.bindBuffer(_gl.ARRAY_BUFFER, buffer);
|
|
_gl.vertexAttribPointer(location, size, glType, false, 0, 0);
|
|
}
|
|
|
|
if (geometry.isUseIndices()) {
|
|
_gl.bindBuffer(_gl.ELEMENT_ARRAY_BUFFER, indicesBuffer.buffer);
|
|
}
|
|
}
|
|
|
|
return vao;
|
|
},
|
|
|
|
renderPreZ: function (list, scene, camera) {
|
|
var _gl = this.gl;
|
|
var preZPassMaterial = this._prezMaterial || new Material({
|
|
shader: new Shader(Shader.source('clay.prez.vertex'), Shader.source('clay.prez.fragment'))
|
|
});
|
|
this._prezMaterial = preZPassMaterial;
|
|
|
|
_gl.colorMask(false, false, false, false);
|
|
_gl.depthMask(true);
|
|
|
|
// Status
|
|
this.renderPass(list, camera, {
|
|
ifRender: function (renderable) {
|
|
return !renderable.ignorePreZ;
|
|
},
|
|
isMaterialChanged: function (renderable, prevRenderable) {
|
|
var matA = renderable.material;
|
|
var matB = prevRenderable.material;
|
|
return matA.get('diffuseMap') !== matB.get('diffuseMap')
|
|
|| (matA.get('alphaCutoff') || 0) !== (matB.get('alphaCutoff') || 0);
|
|
},
|
|
getUniform: function (renderable, depthMaterial, symbol) {
|
|
if (symbol === 'alphaMap') {
|
|
return renderable.material.get('diffuseMap');
|
|
}
|
|
else if (symbol === 'alphaCutoff') {
|
|
if (renderable.material.isDefined('fragment', 'ALPHA_TEST')
|
|
&& renderable.material.get('diffuseMap')
|
|
) {
|
|
var alphaCutoff = renderable.material.get('alphaCutoff');
|
|
return alphaCutoff || 0;
|
|
}
|
|
return 0;
|
|
}
|
|
else if (symbol === 'uvRepeat') {
|
|
return renderable.material.get('uvRepeat');
|
|
}
|
|
else if (symbol === 'uvOffset') {
|
|
return renderable.material.get('uvOffset');
|
|
}
|
|
else {
|
|
return depthMaterial.get(symbol);
|
|
}
|
|
},
|
|
getMaterial: function () {
|
|
return preZPassMaterial;
|
|
},
|
|
sort: this.opaqueSortCompare
|
|
});
|
|
|
|
_gl.colorMask(true, true, true, true);
|
|
_gl.depthMask(true);
|
|
},
|
|
|
|
/**
|
|
* Dispose given scene, including all geometris, textures and shaders in the scene
|
|
* @param {clay.Scene} scene
|
|
*/
|
|
disposeScene: function(scene) {
|
|
this.disposeNode(scene, true, true);
|
|
scene.dispose();
|
|
},
|
|
|
|
/**
|
|
* Dispose given node, including all geometries, textures and shaders attached on it or its descendant
|
|
* @param {clay.Node} node
|
|
* @param {boolean} [disposeGeometry=false] If dispose the geometries used in the descendant mesh
|
|
* @param {boolean} [disposeTexture=false] If dispose the textures used in the descendant mesh
|
|
*/
|
|
disposeNode: function(root, disposeGeometry, disposeTexture) {
|
|
// Dettached from parent
|
|
if (root.getParent()) {
|
|
root.getParent().remove(root);
|
|
}
|
|
var disposedMap = {};
|
|
root.traverse(function(node) {
|
|
var material = node.material;
|
|
if (node.geometry && disposeGeometry) {
|
|
node.geometry.dispose(this);
|
|
}
|
|
if (disposeTexture && material && !disposedMap[material.__uid__]) {
|
|
var textureUniforms = material.getTextureUniforms();
|
|
for (var u = 0; u < textureUniforms.length; u++) {
|
|
var uniformName = textureUniforms[u];
|
|
var val = material.uniforms[uniformName].value;
|
|
var uniformType = material.uniforms[uniformName].type;
|
|
if (!val) {
|
|
continue;
|
|
}
|
|
if (uniformType === 't') {
|
|
val.dispose && val.dispose(this);
|
|
}
|
|
else if (uniformType === 'tv') {
|
|
for (var k = 0; k < val.length; k++) {
|
|
if (val[k]) {
|
|
val[k].dispose && val[k].dispose(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
disposedMap[material.__uid__] = true;
|
|
}
|
|
// Particle system and AmbientCubemap light need to dispose
|
|
if (node.dispose) {
|
|
node.dispose(this);
|
|
}
|
|
}, this);
|
|
},
|
|
|
|
/**
|
|
* Dispose given geometry
|
|
* @param {clay.Geometry} geometry
|
|
*/
|
|
disposeGeometry: function(geometry) {
|
|
geometry.dispose(this);
|
|
},
|
|
|
|
/**
|
|
* Dispose given texture
|
|
* @param {clay.Texture} texture
|
|
*/
|
|
disposeTexture: function(texture) {
|
|
texture.dispose(this);
|
|
},
|
|
|
|
/**
|
|
* Dispose given frame buffer
|
|
* @param {clay.FrameBuffer} frameBuffer
|
|
*/
|
|
disposeFrameBuffer: function(frameBuffer) {
|
|
frameBuffer.dispose(this);
|
|
},
|
|
|
|
/**
|
|
* Dispose renderer
|
|
*/
|
|
dispose: function () {},
|
|
|
|
/**
|
|
* Convert screen coords to normalized device coordinates(NDC)
|
|
* Screen coords can get from mouse event, it is positioned relative to canvas element
|
|
* NDC can be used in ray casting with Camera.prototype.castRay methods
|
|
*
|
|
* @param {number} x
|
|
* @param {number} y
|
|
* @param {clay.Vector2} [out]
|
|
* @return {clay.Vector2}
|
|
*/
|
|
screenToNDC: function(x, y, out) {
|
|
if (!out) {
|
|
out = new Vector2();
|
|
}
|
|
// Invert y;
|
|
y = this._height - y;
|
|
|
|
var viewport = this.viewport;
|
|
var arr = out.array;
|
|
arr[0] = (x - viewport.x) / viewport.width;
|
|
arr[0] = arr[0] * 2 - 1;
|
|
arr[1] = (y - viewport.y) / viewport.height;
|
|
arr[1] = arr[1] * 2 - 1;
|
|
|
|
return out;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Opaque renderables compare function
|
|
* @param {clay.Renderable} x
|
|
* @param {clay.Renderable} y
|
|
* @return {boolean}
|
|
* @static
|
|
*/
|
|
Renderer.opaqueSortCompare = Renderer.prototype.opaqueSortCompare = function(x, y) {
|
|
// Priority renderOrder -> program -> material -> geometry
|
|
if (x.renderOrder === y.renderOrder) {
|
|
if (x.__program === y.__program) {
|
|
if (x.material === y.material) {
|
|
return x.geometry.__uid__ - y.geometry.__uid__;
|
|
}
|
|
return x.material.__uid__ - y.material.__uid__;
|
|
}
|
|
if (x.__program && y.__program) {
|
|
return x.__program.__uid__ - y.__program.__uid__;
|
|
}
|
|
return 0;
|
|
}
|
|
return x.renderOrder - y.renderOrder;
|
|
};
|
|
|
|
/**
|
|
* Transparent renderables compare function
|
|
* @param {clay.Renderable} a
|
|
* @param {clay.Renderable} b
|
|
* @return {boolean}
|
|
* @static
|
|
*/
|
|
Renderer.transparentSortCompare = Renderer.prototype.transparentSortCompare = function(x, y) {
|
|
// Priority renderOrder -> depth -> program -> material -> geometry
|
|
|
|
if (x.renderOrder === y.renderOrder) {
|
|
if (x.__depth === y.__depth) {
|
|
if (x.__program === y.__program) {
|
|
if (x.material === y.material) {
|
|
return x.geometry.__uid__ - y.geometry.__uid__;
|
|
}
|
|
return x.material.__uid__ - y.material.__uid__;
|
|
}
|
|
if (x.__program && y.__program) {
|
|
return x.__program.__uid__ - y.__program.__uid__;
|
|
}
|
|
return 0;
|
|
}
|
|
// Depth is negative
|
|
// So farther object has smaller depth value
|
|
return x.__depth - y.__depth;
|
|
}
|
|
return x.renderOrder - y.renderOrder;
|
|
};
|
|
|
|
// Temporary variables
|
|
var matrices = {
|
|
IDENTITY: mat4Create(),
|
|
|
|
WORLD: mat4Create(),
|
|
VIEW: mat4Create(),
|
|
PROJECTION: mat4Create(),
|
|
WORLDVIEW: mat4Create(),
|
|
VIEWPROJECTION: mat4Create(),
|
|
WORLDVIEWPROJECTION: mat4Create(),
|
|
|
|
WORLDINVERSE: mat4Create(),
|
|
VIEWINVERSE: mat4Create(),
|
|
PROJECTIONINVERSE: mat4Create(),
|
|
WORLDVIEWINVERSE: mat4Create(),
|
|
VIEWPROJECTIONINVERSE: mat4Create(),
|
|
WORLDVIEWPROJECTIONINVERSE: mat4Create(),
|
|
|
|
WORLDTRANSPOSE: mat4Create(),
|
|
VIEWTRANSPOSE: mat4Create(),
|
|
PROJECTIONTRANSPOSE: mat4Create(),
|
|
WORLDVIEWTRANSPOSE: mat4Create(),
|
|
VIEWPROJECTIONTRANSPOSE: mat4Create(),
|
|
WORLDVIEWPROJECTIONTRANSPOSE: mat4Create(),
|
|
WORLDINVERSETRANSPOSE: mat4Create(),
|
|
VIEWINVERSETRANSPOSE: mat4Create(),
|
|
PROJECTIONINVERSETRANSPOSE: mat4Create(),
|
|
WORLDVIEWINVERSETRANSPOSE: mat4Create(),
|
|
VIEWPROJECTIONINVERSETRANSPOSE: mat4Create(),
|
|
WORLDVIEWPROJECTIONINVERSETRANSPOSE: mat4Create()
|
|
};
|
|
|
|
/**
|
|
* @name clay.Renderer.COLOR_BUFFER_BIT
|
|
* @type {number}
|
|
*/
|
|
Renderer.COLOR_BUFFER_BIT = glenum.COLOR_BUFFER_BIT;
|
|
/**
|
|
* @name clay.Renderer.DEPTH_BUFFER_BIT
|
|
* @type {number}
|
|
*/
|
|
Renderer.DEPTH_BUFFER_BIT = glenum.DEPTH_BUFFER_BIT;
|
|
/**
|
|
* @name clay.Renderer.STENCIL_BUFFER_BIT
|
|
* @type {number}
|
|
*/
|
|
Renderer.STENCIL_BUFFER_BIT = glenum.STENCIL_BUFFER_BIT;
|
|
|
|
export default Renderer;
|