You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
465 lines
14 KiB
465 lines
14 KiB
/*
|
|
* @module echarts-gl/core/ViewGL
|
|
* @author Yi Shen(http://github.com/pissang)
|
|
*/
|
|
import * as echarts from 'echarts/lib/echarts';
|
|
import Scene from 'claygl/src/Scene';
|
|
import ShadowMapPass from 'claygl/src/prePass/ShadowMap';
|
|
import PerspectiveCamera from 'claygl/src/camera/Perspective';
|
|
import OrthographicCamera from 'claygl/src/camera/Orthographic';
|
|
import Matrix4 from 'claygl/src/math/Matrix4';
|
|
import Vector3 from 'claygl/src/math/Vector3';
|
|
import Vector2 from 'claygl/src/math/Vector2';
|
|
import notifier from 'claygl/src/core/mixin/notifier';
|
|
import EffectCompositor from '../effect/EffectCompositor';
|
|
import TemporalSuperSampling from '../effect/TemporalSuperSampling';
|
|
import halton from '../effect/halton';
|
|
/**
|
|
* @constructor
|
|
* @alias module:echarts-gl/core/ViewGL
|
|
* @param {string} [projection='perspective']
|
|
*/
|
|
|
|
function ViewGL(projection) {
|
|
projection = projection || 'perspective';
|
|
/**
|
|
* @type {module:echarts-gl/core/LayerGL}
|
|
*/
|
|
|
|
this.layer = null;
|
|
/**
|
|
* @type {clay.Scene}
|
|
*/
|
|
|
|
this.scene = new Scene();
|
|
/**
|
|
* @type {clay.Node}
|
|
*/
|
|
|
|
this.rootNode = this.scene;
|
|
this.viewport = {
|
|
x: 0,
|
|
y: 0,
|
|
width: 0,
|
|
height: 0
|
|
};
|
|
this.setProjection(projection);
|
|
this._compositor = new EffectCompositor();
|
|
this._temporalSS = new TemporalSuperSampling();
|
|
this._shadowMapPass = new ShadowMapPass();
|
|
var pcfKernels = [];
|
|
var off = 0;
|
|
|
|
for (var i = 0; i < 30; i++) {
|
|
var pcfKernel = [];
|
|
|
|
for (var k = 0; k < 6; k++) {
|
|
pcfKernel.push(halton(off, 2) * 4.0 - 2.0);
|
|
pcfKernel.push(halton(off, 3) * 4.0 - 2.0);
|
|
off++;
|
|
}
|
|
|
|
pcfKernels.push(pcfKernel);
|
|
}
|
|
|
|
this._pcfKernels = pcfKernels;
|
|
this.scene.on('beforerender', function (renderer, scene, camera) {
|
|
if (this.needsTemporalSS()) {
|
|
this._temporalSS.jitterProjection(renderer, camera);
|
|
}
|
|
}, this);
|
|
}
|
|
/**
|
|
* Set camera type of group
|
|
* @param {string} cameraType 'perspective' | 'orthographic'
|
|
*/
|
|
|
|
|
|
ViewGL.prototype.setProjection = function (projection) {
|
|
var oldCamera = this.camera;
|
|
oldCamera && oldCamera.update();
|
|
|
|
if (projection === 'perspective') {
|
|
if (!(this.camera instanceof PerspectiveCamera)) {
|
|
this.camera = new PerspectiveCamera();
|
|
|
|
if (oldCamera) {
|
|
this.camera.setLocalTransform(oldCamera.localTransform);
|
|
}
|
|
}
|
|
} else {
|
|
if (!(this.camera instanceof OrthographicCamera)) {
|
|
this.camera = new OrthographicCamera();
|
|
|
|
if (oldCamera) {
|
|
this.camera.setLocalTransform(oldCamera.localTransform);
|
|
}
|
|
}
|
|
} // PENDING
|
|
|
|
|
|
this.camera.near = 0.1;
|
|
this.camera.far = 2000;
|
|
};
|
|
/**
|
|
* Set viewport of group
|
|
* @param {number} x Viewport left bottom x
|
|
* @param {number} y Viewport left bottom y
|
|
* @param {number} width Viewport height
|
|
* @param {number} height Viewport height
|
|
* @param {number} [dpr=1]
|
|
*/
|
|
|
|
|
|
ViewGL.prototype.setViewport = function (x, y, width, height, dpr) {
|
|
if (this.camera instanceof PerspectiveCamera) {
|
|
this.camera.aspect = width / height;
|
|
}
|
|
|
|
dpr = dpr || 1;
|
|
this.viewport.x = x;
|
|
this.viewport.y = y;
|
|
this.viewport.width = width;
|
|
this.viewport.height = height;
|
|
this.viewport.devicePixelRatio = dpr; // Source and output of compositor use high dpr texture.
|
|
// But the intermediate texture of bloom, dof effects use fixed 1.0 dpr
|
|
|
|
this._compositor.resize(width * dpr, height * dpr);
|
|
|
|
this._temporalSS.resize(width * dpr, height * dpr);
|
|
};
|
|
/**
|
|
* If contain screen point x, y
|
|
* @param {number} x offsetX
|
|
* @param {number} y offsetY
|
|
* @return {boolean}
|
|
*/
|
|
|
|
|
|
ViewGL.prototype.containPoint = function (x, y) {
|
|
var viewport = this.viewport;
|
|
var height = this.layer.renderer.getHeight(); // Flip y;
|
|
|
|
y = height - y;
|
|
return x >= viewport.x && y >= viewport.y && x <= viewport.x + viewport.width && y <= viewport.y + viewport.height;
|
|
};
|
|
/**
|
|
* Cast a ray
|
|
* @param {number} x offsetX
|
|
* @param {number} y offsetY
|
|
* @param {clay.math.Ray} out
|
|
* @return {clay.math.Ray}
|
|
*/
|
|
|
|
|
|
var ndc = new Vector2();
|
|
|
|
ViewGL.prototype.castRay = function (x, y, out) {
|
|
var renderer = this.layer.renderer;
|
|
var oldViewport = renderer.viewport;
|
|
renderer.viewport = this.viewport;
|
|
renderer.screenToNDC(x, y, ndc);
|
|
this.camera.castRay(ndc, out);
|
|
renderer.viewport = oldViewport;
|
|
return out;
|
|
};
|
|
/**
|
|
* Prepare and update scene before render
|
|
*/
|
|
|
|
|
|
ViewGL.prototype.prepareRender = function () {
|
|
this.scene.update();
|
|
this.camera.update();
|
|
this.scene.updateLights();
|
|
var renderList = this.scene.updateRenderList(this.camera);
|
|
this._needsSortProgressively = false; // If has any transparent mesh needs sort triangles progressively.
|
|
|
|
for (var i = 0; i < renderList.transparent.length; i++) {
|
|
var renderable = renderList.transparent[i];
|
|
var geometry = renderable.geometry;
|
|
|
|
if (geometry.needsSortVerticesProgressively && geometry.needsSortVerticesProgressively()) {
|
|
this._needsSortProgressively = true;
|
|
}
|
|
|
|
if (geometry.needsSortTrianglesProgressively && geometry.needsSortTrianglesProgressively()) {
|
|
this._needsSortProgressively = true;
|
|
}
|
|
}
|
|
|
|
this._frame = 0;
|
|
|
|
this._temporalSS.resetFrame(); // var lights = this.scene.getLights();
|
|
// for (var i = 0; i < lights.length; i++) {
|
|
// if (lights[i].cubemap) {
|
|
// if (this._compositor && this._compositor.isSSREnabled()) {
|
|
// lights[i].invisible = true;
|
|
// }
|
|
// else {
|
|
// lights[i].invisible = false;
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
};
|
|
|
|
ViewGL.prototype.render = function (renderer, accumulating) {
|
|
this._doRender(renderer, accumulating, this._frame);
|
|
|
|
this._frame++;
|
|
};
|
|
|
|
ViewGL.prototype.needsAccumulate = function () {
|
|
return this.needsTemporalSS() || this._needsSortProgressively;
|
|
};
|
|
|
|
ViewGL.prototype.needsTemporalSS = function () {
|
|
var enableTemporalSS = this._enableTemporalSS;
|
|
|
|
if (enableTemporalSS === 'auto') {
|
|
enableTemporalSS = this._enablePostEffect;
|
|
}
|
|
|
|
return enableTemporalSS;
|
|
};
|
|
|
|
ViewGL.prototype.hasDOF = function () {
|
|
return this._enableDOF;
|
|
};
|
|
|
|
ViewGL.prototype.isAccumulateFinished = function () {
|
|
return this.needsTemporalSS() ? this._temporalSS.isFinished() : this._frame > 30;
|
|
};
|
|
|
|
ViewGL.prototype._doRender = function (renderer, accumulating, accumFrame) {
|
|
var scene = this.scene;
|
|
var camera = this.camera;
|
|
accumFrame = accumFrame || 0;
|
|
|
|
this._updateTransparent(renderer, scene, camera, accumFrame);
|
|
|
|
if (!accumulating) {
|
|
this._shadowMapPass.kernelPCF = this._pcfKernels[0]; // Not render shadowmap pass in accumulating frame.
|
|
|
|
this._shadowMapPass.render(renderer, scene, camera, true);
|
|
}
|
|
|
|
this._updateShadowPCFKernel(accumFrame); // Shadowmap will set clear color.
|
|
|
|
|
|
var bgColor = renderer.clearColor;
|
|
renderer.gl.clearColor(bgColor[0], bgColor[1], bgColor[2], bgColor[3]);
|
|
|
|
if (this._enablePostEffect) {
|
|
// normal render also needs to be jittered when have edge pass.
|
|
if (this.needsTemporalSS()) {
|
|
this._temporalSS.jitterProjection(renderer, camera);
|
|
}
|
|
|
|
this._compositor.updateNormal(renderer, scene, camera, this._temporalSS.getFrame());
|
|
} // Always update SSAO to make sure have correct ssaoMap status
|
|
|
|
|
|
this._updateSSAO(renderer, scene, camera, this._temporalSS.getFrame());
|
|
|
|
if (this._enablePostEffect) {
|
|
var frameBuffer = this._compositor.getSourceFrameBuffer();
|
|
|
|
frameBuffer.bind(renderer);
|
|
renderer.gl.clear(renderer.gl.DEPTH_BUFFER_BIT | renderer.gl.COLOR_BUFFER_BIT);
|
|
renderer.render(scene, camera, true, true);
|
|
frameBuffer.unbind(renderer);
|
|
|
|
if (this.needsTemporalSS() && accumulating) {
|
|
this._compositor.composite(renderer, scene, camera, this._temporalSS.getSourceFrameBuffer(), this._temporalSS.getFrame());
|
|
|
|
renderer.setViewport(this.viewport);
|
|
|
|
this._temporalSS.render(renderer);
|
|
} else {
|
|
renderer.setViewport(this.viewport);
|
|
|
|
this._compositor.composite(renderer, scene, camera, null, 0);
|
|
}
|
|
} else {
|
|
if (this.needsTemporalSS() && accumulating) {
|
|
var frameBuffer = this._temporalSS.getSourceFrameBuffer();
|
|
|
|
frameBuffer.bind(renderer);
|
|
renderer.saveClear();
|
|
renderer.clearBit = renderer.gl.DEPTH_BUFFER_BIT | renderer.gl.COLOR_BUFFER_BIT;
|
|
renderer.render(scene, camera, true, true);
|
|
renderer.restoreClear();
|
|
frameBuffer.unbind(renderer);
|
|
renderer.setViewport(this.viewport);
|
|
|
|
this._temporalSS.render(renderer);
|
|
} else {
|
|
renderer.setViewport(this.viewport);
|
|
renderer.render(scene, camera, true, true);
|
|
}
|
|
} // this._shadowMapPass.renderDebug(renderer);
|
|
// this._compositor._normalPass.renderDebug(renderer);
|
|
|
|
};
|
|
|
|
ViewGL.prototype._updateTransparent = function (renderer, scene, camera, frame) {
|
|
var v3 = new Vector3();
|
|
var invWorldTransform = new Matrix4();
|
|
var cameraWorldPosition = camera.getWorldPosition();
|
|
var transparentList = scene.getRenderList(camera).transparent; // Sort transparent object.
|
|
|
|
for (var i = 0; i < transparentList.length; i++) {
|
|
var renderable = transparentList[i];
|
|
var geometry = renderable.geometry;
|
|
Matrix4.invert(invWorldTransform, renderable.worldTransform);
|
|
Vector3.transformMat4(v3, cameraWorldPosition, invWorldTransform);
|
|
|
|
if (geometry.needsSortTriangles && geometry.needsSortTriangles()) {
|
|
geometry.doSortTriangles(v3, frame);
|
|
}
|
|
|
|
if (geometry.needsSortVertices && geometry.needsSortVertices()) {
|
|
geometry.doSortVertices(v3, frame);
|
|
}
|
|
}
|
|
};
|
|
|
|
ViewGL.prototype._updateSSAO = function (renderer, scene, camera) {
|
|
var ifEnableSSAO = this._enableSSAO && this._enablePostEffect;
|
|
|
|
if (ifEnableSSAO) {
|
|
this._compositor.updateSSAO(renderer, scene, camera, this._temporalSS.getFrame());
|
|
}
|
|
|
|
var renderList = scene.getRenderList(camera);
|
|
|
|
for (var i = 0; i < renderList.opaque.length; i++) {
|
|
var renderable = renderList.opaque[i]; // PENDING
|
|
|
|
if (renderable.renderNormal) {
|
|
renderable.material[ifEnableSSAO ? 'enableTexture' : 'disableTexture']('ssaoMap');
|
|
}
|
|
|
|
if (ifEnableSSAO) {
|
|
renderable.material.set('ssaoMap', this._compositor.getSSAOTexture());
|
|
}
|
|
}
|
|
};
|
|
|
|
ViewGL.prototype._updateShadowPCFKernel = function (frame) {
|
|
var pcfKernel = this._pcfKernels[frame % this._pcfKernels.length];
|
|
var renderList = this.scene.getRenderList(this.camera);
|
|
var opaqueList = renderList.opaque;
|
|
|
|
for (var i = 0; i < opaqueList.length; i++) {
|
|
if (opaqueList[i].receiveShadow) {
|
|
opaqueList[i].material.set('pcfKernel', pcfKernel);
|
|
opaqueList[i].material.define('fragment', 'PCF_KERNEL_SIZE', pcfKernel.length / 2);
|
|
}
|
|
}
|
|
};
|
|
|
|
ViewGL.prototype.dispose = function (renderer) {
|
|
this._compositor.dispose(renderer.gl);
|
|
|
|
this._temporalSS.dispose(renderer.gl);
|
|
|
|
this._shadowMapPass.dispose(renderer);
|
|
};
|
|
/**
|
|
* @param {module:echarts/Model} Post effect model
|
|
*/
|
|
|
|
|
|
ViewGL.prototype.setPostEffect = function (postEffectModel, api) {
|
|
var compositor = this._compositor;
|
|
this._enablePostEffect = postEffectModel.get('enable');
|
|
var bloomModel = postEffectModel.getModel('bloom');
|
|
var edgeModel = postEffectModel.getModel('edge');
|
|
var dofModel = postEffectModel.getModel('DOF', postEffectModel.getModel('depthOfField'));
|
|
var ssaoModel = postEffectModel.getModel('SSAO', postEffectModel.getModel('screenSpaceAmbientOcclusion'));
|
|
var ssrModel = postEffectModel.getModel('SSR', postEffectModel.getModel('screenSpaceReflection'));
|
|
var fxaaModel = postEffectModel.getModel('FXAA');
|
|
var colorCorrModel = postEffectModel.getModel('colorCorrection');
|
|
bloomModel.get('enable') ? compositor.enableBloom() : compositor.disableBloom();
|
|
dofModel.get('enable') ? compositor.enableDOF() : compositor.disableDOF();
|
|
ssrModel.get('enable') ? compositor.enableSSR() : compositor.disableSSR();
|
|
colorCorrModel.get('enable') ? compositor.enableColorCorrection() : compositor.disableColorCorrection();
|
|
edgeModel.get('enable') ? compositor.enableEdge() : compositor.disableEdge();
|
|
fxaaModel.get('enable') ? compositor.enableFXAA() : compositor.disableFXAA();
|
|
this._enableDOF = dofModel.get('enable');
|
|
this._enableSSAO = ssaoModel.get('enable');
|
|
this._enableSSAO ? compositor.enableSSAO() : compositor.disableSSAO();
|
|
compositor.setBloomIntensity(bloomModel.get('intensity'));
|
|
compositor.setEdgeColor(edgeModel.get('color'));
|
|
compositor.setColorLookupTexture(colorCorrModel.get('lookupTexture'), api);
|
|
compositor.setExposure(colorCorrModel.get('exposure'));
|
|
['radius', 'quality', 'intensity'].forEach(function (name) {
|
|
compositor.setSSAOParameter(name, ssaoModel.get(name));
|
|
});
|
|
['quality', 'maxRoughness', 'physical'].forEach(function (name) {
|
|
compositor.setSSRParameter(name, ssrModel.get(name));
|
|
});
|
|
['quality', 'focalDistance', 'focalRange', 'blurRadius', 'fstop'].forEach(function (name) {
|
|
compositor.setDOFParameter(name, dofModel.get(name));
|
|
});
|
|
['brightness', 'contrast', 'saturation'].forEach(function (name) {
|
|
compositor.setColorCorrection(name, colorCorrModel.get(name));
|
|
});
|
|
};
|
|
|
|
ViewGL.prototype.setDOFFocusOnPoint = function (depth) {
|
|
if (this._enablePostEffect) {
|
|
if (depth > this.camera.far || depth < this.camera.near) {
|
|
return;
|
|
}
|
|
|
|
this._compositor.setDOFParameter('focalDistance', depth);
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
ViewGL.prototype.setTemporalSuperSampling = function (temporalSuperSamplingModel) {
|
|
this._enableTemporalSS = temporalSuperSamplingModel.get('enable');
|
|
};
|
|
|
|
ViewGL.prototype.isLinearSpace = function () {
|
|
return this._enablePostEffect;
|
|
};
|
|
|
|
ViewGL.prototype.setRootNode = function (rootNode) {
|
|
if (this.rootNode === rootNode) {
|
|
return;
|
|
}
|
|
|
|
var children = this.rootNode.children();
|
|
|
|
for (var i = 0; i < children.length; i++) {
|
|
rootNode.add(children[i]);
|
|
}
|
|
|
|
if (rootNode !== this.scene) {
|
|
this.scene.add(rootNode);
|
|
}
|
|
|
|
this.rootNode = rootNode;
|
|
}; // Proxies
|
|
|
|
|
|
ViewGL.prototype.add = function (node3D) {
|
|
this.rootNode.add(node3D);
|
|
};
|
|
|
|
ViewGL.prototype.remove = function (node3D) {
|
|
this.rootNode.remove(node3D);
|
|
};
|
|
|
|
ViewGL.prototype.removeAll = function (node3D) {
|
|
this.rootNode.removeAll(node3D);
|
|
};
|
|
|
|
Object.assign(ViewGL.prototype, notifier);
|
|
export default ViewGL; |