1.設置后期處理
設置Three.js庫為后期處理做准備,我們需要通過以下步驟對當前的配置進行修改:
1)創建一個EffectComposer(效果組合器)對象,然后在該對象上添加后期處理通道。
2)配置該對象,使它可以渲染我們的場景,並應用額外的后期處理步驟。
3)在render循環中,使用EffectComposer渲染場景、應用通道,並輸出結果。
要使用后期處理,需要引入一些javaSscript文件。這些文件可以在Three.js發布包里找到。路徑是examples/js/postprocessing和example/js/shaders。至少包含下面的文件:
<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>
首先我們創建一個EffectComposer對象,你可以在這個對象的構造函數里出入WebGL-Renderer,如下所示:
var composer = new THREE.EffectComposer(webGLRenderer);
接下來我們要在這個組合器中添加各種通道。第一個要加入的通道是RenderPass。這個通道會渲染場景,但不會講渲染結果輸出到屏幕上。
var renderPass = new THREE.RenderPass(scene, camera); ... var composer = new THREE.EffectComposer(webGLRenderer); composer.addPass(renderPass);
接下來我們要添加一個可以將結果輸出到屏幕上的通道。這里使用FilmPass,我們想創建該對象,然后添加到效果組合器中。代碼如下:
var effectFilm = new THREE.FilmPass(0.8, 0.325, 256, false); effectFilm.renderToScreen = true; var composer = new THREE.EffectComposer(webGLRenderer); composer.addPass(effectFilm);
正如你所看到的,我們創建了一個FilmPass對象,並將它的renderToScreen屬性設置為true。最后,還需要修渲染循環:
function render(){ stats.update(); var delta = clock.getDelta(); orbitControl.update(delta); sphere.rotation.y += 0.002; requestAnimationFrame(render); composer.render(delta); }
這個代碼里我們移出了“webGLRenderer.render(scene, camera);”,用“composer.render(delta)”代替。這將調用EffectComposer的render()函數。由於我們已經將FilmPass的renderToScreen屬性設置為true,所以FilmPass的結果將會輸出到屏幕上。
2.后期處理通道
Three.js庫提供了幾個后期處理通道,你可以直接將其添加到EffectComposer對象。下表是這些通道的概覽。
通道/描述
BloomPass/該通道會使得明亮區域滲入較暗的區域。模擬相機找到過多亮點的情形
DotScreenPass/將一層黑點貼到代表原始圖片的屏幕上
FilmPass/通過掃描線和失真模擬電視屏幕
MaskPass/在當前圖片上貼一層掩膜,后續通道只會影響被貼的區域
RenderPass/該通道在指定的場景和相機的基礎上渲染出一個新場景
ShaderPass/使用該通道你可以傳入一個自定義的着色器,用來生成高級的、自定義的后期處理通道
TexturePass/該通道可以將效果組合器的當前狀態保存為一個紋理,然后可以在其他EffectComposer對象中將該紋理作為輸入參數
下面的例子使用了以上通道的中的BloomPass、DotScreenPass、FilmPass、RenderPass、ShaderPass、TexturePass。首先看下運行效果:
上圖標左上角運用BloomPass、右上角運用FilmPass、左下角運用DotScreenPass、右下角運用原始渲染結果。渲染處理代碼如下:
var renderPass = new THREE.RenderPass(scene, camera); //保存渲染結果,但不會輸出到屏幕 var effectCopy = new THREE.ShaderPass(THREE.CopyShader); //傳入了CopyShader着色器,用於拷貝渲染結果 effectCopy.renderToScreen = true; //設置輸出到屏幕上 var bloomPass = new THREE.BloomPass(3, 25, 5.0, 256); //BloomPass通道效果 var effectFilm = new THREE.FilmPass(0.8, 0.325, 256, false); //FilmPass通道效果 effectFilm.renderToScreen = true; //設置輸出到屏幕上 var dotScreenPass = new THREE.DotScreenPass(); // DotScrrenPass通道效果 //渲染目標 var composer = new THREE.EffectComposer(webGLRenderer); composer.addPass(renderPass); composer.addPass(effectCopy); var renderScene = new THREE.TexturePass(composer.renderTarget2); //左下角 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);
代碼原理比較簡單,composer的作用是渲染最原始的效果,傳入了renderPass和ShaderPass,renderPass運來創建一個新場景,ShaderPass用來輸出結果到屏幕。接下來創建了一個類型為TexturePass的通道對象renderScene。可以將當前狀態保存一份作為紋理。供后面的幾個compoer使用。
composer1首先使用renderScene創建新場景,然后添加dotScreenPass通道,最后使用effectCopy輸出渲染。composer2、composer3、composer4相似。設置了處理通道后,在每次循環渲染時還得分別調用每個composer的render函數重新渲染:
function render() { stats.update(); //sphere.rotation.y=step+=0.01; var delta = clock.getDelta(); orbitControls.update(delta); sphere.rotation.y += 0.002; // render using requestAnimationFrame requestAnimationFrame(render); webGLRenderer.autoClear = false; webGLRenderer.clear(); webGLRenderer.setViewport(0, 0, 2 * halfWidth, 2 * halfHeight); composer.render(delta); 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); }
3.各種通道屬性
FilmPass用來創建類似電視的效果。通道屬性的屬性可通過構造函數傳入,也可以直接修改uiniforms上對應的屬性。屬性說明如下:
屬性/描述
noiseIntensity/通過該屬性可以控制屏幕的顆粒程度
scanlineIntensity/FilmPass會在屏幕上添加一些掃描線。通過該屬性,可以指定掃描線的顯著程度
scanlineCount/該屬性可以控制顯示出來的掃描線數量
grayscale/如果設置為true,輸出結果將會轉換為灰度圖
BoomPass用來在場景中添加泛光效果。屬性可通過構造函數傳入,也可直接修改uniforms屬性。屬性說明如下:
屬性/描述
Strength/該屬性定義的是泛光效果強度。值越高,明亮區域越明亮。而且滲入較暗區域的也就越多
kernelSize/該屬性控制的是泛光效果的偏移量
sigma/可以控制泛光的銳利程度。值越高,泛光越模糊
resolution/泛光效果的解析圖。值太低,那么結果的方塊會比較嚴重
DotSreenPass用來將場景輸出成點集。屬性如下:
center/通過center屬性,可以微調點的偏移量
angle/通過angle,可以更改對齊方式
scale/該屬性設置所有點的大小。scale越小,則點越大
4.使用掩膜的高級效果組合器
Three.js庫具有在特定區域應用通道的能力。我們采取如下步驟實現一個特定區域效果。例如下圖的地圖和火星。我們想在火星上應用一個彩色效果、在地球上應用褐色效果。我們采取如下步驟:
1)創建一個座位背景圖的場景。
2)創建一個場景,里邊有一個看上去像地球的球體。
3)創建一個場景,里邊有一個看上去像火星的球體。
4)創建一個EffectComposer對象,將這三個場景渲染到一個圖片里。
5)在渲染成火星的球體上應用一個彩色效果。
6)在渲染成地球的球體上應用褐色效果。
下面是初始化的代碼:
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.set(-10, 15, 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); 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); spotLight2.position.set(550, 100, 550); spotLight2.intensity = 0.6; sceneEarth.add(spotLight); sceneMars.add(spotLight2); var materialColor = new THREE.MeshBasicMaterial({ map: THREE.ImageUtils.loadTexture("../assets/textures/starry-deep-outer-space-galaxy.jpg"), depthWrite: 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); // add the output of the renderer to the html element document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement); var bgPass = new THREE.RenderPass(sceneBG, cameraBG); var renderPass = new THREE.RenderPass(sceneEarth, camera); renderPass.clear = false; var renderPass2 = new THREE.RenderPass(sceneMars, camera); renderPass2.clear = false; var effectCopy = new THREE.ShaderPass(THREE.CopyShader); effectCopy.renderToScreen = true; var clearMash = new THREE.ClearMaskPass(); var earthMask = new THREE.MaskPass(sceneEarth, camera); var marsMask = new THREE.MaskPass(sceneMars, camera); 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); // 添加月球掩膜通道,以后所有的通道效果都只對月球有效,直到clearMask通道 composer.addPass(effectColorify); // 添加顏色着色器通道 composer.addPass(clearMash); //清理掩膜通道 composer.addPass(earthMask); //添加地圖掩膜通道,之后的所有通道效果都只對月球有效,直到clearMash通道 composer.addPass(effectSepia); // 添加一種自定義着色器通道 composer.addPass(clearMash); // 清理掩膜通道 composer.addPass(effectCopy);
下面是渲染代碼:
function render() { webGLRenderer.autoClear = false; stats.update(); var delta = clock.getDelta(); orbitControls.update(delta); sphere.rotation.y += 0.002; sphere2.rotation.y += 0.002; requestAnimationFrame(render); composer.render(delta); }
5.用ShaderPass定制效果
通過ShaderPass,我們可以傳遞一個自定義的着色器,將大量額外的效果應用到場景中。Three.js擴展的着色器主要分為簡單着色器、模糊着色器、高級效果着色器。
簡單着色器列表:
MirrorShader/該着色器可以為部分屏幕創建鏡面效果
HueStaturationShader/該着色器可以改變顏色的色調和飽和度
VignetteShader/該着色器可以添加暈映效果。該效果可以在圖片中央的周圍顯示黑色的邊框
ColorCorrectionShader/通過這個着色器,你可以調整顏色的分布
RGBShiftSader/該着色器可以將構成顏色的紅、綠、藍分開
BrightnessContrasShader/該着色器可以更改圖片的亮度和對比度
ColorifyShader/可以在屏幕上蒙上一層顏色
SepiaShader/可以在屏幕上創建出類似烏賊墨的效果
模糊效果着色器:
HorizontalBlurShader和VerticalBlurShader/這兩個着色器在場景中應用模糊效果
HorizontalTiltShiftShader和VerticalTiltShiftShader/這兩個着色器可以創建出移軸效果。在移軸效果中只有部分圖片顯示得比較銳利,從而創建出一個看上去像是微縮景觀的場景
TriangleBlurShader/該着色器使用基於三角形的方法,咱場景中應用模糊效果
高級效果的着色器:
BleachBypassShader/該着色器可以創建一種漂白效果。在該效果下,圖片上像素是鍍了一層銀
EdgeShader/該着色器可以探測圖片中的銳利邊界,並突出顯示這些邊界
FXAAShader/該着色器可以在后期處理階段應用鋸齒效果。如果在渲染抗鋸齒影響效率,那么久可以使用該着色器
FocusShader/這是一個簡單的着色器,其結果是中央區域渲染得比較銳利,但周圍比較模糊
6.定制自定義着色器
為Three.js庫創建自定義的着色器,需要實現兩個組件:vertexShader和fragmentShader。組件vertexShader可以用來調整每個頂點的位置,組件fragmentShader可以從來決定每個像素的顏色。對於后期處理着色器來說,我們只要實現fragmentShader即可,然后使用Three.js提供的額、默認的vertexShader。
我們以定制一個灰度着色器為例。首先要創建一個js文件,存放着色器源代碼,這里我們命名一個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} }, vertexShader: [ "varying vec2 vUv;", "void main(){", "vUv = uv;", "gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);", "}" ].join("\n"), fragmentShader: [ "uniform float rPower;", "uniform float gPower;", "uniform float bPower;", "uniform sampler2D tDiffuse;", "varying vec2 vUv;", "void main(){", "vec4 texel = texture2D(tDiffuse, vUv);", "float gray = texel.r * rPower + texel.g * gPower + texel.b * bPower;", "gl_FragColor = vec4(vec3(gray), texel.a);", "}" ].join("\n") }
我們為Threee.js創建了一個叫做CustomGrayScaleShader的自定義着色器該對象包含uniforms、vertexShader和fragmentShader三個屬性。uniforms定義了通過javascript外部傳入的變量,vertexShader定義了頂點着色器,fragmentShader定義了片元着色器代碼。
這里需要注意的是頂點着色器中的uv變量,是着色器代碼的內部變量,表示紋理上的texel(紋理上的像素),銅鼓varying vec2 vUv變量傳遞給片元着色器。着色器代碼定義好后,我們就可以通過以下形式在javascript代碼中使用了:
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; 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);
代碼中創建了兩個着色器通道,分別適合shaderPass和bitPass。shaderPass正好使用的是我們創建的自定義着色器THREE.CustomGrayScaleShader。