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

/*
* @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;