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.
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…
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.
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)
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.
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()
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”.
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.
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?