皮肤网格上的three.js光线投射

问题描述 投票:0回答:1

我正在尝试在一些骨架更改之后光线投射蒙皮网格(知道问题)(没有动画,因此性能不是优先级)。

我在这次尝试中想到的棘手的事情是:

  1. 加载蒙皮网格添加到场景中
  2. 在加载的网格中对特定骨骼的位置进行一些更改
  3. 复制转换加载网格的几何(可能来自缓冲区?)
  4. 从复制的几何图形创建新网格(某种仿鬼网格)并应用于它
  5. 在鬼影网格上设置光线投射,不透明度材质= 0.0

上面的列表应该可以工作,但我在第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或更少版本中,使用我的其余代码(此处未包含)并且没有变形切线只有skinng骨骼)我试着写清楚并且如果有人不帮忙的话请因为我不是专业人士。我不包括我计算转换几何形状的方法beacouse我太难了。

我在这里挖掘了很多关于这个问题,例如issue6440和今天它仍然没有固定。

但现有的方法可以使用它,例如https://jsfiddle.net/fnjkeg9x/1/,但经过几次尝试后我失败了,我的结论是,冲锋队员在变形切片上工作,这可能是我失败的原因。

编辑:

我基于这个主题get-the-global-position-of-a-vertex-of-a-skinned-meshStormtrooper创建了下一个codepen。决定从简单的盒子开始,使皮肤变形网格边界。

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

Codepen取得新进展:qazxsw poi

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

成立的动画模型https://codepen.io/donkeyLuck0/pen/XQbBMQ的例子

javascript three.js raycasting skinning
1个回答
0
投票

您可以使用GPU拾取来“拾取”蒙皮对象。但它不会给你一个位置

注意:GPU拾取需要使用自定义材质渲染每个可拾取对象。你如何实现这一点取决于你。 Sketchfab animation with points tracking通过制作2个场景来做到这一点。这可能对皮肤对象没有用处。

不幸的是,three.js无法覆盖材料AFAICT。这是一个示例,在渲染进行拾取之前替换可拾取对象上的材质,然后在之后恢复它们。您还需要隐藏任何不想拾取的对象。

This article
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;
}
© www.soinside.com 2019 - 2024. All rights reserved.