x

Search in
Sort by:

Question Status:

Search help

  • Simple searches use one or more words. Separate the words with spaces (cat dog) to search cat,dog or both. Separate the words with plus signs (cat +dog) to search for items that may contain cat but must contain dog.
  • You can further refine your search on the search results page, where you can search by keywords, author, topic. These can be combined with each other. Examples
    • cat dog --matches anything with cat,dog or both
    • cat +dog --searches for cat +dog where dog is a mandatory term
    • cat -dog -- searches for cat excluding any result containing dog
    • [cats] —will restrict your search to results with topic named "cats"
    • [cats] [dogs] —will restrict your search to results with both topics, "cats", and "dogs"

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?

Product Version: UE 4.10
Tags:
more ▼

asked Dec 04 '15 at 02:07 PM in Using UE4

avatar image

MaxPower42
643 67 82 118

avatar image MaxPower42 Dec 16 '15 at 11:18 AM

1 2 3 bump

avatar image MaxPower42 Jul 01 '16 at 07:03 AM

anyone?...

avatar image Tim Hobson ♦♦ STAFF Jul 01 '16 at 12:52 PM

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

avatar image MaxPower42 Jul 01 '16 at 03:41 PM

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

avatar image MaxPower42 Jul 01 '16 at 07:36 PM

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.

avatar image MaxPower42 Jul 02 '16 at 07:21 AM

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 :) Wasn't as difficult as I thought.

avatar image wolfyllow Jan 09 '17 at 12:54 PM

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? ;) That will help a lot! Thanks!

avatar image MaxPower42 Jan 09 '17 at 06:32 PM

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.

(comments are locked)
10|2000 characters needed characters left
Viewable by all users

1 answer: sort voted first
 void ComputeNormals(USkeletalMesh* Mesh, const TArray<TArray<FDoubleVertices>*>& 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.

more ▼

answered Jan 09 '17 at 06:09 PM

avatar image

MaxPower42
643 67 82 118

avatar image wolfyllow Jan 11 '17 at 05:54 PM

Thank you very much for sharing!

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

avatar image kostenickj Jul 14 '18 at 04:10 PM

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

avatar image MaxPower42 Jul 14 '18 at 04:28 PM

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.

avatar image kostenickj Jul 14 '18 at 04:53 PM

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?

avatar image MaxPower42 Jul 14 '18 at 06:45 PM

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.

avatar image kostenickj Jul 14 '18 at 10:50 PM

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]
         );
     }
 }

(comments are locked)
10|2000 characters needed characters left
Viewable by all users
Your answer
toggle preview:

Up to 5 attachments (including images) can be used with a maximum of 5.2 MB each and 5.2 MB total.

Follow this question

Once you sign in you will be able to subscribe for any updates here

Answers to this question