Vertex Colors and UVs

Vertex Colors and Multiple UV Sets

A mesh can carry a color attribute (four floats per vertex) and up to six UV sets (uv to uv6). Both built-in materials multiply their base color by vertex colors when present (mesh.useVertexColors defaults to true; set hasVertexAlpha for their alpha to count). Vertex colors carry cheap baked occlusion, and a second UV set holds a lightmap, whose unique layout differs from the albedo's tiling one. Textures pick a set with coordinatesIndex:

Vertex colors darkening a floor's edges, and a lightmap on the second UV setJavaScript
const floor = BABYLON.MeshBuilder.CreateGround('shade',
  { width: 8, height: 6, subdivisions: 4 }, scene);
const p = floor.getVerticesData(BABYLON.VertexBuffer.PositionKind);
const colors = [];
for (let i = 0; i < p.length; i += 3) {                  // 1 in the middle, 0.55 at corners
  const edge = Math.max(Math.abs(p[i]) / 4, Math.abs(p[i + 2]) / 3);
  colors.push(...Array(3).fill(1 - 0.45 * edge * edge), 1);
}
floor.setVerticesData(BABYLON.VertexBuffer.ColorKind, colors);
floor.setVerticesData(BABYLON.VertexBuffer.UV2Kind, floor.getVerticesData('uv'));
floor.material = new BABYLON.StandardMaterial('lit', scene);
floor.material.lightmapTexture = baked;                  // a Texture you baked in Blender
floor.material.lightmapTexture.coordinatesIndex = 1;     // read uv2, not uv
floor.material.useLightmapAsShadowmap = true;            // multiply instead of add
console.log(floor.getVerticesDataKinds().join(', '), floor.getTotalVertices(), colors[0]);
Output
position, normal, uv, color, uv2 25 0.55

The 25 vertices' colors interpolate across 16 cells; finer shading needs more vertices, which is when a lightmap wins. Vertex colors cost one attribute and no texture fetch.

Floor shading two ways: per-vertex colors, and a lightmap read from a second UV setHTMLLive
<!doctype html>
<style>
  body { margin: 0; background: #f7f3ec; font: 13px system-ui, sans-serif; color: #333; }
  canvas { width: 100%; max-width: 600px; height: 320px; display: block; touch-action: none; }
  #info { max-width: 600px; margin: 0; padding: 4px 8px; font-family: monospace; }
</style>
<canvas id="view"></canvas>
<pre id="info"></pre>
<script src="https://cdn.jsdelivr.net/npm/babylonjs@9.28.0/babylon.js"></script>
<script>
const canvas = document.getElementById('view');
const engine = new BABYLON.Engine(canvas, true);
const scene = new BABYLON.Scene(engine);
scene.clearColor = BABYLON.Color4.FromHexString('#f7f3ecff');
const camera = new BABYLON.ArcRotateCamera('cam', -Math.PI / 2, 0.75, 14, new BABYLON.Vector3(0, 0, 0), scene);
camera.attachControl(canvas, true);
new BABYLON.HemisphericLight('sky', new BABYLON.Vector3(0, 1, 0), scene);

// A tiling plank texture for the albedo (read from the first UV set, repeated)
const planks = new BABYLON.DynamicTexture('planks', { width: 256, height: 256 }, scene);
const pc = planks.getContext();
for (let i = 0; i < 4; i++) {
  pc.fillStyle = ['#c79a6c', '#b98b5d', '#cfa678', '#b28157'][i];
  pc.fillRect(0, i * 64, 256, 64);
  pc.fillStyle = 'rgba(60,35,20,0.5)';
  pc.fillRect(0, i * 64, 256, 2);
  pc.fillRect((i * 97) % 256, i * 64, 2, 64);
}
planks.update();
planks.uScale = planks.vScale = 3;                    // tiling: wraps several times

// Left: vertex colors, 1 in the middle and 0.55 at the corners
const shade = BABYLON.MeshBuilder.CreateGround('shade', { width: 8, height: 6, subdivisions: 4 }, scene);
shade.position.x = -4.3;
const p = shade.getVerticesData(BABYLON.VertexBuffer.PositionKind);
const colors = [];
for (let i = 0; i < p.length; i += 3) {
  const edge = Math.max(Math.abs(p[i]) / 4, Math.abs(p[i + 2]) / 3);
  colors.push(...Array(3).fill(1 - 0.45 * edge * edge), 1);
}
shade.setVerticesData(BABYLON.VertexBuffer.ColorKind, colors);
shade.material = new BABYLON.StandardMaterial('vc', scene);
shade.material.diffuseTexture = planks;              // multiplied by the vertex colors
shade.material.specularColor = BABYLON.Color3.Black();

// Right: a baked lightmap (drawn here: two pools of lamp light) read from uv2
const lit = BABYLON.MeshBuilder.CreateGround('lit', { width: 8, height: 6, subdivisions: 4 }, scene);
lit.position.x = 4.3;
lit.setVerticesData(BABYLON.VertexBuffer.UV2Kind, lit.getVerticesData('uv'));   // a unique 0..1 layout
const baked = new BABYLON.DynamicTexture('baked', { width: 256, height: 192 }, scene);
const bc = baked.getContext();
bc.fillStyle = '#6e6a66';
bc.fillRect(0, 0, 256, 192);
for (const [x, y] of [[80, 96], [180, 70]]) {
  const glow = bc.createRadialGradient(x, y, 5, x, y, 80);
  glow.addColorStop(0, '#ffffff'); glow.addColorStop(1, 'rgba(255,255,255,0)');
  bc.fillStyle = glow;
  bc.fillRect(0, 0, 256, 192);
}
baked.update();
lit.material = new BABYLON.StandardMaterial('lm', scene);
lit.material.diffuseTexture = planks;
lit.material.specularColor = BABYLON.Color3.Black();
lit.material.lightmapTexture = baked;
lit.material.lightmapTexture.coordinatesIndex = 1;     // read uv2, not uv
lit.material.useLightmapAsShadowmap = true;            // multiply instead of add

document.getElementById('info').textContent =
  `left:  ${shade.getVerticesDataKinds().join(', ')}  (${shade.getTotalVertices()} vertex colors)\n` +
  `right: ${lit.getVerticesDataKinds().join(', ')}  (lightmap on uv2)`;
engine.runRenderLoop(() => scene.render());
addEventListener('resize', () => engine.resize());
</script>