How to make vertex shader work with Rig/Pose

Hi there,
I’ve been using a custom fragment shader to light my model, with the ‘default’ vertex shader shown on the PlayCanvas custom shader page. I’ve just run into an issue when I try to apply this to a rigged model with a pose, it sets it to T pose. How can I allow my vertex shader to work with a rigged/posed model? Ideally I’d still like to access normals as well.
Thanks!

Anyone have any info on this?

You’d need to see what the standard vertex shader does, and do something similar. See here:

Specifically skinTex.js and how it is used in transform.js

1 Like

Thanks for the response, do you know how I can access and assign the vertex_boneWeights and vertex_boneIndices attributes in skinTex.js?

if those are part of the mesh, those should be available in the shader. What problem are you facing?

My issue is that the character is rendering with no errors, but the mesh is not showing the rigged pose. Here is my complete vertex code:

attribute vec3 aPosition;
attribute vec3 aNormal;
attribute vec2 aUv0;

uniform mat4 matrix_model;
uniform mat3 matrix_normal;
uniform mat4 matrix_viewProjection;

varying vec3 vNormal;
varying vec2 vUv;
varying float depth;
varying vec4 screenPos;

/////////////////////////////////
//Start of copied Skinning Code//
/////////////////////////////////
uniform highp sampler2D texture_poseMap;
uniform vec4 texture_poseMapSize;

#ifdef PIXELSNAP
uniform vec4 uScreenSize;
#endif
#ifdef SCREENSPACE
uniform float projectionFlipY;
#endif
#ifdef MORPHING
uniform vec4 morph_weights_a;
uniform vec4 morph_weights_b;
#endif
#ifdef MORPHING_TEXTURE_BASED
uniform vec4 morph_tex_params;

void getBoneMatrix(const in float index, out vec4 v1, out vec4 v2, out vec4 v3) {
    float i = float(index);
    float j = i * 3.0;
    float dx = texture_poseMapSize.z;
    float dy = texture_poseMapSize.w;
    
    float y = floor(j * dx);
    float x = j - (y * texture_poseMapSize.x);
    y = dy * (y + 0.5);
    // read elements of 4x3 matrix
    v1 = texture2D(texture_poseMap, vec2(dx * (x + 0.5), y));
    v2 = texture2D(texture_poseMap, vec2(dx * (x + 1.5), y));
    v3 = texture2D(texture_poseMap, vec2(dx * (x + 2.5), y));
}
mat4 getSkinMatrix(const in vec4 indices, const in vec4 weights) {
    // get 4 bone matrices
    vec4 a1, a2, a3;
    getBoneMatrix(indices.x, a1, a2, a3);
    vec4 b1, b2, b3;
    getBoneMatrix(indices.y, b1, b2, b3);
    vec4 c1, c2, c3;
    getBoneMatrix(indices.z, c1, c2, c3);
    vec4 d1, d2, d3;
    getBoneMatrix(indices.w, d1, d2, d3);
    // multiply them by weights and add up to get final 4x3 matrix
    vec4 v1 = a1 * weights.x + b1 * weights.y + c1 * weights.z + d1 * weights.w;
    vec4 v2 = a2 * weights.x + b2 * weights.y + c2 * weights.z + d2 * weights.w;
    vec4 v3 = a3 * weights.x + b3 * weights.y + c3 * weights.z + d3 * weights.w;
    // add up weights
    float one = dot(weights, vec4(1.0));
    // transpose to 4x4 matrix
    return mat4(
        v1.x, v2.x, v3.x, 0,
        v1.y, v2.y, v3.y, 0,
        v1.z, v2.z, v3.z, 0,
        v1.w, v2.w, v3.w, one
    );
}

vec2 getTextureMorphCoords() {
    float vertexId = morph_vertex_id;
    vec2 textureSize = morph_tex_params.xy;
    vec2 invTextureSize = morph_tex_params.zw;
    // turn vertexId into int grid coordinates
    float morphGridV = floor(vertexId * invTextureSize.x);
    float morphGridU = vertexId - (morphGridV * textureSize.x);
    // convert grid coordinates to uv coordinates with half pixel offset
    vec2 uv = (vec2(morphGridU, morphGridV) * invTextureSize) + (0.5 * invTextureSize);
    return getImageEffectUV(uv);
}
#endif
#ifdef MORPHING_TEXTURE_BASED_POSITION
uniform highp sampler2D morphPositionTex;
#endif
mat4 getModelMatrix() {
    #ifdef DYNAMICBATCH
    return getBoneMatrix(vertex_boneIndices);
    #elif defined(SKIN)
    return matrix_model * getSkinMatrix(vertex_boneIndices, vertex_boneWeights);
    #elif defined(INSTANCING)
    return mat4(instance_line1, instance_line2, instance_line3, instance_line4);
    #else
    return matrix_model;
    #endif
}
vec4 getPosition() {
    mat4 dModelMatrix = getModelMatrix();
    vec3 localPos = aPosition;
    #ifdef NINESLICED
    // outer and inner vertices are at the same position, scale both
    localPos.xz *= outerScale;
    // offset inner vertices inside
    // (original vertices must be in [-1;1] range)
    vec2 positiveUnitOffset = clamp(aPosition.xz, vec2(0.0), vec2(1.0));
    vec2 negativeUnitOffset = clamp(-aPosition.xz, vec2(0.0), vec2(1.0));
    localPos.xz += (-positiveUnitOffset * innerOffset.xy + negativeUnitOffset * innerOffset.zw) * vertex_texCoord0.xy;
    vTiledUv = (localPos.xz - outerScale + innerOffset.xy) * -0.5 + 1.0; // uv = local pos - inner corner
    localPos.xz *= -0.5; // move from -1;1 to -0.5;0.5
    localPos = localPos.xzy;
    #endif
    #ifdef MORPHING
    #ifdef MORPHING_POS03
    localPos.xyz += morph_weights_a[0] * morph_pos0;
    localPos.xyz += morph_weights_a[1] * morph_pos1;
    localPos.xyz += morph_weights_a[2] * morph_pos2;
    localPos.xyz += morph_weights_a[3] * morph_pos3;
    #endif // MORPHING_POS03
    #ifdef MORPHING_POS47
    localPos.xyz += morph_weights_b[0] * morph_pos4;
    localPos.xyz += morph_weights_b[1] * morph_pos5;
    localPos.xyz += morph_weights_b[2] * morph_pos6;
    localPos.xyz += morph_weights_b[3] * morph_pos7;
    #endif // MORPHING_POS47
    #endif // MORPHING
    #ifdef MORPHING_TEXTURE_BASED_POSITION
    // apply morph offset from texture
    vec2 morphUV = getTextureMorphCoords();
    vec3 morphPos = texture2D(morphPositionTex, morphUV).xyz;
    localPos += morphPos;
    #endif
    vec4 posW = dModelMatrix * vec4(localPos, 1.0);
    #ifdef SCREENSPACE
    posW.zw = vec2(0.0, 1.0);
    #endif
    vec3 dPositionW = posW.xyz;
    vec4 screenPos;
    #ifdef UV1LAYOUT
    screenPos = vec4(vertex_texCoord1.xy * 2.0 - 1.0, 0.5, 1);
    #else
    #ifdef SCREENSPACE
    screenPos = posW;
    screenPos.y *= projectionFlipY;
    #else
    screenPos = matrix_viewProjection * posW;
    #endif
    #ifdef PIXELSNAP
    // snap vertex to a pixel boundary
    screenPos.xy = (screenPos.xy * 0.5) + 0.5;
    screenPos.xy *= uScreenSize.xy;
    screenPos.xy = floor(screenPos.xy);
    screenPos.xy *= uScreenSize.zw;
    screenPos.xy = (screenPos.xy * 2.0) - 1.0;
    #endif
    #endif
    return screenPos;
}
//vec3 getWorldPosition() {
//    return dPositionW;
//}

///////////////////////////////
//END OF COPIED SKINNING CODE//
///////////////////////////////

void main(){
    vUv = aUv0;

    vNormal = normalize(matrix_normal * aNormal);

    //gl_Position = matrix_viewProjection * matrix_model * vec4(aPosition, 1.0);
    gl_Position = getPosition();
}

Since this seems more complex than I anticipated I may shift my approach to just put my frag shader in the diffusePS chunk. The problem is, I don’t know how to access the mesh normals in the frag shader with this approach. Is this possible?

I usually debug these kinds of issues by capturing a frame using SpectorJS (chrome plugin) and looking at the render call and all parameters it receives.
Compare your capture with a normal standard material shader to see what is different.

1 Like

Wow, what an incredible tool! Thank you so much for the help. Let me know if I’m causing any major issues by commenting out the defines on the top.

For anyone who needs it my final code is below:

//#version 300 es

//#define attribute in
//#define varying out
//#define texture2D texture
//#define GL2
//#define VERTEXSHADER



vec2 getGrabScreenPos(vec4 clipPos) {
    vec2 uv = (clipPos.xy / clipPos.w) * 0.5 + 0.5;
    return uv;
}
vec2 getImageEffectUV(vec2 uv) {
    return uv;
}
//#define SHADER_NAME LitShader
varying vec3 vPositionW;
varying vec3 vNormalW;
varying vec2 vUvW;
attribute vec3 vertex_position;
attribute vec3 vertex_normal;
attribute vec2 vertex_texCoord0;
uniform mat4 matrix_viewProjection;
uniform mat4 matrix_model;
uniform mat3 matrix_normal;
vec3 dPositionW;
mat4 dModelMatrix;
mat3 dNormalMatrix;
attribute vec4 vertex_boneWeights;
attribute vec4 vertex_boneIndices;
#define BoneIndexFormat float
#define BoneIndexFormat4 vec4

uniform highp sampler2D texture_poseMap;
uniform vec4 texture_poseMapSize;
void getBoneMatrix(const in BoneIndexFormat index, out vec4 v1, out vec4 v2, out vec4 v3) {
    float i = float(index);
    float j = i * 3.0;
    float dx = texture_poseMapSize.z;
    float dy = texture_poseMapSize.w;
    float y = floor(j * dx);
    float x = j - (y * texture_poseMapSize.x);
    y = dy * (y + 0.5);
    v1 = texture2D(texture_poseMap, vec2(dx * (x + 0.5), y));
    v2 = texture2D(texture_poseMap, vec2(dx * (x + 1.5), y));
    v3 = texture2D(texture_poseMap, vec2(dx * (x + 2.5), y));
}
mat4 getSkinMatrix(const in BoneIndexFormat4 indices, const in vec4 weights) {
    vec4 a1, a2, a3;
    getBoneMatrix(indices.x, a1, a2, a3);
    vec4 b1, b2, b3;
    getBoneMatrix(indices.y, b1, b2, b3);
    vec4 c1, c2, c3;
    getBoneMatrix(indices.z, c1, c2, c3);
    vec4 d1, d2, d3;
    getBoneMatrix(indices.w, d1, d2, d3);
    vec4 v1 = a1 * weights.x + b1 * weights.y + c1 * weights.z + d1 * weights.w;
    vec4 v2 = a2 * weights.x + b2 * weights.y + c2 * weights.z + d2 * weights.w;
    vec4 v3 = a3 * weights.x + b3 * weights.y + c3 * weights.z + d3 * weights.w;
    float one = dot(weights, vec4(1.0));
    return mat4(
    v1.x, v2.x, v3.x, 0, v1.y, v2.y, v3.y, 0, v1.z, v2.z, v3.z, 0, v1.w, v2.w, v3.w, one
    );
}
#define SKIN


mat4 getModelMatrix() {
    return matrix_model * getSkinMatrix(vertex_boneIndices, vertex_boneWeights);
}
vec4 getPosition() {
    dModelMatrix = getModelMatrix();
    vec3 localPos = vertex_position;
    vec4 posW = dModelMatrix * vec4(localPos, 1.0);
    dPositionW = posW.xyz;
    vec4 screenPos;
    screenPos = matrix_viewProjection * posW;
    return screenPos;
}
vec3 getWorldPosition() {
    return dPositionW;
}
vec3 getNormal() {
    dNormalMatrix = mat3(dModelMatrix[0].xyz, dModelMatrix[1].xyz, dModelMatrix[2].xyz);
    vec3 tempNormal = vertex_normal;
    return normalize(dNormalMatrix * tempNormal);
}
void main(void) {
    gl_Position = getPosition();
    vUvW = vertex_texCoord0;
    vPositionW = getWorldPosition();
    vNormalW = getNormal();
}

The vertex_boneWeight semantic is pc.SEMANTIC_BLENDWEIGHT and the vertex_boneIndices semantic is pc.SEMANTIC_BLENDINDICES.