Dynamic Composite Mesh - Real time blendshaping

Hi all,

A quick one:
I want to have a dynamically generated mesh, whose vertices can be defined as follows:

Mesh(t) = Mo + alpha(t)*M1 + beta(t)*M2 + gamma(t)*M3

M0, M1, M2, M3 being constant meshes
and alpha, beta and gamma variables on t.

What is the most efficient way to implement this (kind of morph/blendshape) in Panda?

Thanks for your suggestions

Jean-Claude

The most efficient way in terms of performance would probably be a vertex shader. You could store extra data (positions) on each vertex and access that in the shader.
If you mean most efficient in terms of easiest to implement, I think Panda has some blend shape ability already, though I have not used it myself.

Thanks Teedee,

Right the shader approach seems the way to go.

Assuming I go for a Cg shader, my basic geometry is M0 (Mesh 0) which I have as an egg file, so:

(1) I see how I can fill the alpha, beta and gamma as run time parameters to the shader, but I’m quite puzzled on how to feed the (M1,M2,M3) constant meshes as “float3 vtx_position: POSITION”.

(2) To what node should I then apply the shader to, the NodePath on which I have loaded the M0 egg file?

(3) Same questions if M0 in a subset of a larger mesh…

Thanks for your hints!

You could make use of the AUX entry on the vertices of the EGG file.

<Vertex> n {
  x y z
  <UV> { u v }
  <AUX> name { x y z w }
}

You can have multiple AUX with different names and access those with the shader as “float4 vtx_name”.
You would have to do a bit of customization of your export process for that to work.
If you really need to load them as separate EGG files (for example if you don’t know which EGG files will be blended beforehand) you would have to create a mesh on the fly with the positions of the blend shape meshes as AUX data.

Thank you so much Teedee!

BTW I was just looking at possible shader parameters, and it seems that a node can be a shader input too, so what about loading each morph meshes and have them as shader input?

I’m not at all familiar with this but what about a tentative rough sketch as : (could be completely stupid… anyway…)

# get basic mesh
M0.loadModel("M0.egg") 
M0.reparentTo(render)

# load vertex shader and attach it to Nodepath M0
myVTXShader = Shader.load("myVertexShader.sha")
M0.setshader(myVTXshader)

# get morph meshes
M1.loadModel("M1.egg")
M2.loadModel("M2.egg")
M3.loadModel("M3.egg")

M0.setShaderInput("M1", M1)  # this way ???
M0.setShaderInput("M2", M2)
M0.setShaderInput("M3", M3)

...
then in a processing loop modify alpha, beta, gamma

M0.setShaderInput("alpha", 0.2)
M0.setShaderInput("beta", 1.2)
M0.setShaderInput("gamma", 0.4)

:blush:

I think using a nodepath as an input will just give the transform matrix, not access to geometry. I could be wrong though, it is worth investigating further.

Hi again,

I just checked with a very simple case: a cube with two blend. For whatever reason the expected vertex deformation is not showing up… what happens is a translation of the whole original mesh…

For sure there is something wrong…

The code:

from direct.directbase import DirectStart
from direct.showbase.DirectObject import DirectObject
from direct.interval.IntervalGlobal import *
from panda3d.core import *
from math import pi, sin, cos
from direct.task import Task

t = 0
def updateblend(task):
	global t
	t = t+0.001
	cube.setShaderInput("alpha",  sin(5*t))
	cube.setShaderInput("beta",   sin(2*t))
	return Task.cont
	
cube=loader.loadModel('blendcube')
cube.reparentTo(render)
cube.setHpr(10,15,20)
cube.setScale(1.0,1.5,2.0)
myVertexShader = Shader.load("blend.sha")
cube.setShader(myVertexShader)
taskMgr.add(updateblend,"dynamic blend")
run()

The shader:

//Cg
void vshader(float4 vtx_position: POSITION,
	float4 vtx_color: COLOR,
	float4 vtx_name01, float4 vtx_name02,
	uniform float alpha, uniform float beta,
	uniform float4x4 mat_modelproj,
	out float4 l_position:  POSITION,
	out float4 l_color0: COLOR0)
{  
	float4 vtx_position_blend = vtx_position + alpha*vtx_name01 + beta*vtx_name02;
	vtx_position_blend.w = 1;
	l_position  = mul(mat_modelproj, vtx_position_blend);
	l_color0    = vtx_color;
}

void fshader(float4 l_color0: COLOR0, out float4 o_color: COLOR)
{
	o_color=l_color0;
}

The eggfile:

<CoordinateSystem> { Y-Up }

<Group> cube {
  <Dart> { 1 }
  <Group> groundPlane_transform {
  }
  <Group> pCube1 {
    <VertexPool> pCubeShape1.verts {
      <Vertex> 0 {
        -0.5 -0.5 -0.5
        <Normal> { 0 -1 0 }
        <RGBA> { 1.0 0.5 0.5 1 }
		<AUX> nam01 { 0.0 -0.25 -0.25  1 } 
		<AUX> nam02 { 0.0 -0.15 -0.35  1 } 
      }
      <Vertex> 1 {
        0.5 -0.5 -0.5
        <Normal> { 0 -1 0 }
        <RGBA> { 0.5 1.0 0.5 1 }
 		<AUX> nam01 { 0.0 -0.25 -0.25  1 } 
		<AUX> nam02 { 0.0 -0.15 -0.35  1 } 
     }
      <Vertex> 2 {
        0.5 -0.5 0.5
        <Normal> { 0 -1 0 }
        <RGBA> { 0.5 0.5 1.0 1 }
  		<AUX> nam01 { 0.0 -0.25 0.25  1 } 
		<AUX> nam02 { 0.0 -0.15 0.35  1 } 
    }
      <Vertex> 3 {
        -0.5 -0.5 0.5
        <Normal> { 0 -1 0 }
        <RGBA> { 1.0 1.0 0.5 1 }
 		<AUX> nam01 { -0.0 -0.25 0.25  1 } 
		<AUX> nam02 { -0.1 -0.15 0.35  1 } 
     }
      <Vertex> 4 {
        -0.5 -0.5 0.5
        <Normal> { 0 0 1 }
        <RGBA> { 0.5 1.0 1.0 1 }
 		<AUX> nam01 { -0.05 -0.15 0.25  1 } 
		<AUX> nam02 { -0.02 -0.15 0.35  1 } 
     }
      <Vertex> 5 {
        0.5 -0.5 0.5
        <Normal> { 0 0 1 }
        <RGBA> { 1.0 0.5 1.0 1 }
		<AUX> nam01 { 0.05 -0.25 0.25  1 } 
		<AUX> nam02 { 0.05 -0.15 0.15  1 } 
      }
      <Vertex> 6 {
        0.5 0.5 0.5
        <Normal> { 0 0 1 }
        <RGBA> { 0.0 0.5 0.5 1 }
 		<AUX> nam01 { 0.25 0.15 0.15  1 } 
		<AUX> nam02 { 0.75 0.0 -0.35  1 } 
     }
      <Vertex> 7 {
        -0.5 0.5 0.5
        <Normal> { 0 0 1 }
        <RGBA> { 0.5 0.0 0.5 1 }
		<AUX> nam01 { -0.25 0.05 0.25  1 } 
		<AUX> nam02 { -0.75 -0.05 0.35  1 } 
      }
      <Vertex> 8 {
        -0.5 0.5 0.5
        <Normal> { 0 1 0 }
        <RGBA> { 0.5 0.5 0.0 1 }
		<AUX> nam01 { 0.05 0.25 0.25  1 } 
		<AUX> nam02 { -0.75 0.15 0.35  1 } 
      }
      <Vertex> 9 {
        0.5 0.5 0.5
        <Normal> { 0 1 0 }
        <RGBA> { 0.5 0.0 0.0 1 }
 		<AUX> nam01 { -0.25 -0.5 -0.65  1 } 
		<AUX> nam02 { -0.15 0.0 -0.35  1 } 
     }
      <Vertex> 10 {
        0.5 0.5 -0.5
        <Normal> { 0 1 0 }
        <RGBA> { 0.0 0.5 0.0 1 }
 		<AUX> nam01 { 0.0 0.25 -0.25  1 } 
		<AUX> nam02 { 0.0 0.10 0.35  1 } 
     }
      <Vertex> 11 {
        -0.5 0.5 -0.5
        <Normal> { 0 1 0 }
        <RGBA> { 0.0 1.0 0.5 1 }
 		<AUX> nam01 { -0.05 0.25 0.25  1 } 
		<AUX> nam02 { -0.05 -0.05 -0.15  1 } 
     }
      <Vertex> 12 {
        -0.5 0.5 -0.5
        <Normal> { 0 0 -1 }
        <RGBA> { 0.5 0.5 0.5 1 }
 		<AUX> nam01 { -0.05 0.25 -0.25  1 } 
		<AUX> nam02 { 0.05 0.15 -0.35  1 } 
     }
      <Vertex> 13 {
        0.5 0.5 -0.5
        <Normal> { 0 0 -1 }
        <RGBA> { 0.25 0.5 0.75 1 }
 		<AUX> nam01 { 0.0 0.25 -0.25  1 } 
		<AUX> nam02 { 0.0 0.15 -0.35  1 } 
     }
      <Vertex> 14 {
        0.5 -0.5 -0.5
        <Normal> { 0 0 -1 }
        <RGBA> { 0.5 0.75 0.25 1 }
 		<AUX> nam01 { 0.25 -0.25 -0.25  1 } 
		<AUX> nam02 { 0.75 -0.15 -0.35  1 } 
     }
      <Vertex> 15 {
        -0.5 -0.5 -0.5
        <Normal> { 0 0 -1 }
        <RGBA> { 0.75 0.25 0.5 1 }
 		<AUX> nam01 { -0.25 -0.25 -0.25  1 } 
		<AUX> nam02 { -0.75 -0.15 -0.35  1 } 
     }
      <Vertex> 16 {
        0.5 -0.5 -0.5
        <Normal> { 1 0 0 }
        <RGBA> { 1.0 1.0 1.0 1 }
 		<AUX> nam01 { 0.25 -0.25 -0.25  1 } 
		<AUX> nam02 { 0.75 -0.15 -0.35  1 } 
     }
      <Vertex> 17 {
        0.5 0.5 -0.5
        <Normal> { 1 0 0 }
        <RGBA> { 0.0 0.0 0.0 1 }
		<AUX> nam01 { 0.25 0.25 -0.25  1 } 
		<AUX> nam02 { 0.75 0.15 -0.35  1 } 
      }
      <Vertex> 18 {
        0.5 0.5 0.5
        <Normal> { 1 0 0 }
        <RGBA> { 0.5 0.5 0.0 1 }
		<AUX> nam01 { 0.25 0.25 0.25  1 } 
		<AUX> nam02 { 0.75 0.15 0.35  1 } 
      }
      <Vertex> 19 {
        0.5 -0.5 0.5
        <Normal> { 1 0 0 }
        <RGBA> { 0.5 0.0 0.5 1 }
 		<AUX> nam01 { 0.25 -0.25 0.25  1 } 
		<AUX> nam02 { 0.75 -0.15 0.35  1 } 
     }
      <Vertex> 20 {
        -0.5 0.5 -0.5
        <Normal> { -1 0 0 }
        <RGBA> { 0.0 0.5 0.5 1 }
		<AUX> nam01 { -0.25 0.25 -0.25  1 } 
		<AUX> nam02 { -0.75 0.15 -0.35  1 } 
      }
      <Vertex> 21 {
        -0.5 -0.5 -0.5
        <Normal> { -1 0 0 }
        <RGBA> { 0.0 0.5 1.0 1 }
 		<AUX> nam01 { -0.25 -0.25 -0.25  1 } 
		<AUX> nam02 { -0.75 -0.15 -0.35  1 } 
     }
      <Vertex> 22 {
        -0.5 -0.5 0.5
        <Normal> { -1 0 0 }
        <RGBA> { 0.5 1.0 0.0 1 }
 		<AUX> nam01 { -0.25 -0.25 0.25  1 } 
		<AUX> nam02 { -0.75 -0.15 0.35  1 } 
     }
      <Vertex> 23 {
        -0.5 0.5 0.5
        <Normal> { -1 0 0 }
        <RGBA> { 1.0 0.0 0.5 1 }
		<AUX> nam01 { -0.25 0.25 0.25  1 } 
		<AUX> nam02 { -0.75 0.15 0.35  1 } 
      }
    }
    <Polygon> {
      <Normal> { 0 -1 0 }
      <VertexRef> { 0 1 2 3 <Ref> { pCubeShape1.verts } }
    }
    <Polygon> {
      <Normal> { 0 0 1 }
      <VertexRef> { 4 5 6 7 <Ref> { pCubeShape1.verts } }
    }
    <Polygon> {
      <Normal> { 0 1 0 }
      <VertexRef> { 8 9 10 11 <Ref> { pCubeShape1.verts } }
    }
    <Polygon> {
      <Normal> { 0 0 -1 }
      <VertexRef> { 12 13 14 15 <Ref> { pCubeShape1.verts } }
    }
    <Polygon> {
      <Normal> { 1 0 0 }
      <VertexRef> { 16 17 18 19 <Ref> { pCubeShape1.verts } }
    }
    <Polygon> {
      <Normal> { -1 0 0 }
      <VertexRef> { 20 21 22 23 <Ref> { pCubeShape1.verts } }
    }
  }
}

First thing to check, in your EGG file they are called nam01 and nam02, but in the shader it is name01 and name02 (with an e).
If it still doesn’t work, you could try giving the inputs a context like “vtx_name01: TEXCOORD1”.

eh eh, well done: you spotted this stupid mistake of misnaming!
Thanks a lot

Additional question: is there a limit to the number of
entries, more precisely the number of vtx_xxx parameters that can be passed to a shader?

I’m asking since eventually I may need something like 62 vtx_xxx blend morphs…

Not a limit on the AUX entry, but there is probably a limit on the number of shader inputs.

see next

Yeah I think the shader method would work well if you did not need so many different shapes.
Probably the AUX entries would also work on your larger mesh if you give them a TEXCOORD1, 2, 3, etc. context, but yeah you are limited in number with that approach. Maybe there is some other context that is not limited to 8 inputs.
If one vertex is missing an input, it would probably get 1, 1, 1, 1 or 0, 0, 0, 0.
You can still do this though as long as you aren’t actually using more than 8 of the shapes at the same time. You would just have to generate a shader each time you change which shapes are being used that reads those specific inputs. You don’t need to write it to a file since the shader class can take a string input.

shapes = [1, 7, 35, 62]
aux_inputs = ' '.join(['float4 vtx_name%i: TEXCOORD%i,' % (n, i) for i, n in enumerate(shapes)])
weight_inputs = ' '.join(['uniform float weight%i,' % n for n in shapes])
shadercode = '''//Cg
void vshader(float4 vtx_position: POSITION,
   float4 vtx_color: COLOR,
   %s
   %s
   uniform float4x4 mat_modelproj,
   out float4 l_position:  POSITION,
   out float4 l_color0: COLOR0)
''' % (aux_inputs, weight_inputs)

If you have weight applied to more than 8 shapes at the same time you could sort them and ignore the shapes with lower weight.

Hi,
I just figured out that the 8 (related to TEXTURE8) limit can be extended (I reached 14…), ie with the following shader

//Cg
//Cg profile gp5vp gp5gp

struct BlendMeshes2 {
 float3 vtx_sm1; float3 vtx_sm2; float3 vtx_sm3; 
 float3 vtx_sm4; float3 vtx_sm5; float3 vtx_sm6;
 float3 vtx_sm7; float3 vtx_sm8; float3 vtx_sm9;
 float3 vtx_sm10;float3 vtx_sm11;float3 vtx_sm12;
 float3 vtx_sm13;float3 vtx_sm14;
};

void vshader(
	in float4 vtx_position:   POSITION,
	in float2 vtx_texcoord0:  TEXCOORD0,// I'm keeping this one for texturing!
	in BlendMeshes2	M,
	uniform float a1,
	uniform float a2,
	...
	uniform float a14,
	uniform float4x4 mat_modelproj,
	out float2 l_texcoord0 : TEXCOORD0, 
	out float4 l_position:   POSITION )
{  
  	float4 vtx_position_blend;
	vtx_position_blend.xyz = vtx_position.xyz 
	+ a1*M.vtx_sm1.xyz 
	+ ...
 	+ a14*M.vtx_sm14.xyz 
...

Performs ok!!! :slight_smile:

So I’m wondering:

(1) what are the actual limits as shader inputs (I’m using an upto date GeForce gear)?

(2) to which Semantics input variables has Panda3D bound the vtx_smx?? (I would have thought TEXTCOORD1-7 but apparently some more ‘room’ was found…)

(3) Could well be that other empty slots such as COLOR1,TANGENT0 were used behind the curtain?

(4) I manually tried to use as specified in msdn.microsoft.com/en-us/library … 85%29.aspx
TANGENT(n), NORMAL(n), … with no success (even with setting BASIC_SHADERS_ONLY to false)

Progressing, but things are not really clear. There seems to be some undocumented black_magic on the way Panda3D is passing parameters to the shaders (not really well documented)

The actual limits I suppose it would depend on the shader profile, so you would have to find a reference document. The limit I hit is 20, and looks like your vshader has 20 inputs also (counting the struct as 1).
Maybe you can put the a1, a2, etc. in a struct also?

I suspect the limit of the number of entries are unrelated to the uniform parameters that are passed as register

In fact if I merely remove the ai, ie have simply a non weighted sum of the meshes, I can’t go further than 14

:gobj(error): blendFG_TEX.sha: (110) : error C5041: cannot locate suitable resource to bind parameter "vtx_sm15"
:gobj(error): blendFG_TEX.sha: (110) : error C5041: cannot locate suitable resource to bind parameter "vtx_sm16"

In principle using profile:

should give access to input parameters listed in Cg and msdn.microsoft.com/en-us/library … 85%29.aspx
For whatever (obscure) reason this is not the case!