【问题标题】:Synching two meshes, one with ShaderMaterial同步两个网格,一个使用 ShaderMaterial
【发布时间】:2016-06-04 09:38:17
【问题描述】:

我从相同的几何体创建了两个网格并运行相同的动画。如果我对网格完全不做任何事情,它们就会保持完美的同步,这就是我想要的。但如果我改变它们的位置或旋转,它们就会不同步。

Here's a jsfiddle of an example. 顶部有一团缩小的 js,其中包含来自 r77 源的 EffectComposer.js、ShaderPass.js、RenderPass.js、MaskPass.js 和 CopyShader.js 的内容——这三个。 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一样多。例如,在 update() 函数中:

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 示例中。

【问题讨论】:

  • 我删除了与问题无关的所有内容。 jsfiddle.net/oju429Ld/3 一个合理的猜测是问题出在您的顶点着色器中。
  • 绝对——这或多或少是我从一开始的假设。任何洞察 r66 和 r77 之间的变化导致行为差异(如我在下面的回答中所讨论的)?我之所以问,是因为我相信我链接的第二个 jsfiddle 中的代码(带有工作示例的那个)最初是由您发布的,以回答其他人的问题。

标签: three.js shader


【解决方案1】:

回答我自己的问题:似乎涉及 ShaderMaterial 实现的某些内容自 r66 以来发生了变化(第二个 ---working---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 的更改以解释不同的行为。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-04-08
    • 1970-01-01
    • 1970-01-01
    • 2021-12-19
    • 1970-01-01
    • 2013-03-30
    • 2017-06-13
    • 1970-01-01
    相关资源
    最近更新 更多