three.js学习笔记(十九)——后期处理
介绍
后期处理是指在最终图像(渲染)上添加效果。人们大多在电影制作中使用这种技术,但我们也可以在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'
/*** 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)
一些通道还有可修改属性。像GlitchPass
的goWild
为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版本中已被移除)
WebGLMultisampleRenderTarget
跟 WebGLRenderTarget
相似,但是它可以支持多重采样抗锯齿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学习笔记(十九)——后期处理相关推荐
- Polyworks脚本开发学习笔记(十九)-将数据对象与参考对象对齐的方法
Polyworks脚本开发学习笔记(十九)-将数据对象与参考对象对齐的方法 把开发手册理了一遍,发现还有几个点没有记录下来,其中一个就是使用点对的粗对齐和使用参考目标的精确对齐.为了把这个学习笔记凑够 ...
- JS学习笔记(九)深拷贝浅拷贝【Array、Object为例】
JS学习笔记(九) 本系列更多文章,可以查看专栏 JS学习笔记 文章目录 JS学习笔记(九) 一.赋值&复制 二.浅拷贝(shallow copy) 1. 什么是浅拷贝 2. 数组的浅拷贝 ( ...
- Mr.J-- jQuery学习笔记(十九)--自定义动画实现图标特效
之前有写过自定义动画Mr.J-- jQuery学习笔记(十八)--自定义动画 这次实现一个小demo 图标特效 页面渲染 <!DOCTYPE html> <html lang=&qu ...
- Vue.js 学习笔记 十 自定义按键事件
<div id="divApp"><!--任何键盘动作都会触发--><input type="text" v-on:keyup=& ...
- Vue.js 学习笔记 十二 Vue发起Ajax请求
首先需要导入vue-resource.js,可以自己下载引入,也可以通过Nuget下载,它依赖于Vue.js. 全局使用方式: Vue.http.get(url,[options]).then(suc ...
- three.js学习笔记(十八)——调整材质
介绍 到现在为止,我们都在创建新的着色器材质,但是如果我们想要修改一个Three.js内置的材质呢?或许我们对MeshStandardMaterial的处理结果感到满意,但是希望往里边添加顶点动画. ...
- JS学习笔记十——时间常用方法
前言 时间本身是 JS 中的一个数据类型 Date,是一种引用数据类型,其创建方式有两种:一是 new Date(),创建时间对象,且为当前终端的时间,即电脑时间:二是 new Date(年,月,日, ...
- Vue.js 学习笔记十二:Vue CLI 之创建一个项目
目录 创建一个项目 创建一个项目 运行以下命令来创建一个新项目: vue create vuecli-demo 你会被提示选取一个 preset.你可以选默认的包含了基本的 Babel + ESLin ...
- three.js学习笔记(十)——物理引擎
我们可以利用数学函数和一些解决方案像RayCaster来实现自己的物理效果,但是如果需求更加真实的物理效果,像是物体张力.摩擦力.拉伸.反弹等真实物理效果,最好使用外部库 原理 我们会创建一个Thre ...
最新文章
- 有符号整型的数据范围为什么负数比正数多一个?
- IOS 笔试题(二)
- 南开计算机和国立清华大学,同样来自交通大学,西安交通大学和上海交通大学,为何后来差距这么大?...
- Hystrix能解决的问题
- 三款免费的PHP加速器:APC、eAccelerator、XCache比较
- c/c++教程 - 2.4.2.7~8 类对象作为类成员,static静态成员变量函数
- sublime text3创建文件时生成头部注释
- vue直播rtmp流
- 和丰钢结构企业erp管理软件
- 全球及中国ISO刀柄行业产销需求及消费策略调研报告2022版
- 【HDR学习】HDR视频相关知识讲解(一)
- python绘制直方图
- 100BASE-T1 /1000BASE-T1 车载以太网转换器产品汇总
- AWS IOT C++ SDK 使用
- Tomcat部署war程序
- Unity做360度全景预览,效果类似pano2vr导出的效果或720云做的效果
- android 人生日历,人生日历Android版 功能初体验
- pytorch的expand_as和expand
- 使用Tengine+Lua+GraphicsMagick实现图片自动裁剪缩放
- 用python写pdf_用 Python 写了一个 PDF 转换器,以后再也不用花钱转了
热门文章
- 海洋工作室——网站建设专家:【原】2009.NET年技术大会总结,有图片,说说我理解的技术大会【上】...
- 关于Keyhole和Google Maps(三)
- 达梦8之管理工具超实用小技巧
- Elasticsearch实战 | match_phrase搜不出来,怎么办?
- ecshop和Ucenter 通信失败终极解决方法!(附带php5.3以上,出现其他问题解决方法)
- 微信公众账号使用获取token次数到达上限reach max api daily quota limit rid: 5fd6fd6a-49fcc7d4-65df845f
- 低级程序员和高级程序员的区别在于?
- 2019java面试3年_Java回顾#3 – 2019年对于社区而言最重要的事情
- 取名大师App技术支持
- Hive 程序内存溢出错误分析