ホームページ  >  記事  >  ウェブフロントエンド  >  three.js は GPU を使用してオブジェクトを選択し、交差位置を計算します

three.js は GPU を使用してオブジェクトを選択し、交差位置を計算します

angryTom
angryTom転載
2019-11-29 14:25:553187ブラウズ

three.js は GPU を使用してオブジェクトを選択し、交差位置を計算します

レイキャスティング方法

three.js に付属する Raycaster を使用してオブジェクトを選択するのは非常に簡単です。コードは次のとおりです。

var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
function onMouseMove(event) {   
 // 计算鼠标所在位置的设备坐标
    // 三个坐标分量都是-1到1
    mouse.x = event.clientX / window.innerWidth * 2 - 1;
    mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
}
function pick() {    
    // 使用相机和鼠标位置更新选取光线    
    raycaster.setFromCamera(mouse, camera);    
    // 计算与选取光线相交的物体
    var intersects = raycaster.intersectObjects(scene.children);
}

[関連コースの推奨事項: JavaScript ビデオ チュートリアル ]

バウンディング ボックス フィルタリングを使用して、投影光線と各三角形のサーフェス要素を計算します。交差点が達成されます。

ただし、モデルが 400,000 個の面など非常に大きい場合、オブジェクトの選択とトラバースによる衝突ポイントの位置の計算は非常に遅くなり、ユーザー エクスペリエンスは良くありません。

しかし、GPU を使用してオブジェクトを選択すると、この問題は発生しません。シーンやモデルがどんなに大きくても、オブジェクトの位置とマウスポイントの交点を1フレーム内で取得できます。

GPU を使用してオブジェクトを選択する

実装方法は非常に簡単です:

1. 選択マテリアルを作成し、各モデルのマテリアルを置き換えます。色の違うシーン。

2. マウス位置のピクセルの色を読み取り、その色に基づいてマウス位置のオブジェクトを決定します。

具体的な実装コード:

1. 選択したマテリアルを作成し、シーンを走査し、シーン内の各モデルを別の色に置き換えます。

let maxHexColor = 1;// 更换选取材质
scene.traverseVisible(n => {    
if (!(n instanceof THREE.Mesh)) {
        return;
    }
    n.oldMaterial = n.material;
        if (n.pickMaterial) { // 已经创建过选取材质了
        n.material = n.pickMaterial;
                return;
    }
    let material = new THREE.ShaderMaterial({
        vertexShader: PickVertexShader,
        fragmentShader: PickFragmentShader,
        uniforms: {
            pickColor: {
                value: new THREE.Color(maxHexColor)
            }
        }
    });
    n.pickColor = maxHexColor;
    maxHexColor++;
    n.material = n.pickMaterial = material;
});
 
PickVertexShader:
void main() {
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
 
PickFragmentShader:
uniform vec3 pickColor;void main() {
    gl_FragColor = vec4(pickColor, 1.0);
}

2. WebGLRenderTarget 上にシーンを描画し、マウス位置の色を読み取り、選択されているオブジェクトを決定します。

let renderTarget = new THREE.WebGLRenderTarget(width, height);
let pixel = new Uint8Array(4);// 绘制并读取像素
renderer.setRenderTarget(renderTarget);
renderer.clear();
renderer.render(scene, camera);
renderer.readRenderTargetPixels(renderTarget, offsetX, height - offsetY, 1, 1, pixel); // 读取鼠标所在位置颜色
// 还原原来材质,并获取选中物体
const currentColor = pixel[0] * 0xffff + pixel[1] * 0xff + pixel[2];
let selected = null;

scene.traverseVisible(n => {
    if (!(n instanceof THREE.Mesh)) {
            return;
    }
        if (n.pickMaterial && n.pickColor === currentColor) {
         // 颜色相同

        selected = n; // 鼠标所在位置的物体    
        }
        if (n.oldMaterial) {
            n.material = n.oldMaterial;        delete n.oldMaterial;
        }
});

説明: offsetX と offsetY はマウスの位置、height はキャンバスの高さです。 readRenderTargetPixels 行の意味は、幅 1、高さ 1 のマウス位置 (offsetX、高さ - offsetY) のピクセルの色を選択することです。

pixel は Uint8Array(4) で、rgba カラーの 4 つのチャネルがそれぞれ格納され、各チャネルの値の範囲は 0 ~ 255 です。

完全な実装コード: https://gitee.com/tengge1/ShadowEditor/blob/master/ShadowEditor.Web/src/event/GPUPickEvent.js

GPU を使用して取得します交差点の位置

実装方法も非常に簡単です:

1. 深度シェーダー マテリアルを作成し、シーンの深度を WebGLRenderTarget にレンダリングします。

2. マウス位置の深さを計算し、マウスの位置と深さに基づいて交差位置を計算します。

具体的な実装コード:

1. 深度シェーダー マテリアルを作成し、特定の方法で深度情報をエンコードし、それを WebGLRenderTarget にレンダリングします。

深度マテリアル:

const depthMaterial = new THREE.ShaderMaterial({
    vertexShader: DepthVertexShader,
    fragmentShader: DepthFragmentShader,
    uniforms: {
        far: {
            value: camera.far
        }
    }
});
DepthVertexShader:
precision highp float;
uniform float far;
varying float depth;void main() {
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    depth = gl_Position.z / far;
}
DepthFragmentShader:
precision highp float;
varying float depth;void main() {
    float hex = abs(depth) * 16777215.0; // 0xffffff
    float r = floor(hex / 65535.0);
    float g = floor((hex - r * 65535.0) / 255.0);    
    float b = floor(hex - r * 65535.0 - g * 255.0);    
    float a = sign(depth) >= 0.0 ? 1.0 : 0.0; // depth大于等于0,为1.0;小于0,为0.0。
    gl_FragColor = vec4(r / 255.0, g / 255.0, b / 255.0, a);
}

重要な注意事項:

a. gl_Position.z はカメラ空間の深度であり、線形で、cameraNear から CameraFar までの範囲です。シェーダの可変変数は補間に直接使用できます。

b. gl_Position.z/far の理由は、色として出力しやすいように値を 0 ~ 1 の範囲に変換するためです。

c. スクリーン空間で深度を使用することはできません。透視投影後、深度は -1 ~ 1 になり、そのほとんどは 1 に非常に近い値 (0.9 以上) です。線形ではなく、ほとんど変化しません。出力色はほとんど変化せず、非常に不正確です。

d. フラグメント シェーダーで深度メソッドを取得します: カメラ空間深度は gl_FragCoord.z、スクリーン空間深度は gl_FragCoord.z / gl_FragCoord.w です。

e. 上記はすべて透視投影の場合ですが、正投影では gl_Position.w は 1 となり、カメラ空間とスクリーン空間の深度は同じになります。

f. 奥行きをできるだけ正確に出力するために、rgb の 3 つの成分を使用して奥行きを出力します。 gl_Position.z/far の範囲は 0 ~ 1 で、0xffffff を乗算し、RGB カラー値に変換します。r 成分 1 は 65535、g 成分 1 は 255、b 成分 1 は 1 を表します。

完全な実装コード: https://gitee.com/tengge1/ShadowEditor/blob/master/ShadowEditor.Web/src/event/GPUPickEvent.js

2. マウス位置の読み取り色、読み取ったカラー値をカメラ空間深度値に復元します。

a. WebGLRenderTarget で「暗号化された」深度を描画します。カラーの読み取り方法

let renderTarget = new THREE.WebGLRenderTarget(width, height);
let pixel = new Uint8Array(4);
scene.overrideMaterial = this.depthMaterial;
renderer.setRenderTarget(renderTarget);
renderer.clear();
renderer.render(scene, camera);
renderer.readRenderTargetPixels(renderTarget, offsetX, height - offsetY, 1, 1, pixel);

説明: offsetX と offsetY はマウスの位置、height はキャンバスの高さです。 readRenderTargetPixels 行の意味は、幅 1、高さ 1 のマウス位置 (offsetX、高さ - offsetY) のピクセルの色を選択することです。

pixel は Uint8Array(4) で、rgba カラーの 4 つのチャネルがそれぞれ格納され、各チャネルの値の範囲は 0 ~ 255 です。

b.「暗号化された」カメラ空間深度値を「復号」して、正しいカメラ空間深度値を取得します。

if (pixel[2] !== 0 || pixel[1] !== 0 || pixel[0] !== 0) {
    let hex = (this.pixel[0] * 65535 + this.pixel[1] * 255 + this.pixel[2]) / 0xffffff;    
    if (this.pixel[3] === 0) {
        hex = -hex;
    }
    cameraDepth = -hex * camera.far; // 相机坐标系中鼠标所在点的深度(注意:相机坐标系中的深度值为负值)}

3. 画面上のマウスの位置とカメラ空間の深さに基づいて、交差点のワールド座標系の座標を補間および逆計算します。

let nearPosition = new THREE.Vector3(); // 鼠标屏幕位置在near处的相机坐标系中的坐标
let farPosition = new THREE.Vector3(); // 鼠标屏幕位置在far处的相机坐标系中的坐标
let world = new THREE.Vector3(); // 通过插值计算世界坐标
// 设备坐标
const deviceX = this.offsetX / width * 2 - 1;
const deviceY = - this.offsetY / height * 2 + 1;// 近点
nearPosition.set(deviceX, deviceY, 1); // 屏幕坐标系:(0, 0, 1)
nearPosition.applyMatrix4(camera.projectionMatrixInverse); // 相机坐标系:(0, 0, -far)
// 远点
farPosition.set(deviceX, deviceY, -1); // 屏幕坐标系:(0, 0, -1)
farPosition.applyMatrix4(camera.projectionMatrixInverse); // 相机坐标系:(0, 0, -near)
// 在相机空间,根据深度,按比例计算出相机空间x和y值。
const t = (cameraDepth - nearPosition.z) / (farPosition.z - nearPosition.z);
// 将交点从相机空间中的坐标,转换到世界坐标系坐标。
world.set(
    nearPosition.x + (farPosition.x - nearPosition.x) * t,
    nearPosition.y + (farPosition.y - nearPosition.y) * t,
    cameraDepth
);
world.applyMatrix4(camera.matrixWorld);

完全なコード: https://gitee.com/tengge1/ShadowEditor/blob/master/ShadowEditor.Web/src/event/GPUPickEvent.js

関連アプリケーション

GPU を使用してオブジェクトを選択し、交差位置を計算します。これは主に、非常に高いパフォーマンスが必要な状況で使用されます。例:

1. マウスが 3D モデルに移動したときのホバー効果。

2. モデルを追加するとき、モデルはマウスで移動し、シーンにモデルを配置した効果がリアルタイムでプレビューされます。

3. 距離測定や面積測定などのツール、直線や多角形を平面上でマウスを動かすとリアルタイムにプレビューでき、長さや面積を計算できます。

4. シーンとモデルは非常に大きく、レイ キャスティング方法の選択速度は非常に遅く、ユーザー エクスペリエンスは非常に悪いです。

これは、GPU を使用してオブジェクトを選択し、マウス ホバー効果を実現する図です。赤い境界線は選択効果、黄色の半透明の効果はマウス ホバー効果です。

######わからない?もしかしたら、three.js のさまざまな投影操作に慣れていないかもしれません。以下にthree.jsにおける射影演算式を示します。

three.js は GPU を使用してオブジェクトを選択し、交差位置を計算します

three.js での投影操作


1.modelViewMatrix = Camera.matrixWorldInverse * object.matrixWorld

2. viewMatrix = Camera 。 matrixWorldInverse

3.modelMatrix = object.matrixWorld

4.project = applyMatrix4( Camera.matrixWorldInverse ).applyMatrix4( Camera.projectionMatrix )

5. unproject = applyMatrix4( Camera .projectionMatrixInverse ).applyMatrix4( Camera.matrixWorld )

6. gl_Position = projectMatrix *modelViewMatrix *position

* viewMatrix * modelMatrix *position

参考資料:

1. 完全な実装コード: https://gitee.com/tengge1/ShadowEditor/blob/master/ShadowEditor.Web/src/event /GPUPickEvent.js

2. オープンソースの 3 次元シーン エディターthree.js に基づく: https://github.com/tengge1/ShadowEditor

3. シェーダーを使用して OpenGL で深度値を描画する :https://stackoverflow.com/questions/6408851/draw- the- Depth-value-in-opengl-using-shaders

4. glsl で、実際のフラグメント シェーダー深度値を取得します: https://gamedev.stackexchange.com/questions/93055/getting-the- real-fragment- Depth-in-glsl

この記事は

js チュートリアル

列から引用したものです。ぜひ学習してください。

以上がthree.js は GPU を使用してオブジェクトを選択し、交差位置を計算しますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcnblogs.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。