Méthode Raycasting
Il est très simple de sélectionner des objets à l'aide du Raycaster fourni avec three.js , le Le code est le suivant :
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); }
[Recommandations de cours associées : Tutoriel vidéo JavaScript]
Il utilise le filtrage par boîte englobante pour calculer le rayon de projection et chaque élément de surface triangulaire. l'intersection est réalisée.
Cependant, lorsque le modèle est très grand, par exemple, il y a 400 000 faces, la sélection des objets et le calcul de l'emplacement des points de collision par traversée seront très lents et l'expérience utilisateur sera médiocre.
Mais utiliser GPU pour sélectionner des objets ne pose pas ce problème. Quelle que soit la taille de la scène et du modèle, la position de l'objet et le point d'intersection au point de la souris peuvent être obtenus dans une seule image.
Utilisez le GPU pour sélectionner des objets
La méthode de mise en œuvre est très simple :
1 Créez un matériau de sélection et remplacez le matériau de chaque modèle dans. la scène avec des couleurs différentes.
2. Lisez la couleur du pixel à la position de la souris et déterminez l'objet à la position de la souris en fonction de la couleur.
Code d'implémentation spécifique :
1. Créez un matériau sélectionné, parcourez la scène et remplacez chaque modèle de la scène par une couleur différente.
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. Dessinez la scène sur WebGLRenderTarget, lisez la couleur de la position de la souris et déterminez l'objet sélectionné.
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; } });
Explication : offsetX et offsetY sont la position de la souris et la hauteur est la hauteur du canevas. Le sens de la ligne readRenderTargetPixels est de sélectionner la couleur du pixel à la position de la souris (offsetX, height - offsetY), avec une largeur de 1 et une hauteur de 1.
le pixel est Uint8Array(4), qui stocke respectivement quatre canaux de couleur rgba. La plage de valeurs de chaque canal est de 0 à 255.
Code d'implémentation complet : https://gitee.com/tengge1/ShadowEditor/blob/master/ShadowEditor.Web/src/event/GPUPickEvent.js
Utilisez le GPU pour obtenir points d'intersection Position
La méthode d'implémentation est également très simple :
1. Créez un matériau de shader de profondeur et restituez la profondeur de la scène au WebGLRenderTarget.
2. Calculez la profondeur de la position de la souris et calculez la position de l'intersection en fonction de la position et de la profondeur de la souris.
Code d'implémentation spécifique :
1. Créez un matériau de shader de profondeur, encodez les informations de profondeur d'une certaine manière et restituez-les dans WebGLRenderTarget.
Matériau de profondeur :
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); }
Remarque importante :
a gl_Position.z est la profondeur dans l'espace de la caméra, qui est linéaire et va de cameraNear à cameraFar. Les variables variables du shader peuvent être utilisées directement pour l'interpolation.
b. La raison de gl_Position.z/far est de convertir la valeur dans la plage de 0 à 1 pour une sortie facile sous forme de couleur.
c. La profondeur dans l'espace de l'écran ne peut pas être utilisée. Après la projection en perspective, la profondeur devient -1~1, dont la plupart sont très proches de 1 (plus de 0,9), non linéaires, presque inchangées, et la la couleur de sortie est presque inchangée et très inexacte.
d. Obtenir la méthode de profondeur dans le fragment shader : la profondeur de l'espace de la caméra est gl_FragCoord.z, la profondeur de l'espace de l'écran est gl_FragCoord.z / gl_FragCoord.w.
e. Les descriptions ci-dessus concernent toutes la projection en perspective. En projection orthographique, gl_Position.w vaut 1, et l'espace de la caméra et la profondeur de l'espace de l'écran sont les mêmes.
f. Afin d'afficher la profondeur aussi précisément que possible, les trois composantes de RVB sont utilisées pour afficher la profondeur. La plage gl_Position.z/far est comprise entre 0 et 1, multipliée par 0xffffff et convertie en une valeur de couleur RVB. Le composant r 1 représente 65 535, le composant g 1 représente 255 et le composant b 1 représente 1.
Code d'implémentation complet : https://gitee.com/tengge1/ShadowEditor/blob/master/ShadowEditor.Web/src/event/GPUPickEvent.js
2. Lire la position de la souris Couleur. , restaurez la valeur de couleur lue à la valeur de profondeur de l'espace de la caméra.
a. Dessinez la profondeur "chiffrée" sur le WebGLRenderTarget. Méthode de lecture des couleurs
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);
Description : offsetX et offsetY sont la position de la souris et la hauteur est la hauteur du canevas. Le sens de la ligne readRenderTargetPixels est de sélectionner la couleur du pixel à la position de la souris (offsetX, height - offsetY), avec une largeur de 1 et une hauteur de 1.
le pixel est Uint8Array(4), qui stocke respectivement quatre canaux de couleur rgba. La plage de valeurs de chaque canal est de 0 à 255.
b. "Déchiffrez" la valeur de profondeur de l'espace de la caméra "cryptée" pour obtenir la valeur correcte de la profondeur de l'espace de la caméra.
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. Selon la position de la souris sur l'écran et la profondeur de l'espace de la caméra, interpolez et rétro-calculez les coordonnées dans le système de coordonnées mondial de l'intersection.
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);
Code complet : https://gitee.com/tengge1/ShadowEditor/blob/master/ShadowEditor.Web/src/event/GPUPickEvent.js
Applications associées
Utilisez le GPU pour sélectionner des objets et calculer la position d'intersection, qui est principalement utilisée lorsque de très hautes performances sont requises. Par exemple :
1. L'effet de survol lorsque la souris se déplace vers le modèle 3D.
2. Lors de l'ajout d'un modèle, le modèle se déplacera avec la souris et prévisualisera l'effet du placement du modèle dans la scène en temps réel.
3. Des outils tels que la mesure de distance et la mesure de surface. Les lignes et les polygones peuvent être prévisualisés en temps réel lorsque la souris se déplace sur le plan, et la longueur et la surface peuvent être calculées.
4. La scène et le modèle sont très grands, la vitesse de sélection de la méthode de lancer de rayons est très lente et l'expérience utilisateur est très mauvaise.
Voici une image de l'utilisation du GPU pour sélectionner des objets et obtenir un effet de survol de la souris. La bordure rouge est l'effet de sélection et l'effet translucide jaune est l'effet de survol de la souris.
Tu ne comprends pas ? Peut-être que vous n'êtes pas familier avec les différentes opérations de projection dans three.js. La formule de l'opération de projection dans three.js est donnée ci-dessous.
Opération de projection dans three.js
1. modelViewMatrix = camera.matrixWorldInverse * object.matrixWorld
2. matriceWorldInverse
3. modelMatrix = object.matrixWorld
4. project = applyMatrix4( camera.matrixWorldInverse ).applyMatrix4( camera.projectionMatrix )
5. .projectionMatrixInverse ).applyMatrix4( camera.matrixWorld )
6. gl_Position = projectionMatrix * modelViewMatrix * position
= projectionMatrix * viewMatrix * modelMatrix * position
Référence :
1. Code d'implémentation complet : https://gitee.com/tengge1/ShadowEditor/blob/master/ShadowEditor.Web/src/event /GPUPickEvent.js
2. sur three.js : https://github.com/tengge1/ShadowEditor
3. Utiliser des shaders pour dessiner des valeurs de profondeur dans OpenGL :https://stackoverflow.com/questions/6408851/draw-the -deep-value-in-opengl-using-shaders
4. Dans glsl, obtenez la valeur réelle de la profondeur du fragment shader : https://gamedev.stackexchange.com/questions/93055/getting-the-real. -fragment-deep-in-glsl
Cet article provient de la colonne
tutoriel jsCe qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!