Hardware Skinning first commit, still non functionnal as no material implements it. also it's disabled by default in the skeleton control

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10537 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
rem..om 12 years ago
parent e34d483973
commit eb5525e581
  1. 73
      engine/src/core-data/Common/ShaderLib/Skinning.glsllib
  2. 176
      engine/src/core/com/jme3/animation/SkeletonControl.java
  3. 58
      engine/src/core/com/jme3/scene/Mesh.java
  4. 3
      engine/src/core/com/jme3/scene/VertexBuffer.java

@ -1,36 +1,55 @@
#ifdef USE_HWSKINNING
#ifndef NUM_BONES
#error A required pre-processor define "NUM_BONES" is not set!
#endif
#ifdef NUM_BONES
attribute vec4 inBoneWeight;
attribute vec4 inBoneIndices;
attribute vec4 inBoneIndex;
uniform mat4 m_BoneMatrices[NUM_BONES];
void Skinning_Compute(inout vec4 position, inout vec4 normal){
vec4 index = inBoneIndices;
vec4 weight = inBoneWeight;
vec4 newPos = vec4(0.0);
vec4 newNormal = vec4(0.0);
for (float i = 0.0; i < 4.0; i += 1.0){
mat4 skinMat = m_BoneMatrices[int(index.x)];
newPos += weight.x * (skinMat * position);
newNormal += weight.x * (skinMat * normal);
index = index.yzwx;
weight = weight.yzwx;
}
position = newPos;
normal = newNormal;
void Skinning_Compute(inout vec4 position){
#if NUM_WEIGHTS_PER_VERT == 1
position = m_BoneMatrices[int(inBoneIndex.x)] * position;
#else
mat4 mat = mat4(0.0);
mat += m_BoneMatrices[int(inBoneIndex.x)] * inBoneWeight.x;
mat += m_BoneMatrices[int(inBoneIndex.y)] * inBoneWeight.y;
mat += m_BoneMatrices[int(inBoneIndex.z)] * inBoneWeight.z;
mat += m_BoneMatrices[int(inBoneIndex.w)] * inBoneWeight.w;
position = mat * position;
#endif
}
#else
void Skinning_Compute(inout vec4 position, inout vec4 normal){
// skinning disabled, leave position and normal unaltered
void Skinning_Compute(inout vec4 position, inout vec3 normal){
#if NUM_WEIGHTS_PER_VERT == 1
position = m_BoneMatrices[int(inBoneIndex.x)] * position;
normal = (mat3(m_BoneMatrices[int(inBoneIndex.x)][0].xyz,
m_BoneMatrices[int(inBoneIndex.x)][1].xyz,
m_BoneMatrices[int(inBoneIndex.x)][2].xyz) * normal);
#else
mat4 mat = mat4(0.0);
mat += m_BoneMatrices[int(inBoneIndex.x)] * inBoneWeight.x;
mat += m_BoneMatrices[int(inBoneIndex.y)] * inBoneWeight.y;
mat += m_BoneMatrices[int(inBoneIndex.z)] * inBoneWeight.z;
mat += m_BoneMatrices[int(inBoneIndex.w)] * inBoneWeight.w;
position = mat * position;
normal = (mat3(mat[0].xyz,mat[1].xyz,mat[2].xyz) * normal);
#endif
}
void Skinning_Compute(inout vec4 position, inout vec4 tangent, inout vec3 normal){
#if NUM_WEIGHTS_PER_VERT == 1
position = m_BoneMatrices[int(inBoneIndex.x)] * position;
tangent = m_BoneMatrices[int(inBoneIndex.x)] * tangent;
normal = (mat3(m_BoneMatrices[int(inBoneIndex.x)][0].xyz,
m_BoneMatrices[int(inBoneIndex.x)][1].xyz,
m_BoneMatrices[int(inBoneIndex.x)][2].xyz) * normal);
#else
mat4 mat = mat4(0.0);
mat += m_BoneMatrices[int(inBoneIndex.x)] * inBoneWeight.x;
mat += m_BoneMatrices[int(inBoneIndex.y)] * inBoneWeight.y;
mat += m_BoneMatrices[int(inBoneIndex.z)] * inBoneWeight.z;
mat += m_BoneMatrices[int(inBoneIndex.w)] * inBoneWeight.w;
position = mat * position;
tangent = mat * tangent;
normal = (mat3(mat[0].xyz,mat[1].xyz,mat[2].xyz) * normal);
#endif
}
#endif

@ -32,24 +32,31 @@
package com.jme3.animation;
import com.jme3.export.*;
import com.jme3.material.Material;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix4f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.RendererException;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.*;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
import com.jme3.shader.VarType;
import com.jme3.util.TempVars;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The Skeleton control deforms a model according to a skeleton,
* It handles the computation of the deformation matrices and performs
* the transformations on the mesh
* The Skeleton control deforms a model according to a skeleton, It handles the
* computation of the deformation matrices and performs the transformations on
* the mesh
*
* @author Rémy Bouquet Based on AnimControl by Kirill Vainer
*/
@ -64,10 +71,28 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
*/
private Mesh[] targets;
/**
* Used to track when a mesh was updated. Meshes are only updated
* if they are visible in at least one camera.
* Used to track when a mesh was updated. Meshes are only updated if they
* are visible in at least one camera.
*/
private boolean wasMeshUpdated = false;
/**
* Flag to enable hardware/gpu skinning if available, disable for
* software/cpu skinning, enabled by default
*/
private boolean useHwSkinning = false;
/**
* Flag to check if we have to check the shader if it would work and on fail
* switch to sw skinning
*/
private boolean triedHwSkinning = false;
/**
* Bone offset matrices, recreated each frame
*/
private transient Matrix4f[] offsetMatrices;
/**
* Material references used for hardware skinning
*/
private Material[] materials;
/**
* Serialization only. Do not use.
@ -76,9 +101,49 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
}
/**
* Creates a skeleton control.
* The list of targets will be acquired automatically when
* the control is attached to a node.
* Hint to use hardware/software skinning mode. If gpu skinning fails or is
* disabledIf in hardware mode all or some models display the same animation
* cycle make sure your materials are not identical but clones
*
* @param useHwSkinning the useHwSkinning to set
*
*/
public void setUseHwSkinning(boolean useHwSkinning) {
this.useHwSkinning = useHwSkinning;
this.triedHwSkinning = false;
//next full 10 bones (e.g. 30 on 24 bones )
int bones = ((skeleton.getBoneCount() / 10) + 1) * 10;
for (Material m : materials) {
if (useHwSkinning) {
try {
m.setInt("NumberOfBones", bones);
} catch (java.lang.IllegalArgumentException e) {
Logger.getLogger(SkeletonControl.class.getName()).log(Level.INFO, "{0} material doesn't support Hardware Skinning reverting to software", new String[]{m.getName()});
setUseHwSkinning(false);
}
} else {
if (m.getParam("NumberOfBones") != null) {
m.clearParam("NumberOfBones");
}
}
}
for (Mesh mesh : targets) {
if (isMeshAnimated(mesh)) {
mesh.prepareForAnim(!useHwSkinning); // prepare for software animation
}
}
if (useHwSkinning) {
}
}
public boolean isUseHwSkinning() {
return useHwSkinning;
}
/**
* Creates a skeleton control. The list of targets will be acquired
* automatically when the control is attached to a node.
*
* @param skeleton the skeleton
*/
@ -102,22 +167,19 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
return mesh.getBuffer(Type.BindPosePosition) != null;
}
private Mesh[] findTargets(Node node) {
private void findTargets(Node node, ArrayList<Mesh> targets, HashSet<Material> materials) {
Mesh sharedMesh = null;
ArrayList<Mesh> animatedMeshes = new ArrayList<Mesh>();
for (Spatial child : node.getChildren()) {
if (!(child instanceof Geometry)) {
continue; // could be an attachment node, ignore.
}
for (Spatial child : node.getChildren()) {
if (child instanceof Geometry) {
Geometry geom = (Geometry) child;
// is this geometry using a shared mesh?
Mesh childSharedMesh = geom.getUserData(UserData.JME_SHAREDMESH);
if (childSharedMesh != null) {
// Don't bother with non-animated shared meshes
// Don’t bother with non-animated shared meshes
if (isMeshAnimated(childSharedMesh)) {
// child is using shared mesh,
// so animate the shared mesh but ignore child
@ -126,20 +188,24 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
} else if (sharedMesh != childSharedMesh) {
throw new IllegalStateException("Two conflicting shared meshes for " + node);
}
materials.add(geom.getMaterial());
}
} else {
Mesh mesh = geom.getMesh();
if (isMeshAnimated(mesh)) {
animatedMeshes.add(mesh);
targets.add(mesh);
materials.add(geom.getMaterial());
}
}
} else if (child instanceof Node) {
findTargets((Node) child, targets, materials);
}
}
if (sharedMesh != null) {
animatedMeshes.add(sharedMesh);
targets.add(sharedMesh);
}
return animatedMeshes.toArray(new Mesh[animatedMeshes.size()]);
}
@Override
@ -147,18 +213,40 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
super.setSpatial(spatial);
if (spatial != null) {
Node node = (Node) spatial;
targets = findTargets(node);
HashSet<Material> mats = new HashSet<Material>();
ArrayList<Mesh> meshes = new ArrayList<Mesh>();
findTargets(node, meshes, mats);
targets = meshes.toArray(new Mesh[meshes.size()]);
materials = mats.toArray(new Material[mats.size()]);
//try hw skinning, will be reset to sw skinning if render call fails
setUseHwSkinning(true);
} else {
targets = null;
materials = null;
}
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
if (!wasMeshUpdated) {
if (useHwSkinning) {
//preload scene to check if shader won’t blow with too many bones
if (!triedHwSkinning) {
triedHwSkinning = true;
try {
rm.preloadScene(this.spatial);
} catch (RendererException e) {
//revert back to sw skinning for this model
setUseHwSkinning(false);
}
}
offsetMatrices = skeleton.computeSkinningMatrices();
hardwareSkinUpdate();
} else {
resetToBind(); // reset morph meshes to bind pose
Matrix4f[] offsetMatrices = skeleton.computeSkinningMatrices();
offsetMatrices = skeleton.computeSkinningMatrices();
// if hardware skinning is supported, the matrices and weight buffer
// will be sent by the SkinningShaderLogic object assigned to the shader
@ -166,10 +254,11 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
// NOTE: This assumes that code higher up
// Already ensured those targets are animated
// otherwise a crash will happen in skin update
//if (isMeshAnimated(targets[i])) {
//if (isMeshAnimated(targets)) {
softwareSkinUpdate(targets[i], offsetMatrices);
//}
}
}
wasMeshUpdated = true;
}
@ -180,13 +269,14 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
wasMeshUpdated = false;
}
//only do this for software updates
void resetToBind() {
for (Mesh mesh : targets) {
if (isMeshAnimated(mesh)) {
FloatBuffer bwBuff = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
ByteBuffer biBuff = (ByteBuffer)mesh.getBuffer(Type.BoneIndex).getData();
Buffer bwBuff = mesh.getBuffer(Type.BoneWeight).getData();
Buffer biBuff = mesh.getBuffer(Type.BoneIndex).getData();
if (!biBuff.hasArray() || !bwBuff.hasArray()) {
mesh.prepareForAnim(true); // prepare for software animation
mesh.prepareForAnim(!useHwSkinning); // prepare for software animation
}
VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition);
VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal);
@ -223,11 +313,10 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
Node clonedNode = (Node) spatial;
AnimControl ctrl = spatial.getControl(AnimControl.class);
SkeletonControl clone = new SkeletonControl();
clone.setSpatial(clonedNode);
clone.skeleton = ctrl.getSkeleton();
// Fix animated targets for the cloned node
clone.targets = findTargets(clonedNode);
clone.setSpatial(clonedNode);
// Fix attachments for the cloned node
for (int i = 0; i < clonedNode.getQuantity(); i++) {
@ -269,6 +358,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
/**
* returns the skeleton of this control
*
* @return
*/
public Skeleton getSkeleton() {
@ -277,6 +367,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
/**
* sets the skeleton for this control
*
* @param skeleton
*/
// public void setSkeleton(Skeleton skeleton) {
@ -284,6 +375,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
// }
/**
* returns the targets meshes of this control
*
* @return
*/
public Mesh[] getTargets() {
@ -292,6 +384,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
/**
* sets the target meshes of this control
*
* @param targets
*/
// public void setTargets(Mesh[] targets) {
@ -299,6 +392,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
// }
/**
* Update the mesh according to the given transformation matrices
*
* @param mesh then mesh
* @param offsetMatrices the transformation matrices to apply
*/
@ -316,8 +410,21 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
}
/**
* Update the mesh according to the given transformation matrices
*
* @param mesh then mesh
* @param offsetMatrices the transformation matrices to apply
*/
private void hardwareSkinUpdate() {
for (Material m : materials) {
m.setParam("BoneMatrices", VarType.Matrix4Array, offsetMatrices);
}
}
/**
* Method to apply skinning transforms to a mesh's buffers
*
* @param mesh the mesh
* @param offsetMatrices the offset matices to apply
*/
@ -421,9 +528,12 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
}
/**
* Specific method for skinning with tangents to avoid cluttering the classic skinning calculation with
* null checks that would slow down the process even if tangents don't have to be computed.
* Also the iteration has additional indexes since tangent has 4 components instead of 3 for pos and norm
* Specific method for skinning with tangents to avoid cluttering the
* classic skinning calculation with null checks that would slow down the
* process even if tangents don't have to be computed. Also the iteration
* has additional indexes since tangent has 4 components instead of 3 for
* pos and norm
*
* @param maxWeightsPerVert maximum number of weights per vertex
* @param mesh the mesh
* @param offsetMatrices the offsetMaytrices to apply
@ -574,6 +684,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
OutputCapsule oc = ex.getCapsule(this);
oc.write(targets, "targets", null);
oc.write(skeleton, "skeleton", null);
oc.write(materials, "materials", null);
}
@Override
@ -586,5 +697,10 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
System.arraycopy(sav, 0, targets, 0, sav.length);
}
skeleton = (Skeleton) in.readSavable("skeleton", null);
sav = in.readSavableArray("materials", null);
if (sav != null) {
materials = new Material[sav.length];
System.arraycopy(sav, 0, materials, 0, sav.length);
}
}
}

@ -341,7 +341,7 @@ public class Mesh implements Savable, Cloneable {
BufferUtils.clone(tangents.getData()));
setBuffer(bindTangents);
tangents.setUsage(Usage.Stream);
}
}// else hardware setup does nothing, mesh already in bind pose
}
}
@ -352,22 +352,70 @@ public class Mesh implements Savable, Cloneable {
* @param forSoftwareAnim Should be true to enable the conversion.
*/
public void prepareForAnim(boolean forSoftwareAnim){
if (forSoftwareAnim){
// convert indices
if (forSoftwareAnim) {
// convert indices to ubytes on the heap or floats
VertexBuffer indices = getBuffer(Type.BoneIndex);
ByteBuffer originalIndex = (ByteBuffer) indices.getData();
Buffer buffer = indices.getData();
if (buffer instanceof ByteBuffer) {
ByteBuffer originalIndex = (ByteBuffer) buffer;
ByteBuffer arrayIndex = ByteBuffer.allocate(originalIndex.capacity());
originalIndex.clear();
arrayIndex.put(originalIndex);
indices.updateData(arrayIndex);
} else if (buffer instanceof FloatBuffer) {
//Floats back to bytes
FloatBuffer originalIndex = (FloatBuffer) buffer;
ByteBuffer arrayIndex = ByteBuffer.allocate(originalIndex.capacity());
originalIndex.clear();
for (int i = 0; i < originalIndex.capacity(); i++) {
arrayIndex.put((byte) originalIndex.get(i));
}
indices.updateData(arrayIndex);
}
// convert weights
// convert weights on the heap
VertexBuffer weights = getBuffer(Type.BoneWeight);
FloatBuffer originalWeight = (FloatBuffer) weights.getData();
FloatBuffer arrayWeight = FloatBuffer.allocate(originalWeight.capacity());
originalWeight.clear();
arrayWeight.put(originalWeight);
weights.updateData(arrayWeight);
} else {
//BoneIndex must be 32 bit for attribute type constraints in shaders
VertexBuffer indices = getBuffer(Type.BoneIndex);
Buffer buffer = indices.getData();
if (buffer instanceof ByteBuffer) {
ByteBuffer bIndex = (ByteBuffer) buffer;
final float[] rval = new float[bIndex.capacity()];
for (int i = 0; i < rval.length; i++) {
rval[i] = bIndex.get(i);
}
clearBuffer(Type.BoneIndex);
VertexBuffer ib = new VertexBuffer(Type.BoneIndex);
ib.setupData(Usage.Stream,
4,
Format.Float,
BufferUtils.createFloatBuffer(rval));
setBuffer(ib);
} else if (buffer instanceof FloatBuffer) {
//BoneWeights on DirectBuffer
FloatBuffer originalIndices = (FloatBuffer) buffer;
FloatBuffer arrayIndices = BufferUtils.createFloatBuffer(originalIndices.capacity());
originalIndices.clear();
arrayIndices.put(originalIndices);
indices.setUsage(Usage.Stream);
indices.updateData(arrayIndices);
}
//BoneWeights on DirectBuffer
VertexBuffer weights = getBuffer(Type.BoneWeight);
FloatBuffer originalWeight = (FloatBuffer) weights.getData();
FloatBuffer arrayWeight = BufferUtils.createFloatBuffer(originalWeight.capacity());
originalWeight.clear();
arrayWeight.put(originalWeight);
weights.setUsage(Usage.Static);
weights.updateData(arrayWeight);
}
}

@ -145,7 +145,8 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
* Bone indices, used with animation (4 ubytes).
* If used with software skinning, the usage should be
* {@link Usage#CpuOnly}, and the buffer should be allocated
* on the heap.
* on the heap as a ubytes buffer. For Hardware skinning this should be
* either an int or float buffer due to shader attribute types restrictions.
*/
BoneIndex,

Loading…
Cancel
Save