Skip to content

Commit 004f650

Browse files
committed
Implement cross-section vertex shader
Supports UVW texture coords but not pre-computed normals due to limits on mesh data. Also supports sampling from Texture3Ds.
1 parent 9249b71 commit 004f650

File tree

4 files changed

+154
-24
lines changed

4 files changed

+154
-24
lines changed

model/tetra/tetra_material_4d.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,23 @@ void TetraMaterial4D::set_albedo_source(const TetraColorSource p_albedo_source)
124124
notify_property_list_changed();
125125
}
126126

127+
Ref<Texture3D> TetraMaterial4D::get_texture() const {
128+
return _texture;
129+
}
130+
131+
void TetraMaterial4D::set_texture(const Ref<Texture3D> &p_texture) {
132+
_texture = p_texture;
133+
update_cross_section_material();
134+
}
135+
136+
void TetraMaterial4D::update_cross_section_material() {
137+
if (_cross_section_material.is_null()) {
138+
return;
139+
}
140+
Material4D::update_cross_section_material();
141+
_cross_section_material->set_shader_parameter("albedo_texture", _texture);
142+
}
143+
127144
void TetraMaterial4D::_get_property_list(List<PropertyInfo> *p_list) const {
128145
for (List<PropertyInfo>::Element *E = p_list->front(); E; E = E->next()) {
129146
PropertyInfo &prop = E->get();
@@ -142,11 +159,14 @@ TetraMaterial4D::TetraMaterial4D() {
142159
void TetraMaterial4D::_bind_methods() {
143160
ClassDB::bind_method(D_METHOD("get_albedo_source"), &TetraMaterial4D::get_albedo_source);
144161
ClassDB::bind_method(D_METHOD("set_albedo_source", "albedo_source"), &TetraMaterial4D::set_albedo_source);
162+
ClassDB::bind_method(D_METHOD("get_texture"), &TetraMaterial4D::get_texture);
163+
ClassDB::bind_method(D_METHOD("set_texture", "texture"), &TetraMaterial4D::set_texture);
145164

146165
//ADD_GROUP("Albedo", "albedo_");
147166
ADD_PROPERTY(PropertyInfo(Variant::INT, "albedo_source", PROPERTY_HINT_ENUM, "Single Color,Per Vertex Only,Per Cell Only,Cell UVW Only,Texture4D Only,Per Vertex and Single Color,Per Cell and Single Color,Cell UVW and Single Color,Texture4D and Single Color"), "set_albedo_source", "get_albedo_source");
148167
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "albedo_color"), "set_albedo_color", "get_albedo_color");
149168
ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "albedo_color_array"), "set_albedo_color_array", "get_albedo_color_array");
169+
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture3D"), "set_texture", "get_texture");
150170

151171
BIND_ENUM_CONSTANT(TETRA_COLOR_SOURCE_SINGLE_COLOR);
152172
BIND_ENUM_CONSTANT(TETRA_COLOR_SOURCE_PER_VERT_ONLY);

model/tetra/tetra_material_4d.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
#include "../material_4d.h"
44

5+
#if GODOT_MODULE
6+
#include "scene/resources/texture.h"
7+
#elif GDEXTENSION
8+
#include <godot_cpp/classes/texture3d.hpp>
9+
#endif
10+
511
class TetraMaterial4D : public Material4D {
612
GDCLASS(TetraMaterial4D, Material4D);
713

@@ -20,20 +26,26 @@ class TetraMaterial4D : public Material4D {
2026

2127
private:
2228
TetraColorSource _albedo_source = TETRA_COLOR_SOURCE_SINGLE_COLOR;
29+
Ref<Texture3D> _texture;
2330

2431
static Material4D::ColorSourceFlags _tetra_source_to_flags(const TetraColorSource p_tetra_source);
2532

2633
protected:
2734
static void _bind_methods();
2835
void _get_property_list(List<PropertyInfo> *p_list) const;
2936

37+
void update_cross_section_material() override;
38+
3039
public:
3140
virtual Color get_albedo_color_of_edge(const int64_t p_edge_index, const Ref<Mesh4D> &p_for_mesh) override;
3241
virtual void merge_with(const Ref<Material4D> &p_material, const int p_first_item_count, const int p_second_item_count) override;
3342

3443
TetraColorSource get_albedo_source() const;
3544
void set_albedo_source(const TetraColorSource p_albedo_source);
3645

46+
Ref<Texture3D> get_texture() const;
47+
void set_texture(const Ref<Texture3D> &p_texture);
48+
3749
TetraMaterial4D();
3850
};
3951

model/tetra/tetra_mesh_4d.cpp

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,9 @@ void TetraMesh4D::update_cross_section_mesh() {
176176
// - Vertex position (3)
177177
// - Custom 0-3 (4 * 4)
178178
// - UV1 and UV2 (2 * 2)
179-
// - Normal (3)
180-
// - Tangent (3)
181-
// - Color (4)
179+
// - Normal (3), gets normalized so ~2 slots for arbitrary floats
180+
// - Tangent (3), also gets normalized
181+
// - Color (4), gets clamped to [0, 1]
182182
// Slots that don't work:
183183
// - Bone weights: get truncated, sorted, and normalized automatically
184184
// - Binormal: available in shader, but computed in SurfaceTool from normal/tangent
@@ -195,14 +195,17 @@ void TetraMesh4D::update_cross_section_mesh() {
195195
surface_tool->set_custom(2, vec4_to_color(vertices[cell_indices[i + 2]]));
196196
surface_tool->set_custom(3, vec4_to_color(vertices[cell_indices[i + 3]]));
197197

198-
// UVW texture coords, need 4*3 float slots. Using UV, UV2, Normal, Color, and one vertex.y.
199-
Vector3 uvw1 = cell_uvws[cell_indices[i]];
200-
Vector3 uvw2 = cell_uvws[cell_indices[i + 1]];
201-
Vector3 uvw3 = cell_uvws[cell_indices[i + 2]];
202-
Vector3 uvw4 = cell_uvws[cell_indices[i + 3]];
198+
// UVW texture coords, need 4*3 float slots. Using UV, UV2, Normal, Color, vertex.y, and vertex.z.
199+
// Normal gets normalized somewhere in the pipeline, so last coord of 1.0 will get set to whatever we need to divide by to get
200+
// to get the original coords.
201+
Vector3 uvw1 = cell_uvws[i];
202+
Vector3 uvw2 = cell_uvws[i+1];
203+
Vector3 uvw3 = cell_uvws[i+2];
204+
Vector3 uvw4 = cell_uvws[i+3];
203205
surface_tool->set_uv(Vector2(uvw1.x, uvw1.y));
204206
surface_tool->set_uv2(Vector2(uvw2.x, uvw2.y));
205-
surface_tool->set_normal(uvw3);
207+
surface_tool->set_normal(Vector3(uvw3.x, uvw3.y, 1.0));
208+
// This one gets clamped to [0,1], which should be fine for texture coords.
206209
surface_tool->set_color(Color(uvw4.x, uvw4.y, uvw4.z, uvw1.z));
207210

208211
// Not enough slots left for normals. Also interpolating the 4D normals gives weird results, needs more experimentation.
@@ -211,13 +214,13 @@ void TetraMesh4D::update_cross_section_mesh() {
211214
//// Vertices:
212215

213216
// Not storing actual position data in the vertex positions, x is an index, y is UVW data, z is unused.
214-
surface_tool->add_vertex(Vector3(0.0, uvw2.z, 0.0));
215-
surface_tool->add_vertex(Vector3(1.0, uvw2.z, 0.0));
216-
surface_tool->add_vertex(Vector3(2.0, uvw2.z, 0.0));
217+
surface_tool->add_vertex(Vector3(0.0, uvw2.z, uvw3.z));
218+
surface_tool->add_vertex(Vector3(1.0, uvw2.z, uvw3.z));
219+
surface_tool->add_vertex(Vector3(2.0, uvw2.z, uvw3.z));
217220

218-
surface_tool->add_vertex(Vector3(3.0, uvw2.z, 0.0));
219-
surface_tool->add_vertex(Vector3(4.0, uvw2.z, 0.0));
220-
surface_tool->add_vertex(Vector3(5.0, uvw2.z, 0.0));
221+
surface_tool->add_vertex(Vector3(3.0, uvw2.z, uvw3.z));
222+
surface_tool->add_vertex(Vector3(4.0, uvw2.z, uvw3.z));
223+
surface_tool->add_vertex(Vector3(5.0, uvw2.z, uvw3.z));
221224
}
222225
surface_tool->commit(_cross_section_mesh);
223226

Lines changed: 104 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
shader_type spatial;
22
render_mode skip_vertex_transform;
3-
render_mode unshaded;
3+
// render_mode skip_vertex_transform, unshaded;
44

55
// World space
66
// Not allowed to pass matrices through instance uniforms, so have to unpack into vectors.
@@ -10,17 +10,112 @@ instance uniform vec4 modelview_basis_y;
1010
instance uniform vec4 modelview_basis_z;
1111
instance uniform vec4 modelview_basis_w;
1212

13-
uniform vec4 albedo: source_color;
13+
uniform vec4 albedo : source_color;
14+
uniform sampler3D albedo_texture: source_color;
15+
16+
varying vec3 uvw;
17+
18+
// Maps from an arbitrary edge index to the indices of the vertices in a tetrahedron [a,b,c,d].
19+
const int TETRAHEDRON_EDGE_TO_VERTEX_MAP[] = {
20+
0, 1,
21+
0, 2,
22+
0, 3,
23+
1, 2,
24+
1, 3,
25+
2, 3
26+
};
27+
// Nonsense abstract lookup table for different cases of vertices above and below the cross-section plane. Derived by
28+
// sketching out 3D tetrahedra and hoping it also worked for 4D (it does).
29+
// 16 cases for number of vertices above and below the cross-section plane, each case leads to at most two triangles for 6 vertices.
30+
// Each triangle in the cross-section is made up of vertices that are interpolated along the edges of the tetrahedron,
31+
// falling where the edge intersetcs the cross-section plane. So this table is 16x6, 16 cases, 6 possible verts,
32+
// each value maps to an edge in the TETRAHEDRON_EDGE_TO_VERTEX_MAP. -1 indicates the vertex is unused for that case.
33+
// General pattern: one vertex above and three below is one triangle on the edges between the one vertex above and the rest,
34+
// one vertex below and three above is same but the winding order is flipped, two above and two below is two triangles in some awful pattern.
35+
// Flipping the bit pattern (i.e. flipping the tetrahedron) reverses the winding order of both triangles.
36+
const int CROSS_SECTION_LOOKUP[] = {
37+
-1, -1, -1, -1, -1, -1, // 0000
38+
0, 1, 2, -1, -1, -1, // 0001
39+
0, 4, 3, -1, -1, -1, // 0010
40+
2, 4, 1, 3, 1, 4, // 0011
41+
1, 3, 5, -1, -1, -1, // 0100
42+
0, 3, 2, 5, 2, 3, // 0101
43+
1, 0, 5, 4, 5, 0, // 0110
44+
2, 4, 5, -1, -1, -1, // 0111
45+
2, 5, 4, -1, -1, -1, // 1000
46+
1, 5, 0, 4, 0, 5, // 1001
47+
0, 2, 3, 3, 2, 5, // 1010
48+
1, 5, 3, -1, -1, -1, // 1011
49+
2, 1, 4, 4, 1, 3, // 1100
50+
0, 3, 4, -1, -1, -1, // 1101
51+
0, 2, 1, -1, -1, -1, // 1110
52+
-1, -1, -1, -1, -1, -1 // 1111
53+
};
54+
55+
int get_face_lookup_index(float w1, float w2, float w3, float w4, int vertex_id) {
56+
// Bitwise ops limit compatibility so summing instead.
57+
int negative1 = w1 < 0.0 ? 1 : 0;
58+
int negative2 = w2 < 0.0 ? 2 : 0;
59+
int negative3 = w3 < 0.0 ? 4 : 0;
60+
int negative4 = w4 < 0.0 ? 8 : 0;
61+
int lookup_index = negative1 + negative2 + negative3 + negative4;
62+
63+
// First index in the group of three that the indicated vertex belongs to.
64+
return (lookup_index * 6) + vertex_id - (vertex_id % 3);
65+
}
66+
67+
vec3 slice_edge(vec4 v1, vec4 v2) {
68+
float mix_weight = v1.w / (v1.w - v2.w);
69+
return mix(v1, v2, mix_weight).xyz;
70+
}
1471

1572
void vertex() {
16-
mat4 modelview_basis = mat4(modelview_basis_x, modelview_basis_y, modelview_basis_z, modelview_basis_w);
17-
// Make an arbitrary triangle from the cell so there's something on the screen.
18-
vec4 position = (VERTEX.x == 1.0 ? CUSTOM0 : (VERTEX.y == 1.0 ? CUSTOM1 : CUSTOM2));
19-
position = (modelview_basis * position) + modelview_origin;
20-
POSITION = PROJECTION_MATRIX * vec4(position.xyz, 1.0);
21-
// TODO actual cross-sectioning
73+
mat4 modelview_basis = mat4(modelview_basis_x, modelview_basis_y, modelview_basis_z, modelview_basis_w);
74+
75+
vec4 verts[] = { CUSTOM0, CUSTOM1, CUSTOM2, CUSTOM3 };
76+
verts[0] = (modelview_basis * verts[0]) + modelview_origin;
77+
verts[1] = (modelview_basis * verts[1]) + modelview_origin;
78+
verts[2] = (modelview_basis * verts[2]) + modelview_origin;
79+
verts[3] = (modelview_basis * verts[3]) + modelview_origin;
80+
81+
vec3 uvws[] = { vec3(UV, COLOR.a), vec3(UV2, VERTEX.y), vec3(NORMAL.xy / NORMAL.z, VERTEX.z), COLOR.rgb };
82+
83+
int vertex_id = int(VERTEX.x);
84+
int face = get_face_lookup_index(verts[0].w, verts[1].w, verts[2].w, verts[3].w, vertex_id);
85+
if (CROSS_SECTION_LOOKUP[face] == -1) {
86+
// This vertex is unused, cull
87+
POSITION = vec4(0.0, 0.0, CLIP_SPACE_FAR, 1.0);
88+
} else {
89+
int position_edge = CROSS_SECTION_LOOKUP[face + (vertex_id % 3)];
90+
int edge_vert1 = TETRAHEDRON_EDGE_TO_VERTEX_MAP[position_edge * 2];
91+
int edge_vert2 = TETRAHEDRON_EDGE_TO_VERTEX_MAP[(position_edge * 2) + 1];
92+
vec4 position1 = verts[edge_vert1];
93+
vec4 position2 = verts[edge_vert2];
94+
float mix_weight = position1.w / (position1.w - position2.w);
95+
vec4 position = mix(position1, position2, mix_weight);
96+
// Vertex is view space and used for lighting, position is clip space and used for rasterizing.
97+
VERTEX = position.xyz;
98+
POSITION = PROJECTION_MATRIX * vec4(position.xyz, 1.0);
99+
100+
uvw = mix(uvws[edge_vert1], uvws[edge_vert2], mix_weight);
101+
102+
// Compute flat normals.
103+
int face_v1_edge = CROSS_SECTION_LOOKUP[face];
104+
int face_v2_edge = CROSS_SECTION_LOOKUP[face + 1];
105+
int face_v3_edge = CROSS_SECTION_LOOKUP[face + 2];
106+
vec3 face_v1 = slice_edge(verts[TETRAHEDRON_EDGE_TO_VERTEX_MAP[face_v1_edge * 2]], verts[TETRAHEDRON_EDGE_TO_VERTEX_MAP[face_v1_edge * 2 + 1]]);
107+
vec3 face_v2 = slice_edge(verts[TETRAHEDRON_EDGE_TO_VERTEX_MAP[face_v2_edge * 2]], verts[TETRAHEDRON_EDGE_TO_VERTEX_MAP[face_v2_edge * 2 + 1]]);
108+
vec3 face_v3 = slice_edge(verts[TETRAHEDRON_EDGE_TO_VERTEX_MAP[face_v3_edge * 2]], verts[TETRAHEDRON_EDGE_TO_VERTEX_MAP[face_v3_edge * 2 + 1]]);
109+
vec3 normal = normalize(cross(face_v3 - face_v1, face_v2 - face_v1));
110+
vec3 tangent = normalize(face_v2 - face_v1);
111+
vec3 binormal = normalize(cross(normal, tangent));
112+
NORMAL = normal;
113+
TANGENT = tangent;
114+
BINORMAL = binormal;
115+
}
22116
}
23117

24118
void fragment() {
25-
ALBEDO = albedo.rgb;
119+
ALBEDO = albedo.rgb * texture(albedo_texture, uvw).rgb;
120+
NORMAL = normalize(NORMAL);
26121
}

0 commit comments

Comments
 (0)