Merge pull request #84252 from clayjohn/ensure_tangents

Enhance checks and user experience around tangent arrays in meshes.
This commit is contained in:
Rémi Verschelde 2023-11-02 09:11:32 +01:00
commit 44a54f4500
No known key found for this signature in database
GPG key ID: C3336907360768E1
12 changed files with 127 additions and 19 deletions

View file

@ -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) {

View file

@ -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;

View file

@ -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;
}

View file

@ -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();

View file

@ -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 (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 (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") || 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();
}

View file

@ -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) {

View file

@ -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:");

View file

@ -165,6 +165,7 @@ public:
bool uses_discard = false;
bool uses_roughness = false;
bool uses_normal = false;
bool uses_tangent = false;
bool uses_particle_trails = false;
bool unshaded = false;

View file

@ -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) {

View file

@ -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) {

View file

@ -119,6 +119,7 @@ public:
bool uses_discard = false;
bool uses_roughness = false;
bool uses_normal = false;
bool uses_tangent = false;
bool uses_particle_trails = false;
bool unshaded = false;

View file

@ -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);