Question regarding NPC meshes and LODs

+
Hey all, so I'm a game developer and I'm trying to emulate the NPC randomization in Cyberpunk, and I was curious what the assets look like under the hood,

Basically right now i load all my 200-ish Clothing pieces into Lists in Unity, and then do some math to generate every possible combination- i then COMBINE all the clothing into a single Skinned Mesh, and then Generate 3 LODs for each combination.
This works out fine, the problem is i have like THOUSANDS of possible combos, and I'm wondering how Cyberpunk handles its randomized NPCs and their LODS.

The issue is I can randomize and combine my skinned meshs at runtime for NPCs, but when i go to generate LODs theres a major lag spike for each NPC when they appear, which obviously isn't gonna work in scenes where i have like 50+ NPCs on screen at a time, constantly culling and re-instantiating.

Anyone whos got a handle on how Cyperpunk NPCs handle their randomization and can provide some insight would be super helpful.
 
You will want to get the latest Wolvenkit nightly release here: GitHub - WolvenKit/WolvenKit-nightly-releases: Provides WolvenKit nightly releases.

And then hop on the modding discords if you want to go really deep into this. Its way above my paygrade.

I don't know how much of the following information will be useful to you because I don't understand appearance randomisation at code level, I only sort of vaguely understand the file side of it. But not how the mechanics of it work. Just where to find information related to appearance variations from the perspective of a modder who is mostly focused on materials and material design.

Material instances are embedded in a buffer appended to the mesh (REDEngine .mesh). The .mesh file also contains all the LOD submeshes.

The REDEngine file chain for loading meshes into the game looks like this:

global Entity -> global Appearance -> local Entity -> Mesh/Materials.

You can find global Entities here: \base\characters\entities\

Of particular interest to you will probably be stuff like \base\characters\entities\light_crowd\crowd__city_corpoplaza_ma.ent

You can find global Appearances here: \base\characters\appearances\

Again you are probably interested in the .apps in the \light_crowd\ folder.

You can find cooked Appearances here: \base\cookedappearances\

(more on cookedapps later...)

Local entities are typically found 1 subfolder above the mesh they import. For example, this is the file path to an npc wa (woman average) bolero jacket mesh: \base\characters\garment\citizen_formal\torso\t2_139_jacket__bolero\t2_139_wa_jacket__bolero.mesh

And this is the file path of its local Entity: \base\characters\garment\citizen_formal\torso\t2_139_wa_jacket__bolero.ent

A global Entity (.ent) imports a global Appearance (.app). These app files are huge (double digit megabytes) and consist of arrays of "appearanceDefinitions".

If we use an example such as "corpo_generic_wa.app" this is an appearance file for a group of generic corpo npcs with the wa (woman average) body type. Inside this file there is an appearanceDefinition for "security_lvl1_03", presumably a generic female security guard npc. Inside this array you will find the file path to its associated cookedapp, the file paths to its proxy/shadow mesh and resolved dependencies (mesh/material/animation assets). It has a buffer that contains entity parameter component data - a lot of stuff to do with skinning and animation that I don't understand - geometry hashes, chunk counts etc.

There is a second major type of Appearance called a cooked Appearance (.cookedapp) and they are split up in ways I don't understand. As far as I can tell, they are like bundles of common .app data in 1mb to 4mb files with their resolved dependency assets (mainly material assets and REDEngine material related files) embedded in them. The theory is they are redundant apps split up into smaller parts and structured in a particular way to provide asset streaming relief if you have really slow storage.

The local Entity (.ent) imports meshes, rigs, animgraphs. Entity files are usually tiny (single digit to low double digit kilobytes).

A lot of the information you find in the .ent buffer will look like magic number shit unless you are familiar with .app files. There is a lot more here that I just don't understand so you will almost certainly have to go look at the files yourself and talk to some of the people on the modding discord to get a better sense of how it all fits together. Nim probably knows the most about the file side of things. Numbers the code side of things (and if he doesn't he will know the people who do). Max seems to know a bit about how apps work because he authored AMM and that allows you to swap/ban npc appearances on the fly.
 
Last edited:
Hey thanks so much, this is super helpful!

Do you happen to know where I might find specific polycounts for these LOD meshes? Would I have to dig around in the .app files somehow to get them?
I've been able to code a system that generates LODs for each clothing mesh my NPCs use, so I now basically randomize all the possible NPC meshes (bodyparts, clothing, accessories), as data files that "fill" the skinned mesh slots before i combine them all into a single mesh, which lets me randomize appearances and generate 3 LODs with the data for each clothing piece and each of their individual LODs, etc.

But I'm trying to get a handle on what the average Poly/Triangle count for an average Cyberpunk NPC is, right now my NPCs average from 10,000 to sometimes 80,000 polygons at LOD0, and down to 1000-5000 at LOD3, which is helpful, but I'd like to see if maybe the REDEngine is relying more on textures in the memory or actual Mesh data. Cause theres a TON of little poly accessories like implants and cyberware on these characters, and I'm trying to find a way to emulate that without just having these monstrous 100k+ polycounts
 
You can just export the meshes to .glb using Wolvenkit or .fbx using Noesis (with alphaZomega's import/export py plugin).

Load it up into blender/3ds max/maya and you will see 4 sets of LOD submeshes. LOD0 is the highest level of detail. LOD3 is the lowest level of detail. You can see the vertex/edge/face count in whatever 3d modelling application you use.

For LOD0, base heads are typically around 7k to 9k verts. Base bodies around 6k to 7k verts. Hair meshes are about 10k to 15k verts.

In cyberpunk, the head, hair (includes hair cap), body, arms and all garments are separate meshes. so you will be factoring all of those into the total vert count. Also things like face cyberware/body cyberware, tattoos etc. are often handled as decal meshes. These are usually cutouts of the base head/body/arms, scaled up a tiny bit with the materials instanced from mesh_decal.mt. This is a material shader with alpha transparency. You UV a decal (like a colour tattoo texture, or cyberware lines/widgets), alpha out the edges and project it onto the mesh. The decal mesh inherit bones and vertex weights from the base head/body/arm rig so they deform exactly the same.

I don't do anything with LODs so I haven't really looked at the vert counts and therefore cant give you rough numbers. I have studied a lot of cyberpunk meshes though and by and large the geometry is very efficient. There are no subdivisions where there doesn't need to be. Although hair meshes are relatively big in Cyberpunk compared to other deformables (because they need to curve/bend/twist and swish/swap with dangle physics), its worth noting they are still small compared to other games like HZD. Aloy's hair mesh is like 63k verts or something. Judy's hair mesh is like 11k verts, including the hair cap.

There are no loop cuts or subdivisions and you can see it sometimes like here:



See how the hairs have sharp angles to them? Thats just lack of geometry to curve. So they were really pushing to keep the vert counts down, probably because the scale of what they were trying to do with npc crowds was so enormous. I guess they had strict budgets for everything.
 
Top Bottom