flutter3d
Showcase Changelog 38 packages API reference

Texture transform

since 0.7.0 Formats and I/O

KHR_texture_transform lets a material move, scale or rotate a texture's coordinates without touching the texture itself, the case an atlas export needs when every material sampling one packed image has to read from its own corner of it. This page applies one to a plain mesh.

Step 1: A texture to see the transform on #

An 8x8 checker, small enough that a scale of four tiles it visibly across the plane instead of stretching one copy over the whole surface.

// An 8x8 checker, small enough that scaling the transform below by
// four makes the tiling obvious.
final pixels = Uint8List(8 * 8 * 4);
for (var y = 0; y < 8; y++) {
  for (var x = 0; x < 8; x++) {
    final lit = ((x ~/ 2) + (y ~/ 2)).isEven;
    final o = (y * 8 + x) * 4;
    final value = lit ? 235 : 25;
    pixels[o] = value;
    pixels[o + 1] = value;
    pixels[o + 2] = value;
    pixels[o + 3] = 255;
  }
}
final albedo = context.device.createTextureFromPixels(
  width: 8,
  height: 8,
  format: TextureFormat.r8g8b8a8UNormInt,
  pixels: ByteData.sublistView(pixels),
);

Step 2: Apply the transform to the mesh #

withTextureTransform moves a mesh's own texture coordinates by the extension's own order: scale, then rotate, then offset. It returns a new MeshData rather than editing the one it was given.

// `KHR_texture_transform`'s own order: scale, then rotate, then move.
// Scaling by four here tiles the checker four times across the plane
// instead of stretching one copy of it across the whole surface.
_plain = PlaneShape(width: 2, depth: 2).build();
final transform = TextureTransform(scale: Vector2(4.0, 4.0));
_moved = withTextureTransform(_plain, transform);

Note. This is not what a decoder does. A decoded document keeps the coordinates the file had beside the transform it named, so writing the document back out gives the file back. Applying the transform is what draws the surface correctly, and it happens here, at upload.

Step 3: Check the claim #

The plane's own corner, at U equal to one before the transform, should come back at exactly four.

final layout = _plain.layout;
final stride = layout.floatsPerVertex;
final uvOffset = layout.floatOffsetOf(VertexLayout.texcoord.name);
// The plane's corner at U=1 should come back at U=4, the same
// arithmetic checked independently of whatever `withTextureTransform`
// did internally.
final cornerOffset = stride + uvOffset;
final sourceU = _plain.vertices[cornerOffset];
final movedU = _moved.vertices[cornerOffset];
if ((movedU - sourceU * 4.0).abs() > 1e-6 || sourceU == 0.0) {
  throw StateError('the transform did not scale U by four');
}
if (_moved.vertices.length != _plain.vertices.length) {
  throw StateError('the transform changed the vertex count');
}
if (frame.drawCalls < 1) {
  throw StateError('the floor was not drawn');
}