split stencil/depth tests from unlit material tests

This commit is contained in:
Nick Fisher
2025-06-09 18:15:17 +08:00
parent 073976b40e
commit 78e14574ef
3 changed files with 284 additions and 108 deletions

View File

@@ -0,0 +1,85 @@
import 'dart:io';
import 'dart:math';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:test/test.dart';
import 'helpers.dart';
Future<
({
ThermionAsset blueCube,
MaterialInstance blueMaterialInstance,
ThermionAsset greenCube,
MaterialInstance greenMaterialInstance
})> setup(ThermionViewer viewer) async {
var blueMaterialInstance =
await FilamentApp.instance!.createUnlitMaterialInstance();
final blueCube = await viewer.createGeometry(GeometryHelper.cube(),
materialInstances: [blueMaterialInstance]);
await blueMaterialInstance.setParameterFloat4(
"baseColorFactor", 0.0, 0.0, 1.0, 1.0);
// Position blue cube slightly behind/below/right
await blueCube.setTransform(Matrix4.translation(Vector3(1.0, -1.0, -1.0)));
var greenMaterialInstance =
await FilamentApp.instance!.createUnlitMaterialInstance();
final greenCube = await viewer.createGeometry(GeometryHelper.cube(),
materialInstances: [greenMaterialInstance]);
await greenMaterialInstance.setParameterFloat4(
"baseColorFactor", 0.0, 1.0, 0.0, 1.0);
return (
blueCube: blueCube,
blueMaterialInstance: blueMaterialInstance,
greenCube: greenCube,
greenMaterialInstance: greenMaterialInstance
);
}
void main() async {
final testHelper = TestHelper("material");
await testHelper.setup();
test('disable depth write', () async {
await testHelper.withViewer((viewer) async {
final (
:blueCube,
:blueMaterialInstance,
:greenCube,
:greenMaterialInstance
) = await setup(viewer);
// With depth write enabled on both materials, green cube renders behind the blue cube
await testHelper.capture(
viewer.view, "material_instance_depth_write_enabled");
// Disable depth write on green cube
// Blue cube will always appear in front
await greenMaterialInstance.setDepthWriteEnabled(false);
await testHelper.capture(
viewer.view, "material_instance_depth_write_disabled");
// Set priority for greenCube to render last, making it appear in front
await viewer.setPriority(greenCube.entity, 7);
await testHelper.capture(
viewer.view, "material_instance_depth_write_disabled_with_priority");
});
});
test('set depth func to NEVER', () async {
await testHelper.withViewer((viewer) async {
final (
:blueCube,
:blueMaterialInstance,
:greenCube,
:greenMaterialInstance
) = await setup(viewer);
// Set depth func to NEVER on green cube
await greenMaterialInstance.setDepthFunc(SamplerCompareFunction.N);
// Green cube is not rendered at all
await testHelper.capture(viewer.view, "depth_func_never");
});
});
}

View File

@@ -0,0 +1,175 @@
import 'package:thermion_dart/thermion_dart.dart';
import 'package:test/test.dart';
import 'helpers.dart';
Future<
({
ThermionAsset blueCube,
MaterialInstance blueMaterialInstance,
ThermionAsset greenCube,
MaterialInstance greenMaterialInstance
})> setup(ThermionViewer viewer) async {
var blueMaterialInstance =
await FilamentApp.instance!.createUnlitMaterialInstance();
final blueCube = await viewer.createGeometry(GeometryHelper.cube(),
materialInstances: [blueMaterialInstance]);
await blueMaterialInstance.setParameterFloat4(
"baseColorFactor", 0.0, 0.0, 1.0, 1.0);
// Position blue cube slightly behind/below/right
await blueCube.setTransform(Matrix4.translation(Vector3(1.0, -1.0, -1.0)));
var greenMaterialInstance =
await FilamentApp.instance!.createUnlitMaterialInstance();
final greenCube = await viewer.createGeometry(GeometryHelper.cube(),
materialInstances: [greenMaterialInstance]);
await greenMaterialInstance.setParameterFloat4(
"baseColorFactor", 0.0, 1.0, 0.0, 1.0);
return (
blueCube: blueCube,
blueMaterialInstance: blueMaterialInstance,
greenCube: greenCube,
greenMaterialInstance: greenMaterialInstance
);
}
void main() async {
final testHelper = TestHelper("stencil");
await testHelper.setup();
test('enable stencil write', () async {
await testHelper.withViewer((viewer) async {
final (
:blueCube,
:blueMaterialInstance,
:greenCube,
:greenMaterialInstance
) = await setup(viewer);
// force depth to always pass so we're just comparing stencil test
await greenMaterialInstance.setDepthFunc(SamplerCompareFunction.A);
await blueMaterialInstance.setDepthFunc(SamplerCompareFunction.A);
await testHelper.capture(
viewer.view, "material_instance_depth_pass_stencil_disabled");
assert(await greenMaterialInstance.isStencilWriteEnabled() == false);
assert(await blueMaterialInstance.isStencilWriteEnabled() == false);
await greenMaterialInstance.setStencilWriteEnabled(true);
await blueMaterialInstance.setStencilWriteEnabled(true);
assert(await greenMaterialInstance.isStencilWriteEnabled() == true);
assert(await blueMaterialInstance.isStencilWriteEnabled() == true);
await viewer.view.setStencilBufferEnabled(true);
assert(await viewer.view.isStencilBufferEnabled(), true);
// just a sanity check, output should be the same as above
await testHelper.capture(
viewer.view, "material_instance_depth_pass_stencil_enabled");
}, postProcessing: true, bg: null);
});
test('set stencil compare function to never/always/lt/gt)', () async {
await testHelper.withViewer((viewer) async {
final (
:blueCube,
:blueMaterialInstance,
:greenCube,
:greenMaterialInstance
) = await setup(viewer);
await viewer.view.setStencilBufferEnabled(true);
// ensure the blue cube renders before the green cube
await viewer.setPriority(greenCube.entity, 7);
await viewer.setPriority(blueCube.entity, 0);
for (final mi in [greenMaterialInstance, blueMaterialInstance]) {
await mi.setStencilWriteEnabled(true);
await mi.setDepthCullingEnabled(false);
}
// set stencil compare function to NEVER
for (final mi in [greenMaterialInstance, blueMaterialInstance]) {
await mi.setStencilCompareFunction(
SamplerCompareFunction.N, StencilFace.FRONT_AND_BACK);
}
// should be totally empty
await testHelper.capture(viewer.view, "stencil_never");
// set stencil compare function to ALWAYS
for (final mi in [greenMaterialInstance, blueMaterialInstance]) {
await mi.setStencilCompareFunction(
SamplerCompareFunction.A, StencilFace.FRONT_AND_BACK);
}
// should show green cube in front of blue cube
await testHelper.capture(viewer.view, "stencil_always");
// set the blue cube to always pass the stencil test
await blueMaterialInstance.setStencilCompareFunction(
SamplerCompareFunction.A, StencilFace.FRONT_AND_BACK);
// when blue cube passes depth + stencil, replace the default stencil value (0) with 1
await blueMaterialInstance.setStencilOpDepthStencilPass(StencilOperation.REPLACE);
await blueMaterialInstance.setStencilReferenceValue(1);
// set the green cube to only pass the stencil test where stencil value is
// not equal to 0
await greenMaterialInstance.setStencilCompareFunction(
SamplerCompareFunction.NE, StencilFace.FRONT_AND_BACK);
await greenMaterialInstance.setStencilReferenceValue(0);
// green cube will only be rendered where it overlaps with blue cube
await testHelper.capture(viewer.view, "stencil_ne");
// set the green cube to only pass the stencil test where stencil value is
// equal to 0
await greenMaterialInstance.setStencilCompareFunction(
SamplerCompareFunction.E, StencilFace.FRONT_AND_BACK);
// green cube renders where it does not overlap with blue cube (same as if
// we had disabled depth writes and rendered the green cube, then the blue
// cube)
await testHelper.capture(viewer.view, "stencil_eq");
},
bg: null,
postProcessing: true,
createStencilBuffer: true,
createRenderTarget: false);
});
// test('fail stencil not equal', () async {
// await testHelper.withViewer((viewer) async {
// final (
// :blueCube,
// :blueMaterialInstance,
// :greenCube,
// :greenMaterialInstance
// ) = await setup(viewer);
// // this ensures the blue cube is rendered before the green cube
// await viewer.setPriority(blueCube.entity, 0);
// await viewer.setPriority(greenCube.entity, 1);
// await blueMaterialInstance.setStencilWriteEnabled(true);
// await blueMaterialInstance.setStencilReferenceValue(1);
// await blueMaterialInstance
// .setStencilCompareFunction(SamplerCompareFunction.A);
// await blueMaterialInstance
// .setStencilOpDepthStencilPass(StencilOperation.REPLACE);
// await greenMaterialInstance.setStencilReferenceValue(1);
// await greenMaterialInstance
// .setStencilCompareFunction(SamplerCompareFunction.E);
// // green cube is only rendered where it intersects with the blue cube
// await testHelper.capture(viewer.view, "fail_stencil_ne");
// }, postProcessing: true);
// });
}

View File

@@ -18,8 +18,8 @@ Future<
await blueMaterialInstance.setParameterFloat4( await blueMaterialInstance.setParameterFloat4(
"baseColorFactor", 0.0, 0.0, 1.0, 1.0); "baseColorFactor", 0.0, 0.0, 1.0, 1.0);
// Position blue cube slightly behind and to the right // Position blue cube slightly behind/below/right
await blueCube.setTransform(Matrix4.translation(Vector3(1.0, 0.0, -1.0))); await blueCube.setTransform(Matrix4.translation(Vector3(1.0, -1.0, -1.0)));
var greenMaterialInstance = var greenMaterialInstance =
await FilamentApp.instance!.createUnlitMaterialInstance(); await FilamentApp.instance!.createUnlitMaterialInstance();
@@ -36,7 +36,6 @@ Future<
); );
} }
void main() async { void main() async {
final testHelper = TestHelper("material"); final testHelper = TestHelper("material");
@@ -213,114 +212,31 @@ void main() async {
await viewer.dispose(); await viewer.dispose();
}); });
test('disable depth write', () async { test('disable depth write', () async {
await testHelper.withViewer((viewer) async { await testHelper.withViewer((viewer) async {
final ( final (
:blueCube, :blueCube,
:blueMaterialInstance, :blueMaterialInstance,
:greenCube, :greenCube,
:greenMaterialInstance :greenMaterialInstance
) = await setup(viewer); ) = await setup(viewer);
// With depth write enabled on both materials, green cube renders behind the blue cube // With depth write enabled on both materials, green cube renders behind the blue cube
await testHelper.capture( await testHelper.capture(
viewer.view, "material_instance_depth_write_enabled"); viewer.view, "material_instance_depth_write_enabled");
// Disable depth write on green cube, blue cube will always appear in front (green cube renders behind everything, including the image material, so not it's not visible at all) // Disable depth write on green cube
await greenMaterialInstance.setDepthWriteEnabled(false); // Blue cube will always appear in front
await testHelper.capture( await greenMaterialInstance.setDepthWriteEnabled(false);
viewer.view, "material_instance_depth_write_disabled"); await testHelper.capture(
viewer.view, "material_instance_depth_write_disabled");
// Set priority for greenCube to render last, making it appear in front // Set priority for greenCube to render last, making it appear in front
await viewer.setPriority(greenCube.entity, 7); await viewer.setPriority(greenCube.entity, 7);
await testHelper.capture(viewer.view, await testHelper.capture(
"material_instance_depth_write_disabled_with_priority"); viewer.view, "material_instance_depth_write_disabled_with_priority");
});
}); });
});
test('enable stencil write', () async {
await testHelper.withViewer((viewer) async {
final (
:blueCube,
:blueMaterialInstance,
:greenCube,
:greenMaterialInstance
) = await setup(viewer);
// force depth to always pass so we're just comparing stencil test
await greenMaterialInstance.setDepthFunc(SamplerCompareFunction.A);
await blueMaterialInstance.setDepthFunc(SamplerCompareFunction.A);
await testHelper.capture(
viewer.view, "material_instance_depth_pass_stencil_disabled");
assert(await greenMaterialInstance.isStencilWriteEnabled() == false);
assert(await blueMaterialInstance.isStencilWriteEnabled() == false);
await greenMaterialInstance.setStencilWriteEnabled(true);
await blueMaterialInstance.setStencilWriteEnabled(true);
assert(await greenMaterialInstance.isStencilWriteEnabled() == true);
assert(await blueMaterialInstance.isStencilWriteEnabled() == true);
// just a sanity check, no difference from the last
await testHelper.capture(
viewer.view, "material_instance_depth_pass_stencil_enabled");
}, postProcessing: true, bg: null);
});
test('stencil always fail', () async {
await testHelper.withViewer((viewer) async {
final (
:blueCube,
:blueMaterialInstance,
:greenCube,
:greenMaterialInstance
) = await setup(viewer);
// force depth to always pass so we're just comparing stencil test
await greenMaterialInstance.setDepthFunc(SamplerCompareFunction.A);
await blueMaterialInstance.setDepthFunc(SamplerCompareFunction.A);
await greenMaterialInstance.setStencilWriteEnabled(true);
assert(await greenMaterialInstance.isStencilWriteEnabled() == true);
await greenMaterialInstance
.setStencilCompareFunction(SamplerCompareFunction.N);
// green cube isn't rendered
await testHelper.capture(
viewer.view, "material_instance_stencil_always_fail");
}, postProcessing: true, bg: null);
});
test('fail stencil not equal', () async {
await testHelper.withViewer((viewer) async {
final (
:blueCube,
:blueMaterialInstance,
:greenCube,
:greenMaterialInstance
) = await setup(viewer);
// this ensures the blue cube is rendered before the green cube
await viewer.setPriority(blueCube.entity, 0);
await viewer.setPriority(greenCube.entity, 1);
await blueMaterialInstance.setStencilWriteEnabled(true);
await blueMaterialInstance.setStencilReferenceValue(1);
await blueMaterialInstance
.setStencilCompareFunction(SamplerCompareFunction.A);
await blueMaterialInstance
.setStencilOpDepthStencilPass(StencilOperation.REPLACE);
await greenMaterialInstance.setStencilReferenceValue(1);
await greenMaterialInstance
.setStencilCompareFunction(SamplerCompareFunction.E);
// green cube is only rendered where it intersects with the blue cube
await testHelper.capture(viewer.view, "fail_stencil_ne");
}, postProcessing: true);
});
} }