Recalculate Normals/Tangents at Runtime?

I’ve got a skeletal mesh that gets deformed procedurally. Can I recalculate normals (or tangents) on them like if they were just imported?

1 2 3 bump

anyone?..

As far as I’m aware this can only be on reimport and not while in the editor or at runtime.

I found the code in MeshUtilities.cpp. But I don’t know if I can use it :confused:

So, it looks like all the code I need to compute normals at runtime is inside a structure called FSkeletalMeshUtilityBuilder, which is located in MeshUtilities.cpp. It’s pretty much unreachable though and not even declared in the header MeshUtilities.h.

Am I allowed to copy that code into my project and modify it to my needs? It’s not “editor only”-code.

Actually, nevermind. The code is hardly usable, because it doesn’t work with USkeletalMeshes directly but some other, probably import-related, class. Modifying it would have been much more work than writing it from scratch, which I did :slight_smile:
Wasn’t as difficult as I thought.

Hi! I am having the same problem as you with ProceduralMesh. The procedural mesh is being modified each frame, but there is no clear way to recalculate normals and tangents. Would you kindly share your code? :wink: That will help a lot!
Thanks!

void ComputeNormals(USkeletalMesh* Mesh, const TArray<TArray*>& doubles)
{
FStaticLODModel& Model = Mesh->GetResourceForRendering()->LODModels[0];

	TArray<FSoftSkinVertex*> vi = GetAllVertices(Model);

	const int count = vi.Num();
	TArray<FVector> normal, tangent, bitangent;
	normal.Init(FVector::ZeroVector, count);
	tangent.Init(FVector::ZeroVector, count);
	bitangent.Init(FVector::ZeroVector, count);


	FRawStaticIndexBuffer16or32Interface& ind = *Model.MultiSizeIndexContainer.GetIndexBuffer();
	FVector n;
	uint32 ti[3];

	int i, j, k;

	uint8 subModel = 0;
	uint16 offset = 0;
	
	for (i = 0; i < ind.Num(); i += 3)
	{
		ti[0] = ind.Get(i);
		ti[1] = ind.Get(i + 1);
		ti[2] = ind.Get(i + 2);

		n = FVector::CrossProduct(vi[ti[0]]->Position - vi[ti[1]]->Position, vi[ti[2]]->Position - vi[ti[0]]->Position);

		FVector deltaPos1 = vi[ti[1]]->Position - vi[ti[0]]->Position;
		FVector deltaPos2 = vi[ti[2]]->Position - vi[ti[0]]->Position;

		FVector2D deltaUV1 = vi[ti[1]]->UVs[0] - vi[ti[0]]->UVs[0];
		FVector2D deltaUV2 = vi[ti[2]]->UVs[0] - vi[ti[0]]->UVs[0];

		float r = 1.0f / (deltaUV1.X * deltaUV2.Y - deltaUV1.Y * deltaUV2.X);
		FVector t = (deltaPos1 * deltaUV2.Y - deltaPos2 * deltaUV1.Y)*r;
		FVector b = (deltaPos2 * deltaUV1.X - deltaPos1 * deltaUV2.X)*r;

		if (ti[0] >= uint32(doubles[subModel]->Num() + offset))
		{
			offset += doubles[subModel]->Num();
			subModel++;
		}

		for (j = 0; j < 3; j++)
		{
			normal[ti[j]] += n;

			tangent[ti[j]] += t;
			bitangent[ti[j]] += b;

			for (k = 0; k < (*doubles[subModel])[ti[j] - offset].doubles.Num(); k++)
			{
				normal[(*doubles[subModel])[ti[j] - offset].doubles[k] + offset] += n;
			}
		}
	}
	FVector TanX, TanY;
	for (i = 0; i < count; i++)
	{
		normal[i].Normalize();
		tangent[i].Normalize();
		bitangent[i].Normalize();

		vi[i]->TangentZ = normal[i];
		vi[i]->TangentX = tangent[i];
		vi[i]->TangentY = bitangent[i];
	}
}



TArray<FDoubleVertices> FindDoubleVertices(USkeletalMesh* Mesh)
{
const float DoublePositionTolerance = .001f;

FStaticLODModel& Model = Mesh->GetResourceForRendering()->LODModels[0];
TArray<FSoftSkinVertex*> vi = GetAllVertices(Model);
const int count = vi.Num();

TArray<FDoubleVertices> doubles;
doubles.SetNum(count);

int i, j, k;

for (i = 0; i < count; i++)
{
	if (doubles[i].doubles.Num() != 0)
		continue;

	FDoubleVertices matches;

	for (j = i + 1; j < count; j++)
	{
		if (doubles[j].doubles.Num() != 0)
			continue;

		if (vi[i]->Position.Equals(vi[j]->Position, DoublePositionTolerance))
			matches.doubles.Add(j);
	}

	if (matches.doubles.Num())
	{
		doubles[i] = matches;
		for (j = 0; j < matches.doubles.Num(); j++)
		{
			doubles[matches.doubles[j]].doubles.Add(i);
			for (k = 0; k < matches.doubles.Num(); k++)
			{
				if (k != j)
					doubles[matches.doubles[j]].doubles.Add(matches.doubles[k]);
			}
		}
	}
}

return doubles;
}

GetAllVertices is a simple function that puts pointers to all of a mesh’s vertices into one big array.

I seperated the two steps, because finding double-vertices in a mesh is a rather costly procedure, so you will not want to do it at runtime, but pre-compute and store the data along with your meshes instead.

FDoubleVertices only has a single member: TArray (of uint16) doubles;
I only need it, because UE4 doesn’t allow nested arrays otherwise.

Some of the variable names seem a bit strange. That’s because I used to have a structure “FVertexInfo” that had a single interface to read and modify both rigid or soft vertices. Since rigid vertices have been completely removed for skeletal meshes in 4.14, FVertexInfo is now simply FSoftSkinVertex.

1 Like

I posted the code below, but you will have to modify it a little for static meshes. Shouldn’t be too hard though. If you have questions, ask.

Thank you very much for sharing!

I have modified it for procedural mesh and it works perfect! :slight_smile:

I dont suppose you have a version of this that works in 4.19? Im working on converting it, but they changed a lot.

No, sorry. 4.19 made my project pretty much unportable. The amount of work I would have to put into it is just not worth it. And who knows, maybe a couple of versions from now, they change it all again (without any documentation, deprecation warnings or anything of course…).

From what I gathered, changing this bit of code shouldn’t be too hard, though. Basically, you need to use LODRenderData now instead of LODModel, and where there used to be arrays of vertex structs containing all the data, there are now seperate buffers for each vertex attribute. As long as you don’t need to alter skin weights, you should be fine.

Ya, it isnt too hard actually, ive almost got it converted. Although im confused on the vertex doubles part. The FindDoubleVertices function returns a TArray of FDoubleVertices, but the calculate normals function takes a TArray of TArray FDoubleVertices… what am i missing?

I’m 90% positive that I did this because my skeletal meshes are stitched together from different sub-meshes (body plus clothing, hair and stuff), which all have their own set of double vertices saved as data-assets, so these can be combined by putting pointers to the individual submodel double-arrays (from the data-asset) into an array for any given final franken-mesh. FindDoubles only operates on one “undividable” model at a time. So that’s why, unless my memory is failing me…

So, you can just ignore the outermost array, if you’re doubles don’t need this kind of flexibility.

I got it working, then i ended up using the UKismetProceduralMeshLibrary::CalculateTangentsForMesh function from procedural mesh component instead and it works perfectly, if anyone is interested here is the code:

void ComputeNormals(USkeletalMesh* Mesh)
{
	FSkeletalMeshLODRenderData* Model = &Mesh->GetResourceForRendering()->LODRenderData[0];

	int32 ModelVertNum = Model->StaticVertexBuffers.PositionVertexBuffer.GetNumVertices();

	TArray<FVector> Positions;
	TArray<int32> Triangles;
	TArray<FVector2D> uvs;

	for (int32 i = 0; i < ModelVertNum; i++)
	{
		Positions.Add(Model->StaticVertexBuffers.PositionVertexBuffer.VertexPosition(i));
		uvs.Add(Model->StaticVertexBuffers.StaticMeshVertexBuffer.GetVertexUV(i, 0));
	}

	FRawStaticIndexBuffer16or32Interface& indexBuffer = *(Model->MultiSizeIndexContainer.GetIndexBuffer());

	for (int32 i = 0; i < indexBuffer.Num(); i += 3)
	{
		Triangles.Add(indexBuffer.Get(i));
		Triangles.Add(indexBuffer.Get(i+1));
		Triangles.Add(indexBuffer.Get(i+2));
	}
	TArray<FVector> OutNormals;
	TArray<FProcMeshTangent> OutTangents;
	UKismetProceduralMeshLibrary::CalculateTangentsForMesh(Positions, Triangles, uvs, OutNormals, OutTangents);

	for (int32 i = 0; i < ModelVertNum; i++)
	{

		FVector TanX = OutTangents[i].TangentX;
		FPackedNormal TangentZ = OutNormals[i];
		TangentZ.Vector.W = 255;
		FVector TanZ = TangentZ;

		FVector TanY = (TanZ ^ TanX) * ((float)TangentZ.Vector.W / 127.5f - 1.0f);

		Model->StaticVertexBuffers.StaticMeshVertexBuffer.SetVertexTangents(
			i,
			TanX,
			TanY,
			OutNormals[i]
		);
	}
}
1 Like

Greater solution for me!

:grinning: