Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance checks and user experience around tangent arrays in meshes. #84252

Merged
merged 1 commit into from
Nov 2, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Enhance checks and user experience around tangents.
Ensure `ensure_tangents` option actually creates tangent array. Even if it is just a dummy array.

Allow mesh to generate its own tangents when using compression. This allows users to compress meshes without tangents.

Warn users if they are trying to read from tangents without providing tangents.
clayjohn committed Nov 1, 2023
commit d1043a5f930513aa4b48b014ec7961cad48b0560
5 changes: 5 additions & 0 deletions drivers/gles3/rasterizer_scene_gles3.cpp
Original file line number Diff line number Diff line change
@@ -259,6 +259,11 @@ void RasterizerSceneGLES3::_geometry_instance_add_surface_with_material(Geometry
sdcache->sort.shader_id = p_shader_id;
sdcache->sort.geometry_id = p_mesh.get_local_index();
sdcache->sort.priority = p_material->priority;

GLES3::Mesh::Surface *s = reinterpret_cast<GLES3::Mesh::Surface *>(sdcache->surface);
if (p_material->shader_data->uses_tangent && !(s->format & RS::ARRAY_FORMAT_TANGENT)) {
WARN_PRINT_ED("Attempting to use a shader that requires tangents with a mesh that doesn't contain tangents. Ensure that meshes are imported with the 'ensure_tangents' option. If creating your own meshes, add an `ARRAY_TANGENT` array (when using ArrayMesh) or call `generate_tangents()` (when using SurfaceTool).");
}
}

void RasterizerSceneGLES3::_geometry_instance_add_surface_with_material_chain(GeometryInstanceGLES3 *ginstance, uint32_t p_surface, GLES3::SceneMaterialData *p_material_data, RID p_mat_src, RID p_mesh) {
2 changes: 2 additions & 0 deletions drivers/gles3/storage/material_storage.cpp
Original file line number Diff line number Diff line change
@@ -2924,6 +2924,8 @@ void SceneShaderData::set_code(const String &p_code) {

actions.usage_flag_pointers["TANGENT"] = &uses_tangent;
actions.usage_flag_pointers["BINORMAL"] = &uses_tangent;
actions.usage_flag_pointers["ANISOTROPY"] = &uses_tangent;
actions.usage_flag_pointers["ANISOTROPY_FLOW"] = &uses_tangent;
actions.usage_flag_pointers["COLOR"] = &uses_color;
actions.usage_flag_pointers["UV"] = &uses_uv;
actions.usage_flag_pointers["UV2"] = &uses_uv2;
13 changes: 12 additions & 1 deletion editor/import/editor_import_collada.cpp
Original file line number Diff line number Diff line change
@@ -925,6 +925,9 @@ Error ColladaImport::_create_mesh_surfaces(bool p_optimize, Ref<ImporterMesh> &p
mesh_flags = RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES;
}

// We can't generate tangents without UVs, so create dummy tangents.
bool generate_dummy_tangents = (!binormal_src || !tangent_src) && !uv_src && force_make_tangents;

Ref<SurfaceTool> surftool;
surftool.instantiate();
surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
@@ -934,6 +937,14 @@ Error ColladaImport::_create_mesh_surfaces(bool p_optimize, Ref<ImporterMesh> &p
surftool->set_normal(vertex_array[k].normal);
if (binormal_src && tangent_src) {
surftool->set_tangent(vertex_array[k].tangent);
} else if (generate_dummy_tangents) {
Vector3 tan = Vector3(0.0, 1.0, 0.0).cross(vertex_array[k].normal);
surftool->set_tangent(Plane(tan.x, tan.y, tan.z, 1.0));
}
} else {
// No normals, use a dummy normal since normals will be generated.
if (generate_dummy_tangents) {
surftool->set_tangent(Plane(1.0, 0.0, 0.0, 1.0));
}
}
if (uv_src) {
@@ -985,7 +996,7 @@ Error ColladaImport::_create_mesh_surfaces(bool p_optimize, Ref<ImporterMesh> &p
surftool->generate_tangents();
}

if (!binormal_src || !(tangent_src || generate_tangents) || p_mesh->get_blend_shape_count() != 0 || p_skin_controller) {
if (p_mesh->get_blend_shape_count() != 0 || p_skin_controller) {
// Can't compress if attributes missing or if using vertex weights.
mesh_flags &= ~RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES;
}
14 changes: 11 additions & 3 deletions editor/import/resource_importer_obj.cpp
Original file line number Diff line number Diff line change
@@ -327,6 +327,17 @@ static Error _parse_obj(const String &p_path, List<Ref<Mesh>> &r_meshes, bool p_
}
ERR_FAIL_INDEX_V(norm, normals.size(), ERR_FILE_CORRUPT);
surf_tool->set_normal(normals[norm]);
if (generate_tangents && uvs.is_empty()) {
// We can't generate tangents without UVs, so create dummy tangents.
Vector3 tan = Vector3(0.0, 1.0, 0.0).cross(normals[norm]);
surf_tool->set_tangent(Plane(tan.x, tan.y, tan.z, 1.0));
}
} else {
// No normals, use a dummy normal since normals will be generated.
if (generate_tangents && uvs.is_empty()) {
// We can't generate tangents without UVs, so create dummy tangents.
surf_tool->set_tangent(Plane(1.0, 0.0, 0.0, 1.0));
}
}

if (face[idx].size() >= 2 && !face[idx][1].is_empty()) {
@@ -383,9 +394,6 @@ static Error _parse_obj(const String &p_path, List<Ref<Mesh>> &r_meshes, bool p_

if (generate_tangents && uvs.size()) {
surf_tool->generate_tangents();
} else {
// We need tangents in order to compress vertex data. So disable if tangents aren't generated.
mesh_flags = 0;
}

surf_tool->index();
23 changes: 20 additions & 3 deletions modules/gltf/gltf_document.cpp
Original file line number Diff line number Diff line change
@@ -2797,9 +2797,26 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
array[Mesh::ARRAY_INDEX] = indices;
}

bool generate_tangents = p_state->force_generate_tangents && (primitive == Mesh::PRIMITIVE_TRIANGLES && !a.has("TANGENT") && a.has("TEXCOORD_0") && a.has("NORMAL"));
bool generate_tangents = p_state->force_generate_tangents && (primitive == Mesh::PRIMITIVE_TRIANGLES && !a.has("TANGENT") && a.has("NORMAL"));

if (generate_tangents && !a.has("TEXCOORD_0")) {
// If we don't have UVs we provide a dummy tangent array.
Vector<float> tangents;
tangents.resize(vertex_num * 4);
float *tangentsw = tangents.ptrw();

Vector<Vector3> normals = array[Mesh::ARRAY_NORMAL];
for (int k = 0; k < vertex_num; k++) {
Vector3 tan = Vector3(0.0, 1.0, 0.0).cross(normals[k]);
tangentsw[k * 4 + 0] = tan.x;
tangentsw[k * 4 + 1] = tan.y;
tangentsw[k * 4 + 2] = tan.z;
tangentsw[k * 4 + 3] = 1.0;
}
array[Mesh::ARRAY_TANGENT] = tangents;
}

if (p_state->force_disable_compression || !a.has("POSITION") || !a.has("NORMAL") || !(a.has("TANGENT") || generate_tangents) || p.has("targets") || (a.has("JOINTS_0") || a.has("JOINTS_1"))) {
if (p_state->force_disable_compression || !a.has("POSITION") || !a.has("NORMAL") || p.has("targets") || (a.has("JOINTS_0") || a.has("JOINTS_1"))) {
flags &= ~RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES;
}

@@ -2810,7 +2827,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
mesh_surface_tool->set_skin_weight_count(SurfaceTool::SKIN_8_WEIGHTS);
}
mesh_surface_tool->index();
if (generate_tangents) {
if (generate_tangents && a.has("TEXCOORD_0")) {
//must generate mikktspace tangents.. ergh..
mesh_surface_tool->generate_tangents();
}
Original file line number Diff line number Diff line change
@@ -3707,6 +3707,11 @@ void RenderForwardClustered::_geometry_instance_add_surface_with_material(Geomet
sdcache->sort.priority = p_material->priority;
sdcache->sort.uses_projector = ginstance->using_projectors;
sdcache->sort.uses_softshadow = ginstance->using_softshadows;

uint64_t format = RendererRD::MeshStorage::get_singleton()->mesh_surface_get_format(sdcache->surface);
if (p_material->shader_data->uses_tangent && !(format & RS::ARRAY_FORMAT_TANGENT)) {
WARN_PRINT_ED("Attempting to use a shader that requires tangents with a mesh that doesn't contain tangents. Ensure that meshes are imported with the 'ensure_tangents' option. If creating your own meshes, add an `ARRAY_TANGENT` array (when using ArrayMesh) or call `generate_tangents()` (when using SurfaceTool).");
}
}

void RenderForwardClustered::_geometry_instance_add_surface_with_material_chain(GeometryInstanceForwardClustered *ginstance, uint32_t p_surface, SceneShaderForwardClustered::MaterialData *p_material, RID p_mat_src, RID p_mesh) {
Original file line number Diff line number Diff line change
@@ -65,6 +65,8 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) {
uses_discard = false;
uses_roughness = false;
uses_normal = false;
uses_tangent = false;
bool uses_normal_map = false;
bool wireframe = false;

unshaded = false;
@@ -121,11 +123,16 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) {
actions.usage_flag_pointers["TIME"] = &uses_time;
actions.usage_flag_pointers["ROUGHNESS"] = &uses_roughness;
actions.usage_flag_pointers["NORMAL"] = &uses_normal;
actions.usage_flag_pointers["NORMAL_MAP"] = &uses_normal;
actions.usage_flag_pointers["NORMAL_MAP"] = &uses_normal_map;

actions.usage_flag_pointers["POINT_SIZE"] = &uses_point_size;
actions.usage_flag_pointers["POINT_COORD"] = &uses_point_size;

actions.usage_flag_pointers["TANGENT"] = &uses_tangent;
actions.usage_flag_pointers["BINORMAL"] = &uses_tangent;
actions.usage_flag_pointers["ANISOTROPY"] = &uses_tangent;
actions.usage_flag_pointers["ANISOTROPY_FLOW"] = &uses_tangent;

actions.write_flag_pointers["MODELVIEW_MATRIX"] = &writes_modelview_or_projection;
actions.write_flag_pointers["PROJECTION_MATRIX"] = &writes_modelview_or_projection;
actions.write_flag_pointers["VERTEX"] = &uses_vertex;
@@ -150,6 +157,8 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) {
uses_normal_texture = gen_code.uses_normal_roughness_texture;
uses_vertex_time = gen_code.uses_vertex_time;
uses_fragment_time = gen_code.uses_fragment_time;
uses_normal |= uses_normal_map;
uses_tangent |= uses_normal_map;

#if 0
print_line("**compiling shader:");
Original file line number Diff line number Diff line change
@@ -165,6 +165,7 @@ class SceneShaderForwardClustered {
bool uses_discard = false;
bool uses_roughness = false;
bool uses_normal = false;
bool uses_tangent = false;
bool uses_particle_trails = false;

bool unshaded = false;
Original file line number Diff line number Diff line change
@@ -2508,6 +2508,11 @@ void RenderForwardMobile::_geometry_instance_add_surface_with_material(GeometryI
sdcache->sort.geometry_id = p_mesh.get_local_index();
// sdcache->sort.uses_forward_gi = ginstance->can_sdfgi;
sdcache->sort.priority = p_material->priority;

uint64_t format = RendererRD::MeshStorage::get_singleton()->mesh_surface_get_format(sdcache->surface);
if (p_material->shader_data->uses_tangent && !(format & RS::ARRAY_FORMAT_TANGENT)) {
WARN_PRINT_ED("Attempting to use a shader that requires tangents with a mesh that doesn't contain tangents. Ensure that meshes are imported with the 'ensure_tangents' option. If creating your own meshes, add an `ARRAY_TANGENT` array (when using ArrayMesh) or call `generate_tangents()` (when using SurfaceTool).");
}
}

void RenderForwardMobile::_geometry_instance_add_surface_with_material_chain(GeometryInstanceForwardMobile *ginstance, uint32_t p_surface, SceneShaderForwardMobile::MaterialData *p_material, RID p_mat_src, RID p_mesh) {
Original file line number Diff line number Diff line change
@@ -67,6 +67,8 @@ void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) {
uses_discard = false;
uses_roughness = false;
uses_normal = false;
uses_tangent = false;
bool uses_normal_map = false;
bool wireframe = false;

unshaded = false;
@@ -122,7 +124,12 @@ void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) {
actions.usage_flag_pointers["TIME"] = &uses_time;
actions.usage_flag_pointers["ROUGHNESS"] = &uses_roughness;
actions.usage_flag_pointers["NORMAL"] = &uses_normal;
actions.usage_flag_pointers["NORMAL_MAP"] = &uses_normal;
actions.usage_flag_pointers["NORMAL_MAP"] = &uses_normal_map;

actions.usage_flag_pointers["TANGENT"] = &uses_tangent;
actions.usage_flag_pointers["BINORMAL"] = &uses_tangent;
actions.usage_flag_pointers["ANISOTROPY"] = &uses_tangent;
actions.usage_flag_pointers["ANISOTROPY_FLOW"] = &uses_tangent;

actions.usage_flag_pointers["POINT_SIZE"] = &uses_point_size;
actions.usage_flag_pointers["POINT_COORD"] = &uses_point_size;
@@ -150,6 +157,8 @@ void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) {
uses_screen_texture = gen_code.uses_screen_texture;
uses_depth_texture = gen_code.uses_depth_texture;
uses_normal_texture = gen_code.uses_normal_roughness_texture;
uses_normal |= uses_normal_map;
uses_tangent |= uses_normal_map;

#ifdef DEBUG_ENABLED
if (uses_sss) {
Original file line number Diff line number Diff line change
@@ -119,6 +119,7 @@ class SceneShaderForwardMobile {
bool uses_discard = false;
bool uses_roughness = false;
bool uses_normal = false;
bool uses_tangent = false;
bool uses_particle_trails = false;

bool unshaded = false;
55 changes: 45 additions & 10 deletions servers/rendering_server.cpp
Original file line number Diff line number Diff line change
@@ -460,9 +460,7 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint64_t p_format, uint
}
}

bool using_normals_tangents = (p_format & RS::ARRAY_FORMAT_NORMAL) && (p_format & RS::ARRAY_FORMAT_TANGENT);

if (!using_normals_tangents) {
if (!(p_format & RS::ARRAY_FORMAT_NORMAL)) {
// Early out if we are only setting vertex positions.
for (int i = 0; i < p_vertex_array_len; i++) {
Vector3 pos = (src[i] - r_aabb.position) / r_aabb.size;
@@ -480,13 +478,14 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint64_t p_format, uint

// Validate normal and tangent arrays.
ERR_FAIL_COND_V(p_arrays[RS::ARRAY_NORMAL].get_type() != Variant::PACKED_VECTOR3_ARRAY, ERR_INVALID_PARAMETER);
Variant::Type tangent_type = p_arrays[RS::ARRAY_TANGENT].get_type();
ERR_FAIL_COND_V(tangent_type != Variant::PACKED_FLOAT32_ARRAY && tangent_type != Variant::PACKED_FLOAT64_ARRAY, ERR_INVALID_PARAMETER);

Vector<Vector3> normal_array = p_arrays[RS::ARRAY_NORMAL];
ERR_FAIL_COND_V(normal_array.size() != p_vertex_array_len, ERR_INVALID_PARAMETER);
const Vector3 *normal_src = normal_array.ptr();

Variant::Type tangent_type = p_arrays[RS::ARRAY_TANGENT].get_type();
ERR_FAIL_COND_V(tangent_type != Variant::PACKED_FLOAT32_ARRAY && tangent_type != Variant::PACKED_FLOAT64_ARRAY && tangent_type != Variant::NIL, ERR_INVALID_PARAMETER);

// We need a different version if using double precision tangents.
if (tangent_type == Variant::PACKED_FLOAT32_ARRAY) {
Vector<float> tangent_array = p_arrays[RS::ARRAY_TANGENT];
@@ -524,7 +523,7 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint64_t p_format, uint
memcpy(&vw[p_offsets[ai] + i * p_vertex_stride], vector, sizeof(uint16_t) * 4);
}
}
} else { // PACKED_FLOAT64_ARRAY
} else if (tangent_type == Variant::PACKED_FLOAT64_ARRAY) {
Vector<double> tangent_array = p_arrays[RS::ARRAY_TANGENT];
ERR_FAIL_COND_V(tangent_array.size() != p_vertex_array_len * 4, ERR_INVALID_PARAMETER);
const double *tangent_src = tangent_array.ptr();
@@ -547,6 +546,40 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint64_t p_format, uint
memcpy(&vw[p_offsets[RS::ARRAY_NORMAL] + i * p_normal_stride], vector, 4);
}

// Store vertex position + angle.
{
Vector3 pos = (src[i] - r_aabb.position) / r_aabb.size;
uint16_t vector[4] = {
(uint16_t)CLAMP(pos.x * 65535, 0, 65535),
(uint16_t)CLAMP(pos.y * 65535, 0, 65535),
(uint16_t)CLAMP(pos.z * 65535, 0, 65535),
(uint16_t)CLAMP(angle * 65535, 0, 65535)
};

memcpy(&vw[p_offsets[ai] + i * p_vertex_stride], vector, sizeof(uint16_t) * 4);
}
}
} else { // No tangent array.
// Set data for vertex, normal, and tangent.
for (int i = 0; i < p_vertex_array_len; i++) {
float angle;
Vector3 axis;
// Generate an arbitrary vector that is tangential to normal.
Vector3 tan = Vector3(0.0, 1.0, 0.0).cross(normal_src[i].normalized());
Vector4 tangent = Vector4(tan.x, tan.y, tan.z, 1.0);
_get_axis_angle(normal_src[i], tangent, angle, axis);

// Store axis.
{
Vector2 res = axis.octahedron_encode();
uint16_t vector[2] = {
(uint16_t)CLAMP(res.x * 65535, 0, 65535),
(uint16_t)CLAMP(res.y * 65535, 0, 65535),
};

memcpy(&vw[p_offsets[RS::ARRAY_NORMAL] + i * p_normal_stride], vector, 4);
}

// Store vertex position + angle.
{
Vector3 pos = (src[i] - r_aabb.position) / r_aabb.size;
@@ -1207,7 +1240,11 @@ Error RenderingServer::mesh_create_surface_data_from_arrays(SurfaceData *r_surfa
// If using normals or tangents, then we need all three.
ERR_FAIL_COND_V_MSG(!(format & RS::ARRAY_FORMAT_VERTEX), ERR_INVALID_PARAMETER, "Can't use compression flag 'ARRAY_FLAG_COMPRESS_ATTRIBUTES' while using normals or tangents without vertex array.");
ERR_FAIL_COND_V_MSG(!(format & RS::ARRAY_FORMAT_NORMAL), ERR_INVALID_PARAMETER, "Can't use compression flag 'ARRAY_FLAG_COMPRESS_ATTRIBUTES' while using tangents without normal array.");
ERR_FAIL_COND_V_MSG(!(format & RS::ARRAY_FORMAT_TANGENT), ERR_INVALID_PARAMETER, "Can't use compression flag 'ARRAY_FLAG_COMPRESS_ATTRIBUTES' while using normals without tangent array.");
}

if ((format & RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES) && !(format & RS::ARRAY_FORMAT_TANGENT)) {
// If no tangent array provided, we will generate one.
format |= RS::ARRAY_FORMAT_TANGENT;
}

int vertex_array_size = (vertex_element_size + normal_element_size) * array_len;
@@ -1368,10 +1405,8 @@ Array RenderingServer::_get_array_from_surface(uint64_t p_format, Vector<uint8_t
Vector3 *w = arr_3d.ptrw();

if (p_format & ARRAY_FLAG_COMPRESS_ATTRIBUTES) {
bool using_normals_tangents = (p_format & RS::ARRAY_FORMAT_NORMAL) && (p_format & RS::ARRAY_FORMAT_TANGENT);

// We only have vertices to read, so just read them and skip everything else.
if (!using_normals_tangents) {
if (!(p_format & RS::ARRAY_FORMAT_NORMAL)) {
for (int j = 0; j < p_vertex_len; j++) {
const uint16_t *v = reinterpret_cast<const uint16_t *>(&r[j * vertex_elem_size + offsets[i]]);
Vector3 vec = Vector3(float(v[0]) / 65535.0, float(v[1]) / 65535.0, float(v[2]) / 65535.0);