蒙皮网格上的three.js射线广播

时间:2019-04-01 19:55:35

标签: javascript three.js raycasting skinning

我正在尝试在某些骨架更改(没有动画,因此性能不是优先事项)之后对蒙皮网格物体(已知问题)进行光线投射。

我想像中的棘手事情是:

  1. 将蒙皮网格物体加载到场景中
  2. 在加载的网格上更改特定骨骼的位置
  3. 复制变换后的加载网格的几何形状(也许是从缓冲区?)
  4. 从复制的几何图形创建新的网格物体(某种形式的仿幻网格物体)并将其应用
  5. 在不透明材质为0.0的幻影网格上设置射线投射

上面的列表应该可以工作,但是我在第3点停留在第3点,因为换肤后我无法获得变换后的顶点。

var scene, camera, renderer, mesh, ghostMesh;

var raycaster = new THREE.Raycaster();
var raycasterMeshHelper;

initScene();
render();

function initScene() {
  scene = new THREE.Scene();
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 200);
  camera.position.set(20, 7, 3);
  renderer = new THREE.WebGLRenderer({
    antialias: true
  });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);

  document.body.appendChild(renderer.domElement);
  window.addEventListener('resize', onWindowResize, false);

  var orbit = new THREE.OrbitControls(camera, renderer.domElement);

  //lights stuff
  var ambientLight = new THREE.AmbientLight(0xffffff, 0.3);
  scene.add(ambientLight);
  var lights = [];
  lights[0] = new THREE.PointLight(0xffffff, 1, 0);
  lights[1] = new THREE.PointLight(0xffffff, 1, 0);
  lights[2] = new THREE.PointLight(0xffffff, 1, 0);
  lights[0].position.set(0, 200, 0);
  lights[1].position.set(100, 200, 100);
  lights[2].position.set(-100, -200, -100);
  scene.add(lights[0]);
  scene.add(lights[1]);
  scene.add(lights[2]);


  //raycaster mesh 
  var raycasterMaterial = new THREE.MeshBasicMaterial({
    color: 0xdddddd,
    opacity: 0.7,
    transparent: true
  });
  var geometrySphere = new THREE.SphereGeometry(0.5, 16, 16);
  raycasterMeshHelper = new THREE.Mesh(geometrySphere, raycasterMaterial);
  raycasterMeshHelper.visible = false;
  scene.add(raycasterMeshHelper);

  renderer.domElement.addEventListener('mousemove', onMouseMove, false);


  //model Loading

  var loader = new THREE.JSONLoader();
  loader.load("https://raw.githubusercontent.com/visus100/skinnedTests/master/js_fiddle/skinned_mesh.json", function(geometry) {
    var meshMaterial = new THREE.MeshStandardMaterial({
      color: 0x00df15,
      skinning: true
    });

    mesh = new THREE.SkinnedMesh(geometry, meshMaterial);
    scene.add(mesh);

    var skeleton = new THREE.SkeletonHelper(mesh);
    scene.add(skeleton);

    //some experimental skeletonal changes
    mesh.skeleton.bones[1].rotation.z += 0.10;
    mesh.skeleton.bones[2].rotation.x += -0.65;
    mesh.skeleton.bones[3].rotation.y += -0.45;
    mesh.skeleton.bones[3].position.x += 0.11;

    //updates matrix
    mesh.updateMatrix();
    mesh.geometry.applyMatrix(mesh.matrix);
    mesh.updateMatrixWorld(true);

    //crate ghost mesh geometry
    createGhostMesh();

    //crate point cloud helper from buffergeometry
    var bufferGeometry = new THREE.BufferGeometry().fromGeometry(mesh.geometry);

    var particesMaterial = new THREE.PointsMaterial({
      color: 0xff00ea,
      size: 0.07,
      sizeAttenuation: false
    });
    particles = new THREE.Points(bufferGeometry, particesMaterial);
    particles.sortParticles = true;
    scene.add(particles);

  });
}

function createGhostMesh() {
  var geometryForGhostMesh = new THREE.Geometry();

  //push vertices and other stuff to geometry
  for (i = 0; i < mesh.geometry.vertices.length; i++) {
    var temp = new THREE.Vector3(mesh.geometry.vertices[i].x, mesh.geometry.vertices[i].y, mesh.geometry.vertices[i].z);
    geometryForGhostMesh.vertices.push(temp);

    //////
    //here should be the code for calc translation verices of skinned mesh and added to geometryForGhostMesh
    //////

    geometryForGhostMesh.skinIndices.push(mesh.geometry.skinIndices[i]);
    geometryForGhostMesh.skinWeights.push(mesh.geometry.skinWeights[i]);
  }

  for (i = 0; i < mesh.geometry.faces.length; i++) {
    geometryForGhostMesh.faces.push(mesh.geometry.faces[i]);
  }

  //create material and add to scene

  var ghostMaterial = new THREE.MeshBasicMaterial({
    color: 0xff0000,
    opacity: 0.1,
    transparent: true,
    skinning: true
  });
  ghostMesh = new THREE.Mesh(geometryForGhostMesh, ghostMaterial);
  scene.add(ghostMesh);
}

function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}

function render() {
  requestAnimationFrame(render);
  renderer.render(scene, camera);
};

function onMouseMove(event) {
  //raycaster for ghostMesh 
  if (ghostMesh) {
    var rect = renderer.domElement.getBoundingClientRect();
    var mouseX = ((event.clientX - rect.left) / rect.width) * 2 - 1;
    var mouseY = -((event.clientY - rect.top) / rect.height) * 2 + 1;
    raycaster.setFromCamera(new THREE.Vector2(mouseX, mouseY), camera);

    var intersects = raycaster.intersectObject(ghostMesh);
    if (intersects.length > 0) {
      raycasterMeshHelper.visible = true;
      raycasterMeshHelper.position.set(0, 0, 0);
      raycasterMeshHelper.lookAt(intersects[0].face.normal);
      raycasterMeshHelper.position.copy(intersects[0].point);
    } else {
      raycasterMeshHelper.visible = false;
    }
  }
}
body {
  margin: 0px;
  background-color: #000000;
  overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/98/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>


(请注意,我需要在thre.js中构建r98或更小版本,请保留我的其余代码(此处不引用),并且不包含顶点的正切切线) 我试图清楚地写出它,如果有人没有帮助,请这样做,因为我不是专业人士。 我没有包括计算转换后的几何的方法,因为我太失败了。

我在这里对这个问题进行了很多研究issue6440,但今天仍然没有解决。

但是有现有的方法可以使用它,例如https://jsfiddle.net/fnjkeg9x/1/,但是经过几次尝试后我都失败了,我的结论是冲锋队对变体缠结起作用,这可能就是我失败的原因。



编辑:

我根据主题get-the-global-position-of-a-vertex-of-a-skinned-meshStormtrooper创建了下一个密码笔。 决定从简单的盒子开始,使蒙皮的变形网格成为边界。

结果失败,因为它在行上给出0:
boneMatrix.fromArray(skeleton.boneMatrices, si * 16);
在这里,我将冲锋兵与我的控制台示例输出进行了比较:Screen shot image

Codpen取得了新的进展:https://codepen.io/donkeyLuck0/pen/XQbBMQ

我的另一个想法是以编程方式将这种骨骼形式加载的模型和装备作为变形切线应用(但我什至不知道是否可行以及如何解决)

创建动画模型的示例 Sketchfab animation with points tracking

3 个答案:

答案 0 :(得分:0)

您可以使用GPU拾取来“拾取”蒙皮的对象。虽然它不会给你职位

注意:GPU拾取要求使用自定义材质渲染每个可拾取对象。如何实现取决于您自己。 This article通过制作2个场景来实现。这对于有皮肤的对象可能没有用。

不幸的是,Three.js无法提供替代材料AFAICT的方法。这是一个示例,该示例在渲染以进行拾取之前替换可拾取对象上的材质,然后在之后还原它们。您还需要隐藏任何您不想拾取的对象。

const renderer = new THREE.WebGLRenderer({
  antialias: true,
  canvas: document.querySelector('canvas'),
});
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, 2, 0.1, 200);
camera.position.set(20, 7, 3);

const orbit = new THREE.OrbitControls(camera, renderer.domElement);

//lights stuff
const ambientLight = new THREE.AmbientLight(0xffffff, 0.3);
scene.add(ambientLight);
const lights = [];
lights[0] = new THREE.PointLight(0xffffff, 1, 0);
lights[1] = new THREE.PointLight(0xffffff, 1, 0);
lights[2] = new THREE.PointLight(0xffffff, 1, 0);
lights[0].position.set(0, 200, 0);
lights[1].position.set(100, 200, 100);
lights[2].position.set(-100, -200, -100);
scene.add(lights[0]);
scene.add(lights[1]);
scene.add(lights[2]);


//raycaster mesh 
const raycasterMaterial = new THREE.MeshBasicMaterial({
  color: 0xdddddd,
  opacity: 0.7,
  transparent: true
});
const geometrySphere = new THREE.SphereGeometry(0.5, 16, 16);
raycasterMeshHelper = new THREE.Mesh(geometrySphere, raycasterMaterial);
raycasterMeshHelper.visible = false;
scene.add(raycasterMeshHelper);


//model Loading
const pickableObjects = [];

const loader = new THREE.JSONLoader();
loader.load("https://raw.githubusercontent.com/visus100/skinnedTests/master/js_fiddle/skinned_mesh.json", (geometry) => {
  const meshMaterial = new THREE.MeshStandardMaterial({
    color: 0x00df15,
    skinning: true
  });

  const mesh = new THREE.SkinnedMesh(geometry, meshMaterial);
  scene.add(mesh);

  const id = pickableObjects.length + 1;
  pickableObjects.push({
    mesh,
    renderingMaterial: meshMaterial,
    pickingMaterial: new THREE.MeshPhongMaterial({
      skinning: true,
      emissive: new THREE.Color(id),
      color: new THREE.Color(0, 0, 0),
      specular: new THREE.Color(0, 0, 0),
      //map: texture,
      //transparent: true,
      //side: THREE.DoubleSide,
      //alphaTest: 0.5,
      blending: THREE.NoBlending,
    }),
  });

  //some experimental skeletonal changes
  mesh.skeleton.bones[1].rotation.z += 0.10;
  mesh.skeleton.bones[2].rotation.x += -0.65;
  mesh.skeleton.bones[3].rotation.y += -0.45;
  mesh.skeleton.bones[3].position.x += 0.11;

  //updates matrix
  mesh.updateMatrix();
  mesh.geometry.applyMatrix(mesh.matrix);
  mesh.updateMatrixWorld(true);

});

class GPUPickHelper {
  constructor() {
    // create a 1x1 pixel render target
    this.pickingTexture = new THREE.WebGLRenderTarget(1, 1);
    this.pixelBuffer = new Uint8Array(4);
  }
  pick(cssPosition, scene, camera) {
    const {
      pickingTexture,
      pixelBuffer
    } = this;

    // set the view offset to represent just a single pixel under the mouse
    const pixelRatio = renderer.getPixelRatio();
    camera.setViewOffset(
      renderer.context.drawingBufferWidth, // full width
      renderer.context.drawingBufferHeight, // full top
      cssPosition.x * pixelRatio | 0, // rect x
      cssPosition.y * pixelRatio | 0, // rect y
      1, // rect width
      1, // rect height
    );
    // render the scene
    // r102
    //renderer.setRenderTarget(pickingTexture);
    //renderer.render(scene, camera);
    //renderer.setRenderTarget(null);
    // r98
    renderer.render(scene, camera, pickingTexture);
    // clear the view offset so rendering returns to normal
    camera.clearViewOffset();
    //read the pixel
    renderer.readRenderTargetPixels(
      pickingTexture,
      0, // x
      0, // y
      1, // width
      1, // height
      pixelBuffer);

    const id =
      (pixelBuffer[0] << 16) |
      (pixelBuffer[1] << 8) |
      (pixelBuffer[2]);
    return id;
  }
}

function resizeRendererToDisplaySize(renderer) {
  const canvas = renderer.domElement;
  const width = canvas.clientWidth;
  const height = canvas.clientHeight;
  const needResize = canvas.width !== width || canvas.height !== height;
  if (needResize) {
    renderer.setSize(width, height, false);
  }
  return needResize;
}

const pickPosition = {
  x: 0,
  y: 0,
};
const pickHelper = new GPUPickHelper();
let lastPickedId = 0;
let lastPickedObjectSavedEmissive;

function render(time) {
  time *= 0.001;  // convert to seconds;

  if (resizeRendererToDisplaySize(renderer)) {
    const canvas = renderer.domElement;
    camera.aspect = canvas.clientWidth / canvas.clientHeight;
    camera.updateProjectionMatrix();
  }

  if (lastPickedId) {
    pickableObjects[lastPickedId - 1].renderingMaterial.emissive.setHex(lastPickedObjectSavedEmissive);
    lastPickedId = 0;
  }

  for (pickableObject of pickableObjects) {
    pickableObject.mesh.material = pickableObject.pickingMaterial;
  }
  
  const id = pickHelper.pick(pickPosition, scene, camera, time);
  
  for (pickableObject of pickableObjects) {
    pickableObject.mesh.material = pickableObject.renderingMaterial;
  }
  
  const pickedObject = pickableObjects[id - 1];
  if (pickedObject) {
    lastPickedId = id;
    lastPickedObjectSavedEmissive = pickedObject.renderingMaterial.emissive.getHex();
    pickedObject.renderingMaterial.emissive.setHex((time * 8) % 2 > 1 ? 0xFFFF00 : 0xFF0000);
  }
  
  renderer.render(scene, camera);

  requestAnimationFrame(render);
};
requestAnimationFrame(render);

function setPickPosition(event) {
  pickPosition.x = event.clientX;
  pickPosition.y = event.clientY;
}

function clearPickPosition() {
  // unlike the mouse which always has a position
  // if the user stops touching the screen we want
  // to stop picking. For now we just pick a value
  // unlikely to pick something
  pickPosition.x = -100000;
  pickPosition.y = -100000;
}

window.addEventListener('mousemove', setPickPosition);
window.addEventListener('mouseout', clearPickPosition);
window.addEventListener('mouseleave', clearPickPosition);

window.addEventListener('touchstart', (event) => {
  // prevent the window from scrolling
  event.preventDefault();
  setPickPosition(event.touches[0]);
}, {
  passive: false
});

window.addEventListener('touchmove', (event) => {
  setPickPosition(event.touches[0]);
});

window.addEventListener('touchend', clearPickPosition);


window.addEventListener('mousemove', setPickPosition);
window.addEventListener('mouseout', clearPickPosition);
window.addEventListener('mouseleave', clearPickPosition);
body {
  margin: 0;
}
canvas {
  width: 100vw;
  height: 100vh;
  display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/98/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<canvas></canvas>

答案 1 :(得分:0)

这对游戏来说太晚了,但这是一个GPU拾取的示例,该拾取可与蒙皮网格一起使用,不需要单独的拾取场景即可与主场景保持同步,也不需要用户管理自定义材料:

https://github.com/bzztbomb/three_js_gpu_picking

这里的技巧可以轻松实现材质覆盖和场景重用: https://github.com/bzztbomb/three_js_gpu_picking/blob/master/gpupicker.js#L58

答案 2 :(得分:0)

在修订版 116 的 https://github.com/mrdoob/three.js/pull/19178 中添加了对蒙皮网格的光线投射的适当支持。