介绍

后期处理是指在最终图像(渲染)上添加效果。人们大多在电影制作中使用这种技术,但我们也可以在WebGL中使用。
后期处理可以是略微改善图像或者产生巨大影响效果。
下面链接是Three.js官方文档中一些关于后期处理的示例:
https://threejs.org/docs/index.html?q=po#examples/en/postprocessing/EffectComposer

初设

使用与真实渲染一课相同的设置,但是模型换了。这是一款受欢迎的模型,具有许多细节和良好的纹理,与我们的后期处理非常匹配。

如何工作

大多数情况下,后期处理Post-processing的工作方式是相同的。

Render target渲染目标

我们要在称之为Render target渲染目标的地方进行渲染而不是在画布canvas中渲染,这个Render target会给我们一个与寻常纹理非常相似的纹理,简而言之,我们是在屏幕上在纹理中进行渲染而不是在画布上。
术语Render target为Three.js特定用词,其他地方大多是使用buffer一词。
之后该纹理会应用到面向摄影机并覆盖整个视图的平面,该平面使用具有特殊片元着色器的材质,该材质将实现后期处理效果。如果后处理效果包括使图像变红,则它将仅乘以该片元着色器中像素的红色值。
大多数的后期处理效果只要你有灵感,不仅仅只是调整颜色值而已。
在Three.js中这些效果称为passes通道

Ping-pong buffering乒乓缓冲

在后期处理中,我们可以有多个通道。一个用于运动模糊,一个用于颜色变化,另一个执行景深,等等。正因为我们有多个通道,后期处理需要两个Render target,原因在于我们无法在绘制渲染目标的同时获取其贴图纹理。因此,需要在从第二个渲染目标获取纹理的同时绘制第一个渲染目标,然后在下一个通道,交换这俩个渲染目标,在第二步获取纹理,第一步绘制,然后又到下一个通道,再次交换渲染目标,如此反复。这便是称为乒乓缓冲,两者交替地被读和被写。

画布上的最终效果通道

最终效果通道pass不会位于渲染目标中,因为我们可以将其直接放在画布上,以便用户可以看到最终结果。

结语

所有这些对于初学者来说都是非常复杂的,但很幸运我们不必自己去做。我们所要做的就是使用EffectComposer类,它将为我们处理大部分繁重的工作。

EffectComposer效果合成器

效果合成器EffectComposer会处理创建渲染目标,进行乒乓缓冲将上个通道的纹理发送到当前通道,在画布上绘制最终效果等全部过程。
引入效果合成器:

import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'

同时还需要一个叫RenderPass的通道类,这个通道负责场景的第一次渲染,它会在EffectComposer内部创建的渲染目标中进行渲染,而非画布上:

import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'

现在可以实例化EffectComposer,将渲染器作为参数传进去。
与WebGLRenderer一样,我们需要使用setPixelRatio(…)提供像素比率,并使用setSize(…)调整其大小,下边将使用与渲染器相同的参数:

/*** Post processing*/
const effectComposer = new EffectComposer(renderer)
effectComposer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
effectComposer.setSize(sizes.width, sizes.height)

然后将场景和相机作为参数传给RenderPass,实例化第一个通道,并使用addPass()方法将通道加到效果合成器中:

const renderPass = new RenderPass(scene, camera)
effectComposer.addPass(renderPass)

在tick函数中,使用效果合成器effectComposer来进行渲染,而非以往的渲染器渲染:

const tick = () =>
{// ...// Render// renderer.render(scene, camera)effectComposer.render()// ...
}

通道

因为现在我们只有一个renderPass通道,因此它会跟以往一样直接在画布上进行渲染。
下边会加一些简单的后期处理通道。
(可以在这个文档中找到一系列可用的通道:https://threejs.org/docs/index.html#examples/en/postprocessing/EffectComposer)

DotScreenPass

DotScreenPass将应用某种黑白光栅效果,引入并实例化,添加到效果合成器中,记得要在renderPass后面添加:

import { DotScreenPass } from 'three/examples/jsm/postprocessing/DotScreenPass.js'
// ...
const renderPass = new RenderPass(scene, camera)
effectComposer.addPass(renderPass)const dotScreenPass = new DotScreenPass()
effectComposer.addPass(dotScreenPass)


如果要禁用通道,只要设置通道的enabled属性为false:

const dotScreenPass = new DotScreenPass()
dotScreenPass.enabled = false
effectComposer.addPass(dotScreenPass)

GlitchPass

GlitchPass会添加一种屏幕故障效果,像是在电影中看到的屏幕被入侵时的效果。

import { GlitchPass } from 'three/examples/jsm/postprocessing/GlitchPass.js'// ...const glitchPass = new GlitchPass()
effectComposer.addPass(glitchPass)


一些通道还有可修改属性。像GlitchPassgoWild为true,那么将不间断的持续故障闪烁:

glitchPass.goWild = true

RGBShiftPass

有一些通道需要额外的步骤,像RGBShift通道。
RGBShift没法用作通道但是可以用作着色器,因此我们要引入该着色器RGBShiftShader并将其应用于ShaderPass,然后将着色器通道ShaderPass添加到效果合成器中。
传入RGBShiftShader并实例化ShaderPass,添加到effectComposer:

import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'
import { RGBShiftShader } from 'three/examples/jsm/shaders/RGBShiftShader.js'// ...const rgbShiftPass = new ShaderPass(RGBShiftShader)
effectComposer.addPass(rgbShiftPass)

修复颜色

可以观察到渲染后的颜色变得更暗了,这是因为先前写的下边这个设置渲染器输出编码的代码不再工作了:

 renderer.outputEncoding = THREE.sRGBEncoding

注释掉上边这行代码后程序并没有什么变化。
通道是在渲染目标上进行渲染,而它们用的不是相同的配置。好在效果合成器可以提供我们自己的renderTarget作为第二个参数。
到该路径下查看效果合成器的源码后/node_modules/three/examples/jsm/postprocessing/EffectComposer.js可以发现,渲染目标renderTarget是由带有特定参数的WebGLRenderTarget创建而成的。
前俩个参数是宽和高,这里可以随便给值,因为效果合成器会在调用 setSize(...)方法时重新调整renderTarget的尺寸。
第三个参数是个对象,可以从Three.js效果合成器源码中复制那个对象,然后我们再自己添加一个encoding属性,值为THREE.sRGBEncoding

const renderTarget = new THREE.WebGLRenderTarget(800,600,{minFilter: THREE.LinearFilter,magFilter: THREE.LinearFilter,format: THREE.RGBAFormat,encoding: THREE.sRGBEncoding}
)

之后将 renderTarget添加到效果合成器中:

const effectComposer = new EffectComposer(renderer, renderTarget)

调整尺寸

修复抗锯齿

如果你使用的是像素比大于1的屏幕,你可能看不到现在头盔边缘的锯齿又出现了。
注意,如果只用renderPass,则不会出现锯齿问题,因为渲染是在支持抗锯齿的画布中进行的,所以至少启用一个通道,以便观察锯齿。
之所以出现这种情况是因为WebGLRenderTarget不支持默认的抗锯齿,我们有四种选择:

  • 直接不用抗锯齿
  • 使用一种特定类型的渲染目标renderTarget来管理抗锯齿,但这并不适用于所有现代浏览器。
  • 使用一个通道来做抗锯齿,但是性能较差
  • 结合前两种选择,看浏览器是否支持那个特定类型的渲染目标,不支持则使用通道去做抗锯齿

使用WebGLMultisampleRenderTarget

(注意:教程一直使用的Three.js都为r124版本,而随着Three.js版本更新,WebGLMultisampleRenderTarget在r138版本中已被移除)

WebGLMultisampleRenderTargetWebGLRenderTarget 相似,但是它可以支持多重采样抗锯齿Multi Sample Antialias (MSAA)
我们可以使用WebGLMultisampleRenderTarget来替换 WebGLRenderTarget ,然后可以看到锯齿立即就被消除了:

const renderTarget = new THREE.WebGLMultisampleRenderTarget(// ...
)

但是很遗憾,这对现代浏览器不起作用,这是WebGL2的支持问题,开发者们在几年前更新了WebGL,浏览器也慢慢增加了对不同功能的支持。
点击该链接查看支持情况:https://caniuse.com/webgl2
在Bruno Simon大佬编写课程时,像Safari和iOS Safari等主流浏览器仍然不支持它,如果你在上面这些浏览器测试网站,会得到一个黑屏结果。

使用抗锯齿通道

将渲染目标改回WebGLRenderTarget ,并试着用通道来完成抗锯齿。

const renderTarget = new THREE.WebGLRenderTarget(// ...
)

有不同抗锯齿通道可以选择:

  • FXAA:性能良好,但结果也只是良好,可能会导致模糊
  • SMAA:效果比FXAA好,但同时性能也消耗大(不要与MSAA搞混了)
  • SSAA:质量最好,但性能最差
  • TAA:性能良好但结果有限
  • 其他

这节课程会使用SMAA,引入并实例化SMAAPass,添加到效果合成器中:

import { SMAAPass } from 'three/examples/jsm/postprocessing/SMAAPass.js'// ...const smaaPass = new SMAAPass()
effectComposer.addPass(smaaPass)

可以看到抗锯齿消失

结合两种解决方案

  • 如果屏幕像素比大于1,我们将使用WebGLRenderTarget,不使用抗锯齿通道
  • 如果屏幕像素比为1,并且浏览器支持WebGL 2,使用WebGLMultisampleRenderTarget
  • 如果屏幕像素比为1,并且浏览器不支持WebGL 2,使用WebGLRenderTarget并且采用 SMAAPass

要获取像素比,可以调用渲染器的getPixelRatio() 方法。
要知道浏览器是否支持WebGL 2,可以使用渲染器的capabilities属性,一个包含当前渲染环境(RenderingContext)的功能细节的对象,我们需要的是里边的一个属性isWebGL2

下边先处理渲染目标:

let RenderTargetClass = nullif(renderer.getPixelRatio() === 1 && renderer.capabilities.isWebGL2)
{RenderTargetClass = THREE.WebGLMultisampleRenderTargetconsole.log('Using WebGLMultisampleRenderTarget')
}
else
{RenderTargetClass = THREE.WebGLRenderTargetconsole.log('Using WebGLRenderTarget')
}const renderTarget = new RenderTargetClass(// ...
)

处理通道:

if(renderer.getPixelRatio() === 1 && !renderer.capabilities.isWebGL2)
{const smaaPass = new SMAAPass()effectComposer.addPass(smaaPass)console.log('Using SMAA')
}

UnrealBloomPass

这个通道会添加Bloom敷霜辉光效果到渲染中,它对重现光热、激光、光剑或放射性物质非常有用。

import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass'// ...const unrealBloomPass = new UnrealBloomPass()
effectComposer.addPass(unrealBloomPass)


画面太亮了,需要调整下参数:

  • strength:光的强度
  • radius:亮度的发散半径
  • threshold:限制物体开始发光的亮度值

添加下列代码用以观察:

unrealBloomPass.strength = 0.3
unrealBloomPass.radius = 1
unrealBloomPass.threshold = 0.6gui.add(unrealBloomPass, 'enabled')
gui.add(unrealBloomPass, 'strength').min(0).max(2).step(0.001)
gui.add(unrealBloomPass, 'radius').min(0).max(2).step(0.001)
gui.add(unrealBloomPass, 'threshold').min(0).max(1).step(0.001)

创建自己的通道

着色通道

我们会创建一个最简单的控制颜色的通道。
首先创建一个着色器,有三个属性:

  • uniforms:里边的内容跟以往一样
  • vertexShader:这个顶点着色器几乎总是有相同的代码,会把平面放在视图的前面
  • fragmentShader:片元着色器会处理后期效果
const TintShader = {uniforms:{},vertexShader: `void main(){gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);}`,fragmentShader: `void main(){gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);}`
}

然后创建这个着色器通道,添加到效果合成器中:

const tintPass = new ShaderPass(TintShader)
effectComposer.addPass(tintPass)

屏幕会变成红色,因为片元着色器设置了 gl_FragColor的值为红色

我们需要从上一个通道中获得贴图纹理,这个纹理自动存储在名为 tDiffuse的unifom中。
我们必须将这个unifom的值设为null,效果合成器会更新它,然后在片元着色器检索该uniform:

const TintShader = {uniforms:{tDiffuse: { value: null }},// ...fragmentShader: `uniform sampler2D tDiffuse;void main(){gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);}`
}

现在有了上一个通道的贴图纹理,接下去需要检索像素。
要从sampler2D(一个贴图纹理)中获取像素,需要用 texture2D(...)方法,它接收贴图纹理作为第一个参数,UV坐标作为第二个参数。
像以往一样创建并在片元着色器接收来自顶点着色器的UV坐标变量vUv:

const TintShader = {// ...vertexShader: `varying vec2 vUv;void main(){gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);vUv = uv;}`,fragmentShader: `uniform sampler2D tDiffuse;varying vec2 vUv;void main(){vec4 color = texture2D(tDiffuse, vUv);gl_FragColor = color;}`
}

画面又回来了,但现在我们可以在片元着色器中修改纹理了。

更改下颜色:

const TintShader = {// ...fragmentShader: `uniform sampler2D tDiffuse;varying vec2 vUv;void main(){vec4 color = texture2D(tDiffuse, vUv);color.r += 0.1;gl_FragColor = color;}`
}


同理,可以创建一个名为uTint的uniform来控制颜色变化,在片元着色器中检索并设置:

const TintShader = {uniforms:{tDiffuse: { value: null },uTint: { value: null }},// ...fragmentShader: `uniform sampler2D tDiffuse;uniform vec3 uTint;varying vec2 vUv;void main(){vec4 color = texture2D(tDiffuse, vUv);color.rgb += uTint;gl_FragColor = color;}`
}

注意,我们将该值设为null。
不要直接在着色器对象中设置值,必须在创建完通道后,再去材质中修改值,因为着色器会被多次使用,即便没使用到也一样。

const tintPass = new ShaderPass(TintShader)
tintPass.material.uniforms.uTint.value = new THREE.Vector3()

将该值加到调试面板中:

gui.add(tintPass.material.uniforms.uTint.value, 'x').min(- 1).max(1).step(0.001).name('red')
gui.add(tintPass.material.uniforms.uTint.value, 'y').min(- 1).max(1).step(0.001).name('green')
gui.add(tintPass.material.uniforms.uTint.value, 'z').min(- 1).max(1).step(0.001).name('blue')

位移通道

这次不处理颜色,而是利用UV来产生位移效果。
跟创建着色通道一样,创建一个名为DisplacementShader的着色器,实例化这个着色器通道,添加到效果合成器中:

const DisplacementShader = {uniforms:{tDiffuse: { value: null }},vertexShader: `varying vec2 vUv;void main(){gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);vUv = uv;}`,fragmentShader: `uniform sampler2D tDiffuse;varying vec2 vUv;void main(){vec4 color = texture2D(tDiffuse, vUv);gl_FragColor = color;}`
}const displacementPass = new ShaderPass(DisplacementShader)
effectComposer.addPass(displacementPass)

现在,让我们基于vUv创建一个带扭曲的newUv,在基于x轴的y轴上用了sin函数:

const DisplacementShader = {// ...fragmentShader: `uniform sampler2D tDiffuse;varying vec2 vUv;void main(){vec2 newUv = vec2(vUv.x,vUv.y + sin(vUv.x * 10.0) * 0.1);vec4 color = texture2D(tDiffuse, newUv);gl_FragColor = color;}`
}


下面添加动画效果,为此需要uTime,跟以往一样:

const DisplacementShader = {uniforms:{tDiffuse: { value: null },uTime: { value: null }},// ...fragmentShader: `uniform sampler2D tDiffuse;uniform float uTime;varying vec2 vUv;void main(){vec2 newUv = vec2(vUv.x,vUv.y + sin(vUv.x * 10.0 + uTime) * 0.1);vec4 color = texture2D(tDiffuse, newUv);gl_FragColor = color;}`
}

记得在材质中设置uTime值:

const displacementPass = new ShaderPass(DisplacementShader)
displacementPass.material.uniforms.uTime.value = 0
effectComposer.addPass(displacementPass)

回到动画函数中更新通道:

const clock = new THREE.Clock()const tick = () =>
{const elapsedTime = clock.getElapsedTime()// Update passesdisplacementPass.material.uniforms.uTime.value = elapsedTime// ...
}

蜂巢

我们还可以使用贴图,在/static/textures/interfaceNormalMap.png路径下找到该蜂巢法线贴图。

添加uNormalMap

const DisplacementShader = {uniforms:{// ...uNormalMap: { value: null }},// ...
}

加载完贴图后更新uNormalMap值:

displacementPass.material.uniforms.uNormalMap.value = textureLoader.load('/textures/interfaceNormalMap.png')

更新位移着色器的片元着色器代码:

const DisplacementShader = {// ...fragmentShader: `uniform sampler2D tDiffuse;uniform float uTime;uniform sampler2D uNormalMap;varying vec2 vUv;void main(){vec3 normalColor = texture2D(uNormalMap, vUv).xyz * 2.0 - 1.0;vec2 newUv = vUv + normalColor.xy * 0.1;vec4 color = texture2D(tDiffuse, newUv);vec3 lightDirection = normalize(vec3(- 1.0, 1.0, 0.0));float lightness = clamp(dot(normalColor, lightDirection), 0.0, 1.0);color.rgb += lightness * 2.0;gl_FragColor = color;}`
}

在这里我们不会探讨这段代码做了什么事,因为这不是实现这种效果的正确方式,但依然能看到画面被法线贴图影响后的位移效果。

结尾

可以在官网的例子中搜索后期处理的示例,自己再多尝试其他的后期处理。
https://threejs.org/examples/?q=postprocessing
然后需要记住的是,每次添加通道后将不得不在每一帧上进行渲染,太多通道会导致性能缺陷。

源码

import './style.css'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import * as dat from 'dat.gui'
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'
import { DotScreenPass } from 'three/examples/jsm/postprocessing/DotScreenPass.js'
import { GlitchPass } from 'three/examples/jsm/postprocessing/GlitchPass.js'
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'
import { RGBShiftShader } from 'three/examples/jsm/shaders/RGBShiftShader.js'
import { SMAAPass } from 'three/examples/jsm/postprocessing/SMAAPass.js'
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass'
/*** Base*/
// Debug
const gui = new dat.GUI()// Canvas
const canvas = document.querySelector('canvas.webgl')// Scene
const scene = new THREE.Scene()/*** Loaders*/
const gltfLoader = new GLTFLoader()
const cubeTextureLoader = new THREE.CubeTextureLoader()
const textureLoader = new THREE.TextureLoader()/*** Update all materials*/
const updateAllMaterials = () =>
{scene.traverse((child) =>{if(child instanceof THREE.Mesh && child.material instanceof THREE.MeshStandardMaterial){child.material.envMapIntensity = 5child.material.needsUpdate = truechild.castShadow = truechild.receiveShadow = true}})
}/*** Environment map*/
const environmentMap = cubeTextureLoader.load(['/textures/environmentMaps/0/px.jpg','/textures/environmentMaps/0/nx.jpg','/textures/environmentMaps/0/py.jpg','/textures/environmentMaps/0/ny.jpg','/textures/environmentMaps/0/pz.jpg','/textures/environmentMaps/0/nz.jpg'
])
environmentMap.encoding = THREE.sRGBEncodingscene.background = environmentMap
scene.environment = environmentMap/*** Models*/
gltfLoader.load('/models/DamagedHelmet/glTF/DamagedHelmet.gltf',(gltf) =>{gltf.scene.scale.set(2, 2, 2)gltf.scene.rotation.y = Math.PI * 0.5scene.add(gltf.scene)updateAllMaterials()}
)/*** Lights*/
const directionalLight = new THREE.DirectionalLight('#ffffff', 3)
directionalLight.castShadow = true
directionalLight.shadow.mapSize.set(1024, 1024)
directionalLight.shadow.camera.far = 15
directionalLight.shadow.normalBias = 0.05
directionalLight.position.set(0.25, 3, - 2.25)
scene.add(directionalLight)/*** Sizes*/
const sizes = {width: window.innerWidth,height: window.innerHeight
}window.addEventListener('resize', () =>
{// Update sizessizes.width = window.innerWidthsizes.height = window.innerHeight// Update cameracamera.aspect = sizes.width / sizes.heightcamera.updateProjectionMatrix()// Update rendererrenderer.setSize(sizes.width, sizes.height)renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))// Update effect composereffectComposer.setSize(sizes.width, sizes.height)
})/*** Camera*/
// Base camera
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100)
camera.position.set(4, 1, - 4)
scene.add(camera)// Controls
const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true/*** Renderer*/
const renderer = new THREE.WebGLRenderer({canvas: canvas,antialias: true
})
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFShadowMap
renderer.physicallyCorrectLights = true
// renderer.outputEncoding = THREE.sRGBEncoding
renderer.toneMapping = THREE.ReinhardToneMapping
renderer.toneMappingExposure = 1.5
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))let RenderTargetClass = nullif(renderer.getPixelRatio() === 1 && renderer.capabilities.isWebGL2)
{RenderTargetClass = THREE.WebGLMultisampleRenderTargetconsole.log('Using WebGLMultisampleRenderTarget')
}
else
{RenderTargetClass = THREE.WebGLRenderTargetconsole.log('Using WebGLRenderTarget')
}const renderTarget = new RenderTargetClass(800,600,{minFilter: THREE.LinearFilter,magFilter: THREE.LinearFilter,format: THREE.RGBAFormat,encoding: THREE.sRGBEncoding}
)const TintShader = {uniforms:{tDiffuse: { value: null },uTint: { value: null }},vertexShader: `varying vec2 vUv;void main(){gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);vUv = uv;}`,fragmentShader: `uniform sampler2D tDiffuse;uniform vec3 uTint;varying vec2 vUv;void main(){vec4 color = texture2D(tDiffuse, vUv);color.rgb += uTint;gl_FragColor = color;}`
}const DisplacementShader = {uniforms:{tDiffuse: { value: null },uTime: { value: null },uNormalMap: { value: null }},vertexShader: `varying vec2 vUv;void main(){gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);vUv = uv;}`,fragmentShader: `uniform sampler2D tDiffuse;uniform float uTime;uniform sampler2D uNormalMap;varying vec2 vUv;void main(){vec3 normalColor = texture2D(uNormalMap, vUv).xyz * 2.0 - 1.0;vec2 newUv = vUv + normalColor.xy * 0.1;vec4 color = texture2D(tDiffuse, newUv);vec3 lightDirection = normalize(vec3(- 1.0, 1.0, 0.0));float lightness = clamp(dot(normalColor, lightDirection), 0.0, 1.0);color.rgb += lightness * 2.0;gl_FragColor = color;}`
}/*** Post processing*/
// 效果合成器const effectComposer = new EffectComposer(renderer,renderTarget)effectComposer.setPixelRatio(Math.min(window.devicePixelRatio, 2))effectComposer.setSize(sizes.width, sizes.height)if(renderer.getPixelRatio() === 1 && !renderer.capabilities.isWebGL2){const smaaPass = new SMAAPass()effectComposer.addPass(smaaPass)console.log('Using SMAA')}const renderPass = new RenderPass(scene, camera)
effectComposer.addPass(renderPass)const dotScreenPass = new DotScreenPass()
dotScreenPass.enabled = false
effectComposer.addPass(dotScreenPass)const glitchPass = new GlitchPass()
// glitchPass.goWild = true  // 开启持续不间断的故障闪烁
glitchPass.enabled = false
effectComposer.addPass(glitchPass)const rgbShiftPass = new ShaderPass(RGBShiftShader)
// effectComposer.addPass(rgbShiftPass)const smaaPass = new SMAAPass()
effectComposer.addPass(smaaPass)const unrealBloomPass = new UnrealBloomPass()
// effectComposer.addPass(unrealBloomPass)unrealBloomPass.strength = 0.3
unrealBloomPass.radius = 1
unrealBloomPass.threshold = 0.6gui.add(unrealBloomPass, 'enabled')
gui.add(unrealBloomPass, 'strength').min(0).max(2).step(0.001)
gui.add(unrealBloomPass, 'radius').min(0).max(2).step(0.001)
gui.add(unrealBloomPass, 'threshold').min(0).max(1).step(0.001)// 自定义着色通道
const tintPass = new ShaderPass(TintShader)
tintPass.material.uniforms.uTint.value = new THREE.Vector3()
effectComposer.addPass(tintPass)
gui.add(tintPass.material.uniforms.uTint.value, 'x').min(- 1).max(1).step(0.001).name('red')
gui.add(tintPass.material.uniforms.uTint.value, 'y').min(- 1).max(1).step(0.001).name('green')
gui.add(tintPass.material.uniforms.uTint.value, 'z').min(- 1).max(1).step(0.001).name('blue')// 自定义位移通道
const displacementPass = new ShaderPass(DisplacementShader)
displacementPass.material.uniforms.uTime.value = 0
displacementPass.material.uniforms.uNormalMap.value = textureLoader.load('/textures/interfaceNormalMap.png')
effectComposer.addPass(displacementPass)
/*** Animate*/
const clock = new THREE.Clock()const tick = () =>
{const elapsedTime = clock.getElapsedTime()// Update passesdisplacementPass.material.uniforms.uTime.value = elapsedTime// Update controlscontrols.update()// Render// renderer.render(scene, camera)effectComposer.render()// Call tick again on the next framewindow.requestAnimationFrame(tick)
}tick()

three.js学习笔记(十九)——后期处理相关推荐

  1. Polyworks脚本开发学习笔记(十九)-将数据对象与参考对象对齐的方法

    Polyworks脚本开发学习笔记(十九)-将数据对象与参考对象对齐的方法 把开发手册理了一遍,发现还有几个点没有记录下来,其中一个就是使用点对的粗对齐和使用参考目标的精确对齐.为了把这个学习笔记凑够 ...

  2. JS学习笔记(九)深拷贝浅拷贝【Array、Object为例】

    JS学习笔记(九) 本系列更多文章,可以查看专栏 JS学习笔记 文章目录 JS学习笔记(九) 一.赋值&复制 二.浅拷贝(shallow copy) 1. 什么是浅拷贝 2. 数组的浅拷贝 ( ...

  3. Mr.J-- jQuery学习笔记(十九)--自定义动画实现图标特效

    之前有写过自定义动画Mr.J-- jQuery学习笔记(十八)--自定义动画 这次实现一个小demo 图标特效 页面渲染 <!DOCTYPE html> <html lang=&qu ...

  4. Vue.js 学习笔记 十 自定义按键事件

    <div id="divApp"><!--任何键盘动作都会触发--><input type="text" v-on:keyup=& ...

  5. Vue.js 学习笔记 十二 Vue发起Ajax请求

    首先需要导入vue-resource.js,可以自己下载引入,也可以通过Nuget下载,它依赖于Vue.js. 全局使用方式: Vue.http.get(url,[options]).then(suc ...

  6. three.js学习笔记(十八)——调整材质

    介绍 到现在为止,我们都在创建新的着色器材质,但是如果我们想要修改一个Three.js内置的材质呢?或许我们对MeshStandardMaterial的处理结果感到满意,但是希望往里边添加顶点动画. ...

  7. JS学习笔记十——时间常用方法

    前言 时间本身是 JS 中的一个数据类型 Date,是一种引用数据类型,其创建方式有两种:一是 new Date(),创建时间对象,且为当前终端的时间,即电脑时间:二是 new Date(年,月,日, ...

  8. Vue.js 学习笔记十二:Vue CLI 之创建一个项目

    目录 创建一个项目 创建一个项目 运行以下命令来创建一个新项目: vue create vuecli-demo 你会被提示选取一个 preset.你可以选默认的包含了基本的 Babel + ESLin ...

  9. three.js学习笔记(十)——物理引擎

    我们可以利用数学函数和一些解决方案像RayCaster来实现自己的物理效果,但是如果需求更加真实的物理效果,像是物体张力.摩擦力.拉伸.反弹等真实物理效果,最好使用外部库 原理 我们会创建一个Thre ...

最新文章

  1. 有符号整型的数据范围为什么负数比正数多一个?
  2. IOS 笔试题(二)
  3. 南开计算机和国立清华大学,同样来自交通大学,西安交通大学和上海交通大学,为何后来差距这么大?...
  4. Hystrix能解决的问题
  5. 三款免费的PHP加速器:APC、eAccelerator、XCache比较
  6. c/c++教程 - 2.4.2.7~8 类对象作为类成员,static静态成员变量函数
  7. sublime text3创建文件时生成头部注释
  8. vue直播rtmp流
  9. 和丰钢结构企业erp管理软件
  10. 全球及中国ISO刀柄行业产销需求及消费策略调研报告2022版
  11. 【HDR学习】HDR视频相关知识讲解(一)
  12. python绘制直方图
  13. 100BASE-T1 /1000BASE-T1 车载以太网转换器产品汇总
  14. AWS IOT C++ SDK 使用
  15. Tomcat部署war程序
  16. Unity做360度全景预览,效果类似pano2vr导出的效果或720云做的效果
  17. android 人生日历,人生日历Android版 功能初体验
  18. pytorch的expand_as和expand
  19. 使用Tengine+Lua+GraphicsMagick实现图片自动裁剪缩放
  20. 用python写pdf_用 Python 写了一个 PDF 转换器,以后再也不用花钱转了

热门文章

  1. 海洋工作室——网站建设专家:【原】2009.NET年技术大会总结,有图片,说说我理解的技术大会【上】...
  2. 关于Keyhole和Google Maps(三)
  3. 达梦8之管理工具超实用小技巧
  4. Elasticsearch实战 | match_phrase搜不出来,怎么办?
  5. ecshop和Ucenter 通信失败终极解决方法!(附带php5.3以上,出现其他问题解决方法)
  6. 微信公众账号使用获取token次数到达上限reach max api daily quota limit rid: 5fd6fd6a-49fcc7d4-65df845f
  7. 低级程序员和高级程序员的区别在于?
  8. 2019java面试3年_Java回顾#3 – 2019年对于社区而言最重要的事情
  9. 取名大师App技术支持
  10. Hive 程序内存溢出错误分析