Explosion shader (code & video)

Ahem, matrices, ahem.

(Might need a little updating - I need to look up if newer versions of Codea introduced new methods for combining vectors and matrices.)

@Ignatz OK, I didn’t realise that you could multiply a vector on the left of a matrix in openGL. The code you posted above m.shader.invModel=modelMatrix():inverse():transpose() ... v = v * mInvModel; works. I was just hoping that I could convert the world vector into a model vector outside of the shader and then pass it in to the shader, on the assumption that doing this once in Codea would be faster than doing it, say, several thousand times in the vertex shader (although, the GPU does multiplication so blindingly fast, maybe it makes no difference?). Generally (as the code above shows) localCoord = modelMatrix():inverse * worldCoord, seems to work acceptably well in Codea (out by 0.00001). But for some reason I haven’t been able to fathom yet, something peculiar happens when I try to pass that converted world coord into the shader, and I’ve found that the value for z needs to be placed in w, which just seems wrong.

@LoopSpace thanks for that link, wonderfully informative as ever. One thing that I think has changed is that you say that Codea doesn’t support multiplying a matrix by a vector, which it does do now (though only with the vector on the right). Apologies if I’ve misunderstood.

Codea always multiplied on the right, but it never has done on the left

Codea does now do matrix times vector, but it’s weird. You can do m * v where m is a 4x4 matrix and v is a vec3 and you get the result of the transformation described at the bottom of this section of my matrices description, specifically the transformation [x y z] -> [x'', y'', z'']. That isn’t actually all that useful as it is useless for, say, doing anything with normals. It also means that while you write it as multiplication on the right, it is actually doing multiplication on the left. I haven’t investigated what this means for associativity, but it might be weird.

You can also multiply a matrix by a vec4, but that’s just odd. Try it; if you can discern any logic to it then you’re better than me!

Ok, I think I’ve worked out how to pass the world vector for gravity into the shader. In the shader, if you set w to zero, ie vec4(gravity, 0.) then it works. I’ll post the working code tomorrow.

@LoopSpace - can you explain this strange result?

m=matrix(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16)
    print(m*vec4(1,0,0,0)) -- 1   2  3   4
    print(m*vec4(0,1,0,0)) -- 5   6  7   8
    print(m*vec4(0,0,1,0)) -- 0   0  0   0  << ???
    print(m*vec4(0,0,0,1)) -- 9 10 11 12 << ???
    print(m*vec4(1,1,1,1))

@yojimbo- I think this is the strange thing you were talking about, how z and w swapped around - although (even worse) z is zero above!

@Ignatz that is weird. I get the same result. You get the correct results if you use a custom mat * vec function, eg:

m=matrix(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16)
print(matXVec(m,vec4(1,0,0,0))) -- 1   2  3   4
print(matXVec(m,vec4(0,1,0,0))) -- 5   6  7   8
print(matXVec(m,vec4(0,0,1,0))) -- 9 10 11 12
print(matXVec(m,vec4(0,0,0,1))) -- 13 14 15 16
print(matXVec(m,vec4(1,1,1,1)))

function matXVec(m,v)
    return vec4(
    m[1]*v.x + m[5]*v.y + m[9]*v.z + m[13]*v.w,
    m[2]*v.x + m[6]*v.y + m[10]*v.z + m[14]*v.w,
    m[3]*v.x + m[7]*v.y + m[11]*v.z + m[15]*v.w,
    m[4]*v.x + m[8]*v.y + m[12]*v.z + m[16]*v.w)
end

I assumed actually that the built-in codea mat*vec function resembled the above, but it seems it’s doing something a bit different. Is this a bug? Or just some alternative way of doing matrix multiplications that I don’t know about?

I often use a 3x3 version of the above function (or 2x2 in 2D) if I just want to get the rotation of the matrix, not the translation or scale. I can’t remember where I got this from. Probably from one of you two.

function vecRotMat(v, m)
    return vec3(
    m[1]*v.x + m[5]*v.y + m[9]*v.z,
    m[2]*v.x + m[6]*v.y + m[10]*v.z,
    m[3]*v.x + m[7]*v.y + m[11]*v.z)
end

The gist linked to above now has the updated version. World gravity is transformed to local gravity in Codea and passed to the shader:

m.shader.gravity = modelMatrix():inverse() * vec3(0,0,-0.05)

In the shader, the w of gravity is set to zero:

    highp vec4 A = vec4(gravity, 0.)/(friction*friction) - vec4(trajectory.xyz, 0.)/friction;
    highp vec4 B = origin - A;

    vec4 pos = position - origin; // convert to local
    pos.xy = rotMat * pos.xy; // rotate
    pos += exp(-time*friction)*A + B + time * vec4(gravity, 0.)/friction;

I guess this is necessary because, like a normal, gravity is a relational vector, rather than one describing a fixed point.

That’s a bug, I think

And yes, directions have a w=0, while points have w=1

That’s the behaviour I was referring to. There should be a mat4 x vec4 method and it should be right. I don’t think I ever got round to filing a bug report, though. Any volunteers?

It’s also a bit weird that Codea writes matrix multiplication on the left (m * v) but the effect is multiplication on the right (v * m). It would break associately, except that I don’t know if I would expect associativity with mat4 x vec3 as it’s not a natural operation.

I think that in my own code then i overwrite the matrix x vector multiplication with the correct one.

I get very confused by this on the left/ on the right. So is this multiplication on the right?

function matXVec(m,v)
    return vec4(
    m[1]*v.x + m[5]*v.y + m[9]*v.z + m[13]*v.w,
    m[2]*v.x + m[6]*v.y + m[10]*v.z + m[14]*v.w,
    m[3]*v.x + m[7]*v.y + m[11]*v.z + m[15]*v.w,
    m[4]*v.x + m[8]*v.y + m[12]*v.z + m[16]*v.w)
end

@yojimbo2000 The crucial difference is whether you regard vectors as rows or columns. Then when you write a matrix, depending on your convention either the rows or the columns are where the standard vectors end up. If rows then you need to do v * m and if columns you need to do m * v. It also has an effect on composition. If you want to do A and then B, is the result A * B or B * A? If rows, then it is A * B and if columns then it is B * A.

Codea / OpenGL is always column matrices isn’t it?

So, how should we word the bug report? Is there something more specific we can say than “Mat * vec4 is sometimes weird”?

No, OpenGL is row matrices, contrary to just about everyone else in the world.

That seems a reasonable bug report! Could say that mat4 * vec4 should be the correct matrix multiplication. For bonus marks, it should be possible to do v * m and m * v and have them come out correctly (ie differently).

I already reported the bug to Simeon. I think my code example above is clear enough.

Thank you both.

I’ve added it to the issue tracker. Feel free to vote for it!

https://bitbucket.org/TwoLivesLeft/core/issue/362/mat4-vec4-produces-odd-results

Simeon said he’ll look at it

@yojimbo2000 i tried to add a texture to the disintegration shader with
M.texture=image but did not succeed. Any suggestions?

@piinthesky yeah, the shader in the code is for objects without textures. I’m away from my iPad so can’t test this, but the below should work for textured objects. Let me know if it throws errors:

shaders = {
explodeVert=    [[
uniform mat4 modelViewProjection;
uniform mat4 modelMatrix;
uniform vec4 eye; // -- position of camera (x,y,z,1)
//uniform vec4 light; //--directional light direction (x,y,z,0)
uniform float fogRadius;
uniform vec4 lightColor; //--directional light colour
uniform float time;// animate explosion
//uniform bool hasTexture;
uniform vec3 gravity; 
const float friction = 0.02;
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
attribute vec3 normal;
attribute vec4 origin; //centre of each face
attribute vec4 trajectory; // trajectory + w = angular velocity
varying lowp vec4 vColor;
varying float dist;
varying highp vec2 vTexCoord;
varying vec4 vNormal;
varying vec4 vPosition;
void main()
{
    float angle = time * trajectory.w;
    float angCos = cos(angle);
    float angSin = sin(angle);
    lowp mat2 rotMat = mat2(angCos, angSin, -angSin, angCos); 
    vec3 normRot = normal;
      normRot.xy = rotMat * normRot.xy; 
    vNormal = normalize(modelMatrix * vec4( normRot, 0.0 ));
   // vDirectDiffuse = lightColor * max( 0.0, dot( norm, light )); // brightness of diffuse light
    
    highp vec4 A = vec4(gravity, 0.)/(friction*friction) - vec4(trajectory.xyz, 0.)/friction;
    highp vec4 B = origin - A;
    vec4 pos = position - origin; // convert to local
    pos.xy = rotMat * pos.xy; // rotate
    pos += exp(-time*friction)*A + B + time * vec4(gravity, 0.)/friction;
    vPosition = modelMatrix * pos; 
    
    dist = clamp(1.0-distance(vPosition.xyz, eye.xyz)/fogRadius+0.1, 0.0, 1.1); //(vPosition.y-eye.y)
    
    vColor = color;
    vTexCoord = texCoord;
    gl_Position = modelViewProjection * pos;
}
]],


frag = [[
precision highp float;
uniform lowp sampler2D texture;
uniform float ambient; // --strength of ambient light 0-1
uniform lowp vec4 aerial;
uniform vec4 light; //--directional light direction (x,y,z,0)
uniform vec4 lightColor; //--directional light colour
uniform vec4 eye; // -- position of camera (x,y,z,1)
const float specularPower = 48.;
const float shine = 0.8;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
varying float dist;
varying vec4 vPosition;
varying vec4 vNormal;
// varying vec4 vSpecular;
void main()
{
     
    lowp vec4 pixel= texture2D( texture, vTexCoord ) * vColor;
    lowp vec4 ambientLight = pixel * ambient;    

    vec4 norm = normalize(vNormal);
    if (! gl_FrontFacing) norm = -vNormal; //invert normal if back facing (double-sided faces)
    vec4 viewDirection = normalize(eye - vPosition);
    vec4 diffuse = lightColor * max( 0.0, dot( norm, light )) * pixel; // brightness of diffuse light
    vec4 specular = vec4(1.,1.,1.,1.) * pow(max(0.0, dot(reflect(light, norm), viewDirection)), specularPower) * shine;
    //  vec4 halfAngle = normalize( viewDirection + light );
    //   float spec = pow( max( 0.0, dot( norm, halfAngle)), specularPower );
    // vec4 specular = vec4(1.,1.,1.,1.) * spec * shine; //
    
    vec4 totalColor = mix(aerial, ambientLight + diffuse + specular, dist * dist);
    
    totalColor.a=1.;
    
    gl_FragColor=totalColor;
}
]]}