十八、Three.js后期处理与自定义着色器
在前边章节中,我们已经讲完了three.js的常用功能,今天我们来讲解一个主要功能——渲染后期处理,接下来我们会对这个功能进行介绍,除此之外,我们也会介绍着色器。
我们将要讲解以下知识:
- 配置three.js,用于后期处理。
- 介绍three.js的基本后期处理通道。
- 使用掩码将效果应用到部分场景。
- 模糊效果和高级滤镜。
- 简单的色器。
1. 配置three.js以进行后期处理
为了可以使three.js可以进行后期处理,我们将要对以下配置进行修改:
- 创建EffectComposer(效果组合器)对象,在该对象上我们可以添加后期处理通道。
- 配置EffectComposer对象,使它可以渲染场景,并应用后期处理。
- 在渲染循环中,使用 EffectComposer 来渲染场景、应用通道和输出结果。
在开发前,我们需要保证加载以下依赖:
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer'
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass'
- EffectComposer 文件用于提供 EffectComposer 对象,以便添加后期处理步骤。
- RenderPass 文件用于在EffectComposer 对象上添加渲染通道,如果没有通道,我们的场景就不会被渲染。
首先,我们通过一个案例 —— 模拟电视屏幕效果,来认识EffectComposer 。
我们先引入文件,该文件是电视效果渲染通道:
import { FilmPass } from 'three/examples/jsm/postprocessing/FilmPass'
首先我们需要创建一个EffectComposer 对象,你可以在对象的构造函数里传入THREE.WebGLRenderer。
// 定义渲染器
const renderer = new THREE.WebGLRenderer({
'antialias':true,
'alpha':true
// 创建 EffectComposer 对象
const composer = new EffectComposer(renderer)
接下来,我们要在组合器中添加各种通道。
每个通道都会按照加入EffectComposer的顺序来执行,我们将第一个通道RenderPass 加入,下面这个通道会渲染场景,但是不会输出到屏幕。
// 创建 EffectComposer 对象
const composer = new EffectComposer(renderer)
// 加入RenderPass通道
const renderPass = new RenderPass(scene, camera)
composer.addPass(renderPass)
如上所示,调用addPass就可以把RenderPass添加到EffectComposer上。接下来,添加一个可以把结果输出到屏幕上的通道,当然,并不是所有通道都能实现这个功能,本案例我们将FilmPass通道来将结果输出到屏幕上。我们先创建该对象,然后添加到组合器中。
// 创建 FilmPass 对象通道,并添加到组合器
const effectFilm = new FilmPass(0.8, 0.325, 256, false)
effectFilm.renderToScreen = true
composer.addPass(effectFilm)
如你所见,我们在创建FilmPass时,设置了renderToScreen 为true,所以,当我们将这个通道添加到组合器中时,场景会被渲染并通过FilmPass将结果输出到屏幕。
接下来,我们创建一个物体,展示到场景中。
// 添加一个地球
function createSphere (material) {
const sphereGeometry = new THREE.SphereGeometry(20, 100, 100)
const mesh = new THREE.Mesh(sphereGeometry, material)
return mesh
const textureLoader = new THREE.TextureLoader()
const cubeMaterial = new THREE.MeshLambertMaterial({
'map': textureLoader.load('img/textures/earth/Earth.png'),
'normalMap': textureLoader.load('img/textures/earth/EarthNormal.png')
const sphere = createSphere(cubeMaterial)
scene.add(sphere)
最后,我们更新渲染循环。
...
const clock = new THREE.Clock()
// 帧率动画
function renderScence () {
const delta = clock.getDelta()
sphere.rotation.y += 0.01
trackballControls.update(clock)
requestAnimationFrame(renderScence)
// renderer.render(scene, camera)
composer.render(delta)
renderScence()
2. 后期处理通道
Three.j提供了许多后期处理通道,这些通道可以直接添加到EffectComposer组合器中。
新版three.js中,通道对象文件都在该目录下:three/examples/jsm/postprocessing/
例如:
import { AdaptiveToneMappingPass } from 'three/examples/jsm/postprocessing/AdaptiveToneMappingPass'
接下来,我会列出three.js提供的所有处理通道:
名称 | 描述 |
---|---|
AdaptiveToneMappingPass | 该通道可以根据场景的光照度自动调节场景的亮度。 |
BloomPass | 该通道通过增强场景中明亮的区域来模拟真实世界中的摄像机。 |
BokehPass | 该通道可以实现类似大光圈镜头的景深效果。 |
ClearPass | 该通道清空当前纹理缓存。 |
CubeTexturePass | 用于渲染天空盒。 |
DotScreenPass | 将黑点图层应用于屏幕的原始图片上。 |
FilmPass | 通过扫描线和失真来模拟电视屏幕效果。 |
GlitchPass | 随机在屏幕上显示电脉冲。 |
HalftonePass | 用于模拟传统印刷术的半色调效果。 |
MaskPass | 在图片上显示掩码,后续通道只会影响到掩码区域。 |
OutlinePass | 勾勒出场景中的物体轮廓。 |
RenderPass | 在当前场景和摄像机的基础上渲染出一个新场景。 |
SAOPass | 实现实时环境光遮挡效果。 |
SMAAPass | 全屏反锯齿效果。 |
SSAARenderPass | 使用另一种算法实现全屏反锯齿效果。 |
SSAOPass | 使用另一种算法实现实时环境光遮挡效果 |
SavePass | 该通道执行时会赋值当前渲染结果,在后续步骤中可以使用。实际应用中用处不大。 |
ShaderPass | 自定义着色器通道,可以传入自定义着色器作为参数,以生成一个高级、自定义的后期处理通道。 |
TAARenderPass | 也是一个全屏反锯齿效果。 |
TexturePass | 将合成器的当前状态保存为纹理,然后将其作为参数传入到其他的EffectComposer组合器中。 |
UnrealBloomPass | 该通道与 Bloom 类似,但是它实现的效果更接近于Unreal3D引擎的Bloom效果。 |
接下来,我们会详细介绍那些常用通道的使用方法。
2.1 简单的后期处理通道
我们先来看看使用 FilmPass、BloomPass、DotScreenPass可以做什么。
在以上案例中,我们展示了四个场景,这四个场景分别使用了四种不同的处理通道。左上角使用的是FilmPass,右上角使用的是DotScreenPass,左下角使用的是BloomPass,右下角使用原始渲染结果。
这个示例中,我们使用了ShaderPass和TexturePass,这两个通道可以将原始渲染结果输出作为其它三个场景的输入,以此来重用原始渲染结果。所以在看各个通道前,我们先来看看这两个通道:
const renderPass = new RenderPass(scene, camera)
const effectCopy = new ShaderPass(CopyShader)
effectCopy.renderToScreen = true
const composer = new EffectComposer(renderer)
composer.addPass(renderPass)
composer.addPass(effectCopy)
const renderedScene = new TexturePass(composer.renderTarget2)
这段代码我们创建了EffectComposer对象,该对象输出默认场景(右下角原始场景)。这个组合器有两个通道:RenderPass(用来渲染场景)和ShaderPass(使用CopyShader进行配置后,如果renderToScreen 属性设置为true时,渲染结果会直接输出到屏幕上而不会有进一步的后期处理)。
我们创建一个TexturePass,将其传入到composer.renderTarget2中,这样就可以将renderedScene 作为变量传入其它组合器的输入,而不用从零开始渲染场景。
FilmPass创建类似电视的效果
// 电视效果
const effectFilm = new FilmPass(0.8, 0.325, 256, false)
effectFilm.renderToScreen = true
const effectFilmComposer = new EffectComposer(renderer)
// 创建新场景
effectFilmComposer.addPass(renderedScene)
// 添加FilmPass通道
effectFilmComposer.addPass(effectFilm)
// 使用effectCopy输出渲染
effectFilmComposer.addPass(effectCopy)
FilmPass接受如下参数:
- noiseIntensity 控制场景粗糙程度
- scanlinesIntensity 扫描线的显著程度
- scanLinesCount 扫描线对的数量
- grayscale 是否为灰度图
这些参数可以直接通过构造函数进行传递,当然你也可以直接设置它们:
effectFilm.uniforms.grayscale.value = true
BloomPass在场景添加泛光效果
当使用泛光效果时,场景中的明亮区域会变得更加显著,而且还会渗入到较暗的区域。
// 泛光效果
const bloomPass = new BloomPass()
const bloomComposer = new EffectComposer(renderer)
bloomComposer.addPass(renderedScene)
bloomComposer.addPass(bloomPass)
bloomComposer.addPass(effectCopy)
BloomPass所有可设置的属性如下:
- Strength 定义泛光强度。
- kernelSize 泛光效果偏移量。
- sigma 泛光效果锐利程度。
- Resolution 泛光效果精确度。
DotScreenPass将场景作为点集输出
// 将场景作为点集输出
const dotScreenPass = new DotScreenPass()
const dotScreeComposer = new EffectComposer(renderer)
dotScreeComposer.addPass(renderedScene)
dotScreeComposer.addPass(dotScreenPass)
dotScreeComposer.addPass(effectCopy)
DotScreenPass可以配置的属性如下:
- center 微调偏移量
- angle 改变对齐方式。
- Scale 设置所用点的大小,值越小,点越大。
在同一场景显示多个渲染器的输出结果
// 帧率动画
const width = window.innerWidth
const height = window.innerHeight
const halfWidth = width / 2
const halfHeight = height / 2
renderer.autoClear = false
function renderScence () {
const delta = clock.getDelta()
sphere.rotation.y += 0.01
trackballControls.update(clock)
renderer.clear()
renderer.setViewport(0, 0, halfWidth, halfHeight)
effectFilmComposer.render(delta)
renderer.setViewport(0, halfHeight, halfWidth, halfHeight)
bloomComposer.render(delta)
renderer.setViewport(halfWidth, 0, halfWidth, halfHeight)
dotScreeComposer.render(delta)
renderer.setViewport(halfWidth, halfHeight, halfWidth, halfHeight)
composer.render(delta)
requestAnimationFrame(renderScence)
renderer.render(scene, camera)
renderScence()
renderer.autoClear设置为false并显示调用clear()方法,将不会每次在循环调用组合器的render方法时,会清空场景,而是只在循环开始时清空所有东西。
为了避免所有效果组合器在同一个地方渲染,我们将效果组合器所用的Renderer的视图区域设置成屏幕的不同部分。
完整代码如下:
import * as THREE from 'three'
// 用于鼠标移动镜头
import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls'
// 用于后期处理的依赖
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer'
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass'
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass'
import { CopyShader } from 'three/examples/jsm/shaders/CopyShader'
import { FilmPass } from 'three/examples/jsm/postprocessing/FilmPass'
import { TexturePass } from 'three/examples/jsm/postprocessing/TexturePass'
import { BloomPass } from 'three/examples/jsm/postprocessing/BloomPass'
import { DotScreenPass } from 'three/examples/jsm/postprocessing/DotScreenPass'
// 定义场景
const scene = new THREE.Scene()
// 创建一个粗细为20的坐标轴
const axes = new THREE.AxesHelper(100)
scene.add(axes)
// 定义摄像机
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight)
camera.position.set(-50, 40, 50)
camera.lookAt(scene.position)
// 定义渲染器
const renderer = new THREE.WebGLRenderer({
'antialias':true,
'alpha':true
// 定义场景的颜色为黑色
renderer.setClearColor(0x000000)
// 定义场景大小为整个窗口
renderer.setSize(window.innerWidth, window.innerHeight)
// 通道
const renderPass = new RenderPass(scene, camera)
const effectCopy = new ShaderPass(CopyShader)
effectCopy.renderToScreen = true
const composer = new EffectComposer(renderer)
composer.addPass(renderPass)
composer.addPass(effectCopy)
const renderedScene = new TexturePass(composer.renderTarget2)
// 电视效果
const effectFilm = new FilmPass(0.8, 0.325, 256, false)
effectFilm.renderToScreen = true
const effectFilmComposer = new EffectComposer(renderer)
// 创建新场景
effectFilmComposer.addPass(renderedScene)
// 添加FilmPass通道
effectFilmComposer.addPass(effectFilm)
// 使用effectCopy输出渲染
effectFilmComposer.addPass(effectCopy)
// 泛光效果
const bloomPass = new BloomPass()
const bloomComposer = new EffectComposer(renderer)
bloomComposer.addPass(renderedScene)
bloomComposer.addPass(bloomPass)
bloomComposer.addPass(effectCopy)
// 将场景作为点集输出
const dotScreenPass = new DotScreenPass()
const dotScreeComposer = new EffectComposer(renderer)
dotScreeComposer.addPass(renderedScene)
dotScreeComposer.addPass(dotScreenPass)
dotScreeComposer.addPass(effectCopy)
// 将渲染的结果加入到div中
document.querySelector('#webgl-box').appendChild(renderer.domElement)
// 实现鼠标移动摄像头
const trackballControls = new TrackballControls(camera, renderer.domElement)
const clock = new THREE.Clock()
// 聚光灯
const spotLight = new THREE.SpotLight(0xFFFFFF, 1.2, 150, 120)
spotLight.position.set(-40, 60, -10)
spotLight.castShadow = true
scene.add(spotLight)
const ambientLight = new THREE.AmbientLight(0x3c3c3c)
scene.add(ambientLight)
// 添加一个地球
function createSphere (material) {
const sphereGeometry = new THREE.SphereGeometry(20, 100, 100)
const mesh = new THREE.Mesh(sphereGeometry, material)
return mesh
const textureLoader = new THREE.TextureLoader()
const cubeMaterial = new THREE.MeshLambertMaterial({
'map': textureLoader.load('img/textures/earth/Earth.png'),
'normalMap': textureLoader.load('img/textures/earth/EarthNormal.png')
const sphere = createSphere(cubeMaterial)
scene.add(sphere)
// 帧率动画
const width = window.innerWidth
const height = window.innerHeight
const halfWidth = width / 2
const halfHeight = height / 2
renderer.autoClear = false
function renderScence () {
const delta = clock.getDelta()
sphere.rotation.y += 0.01
trackballControls.update(clock)
renderer.clear()
renderer.setViewport(0, 0, halfWidth, halfHeight)
effectFilmComposer.render(delta)
renderer.setViewport(0, halfHeight, halfWidth, halfHeight)
bloomComposer.render(delta)
renderer.setViewport(halfWidth, 0, halfWidth, halfHeight)
dotScreeComposer.render(delta)
renderer.setViewport(halfWidth, halfHeight, halfWidth, halfHeight)
composer.render(delta)
requestAnimationFrame(renderScence)
renderer.render(scene, camera)
renderScence()
// 窗口大小改变后,改变摄像头
const onResize = () => {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
window.addEventListener('resize', onResize, false)
更多后期处理通道,请自己探索研究~
2.2 使用掩码的高级效果组合
在前边示例中,我们是对整个屏幕上应用后期处理通道,当然,我们也可以在特定的区域使用通道,我们接下来会采用如下步骤制作一个案例:
- 创建一个作为背景图片的场景。
- 创建一个场景,里边包含一个“地球”球体。
- 创建一个场景, 里边包含一个“火星”球体。
- 创建 EffectComposer 对象,用于将这三个场景渲染到一个图片中。
- 在渲染火星的球体上应用泛光效果。
- 在渲染为地球的球体上应用电视效果。
效果图如下:
我们讲解一下核心代码:
首先,我们需要配置各个需要被渲染的场景。
// 配置各个需要渲染的场景
const sceneEarth = new THREE.Scene() // 地球
const sceneMars = new THREE.Scene() // 火星
const sceneBG = new THREE.Scene() // 背景
接下来创建两个函数,分别用来生成地球和火星网格图形,并分别为它们添加各自的光源。
// 添加光源
function addLight (scene) {
const spotLight = new THREE.SpotLight(0xFFFFFF, 1.2, 150, 120)
spotLight.position.set(-10, 30, 40)
scene.add(spotLight)
const ambientLight = new THREE.AmbientLight(0x3c3c3c)
scene.add(ambientLight)
// 创建地球
function addEarth (scene) {
const textureLoader = new THREE.TextureLoader()
const cubeMaterial = new THREE.MeshLambertMaterial({
'map': textureLoader.load('img/textures/earth/Earth.png'),
'normalMap': textureLoader.load('img/textures/earth/EarthNormal.png')
const sphereGeometry = new THREE.SphereGeometry(20, 100, 100)
const mesh = new THREE.Mesh(sphereGeometry, cubeMaterial)
scene.add(mesh)
// 新建一个场景,用来存放可以自由控制的光源
const pivot = new THREE.Object3D()
addLight(pivot)
scene.add(pivot)
return {'earth': mesh, 'pivot': pivot}
// 创建火星
function addMars (scene) {
const textureLoader = new THREE.TextureLoader()
const cubeMaterial = new THREE.MeshLambertMaterial({
'map': textureLoader.load('img/textures/mars/mars_1k_color.jpg'),
'normalMap': textureLoader.load('img/textures/mars/mars_1k_normal.jpg')
const sphereGeometry = new THREE.SphereGeometry(20, 100, 100)
const mesh = new THREE.Mesh(sphereGeometry, cubeMaterial)
scene.add(mesh)
// 新建一个场景,用来存放可以自由控制的光源
const pivot = new THREE.Object3D()
addLight(pivot)
scene.add(pivot)
return {'mars': mesh, 'pivot': pivot}
创建场景。
// 添加背景
const textureLoader = new THREE.TextureLoader()
sceneBG.background = textureLoader.load('img/textures/bg/starry-deep-outer-space-galaxy.jpg')
// 创建场景
const earthAndLight = addEarth(sceneEarth)
sceneEarth.translateX(-16)
sceneEarth.scale.set(1.2, 1.2, 1.2)
const marsAndLight = addMars(sceneMars)
sceneMars.translateX(12)
sceneMars.translateY(6)
sceneMars.scale.set(0.2, 0.2, 0.2)
创建通道及掩码通道。
const bgRenderPass = new RenderPass(sceneBG, camera)
const earthRenderPass = new RenderPass(sceneEarth, camera)
earthRenderPass.clear = false
const marsRenderPass = new RenderPass(sceneMars, camera)
marsRenderPass.clear = false
// setup the mask
const clearMask = new ClearMaskPass()
const earthMask = new MaskPass(sceneEarth, camera)
const marsMask = new MaskPass(sceneMars, camera)
创建电视效果和泛光效果渲染通道。
// 电视效果
const effectFilm = new FilmPass(0.8, 0.325, 256, false)
effectFilm.renderToScreen = true
// 泛光效果
const bloomPass = new BloomPass()
const effectCopy = new ShaderPass(CopyShader)
effectCopy.renderToScreen = true
定义EffectComposer对象,注意 stencilBuffer 设置为 true。然后添加渲染通道到EffectComposer对象。
const composer = new EffectComposer(renderer)
composer.renderTarget1.stencilBuffer = true
composer.renderTarget2.stencilBuffer = true
composer.addPass(bgRenderPass)
composer.addPass(earthRenderPass)
composer.addPass(marsRenderPass)
composer.addPass(earthMask) // 创建地球掩码
composer.addPass(effectFilm) // 创建火星渲染专用处理通道
composer.addPass(marsRenderPass)
composer.addPass(marsMask) // 创建火星掩码
composer.addPass(bloomPass) // 创建火星渲染专用处理通道
composer.addPass(clearMask)
composer.addPass(effectCopy)
最后渲染场景。
function render() {
trackballControls.update(clock)
const delta = clock.getDelta()
earthAndLight.earth.rotation.y += 0.01
earthAndLight.pivot.rotation.y += 0.01
marsAndLight.mars.rotation.y += 0.01
marsAndLight.pivot.rotation.y += 0.01
composer.render(delta)
requestAnimationFrame(render)
render()
切记,不要忘了给渲染器设置:
renderer.autoClear = false
下面贴出完整代码:
import * as THREE from 'three'
// 用于鼠标移动镜头
import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls'
// 用于后期处理的依赖
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer'
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass'
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass'
import { CopyShader } from 'three/examples/jsm/shaders/CopyShader'
import { FilmPass } from 'three/examples/jsm/postprocessing/FilmPass'
import { BloomPass } from 'three/examples/jsm/postprocessing/BloomPass'
import { ClearMaskPass, MaskPass } from 'three/examples/jsm/postprocessing/MaskPass'
export default () => {
// 定义摄像机
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight)
camera.position.set(-50, 40, 50)
// 定义渲染器
const renderer = new THREE.WebGLRenderer({
'antialias':true,
'alpha':true
renderer.autoClear = false
// 定义场景的颜色为黑色
renderer.setClearColor(0x000000)
// 定义场景大小为整个窗口
renderer.setSize(window.innerWidth, window.innerHeight)
// 将渲染的结果加入到div中
document.querySelector('#webgl-box').appendChild(renderer.domElement)
// 配置各个需要渲染的场景
const sceneEarth = new THREE.Scene() // 地球
const sceneMars = new THREE.Scene() // 火星
const sceneBG = new THREE.Scene() // 背景
// 添加光源
function addLight (scene) {
const spotLight = new THREE.SpotLight(0xFFFFFF, 1.2, 150, 120)
spotLight.position.set(-10, 30, 40)
scene.add(spotLight)
const ambientLight = new THREE.AmbientLight(0x3c3c3c)
scene.add(ambientLight)
// 创建地球
function addEarth (scene) {
const textureLoader = new THREE.TextureLoader()
const cubeMaterial = new THREE.MeshLambertMaterial({
'map': textureLoader.load('img/textures/earth/Earth.png'),
'normalMap': textureLoader.load('img/textures/earth/EarthNormal.png')
const sphereGeometry = new THREE.SphereGeometry(20, 100, 100)
const mesh = new THREE.Mesh(sphereGeometry, cubeMaterial)
scene.add(mesh)
// 新建一个场景,用来存放可以自由控制的光源
const pivot = new THREE.Object3D()
addLight(pivot)
scene.add(pivot)
return {'earth': mesh, 'pivot': pivot}
// 创建火星
function addMars (scene) {
const textureLoader = new THREE.TextureLoader()
const cubeMaterial = new THREE.MeshLambertMaterial({
'map': textureLoader.load('img/textures/mars/mars_1k_color.jpg'),
'normalMap': textureLoader.load('img/textures/mars/mars_1k_normal.jpg')
const sphereGeometry = new THREE.SphereGeometry(20, 100, 100)
const mesh = new THREE.Mesh(sphereGeometry, cubeMaterial)
scene.add(mesh)
// 新建一个场景,用来存放可以自由控制的光源
const pivot = new THREE.Object3D()
addLight(pivot)
scene.add(pivot)
return {'mars': mesh, 'pivot': pivot}
// 添加背景
const textureLoader = new THREE.TextureLoader()
sceneBG.background = textureLoader.load('img/textures/bg/starry-deep-outer-space-galaxy.jpg')
// 创建场景
const earthAndLight = addEarth(sceneEarth)
sceneEarth.translateX(-16)
sceneEarth.scale.set(1.2, 1.2, 1.2)
const marsAndLight = addMars(sceneMars)
sceneMars.translateX(12)
sceneMars.translateY(6)
sceneMars.scale.set(0.2, 0.2, 0.2)
// 配置通道,融合场景
const bgRenderPass = new RenderPass(sceneBG, camera)
const earthRenderPass = new RenderPass(sceneEarth, camera)
earthRenderPass.clear = false
const marsRenderPass = new RenderPass(sceneMars, camera)
marsRenderPass.clear = false
// setup the mask
const clearMask = new ClearMaskPass()
const earthMask = new MaskPass(sceneEarth, camera)
const marsMask = new MaskPass(sceneMars, camera)
// 电视效果
const effectFilm = new FilmPass(0.8, 0.325, 256, false)
effectFilm.renderToScreen = true
// 泛光效果
const bloomPass = new BloomPass()
const effectCopy = new ShaderPass(CopyShader)
effectCopy.renderToScreen = true
const composer = new EffectComposer(renderer)
composer.renderTarget1.stencilBuffer = true
composer.renderTarget2.stencilBuffer = true
composer.addPass(bgRenderPass)
composer.addPass(earthRenderPass)
composer.addPass(marsRenderPass)
composer.addPass(earthMask) // 创建地球掩码
composer.addPass(effectFilm) // 创建火星渲染专用处理通道
composer.addPass(marsRenderPass)
composer.addPass(marsMask) // 创建火星掩码
composer.addPass(bloomPass) // 创建火星渲染专用处理通道
composer.addPass(clearMask)
composer.addPass(effectCopy)
// 实现鼠标移动摄像头
const trackballControls = new TrackballControls(camera, renderer.domElement)
const clock = new THREE.Clock()
function render() {
trackballControls.update(clock)
const delta = clock.getDelta()
earthAndLight.earth.rotation.y += 0.01
earthAndLight.pivot.rotation.y += 0.01
marsAndLight.mars.rotation.y += 0.01
marsAndLight.pivot.rotation.y += 0.01
composer.render(delta)
requestAnimationFrame(render)
render()
总结:
掩码的用法很简单,其实就是
A的掩码通道 -> 某效果渲染通道 -> clearMask =》这样只有A上会显示这种效果。
我们需要注意的是,通道clear属性必须设置为false,EffectComposer对象stencilBuffer 属性必须设置为true,renderer.autoClear 必须设置为false,这样,才可以正确的使用掩码通道。
其它后期处理通道这里不再一一讲解,感兴趣的可以自己去实验。
3. 使用 ShaderPass 自定义效果
基于ShaderPass 我们可以编写更多着色器程序来为场景添加更多自定义效果,在three.js中自带了一些现成的着色器程序可以直接使用。
它们被维护在example目录下:
three/examples/jsm/shaders/
着色器库中的着色器用法如下:
var effectCopy = new THREE.ShaderPass(THREE.CopyShader); // 该着色器用作输出效果,因为一般着色器是不能够输出的
effectCopy.renderToScreen = true;
var bleachByPassFilter = new THREE.ShaderPass(THREE.BleachBypassShader); // 漂白效果
var brightnessContrastShader = new THREE.ShaderPass(THREE.BrightnessContrastShader) // 改变亮度与对比度
var colorifyShader = new THREE.ShaderPass(THREE.ColorifyShader); // 为整个屏幕覆盖颜色
var colorCorrectionShader = new THREE.ShaderPass(THREE.ColorCorrectionShader); // 调整颜色分布
var freiChenShader = new THREE.ShaderPass(THREE.FreiChenShader); // 图像边缘检测
var gammaCorrectionShader = new THREE.ShaderPass(THREE.GammaCorrectionShader);
var hueSaturationShader = new THREE.ShaderPass(THREE.HueSaturationShader); // 色调饱和度
var kaleidoShader = new THREE.ShaderPass(THREE.KaleidoShader); // 万花筒效果
var luminosityHighPassShader = new THREE.ShaderPass(THREE.LuminosityHighPassShader); // 亮度效果
var luminosityShader = new THREE.ShaderPass(THREE.LuminosityShader); // 亮度效果
var mirrorShader = new THREE.ShaderPass(THREE.MirrorShader); // 镜面效果
var pixelShader = new THREE.ShaderPass(THREE.PixelShader); // 像素画
pixelShader.uniforms.resolution.value = new THREE.Vector2(256, 256);
var rgbShiftShader = new THREE.ShaderPass(THREE.RGBShiftShader); // 红绿蓝颜色分开
var sepiaShader = new THREE.ShaderPass(THREE.SepiaShader); // 乌贼墨效果
var sobelOperatorShader = new THREE.ShaderPass(THREE.SobelOperatorShader); // 边缘检测
sobelOperatorShader.uniforms.resolution.value = new THREE.Vector2(256, 256);
var vignetteShader = new THREE.ShaderPass(THREE.VignetteShader); // 晕映效果
var horBlurShader = new THREE.ShaderPass(THREE.HorizontalBlurShader); // 屏幕模糊
var verBlurShader = new THREE.ShaderPass(THREE.VerticalBlurShader); // 屏幕模糊
var horTiltShiftShader = new THREE.ShaderPass(THREE.HorizontalTiltShiftShader); // 移轴摄影
var verTiltShiftShader = new THREE.ShaderPass(THREE.VerticalTiltShiftShader); // 移轴摄影
var triangleBlurShader = new THREE.ShaderPass(THREE.TriangleBlurShader, "texture"); // 基于三角形法添加模糊
var focusShader = new THREE.ShaderPass(THREE.FocusShader); // 中央尖锐边角模糊
4. 自定义后期处理着色器
我们还没有系统的讲解着色器语言,所以本章只讲两个简单的着色器案例。
同时推荐大家到
去寻找你需要的效果,里边有很多现成的,效果很棒的着色器代码。
4.1 自定义灰度着色器
如果想使用自定义着色器,你需要实现两个组件:顶点着色器(vertex shader)和 片段着色器(fragment shader)。顶点着色器用于改变每个顶点的位置,片段着色器可以定义每个像素的颜色。对于后期处理,我们只需要实现片段着色器即可。
这里我们不对源码做讲解,有机会我会单独开一篇文章讲解着色器语言(GLSL)。
自定义灰度着色器:
// 自定义灰度着色器
const CustomGrayScaleShader = {
'uniforms': {
'tDiffuse': {'type': 't', 'value': null},
'rPower': {'type': 'f', 'value': 0.2126},
'gPower': {'type': 'f', 'value': 0.7152},
'bPower': {'type': 'f', 'value': 0.0722}
// 0.2126 R + 0.7152 G + 0.0722 B
// vertexshader is always the same for postprocessing steps
'vertexShader': [
'varying vec2 vUv;',
'void main() {',
'vUv = uv;',
'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
].join('\n'),
'fragmentShader': [
// pass in our custom uniforms
'uniform float rPower;',
'uniform float gPower;',
'uniform float bPower;',
// pass in the image/texture we'll be modifying
'uniform sampler2D tDiffuse;',
// used to determine the correct texel we're working on
'varying vec2 vUv;',
// executed, in parallel, for each pixel
'void main() {',
// get the pixel from the texture we're working with (called a texel)
'vec4 texel = texture2D( tDiffuse, vUv );',
// calculate the new color
'float gray = texel.r*rPower + texel.g*gPower + texel.b*bPower;',
// return this new color
'gl_FragColor = vec4( vec3(gray), texel.w );',
].join('\n')
使用该着色器:
...
const renderPass = new RenderPass(scene, camera)
const effectCopy = new ShaderPass(CopyShader)
effectCopy.renderToScreen = true
const effectCustomGrayScale = new ShaderPass(CustomGrayScaleShader)