个人博客地址: https://cxx001.gitee.io
1. 如何使用Threejs的后期处理
后期处理就是在场景渲染完后,最后对场景显示效果调整的手段。
使用后期处理步骤:
(1)创建THREE.EffectComposer对象。(效果组合器)
(2)在该对象上添加后期处理通道。(渲染时就会按照添加的顺序依次处理–即渲染管线类似)
(3)在render循环中,使用THREE.EffectComposer来渲染场景。
<!-- chapter-11-01.html -->
<!DOCTYPE html>
<html>
<head>
<title>Effect composings</title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/stats.js"></script>
<script type="text/javascript" src="../libs/dat.gui.js"></script>
<script type="text/javascript" src="../libs/OrbitControls.js"></script>
<script type="text/javascript" src="../libs/postprocessing/ShaderPass.js"></script>
<script type="text/javascript" src="../libs/shaders/CopyShader.js"></script>
<script type="text/javascript" src="../libs/postprocessing/EffectComposer.js"></script>
<script type="text/javascript" src="../libs/postprocessing/MaskPass.js"></script>
<script type="text/javascript" src="../libs/postprocessing/FilmPass.js"></script>
<script type="text/javascript" src="../libs/shaders/FilmShader.js"></script>
<script type="text/javascript" src="../libs/postprocessing/RenderPass.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>
<script type="text/javascript">
function init() {
var stats = initStats();
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
var webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0x000, 1.0));
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.shadowMapEnabled = true;
var sphere = createMesh(new THREE.SphereGeometry(10, 40, 40));
scene.add(sphere);
camera.position.x = -10;
camera.position.y = 15;
camera.position.z = 25;
camera.lookAt(new THREE.Vector3(0, 0, 0));
var orbitControls = new THREE.OrbitControls(camera);
orbitControls.autoRotate = false;
var clock = new THREE.Clock();
var ambi = new THREE.AmbientLight(0x181818);
scene.add(ambi);
var spotLight = new THREE.DirectionalLight(0xffffff);
spotLight.position.set(550, 100, 550);
spotLight.intensity = 0.6;
scene.add(spotLight);
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);
// 1. 创建效果组合器对象
var composer = new THREE.EffectComposer(webGLRenderer);
// 2. 添加后期处理通道
var renderPass = new THREE.RenderPass(scene, camera); // 该通道会在当前场景和摄像机的基础上渲染出一个新场景
var effectFilm = new THREE.FilmPass(0.8, 0.325, 256, false); // 该通道可以实现电视栅格效果
effectFilm.renderToScreen = true; // 渲染到界面(不是所有通道都有该属性)
composer.addPass(renderPass);
composer.addPass(effectFilm);
var controls = new function () {
this.scanlinesCount = 256; // 控制扫描线数量
this.grayscale = false; // 如果设置为true,输出结果将会被转换为灰度图
this.scanlinesIntensity = 0.3; // 指定扫描线的显著程度
this.noiseIntensity = 0.8; // 控制场景的粗糙程度
this.updateEffectFilm = function () {
effectFilm.uniforms.grayscale.value = controls.grayscale;
effectFilm.uniforms.nIntensity.value = controls.noiseIntensity;
effectFilm.uniforms.sIntensity.value = controls.scanlinesIntensity;
effectFilm.uniforms.sCount.value = controls.scanlinesCount;
};
};
var gui = new dat.GUI();
gui.add(controls, "scanlinesIntensity", 0, 1).onChange(controls.updateEffectFilm);
gui.add(controls, "noiseIntensity", 0, 3).onChange(controls.updateEffectFilm);
gui.add(controls, "grayscale").onChange(controls.updateEffectFilm);
gui.add(controls, "scanlinesCount", 0, 2048).step(1).onChange(controls.updateEffectFilm);
function createMesh(geom) {
var planetTexture = THREE.ImageUtils.loadTexture("../assets/textures/planets/Earth.png");
var specularTexture = THREE.ImageUtils.loadTexture("../assets/textures/planets/EarthSpec.png");
var normalTexture = THREE.ImageUtils.loadTexture("../assets/textures/planets/EarthNormal.png");
var planetMaterial = new THREE.MeshPhongMaterial();
planetMaterial.specularMap = specularTexture;
planetMaterial.specular = new THREE.Color(0x4444aa);
planetMaterial.normalMap = normalTexture;
planetMaterial.map = planetTexture;
//planetMaterial.shininess = 150;
var mesh = THREE.SceneUtils.createMultiMaterialObject(geom, [planetMaterial]);
return mesh;
}
var delta = clock.getDelta();
function render() {
stats.update();
orbitControls.update(delta);
sphere.rotation.y += 0.002;
requestAnimationFrame(render);
// 3. 用效果组合器来渲染
composer.render(delta);
}
render();
function initStats() {
var stats = new Stats();
stats.setMode(0); // 0: fps, 1: ms
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.getElementById("Stats-output").appendChild(stats.domElement);
return stats;
}
}
window.onload = init;
</script>
</body>
</html>
2. Threejs提供的后期处理通道
Threejs库提供了许多后期处理通道,这些通道可以直接添加到THREE.EffectComposer组合器中使用。
(1)简单后期处理通道
<!-- chapter-11-02.html -->
<!DOCTYPE html>
<html>
<head>
<title>Simple passes</title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/stats.js"></script>
<script type="text/javascript" src="../libs/dat.gui.js"></script>
<script type="text/javascript" src="../libs/OrbitControls.js"></script>
<script type="text/javascript" src="../libs/postprocessing/ShaderPass.js"></script>
<script type="text/javascript" src="../libs/shaders/CopyShader.js"></script>
<script type="text/javascript" src="../libs/postprocessing/BloomPass.js"></script>
<script type="text/javascript" src="../libs/shaders/ConvolutionShader.js"></script>
<script type="text/javascript" src="../libs/postprocessing/DotScreenPass.js"></script>
<script type="text/javascript" src="../libs/shaders/DotScreenShader.js"></script>
<script type="text/javascript" src="../libs/postprocessing/EffectComposer.js"></script>
<script type="text/javascript" src="../libs/postprocessing/MaskPass.js"></script>
<script type="text/javascript" src="../libs/postprocessing/FilmPass.js"></script>
<script type="text/javascript" src="../libs/shaders/FilmShader.js"></script>
<script type="text/javascript" src="../libs/postprocessing/RenderPass.js"></script>
<script type="text/javascript" src="../libs/postprocessing/TexturePass.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>
<script type="text/javascript">
function init() {
var stats = initStats();
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
var webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0x000, 1.0));
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.shadowMapEnabled = true;
var sphere = createMesh(new THREE.SphereGeometry(10, 40, 40));
scene.add(sphere);
camera.position.x = -10;
camera.position.y = 15;
camera.position.z = 25;
camera.lookAt(new THREE.Vector3(0, 0, 0));
var orbitControls = new THREE.OrbitControls(camera);
orbitControls.autoRotate = false;
var ambi = new THREE.AmbientLight(0x686868);
scene.add(ambi);
var spotLight = new THREE.DirectionalLight(0xffffff);
spotLight.position.set(550, 100, 550);
spotLight.intensity = 0.6;
scene.add(spotLight);
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);
// 复制场景
var renderPass = new THREE.RenderPass(scene, camera);
var effectCopy = new THREE.ShaderPass(THREE.CopyShader);
effectCopy.renderToScreen = true;
var composer = new THREE.EffectComposer(webGLRenderer);
composer.addPass(renderPass);
composer.addPass(effectCopy);
// 将上面场景输出为纹理,然后就可以作为其它组合器输入使用,这样就可以重复使用而不必每次都从零开始渲染场景。
var renderScene = new THREE.TexturePass(composer.renderTarget2);
// 简单组合器
var bloomPass = new THREE.BloomPass(3, 25, 5.0, 256);
var effectFilm = new THREE.FilmPass(0.8, 0.325, 256, false);
effectFilm.renderToScreen = true;
var dotScreenPass = new THREE.DotScreenPass();
// 左下(黑点图层通道)
var composer1 = new THREE.EffectComposer(webGLRenderer);
composer1.addPass(renderScene);
composer1.addPass(dotScreenPass);
composer1.addPass(effectCopy);
// 右下(原始图)
var composer2 = new THREE.EffectComposer(webGLRenderer);
composer2.addPass(renderScene);
composer2.addPass(effectCopy);
// 左上(泛光效果)
var composer3 = new THREE.EffectComposer(webGLRenderer);
composer3.addPass(renderScene);
composer3.addPass(bloomPass);
composer3.addPass(effectCopy);
// 右上(电视栅格效果)
var composer4 = new THREE.EffectComposer(webGLRenderer);
composer4.addPass(renderScene);
composer4.addPass(effectFilm);
var controls = new function () {
// film
this.scanlinesCount = 256; // 控制扫描线数量
this.grayscale = false; // 设为true,输出结果将会被转换为灰度图
this.scanlinesIntensity = 0.3; // 指定扫描线的显著程度
this.noiseIntensity = 0.8; // 控制场景的粗糙程度
// bloompass
this.strength = 3; // 泛光效果强度
this.kernelSize = 25; // 泛光效果的偏移量
this.sigma = 5.0; // 控制泛光效果的锐利程度,值越大,泛光效果看起来越模糊
this.resolution = 256; // 泛光效果的精确度,值越小,泛光效果的方块化越严重
// dotscreen
this.centerX = 0.5; // 点偏移量
this.centerY = 0.5;
this.angle = 1.57; // 改变点的对齐方式
this.scale = 1; // 设置点的大小。值越小,则点越大
this.updateEffectFilm = function () {
effectFilm.uniforms.grayscale.value = controls.grayscale;
effectFilm.uniforms.nIntensity.value = controls.noiseIntensity;
effectFilm.uniforms.sIntensity.value = controls.scanlinesIntensity;
effectFilm.uniforms.sCount.value = controls.scanlinesCount;
};
this.updateDotScreen = function () {
var dotScreenPass = new THREE.DotScreenPass(new THREE.Vector2(controls.centerX, controls.centerY), controls.angle, controls.scale);
composer1 = new THREE.EffectComposer(webGLRenderer);
composer1.addPass(renderScene);
composer1.addPass(dotScreenPass);
composer1.addPass(effectCopy);
};
this.updateEffectBloom = function () {
bloomPass = new THREE.BloomPass(controls.strength, controls.kernelSize, controls.sigma, controls.resolution);
composer3 = new THREE.EffectComposer(webGLRenderer);
composer3.addPass(renderScene);
composer3.addPass(bloomPass);
composer3.addPass(effectCopy);
};
};
var gui = new dat.GUI();
var bpFolder = gui.addFolder("BloomPass");
bpFolder.add(controls, "strength", 1, 10).onChange(controls.updateEffectBloom);
bpFolder.add(controls, "kernelSize", 1, 100).onChange(controls.updateEffectBloom);
bpFolder.add(controls, "sigma", 1, 10).onChange(controls.updateEffectBloom);
bpFolder.add(controls, "resolution", 0, 1024).onChange(controls.updateEffectBloom);
var fpFolder = gui.addFolder("FilmPass");
fpFolder.add(controls, "scanlinesIntensity", 0, 1).onChange(controls.updateEffectFilm);
fpFolder.add(controls, "noiseIntensity", 0, 3).onChange(controls.updateEffectFilm);
fpFolder.add(controls, "grayscale").onChange(controls.updateEffectFilm);
fpFolder.add(controls, "scanlinesCount", 0, 2048).step(1).onChange(controls.updateEffectFilm);
var dsFolder = gui.addFolder("DotScreenPass");
dsFolder.add(controls, "centerX", 0, 1).onChange(controls.updateDotScreen);
dsFolder.add(controls, "centerY", 0, 1).onChange(controls.updateDotScreen);
dsFolder.add(controls, "angle", 0, 3.14).onChange(controls.updateDotScreen);
dsFolder.add(controls, "scale", 0, 10).onChange(controls.updateDotScreen);
function createMesh(geom) {
var planetTexture = THREE.ImageUtils.loadTexture("../assets/textures/planets/Earth.png");
var specularTexture = THREE.ImageUtils.loadTexture("../assets/textures/planets/EarthSpec.png");
var normalTexture = THREE.ImageUtils.loadTexture("../assets/textures/planets/EarthNormal.png");
var planetMaterial = new THREE.MeshPhongMaterial();
planetMaterial.specularMap = specularTexture;
planetMaterial.specular = new THREE.Color(0x4444aa);
planetMaterial.normalMap = normalTexture;
planetMaterial.map = planetTexture;
var mesh = THREE.SceneUtils.createMultiMaterialObject(geom, [planetMaterial]);
return mesh;
}
var width = window.innerWidth || 2;
var height = window.innerHeight || 2;
var halfWidth = width / 2;
var halfHeight = height / 2;
var clock = new THREE.Clock();
var delta = clock.getDelta();
function render() {
stats.update();
orbitControls.update(delta);
sphere.rotation.y += 0.002;
requestAnimationFrame(render);
// 多场景视口设置
webGLRenderer.autoClear = false;
webGLRenderer.clear();
// 副本渲染,后续才能使用这个副本
webGLRenderer.setViewport(0, 0, 2 * halfWidth, 2 * halfHeight);
composer.render(delta);
// 分别渲染4个视口场景
webGLRenderer.setViewport(0, 0, halfWidth, halfHeight);
composer1.render(delta);
webGLRenderer.setViewport(halfWidth, 0, halfWidth, halfHeight);
composer2.render(delta);
webGLRenderer.setViewport(0, halfHeight, halfWidth, halfHeight);
composer3.render(delta);
webGLRenderer.setViewport(halfWidth, halfHeight, halfWidth, halfHeight);
composer4.render(delta);
}
render();
function initStats() {
var stats = new Stats();
stats.setMode(0); // 0: fps, 1: ms
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.getElementById("Stats-output").appendChild(stats.domElement);
return stats;
}
};
window.onload = init;
</script>
</body>
</html>
(2)使用掩码的高级效果组合器
前面使用后期处理的通道都是针对整个屏幕上,而下面讨论的掩码通道就可以指定特定区域上使用通道。添加掩码通道后,则后续的通道只作用于这个掩码通道区域。
<!-- chapter-11-03.html -->
<!DOCTYPE html>
<html>
<head>
<title>Post processing masks</title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/stats.js"></script>
<script type="text/javascript" src="../libs/dat.gui.js"></script>
<script type="text/javascript" src="../libs/OrbitControls.js"></script>
<script type="text/javascript" src="../libs/postprocessing/ShaderPass.js"></script>
<script type="text/javascript" src="../libs/shaders/CopyShader.js"></script>
<script type="text/javascript" src="../libs/shaders/ColorifyShader.js"></script>
<script type="text/javascript" src="../libs/postprocessing/BloomPass.js"></script>
<script type="text/javascript" src="../libs/shaders/ConvolutionShader.js"></script>
<script type="text/javascript" src="../libs/postprocessing/EffectComposer.js"></script>
<script type="text/javascript" src="../libs/postprocessing/MaskPass.js"></script>
<script type="text/javascript" src="../libs/postprocessing/FilmPass.js"></script>
<script type="text/javascript" src="../libs/shaders/FilmShader.js"></script>
<script type="text/javascript" src="../libs/shaders/SepiaShader.js"></script>
<script type="text/javascript" src="../libs/postprocessing/RenderPass.js"></script>
<script type="text/javascript" src="../libs/postprocessing/SavePass.js"></script>
<script type="text/javascript" src="../libs/postprocessing/TexturePass.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>
<script type="text/javascript">
function init() {
var stats = initStats();
// 3个场景分别渲染地球、火星、背景
var sceneEarth = new THREE.Scene();
var sceneMars = new THREE.Scene();
var sceneBG = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
var cameraBG = new THREE.OrthographicCamera(-window.innerWidth, window.innerWidth, window.innerHeight, -window.innerHeight, -10000, 10000); // 用正交相机来显示背景图
cameraBG.position.z = 50;
var webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0x000, 1.0));
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.shadowMapEnabled = true;
var sphere = createEarthMesh(new THREE.SphereGeometry(10, 40, 40));
sphere.position.x = -10;
var sphere2 = createMarshMesh(new THREE.SphereGeometry(5, 40, 40));
sphere2.position.x = 10;
sceneEarth.add(sphere);
sceneMars.add(sphere2);
camera.position.x = -10;
camera.position.y = 15;
camera.position.z = 25;
camera.lookAt(new THREE.Vector3(0, 0, 0));
var materialColor = new THREE.MeshBasicMaterial({
map: THREE.ImageUtils.loadTexture("../assets/textures/starry-deep-outer-space-galaxy.jpg"),
depthTest: false
});
var bgPlane = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), materialColor);
bgPlane.position.z = -100;
bgPlane.scale.set(window.innerWidth * 2, window.innerHeight * 2, 1);
sceneBG.add(bgPlane);
var orbitControls = new THREE.OrbitControls(camera);
orbitControls.autoRotate = false;
var clock = new THREE.Clock();
// 注意不同场景光源要独立
var ambi = new THREE.AmbientLight(0x181818);
var ambi2 = new THREE.AmbientLight(0x181818);
sceneEarth.add(ambi);
sceneMars.add(ambi2);
var spotLight = new THREE.DirectionalLight(0xffffff);
spotLight.position.set(550, 100, 550);
spotLight.intensity = 0.6;
var spotLight2 = new THREE.DirectionalLight(0xffffff);
spotLight.position.set(550, 100, 550);
spotLight.intensity = 0.6;
sceneEarth.add(spotLight);
sceneMars.add(spotLight2);
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);
// 3个对应的渲染场景通道
var bgPass = new THREE.RenderPass(sceneBG, cameraBG);
var renderPass = new THREE.RenderPass(sceneEarth, camera);
renderPass.clear = false; // 要设置为false,不然每次渲染前会把前面已经渲染的给清除
var renderPass2 = new THREE.RenderPass(sceneMars, camera);
renderPass2.clear = false;
// 复制当前渲染结果显示到屏幕通道
var effectCopy = new THREE.ShaderPass(THREE.CopyShader);
effectCopy.renderToScreen = true;
// THREE.MaskPass到THREE.ClearMaskPass之间的通道只作用在THREE.MaskPass掩码通道对象上
var clearMask = new THREE.ClearMaskPass();
// earth mask
var earthMask = new THREE.MaskPass(sceneEarth, camera);
// earthMask.inverse = true; // 反转掩码
// mars mask
var marsMask = new THREE.MaskPass(sceneMars, camera);
// marsMask.inverse = true;
var effectSepia = new THREE.ShaderPass(THREE.SepiaShader);
effectSepia.uniforms['amount'].value = 0.8;
var effectColorify = new THREE.ShaderPass(THREE.ColorifyShader);
effectColorify.uniforms['color'].value.setRGB(0.5, 0.5, 1);
var composer = new THREE.EffectComposer(webGLRenderer);
// 使用掩码要启动模板缓存
composer.renderTarget1.stencilBuffer = true;
composer.renderTarget2.stencilBuffer = true;
// 渲染原始场景
composer.addPass(bgPass);
composer.addPass(renderPass);
composer.addPass(renderPass2);
// 添加火星球体场景掩码,后续通道只能作用在这个区域
composer.addPass(marsMask);
composer.addPass(effectColorify);
// 同上对地球场景添加掩码,只是要注意添加另一个掩码前要先清除上一个掩码通道
composer.addPass(clearMask);
composer.addPass(earthMask);
composer.addPass(effectSepia);
// 清除上次掩码通道,复制当前场景渲染到屏幕
composer.addPass(clearMask);
composer.addPass(effectCopy);
function createMarshMesh(geom) {
var planetTexture = THREE.ImageUtils.loadTexture("../assets/textures/planets/Mars_2k-050104.png");
var normalTexture = THREE.ImageUtils.loadTexture("../assets/textures/planets/Mars-normalmap_2k.png");
var planetMaterial = new THREE.MeshPhongMaterial();
planetMaterial.normalMap = normalTexture;
planetMaterial.map = planetTexture;
var mesh = THREE.SceneUtils.createMultiMaterialObject(geom, [planetMaterial]);
return mesh;
}
function createEarthMesh(geom) {
var planetTexture = THREE.ImageUtils.loadTexture("../assets/textures/planets/Earth.png");
var specularTexture = THREE.ImageUtils.loadTexture("../assets/textures/planets/EarthSpec.png");
var normalTexture = THREE.ImageUtils.loadTexture("../assets/textures/planets/EarthNormal.png");
var planetMaterial = new THREE.MeshPhongMaterial();
planetMaterial.specularMap = specularTexture;
planetMaterial.specular = new THREE.Color(0x4444aa);
planetMaterial.normalMap = normalTexture;
planetMaterial.map = planetTexture;
var mesh = THREE.SceneUtils.createMultiMaterialObject(geom, [planetMaterial]);
return mesh;
}
var delta = clock.getDelta();
function render() {
webGLRenderer.autoClear = false;
stats.update();
orbitControls.update(delta);
sphere.rotation.y += 0.002;
sphere2.rotation.y += 0.002;
requestAnimationFrame(render);
composer.render(delta);
}
render();
function initStats() {
var stats = new Stats();
stats.setMode(0); // 0: fps, 1: ms
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.getElementById("Stats-output").appendChild(stats.domElement);
return stats;
}
}
window.onload = init;
</script>
</body>
</html>
没对火星球体和地球球体添加掩码处理前:
对火星球体添加掩码通道并添加彩色效果,对地球球体添加掩码并添加褐色效果:
(3)使用THREE.ShaderPass自定义效果
这个通道可以传递一个自定义的着色器,将大量的额外效果添加到场景中。
Threejs提供的简单着色器:
另外,一些提供模糊效果的着色器:
最后,还有一些提供高级效果的着色器:
<!-- chapter-11-04.html -->
<!DOCTYPE html>
<html>
<head>
<title>Shader Pass simple</title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/stats.js"></script>
<script type="text/javascript" src="../libs/dat.gui.js"></script>
<script type="text/javascript" src="../libs/OBJLoader.js"></script>
<script type="text/javascript" src="../libs/MTLLoader.js"></script>
<script type="text/javascript" src="../libs/OBJMTLLoader.js"></script>
<script type="text/javascript" src="../libs/postprocessing/EffectComposer.js"></script>
<script type="text/javascript" src="../libs/postprocessing/ShaderPass.js"></script>
<script type="text/javascript" src="../libs/postprocessing/RenderPass.js"></script>
<script type="text/javascript" src="../libs/postprocessing/MaskPass.js"></script>
<script type="text/javascript" src="../libs/shaders/CopyShader.js"></script>
<script type="text/javascript" src="../libs/shaders/BrightnessContrastShader.js"></script>
<script type="text/javascript" src="../libs/shaders/ColorifyShader.js"></script>
<script type="text/javascript" src="../libs/shaders/SepiaShader.js"></script>
<script type="text/javascript" src="../libs/shaders/RGBShiftShader.js"></script>
<script type="text/javascript" src="../libs/shaders/ColorCorrectionShader.js"></script>
<script type="text/javascript" src="../libs/shaders/MirrorShader.js"></script>
<script type="text/javascript" src="../libs/shaders/VignetteShader.js"></script>
<script type="text/javascript" src="../libs/shaders/HueSaturationShader.js"></script>
<script type="text/javascript" src="../libs/shaders/BlendShader.js"></script>
<script type="text/javascript" src="../libs/shaders/KaleidoShader.js"></script>
<script type="text/javascript" src="../libs/shaders/LuminosityShader.js"></script>
<script type="text/javascript" src="../libs/shaders/TechnicolorShader.js"></script>
<script type="text/javascript" src="../libs/shaders/UnpackDepthRGBAShader.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>
<script type="text/javascript">
function init() {
var stats = initStats();
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
var webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0xaaaaff, 1.0));
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.shadowMapEnabled = true;
camera.position.x = 20;
camera.position.y = 30;
camera.position.z = 40;
camera.lookAt(new THREE.Vector3(-15, -10, -25));
var spotLight = new THREE.SpotLight(0xffffff);
spotLight.castShadow = true;
spotLight.position.set(0, 60, 50);
spotLight.intensity = 1;
spotLight.shadowMapWidth = 2048;
spotLight.shadowMapHeight = 2048;
spotLight.shadowCameraFov = 120;
spotLight.shadowCameraNear = 1;
spotLight.shadowCameraFar = 1000;
var ambiLight = new THREE.AmbientLight(0x444444);
scene.add(ambiLight);
scene.add(spotLight);
var plane = new THREE.BoxGeometry(1600, 1600, 0.1, 40, 40);
var cube = new THREE.Mesh(plane, new THREE.MeshPhongMaterial(
{
color: 0xffffff,
map: THREE.ImageUtils.loadTexture("../assets/textures/general/plaster-diffuse.jpg"),
normalMap: THREE.ImageUtils.loadTexture("../assets/textures/general/plaster-normal.jpg"),
normalScale: new THREE.Vector2(0.6, 0.6)
}));
cube.material.map.wrapS = THREE.RepeatWrapping;
cube.material.map.wrapT = THREE.RepeatWrapping;
cube.material.normalMap.wrapS = THREE.RepeatWrapping;
cube.material.normalMap.wrapT = THREE.RepeatWrapping;
cube.rotation.x = Math.PI / 2;
cube.material.map.repeat.set(80, 80);
cube.receiveShadow = true;
cube.position.z = -150;
cube.position.x = -150;
scene.add(cube);
var cube1 = new THREE.Mesh(new THREE.BoxGeometry(30, 10, 2), new THREE.MeshPhongMaterial({color: 0xff0000}));
cube1.position.x = -15;
cube1.position.y = 5;
cube1.position.z = 15;
cube1.castShadow = true;
scene.add(cube1);
var cube2 = cube1.clone();
cube2.material = cube1.material.clone();
cube2.material.color = new THREE.Color(0x00ff00);
cube2.position.z = 5;
cube2.position.x = -20;
scene.add(cube2);
var cube3 = cube1.clone();
cube3.material = cube1.material.clone();
cube3.material.color = new THREE.Color(0x0000ff);
cube3.position.z = -8;
cube3.position.x = -25;
scene.add(cube3);
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);
var mesh;
var loader = new THREE.OBJMTLLoader();
loader.load('../assets/models/sol/libertStatue.obj', '../assets/models/sol/libertStatue.mtl',
function (event) {
var object = event;
// fix for incorrect uvs.
console.log(event);
var geom = object.children[0].geometry;
var uv3 = geom.faceVertexUvs[0][0];
var uv4 = geom.faceVertexUvs[0][10];
// fill in the missing ones
for (var j = 0; j < 7616 - 7206; j++) {
if (geom.faces[j + 7206] instanceof THREE.Face4) {
geom.faceVertexUvs[0].push(uv4);
} else {
geom.faceVertexUvs[0].push(uv4);
}
}
object.children.forEach(function (e) {
e.castShadow = true
});
object.scale.set(20, 20, 20);
mesh = object;
mesh.position.x = 15;
mesh.position.z = 5;
scene.add(object);
});
// 各种shader通道
var mirror = new THREE.ShaderPass(THREE.MirrorShader);
mirror.enabled = false;
var hue = new THREE.ShaderPass(THREE.HueSaturationShader);
hue.enabled = false;
var vignette = new THREE.ShaderPass(THREE.VignetteShader);
vignette.enabled = false;
var colorCorrection = new THREE.ShaderPass(THREE.ColorCorrectionShader);
colorCorrection.enabled = false;
var rgbShift = new THREE.ShaderPass(THREE.RGBShiftShader);
rgbShift.enabled = false;
var brightness = new THREE.ShaderPass(THREE.BrightnessContrastShader);
brightness.uniforms.brightness.value = 0;
brightness.uniforms.contrast.value = 0;
brightness.enabled = false;
brightness.uniforms.brightness.value = 0;
brightness.uniforms.contrast.value = 0;
var colorify = new THREE.ShaderPass(THREE.ColorifyShader);
colorify.uniforms.color.value = new THREE.Color(0xffffff);
colorify.enabled = false;
var sepia = new THREE.ShaderPass(THREE.SepiaShader);
sepia.uniforms.amount.value = 1;
sepia.enabled = false;
var kal = new THREE.ShaderPass(THREE.KaleidoShader);
kal.enabled = false;
var lum = new THREE.ShaderPass(THREE.LuminosityShader);
lum.enabled = false;
var techni = new THREE.ShaderPass(THREE.TechnicolorShader);
techni.enabled = false;
var unpack = new THREE.ShaderPass(THREE.UnpackDepthRGBAShader);
unpack.enabled = false;
var renderPass = new THREE.RenderPass(scene, camera);
var effectCopy = new THREE.ShaderPass(THREE.CopyShader);
effectCopy.renderToScreen = true;
var composer = new THREE.EffectComposer(webGLRenderer);
composer.addPass(renderPass);
composer.addPass(brightness);
composer.addPass(sepia);
composer.addPass(mirror);
composer.addPass(colorify);
composer.addPass(colorCorrection);
composer.addPass(rgbShift);
composer.addPass(vignette);
composer.addPass(hue);
composer.addPass(kal);
composer.addPass(lum);
composer.addPass(techni);
composer.addPass(unpack);
composer.addPass(effectCopy);
var controls = new function () {
this.brightness = 0.01;
this.contrast = 0.01;
this.select = 'none';
this.color = 0xffffff;
this.amount = 1;
this.powRGB_R = 2;
this.mulRGB_R = 1;
this.powRGB_G = 2;
this.mulRGB_G = 1;
this.powRGB_B = 2;
this.mulRGB_B = 1;
this.rgbAmount = 0.005;
this.angle = 0.0;
this.side = 1;
this.offset = 1;
this.darkness = 1;
this.hue = 0.01;
this.saturation = 0.01;
this.kalAngle = 0;
this.kalSides = 6;
this.rotate = false;
this.switchShader = function () {
switch (controls.select) {
case 'none' :
{
enableShader();
break;
}
case 'colorify' :
{
enableShader(colorify);
break;
}
case 'brightness' :
{
enableShader(brightness);
break;
}
case 'sepia' :
{
enableShader(sepia);
break;
}
case 'colorCorrection' :
{
enableShader(colorCorrection);
break;
}
case 'rgbShift' :
{
enableShader(rgbShift);
break;
}
case 'mirror' :
{
enableShader(mirror);
break;
}
case 'vignette' :
{
enableShader(vignette);
break;
}
case 'hueAndSaturation' :
{
enableShader(hue);
break;
}
case 'kaleidoscope' :
{
enableShader(kal);
break;
}
case 'luminosity' :
{
enableShader(lum);
break;
}
case 'technicolor' :
{
enableShader(techni);
break;
}
case 'unpackDepth' :
{
enableShader(unpack);
break;
}
}
};
this.changeBrightness = function () {
brightness.uniforms.brightness.value = controls.brightness;
brightness.uniforms.contrast.value = controls.contrast;
};
this.changeColor = function () {
colorify.uniforms.color.value = new THREE.Color(controls.color);
};
this.changeSepia = function () {
sepia.uniforms.amount.value = controls.amount;
};
this.changeCorrection = function () {
colorCorrection.uniforms.mulRGB.value = new THREE.Vector3(controls.mulRGB_R, controls.mulRGB_G, controls.mulRGB_B);
colorCorrection.uniforms.powRGB.value = new THREE.Vector3(controls.powRGB_R, controls.powRGB_G, controls.powRGB_B);
};
this.changeRGBShifter = function () {
rgbShift.uniforms.amount.value = controls.rgbAmount;
rgbShift.uniforms.angle.value = controls.angle;
};
this.changeMirror = function () {
mirror.uniforms.side.value = controls.side;
};
this.changeVignette = function () {
vignette.uniforms.darkness.value = controls.darkness;
vignette.uniforms.offset.value = controls.offset;
};
this.changeHue = function () {
hue.uniforms.hue.value = controls.hue;
hue.uniforms.saturation.value = controls.saturation;
};
this.changeKal = function () {
kal.uniforms.sides.value = controls.kalSides;
kal.uniforms.angle.value = controls.kalAngle;
};
function enableShader(shader) {
for (var i = 1; i < composer.passes.length - 1; i++) {
if (composer.passes[i] == shader) {
composer.passes[i].enabled = true;
} else {
composer.passes[i].enabled = false;
}
}
}
};
var gui = new dat.GUI();
gui.add(controls, "select", ['none', "colorify", 'brightness', 'sepia', 'colorCorrection', 'rgbShift', 'mirror', 'vignette', 'hueAndSaturation', 'kaleidoscope', 'luminosity', 'technicolor']).onChange(controls.switchShader);
gui.add(controls, "rotate");
var bnFolder = gui.addFolder("Brightness");
bnFolder.add(controls, "brightness", -1, 1).onChange(controls.changeBrightness);
bnFolder.add(controls, "contrast", -1, 1).onChange(controls.changeBrightness);
var clFolder = gui.addFolder("Colorify");
clFolder.addColor(controls, "color").onChange(controls.changeColor);
var colFolder = gui.addFolder('Color Correction');
colFolder.add(controls, "powRGB_R", 0, 5).onChange(controls.changeCorrection);
colFolder.add(controls, "powRGB_G", 0, 5).onChange(controls.changeCorrection);
colFolder.add(controls, "powRGB_B", 0, 5).onChange(controls.changeCorrection);
colFolder.add(controls, "mulRGB_R", 0, 5).onChange(controls.changeCorrection);
colFolder.add(controls, "mulRGB_G", 0, 5).onChange(controls.changeCorrection);
colFolder.add(controls, "mulRGB_B", 0, 5).onChange(controls.changeCorrection);
var sepiaFolder = gui.addFolder("Sepia");
sepiaFolder.add(controls, "amount", 0, 2).step(0.1).onChange(controls.changeSepia);
var shiftFolder = gui.addFolder("RGB Shift");
shiftFolder.add(controls, "rgbAmount", 0, 0.1).step(0.001).onChange(controls.changeRGBShifter);
shiftFolder.add(controls, "angle", 0, 3.14).step(0.001).onChange(controls.changeRGBShifter);
var mirrorFolder = gui.addFolder("mirror");
mirrorFolder.add(controls, "side", 0, 3).step(1).onChange(controls.changeMirror);
var vignetteFolder = gui.addFolder("vignette");
vignetteFolder.add(controls, "darkness", 0, 2).onChange(controls.changeVignette);
vignetteFolder.add(controls, "offset", 0, 2).onChange(controls.changeVignette);
var hueAndSat = gui.addFolder("hue and saturation");
hueAndSat.add(controls, "hue", -1, 1).step(0.01).onChange(controls.changeHue);
hueAndSat.add(controls, "saturation", -1, 1).step(0.01).onChange(controls.changeHue);
var kalMenu = gui.addFolder("Kaleidoscope");
kalMenu.add(controls, "kalAngle", -2 * Math.PI, 2 * Math.PI).onChange(controls.changeKal);
kalMenu.add(controls, "kalSides", 2, 20).onChange(controls.changeKal);
function render() {
stats.update();
if (controls.rotate) {
if (mesh) mesh.rotation.y += 0.01;
cube1.rotation.y += 0.01;
cube2.rotation.y += 0.01;
cube3.rotation.y += 0.01;
}
requestAnimationFrame(render);
composer.render();
}
render();
function initStats() {
var stats = new Stats();
stats.setMode(0); // 0: fps, 1: ms
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.getElementById("Stats-output").appendChild(stats.domElement);
return stats;
}
};
window.onload = init;
</script>
</body>
</html>
3. 创建自定义后期处理着色器
自定义着色器主要是两个:
- 顶点着色器:用于改变每个顶点的位置。
- 片段着色器:用于定义每个像素的颜色。
着色器程序是使用OpenGL着色器语言GLSL写的,有点像C语言。关于这个是一个很大的话题,本节只会简单介绍使用流程。
示例写了两个着色器,一个是将图片转换为灰度图,一个是将图片转换为8位图。
架子(就是用上面的THREE.ShaderPass通道,只是把传递的着色器程序换成我们自定义的了):
<!-- chapter-11-05.html -->
<!DOCTYPE html>
<html>
<head>
<title>custom shaderpass</title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/stats.js"></script>
<script type="text/javascript" src="../libs/dat.gui.js"></script>
<script type="text/javascript" src="../libs/OrbitControls.js"></script>
<script type="text/javascript" src="../libs/postprocessing/ShaderPass.js"></script>
<script type="text/javascript" src="../libs/shaders/CopyShader.js"></script>
<script type="text/javascript" src="../libs/postprocessing/EffectComposer.js"></script>
<script type="text/javascript" src="../libs/postprocessing/MaskPass.js"></script>
<script type="text/javascript" src="../libs/postprocessing/ShaderPass.js"></script>
<script type="text/javascript" src="../libs/postprocessing/RenderPass.js"></script>
<!--- 导入自定义着色器程序 -->
<script type="text/javascript" src="custom-shader.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="Stats-output">
</div>
<!-- Div which will hold the Output -->
<div id="WebGL-output">
</div>
<script type="text/javascript">
function init() {
var stats = initStats();
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
var webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0x000, 1.0));
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.shadowMapEnabled = true;
var sphere = createMesh(new THREE.SphereGeometry(10, 40, 40));
scene.add(sphere);
camera.position.x = -10;
camera.position.y = 15;
camera.position.z = 25;
camera.lookAt(new THREE.Vector3(0, 0, 0));
var orbitControls = new THREE.OrbitControls(camera);
orbitControls.autoRotate = false;
var clock = new THREE.Clock();
var ambi = new THREE.AmbientLight(0x181818);
scene.add(ambi);
var spotLight = new THREE.DirectionalLight(0xffffff);
spotLight.position.set(550, 100, 550);
spotLight.intensity = 0.6;
scene.add(spotLight);
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);
var renderPass = new THREE.RenderPass(scene, camera);
var effectCopy = new THREE.ShaderPass(THREE.CopyShader);
effectCopy.renderToScreen = true;
// 置灰着色器通道
var shaderPass = new THREE.ShaderPass(THREE.CustomGrayScaleShader);
shaderPass.enabled = false;
// 转8位图着色器通道
var bitPass = new THREE.ShaderPass(THREE.CustomBitShader);
bitPass.enabled = false;
var composer = new THREE.EffectComposer(webGLRenderer);
composer.addPass(renderPass);
composer.addPass(shaderPass);
composer.addPass(bitPass);
composer.addPass(effectCopy);
var controls = new function () {
this.grayScale = false;
this.rPower = 0.2126;
this.gPower = 0.7152;
this.bPower = 0.0722;
this.bitShader = false;
this.bitSize = 8;
this.updateEffectFilm = function () {
shaderPass.enabled = controls.grayScale;
shaderPass.uniforms.rPower.value = controls.rPower;
shaderPass.uniforms.gPower.value = controls.gPower;
shaderPass.uniforms.bPower.value = controls.bPower;
};
this.updateBit = function () {
bitPass.enabled = controls.bitShader;
bitPass.uniforms.bitSize.value = controls.bitSize;
}
};
var gui = new dat.GUI();
var grayMenu = gui.addFolder('gray scale');
grayMenu.add(controls, 'grayScale').onChange(controls.updateEffectFilm);
grayMenu.add(controls, 'rPower', 0, 1).onChange(controls.updateEffectFilm);
grayMenu.add(controls, 'gPower', 0, 1).onChange(controls.updateEffectFilm);
grayMenu.add(controls, 'bPower', 0, 1).onChange(controls.updateEffectFilm);
var bitMenu = gui.addFolder('bit');
bitMenu.add(controls, 'bitShader').onChange(controls.updateBit);
bitMenu.add(controls, 'bitSize', 2, 24).step(1).onChange(controls.updateBit);
function createMesh(geom) {
var planetTexture = THREE.ImageUtils.loadTexture("../assets/textures/planets/Earth.png");
var specularTexture = THREE.ImageUtils.loadTexture("../assets/textures/planets/EarthSpec.png");
var normalTexture = THREE.ImageUtils.loadTexture("../assets/textures/planets/EarthNormal.png");
var planetMaterial = new THREE.MeshPhongMaterial();
planetMaterial.specularMap = specularTexture;
planetMaterial.specular = new THREE.Color(0x4444aa);
planetMaterial.normalMap = normalTexture;
planetMaterial.map = planetTexture;
var mesh = THREE.SceneUtils.createMultiMaterialObject(geom, [planetMaterial]);
return mesh;
}
var delta = clock.getDelta();
function render() {
stats.update();
orbitControls.update(delta);
sphere.rotation.y += 0.002;
requestAnimationFrame(render);
composer.render(delta);
}
render();
function initStats() {
var stats = new Stats();
stats.setMode(0); // 0: fps, 1: ms
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.getElementById("Stats-output").appendChild(stats.domElement);
return stats;
}
}
window.onload = init;
</script>
</body>
</html>
着色器程序:
// custom-shader.js
THREE.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")
};
THREE.CustomBitShader = {
uniforms: {
"tDiffuse": {type: "t", value: null},
"bitSize": {type: "i", value: 4}
},
vertexShader: [
"varying vec2 vUv;",
"void main() {",
"vUv = uv;",
"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
"}"
].join("\n"),
fragmentShader: [
"uniform int bitSize;",
"uniform sampler2D tDiffuse;",
"varying vec2 vUv;",
"void main() {",
"vec4 texel = texture2D( tDiffuse, vUv );",
"float n = pow(float(bitSize),2.0);",
"float newR = floor(texel.r*n)/n;",
"float newG = floor(texel.g*n)/n;",
"float newB = floor(texel.b*n)/n;",
"gl_FragColor = vec4( vec3(newR,newG,newB), 1.0);",
"}"
].join("\n")
};