diff --git a/bin/data/config.json b/bin/data/config.json index 4dad3707..6f279263 100644 --- a/bin/data/config.json +++ b/bin/data/config.json @@ -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 diff --git a/bin/data/entities/light.json b/bin/data/entities/light.json index e7218503..c2b03181 100644 --- a/bin/data/entities/light.json +++ b/bin/data/entities/light.json @@ -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], diff --git a/bin/data/scenes/sourceengine/cs_office.json b/bin/data/scenes/sourceengine/cs_office.json index 60581037..398df6e8 100644 --- a/bin/data/scenes/sourceengine/cs_office.json +++ b/bin/data/scenes/sourceengine/cs_office.json @@ -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": { - - } - } - } + ] } \ No newline at end of file diff --git a/bin/data/scenes/sourceengine/mds_mcdonalds.json b/bin/data/scenes/sourceengine/mds_mcdonalds.json index 0968a1ce..c5759688 100644 --- a/bin/data/scenes/sourceengine/mds_mcdonalds.json +++ b/bin/data/scenes/sourceengine/mds_mcdonalds.json @@ -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" } + ] } \ No newline at end of file diff --git a/bin/data/shaders/common/functions.h b/bin/data/shaders/common/functions.h index 46a7a0fa..57eb3c24 100644 --- a/bin/data/shaders/common/functions.h +++ b/bin/data/shaders/common/functions.h @@ -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 ); diff --git a/bin/data/shaders/common/shadows.h b/bin/data/shaders/common/shadows.h index 25556b97..15af223b 100644 --- a/bin/data/shaders/common/shadows.h +++ b/bin/data/shaders/common/shadows.h @@ -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; } diff --git a/bin/data/shaders/common/structs.h b/bin/data/shaders/common/structs.h index 49a7ee32..664c7ca3 100644 --- a/bin/data/shaders/common/structs.h +++ b/bin/data/shaders/common/structs.h @@ -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; diff --git a/engine/inc/uf/engine/graph/graph.h b/engine/inc/uf/engine/graph/graph.h index 78eeca73..6777bd67 100644 --- a/engine/inc/uf/engine/graph/graph.h +++ b/engine/inc/uf/engine/graph/graph.h @@ -34,8 +34,6 @@ namespace pod { uf::stl::vector images; // uf::stl::vector materials; // uf::stl::vector textures; // - - uf::stl::vector texture2Ds; // uf::stl::vector samplers; // // Lighting information @@ -104,6 +102,8 @@ namespace pod { uf::stl::KeyMap> joints; uf::stl::KeyMap entities; + uf::stl::vector cubemaps; + uf::stl::vector shadow2Ds; uf::stl::vector shadowCubes; diff --git a/engine/inc/uf/engine/graph/pod.inl b/engine/inc/uf/engine/graph/pod.inl index 4e19f190..e113d160 100644 --- a/engine/inc/uf/engine/graph/pod.inl +++ b/engine/inc/uf/engine/graph/pod.inl @@ -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; }; diff --git a/engine/inc/uf/ext/valve/common.h b/engine/inc/uf/ext/valve/common.h index 9c61488f..84c78d35 100644 --- a/engine/inc/uf/ext/valve/common.h +++ b/engine/inc/uf/ext/valve/common.h @@ -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 ); + } } \ No newline at end of file diff --git a/engine/inc/uf/utils/mesh/mesh.h b/engine/inc/uf/utils/mesh/mesh.h index 71c69029..dbaf2270 100644 --- a/engine/inc/uf/utils/mesh/mesh.h +++ b/engine/inc/uf/utils/mesh/mesh.h @@ -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 diff --git a/engine/src/engine/ext/scene/behavior.cpp b/engine/src/engine/ext/scene/behavior.cpp index b428f732..0c49196a 100644 --- a/engine/src/engine/ext/scene/behavior.cpp +++ b/engine/src/engine/ext/scene/behavior.cpp @@ -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); diff --git a/engine/src/engine/graph/decode.cpp b/engine/src/engine/graph/decode.cpp index 880ace7e..9385021c 100644 --- a/engine/src/engine/graph/decode.cpp +++ b/engine/src/engine/graph/decode.cpp @@ -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 ); diff --git a/engine/src/engine/graph/encode.cpp b/engine/src/engine/graph/encode.cpp index 2cbbf65e..92359759 100644 --- a/engine/src/engine/graph/encode.cpp +++ b/engine/src/engine/graph/encode.cpp @@ -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 ); diff --git a/engine/src/engine/graph/graph.cpp b/engine/src/engine/graph/graph.cpp index c08fbe0c..382b0b14 100644 --- a/engine/src/engine/graph/graph.cpp +++ b/engine/src/engine/graph/graph.cpp @@ -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(); - 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 isSrgb; + // stores temporary metadata for textures (that can be deduced at runtime) + uf::stl::unordered_map 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 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(); + 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 gpuIndex2D(storage.images.keys.size(), -1); + uf::stl::vector 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 isSrgb; // cringe - uf::stl::unordered_map textureReferences; // determine which textures are in use or not + uf::stl::unordered_map 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 diff --git a/engine/src/ext/valve/bsp.cpp b/engine/src/ext/valve/bsp.cpp index 79604c9f..5a7e9029 100644 --- a/engine/src/ext/valve/bsp.cpp +++ b/engine/src/ext/valve/bsp.cpp @@ -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 dispverts; // disptris uf::stl::vector pakfile; - // cubemaps + uf::stl::vector cubemaps; // overlay uf::stl::vector lighting; // ambient lighting @@ -225,6 +230,7 @@ namespace impl { uf::stl::vector modelToMesh; uf::stl::vector texdataToMaterial; + uf::stl::vector 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::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 - uf::stl::vector extractLump( const uf::stl::vector& buffer, const impl::BspLump& lump ) { - uf::stl::vector data; - if ( lump.length == 0 || lump.offset >= buffer.size() ) return data; + void extractLump( const uf::stl::vector& buffer, const impl::BspLump& lump, uf::stl::vector& 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 + uf::stl::vector extractLump( const uf::stl::vector& buffer, const impl::BspLump& lump ) { + uf::stl::vector data; + impl::extractLump( buffer, lump, data ); return data; } template<> - uf::stl::vector extractLump( const uf::stl::vector& buffer, const impl::BspLump& lump ) { - uf::stl::vector data; - if ( lump.length == 0 || lump.offset >= buffer.size() ) return data; + void extractLump( const uf::stl::vector& buffer, const impl::BspLump& lump, uf::stl::vector& 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 extractLump( const uf::stl::vector& buffer, const impl::BspLump& lump ) { + uf::stl::vector data; + impl::extractLump( buffer, lump, data ); return data; } - uf::stl::string extractLumpString( const uf::stl::vector& buffer, const impl::BspLump& lump ) { + void extractLumpString( const uf::stl::vector& buffer, const impl::BspLump& lump, uf::stl::string& str ) { auto data = impl::extractLump( 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& 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(buffer, header->lumps[impl::BspLump::LUMP_VERTICES]); - context.edges = impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_EDGES]); - context.surfedges = impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_SURFEDGES]); - context.faces = impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_FACES]); - context.texinfos = impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_TEXINFO]); - context.texdatas = impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_TEXDATA]); - context.stringTable = impl::extractLump(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(buffer, header->lumps[impl::BspLump::LUMP_MODELS]); - context.entities = impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_ENTITIES]); - context.gameLumps = impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_GAME_LUMP]); - context.dispinfos = impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_DISPINFO]); - context.dispverts = impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_DISP_VERTS]); - context.pakfile = impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_PAKFILE]); - context.lighting = impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_LIGHTING]); + impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_VERTICES], context.vertices); + impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_EDGES], context.edges); + impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_SURFEDGES], context.surfedges); + impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_FACES], context.faces); + impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_TEXINFO], context.texinfos); + impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_TEXDATA], context.texdatas); + impl::extractLump(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(buffer, header->lumps[impl::BspLump::LUMP_MODELS], context.models); + impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_ENTITIES], context.entities); + impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_GAME_LUMP], context.gameLumps); + impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_DISPINFO], context.dispinfos); + impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_DISP_VERTS], context.dispverts); + impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_PAKFILE], context.pakfile); + impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_CUBEMAPS], context.cubemaps); + impl::extractLump(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(0.0f); material.factorRoughness = vmt["$roughness"].as(1.0f); - if ( vmt["$envmap"].as() != "" ) material.factorRoughness = 0.3f; - if ( vmt["$phong"].as(0) == 1 ) material.factorRoughness = std::min(material.factorRoughness, 0.5f); - + if ( vmt["$phong"].as(0) == 1 ) { + material.factorRoughness = std::min(material.factorRoughness, 0.5f); + } if ( vmt["$translucent"].as(0) == 1 ) { material.modeAlpha = pod::Material::AlphaMode::BLEND; - } else if ( vmt["$alphatest"].as(0) == 1 ) { + } + if ( vmt["$alphatest"].as(0) == 1 ) { material.modeAlpha = pod::Material::AlphaMode::MASK; material.factorAlphaCutoff = vmt["$alphatestreference"].as(0.5f); } - if ( vmt["$nocull"].as(0) == 1 ) material.modeCull = pod::Material::CullMode::NONE; + if ( vmt["$nocull"].as(0) == 1 ) { + material.modeCull = pod::Material::CullMode::NONE; + } if ( vmt["$selfillum"].as(0) == 1 ) { material.colorEmissive = { 1.0f, 1.0f, 1.0f, 1.0f }; material.modeAlpha = pod::Material::AlphaMode::EMISSIVE; } - if ( !vmt["$basetexture"].is() ) goto PEETAH; + // cubemap + if ( vmt["$envmap"].as() != "" ) { + auto matName = uf::string::lowercase(vmt["$envmap"].as()); + 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() ) { + auto matName = uf::string::lowercase(vmt["$bumpmap"].as()); + 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() ) { + auto matName = uf::string::lowercase(vmt["$mrao"].as()); + 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() ) { + vtfPath = ::fmt::format("materials/{}.vtf", vmt["$basetexture"].as()); + if ( ext::valve::loadVtf( image, vtfPath ) ) { + continue; + } + } - vtfPath = ::fmt::format("materials/{}.vtf", vmt["$basetexture"].as()); - if ( !ext::valve::loadVtf( image, vtfPath ) ) goto PEETAH; - continue; PEETAH: image.loadFromBuffer( missing_pixels, { 2, 2 }, 8, 4 ); } diff --git a/engine/src/ext/valve/common.cpp b/engine/src/ext/valve/common.cpp index 0e5b11ca..9d4cc16e 100644 --- a/engine/src/ext/valve/common.cpp +++ b/engine/src/ext/valve/common.cpp @@ -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; } diff --git a/engine/src/ext/valve/mdl.cpp b/engine/src/ext/valve/mdl.cpp index 2a3590cd..a3e5ad52 100644 --- a/engine/src/ext/valve/mdl.cpp +++ b/engine/src/ext/valve/mdl.cpp @@ -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; diff --git a/engine/src/ext/valve/vtf.cpp b/engine/src/ext/valve/vtf.cpp index 5a6d2cfd..e9854a00 100644 --- a/engine/src/ext/valve/vtf.cpp +++ b/engine/src/ext/valve/vtf.cpp @@ -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(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(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 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; diff --git a/engine/src/ext/vulkan/texture.cpp b/engine/src/ext/vulkan/texture.cpp index d310f5f9..f17f8b61 100644 --- a/engine/src/ext/vulkan/texture.cpp +++ b/engine/src/ext/vulkan/texture.cpp @@ -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,