cubemap handling from vbsp parser (eventually to be used for PBR irradiance or whatever i guess)

This commit is contained in:
ecker 2026-06-13 23:53:05 -05:00
parent 7034da71de
commit d5f374350e
20 changed files with 492 additions and 179 deletions

View File

@ -3,7 +3,7 @@
"scenes": {
"start": "StartMenu",
"lights": { "enabled": true,
"lightmaps": false,
"lightmaps": true,
"max": 32,
"shadows": {
"enabled": true,
@ -21,8 +21,8 @@
},
"textures": {
"max": {
"2D": 1024,
"cube": 1024,
"2D": 512,
"cube": 128,
"3D": 128
}
},
@ -115,12 +115,12 @@
"pipelines": {
"deferred": true,
"gui": true,
"vsync": false, // vsync on vulkan side rather than engine-side
"vsync": true, // vsync on vulkan side rather than engine-side
"hdr": true,
"vxgi": false, // to-do: fix issues
"culling": false,
"bloom": false,
"dof": false,
"bloom": true,
"dof": true,
"rt": false,
"fsr": false,
"postProcess": false // "postProcess.chromab" // false

View File

@ -25,7 +25,7 @@
"fov": 90,
"bias": {
"constant": -1.25,
"slope": -1.75,
"slope": -3,
"shader": 0.00001 // 0.000005 //0.000000005
},
"radius": [0.5, 0],

View File

@ -3,13 +3,5 @@
"assets": [
// { "filename": "./maps/cs_office.bsp" }
{ "filename": "./maps/cs_office/graph.json" }
],
"metadata": {
"graph": {
// realistically shouldn't ever need to define additional tags for BSPs
"tags": {
}
}
}
]
}

View File

@ -1,16 +1,7 @@
{
"import": "./base_sourceengine.json",
"assets": [
// { "filename": "./maps/mds_mcdonalds.bsp" }
{ "filename": "./maps/mds_mcdonalds/graph.json" }
],
"metadata": {
"graph": {
// "bgm": "./audio/soundscape/sh2_ambience.wav",
// realistically shouldn't ever need to define additional tags for BSPs
"tags": {
}
}
}
// { "filename": "./maps/mcdonalds-mds.bsp" }
{ "filename": "./maps/mcdonalds-mds/graph.json" }
]
}

View File

@ -314,6 +314,32 @@ void populateSurfaceMaterial() {
if ( validTextureIndex( material.indexEmissive ) ) {
surface.light *= sampleTexture( material.indexEmissive );
}
// Cubemap
int cubemapIndex = -1;
if ( 0 <= surface.instance.cubemapID ) cubemapIndex = surface.instance.cubemapID; // instance takes priority over material
/*
else if ( 0 <= material.indexCubemap ) cubemapIndex = material.indexCubemap;
if ( 0 <= cubemapIndex && surface.material.roughness < 1.0 ) {
const Texture texture = textures[cubemapIndex];
vec3 V = normalize(surface.position.eye);
vec3 N = surface.normal.eye;
vec3 R = reflect(V, N);
mat3 invView = mat3(ubo.eyes[surface.pass].iView);
vec3 worldR = invView * R;
float mipLevel = surface.material.roughness * mipLevels(textureSize(samplerCubemaps[nonuniformEXT(texture.index)], 0).xy);
vec3 reflection = textureLod(samplerCubemaps[nonuniformEXT(texture.index)], worldR, mipLevel).rgb;
vec3 F0 = mix(vec3(0.04), surface.material.albedo.rgb, surface.material.metallic);
float cosTheta = max(dot(N, -V), 0.0);
vec3 F = F0 + (max(vec3(1.0 - surface.material.roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0);
surface.light.rgb += reflection * F * surface.material.occlusion;
}
*/
// (Occlusion/)Metallic/Roughness map
if ( validTextureIndex( material.indexMetallicRoughness ) ) {
vec4 samp = sampleTexture( material.indexMetallicRoughness );

View File

@ -54,9 +54,9 @@ float omniShadowMap( const Light light, float def ) {
vec4 positionClip = light.projection * views[index] * vec4(surface.position.world - light.position, 1.0);
positionClip.xy /= positionClip.w;
if ( positionClip.x < -1 || positionClip.x >= 1 ) return 0.0;
if ( positionClip.y < -1 || positionClip.y >= 1 ) return 0.0;
if ( positionClip.z <= 0 || positionClip.z >= 1 ) return 0.0;
if ( positionClip.x < -1 || positionClip.x >= 1 ) return 1.0f;
if ( positionClip.y < -1 || positionClip.y >= 1 ) return 1.0f;
if ( positionClip.z <= 0 || positionClip.z >= 1 ) return 1.0f;
const float bias = light.depthBias;
const float eyeDepth = abs(positionClip.z / positionClip.w);
@ -77,7 +77,7 @@ float omniShadowMap( const Light light, float def ) {
sampled = texture(samplerCubemaps[nonuniformEXT(light.indexMap)], D).r ;
} else {
for ( int i = 0; i < samples; ++i ) {
const int idx = int( float(samples) * random(floor(surface.position.world.xyz * 1000.0), i)) % samples;
const int idx = int( float(samples) * interleavedGradientNoise(surface.fragCoord.xy + i)) % samples;
vec2 poisson = poissonDisk[idx] / 700.0;
vec3 P = vec3( poisson.xy, (poisson.x + poisson.y) * 0.5 );
sampled = texture(samplerCubemaps[nonuniformEXT(light.indexMap)], D + P ).r ;
@ -92,7 +92,7 @@ float omniShadowMap( const Light light, float def ) {
sampled = texture(samplerTextures[nonuniformEXT(light.indexMap + index)], uv).r ;
} else {
for ( int i = 0; i < samples; ++i ) {
const int idx = int( float(samples) * random(floor(surface.position.world.xyz * 1000.0), i)) % samples;
const int idx = int( float(samples) * interleavedGradientNoise(surface.fragCoord.xy + i)) % samples;
sampled = texture(samplerTextures[nonuniformEXT(light.indexMap + index)], uv + poissonDisk[idx] / 700.0 ).r ;
if ( eyeDepth < sampled - bias ) factor -= 1.0 / samples;
}
@ -118,16 +118,16 @@ float shadowFactor( const Light light, float def ) {
vec4 positionClip = light.projection * light.view * vec4(surface.position.world, 1.0);
positionClip.xyz /= positionClip.w;
if ( positionClip.x < -1 || positionClip.x >= 1 ) return def; //0.0;
if ( positionClip.y < -1 || positionClip.y >= 1 ) return def; //0.0;
if ( positionClip.z <= 0 || positionClip.z >= 1 ) return def; //0.0;
if ( positionClip.x < -1 || positionClip.x >= 1 ) return def;
if ( positionClip.y < -1 || positionClip.y >= 1 ) return def;
if ( positionClip.z <= 0 || positionClip.z >= 1 ) return def;
float factor = 1.0;
// spot light
if ( abs(light.type) == 2 || abs(light.type) == 3 ) {
const float dist = length( positionClip.xy );
if ( dist > 0.5 ) return def; //0.0;
if ( dist > 0.5 ) return def;
// spot light with attenuation
if ( abs(light.type) == 3 ) {
@ -142,7 +142,7 @@ float shadowFactor( const Light light, float def ) {
const float invResolution = 1.0 / textureSize( samplerTextures[nonuniformEXT(light.indexMap)], 0 ).x;
if ( samples < 1 ) return eyeDepth < texture(samplerTextures[nonuniformEXT(light.indexMap)], uv).r - bias ? 0.0 : factor;
for ( int i = 0; i < samples; ++i ) {
const int index = int( float(samples) * random(floor(surface.position.world.xyz * 1000.0), i)) % samples;
const int index = int( float(samples) * interleavedGradientNoise(surface.fragCoord.xy + i)) % samples;
const float lightDepth = texture(samplerTextures[nonuniformEXT(light.indexMap)], uv + poissonDisk[index] * invResolution ).r;
if ( eyeDepth < lightDepth - bias ) factor -= 1.0 / samples;
}

View File

@ -66,7 +66,7 @@ struct Material {
int indexOcclusion;
int indexMetallicRoughness;
int padding1;
int indexCubemap;
int modeCull;
int modeAlpha;
};
@ -118,7 +118,7 @@ struct Instance {
int jointID;
int lightmapID;
uint imageID;
int cubemapID;
uint auxID;
Bounds bounds;

View File

@ -34,8 +34,6 @@ namespace pod {
uf::stl::vector<uf::stl::string> images; //
uf::stl::vector<uf::stl::string> materials; //
uf::stl::vector<uf::stl::string> textures; //
uf::stl::vector<uf::stl::string> texture2Ds; //
uf::stl::vector<uf::stl::string> samplers; //
// Lighting information
@ -104,6 +102,8 @@ namespace pod {
uf::stl::KeyMap<uf::stl::vector<pod::Matrix4f>> joints;
uf::stl::KeyMap<uf::Entity*> entities;
uf::stl::vector<uf::renderer::TextureCube> cubemaps;
uf::stl::vector<uf::renderer::Texture2D> shadow2Ds;
uf::stl::vector<uf::renderer::TextureCube> shadowCubes;

View File

@ -30,7 +30,7 @@ namespace pod {
int32_t indexOcclusion = -1;
int32_t indexMetallicRoughness = -1;
int32_t padding1 = -1;
int32_t indexCubemap = -1;
int32_t modeCull = -1;
int32_t modeAlpha = -1;
};

View File

@ -24,5 +24,9 @@ namespace impl {
ext::json::Value processValue( const uf::stl::string& v );
uf::stl::string readString( std::ifstream& file );
bool parseKeyValue( const uf::stl::string& line, uf::stl::string& key, uf::stl::string& value );
size_t addMaterial( pod::Graph& graph, const uf::stl::string& name );
size_t addMaterial( pod::Graph& graph, const uf::stl::string& name, int32_t& textureID );
inline size_t addMaterial( pod::Graph& graph, const uf::stl::string& name ) {
int32_t textureID = -1;
return impl::addMaterial( graph, name, textureID );
}
}

View File

@ -96,8 +96,8 @@ namespace pod {
alignas(4) int32_t jointID = -1; // offset for skins(?)
alignas(4) int32_t lightmapID = -1; // index for lightmap to use
alignas(4) uint32_t imageID = 0; // unused?
alignas(4) uint32_t auxID = 0; // also the lightmap ID?
alignas(4) int32_t cubemapID = -1; // index for cubemap to use
alignas(4) uint32_t auxID = 0; // index for which grid this belonged to (which is then used to deduce lightmap ID)
// AABB for this primitive
// should be for the specific draw call itself, rather than the mesh(let) entirely

View File

@ -541,6 +541,21 @@ void ext::ExtSceneBehavior::tick( uf::Object& self ) {
storage.shadow2Ds.clear(); storage.shadow2Ds.reserve(metadata.light.max);
storage.shadowCubes.clear(); storage.shadowCubes.reserve(metadata.light.max);
uint32_t texture2dOffset = 0;
uint32_t textureCubeOffset = 0;
for ( size_t i = 0; i < storage.images.keys.size(); ++i ) {
auto& key = storage.images.keys[i];
auto& image = storage.images.map[key].handle;
if ( image.viewType == uf::renderer::enums::Image::VIEW_TYPE_CUBE ) {
textureCubeOffset++;
} else {
texture2dOffset++;
}
}
//UF_MSG_DEBUG("texture2D={}, textureCube={}", texture2dOffset, textureCubeOffset);
// traverse scene graph
for ( auto entity : graph ) {
// ignore this scene, our controller, and anything that isn't actually a light
@ -631,7 +646,7 @@ void ext::ExtSceneBehavior::tick( uf::Object& self ) {
// split cubemap (shouldn't actually get used)
if ( metadata.shadow.typeMap == MODE_SEPARATE_2DS ) {
UF_MSG_WARNING("deprecated feature used: separate Texture2Ds for shadow maps");
boundIndexMap = storage.shadow2Ds.size();
boundIndexMap = texture2dOffset + storage.shadow2Ds.size();
boundTypeMap = MODE_SEPARATE_2DS;
for ( auto& attachment : renderMode.renderTarget.attachments ) {
if (!(attachment.descriptor.usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)) continue;
@ -642,7 +657,7 @@ void ext::ExtSceneBehavior::tick( uf::Object& self ) {
}
// cubemap
} else if ( metadata.shadow.typeMap == MODE_CUBEMAP ) {
boundIndexMap = storage.shadowCubes.size();
boundIndexMap = textureCubeOffset + storage.shadowCubes.size();
boundTypeMap = MODE_CUBEMAP;
for ( auto& attachment : renderMode.renderTarget.attachments ) {
if (!(attachment.descriptor.usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)) continue;
@ -652,7 +667,7 @@ void ext::ExtSceneBehavior::tick( uf::Object& self ) {
}
} else {
// separate 2D maps
boundIndexMap = storage.shadow2Ds.size();
boundIndexMap = texture2dOffset + storage.shadow2Ds.size();
for ( auto& attachment : renderMode.renderTarget.attachments ) {
if ( !(attachment.descriptor.usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)) continue;
if (attachment.descriptor.layout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) continue;
@ -1133,13 +1148,23 @@ void ext::ExtSceneBehavior::bindBuffers( uf::Object& self, uf::renderer::Graphic
texturesCube.reserve( metadata.max.texturesCube );
// bind scene textures
for ( auto& key : storage.images.keys ) textures2D.emplace_back().aliasTexture( storage.images.map[key].handle );
for ( auto& key : storage.images.keys ) {
auto& texture = storage.images.map[key].handle;
if ( texture.viewType != uf::renderer::enums::Image::VIEW_TYPE_2D ) continue;
textures2D.emplace_back().aliasTexture( texture );
}
size_t indexSkybox = 0;
size_t indexNoise = 0;
// bind only if this is the deferred rendermode
if ( shaderPipeline == "deferred" ) {
// bind cubemaps
for ( auto& key : storage.images.keys ) {
auto& texture = storage.images.map[key].handle;
if ( texture.viewType != uf::renderer::enums::Image::VIEW_TYPE_CUBE ) continue;
texturesCube.emplace_back().aliasTexture(texture);
}
// bind shadow maps
for ( auto& texture : storage.shadow2Ds ) textures2D.emplace_back().aliasTexture(texture);
for ( auto& texture : storage.shadowCubes ) texturesCube.emplace_back().aliasTexture(texture);

View File

@ -96,6 +96,7 @@ namespace {
material.indexEmissive = json["iEmissive"].as(material.indexEmissive);
material.indexOcclusion = json["iOcclusion"].as(material.indexOcclusion);
material.indexMetallicRoughness = json["iMetallicRoughness"].as(material.indexMetallicRoughness);
material.indexCubemap = json["iCubemap"].as(material.indexCubemap);
material.modeCull = json["modeCull"].as(material.modeCull);
material.modeAlpha = json["modeAlpha"].as(material.modeAlpha);
@ -168,6 +169,7 @@ namespace {
instance.primitiveID = json["primitiveID"].as( instance.primitiveID );
instance.meshID = json["meshID"].as( instance.meshID );
instance.lightmapID = json["lightmapID"].as( instance.lightmapID );
instance.cubemapID = json["cubemapID"].as( -1 /*instance.cubemapID*/ );
instance.auxID = json["auxID"].as( instance.auxID );
instance.objectID = json["objectID"].as( instance.objectID );
instance.bounds.min = uf::vector::decode( json["bounds"]["min"], instance.bounds.min );

View File

@ -58,6 +58,7 @@ namespace {
if ( material.indexEmissive >= 0 ) json["iEmissive"] = material.indexEmissive;
if ( material.indexOcclusion >= 0 ) json["iOcclusion"] = material.indexOcclusion;
if ( material.indexMetallicRoughness >= 0 ) json["iMetallicRoughness"] = material.indexMetallicRoughness;
if ( material.indexCubemap >= 0 ) json["iCubemap"] = material.indexCubemap;
json["modeCull"] = material.modeCull;
json["modeAlpha"] = material.modeAlpha;
return json;
@ -117,8 +118,9 @@ namespace {
json["primitiveID"] = instance.primitiveID;
json["meshID"] = instance.meshID;
json["lightmapID"] = instance.lightmapID;
json["auxID"] = instance.auxID;
json["cubemapID"] = instance.cubemapID;
json["objectID"] = instance.objectID;
json["auxID"] = instance.auxID;
json["bounds"]["min"] = uf::vector::encode( instance.bounds.min, settings );
json["bounds"]["max"] = uf::vector::encode( instance.bounds.max, settings );

View File

@ -30,6 +30,12 @@
// to-do: fix LOD1+ breaking
namespace {
struct TextureDescriptor {
bool srgb = false;
size_t layers = 1;
size_t references = 0;
};
uf::stl::string keyedID( size_t id ) {
return ::fmt::format("{}", id);
}
@ -55,28 +61,11 @@ namespace {
auto& storage = uf::graph::getStorage( graph );
for ( auto& key : storage.images.keys ) graphic.material.textures.emplace_back().aliasTexture( storage.images.map[key].handle );
// bind scene's voxel texture
#if 0 && UF_USE_VULKAN
if ( uf::renderer::settings::pipelines::vxgi ) {
auto& scene = uf::scene::getCurrentScene();
auto& sceneTextures = scene.getComponent<pod::SceneTextures>();
for ( auto& t : sceneTextures.voxels.id ) graphic.material.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.normal ) graphic.material.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.radiance ) graphic.material.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.output ) graphic.material.textures.emplace_back().aliasTexture(t);
/*
auto& shader = graphic.material.getShader("fragment", uf::renderer::settings::pipelines::names::vxgi);
shader.textures.clear();
for ( auto& t : sceneTextures.voxels.id ) shader.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.normal ) shader.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.radiance ) shader.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.output ) shader.textures.emplace_back().aliasTexture(t);
*/
for ( auto& key : storage.images.keys ) {
auto& texture = storage.images.map[key].handle;
if ( texture.viewType != uf::renderer::enums::Image::VIEW_TYPE_2D ) continue;
graphic.material.textures.emplace_back().aliasTexture( texture );
}
#endif
}
void bindShaders( pod::Graph& graph, uf::Object& entity, uf::Mesh& mesh ) {
@ -91,8 +80,10 @@ namespace {
uf::stl::string root = uf::io::directory( graph.name );
size_t texture2Ds = 0;
size_t texture3Ds = 0;
size_t textureCubes = 0;
for ( auto& texture : graphic.material.textures ) {
if ( texture.width > 1 && texture.height > 1 && texture.depth == 1 && texture.layers == 1 ) ++texture2Ds;
else if ( texture.width > 1 && texture.height > 1 && texture.depth == 1 && texture.layers == 6 ) ++textureCubes;
else if ( texture.width > 1 && texture.height > 1 && texture.depth > 1 && texture.layers == 1 ) ++texture3Ds;
}
@ -814,8 +805,8 @@ void uf::graph::process( pod::Graph& graph ) {
} );
}
//
uf::stl::unordered_map<uf::stl::string, bool> isSrgb;
// stores temporary metadata for textures (that can be deduced at runtime)
uf::stl::unordered_map<uf::stl::string, TextureDescriptor> textureDescriptors;
// process lightmap
UF_DEBUG_TIMER_MULTITRACE("Parsing lightmaps");
@ -823,7 +814,7 @@ void uf::graph::process( pod::Graph& graph ) {
if ( storage.textures.map.count("lightmap_atlas") > 0 ) {
graphMetadataJson["lights"]["lightmap"] = true;
graphMetadataJson["baking"]["enabled"] = false;
isSrgb["lightmap_atlas"] = false;
textureDescriptors["lightmap_atlas"].srgb = false;
} else {
constexpr const char* UF_GRAPH_DEFAULT_LIGHTMAP = "./lightmap.%i.png";
uf::stl::unordered_map<size_t, uf::stl::string> filenames;
@ -899,7 +890,7 @@ void uf::graph::process( pod::Graph& graph ) {
graphMetadataJson["lights"]["lightmaps"][i] = f;
graphMetadataJson["baking"]["enabled"] = false;
isSrgb[f] = false;
textureDescriptors[f].srgb = false;
}
}
@ -912,16 +903,27 @@ void uf::graph::process( pod::Graph& graph ) {
}
}
// process cubemaps
ext::json::forEach( graph.metadata["cubemaps"], [&]( const ext::json::Value& value ) {
auto name = value.as<uf::stl::string>();
textureDescriptors[name].layers = 6;
});
// figure out what texture is what exactly
UF_DEBUG_TIMER_MULTITRACE("Determining format of textures");
for ( auto& key : graph.materials ) {
auto& material = storage.materials[key];
auto ID = material.indexAlbedo;
if ( !(0 <= ID && ID < graph.textures.size()) ) continue;
if ( 0 <= material.indexCubemap && material.indexCubemap < graph.textures.size() ) {
auto texName = graph.textures[material.indexCubemap];
textureDescriptors[texName].layers = 6;
textureDescriptors[texName].srgb = true;
}
auto texName = graph.textures[ID];
isSrgb[texName] = true;
if ( (0 <= material.indexAlbedo && material.indexAlbedo < graph.textures.size() ) ) {
auto texName = graph.textures[material.indexAlbedo];
textureDescriptors[texName].srgb = true;
}
}
UF_DEBUG_TIMER_MULTITRACE("Processing images...");
@ -949,7 +951,10 @@ void uf::graph::process( pod::Graph& graph ) {
texture.sampler.descriptor.filter.min = filter;
texture.sampler.descriptor.filter.mag = filter;
texture.srgb = isSrgb[key];
texture.layers = textureDescriptors[key].layers;
texture.srgb = textureDescriptors[key].srgb;
image.size.y /= texture.layers;
texture.loadFromImage( image );
#if UF_ENV_DREAMCAST
@ -1072,6 +1077,25 @@ void uf::graph::process( pod::Graph& graph ) {
// remap textures->images IDs
UF_DEBUG_TIMER_MULTITRACE("Remapping texture -> image IDs");
// separate texture2Ds and textureCubes from images
uf::stl::vector<int32_t> gpuIndex2D(storage.images.keys.size(), -1);
uf::stl::vector<int32_t> gpuIndexCube(storage.images.keys.size(), -1);
int32_t count2D = 0;
int32_t countCube = 0;
for ( size_t i = 0; i < storage.images.keys.size(); ++i ) {
auto& key = storage.images.keys[i];
auto& image = storage.images.map[key].handle;
if ( image.viewType == uf::renderer::enums::Image::VIEW_TYPE_CUBE ) {
gpuIndexCube[i] = countCube++;
} else {
gpuIndex2D[i] = count2D++;
}
}
UF_MSG_DEBUG("texture2Ds={}, textureCubes={}", count2D, countCube);
for ( auto& name : graph.textures ) {
auto& texture = storage.textures[name];
auto& keys = storage.images.keys;
@ -1080,7 +1104,15 @@ void uf::graph::process( pod::Graph& graph ) {
if ( !(0 <= texture.index && texture.index < graph.images.size()) ) continue;
auto& needle = graph.images[texture.index];
texture.index = indices[needle];
int32_t storageImageIndex = indices[needle];
auto& image = storage.images.map[keys[storageImageIndex]].handle;
if ( image.viewType == uf::renderer::enums::Image::VIEW_TYPE_CUBE ) {
texture.index = gpuIndexCube[storageImageIndex];
} else {
texture.index = gpuIndex2D[storageImageIndex];
}
UF_MSG_DEBUG("name={}, index={}, layers={}, type={}", name, texture.index, image.layers, image.viewType == uf::renderer::enums::Image::VIEW_TYPE_CUBE ? "cube" : "2D" );
}
// remap materials->texture IDs
@ -1089,7 +1121,7 @@ void uf::graph::process( pod::Graph& graph ) {
auto& material = storage.materials[name];
auto& keys = storage.textures.keys;
auto& indices = storage.textures.indices;
int32_t* IDs[] = { &material.indexAlbedo, &material.indexNormal, &material.indexEmissive, &material.indexOcclusion, &material.indexMetallicRoughness };
int32_t* IDs[] = { &material.indexAlbedo, &material.indexNormal, &material.indexEmissive, &material.indexOcclusion, &material.indexMetallicRoughness, &material.indexCubemap };
for ( auto* pointer : IDs ) {
auto& ID = *pointer;
if ( !(0 <= ID && ID < graph.textures.size()) ) continue;
@ -1122,6 +1154,15 @@ void uf::graph::process( pod::Graph& graph ) {
auto& needle = graph.textures[instance.lightmapID];
instance.lightmapID = indices[needle];
}
if ( 0 <= instance.cubemapID && instance.cubemapID < graph.textures.size() ) {
auto& keys = storage.textures.keys;
auto& indices = storage.textures.indices;
if ( !(0 <= instance.cubemapID && instance.cubemapID < graph.textures.size()) ) continue;
auto& needle = graph.textures[instance.cubemapID];
instance.cubemapID = indices[needle];
}
// remap a skinID as an actual jointID
if ( 0 <= instance.jointID && instance.jointID < graph.skins.size() ) {
@ -1148,6 +1189,13 @@ void uf::graph::process( pod::Graph& graph ) {
auto& needle = graph.textures[instance.lightmapID];
instance.lightmapID = indices[needle];
}
if ( 0 <= instance.cubemapID && instance.cubemapID < graph.textures.size() ) {
auto& keys = storage.textures.keys;
auto& indices = storage.textures.indices;
auto& needle = graph.textures[instance.cubemapID];
instance.cubemapID = indices[needle];
}
if ( 0 <= instance.jointID && instance.jointID < graph.skins.size() ) {
auto& skinName = graph.skins[instance.jointID];
instance.jointID = 0;
@ -1898,6 +1946,7 @@ void uf::graph::destroy( pod::Graph::Storage& storage, bool soft ) {
}
for ( auto& t : storage.shadow2Ds ) t.destroy();
for ( auto& t : storage.shadowCubes ) t.destroy();
for ( auto& t : storage.cubemaps ) t.destroy();
for ( auto pair : storage.atlases.map ) pair.second.clear();
for ( auto pair : storage.meshes.map ) pair.second.destroy();
@ -1916,6 +1965,7 @@ void uf::graph::destroy( pod::Graph::Storage& storage, bool soft ) {
storage.entities.clear();
storage.shadow2Ds.clear();
storage.shadowCubes.clear();
storage.cubemaps.clear();
// cleanup storage buffers
if ( !soft ) {
@ -2195,13 +2245,13 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
// lightmaps are not sRGB, while textures (usually) are
#define INCREMENT_TEXTURE_REFCOUNT( ID, isSRGB ) if ( 0 <= ID && ID < graph.textures.size() ) {\
auto& key = graph.textures[ID];\
textureReferences[key] += visible ? 1 : 0;\
isSrgb[key] = isSRGB;\
textureDescriptors[key].srgb = isSRGB;\
textureDescriptors[key].references += visible ? 1 : 0;\
textureDescriptors[key].layers = 1;\
}
uf::stl::unordered_map<uf::stl::string, bool> isSrgb; // cringe
uf::stl::unordered_map<uf::stl::string, size_t> textureReferences;
// determine which textures are in use or not
uf::stl::unordered_map<uf::stl::string, TextureDescriptor> textureDescriptors;
for ( size_t drawID = 0; drawID < primitives.size(); ++drawID ) {
auto& primitive = primitives[drawID];
auto& instance = primitive.instance;
@ -2223,10 +2273,10 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
}
// iterate through our ref counts
for ( auto& [ key, count ] : textureReferences ) {
for ( auto& [ key, descriptor ] : textureDescriptors ) {
auto& image = storage.images[key].data;
auto& texture = storage.images[key].handle;
bool visible = count > 0;
bool visible = descriptor.references > 0;
if ( visible && (!texture.generated() || texture.aliased) ) {
// load image
@ -2257,7 +2307,8 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
texture.sampler.descriptor.filter.min = filter;
texture.sampler.descriptor.filter.mag = filter;
texture.srgb = isSrgb[key];
texture.layers = descriptor.layers;
texture.srgb = descriptor.srgb;
texture.loadFromImage( image );
#if UF_ENV_DREAMCAST

View File

@ -165,6 +165,11 @@ namespace impl {
float alpha;
};
struct BspCubemap {
pod::Vector3i origin;
int32_t size;
};
struct BspTexInfo {
pod::Vector4f textureVecs[2];
pod::Vector4f lightmapVecs[2];
@ -214,7 +219,7 @@ namespace impl {
uf::stl::vector<impl::BspDispVert> dispverts;
// disptris
uf::stl::vector<uint8_t> pakfile;
// cubemaps
uf::stl::vector<impl::BspCubemap> cubemaps;
// overlay
uf::stl::vector<uint8_t> lighting;
// ambient lighting
@ -225,6 +230,7 @@ namespace impl {
uf::stl::vector<int32_t> modelToMesh;
uf::stl::vector<int32_t> texdataToMaterial;
uf::stl::vector<int32_t> cubemapIDs;
pod::Atlas lightmapAtlas;
};
@ -252,6 +258,28 @@ namespace impl {
return io;
}
int32_t findClosestCubemap( const impl::BspContext& context, const pod::Vector3f& position ) {
if ( context.cubemapIDs.empty() ) return -1;
int32_t bestID = context.cubemapIDs[0];
float minDistance = std::numeric_limits<float>::max();
for ( size_t i = 0; i < context.cubemaps.size(); ++i ) {
pod::Vector3f cubePos = impl::convertPos(pod::Vector3f{
context.cubemaps[i].origin.x,
context.cubemaps[i].origin.y,
context.cubemaps[i].origin.z
});
float distSq = uf::vector::distanceSquared( position, cubePos );
if ( distSq < minDistance ) {
minDistance = distSq;
bestID = context.cubemapIDs[i];
}
}
return bestID;
};
pod::Atlas::hash_t faceHash( size_t i ) {
return ::fmt::format("face_{}", i);
}
@ -436,33 +464,46 @@ namespace impl {
};
template<typename T>
uf::stl::vector<T> extractLump( const uf::stl::vector<uint8_t>& buffer, const impl::BspLump& lump ) {
uf::stl::vector<T> data;
if ( lump.length == 0 || lump.offset >= buffer.size() ) return data;
void extractLump( const uf::stl::vector<uint8_t>& buffer, const impl::BspLump& lump, uf::stl::vector<T>& data ) {
if ( lump.length == 0 || lump.offset >= buffer.size() ) return;
size_t count = lump.length / sizeof(T);
data.resize(count);
std::copy(buffer.data() + lump.offset, buffer.data() + lump.offset + lump.length, (uint8_t*)(data.data()) );
}
template<typename T>
uf::stl::vector<T> extractLump( const uf::stl::vector<uint8_t>& buffer, const impl::BspLump& lump ) {
uf::stl::vector<T> data;
impl::extractLump<T>( buffer, lump, data );
return data;
}
template<>
uf::stl::vector<impl::BspGameLump> extractLump( const uf::stl::vector<uint8_t>& buffer, const impl::BspLump& lump ) {
uf::stl::vector<impl::BspGameLump> data;
if ( lump.length == 0 || lump.offset >= buffer.size() ) return data;
void extractLump( const uf::stl::vector<uint8_t>& buffer, const impl::BspLump& lump, uf::stl::vector<impl::BspGameLump>& data ) {
if ( lump.length == 0 || lump.offset >= buffer.size() ) return;
const uint8_t* glData = buffer.data() + lump.offset;
int32_t lumpCount = *(const int32_t*)glData;
data.resize(lumpCount);
std::copy(glData + 4, glData + 4 + (lumpCount * sizeof(impl::BspGameLump)), (uint8_t*)(data.data()));
}
template<>
uf::stl::vector<impl::BspGameLump> extractLump( const uf::stl::vector<uint8_t>& buffer, const impl::BspLump& lump ) {
uf::stl::vector<impl::BspGameLump> data;
impl::extractLump( buffer, lump, data );
return data;
}
uf::stl::string extractLumpString( const uf::stl::vector<uint8_t>& buffer, const impl::BspLump& lump ) {
void extractLumpString( const uf::stl::vector<uint8_t>& buffer, const impl::BspLump& lump, uf::stl::string& str ) {
auto data = impl::extractLump<char>( buffer, lump );
return uf::stl::string( data.data(), data.size() );
str = uf::stl::string( data.data(), data.size() );
}
uf::stl::string extractLumpString( const uf::stl::vector<uint8_t>& buffer, const impl::BspLump& lump ) {
uf::stl::string str;
impl::extractLumpString( buffer, lump, str );
return str;
}
void processNodes( pod::Graph& graph, const impl::BspContext& context, float scale = impl::sourceToMeters ) {
@ -638,21 +679,22 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co
graph.root.index = -1;
impl::BspContext context;
context.vertices = impl::extractLump<impl::BspVertex>(buffer, header->lumps[impl::BspLump::LUMP_VERTICES]);
context.edges = impl::extractLump<impl::BspEdge>(buffer, header->lumps[impl::BspLump::LUMP_EDGES]);
context.surfedges = impl::extractLump<int32_t>(buffer, header->lumps[impl::BspLump::LUMP_SURFEDGES]);
context.faces = impl::extractLump<impl::BspFace>(buffer, header->lumps[impl::BspLump::LUMP_FACES]);
context.texinfos = impl::extractLump<impl::BspTexInfo>(buffer, header->lumps[impl::BspLump::LUMP_TEXINFO]);
context.texdatas = impl::extractLump<impl::BspTexData>(buffer, header->lumps[impl::BspLump::LUMP_TEXDATA]);
context.stringTable = impl::extractLump<int32_t>(buffer, header->lumps[impl::BspLump::LUMP_TEXDATA_STRING_TABLE]);
context.stringData = impl::extractLumpString(buffer, header->lumps[impl::BspLump::LUMP_TEXDATA_STRING_DATA]);
context.models = impl::extractLump<impl::BspModel>(buffer, header->lumps[impl::BspLump::LUMP_MODELS]);
context.entities = impl::extractLump<int8_t>(buffer, header->lumps[impl::BspLump::LUMP_ENTITIES]);
context.gameLumps = impl::extractLump<impl::BspGameLump>(buffer, header->lumps[impl::BspLump::LUMP_GAME_LUMP]);
context.dispinfos = impl::extractLump<impl::BspDispInfo>(buffer, header->lumps[impl::BspLump::LUMP_DISPINFO]);
context.dispverts = impl::extractLump<impl::BspDispVert>(buffer, header->lumps[impl::BspLump::LUMP_DISP_VERTS]);
context.pakfile = impl::extractLump<uint8_t>(buffer, header->lumps[impl::BspLump::LUMP_PAKFILE]);
context.lighting = impl::extractLump<uint8_t>(buffer, header->lumps[impl::BspLump::LUMP_LIGHTING]);
impl::extractLump<impl::BspVertex>(buffer, header->lumps[impl::BspLump::LUMP_VERTICES], context.vertices);
impl::extractLump<impl::BspEdge>(buffer, header->lumps[impl::BspLump::LUMP_EDGES], context.edges);
impl::extractLump<int32_t>(buffer, header->lumps[impl::BspLump::LUMP_SURFEDGES], context.surfedges);
impl::extractLump<impl::BspFace>(buffer, header->lumps[impl::BspLump::LUMP_FACES], context.faces);
impl::extractLump<impl::BspTexInfo>(buffer, header->lumps[impl::BspLump::LUMP_TEXINFO], context.texinfos);
impl::extractLump<impl::BspTexData>(buffer, header->lumps[impl::BspLump::LUMP_TEXDATA], context.texdatas);
impl::extractLump<int32_t>(buffer, header->lumps[impl::BspLump::LUMP_TEXDATA_STRING_TABLE], context.stringTable);
impl::extractLumpString(buffer, header->lumps[impl::BspLump::LUMP_TEXDATA_STRING_DATA], context.stringData);
impl::extractLump<impl::BspModel>(buffer, header->lumps[impl::BspLump::LUMP_MODELS], context.models);
impl::extractLump<int8_t>(buffer, header->lumps[impl::BspLump::LUMP_ENTITIES], context.entities);
impl::extractLump<impl::BspGameLump>(buffer, header->lumps[impl::BspLump::LUMP_GAME_LUMP], context.gameLumps);
impl::extractLump<impl::BspDispInfo>(buffer, header->lumps[impl::BspLump::LUMP_DISPINFO], context.dispinfos);
impl::extractLump<impl::BspDispVert>(buffer, header->lumps[impl::BspLump::LUMP_DISP_VERTS], context.dispverts);
impl::extractLump<uint8_t>(buffer, header->lumps[impl::BspLump::LUMP_PAKFILE], context.pakfile);
impl::extractLump<impl::BspCubemap>(buffer, header->lumps[impl::BspLump::LUMP_CUBEMAPS], context.cubemaps);
impl::extractLump<uint8_t>(buffer, header->lumps[impl::BspLump::LUMP_LIGHTING], context.lighting);
context.modelToMesh.assign( context.models.size(), -1 );
context.texdataToMaterial.assign( context.texdatas.size(), -1 );
@ -660,6 +702,31 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co
// mount pakfile
size_t pakfileMount = uf::vfs::mount( ext::zlib::createZipMount(::fmt::format("pakfile://{}", filename), context.pakfile, 1000 ) );
// deduce mapname
uf::stl::string mapName = filename; {
auto slashPos = mapName.find_last_of("/\\");
if (slashPos != uf::stl::string::npos) mapName = mapName.substr(slashPos + 1);
auto dotPos = mapName.find_last_of('.');
if (dotPos != uf::stl::string::npos) mapName = mapName.substr(0, dotPos);
}
// load baked cubemaps
for ( const auto& cube : context.cubemaps ) {
auto matName = ::fmt::format("maps/{}/c{}_{}_{}", mapName, cube.origin.x, cube.origin.y, cube.origin.z);
auto vtfPath = ::fmt::format("materials/{}.vtf", matName);
graph.metadata["cubemaps"].emplace_back( matName );
int32_t textureID = -1;
impl::addMaterial( graph, matName, textureID );
auto& image = storage.images[matName].data;
auto& texture = storage.images[matName].handle;
if ( ext::valve::loadVtf( image, vtfPath ) ) {
UF_MSG_DEBUG("Loaded cubemap={}", vtfPath);
context.cubemapIDs.emplace_back( storage.materials[matName].indexCubemap = textureID );
}
}
// read materials
for ( int32_t texDataID = 0; texDataID < context.texdatas.size(); ++texDataID ) {
const auto& data = context.texdatas[texDataID];
@ -673,7 +740,9 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co
}
}
context.texdataToMaterial[texDataID] = impl::addMaterial( graph, matName );
int32_t textureID = -1;
context.texdataToMaterial[texDataID] = impl::addMaterial( graph, matName, textureID );
storage.materials[matName].indexAlbedo = textureID;
}
// read lightmaps
@ -726,6 +795,7 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co
if ( texDataID < 0 || texDataID >= context.texdatas.size() ) continue;
size_t materialID = context.texdataToMaterial[texDataID];
auto& matName = graph.materials[materialID];
// read brush
auto& meshlet = meshlets[materialID];
@ -734,6 +804,14 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co
if ( 0 <= face.lightofs ) {
meshlet.primitive.instance.lightmapID = atlasTextureID;
}
if ( !context.cubemapIDs.empty() ) {
int32_t pivotSurfEdge = context.surfedges[face.firstedge];
uint16_t pivotVertID = pivotSurfEdge >= 0 ? context.edges[pivotSurfEdge].x : context.edges[-pivotSurfEdge].y;
pod::Vector3f p0 = impl::convertPos( context.vertices[pivotVertID] );
meshlet.primitive.instance.cubemapID = impl::findClosestCubemap( context, p0 );
}
if ( face.dispinfo != -1 ) {
impl::buildDisplacement( context, meshlet, faceID );
@ -923,32 +1001,96 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co
auto vtfPath = ::fmt::format("materials/{}.vtf", matName);
auto& image = storage.images[matName].data;
auto& material = storage.materials[matName];
if ( !image.getPixels().empty() ) continue;
if ( !ext::valve::loadVmt( vmt, vmtPath ) ) goto PEETAH;
material.factorMetallic = vmt["$metalness"].as<float>(0.0f);
material.factorRoughness = vmt["$roughness"].as<float>(1.0f);
if ( vmt["$envmap"].as<uf::stl::string>() != "" ) material.factorRoughness = 0.3f;
if ( vmt["$phong"].as<int>(0) == 1 ) material.factorRoughness = std::min(material.factorRoughness, 0.5f);
if ( vmt["$phong"].as<int>(0) == 1 ) {
material.factorRoughness = std::min(material.factorRoughness, 0.5f);
}
if ( vmt["$translucent"].as<int>(0) == 1 ) {
material.modeAlpha = pod::Material::AlphaMode::BLEND;
} else if ( vmt["$alphatest"].as<int>(0) == 1 ) {
}
if ( vmt["$alphatest"].as<int>(0) == 1 ) {
material.modeAlpha = pod::Material::AlphaMode::MASK;
material.factorAlphaCutoff = vmt["$alphatestreference"].as<float>(0.5f);
}
if ( vmt["$nocull"].as<int>(0) == 1 ) material.modeCull = pod::Material::CullMode::NONE;
if ( vmt["$nocull"].as<int>(0) == 1 ) {
material.modeCull = pod::Material::CullMode::NONE;
}
if ( vmt["$selfillum"].as<int>(0) == 1 ) {
material.colorEmissive = { 1.0f, 1.0f, 1.0f, 1.0f };
material.modeAlpha = pod::Material::AlphaMode::EMISSIVE;
}
if ( !vmt["$basetexture"].is<uf::stl::string>() ) goto PEETAH;
// cubemap
if ( vmt["$envmap"].as<uf::stl::string>() != "" ) {
auto matName = uf::string::lowercase(vmt["$envmap"].as<uf::stl::string>());
auto vtfPath = ::fmt::format("materials/{}.vtf", matName);
// retrieve
if ( matName == "env_cubemap" ) {
// is handled on an instance level
} else if ( storage.images.map.count(matName) > 0 ) {
material.indexCubemap = storage.textures[matName].index;
} else {
int32_t textureID = -1;
impl::addMaterial( graph, matName, textureID );
graph.metadata["cubemaps"].emplace_back( matName );
auto& image = storage.images[matName].data;
auto& texture = storage.images[matName].handle;
texture.viewType = uf::renderer::enums::Image::VIEW_TYPE_CUBE;
if ( ext::valve::loadVtf( image, vtfPath ) ) {
UF_MSG_DEBUG("Loaded cubemap={}", matName);
material.indexCubemap = textureID;
}
}
}
// normal map
if ( vmt["$bumpmap"].is<uf::stl::string>() ) {
auto matName = uf::string::lowercase(vmt["$bumpmap"].as<uf::stl::string>());
auto vtfPath = ::fmt::format("materials/{}.vtf", matName);
// retrieve
if ( storage.images.map.count(matName) > 0 ) {
material.indexNormal = storage.textures[matName].index;
} else {
int32_t textureID = -1;
impl::addMaterial( graph, matName, textureID );
auto& image = storage.images[matName].data;
if ( ext::valve::loadVtf( image, vtfPath ) ) material.indexNormal = textureID;
}
}
// metallic/roughness/occlusion map
if ( vmt["$mrao"].is<uf::stl::string>() ) {
auto matName = uf::string::lowercase(vmt["$mrao"].as<uf::stl::string>());
auto vtfPath = ::fmt::format("materials/{}.vtf", matName);
// retrieve
if ( storage.images.map.count(matName) > 0 ) {
material.indexMetallicRoughness = storage.textures[matName].index;
} else {
int32_t textureID = -1;
impl::addMaterial( graph, matName, textureID );
auto& image = storage.images[matName].data;
if ( ext::valve::loadVtf( image, vtfPath ) ) material.indexMetallicRoughness = textureID;
}
}
// albedo map
if ( vmt["$basetexture"].is<uf::stl::string>() ) {
vtfPath = ::fmt::format("materials/{}.vtf", vmt["$basetexture"].as<uf::stl::string>());
if ( ext::valve::loadVtf( image, vtfPath ) ) {
continue;
}
}
vtfPath = ::fmt::format("materials/{}.vtf", vmt["$basetexture"].as<uf::stl::string>());
if ( !ext::valve::loadVtf( image, vtfPath ) ) goto PEETAH;
continue;
PEETAH:
image.loadFromBuffer( missing_pixels, { 2, 2 }, 8, 4 );
}

View File

@ -18,26 +18,23 @@ uf::stl::string impl::readString( std::ifstream& file ) {
return str;
}
size_t impl::addMaterial( pod::Graph& graph, const uf::stl::string& name ) {
size_t impl::addMaterial( pod::Graph& graph, const uf::stl::string& name, int32_t& textureID ) {
auto& storage = uf::graph::getStorage( graph );
textureID = graph.textures.size();
size_t imageID = graph.images.size();
auto imgKeyName = graph.images.emplace_back(name);
auto& image = storage.images[imgKeyName].data;
size_t textureID = graph.textures.size();
auto texKeyName = graph.textures.emplace_back(name);
storage.textures[texKeyName].index = imageID;
size_t materialID = graph.materials.size();
auto matKeyName = graph.materials.emplace_back(name);
auto& material = storage.materials[matKeyName];
material.indexAlbedo = textureID;
graph.images.emplace_back(name);
graph.textures.emplace_back(name);
graph.materials.emplace_back(name);
storage.textures[name].index = imageID;
auto& material = storage.materials[name];
material.colorBase = {1.0f, 1.0f, 1.0f, 1.0f};
material.factorMetallic = 0.0f;
material.factorRoughness = 1.0f;
material.factorOcclusion = 1.0f;
return materialID;
}

View File

@ -412,11 +412,12 @@ bool ext::valve::loadMdl( pod::Graph& graph, const uf::stl::string& filename ) {
size_t materialID = 0;
uf::stl::string matName = "missing_texture";
if ( mdlMesh.material < materials.size() ) matName = materials[mdlMesh.material];
auto it = std::find(graph.materials.begin(), graph.materials.end(), matName);
if ( it == graph.materials.end() ) {
materialID = impl::addMaterial( graph, matName );
if ( auto it = std::find(graph.materials.begin(), graph.materials.end(), matName); it != graph.materials.end() ) {
materialID = (int32_t)(std::distance(graph.materials.begin(), it));
} else {
materialID = (int32_t)std::distance(graph.materials.begin(), it);
int32_t textureID = -1;
materialID = impl::addMaterial( graph, matName, textureID );
storage.materials[matName].indexAlbedo = textureID;
}
meshlet.primitive.instance.materialID = materialID;

View File

@ -11,6 +11,8 @@ namespace impl {
constexpr uint32_t IMAGE_FORMAT_DXT1 = 13;
constexpr uint32_t IMAGE_FORMAT_DXT5 = 15;
constexpr uint32_t TEXTUREFLAGS_ENVMAP = 0x00002000;
#pragma pack(push, 1)
struct VTFHeader {
char signature[4]; // "VTF\0"
@ -152,56 +154,134 @@ bool ext::valve::loadVtf( pod::Image& image, const uf::stl::string& filename ) {
const impl::VTFHeader* header = (const impl::VTFHeader*)(buffer.data());
if ( strncmp(header->signature, "VTF", 3) != 0 ) return false;
bool isCubemap = (header->flags & impl::TEXTUREFLAGS_ENVMAP) != 0;
int numFrames = std::max<int>(1, header->frames);
size_t singleFaceSize = 0;
for ( int mip = header->mipmapCount - 1; mip >= 0; --mip ) {
int mipWidth = std::max(1, header->width >> mip);
int mipHeight = std::max(1, header->height >> mip);
int blocksX = (mipWidth + 3) / 4;
int blocksY = (mipHeight + 3) / 4;
if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT1 ) singleFaceSize += blocksX * blocksY * 8;
else if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT5 ) singleFaceSize += blocksX * blocksY * 16;
else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGRA8888 ) singleFaceSize += mipWidth * mipHeight * 4;
}
size_t offset = header->headerSize;
if ( header->lowResImageFormat != 0xFFFFFFFF ) {
offset += std::max<size_t>(1, (header->lowResImageWidth * header->lowResImageHeight) / 2);
}
for ( int mip = header->mipmapCount - 1; mip > 0; --mip ) {
int mipWidth = std::max(1, header->width >> mip);
int mipHeight = std::max(1, header->height >> mip);
size_t remainingBytes = buffer.size() - offset;
size_t bytesPerFace = singleFaceSize * numFrames;
int actualFaces = bytesPerFace > 0 ? (remainingBytes / bytesPerFace) : 1;
if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT1 ) {
offset += std::max(8, (mipWidth / 4) * (mipHeight / 4) * 8);
} else if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT5 ) {
offset += std::max(16, (mipWidth / 4) * (mipHeight / 4) * 16);
} else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGRA8888 ) {
offset += mipWidth * mipHeight * 4;
if ( actualFaces == 6 || actualFaces == 7 ) {
isCubemap = true;
}
int numFaces = 1;
if ( isCubemap ) {
if ( actualFaces >= 6 ) {
numFaces = actualFaces;
} else {
isCubemap = false;
numFaces = 1;
}
}
const uint8_t* data = buffer.data() + offset;
image.size = { header->width, header->height };
for ( int mip = header->mipmapCount - 1; mip > 0; --mip ) {
int mipWidth = std::max(1, header->width >> mip);
int mipHeight = std::max(1, header->height >> mip);
int blocksX = (mipWidth + 3) / 4;
int blocksY = (mipHeight + 3) / 4;
size_t mipSize = 0;
if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT1 ) mipSize = blocksX * blocksY * 8;
else if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT5 ) mipSize = blocksX * blocksY * 16;
else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGRA8888 ) mipSize = mipWidth * mipHeight * 4;
offset += mipSize * numFrames * numFaces;
}
int outputFaces = isCubemap ? 6 : 1;
image.size = { header->width, header->height * outputFaces };
image.channels = 4;
image.bpp = 8 * 4;
image.pixels.resize( header->width * header->height * 4 );
image.pixels.resize( header->width * header->height * 4 * outputFaces );
int dataSize = 0;
if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT1 ) {
int blockCountX = (header->width + 3) / 4;
int blockCountY = (header->height + 3) / 4;
for ( int by = 0; by < blockCountY; ++by ) {
for ( int bx = 0; bx < blockCountX; ++bx ) {
impl::decompressDXT1Block(data + (by * blockCountX + bx) * 8, image.pixels.data(), bx * 4, by * 4, header->width, header->height);
int faceSizePixels = header->width * header->height;
int blocksX = (header->width + 3) / 4;
int blocksY = (header->height + 3) / 4;
const int faceMap[6] = { 4, 5, 0, 1, 2, 3 };
const int faceModes[6] = { 2, 0, 6, 1, 1, 3 };
for ( int face = 0; face < outputFaces; ++face ) {
const uint8_t* data = buffer.data() + offset;
int mappedFace = (isCubemap && face < 6) ? faceMap[face] : face;
uint8_t* outPixels = image.pixels.data() + (mappedFace * faceSizePixels * 4);
if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT1 ) {
for ( int by = 0; by < blocksY; ++by ) {
for ( int bx = 0; bx < blocksX; ++bx ) {
impl::decompressDXT1Block(data + (by * blocksX + bx) * 8, outPixels, bx * 4, by * 4, header->width, header->height);
}
}
offset += blocksX * blocksY * 8;
} else if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT5 ) {
for ( int by = 0; by < blocksY; ++by) {
for ( int bx = 0; bx < blocksX; ++bx) {
impl::decompressDXT5Block(data + (by * blocksX + bx) * 16, outPixels, bx * 4, by * 4, header->width, header->height);
}
}
offset += blocksX * blocksY * 16;
} else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGRA8888 ) {
for ( auto i = 0; i < faceSizePixels; ++i ) {
outPixels[i * 4 + 0] = data[i * 4 + 2];
outPixels[i * 4 + 1] = data[i * 4 + 1];
outPixels[i * 4 + 2] = data[i * 4 + 0];
outPixels[i * 4 + 3] = data[i * 4 + 3];
}
offset += faceSizePixels * 4;
}
if ( isCubemap ) {
int mode = faceModes[mappedFace];
if ( mode == 0 ) continue;
uf::stl::vector<uint8_t> temp(faceSizePixels * 4);
memcpy(temp.data(), outPixels, faceSizePixels * 4);
int size = header->width;
int max = size - 1;
for ( int y = 0; y < size; ++y ) {
for ( int x = 0; x < size; ++x ) {
int srcX = x;
int srcY = y;
switch ( mode ) {
case 1: srcX = y; srcY = max - x; break; // 90 CW
case 2: srcX = max - x; srcY = max - y; break; // 180
case 3: srcX = max - y; srcY = x; break; // 90 CCW
case 4: srcX = max - x; srcY = y; break; // Flip Horizontal
case 5: srcX = x; srcY = max - y; break; // Flip Vertical
case 6: srcX = y; srcY = x; break; // Transpose (Diagonal Flip)
case 7: srcX = max - y; srcY = max - x; break; // Anti-Transpose
}
int srcIdx = (srcY * size + srcX) * 4;
int dstIdx = (y * size + x) * 4;
outPixels[dstIdx + 0] = temp[srcIdx + 0];
outPixels[dstIdx + 1] = temp[srcIdx + 1];
outPixels[dstIdx + 2] = temp[srcIdx + 2];
outPixels[dstIdx + 3] = temp[srcIdx + 3];
}
}
}
} else if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT5 ) {
int blockCountX = (header->width + 3) / 4;
int blockCountY = (header->height + 3) / 4;
for ( int by = 0; by < blockCountY; ++by) {
for ( int bx = 0; bx < blockCountX; ++bx) {
impl::decompressDXT5Block(data + (by * blockCountX + bx) * 16, image.pixels.data(), bx * 4, by * 4, header->width, header->height);
}
}
} else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGRA8888 ) {
for ( auto i = 0; i < header->width * header->height; ++i ) {
image.pixels[i * 4 + 0] = data[i * 4 + 2];
image.pixels[i * 4 + 1] = data[i * 4 + 1];
image.pixels[i * 4 + 2] = data[i * 4 + 0];
image.pixels[i * 4 + 3] = data[i * 4 + 3];
}
} else {
UF_MSG_WARNING("Unsupported VTF format: {}", header->highResImageFormat);
}
return true;

View File

@ -474,8 +474,8 @@ void ext::vulkan::Texture::loadFromImage(
format,
image.getDimensions().x,
image.getDimensions().y,
1,
1,
MAX(this->depth, 1),
MAX(this->layers, 1),
device,
usage,
layout,