mirror of
https://github.com/blender/blender
synced 2026-09-29 04:37:17 +03:00
USD: Export normals based on Blender internal data model
Most game engines use and store normal data per-point. Blender's default USD export always converts them to face-varying/corner. This PR exports normals following Blender internal data model (point, corner or face), thus making import in engines more straightforward for meshes that already use per-point normals without additional conversion steps. USD export tests have also been updated to reflect this change. Pull Request: https://projects.blender.org/blender/blender/pulls/159858
This commit is contained in:
parent
70898c5508
commit
46d0a7ccd4
4 changed files with 79 additions and 13 deletions
|
|
@ -702,36 +702,36 @@ void USDGenericMeshWriter::write_normals(const Mesh *mesh, pxr::UsdGeomMesh &usd
|
|||
{
|
||||
pxr::UsdTimeCode time = get_export_time_code();
|
||||
|
||||
Span<float3> src_normals;
|
||||
pxr::VtVec3fArray loop_normals;
|
||||
loop_normals.resize(mesh->corners_num);
|
||||
|
||||
MutableSpan dst_normals(reinterpret_cast<float3 *>(loop_normals.data()), loop_normals.size());
|
||||
|
||||
pxr::TfToken interpolation;
|
||||
switch (mesh->normals_domain()) {
|
||||
case bke::MeshNormalDomain::Point: {
|
||||
array_utils::gather(mesh->vert_normals(), mesh->corner_verts(), dst_normals);
|
||||
src_normals = mesh->vert_normals();
|
||||
interpolation = pxr::UsdGeomTokens->vertex;
|
||||
break;
|
||||
}
|
||||
case bke::MeshNormalDomain::Face: {
|
||||
const OffsetIndices faces = mesh->faces();
|
||||
const Span<float3> face_normals = mesh->face_normals();
|
||||
for (const int i : faces.index_range()) {
|
||||
dst_normals.slice(faces[i]).fill(face_normals[i]);
|
||||
}
|
||||
src_normals = mesh->face_normals();
|
||||
interpolation = pxr::UsdGeomTokens->uniform;
|
||||
break;
|
||||
}
|
||||
case bke::MeshNormalDomain::Corner: {
|
||||
array_utils::copy(mesh->corner_normals(), dst_normals);
|
||||
src_normals = mesh->corner_normals();
|
||||
interpolation = pxr::UsdGeomTokens->faceVarying;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
loop_normals.resize(src_normals.size());
|
||||
MutableSpan dst_normals(reinterpret_cast<float3 *>(loop_normals.data()), loop_normals.size());
|
||||
array_utils::copy(src_normals, dst_normals);
|
||||
pxr::UsdAttribute attr_normals = usd_mesh.CreateNormalsAttr(pxr::VtValue(), true);
|
||||
if (!attr_normals.HasValue()) {
|
||||
attr_normals.Set(loop_normals, pxr::UsdTimeCode::Default());
|
||||
}
|
||||
usd_value_writer_.SetAttribute(attr_normals, pxr::VtValue(loop_normals), time);
|
||||
usd_mesh.SetNormalsInterpolation(pxr::UsdGeomTokens->faceVarying);
|
||||
usd_mesh.SetNormalsInterpolation(interpolation);
|
||||
}
|
||||
|
||||
void USDGenericMeshWriter::write_surface_velocity(const Mesh *mesh,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include "BKE_context.hh"
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_mesh_types.hh"
|
||||
|
||||
#include "BLI_fileops.h"
|
||||
#include "BLI_listbase.h"
|
||||
|
|
@ -192,7 +193,23 @@ class UsdExportTest : public BlendfileLoadingBaseTest {
|
|||
EXPECT_EQ(mesh->verts_num, positions.size());
|
||||
EXPECT_EQ(mesh->faces_num, face_counts.size());
|
||||
EXPECT_EQ(mesh->corners_num, face_indices.size());
|
||||
EXPECT_EQ(mesh->corners_num, normals.size());
|
||||
switch (mesh->normals_domain()) {
|
||||
case bke::MeshNormalDomain::Point: {
|
||||
EXPECT_EQ(mesh_prim.GetNormalsInterpolation(), pxr::UsdGeomTokens->vertex);
|
||||
EXPECT_EQ(mesh->verts_num, normals.size());
|
||||
break;
|
||||
}
|
||||
case bke::MeshNormalDomain::Face: {
|
||||
EXPECT_EQ(mesh_prim.GetNormalsInterpolation(), pxr::UsdGeomTokens->uniform);
|
||||
EXPECT_EQ(mesh->faces_num, normals.size());
|
||||
break;
|
||||
}
|
||||
case bke::MeshNormalDomain::Corner: {
|
||||
EXPECT_EQ(mesh_prim.GetNormalsInterpolation(), pxr::UsdGeomTokens->faceVarying);
|
||||
EXPECT_EQ(mesh->corners_num, normals.size());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
3
tests/files/usd/usd_mesh_normals.blend
Normal file
3
tests/files/usd/usd_mesh_normals.blend
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:da2a4f52b758459c019ed536c5fa279b989a9569891cb57a55602069bb734c59
|
||||
size 107016
|
||||
|
|
@ -2209,6 +2209,52 @@ class USDExportTest(AbstractUSDTest):
|
|||
check_colorspace(stage.GetPrimAtPath(f"/root/_materials/{mat.name}"), "Material")
|
||||
check_colorspace(stage.GetPrimAtPath(f"/root/{mesh_name}/{mesh_name}"), "Mesh")
|
||||
|
||||
def test_export_mesh_normals(self):
|
||||
"""Test that each exported USD normal interpolation and number matches
|
||||
Blender mesh normal domain"""
|
||||
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "usd_mesh_normals.blend"))
|
||||
export_path = self.tempdir / "usd_mesh_normals.usda"
|
||||
|
||||
self.export_and_validate(
|
||||
filepath=str(export_path),
|
||||
evaluation_mode="RENDER",
|
||||
)
|
||||
|
||||
stage = Usd.Stage.Open(str(export_path))
|
||||
|
||||
# validate face normals (uniform)
|
||||
faceMesh = UsdGeom.Mesh(stage.GetPrimAtPath("/root/shade_flat/shade_flat"))
|
||||
self.assertEqual(
|
||||
len(faceMesh.GetNormalsAttr().Get()),
|
||||
len(faceMesh.GetFaceVertexCountsAttr().Get()),
|
||||
"Number of normals should equal number of faces")
|
||||
self.assertEqual(
|
||||
faceMesh.GetNormalsInterpolation(),
|
||||
UsdGeom.Tokens.uniform,
|
||||
"Normals should be uniform interpolated")
|
||||
|
||||
# validate corner normals (face-varying)
|
||||
cornerMesh = UsdGeom.Mesh(stage.GetPrimAtPath("/root/shade_auto22/shade_auto22"))
|
||||
self.assertEqual(
|
||||
len(cornerMesh.GetNormalsAttr().Get()),
|
||||
len(cornerMesh.GetFaceVertexIndicesAttr().Get()),
|
||||
"Number of normals should equal number of indices")
|
||||
self.assertEqual(
|
||||
cornerMesh.GetNormalsInterpolation(),
|
||||
UsdGeom.Tokens.faceVarying,
|
||||
"Normals should be faceVarying interpolated")
|
||||
|
||||
# validate point normals (vertex)
|
||||
pointMesh = UsdGeom.Mesh(stage.GetPrimAtPath("/root/shade_smooth/shade_smooth"))
|
||||
self.assertEqual(
|
||||
len(pointMesh.GetNormalsAttr().Get()),
|
||||
len(pointMesh.GetPointsAttr().Get()),
|
||||
"Number of normals should equal number of points")
|
||||
self.assertEqual(
|
||||
pointMesh.GetNormalsInterpolation(),
|
||||
UsdGeom.Tokens.vertex,
|
||||
"Normals should be vertex interpolated")
|
||||
|
||||
|
||||
class USDHookBase:
|
||||
instructions = {}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue