同步两个网格,一个与ShaderMaterial

时间:2016-06-03 04:43:26

标签: three.js shader

我有两个从相同几何体创建并运行相同动画的网格物体。如果我对网格完全没有任何作用,那么它们就会保持完美的锁定状态,这就是我想要的。但如果我改变他们的位置或轮换,他们就会失去同步。

Here's a jsfiddle of an example.在顶部有一小块缩小的js,其中包含来自r77源的EffectComposer.js,ShaderPass.js,RenderPass.js,MaskPass.js和CopyShader.js的内容 - - three.js CDN不包含它们,并且jsfiddle赢得了从three.js github repo链接到它们的工作。示例问题的开头是 THREE.OutlineShader 的定义:

THREE.OutlineShader = {

  uniforms: {
    "offset": {
      type: "f",
      value: 2.0
    },
    "boneTexture": {
      type: "t",
      value: null
    },
    "boneTextureWidth": {
      type: "i",
      value: null
    },
    "boneTextureHeight": {
      type: "i",
      value: null
    },
  },

  vertexShader: [
    "uniform sampler2D boneTexture;",
    "uniform int boneTextureWidth;",
    "uniform int boneTextureHeight;",
    "uniform float offset;",
    "mat4 getBoneMatrix(const in float i) {",
    "float j = i * 4.0;",
    "float x = mod(j, float(boneTextureWidth));",
    "float y = floor(j / float(boneTextureWidth));",
    "float dx = 1.0 / float(boneTextureWidth);",
    "float dy = 1.0 / float(boneTextureHeight);",
    "y = dy * (y + 0.5);",
    "vec4 v1 = texture2D(boneTexture, vec2(dx * (x + 0.5), y));",
    "vec4 v2 = texture2D(boneTexture, vec2(dx * (x + 1.5), y));",
    "vec4 v3 = texture2D(boneTexture, vec2(dx * (x + 2.5), y));",
    "vec4 v4 = texture2D(boneTexture, vec2(dx * (x + 3.5), y));",
    "mat4 bone = mat4(v1, v2, v3, v4);",
    "return bone;",
    "}",
    "void main() {",
    "mat4 boneMatX = getBoneMatrix(skinIndex.x);",
    "mat4 boneMatY = getBoneMatrix(skinIndex.y);",
    "mat4 boneMatZ = getBoneMatrix(skinIndex.z);",
    "mat4 boneMatW = getBoneMatrix(skinIndex.w);",
    "vec4 skinVertex = vec4(position + normal * offset, 1.0);",
    "vec4 skinned = boneMatX * skinVertex * skinWeight.x;",
    "skinned += boneMatY * skinVertex * skinWeight.y;",
    "skinned += boneMatZ * skinVertex * skinWeight.z;",
    "skinned += boneMatW * skinVertex * skinWeight.w;",
    "vec4 mvPosition = modelViewMatrix * skinned;",
    "gl_Position = projectionMatrix * mvPosition;",
    "}"
  ].join("\n"),

  fragmentShader: [
    "uniform int boneTextureWidth;",
    "void main() {",
    "gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);",
    "}"
  ].join("\n")
};

var camera, light, renderer, composer, clock;
var sceneMain, sceneOutline;
var meshMain = null,
  meshOutline = null;
var mixerMain, mixerOutline;
var animMain, animOutline;
var height = 500,
  width = 500;
var objData = '{"metadata":{"formatVersion":3.1,"generatedBy":"Blender 2.7 Exporter","vertices":24,"faces":22,"normals":18,"colors":0,"uvs":[],"materials":1,"morphTargets":0,"bones":2},"scale":1.000000,"materials":[{"DbgColor":15658734,"DbgIndex":0,"DbgName":"Material","blending":"NormalBlending","colorDiffuse":[0.1569801711586143,0.17312412519937936,0.6400000190734865],"colorEmissive":[0.0,0.0,0.0],"colorSpecular":[0.2535329759120941,0.0,0.007157782092690468],"depthTest":true,"depthWrite":true,"shading":"Lambert","specularCoef":50,"opacity":1.0,"transparent":false,"vertexColors":false}],"vertices":[1.51034,-1,-1,1.51034,-1,1,-0.489661,-1,1,-0.489661,-1,-1,1.51034,1,-1,1.51034,1,1,-0.489662,1,1,-0.489661,1,-1,3.23233,-1,-0.999999,3.23233,-1,1,3.23233,1,-0.999999,3.23233,1,1,-1.98848,-1,1,-1.98848,-1,-1,-1.98848,1,0.999999,-1.98848,1,-1,1.51034,-5.70811,-1,1.51034,-5.70811,1,3.23233,-5.70811,-0.999999,3.23233,-5.70811,1,-0.489661,-5.62708,1,-0.48966,-5.62708,-1,-1.98848,-5.62708,1,-1.98848,-5.62708,-1],"morphTargets":[],"normals":[-0.301492,-0.301492,-0.904508,-0.301492,-0.301492,0.904508,0.301492,-0.301492,0.904508,0.301492,-0.301492,-0.904508,0,0.707083,-0.707083,0,0.707083,0.707083,0.577349,0.577349,0.577349,0.577349,0.577349,-0.577349,-0.577349,0.577349,-0.577349,-0.577349,0.577349,0.577349,0.707083,0,-0.707083,0.707083,0,0.707083,0.577349,-0.577349,-0.577349,-0.577349,-0.577349,-0.577349,-0.707083,0,0.707083,-0.707083,0,-0.707083,-0.577349,-0.577349,0.577349,0.577349,-0.577349,0.577349],"colors":[],"uvs":[],"faces":[35,0,1,2,3,0,0,1,2,3,35,4,5,11,10,0,4,5,6,7,35,1,5,6,2,0,1,5,5,2,35,6,7,15,14,0,5,4,8,9,35,4,0,3,7,0,4,0,3,4,35,8,10,11,9,0,10,7,6,11,35,5,1,9,11,0,5,1,11,6,35,0,4,10,8,0,0,4,7,10,35,0,8,18,16,0,0,10,12,13,35,12,14,15,13,0,14,9,8,15,35,7,3,13,15,0,4,3,15,8,35,2,6,14,12,0,2,5,9,14,35,2,12,22,20,0,2,14,16,17,35,17,16,18,19,0,16,13,12,17,35,9,1,17,19,0,11,1,16,17,35,8,9,19,18,0,10,11,17,12,35,1,0,16,17,0,1,0,13,16,35,21,20,22,23,0,12,17,16,13,35,13,3,21,23,0,15,3,12,13,35,12,13,23,22,0,14,15,13,16,35,3,2,20,21,0,3,2,17,12,35,4,7,6,5,0,4,4,5,5],"bones":[{"parent":-1,"name":"leg.R","pos":[-1.24994,0.43791,0.191651],"rotq":[-0.00523508,-0.706875,-0.707296,-0.00579363],"scl":[1,1,1]},{"parent":-1,"name":"leg.L","pos":[2.49995,0.280193,0.066556],"rotq":[-0.00523507,-0.706875,-0.707296,-0.00579363],"scl":[1,1,1]}],"skinIndices":[1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,-1,1,-1,1,-1,1,-1,0,-1,0,-1,0,-1,0,1,1,-1,1,-1,1,-1,1,-1,0,-1,0,-1,0,-1,0,-1],"skinWeights":[0.928373,0.0680346,0.937978,0.0587701,0.949888,0.0463839,0.937937,0.0591265,0.821856,0.122838,0.79233,0.145709,0.876929,0.0825711,0.830405,0.115734,0.989868,0,0.992278,0,0.968805,0,0.966368,0,0.993762,0,0.989439,0,0.978637,0,0.962526,0.00173758,0.997334,0,0.997776,0,0.999229,0,0.999402,0,0.998345,0,0.997508,0,0.99955,0,0.999106,0],"animations":[{"name":"ArmatureAction","fps":24,"length":0.416667,"hierarchy":[{"parent":-1,"keys":[{"time":0,"pos":[-1.24994,0.43791,0.191651],"rot":[-0.00643926,-0.522937,-0.852335,-0.0044168],"scl":[1,1,1]},{"time":0.0416667,"pos":[-1.24994,0.43791,0.191651],"rot":[-0.00683821,-0.561328,-0.827555,-0.00415746],"scl":[1,1,1]},{"time":0.0833333,"pos":[-1.24994,0.43791,0.191651],"rot":[-0.00791262,-0.665775,-0.746103,-0.00335735],"scl":[1,1,1]},{"time":0.125,"pos":[-1.24994,0.43791,0.191651],"rot":[0.00910612,0.78443,0.620147,0.00222404],"scl":[1,1,1]},{"time":0.166667,"pos":[-1.24994,0.43791,0.191651],"rot":[0.00983298,0.859093,0.511724,0.00131562],"scl":[1,1,1]},{"time":0.208333,"pos":[-1.24994,0.43791,0.191651],"rot":[0.0100438,0.881367,0.472325,0.000997505],"scl":[1,1,1]},{"time":0.25,"pos":[-1.24994,0.43791,0.191651],"rot":[0.00983298,0.859093,0.511724,0.00131562],"scl":[1,1,1]},{"time":0.291667,"pos":[-1.24994,0.43791,0.191651],"rot":[0.00910612,0.78443,0.620147,0.00222404],"scl":[1,1,1]},{"time":0.333333,"pos":[-1.24994,0.43791,0.191651],"rot":[-0.00791262,-0.665775,-0.746103,-0.00335735],"scl":[1,1,1]},{"time":0.375,"pos":[-1.24994,0.43791,0.191651],"rot":[-0.00683821,-0.561328,-0.827555,-0.00415746],"scl":[1,1,1]},{"time":0.416667,"pos":[-1.24994,0.43791,0.191651],"rot":[-0.00643926,-0.522937,-0.852335,-0.0044168],"scl":[1,1,1]}]},{"parent":0,"keys":[{"time":0,"pos":[2.49995,0.280193,0.066556],"rot":[0.0033329,0.881416,0.472275,0.00706144],"scl":[1,1,1]},{"time":0.0416667,"pos":[2.49995,0.280193,0.066556],"rot":[0.00316317,0.858922,0.512045,0.00734349],"scl":[1,1,1]},{"time":0.0833333,"pos":[2.49995,0.280193,0.066556],"rot":[0.00263566,0.783706,0.621074,0.00807219],"scl":[1,1,1]},{"time":0.125,"pos":[2.49995,0.280193,0.066556],"rot":[-0.00187897,-0.664887,-0.74689,-0.00880854],"scl":[1,1,1]},{"time":0.166667,"pos":[2.49995,0.280193,0.066556],"rot":[-0.00126496,-0.561008,-0.827759,-0.00919252],"scl":[1,1,1]},{"time":0.208333,"pos":[2.49995,0.280193,0.066556],"rot":[-0.00104832,-0.522977,-0.852295,-0.009288],"scl":[1,1,1]},{"time":0.25,"pos":[2.49995,0.280193,0.066556],"rot":[-0.00126496,-0.561007,-0.827759,-0.00919252],"scl":[1,1,1]},{"time":0.291667,"pos":[2.49995,0.280193,0.066556],"rot":[-0.00187897,-0.664887,-0.74689,-0.00880854],"scl":[1,1,1]},{"time":0.333333,"pos":[2.49995,0.280193,0.066556],"rot":[0.00263566,0.783706,0.621074,0.00807219],"scl":[1,1,1]},{"time":0.375,"pos":[2.49995,0.280193,0.066556],"rot":[0.00316317,0.858921,0.512045,0.0073435],"scl":[1,1,1]},{"time":0.416667,"pos":[2.49995,0.280193,0.066556],"rot":[0.0033329,0.881416,0.472275,0.00706144],"scl":[1,1,1]}]}]}]}';
load();

function load() {
  var loader = new THREE.JSONLoader();

  clock = new THREE.Clock();

  sceneMain = new THREE.Scene();
  sceneOutline = new THREE.Scene();

  var obj = loader.parse(JSON.parse(objData));
  for (var k in obj.materials) {
    obj.materials[k].skinning = true;
  }
  setModel(obj.geometry, obj.materials);
  init();
  animate();
}

function init() {
  camera = new THREE.PerspectiveCamera(40, height / width, 1, 10000);
  camera.position.set(0, 0, 25);

  light = new THREE.DirectionalLight(0xffffff)
  light.position.set(1, 1, 1);
  sceneMain.add(light);


  renderer = new THREE.WebGLRenderer({
    width: width,
    height: height,
    antialias: true,
  });
  renderer.setSize(width, height);
  renderer.setClearColor(0x666666);
  renderer.autoClear = false;
  renderer.gammaInput = true;
  renderer.gammaOutput = true;
  document.body.appendChild(renderer.domElement);

  var renderTarget = new THREE.WebGLRenderTarget(width, height, {
    minFilter: THREE.LinearFilter,
    magFilter: THREE.LinearFilter,
    format: THREE.RGBAFormat,
    stencilBuffer: true,
  });
  composer = new THREE.EffectComposer(renderer, renderTarget);
  composer.renderTarget1.stencilBuffer = true;
  composer.renderTarget2.stencilBuffer = true;

  var pMain = new THREE.RenderPass(sceneMain, camera);
  var pOut = new THREE.RenderPass(sceneOutline, camera);
  pOut.clear = false;
  var pCopy = new THREE.ShaderPass(THREE.CopyShader);
  pCopy.renderToScreen = true;

  composer.addPass(pMain);
  composer.addPass(pOut);
  composer.addPass(pCopy);

  animMain.play();
  animOutline.play();
}

function setModel(geometry, materials) {
  meshMain = new THREE.SkinnedMesh(geometry,
    new THREE.MeshFaceMaterial(materials));
  sceneMain.add(meshMain);

  mixerMain = new THREE.AnimationMixer(meshMain);
  animMain = mixerMain.clipAction(geometry.animations[0]);

  var shader = THREE.OutlineShader;
  var shaderMaterial = new THREE.ShaderMaterial({
    uniforms: THREE.UniformsUtils.clone(shader.uniforms),
    vertexShader: shader.vertexShader,
    fragmentShader: shader.fragmentShader,
    skinning: true,
    side: THREE.BackSide,
  });
  meshOutline = new THREE.SkinnedMesh(geometry, shaderMaterial);
  shaderMaterial.uniforms['boneTextureWidth'].value = meshOutline.skeleton.boneTextureWidth;
  shaderMaterial.uniforms['boneTextureHeight'].value = meshOutline.skeleton.boneTextureHeight;
  shaderMaterial.uniforms['boneTexture'].value = meshOutline.skeleton.boneTexture;
  shaderMaterial.uniforms['offset'].value = 0.5;
  shaderMaterial.uniforms['boneTextureWidth'].value.needsUpdate = true;
  shaderMaterial.uniforms['boneTextureHeight'].value.needsUpdate = true;
  shaderMaterial.uniforms['boneTexture'].value.needsUpdate = true;
  shaderMaterial.uniforms['offset'].value.needsUpdate = true;
  sceneOutline.add(meshOutline);

  mixerOutline = new THREE.AnimationMixer(meshOutline);
  animOutline = mixerOutline.clipAction(geometry.animations[0]);

}

function animate() {
  var delta = clock.getDelta();

  requestAnimationFrame(animate);

  update(delta);
  render(delta);
}

function update(delta) {
  if (meshMain && meshOutline) {
    meshMain.rotation.y += 1 * delta;
    meshOutline.rotation.y += 1 * delta;

    mixerMain.update(delta);
    mixerOutline.update(delta);
  }
}

function render(delta) {
  composer.render(delta);
}

问题显然是由ShaderMaterial和/或着色器本身造成的,因为将第二个网格的材质更改为例如MeshBasicMaterial导致预期的行为(两个网格保持同步)。

着色器从前一段时间发布的this jsfiddle解除了。它使用了古老的three.js版本。我并不完全清楚在创建ShaderMaterial实例时填充boneTexture,boneTextureWidth和boneTextureHeight制服的预期/正确方法。我是根据网格骨架中的值手动完成的,但如果错误的话,我不会感到惊讶。

同样,我只是想了解为什么同时以相同的方式翻译两个网格会导致它们失去同步,如第一个jsfiddle示例中所示。

编辑:我发现使用ShaderMaterial( meshOutline )的网格与其他网格( meshMain )同步,如果meshOutline正好旋转了一半与meshMain一样多。例如,在更新()功能中:

meshMain.rotation.y += 1 * delta;
meshOutline.rotation.y += 1 * delta / 2;

...将导致两个网格显然同步旋转。如果用坐标(例如x)移动替换旋转,则同样如此:

//meshMain.rotation.y += 1 * delta;
//meshOutline.rotation.y += 1 * delta / 2;
var dx = Math.random() - 0.5;
meshMain.position.x += dx;
meshOutline.position.x += dx / 2;

...将导致两个网格一起来回移动。但如果两者都有 合并,即:

meshMain.rotation.y += 1 * delta;
meshOutline.rotation.y += 1 * delta / 2;
var dx = Math.random() - 0.5;
meshMain.position.x += dx;
meshOutline.position.x += dx / 2;

他们疯狂地不同步。

这显然意味着我不了解着色器如何从three.js获取顶点位置。我知道着色器正在计算顶点位置并使用它们,因为当您使用ShaderMaterial时会发生什么。我不理解的是如何保持着色器使用当前的数据以及在three.js中对网格发生的事情。这显然发生在我上面链接的第二个jsfiddle例子中。

1 个答案:

答案 0 :(得分:0)

回答我自己的问题:看起来好像涉及ShaderMaterial实现的东西自r66以来已经改变了(第二个版本中使用的版本---工作--- jsfiddle示例,我从中获得了着色器代码)。

我最终做的是通过ShaderChunk源来查看我是否可以使用来自three.js源的块着色器代码重现我想做的事情(想想也许这只是一些默认或任何设置在后台,我没有在自定义着色器代码中做)。我最终得到的是(对于顶点着色器):

vertexShader: [
   "uniform float offset;",
   THREE.ShaderChunk["common"],
   THREE.ShaderChunk["skinning_pars_vertex"],
   "void main() {",
      "vec3 transformed = vec3(position + normal * offset);",
      THREE.ShaderChunk["skinbase_vertex"],
      THREE.ShaderChunk["skinning_vertex"],
      THREE.ShaderChunk["project_vertex"],
   "}"
].join( "\n" ),

隐藏在 skinning_vertex.glsl 中的重要区别是skinning_vertex着色器块的来源:

#ifdef USE_SKINNING

    vec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );

    vec4 skinned = vec4( 0.0 );
    skinned += boneMatX * skinVertex * skinWeight.x;
    skinned += boneMatY * skinVertex * skinWeight.y;
    skinned += boneMatZ * skinVertex * skinWeight.z;
    skinned += boneMatW * skinVertex * skinWeight.w;
    skinned  = bindMatrixInverse * skinned;

#endif

我所拥有的自定义着色器中没有发生的事情(在使用r66的示例中并不是这样)是在第一行和最后一行 - - 首先乘以 bindMatrix ,然后再乘以 bindMatrixInverse 。我有点疑惑为什么需要这个,因为according to the docs这两个制服只有在SkinnedMesh将 bindMode 设置为"分离"时才定义。 (而不是"附加",默认值。)

但无论如何变化 - 无论是通过使用基于ShaderChunk的着色器还是通过编辑我的自定义着色器来包含差异 - 都会产生所需的结果。

这回答了我的问题,但我仍然欢迎任何关于文档涵盖范围的指示或对r66到r77的变化的解释,这些变化解释了不同的行为。