序文このチュートリアルでは、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 のインストールと設定のチュートリアル
MySQL Binログデータの回復: 誤ってデータベースを削除した場合前書き: テスト マシンで誤っ...
1. MySQL 8.0.12 バージョンのインストール手順。 1. ダウンロードhttps://d...
1. Concat関数。よく使用される接続文字列: concat 関数。たとえば、SQLクエリ条件...
NC のフルネームは Netcat (Network Knife) で、作成者は Hobbit &a...
Xhtml には、あまり使用されないが非常に便利なタグが多数あります。半分の労力で 2 倍の結果を達...
1. protoをコンパイルするすべての .proto ファイルを保存するために、src フォルダー...
誰でも時々データをコピーして貼り付ける必要があると思います。コピーして貼り付けるためにファイルを開く...
目次1.ソケットを作成する2. ソケットをバインドする3. 聞き手を作る。聞く4. 接続が受け入れら...
目次1. スタックの定義2. JSスタックの調査1. スタックとヒープ2. 基本型と参照型3. 値渡...
MySQL を頻繁に使用する人は、次のような状況に遭遇する可能性があります。 1. フィールド タ...
1. MySQLデータベースのエンコーディングを確認する mysql -u ユーザー名 -p パスワ...
1. provideとinjectの説明Provide と Inject により、ネストされたコンポ...
序文:グループ化関数はグループ内の最初のデータを取得しますが、各グループ内の最新のデータを取得する必...
序文この記事は主に CentOS7 で PHP スケジュールタスクを実行することに関する関連コンテン...
まず、Linux システムで実行されているノード プロセスはプロセスを強制終了できないことを紹介しま...