logo头像
Snippet 博客主题

threejs后期处理

1. 如何使用Threejs的后期处理

后期处理就是在场景渲染完后,最后对场景显示效果调整的手段。

使用后期处理步骤:

(1)创建THREE.EffectComposer对象。(效果组合器)

(2)在该对象上添加后期处理通道。(渲染时就会按照添加的顺序依次处理–即渲染管线类似)

(3)在render循环中,使用THREE.EffectComposer来渲染场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
<!-- 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)简单后期处理通道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
<!-- 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)使用掩码的高级效果组合器

前面使用后期处理的通道都是针对整个屏幕上,而下面讨论的掩码通道就可以指定特定区域上使用通道。添加掩码通道后,则后续的通道只作用于这个掩码通道区域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
<!-- 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提供的简单着色器:

另外,一些提供模糊效果的着色器:

最后,还有一些提供高级效果的着色器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
<!-- 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通道,只是把传递的着色器程序换成我们自定义的了):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
<!-- 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>

着色器程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// 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")
};