I’m basing this off of http://www.terathon.com/code/tangent.html
I had to store an array of all triangles that are attached to each other and then calculate the tangents together. Vertex normals are stored in triangle tanX, tangents in tanY, and bitangents in tanZ. Attempting to orthogonalize and calculate the bitangents makes the lighting worse.
// UE4 Procedural Mesh Generation from the Epic Wiki (https://wiki.unrealengine.com/Procedural_Mesh_Generation)
//
// forked from "Engine/Plugins/Runtime/CustomMeshComponent/Source/CustomMeshComponent/Classes/CustomMeshComponent.h"
#pragma once
#include "ProceduralMeshComponent.generated.h"
USTRUCT(BlueprintType)
struct FProceduralMeshVertex
{
GENERATED_USTRUCT_BODY()
UPROPERTY(EditAnywhere, Category = Triangle)
FVector Position;
UPROPERTY(EditAnywhere, Category = Triangle)
FColor Color;
UPROPERTY(EditAnywhere, Category = Triangle)
float U;
UPROPERTY(EditAnywhere, Category = Triangle)
float V;
UPROPERTY(EditAnywhere, Category = Triangle)
FVector tanX;
UPROPERTY(EditAnywhere, Category = Triangle)
FVector tanY;
UPROPERTY(EditAnywhere, Category = Triangle)
FVector tanZ;
UPROPERTY(EditAnywhere, Category = Triangle)
bool manualTangents = false;
};
USTRUCT(BlueprintType)
struct FProceduralMeshTriangle
{
GENERATED_USTRUCT_BODY()
UPROPERTY(EditAnywhere, Category = Triangle)
FProceduralMeshVertex Vertex0;
UPROPERTY(EditAnywhere, Category = Triangle)
FProceduralMeshVertex Vertex1;
UPROPERTY(EditAnywhere, Category = Triangle)
FProceduralMeshVertex Vertex2;
UPROPERTY(EditAnywhere, Category = Triangle)
TArray<int8> attchedTriangles;
UPROPERTY(EditAnywhere, Category = Triangle)
bool normalsHaveBeenCalculated = false;
//used to keep track of the triangles intended position in the array
UPROPERTY(EditAnywhere, Category = Triangle)
int8 triangleIndexNumber;
};
/** Component that allows you to specify custom triangle mesh geometry */
UCLASS(editinlinenew, meta = (BlueprintSpawnableComponent), ClassGroup = Rendering)
class UProceduralMeshComponent : public UMeshComponent, public IInterface_CollisionDataProvider
{
GENERATED_UCLASS_BODY()
public:
/** Set the geometry to use on this triangle mesh */
UFUNCTION(BlueprintCallable, Category = "Components|ProceduralMesh")
bool SetProceduralMeshTriangles(const TArray<FProceduralMeshTriangle>& Triangles);
/** Description of collision */
UPROPERTY(BlueprintReadOnly, Category = "Collision")
class UBodySetup* ModelBodySetup;
// Begin Interface_CollisionDataProvider Interface
virtual bool GetPhysicsTriMeshData(struct FTriMeshCollisionData* CollisionData, bool InUseAllTriData) override;
virtual bool ContainsPhysicsTriMeshData(bool InUseAllTriData) const override;
virtual bool WantsNegXTriMesh() override{ return false; }
// End Interface_CollisionDataProvider Interface
// Begin UPrimitiveComponent interface.
virtual FPrimitiveSceneProxy* CreateSceneProxy() override;
virtual class UBodySetup* GetBodySetup() override;
// End UPrimitiveComponent interface.
// Begin UMeshComponent interface.
virtual int32 GetNumMaterials() const override;
// End UMeshComponent interface.
void UpdateBodySetup();
void UpdateCollision();
//calcualte our normals, tangents, and bitangents
FProceduralMeshTriangle GetNormals(TArray<FProceduralMeshTriangle>& Triangles);
private:
// Begin USceneComponent interface.
virtual FBoxSphereBounds CalcBounds(const FTransform & LocalToWorld) const override;
// Begin USceneComponent interface.
/** */
TArray<FProceduralMeshTriangle> ProceduralMeshTris;
friend class FProceduralMeshSceneProxy;
};
{
//Store an array of identical triangles
TArray<FProceduralMeshTriangle> storedTriangles;
for (int TriIdx = 0; TriIdx < Component->ProceduralMeshTris.Num(); TriIdx++)
{
storedTriangles.Add(Component->ProceduralMeshTris[TriIdx]);
storedTriangles[TriIdx].triangleIndexNumber = TriIdx;
}
//search predefined attached triangles for matching vertices and average tangents
for (int i = 0; i < storedTriangles.Num() - 1; i++)
{
//store an array of all attached triangles so that they can have thier normals calculated
TArray<FProceduralMeshTriangle> attachedTriangles;
if (!storedTriangles*.normalsHaveBeenCalculated)
{
attachedTriangles.Add(storedTriangles*);
storedTriangles*.normalsHaveBeenCalculated = true;
//add all attached triangles to the array that gets bulk normals calculation
//set normalsHaveBeenCalculated to true to prevent the values from getting overwritten
for (int j = 0; j < storedTriangles*.attchedTriangles.Num(); j++)
{
attachedTriangles.Add(storedTriangles[storedTriangles*.attchedTriangles[j]]);
storedTriangles[storedTriangles*.attchedTriangles[j]].normalsHaveBeenCalculated = true;
//get a refernce to the the attached triange that we are currently searching
//FProceduralMeshTriangle currentAttachedTriangle = storedTriangles[storedTriangles*.attchedTriangles[j]];
}
//Lengyel, Eric. “Computing Tangent Space Basis Vectors for an Arbitrary Mesh”. Terathon Software 3D Graphics Library, 2001. http://www.terathon.com/code/tangent.html
for (long a = 0; a < attachedTriangles.Num(); a++)
{
//determine which way the normals are facing
int8 handedness;
FVector v1 = attachedTriangles[a].Vertex0.Position;
FVector v2 = attachedTriangles[a].Vertex1.Position;
FVector v3 = attachedTriangles[a].Vertex2.Position;
FVector2D w1 = FVector2D(attachedTriangles[a].Vertex0.U, attachedTriangles[a].Vertex0.V);
FVector2D w2 = FVector2D(attachedTriangles[a].Vertex1.U, attachedTriangles[a].Vertex1.V);
FVector2D w3 = FVector2D(attachedTriangles[a].Vertex2.U, attachedTriangles[a].Vertex2.V);
float x1 = v2.X - v1.X;
float x2 = v3.X - v1.X;
float y1 = v2.Y - v1.Y;
float y2 = v3.Y - v1.Y;
float z1 = v2.Z - v1.Z;
float z2 = v3.Z - v1.Z;
float s1 = w2.X - w1.X;
float s2 = w3.X - w1.X;
float t1 = w2.Y - w1.Y;
float t2 = w3.Y - w1.Y;
float r = 1.0F / (s1 * t2 - s2 * t1);
FVector sdir((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r,
(t2 * z1 - t1 * z2) * r);
FVector tdir((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r,
(s1 * z2 - s2 * z1) * r);
attachedTriangles[a].Vertex0.tanY += sdir;
attachedTriangles[a].Vertex1.tanY += sdir;
attachedTriangles[a].Vertex2.tanY += sdir;
attachedTriangles[a].Vertex0.tanZ += tdir;
attachedTriangles[a].Vertex1.tanZ += tdir;
attachedTriangles[a].Vertex2.tanZ += tdir;
//attachedTriangles[a].
storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex0.tanY = attachedTriangles[a].Vertex0.tanY;
storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex0.tanZ = attachedTriangles[a].Vertex0.tanZ;
storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex1.tanY = attachedTriangles[a].Vertex1.tanY;
storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex1.tanZ = attachedTriangles[a].Vertex1.tanZ;
storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex2.tanY = attachedTriangles[a].Vertex2.tanY;
storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex2.tanZ = attachedTriangles[a].Vertex2.tanZ;
const FVector n = attachedTriangles[a].Vertex0.tanX;
const FVector t = attachedTriangles[a].Vertex0.tanY;
// Gram-Schmidt orthogonalize
//storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex0.tanY = (t - n * (n | t)).SafeNormal();
//calculateHandedness
handedness = ((( n ^ t) | storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex0.tanZ) < 0.0F) ? -1.0F : 1.0F;
//calculate Bitangent
//storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex0.tanZ = (n ^ t) * handedness;
const FVector nn = attachedTriangles[a].Vertex1.tanX;
const FVector tt = attachedTriangles[a].Vertex1.tanY;
// Gram-Schmidt orthogonalize
//storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex1.tanY = (tt - nn * (nn | tt)).SafeNormal();
//calculateHandedness
handedness = (((nn ^ tt) | storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex1.tanZ) < 0.0F) ? -1.0F : 1.0F;
//calculate Bitangent
//storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex1.tanZ = (nn ^ tt) * handedness;
const FVector nnn = attachedTriangles[a].Vertex2.tanX;
const FVector ttt = attachedTriangles[a].Vertex2.tanY;
// Gram-Schmidt orthogonalize
//storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex2.tanY = (ttt - nnn * (nnn | ttt)).SafeNormal();
//calculateHandedness
handedness = (((nnn ^ ttt) | storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex2.tanZ) < 0.0F) ? -1.0F : 1.0F;
//calculate Bitangent
//storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex2.tanZ = (nnn ^ ttt) * handedness;
}
Edit: got it - I outputting the normals as bitangents.