Files
cup_edit/thermion_dart/test/texture_tests.dart
2025-03-10 22:21:25 +08:00

408 lines
15 KiB
Dart

@Timeout(const Duration(seconds: 600))
import 'dart:ffi';
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'package:test/test.dart';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:vector_math/vector_math_64.dart';
import 'helpers.dart';
void main() async {
final testHelper = TestHelper("material");
group("image", () {
test('create 2D texture & set from decoded image', () async {
await testHelper.withViewer((viewer) async {
var imageData =
File("${testHelper.testDir}/assets/cube_texture_512x512.png")
.readAsBytesSync();
final image = await viewer.decodeImage(imageData);
expect(await image.getChannels(), 4);
expect(await image.getWidth(), 512);
expect(await image.getHeight(), 512);
final texture = await viewer.createTexture(
await image.getWidth(), await image.getHeight(),
textureFormat: TextureFormat.RGBA32F);
await texture.setLinearImage(
image, PixelDataFormat.RGBA, PixelDataType.FLOAT);
await texture.dispose();
}, bg: kRed);
});
test('create 2D texture and set image from raw buffer', () async {
await testHelper.withViewer((viewer) async {
var imageData =
File("${testHelper.testDir}/assets/cube_texture_512x512.png")
.readAsBytesSync();
final image = await viewer.decodeImage(imageData);
expect(await image.getChannels(), 4);
expect(await image.getWidth(), 512);
expect(await image.getHeight(), 512);
final texture = await viewer.createTexture(
await image.getWidth(), await image.getHeight(),
textureFormat: TextureFormat.RGBA32F);
var data = await image.getData();
await texture.setImage(0, data.buffer.asUint8List(data.offsetInBytes),
512, 512, 4, PixelDataFormat.RGBA, PixelDataType.FLOAT);
await texture.dispose();
}, bg: kRed);
});
test('create 3D texture and set image from buffers', () async {
await testHelper.withViewer((viewer) async {
final width = 128;
final height = 128;
final channels = 4;
final depth = 5;
final texture = await viewer.createTexture(width, height,
depth: depth,
textureSamplerType: TextureSamplerType.SAMPLER_3D,
textureFormat: TextureFormat.RGBA32F);
for (int i = 0; i < depth; i++) {
final buffer = Uint8List(width * height * channels * sizeOf<Float>());
await texture.setImage3D(0, 0, 0, i, width, height, channels, 1,
buffer, PixelDataFormat.RGBA, PixelDataType.FLOAT);
}
await texture.dispose();
}, bg: kRed);
});
test('apply 3D texture material ', () async {
await testHelper.withViewer((viewer) async {
final material = await viewer.createMaterial(File(
"/Users/nickfisher/Documents/thermion/materials/texture_array.filamat")
.readAsBytesSync());
final materialInstance = await material.createInstance();
final sampler = await viewer.createTextureSampler();
final cube = await viewer.createGeometry(GeometryHelper.cube(),
materialInstances: [materialInstance]);
final width = 1;
final height = 1;
final channels = 4;
final numTextures = 2;
final texture = await viewer.createTexture(width, height,
depth: numTextures,
textureSamplerType: TextureSamplerType.SAMPLER_3D,
textureFormat: TextureFormat.RGBA32F);
for (int i = 0; i < numTextures; i++) {
var pixelBuffer = Float32List.fromList(
[i == 0 ? 1.0 : 0.0, i == 1 ? 1.0 : 0.0, 0.0, 1.0]);
var byteBuffer =
pixelBuffer.buffer.asUint8List(pixelBuffer.offsetInBytes);
await texture.setImage3D(0, 0, 0, i, width, height, channels, 1,
byteBuffer, PixelDataFormat.RGBA, PixelDataType.FLOAT);
}
await materialInstance.setParameterTexture(
"textures", texture, sampler);
await materialInstance.setParameterInt("activeTexture", 0);
await testHelper.capture(viewer, "3d_texture_0");
await materialInstance.setParameterInt("activeTexture", 1);
await testHelper.capture(viewer, "3d_texture_1");
await viewer.destroyAsset(cube);
await materialInstance.dispose();
await material.dispose();
await texture.dispose();
});
});
});
group("sampler", () {
test('create sampler', () async {
await testHelper.withViewer((viewer) async {
final sampler = viewer.createTextureSampler();
}, bg: kRed);
});
});
group('projection', () {
Future withProjectionMaterial(
ThermionViewer viewer,
Future Function(TextureSampler sampler, MaterialInstance mi,
RenderTarget rt, int width, int height)
fn) async {
// setup render target
final view = await viewer.getViewAt(0);
final vp = await view.getViewport();
;
final rtTextureHandle = await testHelper.createTexture(512, 512);
final (viewportWidth, viewportHeight) = (vp.width, vp.height);
final rt = await viewer.createRenderTarget(
viewportWidth, viewportHeight, rtTextureHandle.metalTextureAddress);
await view.setRenderTarget(rt);
// setup base material + geometry
final sampler = await viewer.createTextureSampler();
var projectionMaterial = await viewer.createMaterial(File(
"/Users/nickfisher/Documents/thermion/materials/capture_uv.filamat")
.readAsBytesSync());
expect(await projectionMaterial.hasParameter("flipUVs"), true);
var projectionMaterialInstance =
await projectionMaterial.createInstance();
await projectionMaterialInstance.setParameterBool("flipUVs", true);
await projectionMaterialInstance.setParameterTexture(
"color", await rt.getColorTexture(), sampler);
await fn(sampler, projectionMaterialInstance, rt, viewportWidth,
viewportHeight);
// cleanup
await sampler.dispose();
await projectionMaterialInstance.dispose();
await projectionMaterial.dispose();
}
Future withCube(
ThermionViewer viewer,
Future Function(ThermionAsset asset, MaterialInstance mi,
Future Function() resetMaterial)
fn) async {
// var material = await viewer.createUbershaderMaterialInstance(unlit: true);
var material = await viewer.createUnlitMaterialInstance();
final cube = await viewer
.createGeometry(GeometryHelper.cube(), materialInstances: [material]);
var sampler = await viewer.createTextureSampler();
var inputTextureData =
File("${testHelper.testDir}/assets/cube_texture2_512x512.png")
.readAsBytesSync();
var inputImage = await viewer.decodeImage(inputTextureData);
var inputTexture = await viewer.createTexture(
await inputImage.getWidth(), await inputImage.getHeight(),
textureFormat: TextureFormat.RGBA32F);
await inputTexture.setLinearImage(
inputImage, PixelDataFormat.RGBA, PixelDataType.FLOAT);
final resetMaterial = () async {
await material.setParameterInt("baseColorIndex", 0);
await material.setParameterTexture(
"baseColorMap", inputTexture, sampler);
await material.setParameterFloat4(
"baseColorFactor", 1.0, 1.0, 1.0, 1.0);
};
await resetMaterial();
await fn(cube, material, resetMaterial);
}
test('project texture & UV unwrap', () async {
await testHelper.withViewer((viewer) async {
final camera = await viewer.getMainCamera();
await withProjectionMaterial(viewer,
(sampler, projectionMaterialInstance, rt, width, height) async {
await withCube(viewer, (cube, ubershader, resetMaterial) async {
var objects = {"cube": cube};
for (final entry in objects.entries) {
final object = entry.value;
final key = entry.key;
await object.addToScene();
var divisions = 8;
for (int i = 0; i < divisions; i++) {
await camera.lookAt(Vector3(sin(i / divisions * pi) * 5, 0,
cos(i / divisions * pi) * 5));
await testHelper.capture(viewer, "color_${key}_$i",
renderTarget: rt);
await object.setMaterialInstanceAt(projectionMaterialInstance);
var projectionOutput = await testHelper
.capture(viewer, "uv_capture_${key}_$i", renderTarget: rt);
var floatPixelBuffer = Float32List.fromList(
projectionOutput.map((p) => p.toDouble() / 255.0).toList());
final projectedImage = await viewer.createImage(512, 512, 4);
final data = await projectedImage.getData();
data.setRange(0, data.length, floatPixelBuffer);
final projectedTexture = await viewer.createTexture(512, 512,
textureFormat: TextureFormat.RGBA32F);
await projectedTexture.setLinearImage(
projectedImage, PixelDataFormat.RGBA, PixelDataType.FLOAT);
await ubershader.setParameterTexture(
"baseColorMap", projectedTexture, sampler);
await object.setMaterialInstanceAt(ubershader);
await testHelper.capture(viewer, "retextured_${key}_$i",
renderTarget: rt);
await resetMaterial();
}
await viewer.destroyAsset(object);
}
});
});
}, viewportDimensions: (width: 512, height: 512));
});
Future usingVDTM(
ThermionViewer viewer,
List<Vector3> cameraPositions,
int width,
int height,
int channels,
Future Function(Texture texture, MaterialInstance mi) fn) async {
final sampler = await viewer.createTextureSampler();
var texture = await viewer.createTexture(width, height,
textureSamplerType: TextureSamplerType.SAMPLER_3D,
depth: cameraPositions.length,
textureFormat: TextureFormat.RGBA32F);
final vdtm = await viewer.createMaterial(
File("/Users/nickfisher/Documents/thermion/materials/vdtm.filamat")
.readAsBytesSync());
final materialInstance = await vdtm.createInstance();
await materialInstance.setParameterFloat3Array(
"cameraPositions", cameraPositions);
await materialInstance.setParameterTexture(
"perspectives", texture, sampler);
await fn(texture, materialInstance);
await materialInstance.dispose();
await vdtm.dispose();
await texture.dispose();
await sampler.dispose();
}
test('view dependent texture mapping (interpolated colors)', () async {
await testHelper.withViewer((viewer) async {
final cameraPositions = [
Vector3(0, 0, 5),
Vector3(5, 0, 0),
Vector3(0, 0, -5)
];
final camera = await viewer.getMainCamera();
final (numCameraPositions, width, height, channels) =
(cameraPositions.length, 1, 1, 4);
await usingVDTM(viewer, cameraPositions, width, height, channels,
(texture, materialInstance) async {
for (int i = 0; i < numCameraPositions; i++) {
final pixelBuffer = Float32List.fromList([
1 - (i / numCameraPositions),
i / numCameraPositions,
0.0,
1.0
]);
var byteBuffer =
pixelBuffer.buffer.asUint8List(pixelBuffer.offsetInBytes);
await texture.setImage3D(0, 0, 0, i, width, height, channels, 1,
byteBuffer, PixelDataFormat.RGBA, PixelDataType.FLOAT);
}
final cube = await viewer.createGeometry(GeometryHelper.cube(),
materialInstances: [materialInstance]);
for (int i = 0; i < 8; i++) {
final cameraPosition =
Vector3(sin(pi * (i / 7)) * 5, 0, cos(pi * (i / 7)) * 5);
await camera.lookAt(cameraPosition);
await testHelper.capture(
viewer, "view_dependent_texture_mapping_$i");
}
});
}, viewportDimensions: (width: 512, height: 512));
});
test('VDTM + Texture Projection', () async {
await testHelper.withViewer((viewer) async {
final cameraPositions = [
Vector3(0, 0, 5),
Vector3(5, 0, 0),
Vector3(0, 0, -5)
];
final camera = await viewer.getMainCamera();
await withProjectionMaterial(viewer, (TextureSampler projectionSampler,
MaterialInstance projectionMaterialInstance,
RenderTarget rt,
int width,
int height) async {
await withCube(viewer, (cube, ubershader, resetMaterial) async {
var pixelBuffers = <Float32List>[];
for (int i = 0; i < cameraPositions.length; i++) {
await camera.lookAt(cameraPositions[i]);
await testHelper.capture(viewer, "vdtm_$i", renderTarget: rt);
await cube.setMaterialInstanceAt(projectionMaterialInstance);
var projectionOutput = await testHelper
.capture(viewer, "vdtm_unwrapped_$i", renderTarget: rt);
var floatPixelBuffer = Float32List.fromList(
projectionOutput.map((p) => p.toDouble() / 255.0).toList());
pixelBuffers.add(floatPixelBuffer);
final projectedImage = await viewer.createImage(width, height, 4);
final data = await projectedImage.getData();
data.setRange(0, data.length, floatPixelBuffer);
final projectedTexture = await viewer.createTexture(width, height,
textureFormat: TextureFormat.RGBA32F);
await projectedTexture.setLinearImage(
projectedImage, PixelDataFormat.RGBA, PixelDataType.FLOAT);
await ubershader.setParameterTexture(
"baseColorMap", projectedTexture, projectionSampler);
await cube.setMaterialInstanceAt(ubershader);
await testHelper.capture(viewer, "vdtm_projected_$i",
renderTarget: rt);
await resetMaterial();
}
await usingVDTM(viewer, cameraPositions, width, height, 4,
(vdtmTexture, vdtmMaterial) async {
await cube.setMaterialInstanceAt(vdtmMaterial);
for (int i = 0; i < cameraPositions.length; i++) {
await vdtmTexture.setImage3D(
0,
0,
0,
i,
width,
height,
4,
1,
pixelBuffers[i]
.buffer
.asUint8List(pixelBuffers[i].offsetInBytes),
PixelDataFormat.RGBA,
PixelDataType.FLOAT);
}
for (int i = 0; i < 8; i++) {
await camera.lookAt(
Vector3(sin(pi * (i / 7)) * 5, 0, cos(pi * (i / 7)) * 5));
await testHelper.capture(viewer, "vdtm_reprojected_$i",
renderTarget: rt);
}
});
});
});
}, viewportDimensions: (width: 512, height: 512));
});
});
}