序文このチュートリアルでは、Three.js を使用して 3 つのステップでオブジェクトをガラスのように見せる方法を学習します。 3D オブジェクトをレンダリングする場合、3D ソフトウェアを使用する場合でも、リアルタイム表示に WebGL を使用する場合でも、オブジェクトを表示して目的の外観にするには、常にマテリアルを割り当てる必要があります。 Three.js などのライブラリの既成の手順を使用して、さまざまな種類のマテリアルを模倣できますが、このチュートリアルでは、3 つのオブジェクト (3 つの手順) を使用して、オブジェクトがガラスのように動作するように見せる方法を説明します。 ステップ1: セットアップと前方屈折このデモンストレーションではダイヤモンドジオメトリを使用しますが、単純なボックスやその他のジオメトリを使用することもできます。 プロジェクトを構築しましょう。レンダラー、シーン、パースペクティブカメラ、ジオメトリが必要です。ジオメトリをレンダリングするには、マテリアルを割り当てる必要があります。このマテリアルの作成がこのチュートリアルの主な焦点になります。それでは、基本的な頂点シェーダーとフラグメント シェーダーを使用して新しい ShaderMaterial を作成しましょう。 皆さんの予想に反して、私たちの素材は透明ではありません。実際、ダイヤモンドの背後にあるものはすべてサンプリングして変形します。これを行うには、シーン(ダイヤモンドなし)をテクスチャにレンダリングする必要があります。私は正投影カメラを使用してフルスクリーンの平面をレンダリングしていますが、これは他のオブジェクトで満たされたシーンである可能性もあります。 Three.js で背景ジオメトリを菱形から分割する最も簡単な方法は、「レイヤー」を使用することです。 this.orthoCamera = 新しい THREE.OrthographicCamera( 幅 / - 2, 幅 / 2, 高さ / 2, 高さ / - 2, 1, 1000 ); // カメラをレイヤー 1 に割り当てます (レイヤー 0 がデフォルト) this.orthoCamera.layers.set(1); const tex = loadTexture('texture.jpg') を待機します。 this.quad = 新しい THREE.Mesh(新しい THREE.PlaneBufferGeometry()、新しい THREE.MeshBasicMaterial({map: tex})); this.quad.scale.set(幅、高さ、1); // 平面もレイヤー1に移動します this.quad.layers.set(1); this.scene.add(this.quad); レンダリング ループは次のようになります。 this.envFBO = 新しい THREE.WebGLRenderTarget(幅、高さ); this.renderer.autoClear = false; 与える() { アニメーションフレームをリクエストします( this.render ); this.renderer.clear(); // 背景を fbo にレンダリングする this.renderer.setRenderTarget(this.envFbo); this.renderer.render( this.scene, this.orthoCamera ); // 背景を画面にレンダリングする this.renderer.setRenderTarget(null); this.renderer.render( this.scene, this.orthoCamera ); this.renderer.clearDepth(); // ジオメトリを画面にレンダリングする this.renderer.render( this.scene, this.camera ); }; さて、ここで少し理論をお話ししましょう。ガラスなどの透明な素材は曲げることができるため、見えるようになります。これは、光がガラスを通過する際の速度が空気を通過する際の速度よりも遅いため、光波が斜めのガラス物体に当たると、速度の変化によって光波の方向が変わるためです。この波の方向の変化は屈折の現象を表しています。 これをコードで再現するには、ワールド空間における目のベクトルとダイヤモンドの表面 (法線) ベクトルの間の角度を知る必要があります。これらのベクトルを計算するために頂点シェーダーを更新しましょう。 変化する vec3 eyeVector; 変化する vec3 worldNormal; void main() { vec4 ワールド位置 = modelMatrix * vec4( 位置、 1.0 ); eyeVector = normalize(worldPos.xyz - cameraPosition); ワールドノーマル = 正規化( modelViewMatrix * vec4(normal, 0.0)).xyz; gl_Position = projectionMatrix * modelViewMatrix * vec4(位置、1.0); } フラグメント シェーダーでは、glsl の組み込み屈折関数の最初の 2 つのパラメーターとして eyeVector と worldNormal を使用できるようになりました。 3 番目のパラメータは屈折率の比で、高速媒体 (空気) の屈折率 (IOR) を低速媒体 (ガラス) の IOR で割ったものです。この場合の値は 1.0/1.5 ですが、必要な結果を得るために値を調整できます。たとえば、水の IOR は 1.33 で、ダイヤモンドの IOR は 2.42 です。 均一なサンプラー2D envMap; 均一な vec2 解像度。 変化する vec3 worldNormal; vec3 viewDirection を変更する; void main() { // 画面座標を取得 vec2 uv = gl_FragCoord.xy / 解像度; vec3 法線 = ワールド法線; // 屈折を計算して画面座標に追加します vec3 屈折 = refract(eyeVector, normal, 1.0/ior); uv += 屈折.xy; // 背景テクスチャをサンプリングする vec4 tex = texture2D(envMap, uv); vec4 出力 = tex; gl_FragColor = vec4(出力.rgb、1.0); } 非常に素晴らしい!屈折シェーダーの作成に成功しました。しかし、私たちのダイヤモンドはほとんど見えません...それは、ガラスの視覚的特性の 1 つだけを扱っているからです。すべての光が物質を通過して屈折するわけではなく、実際には一部の光は反射されます。どうやってそれを達成するか見てみましょう! ステップ2: 反射とフレネル方程式簡単にするために、このチュートリアルでは適切な反射を計算せず、反射光として白だけを使用します。さて、いつ反射し、いつ屈折するかをどうやって知るのでしょうか?理論的には、これは材料の屈折率に依存し、入射ベクトルと表面法線の間の角度が臨界角よりも大きい場合、光波は反射されます。 フラグメント シェーダーでは、フレネル方程式を使用して、反射光線と屈折光線の比率を計算します。残念ながら glsl にもこの方程式は組み込まれていませんが、ここからコピーできます: float Fresnel(vec3 eyeVector, vec3 worldNormal) { pow( 1.0 + dot( eyeVector, worldNormal), 3.0 ) を返します。 } これで、先ほど計算したフレネル比に基づいて、屈折テクスチャの色と白い反射の色を簡単にブレンドできるようになりました。 均一なサンプラー2D envMap; 均一な vec2 解像度。 変化する vec3 worldNormal; vec3 viewDirection を変更する; float Fresnel(vec3 eyeVector, vec3 worldNormal) { pow( 1.0 + dot( eyeVector, worldNormal), 3.0 ) を返します。 } void main() { // 画面座標を取得 vec2 uv = gl_FragCoord.xy / 解像度; vec3 法線 = ワールド法線; // 屈折を計算して画面座標に追加します vec3 屈折 = refract(eyeVector, normal, 1.0/ior); uv += 屈折.xy; // 背景テクスチャをサンプリングする vec4 tex = texture2D(envMap, uv); vec4 出力 = tex; // フレネル比を計算する float f = Fresnel(eyeVector, normal); // 屈折色と反射色を混ぜる output.rgb = mix(output.rgb、vec3(1.0)、f); gl_FragColor = vec4(出力.rgb、1.0); } 見た目はかなり良くなりましたが、まだいくつか欠点があります... 透明なオブジェクトの反対側が見えません。これを直しましょう! ステップ3: 多面屈折これまで反射と屈折について学んだことから、光は物体から出ていく前に物体内部で何度も往復する可能性があることがわかります。 物理的に正しい結果を得るには、すべての光線をトレースする必要がありますが、残念ながら、これは計算負荷が大きすぎてリアルタイムでレンダリングできません。そこで、ダイヤモンドの裏側を少なくとも視覚的に確認するための簡単な近似値を紹介します。 フラグメント シェーダーでは、ジオメトリの前面と背面のワールド法線が必要です。両側を同時にレンダリングすることはできないため、最初に背面の法線をテクスチャにレンダリングする必要があります。 手順 1 と同じように新しい ShaderMaterial を作成しますが、今回はワールド法線を gl_FragColor としてレンダリングします。 変化する vec3 worldNormal; void main() { gl_FragColor = vec4(ワールドノーマル、1.0); } 次に、レンダリング ループを更新して、バックフェース パスを含めます。 this.backfaceFbo = 新しい THREE.WebGLRenderTarget(幅、高さ); ... 与える() { アニメーションフレームをリクエストします( this.render ); this.renderer.clear(); // 背景を fbo にレンダリングする this.renderer.setRenderTarget(this.envFbo); this.renderer.render( this.scene, this.orthoCamera ); // ダイヤモンドの裏面を fbo にレンダリングします this.mesh.material = this.backfaceMaterial; this.renderer.setRenderTarget(this.backfaceFbo); this.renderer.clearDepth(); this.renderer.render( this.scene, this.camera ); // 背景を画面にレンダリングする this.renderer.setRenderTarget(null); this.renderer.render( this.scene, this.orthoCamera ); this.renderer.clearDepth(); // 屈折マテリアル付きのダイヤモンドを画面にレンダリングする this.mesh.material = this.refractionMaterial; this.renderer.render( this.scene, this.camera ); }; ここで、屈折マテリアルの背面法線テクスチャをサンプリングします。 vec3 backfaceNormal = texture2D(backfaceMap, uv).rgb; 最後に、前面法線と背面法線を結合します。 浮動小数点数 a = 0.33; vec3 法線 = worldNormal * (1.0 - a) - backfaceNormal * a; この式では、a は、背面法線をどの程度適用するかを示すスカラー値にすぎません。 やったー!私たちがダイヤモンドのすべての面を見ることができるのは、ダイヤモンドの材質によって屈折したり反射したりしているからです。 制限すでに説明したように、このアプローチでは物理的に正しい透明なマテリアルをリアルタイムでレンダリングすることは不可能です。複数のガラス オブジェクトを互いの前にレンダリングするときに、別の問題が発生します。環境を一度だけサンプリングするため、オブジェクトのチェーンを透視することはできません。最後に、ここで示したスクリーン空間屈折は、キャンバスの端の近くではうまく機能しません。これは、光が境界外の値に屈折する可能性があり、背景シーンをレンダリング ターゲットにレンダリングするときにそのデータをキャプチャしなかったためです。 もちろん、これらの制限を克服する方法はありますが、それらはすべて WebGL でのリアルタイム レンダリングに適したソリューションではない可能性があります。 以上が、threejsを使用してリアルタイムポリゴン屈折を実現する方法の詳細です。JSライブラリの詳細については、123WORDPRESS.COMの他の関連記事に注目してください。 以下もご興味があるかもしれません:
|
<<: Python スクリプトを Ubuntu で直接実行する方法
>>: Windows での MySQL 5.7.10 のインストールと設定のチュートリアル
1. nginx はなぜ gzip を使用するのですか? 1. 圧縮の役割:ページがgzipで圧縮さ...
目次1. MySQL msiバージョンをダウンロードする2. インストール3. 環境変数を設定する1...
この記事の例では、ミニプログラムでリストカウントダウンを実装するための具体的なコードを参考までに共有...
擬似配列と配列JavaScript では、5 つのプリミティブ データ型を除き、関数を含め、その他す...
目次概要0. JavaScriptとWeb開発の基礎1. Vueの基本概念Vue コア機能コンポーネ...
MySQL グリーンバージョン設定コードと 1067 エラーMySQL エンコーディングを表示 ...
目次関数パラメータの2つの主要なカテゴリ位置パラメータ可変長パラメータ名前空間要約する関数パラメータ...
execute、executeUpdate、executeQuery の違い (およびそれらの戻り値...
1. 設置環境1. HUAWEI mate x CPU i5 82500u、8g メモリ、独立グラフ...
1. web01にzabbix-agentをインストールするZabbix ウェアハウスをデプロイする...
<br />1998年に最初の個人ページが誕生してから2008年の今日まで、デザイン業界...
最近、Microsoft は Docker をネイティブにサポートする Windows Server...
導入Dockerfile ビルドの実行は、単一のコンテナの手動操作です。マイクロサービス アーキテク...
この記事ではJavaScript検索のデータ表示コードを参考までに共有します。具体的な内容は以下のと...
序文最近、高可用性プロジェクトに取り組む際には、データの同期が必要になっています。ノードが 2 つし...