接着上篇 ThreeJS系列1_CinematicCameraJS插件介紹html
// 描述信息 this.type = 'CinematicCamera';
this.shaderSettings = { rings: 3, samples: 4 };
this.postprocessing.materialBokeh = new ShaderMaterial( { //...忽略代碼 defines: { RINGS: this.shaderSettings.rings, SAMPLES: this.shaderSettings.samples, DEPTH_PACKING: 1 } } );
shaderSettings 最終做爲 postprocessing.materialBokeh 的值使用vue
shaderSettings --> postprocessing.materialBokeh小程序
// ShaderMaterial 使用自定義shader渲染的材質。 shader是一個用GLSL編寫的小程序 ,在GPU上運行。 this.materialDepth = new ShaderMaterial( { uniforms: depthShader.uniforms, vertexShader: depthShader.vertexShader, fragmentShader: depthShader.fragmentShader } ); this.materialDepth.uniforms[ 'mNear' ].value = near; this.materialDepth.uniforms[ 'mFar' ].value = far;
// scene.overrideMaterial: // 若是不爲空,它將強制場景中的每一個物體使用這裏的材質來渲染。默認值爲null。 scene.overrideMaterial = this.materialDepth;
當作 scene.overrideMaterialapp
this.postprocessing = { enabled: true };
// 主要在這個方法中進行更多的初始化 this.initPostProcessing(); ... // 在這個方法中進行聚焦操做 CinematicCamera.prototype.focusAt = function ( focusDistance ) { ... this.postprocessing.bokeh_uniforms[ 'focalDepth' ].value = this.ldistance; };
CinematicCamera主要參數在postprocessing中設置dom
// 提供fnumber和coc(混淆圈)做爲額外參數 CinematicCamera.prototype.setLens = function ( focalLength, filmGauge, fNumber, coc ) { // 對於cinematicCamera來講,擁有一個默認的鏡頭設置很重要 if ( focalLength === undefined ) focalLength = 35; if ( filmGauge !== undefined ) this.filmGauge = filmGauge; this.setFocalLength( focalLength ); // 若是沒有提供fnumber和coc, cinematicCamera嘗試充當一個基本的PerspectiveCamera if ( fNumber === undefined ) fNumber = 8; if ( coc === undefined ) coc = 0.019; this.fNumber = fNumber; this.coc = coc; // fNumber是光圈對焦的 this.aperture = focalLength / this.fNumber; // 超過焦距的鏡頭時須要計算depthOfField試圖集中在遠處給fNumber和focalLength this.hyperFocal = ( focalLength * focalLength ) / ( this.aperture * this.coc ); };
做用: 初始化相機焦距相關ide
// 距相機較遠的對焦功能, focusDistance 表示對焦物體到相機距離 CinematicCamera.prototype.focusAt = function ( focusDistance ) { if ( focusDistance === undefined ) focusDistance = 20; var focalLength = this.getFocalLength(); // 與相機之間的距離(正常狀況下爲函數集)來聚焦 this.focus = focusDistance; // 距相機最近的對焦點(未使用) this.nearPoint = ( this.hyperFocal * this.focus ) / ( this.hyperFocal + ( this.focus - focalLength ) ); // 距相機最遠的對焦點(未使用) this.farPoint = ( this.hyperFocal * this.focus ) / ( this.hyperFocal - ( this.focus - focalLength ) ); // 全部東西都集中在裏面的空間的間隙或寬度(未使用) this.depthOfField = this.farPoint - this.nearPoint; // 考慮標準鏡頭的最小焦距(未使用) if ( this.depthOfField < 0 ) this.depthOfField = 0; this.sdistance = this.smoothstep( this.near, this.far, this.focus ); this.ldistance = this.linearize( 1 - this.sdistance ); this.postprocessing.bokeh_uniforms[ 'focalDepth' ].value = this.ldistance; }; // 線性化, 具體原理我也不懂 CinematicCamera.prototype.linearize = function ( depth ) { var zfar = this.far; var znear = this.near; return - zfar * znear / ( depth * ( zfar - znear ) - zfar ); }; // 平滑處理 CinematicCamera.prototype.smoothstep = function ( near, far, depth ) { var x = this.saturate( ( depth - near ) / ( far - near ) ); return x * x * ( 3 - 2 * x ); }; // 判斷x是否在0-1之間, 若x>1, 返回1; 若x<0, 返回0; 若x在0-1, 返回x CinematicCamera.prototype.saturate = function ( x ) { return Math.max( 0, Math.min( 1, x ) ); };
做用: 修改相機的焦距, 雖然原理可能看不懂, 可是使用起來仍是十分簡單的: focusAt(焦距)函數
CinematicCamera.prototype.initPostProcessing = function () { // 判斷是否啓用postprocessing(後置處理), 不啓用, 初始化直接結束, this指實例化相機對象 if ( this.postprocessing.enabled ) { this.postprocessing.scene = new Scene(); this.postprocessing.camera = new OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, - 10000, 10000 ); this.postprocessing.scene.add( this.postprocessing.camera ); var pars = { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBFormat }; // WebGLRenderTarget: render target是一個緩衝,就是在這個緩衝中,視頻卡爲正在後臺渲染的場景繪製 // 像素。 它用於不一樣的效果,例如用於在一個圖像顯示在屏幕上以前先作一些處理。 this.postprocessing.rtTextureDepth = new WebGLRenderTarget( window.innerWidth, window.innerHeight, pars ); this.postprocessing.rtTextureColor = new WebGLRenderTarget( window.innerWidth, window.innerHeight, pars ); var bokeh_shader = BokehShader; this.postprocessing.bokeh_uniforms = UniformsUtils.clone( bokeh_shader.uniforms ); // 能夠設置的參數以下所示 this.postprocessing.bokeh_uniforms[ "tColor" ].value = this.postprocessing.rtTextureColor.texture; this.postprocessing.bokeh_uniforms[ "tDepth" ].value = this.postprocessing.rtTextureDepth.texture; this.postprocessing.bokeh_uniforms[ "manualdof" ].value = 0; this.postprocessing.bokeh_uniforms[ "shaderFocus" ].value = 0; this.postprocessing.bokeh_uniforms[ "fstop" ].value = 2.8; this.postprocessing.bokeh_uniforms[ "showFocus" ].value = 1; this.postprocessing.bokeh_uniforms[ "focalDepth" ].value = 0.1; //console.log( this.postprocessing.bokeh_uniforms[ "focalDepth" ].value ); this.postprocessing.bokeh_uniforms[ "znear" ].value = this.near; this.postprocessing.bokeh_uniforms[ "zfar" ].value = this.near; this.postprocessing.bokeh_uniforms[ "textureWidth" ].value = window.innerWidth; this.postprocessing.bokeh_uniforms[ "textureHeight" ].value = window.innerHeight; this.postprocessing.materialBokeh = new ShaderMaterial( { uniforms: this.postprocessing.bokeh_uniforms, vertexShader: bokeh_shader.vertexShader, fragmentShader: bokeh_shader.fragmentShader, defines: { RINGS: this.shaderSettings.rings, SAMPLES: this.shaderSettings.samples, DEPTH_PACKING: 1 } } ); this.postprocessing.quad = new Mesh( new PlaneBufferGeometry( window.innerWidth, window.innerHeight ), this.postprocessing.materialBokeh ); this.postprocessing.quad.position.z = - 500; this.postprocessing.scene.add( this.postprocessing.quad ); } };
加入啓用了postprocessing, 那麼使用這個方法渲染場景, 代替renderer.render(scene, camera)post
CinematicCamera.prototype.renderCinematic = function ( scene, renderer ) { if ( this.postprocessing.enabled ) { var currentRenderTarget = renderer.getRenderTarget(); renderer.clear(); // Render scene into texture scene.overrideMaterial = null; renderer.setRenderTarget( this.postprocessing.rtTextureColor ); renderer.clear(); renderer.render( scene, this ); // Render depth into texture scene.overrideMaterial = this.materialDepth; renderer.setRenderTarget( this.postprocessing.rtTextureDepth ); renderer.clear(); renderer.render( scene, this ); // Render bokeh composite renderer.setRenderTarget( null ); renderer.render( this.postprocessing.scene, this.postprocessing.camera ); renderer.setRenderTarget( currentRenderTarget ); } };
- 能夠更改的屬性
- 使用步驟
camera = new CinematicCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
// 初始化焦距 camera.setLens(5); // 初始化位置 camera.position.set(2, 1, 500);
建立屬性對象, 下面全部均可以設置ui
let effectController = { // BokehDepthShader中屬性 focalLength: 15, // jsDepthCalculation: true, // shaderFocus: false, fstop: 2.8, // maxblur: 1.0, showFocus: false, focalDepth: 3, // manualdof: false, // vignetting: false, // depthblur: false, // // threshold: 0.5, // gain: 2.0, // bias: 0.5, // fringe: 0.7, // // focalLength: 22, // noise: true, // pentagon: false, // // dithering: 0.0001 };
將屬性對象賦值到相機上this
let matChanger = function(){ // 遍歷屬性對象, 賦值到相機的屬性上 for(let e in effectController){ if( e in camera.postprocessing.bokeh_uniforms){ camera.postprocessing.bokeh_uniforms[e].value = effectController[e]; } } camera.postprocessing.bokeh_uniforms[ 'znear' ].value = camera.near; camera.postprocessing.bokeh_uniforms[ 'zfar' ].value = camera.far; camera.setLens( effectController.focalLength, camera.frameHeight, effectController.fstop, camera.coc ); effectController[ 'focalDepth' ] = camera.postprocessing.bokeh_uniforms[ 'focalDepth' ].value; }; // 執行方法 matChanger();
camera.focusAt(targetDistance);
<template> <div ref="container"> </div> </template> <script> import * as THREE from 'three'; import {OrbitControls} from "../../assets/examples/jsm/controls/OrbitControls"; import Stats from "../../assets/examples/jsm/libs/stats.module"; import {CinematicCamera} from "../../assets/examples/jsm/cameras/CinematicCamera"; import {GUI} from "../../assets/examples/jsm/libs/dat.gui.module"; let scene, renderer; let camera; let container, stats; let raycaster, mouse = new THREE.Vector2(), INTERSECTED; let radius = 100, theta = 0; function init() { container = this.$refs.container; scene = new THREE.Scene(); scene.background = new THREE.Color( 0xf0f0f0 ); renderer = new THREE.WebGLRenderer({ antialias: true, }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); container.appendChild(renderer.domElement); // camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight); // camera.position.set(4, 5, 6); camera = new CinematicCamera(60, window.innerWidth / window.innerHeight, 1, 1000); // 重寫了一個PerspectiveCamera不要的方法 camera.setLens(5); camera.position.set(2, 1, 500); // let orbitControls = new OrbitControls(camera, renderer.domElement); // scene.add(new THREE.AxesHelper(5)); stats = new Stats(); container.appendChild(stats.dom); // light scene.add( new THREE.AmbientLight( 0xffffff, 0.3 ) ); var light = new THREE.DirectionalLight( 0xffffff, 0.35 ); light.position.set( 1, 1, 1 ).normalize(); scene.add( light ); var geometry = new THREE.SphereBufferGeometry(10, 32, 32); // var geometry = new THREE.BoxBufferGeometry( 20, 20, 20 ); for ( var i = 0; i < 1500; i ++ ) { // 不同的顏色 var object = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial( { color: Math.random() * 0xffffff } ) ); // 位置 -400到400 object.position.x = Math.random() * 800 - 400; object.position.y = Math.random() * 800 - 400; object.position.z = Math.random() * 800 - 400; scene.add( object ); } raycaster = new THREE.Raycaster(); let effectController = { // BokehDepthShader中屬性 focalLength: 15, // jsDepthCalculation: true, // shaderFocus: false, fstop: 2.8, // maxblur: 1.0, showFocus: false, focalDepth: 3, // manualdof: false, // vignetting: false, // depthblur: false, // // threshold: 0.5, // gain: 2.0, // bias: 0.5, // fringe: 0.7, // // focalLength: 22, // noise: true, // pentagon: false, // // dithering: 0.0001 }; let matChanger = function(){ for(let e in effectController){ if( e in camera.postprocessing.bokeh_uniforms){ camera.postprocessing.bokeh_uniforms[e].value = effectController[e]; } } camera.postprocessing.bokeh_uniforms[ 'znear' ].value = camera.near; camera.postprocessing.bokeh_uniforms[ 'zfar' ].value = camera.far; camera.setLens( effectController.focalLength, camera.frameHeight, effectController.fstop, camera.coc ); effectController[ 'focalDepth' ] = camera.postprocessing.bokeh_uniforms[ 'focalDepth' ].value; }; matChanger(); this.camera = camera; window.addEventListener('resize', onResize, false); window.addEventListener('mousemove', onMousemove, false); } function onMousemove(event) { // 取消事件的默認動做 event.preventDefault(); mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; } function onResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } function animation() { stats.update(); render(); // renderer.render(scene, camera); requestAnimationFrame(animation); } function render() { theta += 0.1; // MathUtils.degToRad(theta) 將度轉化爲弧度 camera.position.x = radius * Math.sin(THREE.MathUtils.degToRad(theta)); camera.position.y = radius * Math.sin(THREE.MathUtils.degToRad(theta)); camera.position.z = radius * Math.cos(THREE.MathUtils.degToRad(theta)); camera.lookAt(scene.position); // 更新物體及後代的全局變換 camera.updateMatrixWorld(); raycaster.setFromCamera(mouse, camera); let intersects = raycaster.intersectObjects(scene.children); if (intersects.length > 0) { let targetDistance = intersects[0].distance; camera.focusAt(targetDistance); if (INTERSECTED != intersects[0].object) { if(INTERSECTED) INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex ); INTERSECTED = intersects[ 0 ].object; INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex(); // MeshLambertMaterial的屬性emissive // 材質的放射(光)顏色,基本上是不受其餘光照影響的固有顏色。默認爲黑色。 INTERSECTED.material.emissive.setHex( 0xff0000 ); } }else { if(INTERSECTED) INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex ); INTERSECTED = null; } if (camera.postprocessing.enabled) { camera.renderCinematic(scene, renderer); }else { scene.overrideMaterial = null; renderer.clear(); renderer.render(scene, camera); } } export default { name: "CameraCinematic", data(){ return { effectController: { }, camera: {}, } }, mounted() { init.call(this); animation(); } } </script> <style scoped> </style>