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

article/2025/10/8 23:25:23

介绍

后期处理是指在最终图像(渲染)上添加效果。人们大多在电影制作中使用这种技术,但我们也可以在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()

http://chatgpt.dhexx.cn/article/AuMvHkOs.shtml

相关文章

JMM--

数据同步的八大原子操作 1.lock 作用于主内存中的变量,把一个变量标记为一条线程的独占状态。 2.unlock 作用于主内存中的变量,把一个处于锁定状态的变量释放出来,释放后的变量才能被其它线程锁定。 3.read 作用于主内存中的变量&#xff0c…

Three.js--》实现3d小岛模型搭建

目录 项目搭建 初始化three.js基础代码 设置环境背景 设置水面样式 添加天空小岛 今天简单实现一个three.js的小Demo,加强自己对three知识的掌握与学习,只有在项目中才能灵活将所学知识运用起来,话不多说直接开始。 项目搭建 本案例还…

JMS简介

jms即Java消息服务(Java Message Service)应用程序接口是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体…

java jsm_JSM 基础

JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多…

JSM

消息中间件 消息中间件利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型,它可以在分布式环境下扩展进程间的通信。对于消息中间件,常见的角色大致也就有Producer&am…

【HLL】使用 HyperLogLog 去重案例

1.概述 HyperLogLog一个常用的场景就是统计网站的UV。 ##基数 简单来说,基数(cardinality,也译作势),是指一个集合(这里的集合允许存在重复元素)中不同元素的个数。例如看下面的集合: {1,2,3,4,5,2,3,9,7} 这个集合有9个元素,但是2和3各出现了两次,因此不重复的元素…

5redis-----------redis高级--GEO-查询附近的人、基数统计算法HLL 、布隆过滤器、缓存雪崩穿透击穿-------全栈式开发44

redis高级 一、GEO查询附近的人二、基数统计算法-HyperLogLog三、布隆过滤器四、缓存雪崩&缓存穿透(一)缓存雪崩(二)缓存穿透(三)缓存击穿 一、GEO查询附近的人 引入 我们所处的任何位置都可以用经纬…

【Hll】Hll HyperLogLog: Cardinality Estimation(基数估计算法源码解析)

1.概述 好文章,转载防丢失 主要是这里有源码,我遇到问题了,问题是flink在累加器中使用的时候,每次累加最终结果是1,2 每次到了2 就会重新回到1,很郁闷于是看看源码 2.背景 我们经常会统计某个字段的dis…

PostgreSQL HLL插件介绍—晟数学院

更多精彩内容:请登录:ke.sandata.com.cn 前言 HLL是 HyperLogLog数据结构的简称。PostgresSQL通过插件的方式引入了这种新的数据类型hll。HyperLogLog是一个具有固定大小,类似于集合结构,用于可调精度的不同值计数。例如,在1280字节的hll数据结构中,它可以在很小的误差…

Redis介绍、优点,缺点、数据类型:字符串、集合、列表、散列、有序集合、HLL、GEO操作

Redis Redis(REmote DIctionary Server)是一个非常流行的基于内存的轻量级键值数据库(key-value database)。与其把Redis称为一种数据库,不如说Redis是一种数据结构服务器更为恰当。Redis原生地在内存中实现了多种类型…

java postgresql插件_PostgreSQL HLL插件介绍

前言 HLL是 HyperLogLog数据结构的简称。PostgresSQL通过插件的方式引入了这种新的数据类型hll。HyperLogLog是一个具有固定大小,类似于集合结构,用于可调精度的不同值计数。例如,在1280字节的hll数据结构中,它可以在很小的误差范…

DataSketches HLL Sketch module

上图是官网的介绍,翻译后的意思是此模块提供Apache Druid聚合器为不同的计数基于HLL sketch来自datasketches数据库。摄入的时候这个聚合器创建HLL sketch对象存储在Druid的segments中。在查询的时候sketches被读取并且被合并到一起。最后默认情况下,你可…

UV 统计- HLL算法(JAVA实现)

HLL是什么 HyperLogLog(HLL)算法经常在数据库中被用来统计某一字段的Distinct Value,比如Redis的HyperLogLog结构。目前在我们项目中用于UV统计。 网上有一篇大佬博文十分深入: https://www.jianshu.com/p/55defda6dcd2 注意&…

HTTP的Referrer Policy

客户端通过设置Referrer Policy来控制是否在请求头中告知服务端请求来源。来源信息写在生成的请求头的referer中。 注意 Referer 实际上是单词 “referrer” 的错误拼写。Referrer-Policy 这个首部并没有延续这个错误拼写。 Referrer Policy的取值: no-referrer 整…

接口基础-HTTP请求中的referrer和Referrer-Policy

本文将介绍一个涉及安全和隐私的http请求头中的字段—referrer,以及如何通过Referrer Policy去修改referrer的值或者是显示与否。 什么是referrer 当一个用户点击当前页面中的一个链接,然后跳转到目标页面时,目标页面会收到一个信息&#x…

Referrer还是Referer? 一个迷人的错误

诗人郑愁予曾经在一首诗中写道:我达达的马蹄是个美丽的错误,我不是归人,是个过客。而对我来说,十九岁之前的我,一样是个沉浸在诗歌中的文艺少年。十九岁之后的我,作为一名程序员,更多的是邂逅各…

HTTP系列之Referer和Referrer policy简介

文章目录 1、前言摘要2、Referer简介3、Referer安全性4、相关术语5、Referrer Policy5.1、no-referrer5.2、no-referrer-when-downgrade5.3、same-origin5.4、origin5.5、strict-origin5.6、origin-when-cross-origin5.7、strict-origin-when-cross-origin5.8、unsafe-url5.9、…

浅析HTTP请求中的referrer和Referrer-Policy

本文将介绍一个涉及安全和隐私的http请求头中的字段—referrer,以及如何通过Referrer Policy去修改referrer的值或者是显示与否。 什么是referrer 当一个用户点击当前页面中的一个链接,然后跳转到目标页面时,目标页面会收到一个信息&#xff…

document.referrer之隐藏来源

document.referrer document.referrer是用来获取跳转链接的来源,正规的解释是:referrer 属性可返回载入当前文档的文档的 URL。 实际中使用在广告相关业务中较多,包括推广等。 举个例子: 比如我们从百度中跳转到w3c,那我们从w3…

java referrer_JavaScript中document.referrer的用法详解

前言 在JavaScript中,document对象有很多属性,其中有3个与对网页的请求有关的属性,它们分别是URL、domain和referrer。 URL属性包含页面完整的URL,domain属性中只包含页面的域名,而referrer属性中则保存着链接到当前页…