Hello, I’m converting morph targets to a texture to overcome webgl limit (4 active morph targets at the same time). My model has 49 morph targets and the file is way too big (40 mb) so I want to compress the model with meshopt (140kb). The model is skinned and I’m playing skeletal animations.
The issue is, the code for converting morph targets to texture based morph targets totally deforms the model (or how it appears). I see only blinking patches and stripes of color. This piece of code is way beyond my understanding so I don’t know where to even start looking for an issue or if this approach is even technologically possible. Everything works perfectly fine without meshopt compression.
Is it possible that meshopt reduces the amount of shape keys so the real amount is different? I even tried to manually change the number of shape keys in this code from 1-49 - btw at 1 it looked ok just the shape keys were not applied to the model.
changeToTextureBasedMorphTargets(skinnedMesh) {
// Create morphTarget positions texture
let size = 4096 * 4096;
let data = new Float32Array(3 * size);
let vertIndexs = new Float32Array(
skinnedMesh.geometry.attributes.position.count
);
//Loop over each original vertex
let stride = 0;
let vertexId = 0;
let min = Number.MAX_VALUE;
let max = Number.MIN_VALUE;
for (
let v = 0;
v < skinnedMesh.geometry.attributes.position.array.length;
v += 3
) {
//Loop over all blendshapes
for (
let i = 0;
i < skinnedMesh.geometry.morphAttributes.position.length;
i++
) {
let morphAttr = skinnedMesh.geometry.morphAttributes.position[i];
checkMinMax(morphAttr.array, v);
//Copy x, y, and z for the given vertex
data[stride] = morphAttr.array[v];
data[stride + 1] = morphAttr.array[v + 1];
data[stride + 2] = morphAttr.array[v + 2];
stride += 3;
}
vertexId++;
//Also set vertIndex at v to v which is the vert index
vertIndexs[vertexId] = vertexId;
}
//console.log("min: ", min, "max: ", max);
function checkMinMaxComponent(value) {
if (value < min) {
min = value;
}
if (value > max) {
max = value;
}
}
function checkMinMax(array, startIndex) {
checkMinMaxComponent(array[startIndex]);
checkMinMaxComponent(array[startIndex + 1]);
checkMinMaxComponent(array[startIndex + 2]);
}
// Remove existing morph target code related attributes
skinnedMesh.geometry.deleteAttribute("morphTarget0");
skinnedMesh.geometry.deleteAttribute("morphTarget1");
skinnedMesh.geometry.deleteAttribute("morphTarget2");
// Add new vert index attribute
skinnedMesh.geometry.setAttribute(
"vertIndex",
new THREE.BufferAttribute(vertIndexs, 1)
);
//CREATE DATA TEXTURE AND PLACE ON SHADER MAT
let dataTexture = new THREE.DataTexture(
data,
4096,
4096,
THREE.RGBFormat,
THREE.FloatType
);
dataTexture.needsUpdate = true;
//Disable all morphTarget logic by setting appropriate flags
skinnedMesh.material.morphTargets = false;
skinnedMesh.material.morphNormals = false;
const morphTargetDeclarations = `
//Data texture
uniform sampler2D texture0;
//Blendshape influences
uniform float morphTargetInfluences[49];
//Current vertex index
attribute float vertIndex;
`;
const morphTargetVertexShaderCode = `
//Offset used for fixing the x y coordinates on the data texture
float offset = vertIndex * 49.;
//Loop over every blendshape
for(int i=0; i<49; i++) {
float iFloat = float(i);
//If influence is 0, lets not waste GPU processing, move on
if(morphTargetInfluences[i] == 0.) {
continue;
}
//Find the x and y position of the vertex data based on vertex index and blendshape index
float x = mod(offset + iFloat, 4096.);
float y = floor((offset + iFloat) / 4096.);
//Grab the data at x and y
vec2 texCoord = vec2(x / 4096.,y / 4096.);
vec4 data = texture2D(texture0, texCoord);
//Modify the current vertex position with the data found in the texture and the current blendshape influence
transformed.x += data.x * morphTargetInfluences[i];
transformed.y += data.y * morphTargetInfluences[i];
transformed.z += data.z * morphTargetInfluences[i];
}
`;
// Replace all existing morph target vertex shader code with our own
skinnedMesh.material.onBeforeCompile = function (shader) {
// Add new necessary uniforms
shader.uniforms.texture0 = {
type: "t",
value: dataTexture,
};
// Now when we change the morphTargetInfluences it's linked to show up in the
// vertex shader
shader.uniforms.morphTargetInfluences = {
value: skinnedMesh.morphTargetInfluences,
};
// Replace morphTarget code declarations with our own
shader.vertexShader = shader.vertexShader.replace(
"#include <morphtarget_pars_vertex>",
morphTargetDeclarations
);
shader.vertexShader = shader.vertexShader.replace(
"#include <morphtarget_vertex>",
morphTargetVertexShaderCode
);
};
}
This is what happens to my meshopt compressed model. I see only blinking patches of color.
Thank you for any help.