リソースPhyramidの教材。サイトの見出しはまったく同じです。
2014年にサイトを更新し、3D Maxの幾何学的図形で構成される帽子の3次元背景を作成しました。 しかし、その後、JSでリアルタイムに生成する方がはるかにクールだと考えました。 すぐに言ってすぐに、素晴らしいthree.jsフレームワークの助けを借りて、簡単な小さなスケッチを作成しました。 そして、ここにそれがどうだったかがあります。
コードのスタイルに関する注意:最初は機能的なスタイルのみを使用したかったのですが、Webの特性とアルゴリズムの動作のために、OOPに切り替えました。
表面を作成する
最初のステップは、シーンの主要部分を作成することでした。 これを行うために、セグメントが100x100の平面を作成し、頂点をランダムにシフトしました。 重要な点は、頂点が変更され、ライティングを再計算する必要があることをthree.jsが認識できるように、geometry.dynamic = trueおよびgeometry.normalsNeedUpdate = trueを設定する必要があることです。
var makePlaneGeometry = function(width, height, widthSegments, heightSegments) { var geometry = new THREE.PlaneGeometry(width, height, widthSegments, heightSegments); var X_OFFSET_DAMPEN = 0.5; var Y_OFFSET_DAMPEN = 0.1; var Z_OFFSET_DAMPEN = 0.1; var randSign = function() { return (Math.random() > 0.5) ? 1 : -1; }; for (var vertIndex = 0; vertIndex < geometry.vertices.length; vertIndex++) { geometry.vertices[vertIndex].x += Math.random() / X_OFFSET_DAMPEN * randSign(); geometry.vertices[vertIndex].y += Math.random() / Y_OFFSET_DAMPEN * randSign(); geometry.vertices[vertIndex].z += Math.random() / Z_OFFSET_DAMPEN * randSign(); } geometry.dynamic = true; geometry.computeFaceNormals(); geometry.computeVertexNormals(); geometry.normalsNeedUpdate = true; return geometry; }; var makePlane = function(geometry) { var material = new THREE.MeshBasicMaterial({color: 0x00576b, wireframe: true}); var plane = new THREE.Mesh(geometry, material); return plane; }; var init = function(container, viewWidth, viewHeight) { var scene = makeScene(); // (...) var plane = makePlane(makePlaneGeometry(400, 400, 100, 100)); scene.add(plane); // (...) };
フレームで遊ぶ
シンプルなワイヤフレームマテリアルは、モデルの視覚化に役立ちました。
var material = new THREE.MeshBasicMaterial({color: 0x00576b, wireframe: true});
TrackballControls.jsは、シーン内を移動するために使用されました。 そして、結果として得られたものは次のとおりです。
クールですが、まだ磨かれていません。 実際の素材と光を追加します。
マテリアルとライトを追加する
目的の外観を実現するには、アンビエントオクルージョンシェーディングモデルが必要でした。 さらに、スムージングせずにモデルのエッジを表示する必要があります。 したがって、フラットシェーディングランバートは完全に適合します。
var material = new THREE.MeshLambertMaterial({color: 0xffffff, shading: THREE.FlatShading});
2つの光源が使用されました。 最初の-アンビエントは、均一な照明のために配置されました。 2番目の監督は、モデルに多角形の外観を与えるこれらすべてのクールな影を作成しました。
var makeLights = function() { var ambientLight = new THREE.AmbientLight(0x1a1a1a); this.scene.add(ambientLight); var dirLight = new THREE.DirectionalLight(0xdfe8ef, 0.09); dirLight.position.set(5, 2, 1); this.scene.add(dirLight); };
カメラの配置
45度の角度から平面を見ているカメラを配置したかったのですが、これは非常に簡単です。 カメラで遊んだ後、75度の角度を選択しました。これにより、「山の頂上から」の観察効果が得られます。
var camera = new THREE.PerspectiveCamera(fov, aspectRatio, 0.1, 1000); camera.up = new THREE.Vector3(0, 1, 0); camera.rotation.x = 75 * Math.PI / 180; camera.position.z = zPos;
視野は、Quake 180度でFOVを設定するときのように、広いキャンバス上でシーンが奇妙に見えるため、問題を引き起こしました。 画面解像度に基づいてFOVを大まかに計算するコードを作成しました。
ヘイズとアルファブレンディング
画像はすでに目標に似始めていますが、1つの問題があります。 平面の境界がはっきりと見えます。 これは、カメラを見下ろしたときの典型的な例です。
まず、平面を球体に変換し、球体の内側の中央にカメラを配置したいと考えました。 このアプローチは問題を解決するように見えましたが、表面はまだそのようには見えず、ポールの折り目に集まっていました。
解決策は、指数関数的なヘイズを追加することでした。 アルファブレンディングをオンにした後、ヘイズはスプラッシュスクリーンの背景にスムーズに移行し、クールな効果をもたらしました。
var renderer = new THREE.WebGLRenderer({antialiasing: true, alpha: true}); (...) scene.fog = new THREE.FogExp2(0x222228, 0.003);
以下は、ヘイズ効果が強化された画像です。
インタラクティブ機能(パート1-マウス)
最後に、シーンは正しく見え始めましたが、制御はまだ不完全でした。 TrackballControlsを使用すると、ステージ内を自由に移動できますが、Z軸を中心に回転する必要があるだけでした。
ユーザーがマウスを移動するとき、自動回転をオフにし、次のフレームのZ周りの回転に追加するためにマウスが移動した距離を覚えておく必要があります。
var registerMouseMove = function(event) { this.autorotation = false; var mouseXOnMouseMove = event.clientX - (this.width / 2); var MOUSE_MOVE_DAMPENING = 0.0075; this.targetRotation = this.targetRotationOnMouseDown + (mouseXOnMouseMove - this.mouseXOnMouseDown) * MOUSE_MOVE_DAMPENING; };
また、クリックハンドラーも必要です。これにより、ユーザーがマウスボタンを押した場合にのみ動きが登録されるようになります(そして、距離を計算するためにマウスの初期位置を記憶します)。
var registerMouseDown = function(event) { startMouseMovementDetection(); this.mouseXOnMouseDown = event.clientX - (this.width / 2); this.targetRotationOnMouseDown = this.targetRotation; };
残っているのは、ターン自体を行うことです。
if (this.autorotation) { this.object.rotation.z += OBJECT_AUTOROTATION_AMOUNT; } else { this.object.rotation.z -= (this.targetRotation + this.object.rotation.z) * TARGET_ROTATION_DAMPENING; }
移動に別の制限を追加しました。オブジェクトの移動が遅すぎる場合、ノイズまたは最後のドラッグによる残留現象であると見なし、回転方法を自動回転の状態に戻します。
if (Math.abs(this.targetRotation + this.object.rotation.z) < OBJECT_ROTATION_THRESHOLD) { this.autorotation = true; }
双方向性(パート2-タッチ)
ほぼ完了! タッチコントロールで何か他のことをする必要があります。 マウス制御とほぼ同じように機能します。
var registerTouchDown = function(event) { if (event.touches.length === 1) { this.mouseXOnMouseDown = event.touches[0].pageX - (this.width / 2); this.mouseYOnMouseDown = event.touches[0].pageY - (this.height / 2); this.targetRotationOnMouseDown = this.targetRotation; } }
しかし、問題があります。 タッチスクリーンデバイスでは、シーンの移動を担当するジェスチャがページのスクロールも担当します。 実際にはスクロールをオフにしているため、これは処理に悪影響を及ぼしました。
このため、引っ張る方向を確認する必要がありました。 ほぼ水平の場合、平面を回転させます。 ほぼ垂直の場合は、何もせず、デフォルトのイベントを発生させました。
function registerTouchMove(event) { if (event.touches.length === 1) { var MOUSE_MOVE_DAMPENING = 0.01; this.autorotation = false; var mouseXOnMouseMove = event.touches[0].pageX - (this.width / 2); var mouseYOnMouseMove = event.touches[0].pageY - (this.height / 2); var xDiff = mouseXOnMouseMove - this.mouseXOnMouseDown; var yDiff = mouseYOnMouseMove - this.mouseYOnMouseDown; if (Math.abs(xDiff) > Math.abs(yDiff)) { event.preventDefault(); this.targetRotation = this.targetRotationOnMouseDown + xDiff * MOUSE_MOVE_DAMPENING; } } }
サイズ変更時の解像度のサイズ変更
最後になりましたが、ブラウザーウィンドウのサイズを変更するときに画像全体を動的に更新する機能。
var updateDimensions = function() { this.width = this.container.offsetWidth; this.height = this.container.offsetHeight; var aspectRatio = this.width / this.height; var fov = fovForAspectRatio(aspectRatio); var zPos = cameraZPositionForFov(fov); this.camera.aspect = aspectRatio; this.camera.fov = fov; this.camera.position.z = zPos; this.camera.updateProjectionMatrix(); this.renderer.setSize(this.width, this.height); };
結果
できた! 全画面を表示するとどのようになるかを示します(404ページに全画面表示があります)。 生きている例 。
3次元のタイトルを作成することは非常にエキサイティングな体験であり、three.jsの力に感銘を受けました。 この記事が似たようなものの作成に役立つことを願っています。