615 lines
15 KiB
JavaScript
615 lines
15 KiB
JavaScript
import Shader from 'claygl/src/Shader';
|
|
import Texture2D from 'claygl/src/Texture2D';
|
|
import Texture from 'claygl/src/Texture';
|
|
import FrameBuffer from 'claygl/src/FrameBuffer';
|
|
import createCompositor from 'claygl/src/compositor/createCompositor';
|
|
import SSAOPass from './SSAOPass';
|
|
import SSRPass from './SSRPass';
|
|
import poissonKernel from './poissonKernel';
|
|
import graphicGL from '../util/graphicGL';
|
|
import NormalPass from './NormalPass';
|
|
import EdgePass from './EdgePass';
|
|
import effectJson from './composite.js';
|
|
import blurCode from 'claygl/src/shader/source/compositor/blur.glsl.js';
|
|
import lutCode from 'claygl/src/shader/source/compositor/lut.glsl.js';
|
|
import outputCode from 'claygl/src/shader/source/compositor/output.glsl.js';
|
|
import brightCode from 'claygl/src/shader/source/compositor/bright.glsl.js';
|
|
import downsampleCode from 'claygl/src/shader/source/compositor/downsample.glsl.js';
|
|
import upsampleCode from 'claygl/src/shader/source/compositor/upsample.glsl.js';
|
|
import hdrCode from 'claygl/src/shader/source/compositor/hdr.glsl.js';
|
|
import blendCode from 'claygl/src/shader/source/compositor/blend.glsl.js';
|
|
import fxaaCode from 'claygl/src/shader/source/compositor/fxaa.glsl.js';
|
|
import DOFCode from './DOF.glsl.js';
|
|
import edgeCode from './edge.glsl.js';
|
|
Shader['import'](blurCode);
|
|
Shader['import'](lutCode);
|
|
Shader['import'](outputCode);
|
|
Shader['import'](brightCode);
|
|
Shader['import'](downsampleCode);
|
|
Shader['import'](upsampleCode);
|
|
Shader['import'](hdrCode);
|
|
Shader['import'](blendCode);
|
|
Shader['import'](fxaaCode);
|
|
Shader['import'](DOFCode);
|
|
Shader['import'](edgeCode);
|
|
|
|
function makeCommonOutputs(getWidth, getHeight) {
|
|
return {
|
|
color: {
|
|
parameters: {
|
|
width: getWidth,
|
|
height: getHeight
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
var FINAL_NODES_CHAIN = ['composite', 'FXAA'];
|
|
|
|
function EffectCompositor() {
|
|
this._width;
|
|
this._height;
|
|
this._dpr;
|
|
this._sourceTexture = new Texture2D({
|
|
type: Texture.HALF_FLOAT
|
|
});
|
|
this._depthTexture = new Texture2D({
|
|
format: Texture.DEPTH_COMPONENT,
|
|
type: Texture.UNSIGNED_INT
|
|
});
|
|
this._framebuffer = new FrameBuffer();
|
|
|
|
this._framebuffer.attach(this._sourceTexture);
|
|
|
|
this._framebuffer.attach(this._depthTexture, FrameBuffer.DEPTH_ATTACHMENT);
|
|
|
|
this._normalPass = new NormalPass();
|
|
this._compositor = createCompositor(effectJson);
|
|
|
|
var sourceNode = this._compositor.getNodeByName('source');
|
|
|
|
sourceNode.texture = this._sourceTexture;
|
|
|
|
var cocNode = this._compositor.getNodeByName('coc');
|
|
|
|
this._sourceNode = sourceNode;
|
|
this._cocNode = cocNode;
|
|
this._compositeNode = this._compositor.getNodeByName('composite');
|
|
this._fxaaNode = this._compositor.getNodeByName('FXAA');
|
|
this._dofBlurNodes = ['dof_far_blur', 'dof_near_blur', 'dof_coc_blur'].map(function (name) {
|
|
return this._compositor.getNodeByName(name);
|
|
}, this);
|
|
this._dofBlurKernel = 0;
|
|
this._dofBlurKernelSize = new Float32Array(0);
|
|
this._finalNodesChain = FINAL_NODES_CHAIN.map(function (name) {
|
|
return this._compositor.getNodeByName(name);
|
|
}, this);
|
|
var gBufferObj = {
|
|
normalTexture: this._normalPass.getNormalTexture(),
|
|
depthTexture: this._normalPass.getDepthTexture()
|
|
};
|
|
this._ssaoPass = new SSAOPass(gBufferObj);
|
|
this._ssrPass = new SSRPass(gBufferObj);
|
|
this._edgePass = new EdgePass(gBufferObj);
|
|
}
|
|
|
|
EffectCompositor.prototype.resize = function (width, height, dpr) {
|
|
dpr = dpr || 1;
|
|
var width = width * dpr;
|
|
var height = height * dpr;
|
|
var sourceTexture = this._sourceTexture;
|
|
var depthTexture = this._depthTexture;
|
|
sourceTexture.width = width;
|
|
sourceTexture.height = height;
|
|
depthTexture.width = width;
|
|
depthTexture.height = height;
|
|
var rendererMock = {
|
|
getWidth: function () {
|
|
return width;
|
|
},
|
|
getHeight: function () {
|
|
return height;
|
|
},
|
|
getDevicePixelRatio: function () {
|
|
return dpr;
|
|
}
|
|
};
|
|
|
|
function wrapCallback(obj, key) {
|
|
if (typeof obj[key] === 'function') {
|
|
var oldFunc = obj[key].__original || obj[key]; // Use viewport width/height instead of renderer width/height
|
|
|
|
obj[key] = function (renderer) {
|
|
return oldFunc.call(this, rendererMock);
|
|
};
|
|
|
|
obj[key].__original = oldFunc;
|
|
}
|
|
}
|
|
|
|
this._compositor.nodes.forEach(function (node) {
|
|
for (var outKey in node.outputs) {
|
|
var parameters = node.outputs[outKey].parameters;
|
|
|
|
if (parameters) {
|
|
wrapCallback(parameters, 'width');
|
|
wrapCallback(parameters, 'height');
|
|
}
|
|
}
|
|
|
|
for (var paramKey in node.parameters) {
|
|
wrapCallback(node.parameters, paramKey);
|
|
}
|
|
});
|
|
|
|
this._width = width;
|
|
this._height = height;
|
|
this._dpr = dpr;
|
|
};
|
|
|
|
EffectCompositor.prototype.getWidth = function () {
|
|
return this._width;
|
|
};
|
|
|
|
EffectCompositor.prototype.getHeight = function () {
|
|
return this._height;
|
|
};
|
|
|
|
EffectCompositor.prototype._ifRenderNormalPass = function () {
|
|
return this._enableSSAO || this._enableEdge || this._enableSSR;
|
|
};
|
|
|
|
EffectCompositor.prototype._getPrevNode = function (node) {
|
|
var idx = FINAL_NODES_CHAIN.indexOf(node.name) - 1;
|
|
var prevNode = this._finalNodesChain[idx];
|
|
|
|
while (prevNode && !this._compositor.getNodeByName(prevNode.name)) {
|
|
idx -= 1;
|
|
prevNode = this._finalNodesChain[idx];
|
|
}
|
|
|
|
return prevNode;
|
|
};
|
|
|
|
EffectCompositor.prototype._getNextNode = function (node) {
|
|
var idx = FINAL_NODES_CHAIN.indexOf(node.name) + 1;
|
|
var nextNode = this._finalNodesChain[idx];
|
|
|
|
while (nextNode && !this._compositor.getNodeByName(nextNode.name)) {
|
|
idx += 1;
|
|
nextNode = this._finalNodesChain[idx];
|
|
}
|
|
|
|
return nextNode;
|
|
};
|
|
|
|
EffectCompositor.prototype._addChainNode = function (node) {
|
|
var prevNode = this._getPrevNode(node);
|
|
|
|
var nextNode = this._getNextNode(node);
|
|
|
|
if (!prevNode) {
|
|
return;
|
|
}
|
|
|
|
node.inputs.texture = prevNode.name;
|
|
|
|
if (nextNode) {
|
|
node.outputs = makeCommonOutputs(this.getWidth.bind(this), this.getHeight.bind(this));
|
|
nextNode.inputs.texture = node.name;
|
|
} else {
|
|
node.outputs = null;
|
|
}
|
|
|
|
this._compositor.addNode(node);
|
|
};
|
|
|
|
EffectCompositor.prototype._removeChainNode = function (node) {
|
|
var prevNode = this._getPrevNode(node);
|
|
|
|
var nextNode = this._getNextNode(node);
|
|
|
|
if (!prevNode) {
|
|
return;
|
|
}
|
|
|
|
if (nextNode) {
|
|
prevNode.outputs = makeCommonOutputs(this.getWidth.bind(this), this.getHeight.bind(this));
|
|
nextNode.inputs.texture = prevNode.name;
|
|
} else {
|
|
prevNode.outputs = null;
|
|
}
|
|
|
|
this._compositor.removeNode(node);
|
|
};
|
|
/**
|
|
* Update normal
|
|
*/
|
|
|
|
|
|
EffectCompositor.prototype.updateNormal = function (renderer, scene, camera, frame) {
|
|
if (this._ifRenderNormalPass()) {
|
|
this._normalPass.update(renderer, scene, camera);
|
|
}
|
|
};
|
|
/**
|
|
* Render SSAO after render the scene, before compositing
|
|
*/
|
|
|
|
|
|
EffectCompositor.prototype.updateSSAO = function (renderer, scene, camera, frame) {
|
|
this._ssaoPass.update(renderer, camera, frame);
|
|
};
|
|
/**
|
|
* Enable SSAO effect
|
|
*/
|
|
|
|
|
|
EffectCompositor.prototype.enableSSAO = function () {
|
|
this._enableSSAO = true;
|
|
};
|
|
/**
|
|
* Disable SSAO effect
|
|
*/
|
|
|
|
|
|
EffectCompositor.prototype.disableSSAO = function () {
|
|
this._enableSSAO = false;
|
|
};
|
|
/**
|
|
* Enable SSR effect
|
|
*/
|
|
|
|
|
|
EffectCompositor.prototype.enableSSR = function () {
|
|
this._enableSSR = true; // this._normalPass.enableTargetTexture3 = true;
|
|
};
|
|
/**
|
|
* Disable SSR effect
|
|
*/
|
|
|
|
|
|
EffectCompositor.prototype.disableSSR = function () {
|
|
this._enableSSR = false; // this._normalPass.enableTargetTexture3 = false;
|
|
};
|
|
/**
|
|
* Render SSAO after render the scene, before compositing
|
|
*/
|
|
|
|
|
|
EffectCompositor.prototype.getSSAOTexture = function () {
|
|
return this._ssaoPass.getTargetTexture();
|
|
};
|
|
/**
|
|
* @return {clay.FrameBuffer}
|
|
*/
|
|
|
|
|
|
EffectCompositor.prototype.getSourceFrameBuffer = function () {
|
|
return this._framebuffer;
|
|
};
|
|
/**
|
|
* @return {clay.Texture2D}
|
|
*/
|
|
|
|
|
|
EffectCompositor.prototype.getSourceTexture = function () {
|
|
return this._sourceTexture;
|
|
};
|
|
/**
|
|
* Disable fxaa effect
|
|
*/
|
|
|
|
|
|
EffectCompositor.prototype.disableFXAA = function () {
|
|
this._removeChainNode(this._fxaaNode);
|
|
};
|
|
/**
|
|
* Enable fxaa effect
|
|
*/
|
|
|
|
|
|
EffectCompositor.prototype.enableFXAA = function () {
|
|
this._addChainNode(this._fxaaNode);
|
|
};
|
|
/**
|
|
* Enable bloom effect
|
|
*/
|
|
|
|
|
|
EffectCompositor.prototype.enableBloom = function () {
|
|
this._compositeNode.inputs.bloom = 'bloom_composite';
|
|
|
|
this._compositor.dirty();
|
|
};
|
|
/**
|
|
* Disable bloom effect
|
|
*/
|
|
|
|
|
|
EffectCompositor.prototype.disableBloom = function () {
|
|
this._compositeNode.inputs.bloom = null;
|
|
|
|
this._compositor.dirty();
|
|
};
|
|
/**
|
|
* Enable depth of field effect
|
|
*/
|
|
|
|
|
|
EffectCompositor.prototype.enableDOF = function () {
|
|
this._compositeNode.inputs.texture = 'dof_composite';
|
|
|
|
this._compositor.dirty();
|
|
};
|
|
/**
|
|
* Disable depth of field effect
|
|
*/
|
|
|
|
|
|
EffectCompositor.prototype.disableDOF = function () {
|
|
this._compositeNode.inputs.texture = 'source';
|
|
|
|
this._compositor.dirty();
|
|
};
|
|
/**
|
|
* Enable color correction
|
|
*/
|
|
|
|
|
|
EffectCompositor.prototype.enableColorCorrection = function () {
|
|
this._compositeNode.define('COLOR_CORRECTION');
|
|
|
|
this._enableColorCorrection = true;
|
|
};
|
|
/**
|
|
* Disable color correction
|
|
*/
|
|
|
|
|
|
EffectCompositor.prototype.disableColorCorrection = function () {
|
|
this._compositeNode.undefine('COLOR_CORRECTION');
|
|
|
|
this._enableColorCorrection = false;
|
|
};
|
|
/**
|
|
* Enable edge detection
|
|
*/
|
|
|
|
|
|
EffectCompositor.prototype.enableEdge = function () {
|
|
this._enableEdge = true;
|
|
};
|
|
/**
|
|
* Disable edge detection
|
|
*/
|
|
|
|
|
|
EffectCompositor.prototype.disableEdge = function () {
|
|
this._enableEdge = false;
|
|
};
|
|
/**
|
|
* Set bloom intensity
|
|
* @param {number} value
|
|
*/
|
|
|
|
|
|
EffectCompositor.prototype.setBloomIntensity = function (value) {
|
|
this._compositeNode.setParameter('bloomIntensity', value);
|
|
};
|
|
|
|
EffectCompositor.prototype.setSSAOParameter = function (name, value) {
|
|
switch (name) {
|
|
case 'quality':
|
|
// PENDING
|
|
var kernelSize = {
|
|
low: 6,
|
|
medium: 12,
|
|
high: 32,
|
|
ultra: 62
|
|
}[value] || 12;
|
|
|
|
this._ssaoPass.setParameter('kernelSize', kernelSize);
|
|
|
|
break;
|
|
|
|
case 'radius':
|
|
this._ssaoPass.setParameter(name, value);
|
|
|
|
this._ssaoPass.setParameter('bias', value / 200);
|
|
|
|
break;
|
|
|
|
case 'intensity':
|
|
this._ssaoPass.setParameter(name, value);
|
|
|
|
break;
|
|
|
|
default:
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
console.warn('Unkown SSAO parameter ' + name);
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
EffectCompositor.prototype.setDOFParameter = function (name, value) {
|
|
switch (name) {
|
|
case 'focalDistance':
|
|
case 'focalRange':
|
|
case 'fstop':
|
|
this._cocNode.setParameter(name, value);
|
|
|
|
break;
|
|
|
|
case 'blurRadius':
|
|
for (var i = 0; i < this._dofBlurNodes.length; i++) {
|
|
this._dofBlurNodes[i].setParameter('blurRadius', value);
|
|
}
|
|
|
|
break;
|
|
|
|
case 'quality':
|
|
var kernelSize = {
|
|
low: 4,
|
|
medium: 8,
|
|
high: 16,
|
|
ultra: 32
|
|
}[value] || 8;
|
|
this._dofBlurKernelSize = kernelSize;
|
|
|
|
for (var i = 0; i < this._dofBlurNodes.length; i++) {
|
|
this._dofBlurNodes[i].pass.material.define('POISSON_KERNEL_SIZE', kernelSize);
|
|
}
|
|
|
|
this._dofBlurKernel = new Float32Array(kernelSize * 2);
|
|
break;
|
|
|
|
default:
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
console.warn('Unkown DOF parameter ' + name);
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
EffectCompositor.prototype.setSSRParameter = function (name, value) {
|
|
if (value == null) {
|
|
return;
|
|
}
|
|
|
|
switch (name) {
|
|
case 'quality':
|
|
// PENDING
|
|
var maxIteration = {
|
|
low: 10,
|
|
medium: 15,
|
|
high: 30,
|
|
ultra: 80
|
|
}[value] || 20;
|
|
var pixelStride = {
|
|
low: 32,
|
|
medium: 16,
|
|
high: 8,
|
|
ultra: 4
|
|
}[value] || 16;
|
|
|
|
this._ssrPass.setParameter('maxIteration', maxIteration);
|
|
|
|
this._ssrPass.setParameter('pixelStride', pixelStride);
|
|
|
|
break;
|
|
|
|
case 'maxRoughness':
|
|
this._ssrPass.setParameter('minGlossiness', Math.max(Math.min(1.0 - value, 1.0), 0.0));
|
|
|
|
break;
|
|
|
|
case 'physical':
|
|
this.setPhysicallyCorrectSSR(value);
|
|
break;
|
|
|
|
default:
|
|
console.warn('Unkown SSR parameter ' + name);
|
|
}
|
|
};
|
|
|
|
EffectCompositor.prototype.setPhysicallyCorrectSSR = function (physical) {
|
|
this._ssrPass.setPhysicallyCorrect(physical);
|
|
};
|
|
/**
|
|
* Set color of edge
|
|
*/
|
|
|
|
|
|
EffectCompositor.prototype.setEdgeColor = function (value) {
|
|
var color = graphicGL.parseColor(value);
|
|
|
|
this._edgePass.setParameter('edgeColor', color);
|
|
};
|
|
|
|
EffectCompositor.prototype.setExposure = function (value) {
|
|
this._compositeNode.setParameter('exposure', Math.pow(2, value));
|
|
};
|
|
|
|
EffectCompositor.prototype.setColorLookupTexture = function (image, api) {
|
|
this._compositeNode.pass.material.setTextureImage('lut', this._enableColorCorrection ? image : 'none', api, {
|
|
minFilter: graphicGL.Texture.NEAREST,
|
|
magFilter: graphicGL.Texture.NEAREST,
|
|
flipY: false
|
|
});
|
|
};
|
|
|
|
EffectCompositor.prototype.setColorCorrection = function (type, value) {
|
|
this._compositeNode.setParameter(type, value);
|
|
};
|
|
|
|
EffectCompositor.prototype.isSSREnabled = function () {
|
|
return this._enableSSR;
|
|
};
|
|
|
|
EffectCompositor.prototype.composite = function (renderer, scene, camera, framebuffer, frame) {
|
|
var sourceTexture = this._sourceTexture;
|
|
var targetTexture = sourceTexture;
|
|
|
|
if (this._enableEdge) {
|
|
this._edgePass.update(renderer, camera, sourceTexture, frame);
|
|
|
|
sourceTexture = targetTexture = this._edgePass.getTargetTexture();
|
|
}
|
|
|
|
if (this._enableSSR) {
|
|
this._ssrPass.update(renderer, camera, sourceTexture, frame);
|
|
|
|
targetTexture = this._ssrPass.getTargetTexture();
|
|
|
|
this._ssrPass.setSSAOTexture(this._enableSSAO ? this._ssaoPass.getTargetTexture() : null); // var lights = scene.getLights();
|
|
// for (var i = 0; i < lights.length; i++) {
|
|
// if (lights[i].cubemap) {
|
|
// this._ssrPass.setAmbientCubemap(lights[i].cubemap, lights[i].intensity);
|
|
// }
|
|
// }
|
|
|
|
}
|
|
|
|
this._sourceNode.texture = targetTexture;
|
|
|
|
this._cocNode.setParameter('depth', this._depthTexture);
|
|
|
|
var blurKernel = this._dofBlurKernel;
|
|
var blurKernelSize = this._dofBlurKernelSize;
|
|
var frameAll = Math.floor(poissonKernel.length / 2 / blurKernelSize);
|
|
var kernelOffset = frame % frameAll;
|
|
|
|
for (var i = 0; i < blurKernelSize * 2; i++) {
|
|
blurKernel[i] = poissonKernel[i + kernelOffset * blurKernelSize * 2];
|
|
}
|
|
|
|
for (var i = 0; i < this._dofBlurNodes.length; i++) {
|
|
this._dofBlurNodes[i].setParameter('percent', frame / 30.0);
|
|
|
|
this._dofBlurNodes[i].setParameter('poissonKernel', blurKernel);
|
|
}
|
|
|
|
this._cocNode.setParameter('zNear', camera.near);
|
|
|
|
this._cocNode.setParameter('zFar', camera.far);
|
|
|
|
this._compositor.render(renderer, framebuffer);
|
|
};
|
|
|
|
EffectCompositor.prototype.dispose = function (renderer) {
|
|
this._sourceTexture.dispose(renderer);
|
|
|
|
this._depthTexture.dispose(renderer);
|
|
|
|
this._framebuffer.dispose(renderer);
|
|
|
|
this._compositor.dispose(renderer);
|
|
|
|
this._normalPass.dispose(renderer);
|
|
|
|
this._ssaoPass.dispose(renderer);
|
|
};
|
|
|
|
export default EffectCompositor; |