webgl中的人脸动画

Face animation in webgl

我需要一些关于 webgl 的帮助。

我必须从代​​码中打开面部模型 (Lee Perry Smith) 的嘴巴,但我不知道如何识别正确的顶点来做到这一点。

对于我的任务,我不允许使用 three.js.

我试图从搅拌机中获取索引,但由于某种原因我没有运气(就像搅拌机中识别的顶点不对应于我为 webgl 生成的儿子。

有人知道吗..?

更多信息:
我在搅拌机中使用了这个片段来获取索引:http://blenderscripting.blogspot.it/2011/07/getting-index-of-currently-selected.html

然后进入我的 javascript 并使用此功能编辑顶点坐标(只是为了看看它们是否正确,即使这不是真正想要的转换):

function move_vertex(indices,x,y,z){
    vertex = headObject.vertices[0];
    indices.forEach(function(index){
        vertex[3*index] += x;
        vertex[3*index+1]+=y;
        vertex[3*index+2]+=z;
    });

gl.bindBuffer(gl.ARRAY_BUFFER,headObject.modelVertexBuffer[0]);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array(vertex));
gl.bindBuffer(gl.ARRAY_BUFFER,null);

}

基本上有无限种方法可以做到这一点。不知道哪个适合你的情况。

一种方法是使用 a skinning system。将嘴部顶点附加到骨骼上并移动骨骼。

另一种方法是使用变形目标。基本上保存网格一次张开嘴和一次闭嘴。在 webgl 中加载两个网格,将它们传递给你的着色器并在它们之间进行 lerp

attribute vec4 position1;  // data from mouth closed model
attribute vec4 position2;  // data from mouth open model

uniform float mixAmount;
uniform mat4 worldViewProjection;

 ...

  // compute the position to use based on the mixAmount
  // 0 = close mouth
  // 1 = open mouth
  // 0.5 = 50% between open and closed mouth etc..
  vec4 position = mix(position1, position2, mixAmount);

  // use the result in the standard way
  gl_Position = worldViewProjection * position;

尽管您希望对结果进行归一化,但您可以对法线进行类似的混合。

大多数建模包都支持在包内使用变形目标。是否导出数据取决于文件格式和导出器。将某些东西组合在一起的简单方法就是导出面部两次并使用您拥有的代码加载 2 个文件。

另一个可能是使用顶点颜色。在您的建模程序中,为嘴唇顶点涂上不同的颜色,然后在您的代码中按颜色找到这些顶点。

另一种方法是给嘴唇分配一个不同的 material 然后使用 material 找到顶点。

一些 3d 建模程序允许您将元数据添加到顶点。这基本上是顶点颜色方法的一种变体。您可能需要编写自己的导出器,因为很少有 3rd 方格式支持额外数据。即使该格式理论上可以支持额外的数据,大多数出口商也不会出口它。

类似地,一些 3d 建模程序允许您将顶点添加到 selections/clusters/groups,然后您可以参考它来找到嘴唇。同样,此方法可能需要您自己的导出器,因为大多数格式不支持此数据

另一种方法确实很老套,但可以在紧要关头完成工作。 Select 唇顶点并将它们向右移动 1000 个单位。然后在你的程序中,你可以找到所有的顶点都太靠右了,然后从每个顶点中减去 1000 个单位,将它们放回原来的位置。这可能会弄乱你的法线,但你可以在之后重新计算法线。

还有一个方法是使用您拥有的数据并编写一个界面以一次突出显示每个顶点,记下哪些顶点是嘴巴。

例如在屏幕上放一个<input type="number">。根据数字对那个顶点做一些事情。设置顶点颜色或调整它的位置,您可以通过一些操作来查看它。然后写下哪些顶点是嘴巴。如果你幸运的话,它们在某个范围内,所以你只需要写下第一个和最后一个。

const m4 = twgl.m4;
const v3 = twgl.v3;
const gl = document.querySelector("canvas").getContext("webgl");

const vs = `
attribute vec4 a_position;
attribute vec4 a_normal;

uniform mat4 u_matrix;

varying vec4 v_color;

void main() {
  // Multiply the position by the matrix.
  gl_Position = u_matrix * a_position;

  // Pass the normal as a color to the fragment shader.
  v_color = a_normal * .5 + .5;
}
`;
const fs = `
precision mediump float;

// Passed in from the vertex shader.
varying vec4 v_color;

void main() {
   gl_FragColor = v_color;
}
`;

// Yes, this sample is using TWGL (https://twgljs.org).
// You should be able to tell what it's doing from the names
// of the functions and be able to easily translate that to raw WebGL

const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
  a_position: HeadData.positions,
  a_normal: HeadData.normals,
});

const numVertices = bufferInfo.numElements;
let vertexId = 0;       // id of vertex we're inspecting
let newVertexId = 251;  // id of vertex we want to inspect

 // these are normals and get converted to colors in the shader
const black = new Float32Array([-1, -1, -1]); 
const red   = new Float32Array([ 1, -1, -1]);
const white = new Float32Array([ 1,  1,  1]);
const colors = [
  black,
  red,
  white,
];

const numElem = document.querySelector("#number");
numElem.textContent = newVertexId;
  
document.querySelector("#prev").addEventListener('click', e => {
  newVertexId = (newVertexId + numVertices - 1) % numVertices;
  numElem.textContent = newVertexId;
});

document.querySelector("#next").addEventListener('click', e => {
  newVertexId = (newVertexId + 1) % numVertices;
  numElem.textContent = newVertexId;
});

let frameCount = 0;
function render(time) {
  ++frameCount;
  
  twgl.resizeCanvasToDisplaySize(gl.canvas);
  
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  
  gl.enable(gl.DEPTH_TEST);
  gl.enable(gl.CULL_FACE);
  
  // restore old data
  
  // for what's in bufferInfo see 
  // http://twgljs.org/docs/module-twgl.html#.BufferInfo
  const origData = new Float32Array(
    HeadData.normals.slice(vertexId * 3, (vertexId + 3) * 3));
  const oldOffset = vertexId * 3 * 4; // 4 bytes per float
  gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.a_normal.buffer);
  gl.bufferSubData(gl.ARRAY_BUFFER, oldOffset, origData);
  
  // set new vertex to a color
  const newOffset = newVertexId * 3 * 4; // 4 bytes per float
  gl.bufferSubData(
     gl.ARRAY_BUFFER, 
     newOffset, 
     colors[(frameCount / 3 | 0) % colors.length]);

  vertexId = newVertexId;
  
  const fov = 45 * Math.PI / 180;
  const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
  const zNear = 0.1;
  const zFar = 50;
  const projection = m4.perspective(fov, aspect, zNear, zFar);
  
  const eye = [0, 0, 25];
  const target = [0, 0, 0];
  const up = [0, 1, 0];
  const camera = m4.lookAt(eye, target, up);
  const view = m4.inverse(camera);

  const viewProjection = m4.multiply(projection, view);

  const world = m4.identity();
  const worldViewProjection = m4.multiply(viewProjection, world);
  
  gl.useProgram(programInfo.program);
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  twgl.setUniforms(programInfo, {
    u_matrix: worldViewProjection,
  });
  
  gl.drawArrays(gl.TRIANGLES, 0, numVertices);
  
  requestAnimationFrame(render);
}
                   
requestAnimationFrame(render);
  
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
.ui { 
  position: absolute;
  left: 1em;
  top: 1em;
  background: rgba(0,0,0,0.9);
  padding: 1em;
  font-size: large;
  color: white;
  font-family: monospace;
}
#number {
  display: inline-block;
  text-align: center;
}
<script src="https://twgljs.org/dist/2.x/twgl-full.min.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/headdata.js"></script>
<canvas></canvas>
<div class="ui">
  <button id="prev">⬅</button>
  <span>vert ndx:</span><span id="number"></span>
  <button id="next">➡</button>
</div>