Curved world shader does not work with dynamic batching

I have used the following curved world shader in the game.
https://playcanvas.com/editor/scene/1353984

Problem : Curve shader works with static batching but does not work with dynamic batching.

Here is the test project for the issue.
https://playcanvas.com/editor/scene/1837572

Hi @yash_mehrotra,

Thatā€™s correct, when enabling dynamic batching the engine overrides the curved world transform chunk removing the effect. Sadly currently there isnā€™t any elegant way around that. There is a feature request on this:

1 Like

Faced the same problem. When batching is enabled, the curve effect is canceled. Now is there any solution (elegant or not)?

Is it marked as solved?

Changed code from example

var CurvedWorld = pc.createScript('curvedWorld');

CurvedWorld.attributes.add('curvePower', {
  type: 'vec4',
  default: [1, -1, 0, 0],
  description: 'Determines the amount of curviness to be applied.',
});

CurvedWorld.prototype.initialize = function () {
  CurvedWorld.instance = this;
  // --- variables
  //this.activeCamera = this.app.root.findByName('Camera');

  // --- make sure all materials are loaded before calling this
  this.materials = this.app.assets.filter(function (asset) {
    return asset.type === 'material';
  }).map(o => o.resource);

  // --- update the material shader
  this.materials.forEach(material => 
  {
    this.updateShader(material);;
    material.setParameter('curvePower', [this.curvePower.x * 0.001, this.curvePower.y * 0.001, this.curvePower.z * 0.001, this.curvePower.w * 0.001])
  });

  // --- events
  this.on('attr', () => {
    this.materials.forEach(material => 
    {
      material.setParameter('curvePower', [this.curvePower.x * 0.001, this.curvePower.y * 0.001, this.curvePower.z * 0.001, this.curvePower.w * 0.001]);
      this.updateShaderUniforms(material)
    });
  });
};

CurvedWorld.prototype.postInitialize = function(){
  console.log(this.app.batcher._batchList);
};

CurvedWorld.prototype.updateShader = function (material) {
  //console.log(material.chunks.transformVS);
  material.chunks.transformVS =
    //'uniform vec3 curveDirection;\n' +
    //'uniform float curvePower;\n' +
    'uniform vec4 curvePower;\n' +
    'uniform vec3 curveCenterPos;\n' +
    //'uniform vec3 curveCameraDirection;\n' +
    '#ifdef PIXELSNAP\n' +
    'uniform vec4 uScreenSize;\n' +
    '#endif\n' +
    '#ifdef MORPHING\n' +
    'uniform vec4 morph_weights_a;\n' +
    'uniform vec4 morph_weights_b;\n' +
    '#endif\n' +
    '#ifdef MORPHING_TEXTURE_BASED\n' +
    'uniform vec4 morph_tex_params;\n' +
    'vec2 getTextureMorphCoords() {\n' +
    '    float vertexId = morph_vertex_id;\n' +
    '    vec2 textureSize = morph_tex_params.xy;\n' +
    '    vec2 invTextureSize = morph_tex_params.zw;\n' +
    '    // turn vertexId into int grid coordinates\n' +
    '    float morphGridV = floor(vertexId * invTextureSize.x);\n' +
    '    float morphGridU = vertexId - (morphGridV * textureSize.x);\n' +
    '    // convert grid coordinates to uv coordinates with half pixel offset\n' +
    '    return (vec2(morphGridU, morphGridV) * invTextureSize) + (0.5 * invTextureSize);\n' +
    '}\n' +
    '#endif\n' +
    '#ifdef MORPHING_TEXTURE_BASED_POSITION\n' +
    'uniform highp sampler2D morphPositionTex;\n' +
    '#endif\n' +
    'mat4 getModelMatrix() {\n' +
    '    #ifdef DYNAMICBATCH\n' +
    '    return getBoneMatrix(vertex_boneIndices);\n' +
    '    #elif defined(SKIN)\n' +
    '    return matrix_model * getSkinMatrix(vertex_boneIndices, vertex_boneWeights);\n' +
    '    #elif defined(INSTANCING)\n' +
    '    return mat4(instance_line1, instance_line2, instance_line3, instance_line4);\n' +
    '    #else\n' +
    '    return matrix_model;\n' +
    '    #endif\n' +
    '}\n' +
    'vec4 getPosition() {\n' +
    '    dModelMatrix = getModelMatrix();\n' +
    '    vec3 localPos = vertex_position;\n' +
    '    #ifdef NINESLICED\n' +
    '    // outer and inner vertices are at the same position, scale both\n' +
    '    localPos.xz *= outerScale;\n' +
    '    // offset inner vertices inside\n' +
    '    // (original vertices must be in [-1;1] range)\n' +
    '    vec2 positiveUnitOffset = clamp(vertex_position.xz, vec2(0.0), vec2(1.0));\n' +
    '    vec2 negativeUnitOffset = clamp(-vertex_position.xz, vec2(0.0), vec2(1.0));\n' +
    '    localPos.xz += (-positiveUnitOffset * innerOffset.xy + negativeUnitOffset * innerOffset.zw) * vertex_texCoord0.xy;\n' +
    '    vTiledUv = (localPos.xz - outerScale + innerOffset.xy) * -0.5 + 1.0; // uv = local pos - inner corner\n' +
    '    localPos.xz *= -0.5; // move from -1;1 to -0.5;0.5\n' +
    '    localPos = localPos.xzy;\n' +
    '    #endif\n' +
    '    #ifdef MORPHING\n' +
    '    #ifdef MORPHING_POS03\n' +
    '    localPos.xyz += morph_weights_a[0] * morph_pos0;\n' +
    '    localPos.xyz += morph_weights_a[1] * morph_pos1;\n' +
    '    localPos.xyz += morph_weights_a[2] * morph_pos2;\n' +
    '    localPos.xyz += morph_weights_a[3] * morph_pos3;\n' +
    '    #endif // MORPHING_POS03\n' +
    '    #ifdef MORPHING_POS47\n' +
    '    localPos.xyz += morph_weights_b[0] * morph_pos4;\n' +
    '    localPos.xyz += morph_weights_b[1] * morph_pos5;\n' +
    '    localPos.xyz += morph_weights_b[2] * morph_pos6;\n' +
    '    localPos.xyz += morph_weights_b[3] * morph_pos7;\n' +
    '    #endif // MORPHING_POS47\n' +
    '    #endif // MORPHING\n' +
    '    #ifdef MORPHING_TEXTURE_BASED_POSITION\n' +
    '    // apply morph offset from texture\n' +
    '    vec2 morphUV = getTextureMorphCoords();\n' +
    '    vec3 morphPos = texture2D(morphPositionTex, morphUV).xyz;\n' +
    '    localPos += morphPos;\n' +
    '    #endif\n' +
    '    vec4 posW = dModelMatrix * vec4(localPos, 1.0);\n' +
    // --- START custom curve code
    // '    float amountX = curveCameraDirection.x * pow(posW.x - curveCenterPos.x, 2.0);\n' +
    // '    float amountY = curveCameraDirection.y * pow(posW.y - curveCenterPos.y, 2.0);\n' +
    // '    float amountZ = curveCameraDirection.z * pow(posW.z - curveCenterPos.z, 2.0);\n' +
    // '    float amountSum = (amountX * curveCameraDirection.x + amountY * curveCameraDirection.y + amountZ * curveCameraDirection.z) * curvePower;\n' +
    // '    posW = posW + vec4(-amountX * curveCameraDirection.x * curvePower.x,-amountY * curveCameraDirection.y * curvePower.y,-amountZ * curveCameraDirection.z * curvePower.z,0);\n' +
    // '    posW.x += -amountSum;\n' +
    '    float amountX = pow(posW.x - curveCenterPos.x, 2.0);\n' +
    //'    float amountY = pow(posW.y - curveCenterPos.y, 2.0);\n' +
    '    float amountZ = pow(posW.z - curveCenterPos.z, 2.0);\n' +
    //'    float amountSum = amountX + amountY + amountZ;\n' +
    '    float amountSum = amountX + amountZ;\n' +
    '    posW += vec4(curvePower.x, curvePower.y, curvePower.z, curvePower.w) * amountSum;\n' +
    // --- END custom curve code

    '    #ifdef SCREENSPACE\n' +
    '    posW.zw = vec2(0.0, 1.0);\n' +
    '    #endif\n' +
    '    dPositionW = posW.xyz;\n' +
    '    vec4 screenPos;\n' +
    '    #ifdef UV1LAYOUT\n' +
    '    screenPos = vec4(vertex_texCoord1.xy * 2.0 - 1.0, 0.5, 1);\n' +
    '    #else\n' +
    '    #ifdef SCREENSPACE\n' +
    '    screenPos = posW;\n' +
    '    #else\n' +
    '    screenPos = matrix_viewProjection * posW;\n' +
    '    #endif\n' +
    '    #ifdef PIXELSNAP\n' +
    '    // snap vertex to a pixel boundary\n' +
    '    screenPos.xy = (screenPos.xy * 0.5) + 0.5;\n' +
    '    screenPos.xy *= uScreenSize.xy;\n' +
    '    screenPos.xy = floor(screenPos.xy);\n' +
    '    screenPos.xy *= uScreenSize.zw;\n' +
    '    screenPos.xy = (screenPos.xy * 2.0) - 1.0;\n' +
    '    #endif\n' +
    '    #endif\n' +
    '    return screenPos;\n' +
    '}\n' +
    'vec3 getWorldPosition() {\n' +
    '    return dPositionW;\n' +
    '}\n';
  
  material.update();
  
  //console.log(material.chunks.transformVS);

  this.updateShaderUniforms(material);
};

CurvedWorld.prototype.postUpdate = function () {
  if (!this.materials) return;

  this.materials.forEach(material => this.updateShaderUniforms(material));
};

CurvedWorld.prototype.updateShaderUniforms = function (material) {

  this.activeCamera = this.app.root.findByName('Camera');
  if(!this.activeCamera) return;
  if(!this.activeCamera.followCamera) return;
  
  //const targetPos = this.activeCamera.getPosition();
  const targetPos = this.activeCamera.followCamera.followTarget.getPosition();
  material.setParameter('curveCenterPos', [targetPos.x, targetPos.y, targetPos.z]);
};

This post from another thread also looks like a solution, but I canā€™t figure out how to implement it.