Generate tangent frames: the source
Open the live demo · Read the guide · View on GitHub
Regions the guide quotes: undressed (line 23), compare (line 30), bump (line 35), check (line 72).
1/// One torus left with a flat, constant tangent and one with tangents
2/// generated from its UVs, both lit by the same tilted normal map.
3library;
5import 'package:flutter3d/flutter3d.dart';
6import 'package:flutter3d_showcase/src/demo/demo.dart';
7import 'package:vector_math/vector_math.dart';
9final class TangentsDemo extends ShowcaseDemo {
10 late final MeshData _flatMesh;
11 late final MeshData _generatedMesh;
14 void configureView(DemoContext context) {
15 context.orbit
16 ..distance = 6.5
17 ..pitch = 0.2
18 ..yaw = 0.4;
19 }
22 Scene build(DemoContext context) {
23 final MeshData undressed = const TorusShape(
24 radius: 0.85,
25 tubeRadius: 0.34,
26 segments: 40,
27 tubeSegments: 20,
28 ).build(layout: VertexLayout.positionNormalTexcoord);
30 _flatMesh = undressed.convertedTo(VertexLayout.standard);
31 _generatedMesh = undressed.withGeneratedTangents(
32 target: VertexLayout.standard,
33 );
35 final TextureHandle tilt = SolidColorTexture(
36 Vector4(0.78, 0.5, 0.86, 1.0),
37 ).upload(context.device);
38 Material bumpMaterial(String name) => Material(
39 name: name,
40 baseColor: Vector4(0.6, 0.64, 0.7, 1.0),
41 metallic: 0.85,
42 roughness: 0.2,
43 normal: tilt,
44 );
46 final Scene scene = Scene()
47 ..ambientColor = Vector3(0.4, 0.48, 0.64)
48 ..ambientIntensity = 0.14
49 ..add(
50 MeshNode(
51 DeviceMesh.upload(context.device, _flatMesh),
52 bumpMaterial('flat tangent'),
53 name: 'flat',
54 )..setPosition(-1.3, 1.0, 0.0),
55 )
56 ..add(
57 MeshNode(
58 DeviceMesh.upload(context.device, _generatedMesh),
59 bumpMaterial('generated tangent'),
60 name: 'generated',
61 )..setPosition(1.3, 1.0, 0.0),
62 )
63 ..add(
64 LightNode(name: 'key', intensity: 3.4)
65 ..setLocalForward(Vector3(-0.4, -0.7, -0.5)),
66 );
67 return scene;
68 }
71 void verify(Scene scene, FrameResult frame) {
72 final int stride = VertexLayout.standard.floatsPerVertex;
73 final int tangentOffset = VertexLayout.standard.floatOffsetOf(
74 VertexLayout.tangent.name,
75 );
76 bool tangentVaries(MeshData mesh) {
77 double? firstX;
78 for (var v = 0; v < mesh.vertexCount; v++) {
79 final double x = mesh.vertices[v * stride + tangentOffset];
80 firstX ??= x;
81 if ((x - firstX).abs() > 1e-4) return true;
82 }
83 return false;
84 }
86 if (tangentVaries(_flatMesh) ||
87 !tangentVaries(_generatedMesh) ||
88 frame.drawCalls < 2) {
89 throw StateError('the generated tangents did not follow the surface');
90 }
91 }
92}