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.
This commit is contained in:
clayjohn 2023-10-31 15:51:07 +01:00
parent 6afd320984
commit d1043a5f93
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);