EffectComposer源码分析
后处理
后处理(post-processing)是一种在渲染图像到屏幕之前对图像进行处理的技术。在计算机图形学中,后处理通常用于增强图像的质量、添加特效或者进行其他视觉上的改进
THREE.js中后处理
工作方式是你需要创建EffectComposer然后增加一些Pass对象。每一个Pass阶段都可以增加一些后置处理特效,添加模糊,添加光晕,添加噪点,调整色相,饱和度,对比度等等。最终把效果渲染到canvas。
理解EffectComposer是如何工作的是有一点重要的。它创建两个render targets。让我们称他们为renderTargetArtA和renderTargetBrtB
然后你调用EffectComposer.addPass按照你想要应用它们的顺序增加pass。然后它们就被向下图所示的被应用。
首先 你传入
RenderPass的场景被渲染到rtA,不管rtA的内容是啥,它继续向下一个pass传递。下一个pass将它作为输入做一些操作然后将其写入到rtB。然后rtB传到下一个pass,将rtB作为输入作一些操作然后在写回rtA。这个过程在整个pass过程中持续发生。
EffectComposer.js文件分析
import {
Clock,
HalfFloatType,
NoBlending,
Vector2,
WebGLRenderTarget
} from 'three';
import { CopyShader } from '../shaders/CopyShader.js';
import { ShaderPass } from './ShaderPass.js';
import { MaskPass } from './MaskPass.js';
import { ClearMaskPass } from './MaskPass.js';
class EffectComposer {
constructor( renderer, renderTarget ) {
//渲染器
this.renderer = renderer;
//像素比
this._pixelRatio = renderer.getPixelRatio();
/*
*创建composer时没有传入rt,就创建一个,并设置尺寸
*/
if ( renderTarget === undefined ) {
const size = renderer.getSize( new Vector2() );
this._width = size.width;
this._height = size.height;
renderTarget = new WebGLRenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType } );
renderTarget.texture.name = 'EffectComposer.rt1';
} else {
this._width = renderTarget.width;
this._height = renderTarget.height;
}
this.renderTarget1 = renderTarget;//rtA
this.renderTarget2 = renderTarget.clone();//rtB
this.renderTarget2.texture.name = 'EffectComposer.rt2';
this.writeBuffer = this.renderTarget1;//rtA
this.readBuffer = this.renderTarget2;//rtB
this.renderToScreen = true;//如果设置为true,则传递的结果将呈现到屏幕上
this.passes = [];//添加的pass都储存在这里
this.copyPass = new ShaderPass( CopyShader );
this.copyPass.material.blending = NoBlending;
this.clock = new Clock();
}
swapBuffers() {
const tmp = this.readBuffer;
this.readBuffer = this.writeBuffer;
this.writeBuffer = tmp;
}
addPass( pass ) {
this.passes.push( pass );
pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
}
insertPass( pass, index ) {
this.passes.splice( index, 0, pass );
pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
}
removePass( pass ) {
const index = this.passes.indexOf( pass );
if ( index !== - 1 ) {
this.passes.splice( index, 1 );
}
}
//是不是最后一个开启的pass
isLastEnabledPass( passIndex ) {
for ( let i = passIndex + 1; i < this.passes.length; i ++ ) {
if ( this.passes[ i ].enabled ) {
return false;
}
}
return true;
}
//渲染所有的pass
render( deltaTime ) {
// deltaTime value is in seconds
if ( deltaTime === undefined ) {
deltaTime = this.clock.getDelta();
}
const currentRenderTarget = this.renderer.getRenderTarget();
let maskActive = false;
//遍历每个pass
for ( let i = 0, il = this.passes.length; i < il; i ++ ) {
const pass = this.passes[ i ];
if ( pass.enabled === false ) continue; //pass是否启用
pass.renderToScreen = ( this.renderToScreen && this.isLastEnabledPass( i ) );
pass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime, maskActive );
if ( pass.needsSwap ) {//是否交换缓冲区对象 rtA <---> rtB, needsSwap在Pass基类中默认值为true
if ( maskActive ) {//激活遮罩
const context = this.renderer.getContext();
const stencil = this.renderer.state.buffers.stencil;
//context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff );
stencil.setFunc( context.NOTEQUAL, 1, 0xffffffff );
this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime );
//context.stencilFunc( context.EQUAL, 1, 0xffffffff );
stencil.setFunc( context.EQUAL, 1, 0xffffffff );
}
this.swapBuffers();//交换缓冲区
}
if ( MaskPass !== undefined ) {
if ( pass instanceof MaskPass ) {
maskActive = true;
} else if ( pass instanceof ClearMaskPass ) {
maskActive = false;
}
}
}
this.renderer.setRenderTarget( currentRenderTarget );
}
reset( renderTarget ) {
if ( renderTarget === undefined ) {
const size = this.renderer.getSize( new Vector2() );
this._pixelRatio = this.renderer.getPixelRatio();
this._width = size.width;
this._height = size.height;
renderTarget = this.renderTarget1.clone();
renderTarget.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
}
this.renderTarget1.dispose();
this.renderTarget2.dispose();
this.renderTarget1 = renderTarget;
this.renderTarget2 = renderTarget.clone();
this.writeBuffer = this.renderTarget1;
this.readBuffer = this.renderTarget2;
}
setSize( width, height ) {
this._width = width;
this._height = height;
const effectiveWidth = this._width * this._pixelRatio;
const effectiveHeight = this._height * this._pixelRatio;
this.renderTarget1.setSize( effectiveWidth, effectiveHeight );
this.renderTarget2.setSize( effectiveWidth, effectiveHeight );
for ( let i = 0; i < this.passes.length; i ++ ) {
this.passes[ i ].setSize( effectiveWidth, effectiveHeight );
}
}
setPixelRatio( pixelRatio ) {
this._pixelRatio = pixelRatio;
this.setSize( this._width, this._height );
}
dispose() {
this.renderTarget1.dispose();
this.renderTarget2.dispose();
this.copyPass.dispose();
}
}
export { EffectComposer };