Dynamically created UStaticMesh (BuildFromMeshDescriptions) does not cast dynamic shadow

Hello all,
I’ve some simple code to create dynamically a StaticMesh from the BeginPlay of a C++ AActor class.
Unfortunately, the Static Mesh can receive shadow from other geometry but is unable to cast shadow.
I know that the problem comes from the initialisation of the StaticMesh because if instead of building it I load it from an asset (C:/Program Files/Epic Games/UE_5.1/Engine/Content/BasicShapes/Sphere.uasset) it works as expected.

Does anyone have an idea?
Cheers!

Here is a minimum relevant code snipset:

void AMyDynActor::CreateSphere(std::vector<FVector3f>& Vertices, std::vector<FVector3f>& Normals, std::vector<FVector2f>& UVs, std::vector<int32>& Triangles, float radius)
{
	int sectorCount = 32;
	int	stackCount = 32;
	// build vertices of sphere with smooth shading using parametric equation
	// x = r * cos(u) * cos(v)
	// y = r * cos(u) * sin(v)
	// z = r * sin(u)
	// where u: stack(latitude) angle (-90 <= u <= 90)
	//       v: sector(longitude) angle (0 <= v <= 360)

	float x, y, z, xy;                              // vertex position
	float s, t;                                     // texCoord

	float sectorStep = 2 * PI / sectorCount;
	float stackStep = PI / stackCount;
	float sectorAngle, stackAngle;

	for (int i = 0; i <= stackCount; ++i)
	{
		stackAngle = PI / 2 - i * stackStep;        // starting from pi/2 to -pi/2
		xy = radius * cosf(stackAngle);             // r * cos(u)
		z = radius * sinf(stackAngle);              // r * sin(u)

		// add (sectorCount+1) vertices per stack
		// the first and last vertices have same position and normal, but different tex coords
		for (int j = 0; j <= sectorCount; ++j)
		{
			sectorAngle = j * sectorStep;           // starting from 0 to 2pi

			// vertex position
			x = xy * cosf(sectorAngle);             // r * cos(u) * cos(v)
			y = xy * sinf(sectorAngle);             // r * cos(u) * sin(v)
			FVector3f pos(x, y, z);
			Vertices.push_back(pos);
			pos.Normalize();
			Normals.push_back(pos);

			// vertex tex coord between [0, 1]
			s = 1.0 - (float)j / sectorCount;
			t = (float)i / stackCount;
			UVs.push_back(FVector2f(s, t));
		}
	}

	//build grid
	//id-sectorCount-1   id-sectorCount
	//   ----						
	//  |  / |
	//  | /  |
	//   ----
	//id-1    id	
	for (int i = 1; i <= stackCount; ++i)
	{
		for (int j = 1; j <= sectorCount; ++j)
		{
				int id0 = i + j * stackCount;
				Triangles.push_back(id0 - sectorCount);
				Triangles.push_back(id0 - 1);
				Triangles.push_back(id0 - sectorCount - 1);
				Triangles.push_back(id0);
				Triangles.push_back(id0 - 1);
				Triangles.push_back(id0 - sectorCount);
		}
	}
}


void AMyDynActor::FillMeshDescription(FMeshDescription& meshDesc, std::vector<FVector3f>& P, std::vector<FVector3f>& N, std::vector<FVector2f>& uv, std::vector<int32>& t)
{
	FStaticMeshAttributes attributes(meshDesc);
	attributes.Register();

	//reserve stuff
	meshDesc.ReserveNewVertices(P.size());
	meshDesc.ReserveNewVertexInstances(P.size());

	meshDesc.CreatePolygonGroup();
	meshDesc.ReserveNewPolygons(t.size() / 3);
	meshDesc.ReserveNewTriangles(t.size() / 3);
	meshDesc.ReserveNewEdges(t.size());

	for (int v = 0; v < P.size(); ++v)
	{
		meshDesc.CreateVertex();
		meshDesc.CreateVertexInstance(v);
	}

	//fill traiangles data
	for (int ti = 0; ti < t.size(); ti += 3)
	{
		meshDesc.CreateTriangle(0, { t[ti],t[ti + 1] ,t[ti + 2] });
	}

	//fill vertex data
	auto positions = meshDesc.GetVertexPositions().GetRawArray();;
	auto uvs = meshDesc.VertexInstanceAttributes().GetAttributesRef<FVector2f>(MeshAttribute::VertexInstance::TextureCoordinate).GetRawArray();
	auto normals = meshDesc.VertexInstanceAttributes().GetAttributesRef<FVector3f>(MeshAttribute::VertexInstance::Normal).GetRawArray();
	for (int v = 0; v < P.size(); ++v)
	{
		uvs[v] = uv[v];
		positions[v] = P[v];
		normals[v] = N[v];
	}

}

// Called when the game starts or when spawned
void AMyDynActor::BeginPlay()
{
	Super::BeginPlay();

	//build sphere desc
	std::vector<FVector3f>	Vertices;
	std::vector<FVector3f>	Normals;
	std::vector<FVector2f>	UVs;
	std::vector<int32>		Triangles;
	CreateSphere(Vertices, Normals, UVs, Triangles, 100);
	FMeshDescription meshDesc;
	FillMeshDescription(meshDesc, Vertices, Normals, UVs, Triangles);

	//build static mesh
	StaticMesh = NewObject<UStaticMesh>(this, FName("staticMesh"));
	StaticMesh->SetRenderData(MakeUnique<FStaticMeshRenderData>());
	StaticMesh->CreateBodySetup();
	StaticMesh->bAllowCPUAccess = true;
	StaticMesh->GetBodySetup()->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple;
	UStaticMesh::FBuildMeshDescriptionsParams mdParams;
	StaticMesh->BuildFromMeshDescriptions({ &meshDesc }, mdParams);




	//build mesh component
	UStaticMeshComponent* mesh = NewObject<UStaticMeshComponent>(this, UStaticMeshComponent::StaticClass(), FName("importedMesh"));
	mesh->RegisterComponent();
	mesh->SetRelativeLocation(FVector(20000, 20000, 0));
	mesh->SetWorldScale3D(FVector(50, 50, 1000));
	mesh->SetStaticMesh(StaticMesh);
	mesh->SetMaterial(0, Material);
	mesh->CastShadow = true;
	mesh->bCastFarShadow = true;
	mesh->bCastDynamicShadow = true;
	mesh->bCastShadowAsTwoSided = true;

	this->SetRootComponent(mesh);
	
}

Hi mrZog,

How are you setting the material?
Try adding this code at the end of your beginplay before the SetRootComponent(mesh):

FMeshSectionInfo msi=mesh->GetSectionInfoMap().Get(o,0);
msi.bCastShadow=true;
mesh->GetSectionInfoMap().Set(0,0,msi);

Hi RecourseDesign,
Thanks for the reply but it doesn’t correct the problem.

I’m setting the material directly from the parent UStaticMeshComponent: mesh->SetMaterial(0, Material);

When I try to inspect FMeshSectionInfo msi = StaticMesh->GetSectionInfoMap().Get(0, 0) in debugger, msi.bCastShadow is already set to true:

- msi {MaterialIndex=0 bEnableCollision=true bCastShadow=true …} FMeshSectionInfo
MaterialIndex 0 int
bEnableCollision true bool
bCastShadow true bool
bVisibleInRayTracing true bool
bAffectDistanceFieldLighting true bool
bForceOpaque false bool

FYI, a colleague of mine found the culprit…
Adding StaticMesh->GetStaticMaterials().Add(FStaticMaterial());
just after the StaticMesh instanciation solve the shadowing problem.

however, I don’t really understand why :slight_smile:

2 Likes