From def459e72cffb72e036b51b99f0fd0a4aae36663 Mon Sep 17 00:00:00 2001 From: ecker Date: Sat, 20 Jun 2026 00:24:33 -0500 Subject: [PATCH] added 3D skybox rendering (to-do: fix some quirks), fixing lighting issues (to-do: fix tangents or normal maps or something i dont know) --- bin/data/config.json | 4 +- .../sourceengine/base_sourceengine.json | 5 +- bin/data/scenes/sourceengine/de_dust2.json | 7 + .../scenes/sourceengine/mds_mcdonalds.json | 8 +- .../scenes/sourceengine/sourceengine.json | 3 +- bin/data/shaders/common/functions.h | 29 +- bin/data/shaders/display/deferred/comp/comp.h | 8 +- bin/data/shaders/graph/base/vert.h | 10 +- engine/inc/uf/utils/memory/key_map.h | 20 +- engine/src/engine/ext/scene/behavior.cpp | 7 +- engine/src/engine/graph/graph.cpp | 15 +- engine/src/engine/scene/scene.cpp | 34 +- engine/src/ext/valve/bsp.cpp | 418 +++++++++++++----- engine/src/ext/valve/mdl.cpp | 122 ++--- engine/src/ext/valve/vtf.cpp | 24 + engine/src/ext/vulkan/graphic.cpp | 3 +- .../src/ext/vulkan/rendermodes/deferred.cpp | 120 ++--- 17 files changed, 521 insertions(+), 316 deletions(-) create mode 100644 bin/data/scenes/sourceengine/de_dust2.json diff --git a/bin/data/config.json b/bin/data/config.json index aa46cdca..869709ce 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, @@ -426,7 +426,7 @@ }, "loader": { "assert": true, - "async": true + "async": false }, "hooks": { "defer lazy calls": true diff --git a/bin/data/scenes/sourceengine/base_sourceengine.json b/bin/data/scenes/sourceengine/base_sourceengine.json index 91d65f11..8ec94738 100644 --- a/bin/data/scenes/sourceengine/base_sourceengine.json +++ b/bin/data/scenes/sourceengine/base_sourceengine.json @@ -13,7 +13,7 @@ "physics": { "type": "mesh", "static": true, "mass": 0 }, "grid": { "size": [8,1,8], "epsilon": 0.001, "cleanup": true, "print": true, "clip": true }, "unwrap mesh": true - }, + } /*"light_environment": { "ignore": false, "light": { // "color": [0.1, 0.1, 0.1], "power": 1000, @@ -36,8 +36,7 @@ // "/^prop_dynamic/": { "action": "load", "payload": { "import": "ent://prop.json" } }, // "/^func_physbox/": { "action": "load", "payload": { "import": "ent://prop.json" } }, // "/^prop_physics/": { "action": "load", "payload": { "import": "ent://prop.json" } }, - - "/^tools\\/toolsnodraw/": { "material": { "base": [ 1.0, 1.0, 1.0, 0.0 ] } } + // "/^tools\\/toolsnodraw/": { "material": { "base": [ 1.0, 1.0, 1.0, 0.0 ] } } } } } diff --git a/bin/data/scenes/sourceengine/de_dust2.json b/bin/data/scenes/sourceengine/de_dust2.json new file mode 100644 index 00000000..ac26e0d2 --- /dev/null +++ b/bin/data/scenes/sourceengine/de_dust2.json @@ -0,0 +1,7 @@ +{ + "import": "./base_sourceengine.json", + "assets": [ + { "filename": "./maps/de_dust2.bsp" } + // { "filename": "./maps/de_dust2/graph.json" } + ] +} \ 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 45290690..9532b7bd 100644 --- a/bin/data/scenes/sourceengine/mds_mcdonalds.json +++ b/bin/data/scenes/sourceengine/mds_mcdonalds.json @@ -1,9 +1,9 @@ { "import": "./base_sourceengine.json", "assets": [ - // { "filename": "./maps/mcdonalds-mds.bsp" } - { "filename": "./maps/mcdonalds-mds/graph.json" }, - { "filename": "ent://burger.json", "delay": 1 }, - { "filename": "ent://craeture.json", "delay": 2 } + { "filename": "./maps/mcdonalds-mds.bsp" } + // { "filename": "./maps/mcdonalds-mds/graph.json" } + // ,{ "filename": "ent://burger.json", "delay": 1 } + // ,{ "filename": "ent://craeture.json", "delay": 2 } ] } \ No newline at end of file diff --git a/bin/data/scenes/sourceengine/sourceengine.json b/bin/data/scenes/sourceengine/sourceengine.json index bdb37541..0313721d 100644 --- a/bin/data/scenes/sourceengine/sourceengine.json +++ b/bin/data/scenes/sourceengine/sourceengine.json @@ -1,7 +1,8 @@ { // "import": "./rp_downtown_v2.json" // "import": "./ss2_medsci1.json" - "import": "./mds_mcdonalds.json" +// "import": "./mds_mcdonalds.json" // "import": "./cs_office.json" + "import": "./de_dust2.json" // "import": "./gm_construct.json" } \ No newline at end of file diff --git a/bin/data/shaders/common/functions.h b/bin/data/shaders/common/functions.h index f0728e73..90b47e69 100644 --- a/bin/data/shaders/common/functions.h +++ b/bin/data/shaders/common/functions.h @@ -322,21 +322,21 @@ void populateSurfaceMaterial() { 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); + 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; + 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; + 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); + 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; + surface.light.rgb += reflection * F * surface.material.occlusion; } // (Occlusion/)Metallic/Roughness map if ( validTextureIndex( material.indexMetallicRoughness ) ) { @@ -402,6 +402,8 @@ uvec4 uvec2_16x4( uvec2 i ) { void populateSurface( InstanceAddresses addresses, uvec3 indices ) { Triangle triangle; Vertex points[3]; + + float uvHandedness = 1.0; if ( isValidAddress(addresses.position) ) { VPos buf = VPos(nonuniformEXT(addresses.position)); #pragma unroll 3 @@ -432,9 +434,12 @@ void populateSurface( InstanceAddresses addresses, uvec3 indices ) { vec2 deltaUV1 = points[1].uv - points[0].uv; vec2 deltaUV2 = points[2].uv - points[0].uv; - float r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x); + float det = (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x); + float r = 1.0f / det; vec3 tangent_tri = (edge1 * deltaUV2.y - edge2 * deltaUV1.y) * r; + uvHandedness = (det < 0.0) ? -1.0 : 1.0; + #pragma unroll 3 for ( uint _ = 0; _ < 3; ++_ ) points[_].tangent = tangent_tri; } @@ -489,7 +494,7 @@ void populateSurface( InstanceAddresses addresses, uvec3 indices ) { surface.tangent.world = normalize(vec3( surface.object.model * vec4(triangle.point.tangent, 0.0) )); surface.tangent.world = normalize(surface.tangent.world - dot(surface.tangent.world, surface.normal.world) * surface.normal.world); - vec3 bitangent = normalize(vec3( surface.object.model * vec4(cross( triangle.point.normal, triangle.point.tangent ), 0.0) )); + vec3 bitangent = normalize(vec3( surface.object.model * vec4(cross( triangle.point.normal, triangle.point.tangent ) * uvHandedness, 0.0) )); surface.tbn = mat3(surface.tangent.world, bitangent, surface.normal.world); } // bind UVs diff --git a/bin/data/shaders/display/deferred/comp/comp.h b/bin/data/shaders/display/deferred/comp/comp.h index 0c7558b5..a7415f59 100644 --- a/bin/data/shaders/display/deferred/comp/comp.h +++ b/bin/data/shaders/display/deferred/comp/comp.h @@ -198,7 +198,7 @@ void populateSurface() { surface.material.roughness = 0; surface.material.occlusion = 0; - float depth = 0.0; + float depth = IMAGE_LOAD(samplerDepth).r; { #if USE_CAMERA_VIEWPORT @@ -218,10 +218,8 @@ void populateSurface() { surface.ray.direction = normalize( far3 - near3 ); surface.ray.origin = ubo.eyes[surface.pass].eyePos.xyz; // near3.xyz; // eyePos.xyz - depth = IMAGE_LOAD(samplerDepth).r; - vec4 eye = iProjection * vec4(surface.fragCoord, depth, 1.0); - eye /= eye.w; + if ( eye.w > 0.0001 ) eye /= eye.w; surface.position.eye = eye.xyz; surface.position.world = vec3( iView * eye ); @@ -234,7 +232,7 @@ void populateSurface() { const uvec2 ID = msaa.IDs[msaa.currentID]; #endif - if ( ID.x == 0 || ID.y == 0 || depth <= 0.0 ) { + if ( ID.x == 0 || ID.y == 0 /*|| depth <= 0.0*/ ) { USE_SKYBOX_ON_DIVERGENCE = true; } /* diff --git a/bin/data/shaders/graph/base/vert.h b/bin/data/shaders/graph/base/vert.h index b391afa7..f97b48a8 100644 --- a/bin/data/shaders/graph/base/vert.h +++ b/bin/data/shaders/graph/base/vert.h @@ -10,7 +10,7 @@ layout (location = 1) in vec2 inUv; layout (location = 2) in vec4 inColor; layout (location = 3) in vec2 inSt; layout (location = 4) in vec3 inNormal; -layout (location = 5) in vec4 inTangent; +layout (location = 5) in vec3 inTangent; #if SKINNED layout (location = 6) in uvec4 inJoints; layout (location = 7) in vec4 inWeights; @@ -19,6 +19,7 @@ layout (location = 5) in vec4 inTangent; layout( push_constant ) uniform PushBlock { uint pass; uint draw; + uint aux; } PushConstant; layout (binding = 0) uniform Camera { @@ -69,12 +70,15 @@ void main() { const Object object = objects[instance.objectID]; const uint jointID = instance.jointID; + uint viewportIndex = gl_ViewIndex; + if ( PushConstant.aux == 1 ) viewportIndex += 2; + #if BAKING const mat4 view = mat4(1); const mat4 projection = mat4(1); #else - const mat4 view = camera.viewport[/*PushConstant.pass*/gl_ViewIndex].view; - const mat4 projection = camera.viewport[/*PushConstant.pass*/gl_ViewIndex].projection; + const mat4 view = camera.viewport[viewportIndex].view; + const mat4 projection = camera.viewport[viewportIndex].projection; #endif #if SKINNED const mat4 skinned = joints.length() <= 0 || jointID < 0 ? mat4(1.0) : inWeights.x * joints[jointID + int(inJoints.x)] + inWeights.y * joints[jointID + int(inJoints.y)] + inWeights.z * joints[jointID + int(inJoints.z)] + inWeights.w * joints[jointID + int(inJoints.w)]; diff --git a/engine/inc/uf/utils/memory/key_map.h b/engine/inc/uf/utils/memory/key_map.h index 53c0bb1a..8e5b7579 100644 --- a/engine/inc/uf/utils/memory/key_map.h +++ b/engine/inc/uf/utils/memory/key_map.h @@ -98,19 +98,17 @@ uf::stl::vector uf::stl::KeyMap::flattenByIndex() const { template void uf::stl::KeyMap::merge( KeyMap&& other ) { - this->reserve( this->keys.size() + other.keys.size() ); + this->reserve( this->keys.size() + other.keys.size() ); - for ( auto& key : other.keys ) { - // Bypass custom operator[] and force a direct map assignment - if ( this->map.count(key) == 0 ) { - this->indices[key] = this->keys.size(); - this->keys.emplace_back(key); - } - // Explicitly copy to prevent any weird std::move state corruption - this->map[key] = other.map.at(key); - } + for ( auto& key : other.keys ) { + if ( this->map.count(key) == 0 ) { + this->indices[key] = this->keys.size(); + this->keys.emplace_back(key); + } + this->map[key] = other.map.at(key); + } - other.clear(); + other.clear(); } template diff --git a/engine/src/engine/ext/scene/behavior.cpp b/engine/src/engine/ext/scene/behavior.cpp index 40de3102..aea38229 100644 --- a/engine/src/engine/ext/scene/behavior.cpp +++ b/engine/src/engine/ext/scene/behavior.cpp @@ -282,8 +282,8 @@ void ext::ExtSceneBehavior::initialize( uf::Object& self ) { #endif } void ext::ExtSceneBehavior::tick( uf::Object& self ) { -// auto& assetLoader = this->getComponent(); uf::asset::processQueue(); +// auto& assetLoader = this->getComponent(); auto& metadata = this->getComponent(); auto& metadataJson = this->getComponent(); @@ -625,8 +625,9 @@ void ext::ExtSceneBehavior::tick( uf::Object& self ) { constexpr uint32_t MODE_CUBEMAP = 1; constexpr uint32_t MODE_SEPARATE_2DS = 2; - for ( uint32_t i = 0; i < entities.size(); ++i ) { - auto& info = entities[i]; + //for ( uint32_t i = 0; i < entities.size(); ++i ) { + for ( auto idx : indices ) { + auto& info = entities[idx]; uf::Entity* entity = info.entity; int32_t boundIndexMap = -1; diff --git a/engine/src/engine/graph/graph.cpp b/engine/src/engine/graph/graph.cpp index fa855c7c..0cde225c 100644 --- a/engine/src/engine/graph/graph.cpp +++ b/engine/src/engine/graph/graph.cpp @@ -710,12 +710,20 @@ void uf::graph::initializeGraphics( pod::Graph& graph, uf::Object& entity, uf::M graphic.initialize(); graphic.initializeMesh( mesh ); + graphic.device = &uf::renderer::device; graphic.material.device = &uf::renderer::device; graphic.descriptor.frontFace = graphMetadataJson["renderer"]["invert"].as(true) ? uf::renderer::enums::Face::CW : uf::renderer::enums::Face::CCW; graphic.descriptor.cullMode = uf::renderer::enums::CullMode::BACK; + + auto& entityName = entity.getName(); + auto& metadataJson = entity.getComponent(); + auto& metadataValve = metadataJson["valve"]; + if ( entityName == "skybox" || metadataValve["skyboxed"].as(false) ) { + graphic.descriptor.aux = 1; // to-do: some global enums for this shit + } - auto tag = ext::json::find( entity.getName(), graphMetadataJson["tags"] ); + auto tag = ext::json::find( entityName, graphMetadataJson["tags"] ); if ( !ext::json::isObject( tag ) ) { tag["renderer"] = graphMetadataJson["renderer"]; } @@ -731,7 +739,7 @@ void uf::graph::initializeGraphics( pod::Graph& graph, uf::Object& entity, uf::M } // query materials if culling needs to be disabled - if ( entity.getName() != "worldspawn" ) { + if ( entityName != "worldspawn" ) { for ( auto& primitive : primitives ) { auto materialID = primitive.instance.materialID; if ( 0 <= materialID && materialID <= graph.materials.size() ) { @@ -1019,6 +1027,9 @@ void uf::graph::process( pod::Graph& graph ) { // nodraw if ( name.starts_with("tools/") ) { material.colorBase.w = 0.0f; + material.modeAlpha = pod::Material::AlphaMode::MASK; + material.factorAlphaCutoff = 1.0f; + material.indexAlbedo = -1; } } diff --git a/engine/src/engine/scene/scene.cpp b/engine/src/engine/scene/scene.cpp index 8e305fdd..388085c6 100644 --- a/engine/src/engine/scene/scene.cpp +++ b/engine/src/engine/scene/scene.cpp @@ -67,12 +67,6 @@ const uf::Entity& uf::Scene::getController() const { } uf::Camera& uf::Scene::getCamera( uf::Entity& controller ) { - // ??? -/* - if ( auto currentRenderMode = uf::renderer::getCurrentRenderMode(); currentRenderMode && !currentRenderMode->getName().empty() ) { - return controller.getComponent(); - } -*/ #if !UF_SCENE_GLOBAL_GRAPH auto& metadata = this->getComponent(); #endif @@ -83,15 +77,39 @@ uf::Camera& uf::Scene::getCamera( uf::Entity& controller ) { if ( lastFrame != uf::time::frame ) { auto& sourceCamera = controller.getComponent(); - if ( controller.getName() == "Player" ) sourceCamera.update(); + auto& controllerName = controller.getName(); + if ( controllerName == "Player" ) sourceCamera.update(); // copy all matrices for ( auto i = 0; i < uf::camera::maxViews; ++i ) { cachedCamera.setView(sourceCamera.getView(i), i); cachedCamera.setProjection(sourceCamera.getProjection(i), i); } // flatten the transform in the event the parent transform updates later - cachedCamera.setTransform(uf::transform::flatten(sourceCamera.getTransform())); + auto transform = uf::transform::flatten(sourceCamera.getTransform()); + cachedCamera.setTransform(transform); lastFrame = uf::time::frame; + + // handle sky_camera + if ( auto entity = this->findByName("sky_camera"); entity && controllerName == "Player" ) { + auto& metadatavalve = entity->getComponent()["valve"]; + float scale = metadatavalve["scale"].as(16.0f); + + auto cameraTransform = entity->getComponent>(); + cameraTransform.position += (transform.position / scale); + cameraTransform.orientation = transform.orientation; + + uf::Camera skyCamera = sourceCamera; + skyCamera.setTransform(cameraTransform); + skyCamera.update(); + + int auxOffset = 2; + for ( auto i = 0; i < 2; ++i ) { + if ( (i + auxOffset) < uf::camera::maxViews ) { + cachedCamera.setView(skyCamera.getView(i), i + auxOffset); + cachedCamera.setProjection(sourceCamera.getProjection(i), i + auxOffset); + } + } + } } return cachedCamera; } diff --git a/engine/src/ext/valve/bsp.cpp b/engine/src/ext/valve/bsp.cpp index 8b936da1..a7a0e94d 100644 --- a/engine/src/ext/valve/bsp.cpp +++ b/engine/src/ext/valve/bsp.cpp @@ -6,6 +6,7 @@ #include #include +#include namespace impl { struct RGBE { @@ -121,6 +122,31 @@ namespace impl { #pragma pack(push, 1) + struct BspPlane { + pod::Vector3f normal; + float dist; + int32_t type; + }; + + struct BspNode { + int32_t planenum; + int32_t children[2]; + int16_t mins[3], maxs[3]; + uint16_t firstface, numfaces; + int16_t area; + int16_t padding; + }; + + struct BspLeaf { + int32_t contents; + int16_t cluster; + int16_t area_flags; + int16_t mins[3], maxs[3]; + uint16_t firstleafface, numleaffaces; + uint16_t firstleafbrush, numleafbrushes; + int16_t leafWaterDataID; + }; + struct BspDispSubNeighbor { uint16_t neighbor; uint8_t neighborOrientation; @@ -205,14 +231,17 @@ namespace impl { uf::stl::vector surfedges; uf::stl::vector faces; // brush, brushside - // node, leaf - // leafface, leaffbrush + uf::stl::vector planes; + uf::stl::vector nodes; + uf::stl::vector leafs; + uf::stl::vector leaffaces; + // leaffbrush uf::stl::vector texinfos; uf::stl::vector texdatas; uf::stl::vector stringTable; uf::stl::string stringData; uf::stl::vector models; - // visibility + uf::stl::vector visibility; uf::stl::vector entities; uf::stl::vector gameLumps; uf::stl::vector dispinfos; @@ -228,6 +257,8 @@ namespace impl { // worldlight // other + int32_t skyArea = -1; + uf::stl::unordered_set skyboxFaces; uf::stl::vector modelToMesh; uf::stl::vector texdataToMaterial; uf::stl::vector cubemapIDs; @@ -258,6 +289,49 @@ namespace impl { return io; } + int32_t findLeaf( const impl::BspContext& context, const pod::Vector3f& pos ) { + if ( context.nodes.empty() || context.planes.empty() || context.leafs.empty() ) return 0; + + int32_t node = 0; + while ( node >= 0 ) { + const auto& n = context.nodes[node]; + const auto& p = context.planes[n.planenum]; + + float d = uf::vector::dot(pos, p.normal) - p.dist; + + node = n.children[d < 0 ? 1 : 0]; + } + return -node - 1; + } + + bool isClusterVisible( const impl::BspContext& context, int32_t visCluster, int32_t testCluster ) { + if ( context.visibility.size() < 8 || visCluster < 0 || testCluster < 0 ) return true; + + // PVS Header: [num_clusters] [byteofs_pvs[num_clusters]] [byteofs_phs[num_clusters]] + int32_t numClusters = *(int32_t*)(context.visibility.data()); + if ( visCluster >= numClusters || testCluster >= numClusters ) return false; + + // offset to this cluster's PVS data + int32_t byteOffset = *(int32_t*)(context.visibility.data() + 4 + visCluster * 8); + const uint8_t* pvs = context.visibility.data() + byteOffset; + + // decompress RLE + int32_t clusterCounter = 0; + while ( clusterCounter <= testCluster ) { + if ( *pvs == 0 ) { + clusterCounter += 8 * pvs[1]; + pvs += 2; + } else { + if ( clusterCounter + 8 > testCluster ) { + return (*pvs & (1 << (testCluster - clusterCounter))) != 0; + } + clusterCounter += 8; + pvs++; + } + } + return false; + } + int32_t findClosestCubemap( const impl::BspContext& context, const pod::Vector3f& position ) { if ( context.cubemapIDs.empty() ) return -1; @@ -392,10 +466,13 @@ namespace impl { pod::Vector3f tangent = uf::vector::normalize(pR - pL); pod::Vector3f bitangent = uf::vector::normalize(pU - pD); + pod::Vector3f normal = uf::vector::normalize(uf::vector::cross(bitangent, tangent)); + // float w = (uf::vector::dot(uf::vector::cross(normal, tangent), bitangent) < 0.0f) ? -1.0f : 1.0f; + meshlet.vertices[id].normal = normal; - meshlet.vertices[id].tangent = tangent; + meshlet.vertices[id].tangent = tangent = uf::vector::normalize(tangent - normal * uf::vector::dot(normal, tangent)); } } @@ -435,7 +512,7 @@ namespace impl { const auto& face = context.faces[faceID]; pod::Vector4f vertex = context.vertices[vertexID]; vertex.w = 1; // for dot products - + // add index meshlet.indices.emplace_back((uint32_t)(meshlet.vertices.size())); // add vertex @@ -443,7 +520,7 @@ namespace impl { v.position = pos; v.color = { 1.0f, 1.0f, 1.0f, 1.0f }; v.normal = normal; - + // has texture information if ( face.texinfo >= 0 && face.texinfo < context.texinfos.size() ) { const auto& info = context.texinfos[face.texinfo]; @@ -459,7 +536,11 @@ namespace impl { v.st = uf::atlas::mapUv( context.lightmapAtlas, v.st, impl::faceHash( faceID ) ); - v.tangent = uf::vector::normalize( impl::convertPos( info.textureVecs[0], 1) ); + pod::Vector3f t = uf::vector::normalize( impl::convertPos( info.textureVecs[0], 1.0f ) ); + pod::Vector3f b = uf::vector::normalize( impl::convertPos( info.textureVecs[1], 1.0f ) ); + + v.tangent = uf::vector::normalize(t - normal * uf::vector::dot(normal, t)); + // float w = (uf::vector::dot( uf::vector::cross(normal, t), b ) < 0.0f) ? -1.0f : 1.0f; } }; @@ -478,6 +559,21 @@ namespace impl { return data; } + template<> + void extractLump( const uf::stl::vector& buffer, const impl::BspLump& lump, uf::stl::vector& data ) { + if ( lump.length == 0 || lump.offset >= buffer.size() ) return; + + int stride = (lump.version == 0) ? 56 : 32; + + size_t count = lump.length / stride; + data.resize(count); + + const uint8_t* src = buffer.data() + lump.offset; + for ( size_t i = 0; i < count; ++i ) { + std::memcpy(&data[i], src + (i * stride), sizeof(impl::BspLeaf)); + } + } + template<> void extractLump( const uf::stl::vector& buffer, const impl::BspLump& lump, uf::stl::vector& data ) { if ( lump.length == 0 || lump.offset >= buffer.size() ) return; @@ -516,6 +612,9 @@ namespace impl { for ( auto nodeID : graph.root.children ) { auto& node = graph.nodes[nodeID]; auto& metadata = node.metadata["valve"]; + + if ( !ext::json::isObject( metadata ) ) continue; + auto classname = metadata["classname"].as(""); //UF_MSG_INFO("Entity found: {}", classname); node.name = classname; @@ -691,11 +790,16 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co 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_PLANES], context.planes); + impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_NODES], context.nodes); + impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_LEAFS], context.leafs); + impl::extractLump(buffer, header->lumps[impl::BspLump::LUMP_LEAFFACES], context.leaffaces); 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_VISIBILITY], context.visibility); 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); @@ -730,7 +834,6 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co 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 ); } } @@ -787,106 +890,6 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co storage.textures[textureKey].index = atlasImageID; } - // read models - for ( auto m = 0; m < context.models.size(); ++m ) { - const auto& model = context.models[m]; - uf::stl::unordered_map meshlets; // group by material IDs - - for ( auto i = 0; i < model.numfaces; ++i ) { - const auto faceID = model.firstface + i; - const auto& face = context.faces[faceID]; - - if ( face.numedges < 3 ) continue; - if ( face.texinfo < 0 || face.texinfo >= context.texinfos.size() ) continue; - - int32_t texDataID = context.texinfos[face.texinfo].texData; - 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]; - meshlet.primitive.instance.materialID = materialID; - - 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 ); - continue; - } - - const auto edgeID = face.firstedge; - int32_t pivotSurfEdge = context.surfedges[edgeID]; - uint16_t pivotVertID = pivotSurfEdge >= 0 ? context.edges[pivotSurfEdge].x : context.edges[-pivotSurfEdge].y; - pod::Vector3f p0 = impl::convertPos( context.vertices[pivotVertID] ); - - for ( int16_t i = 1; i < face.numedges - 1; ++i ) { - int32_t se1 = context.surfedges[edgeID + i]; - int32_t se2 = context.surfedges[edgeID + i + 1]; - - uint16_t v1 = se1 >= 0 ? context.edges[se1].x : context.edges[-se1].y; - uint16_t v2 = se2 >= 0 ? context.edges[se2].x : context.edges[-se2].y; - - pod::Vector3f p1 = impl::convertPos( context.vertices[v1] ); - pod::Vector3f p2 = impl::convertPos( context.vertices[v2] ); - pod::Vector3f normal = uf::vector::normalize(uf::vector::cross(p1 - p0, p2 - p0)); - - impl::addVertex( context, meshlet, pivotVertID, p0, normal, faceID ); - impl::addVertex( context, meshlet, v1, p1, normal, faceID ); - impl::addVertex( context, meshlet, v2, p2, normal, faceID ); - } - } - - if ( meshlets.empty() ) continue; - - - auto meshName = ::fmt::format("model_{}", m); - context.modelToMesh[m] = graph.meshes.size(); - - graph.meshes.emplace_back(meshName); - graph.primitives.emplace_back(meshName); - - auto& mesh = storage.meshes[meshName]; - auto& primitives = storage.primitives[meshName]; - - // slice worldspawn - if ( false && m == 0 ) { - /* - if ( ext::json::isObject( meshgrid.metadata ) ) { - if ( meshgrid.metadata["size"].is() ) { - size_t d = meshgrid.metadata["size"].as(); - meshgrid.grid.divisions = {d, d, d}; - } else { - meshgrid.grid.divisions = uf::vector::decode( meshgrid.metadata["size"], meshgrid.grid.divisions ); - } - - meshgrid.eps = meshgrid.metadata["epsilon"].as(meshgrid.eps); - meshgrid.print = meshgrid.metadata["print"].as(meshgrid.print); - meshgrid.clip = meshgrid.metadata["clip"].as(meshgrid.clip); - meshgrid.cleanup = meshgrid.metadata["cleanup"].as(meshgrid.cleanup); - } - */ - uf::meshgrid::Grid grid; - grid.divisions = {8, 8, 8}; - auto mlets = uf::stl::values( meshlets ); - auto partitioned = uf::meshgrid::partition( grid, mlets, EPS, true, true ); - mesh.compile( partitioned, primitives ); - } else { - mesh.compile( meshlets, primitives ); - } - } - // read entities { bool parsing = false; @@ -941,6 +944,186 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co } } + // prepare for segregating skybox from worldspawn + { + pod::Vector3f skyCameraOrigin = {NAN, NAN, NAN}; + for ( auto& node : graph.nodes ) { + auto& metadataValve = node.metadata["valve"]; + if ( metadataValve["classname"].as("") != "sky_camera" ) continue; + auto originStr = metadataValve["origin"].as(""); + if ( originStr == "" ) continue; + skyCameraOrigin = impl::str2vec( originStr ); + break; + } + + if ( uf::vector::isValid( skyCameraOrigin ) ) { + int32_t skyLeafIdx = impl::findLeaf(context, skyCameraOrigin); + + context.skyArea = context.leafs[skyLeafIdx].area_flags & 0x01FF; + + for ( const auto& leaf : context.leafs ) { + int16_t leafArea = leaf.area_flags & 0x01FF; + + if ( leafArea == context.skyArea ) { + for ( int i = 0; i < leaf.numleaffaces; ++i ) { + context.skyboxFaces.insert(context.leaffaces[leaf.firstleafface + i]); + } + } + } + } + } + + // read models + for ( auto m = 0; m < context.models.size(); ++m ) { + const auto& model = context.models[m]; + uf::stl::unordered_map meshlets; // group by material IDs + uf::stl::unordered_map skyboxMeshlets; // segregate skybox geometry + + for ( auto i = 0; i < model.numfaces; ++i ) { + const auto faceID = model.firstface + i; + const auto& face = context.faces[faceID]; + + if ( face.numedges < 3 ) continue; + if ( face.texinfo < 0 || face.texinfo >= context.texinfos.size() ) continue; + + int32_t texDataID = context.texinfos[face.texinfo].texData; + if ( texDataID < 0 || texDataID >= context.texdatas.size() ) continue; + + size_t materialID = context.texdataToMaterial[texDataID]; + auto& matName = graph.materials[materialID]; + + // deduce which group to use + bool isSkybox = (m == 0 && context.skyboxFaces.count(faceID) > 0); + if ( !isSkybox && m == 0 && context.skyArea != -1 ) { + int32_t se0 = context.surfedges[face.firstedge]; + int32_t se1 = context.surfedges[face.firstedge + 1]; + int32_t se2 = context.surfedges[face.firstedge + 2]; + + uint16_t v0 = se0 >= 0 ? context.edges[se0].x : context.edges[-se0].y; + uint16_t v1 = se1 >= 0 ? context.edges[se1].x : context.edges[-se1].y; + uint16_t v2 = se2 >= 0 ? context.edges[se2].x : context.edges[-se2].y; + + pod::Vector3f p0 = context.vertices[v0]; + pod::Vector3f p1 = context.vertices[v1]; + pod::Vector3f p2 = context.vertices[v2]; + + pod::Vector3f normal = uf::vector::normalize(uf::vector::cross(p1 - p0, p2 - p0)); + + pod::Vector3f testPos = p0 + normal * 2.0f; + + int32_t testLeafIdx = impl::findLeaf(context, testPos); + if ( testLeafIdx >= 0 && testLeafIdx < context.leafs.size() ) { + int16_t testArea = context.leafs[testLeafIdx].area_flags & 0x01FF; + if ( testArea == context.skyArea ) { + isSkybox = true; + } + } + } + + // read brush + auto& meshlet = (isSkybox ? skyboxMeshlets : meshlets)[materialID]; + meshlet.primitive.instance.materialID = materialID; + + 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 ); + continue; + } + + + const auto edgeID = face.firstedge; + int32_t pivotSurfEdge = context.surfedges[edgeID]; + uint16_t pivotVertID = pivotSurfEdge >= 0 ? context.edges[pivotSurfEdge].x : context.edges[-pivotSurfEdge].y; + pod::Vector3f p0 = impl::convertPos( context.vertices[pivotVertID] ); + + const auto& plane = context.planes[face.planenum]; + + pod::Vector3f faceNormal = {0.0f, 0.0f, 0.0f}; + + for ( int16_t i = 1; i < face.numedges - 1; ++i ) { + int32_t se1 = context.surfedges[edgeID + i]; + int32_t se2 = context.surfedges[edgeID + i + 1]; + + uint16_t v1 = se1 >= 0 ? context.edges[se1].x : context.edges[-se1].y; + uint16_t v2 = se2 >= 0 ? context.edges[se2].x : context .edges[-se2].y; + + pod::Vector3f p1 = impl::convertPos( context.vertices[v1] ); + pod::Vector3f p2 = impl::convertPos( context.vertices[v2] ); + + faceNormal += uf::vector::cross(p1 - p0, p2 - p0); + } + + faceNormal = uf::vector::normalize(faceNormal); + + for ( int16_t i = 1; i < face.numedges - 1; ++i ) { + int32_t se1 = context.surfedges[edgeID + i]; + int32_t se2 = context.surfedges[edgeID + i + 1]; + + uint16_t v1 = se1 >= 0 ? context.edges[se1].x : context.edges[-se1].y; + uint16_t v2 = se2 >= 0 ? context.edges[se2].x : context.edges[-se2].y; + + pod::Vector3f p1 = impl::convertPos( context.vertices[v1] ); + pod::Vector3f p2 = impl::convertPos( context.vertices[v2] ); + + impl::addVertex( context, meshlet, pivotVertID, p0, faceNormal, faceID ); + impl::addVertex( context, meshlet, v1, p1, faceNormal, faceID ); + impl::addVertex( context, meshlet, v2, p2, faceNormal, faceID ); + } + } + + if ( !meshlets.empty() ) { + auto meshName = ::fmt::format("model_{}", m); + context.modelToMesh[m] = graph.meshes.size(); + + graph.meshes.emplace_back(meshName); + graph.primitives.emplace_back(meshName); + + auto& mesh = storage.meshes[meshName]; + auto& primitives = storage.primitives[meshName]; + + // for ( auto& pair : meshlets ) uf::mesh::tangents( pair.second.vertices, pair.second.indices ); + + // slice worldspawn + if ( false && m == 0 ) { + uf::meshgrid::Grid grid; + grid.divisions = {8, 8, 8}; + auto mlets = uf::stl::values( meshlets ); + auto partitioned = uf::meshgrid::partition( grid, mlets, EPS, true, true ); + mesh.compile( partitioned, primitives ); + } else { + mesh.compile( meshlets, primitives ); + } + } + + if ( !skyboxMeshlets.empty() ) { + auto meshName = ::fmt::format("model_{}_skybox", m); + graph.meshes.emplace_back(meshName); + graph.primitives.emplace_back(meshName); + + auto& mesh = storage.meshes[meshName]; + auto& primitives = storage.primitives[meshName]; + mesh.compile( skyboxMeshlets, primitives ); + + // create a skybox node for this + auto nodeID = graph.nodes.size(); + auto& node = graph.nodes.emplace_back(); + node.name = "skybox"; + node.mesh = (int32_t)(graph.meshes.size() - 1); + graph.root.children.emplace_back(nodeID); + } + } + // read static props for ( const auto& item : context.gameLumps ) { if ( item.id == 1936749168 ) { // 'sprp' (Static Props) @@ -991,6 +1174,16 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co metadata["model"] = dict[type]; metadata["origin"] = ::fmt::format("{} {} {}", origin.x, origin.y, origin.z); metadata["angles"] = ::fmt::format("{} {} {}", angles.x, angles.y, angles.z); + + if ( context.skyArea != -1 ) { + int32_t propLeafIdx = impl::findLeaf(context, origin); + if ( propLeafIdx >= 0 && propLeafIdx < context.leafs.size() ) { + int16_t propArea = context.leafs[propLeafIdx].area_flags & 0x01FF; + if ( propArea == context.skyArea ) { + metadata["skyboxed"] = true; + } + } + } graph.root.children.emplace_back( nodeID ); } @@ -1004,6 +1197,9 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co // load materials uf::stl::vector missing_pixels = { 255, 0, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 0, 255, 255 }; for ( auto matName : graph.materials ) { + if ( matName == "" ) { + continue; + } uf::Serializer vmt; auto vmtPath = ::fmt::format("materials/{}.vmt", matName); auto vtfPath = ::fmt::format("materials/{}.vtf", matName); @@ -1054,13 +1250,15 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co texture.viewType = uf::renderer::enums::Image::VIEW_TYPE_CUBE; if ( ext::valve::loadVtf( image, vtfPath ) ) { - UF_MSG_DEBUG("Loaded cubemap={}", matName); material.indexCubemap = textureID; } } } + // bumpmap + if ( vmt["$ssbump"].as(0) == 1 ) { + material.indexNormal = -1; // normal map - if ( vmt["$bumpmap"].is() ) { + } else if ( vmt["$bumpmap"].is() ) { auto matName = uf::string::lowercase(vmt["$bumpmap"].as()); auto vtfPath = ::fmt::format("materials/{}.vtf", matName); diff --git a/engine/src/ext/valve/mdl.cpp b/engine/src/ext/valve/mdl.cpp index 0d3e483c..1a3bca65 100644 --- a/engine/src/ext/valve/mdl.cpp +++ b/engine/src/ext/valve/mdl.cpp @@ -320,94 +320,66 @@ bool ext::valve::loadMdl( pod::Graph& graph, const uf::stl::string& filename ) { const impl::mstudiomesh_t* mdlMeshes = (const impl::mstudiomesh_t*)((uint8_t*)&mdlModels[m] + mdlModels[m].meshindex); for ( int meshID = 0; meshID < lod0.numMeshes; ++meshID ) { - const impl::vtxMesh_t& mesh = meshes[meshID]; - const impl::mstudiomesh_t& mdlMesh = mdlMeshes[meshID]; + const impl::vtxMesh_t& mesh = meshes[meshID]; + const impl::mstudiomesh_t& mdlMesh = mdlMeshes[meshID]; - auto& meshlet = meshlets.emplace_back(); - uf::stl::unordered_map vertRemap; + auto& meshlet = meshlets.emplace_back(); + uf::stl::unordered_map vertRemap; - const impl::vtxStripGroup_t* stripGroups = (const impl::vtxStripGroup_t*)((uint8_t*)&mesh + mesh.stripGroupHeaderOffset); - for ( int sg = 0; sg < mesh.numStripGroups; ++sg ) { - const impl::vtxStripGroup_t& stripGroup = stripGroups[sg]; + const impl::vtxStripGroup_t* stripGroups = (const impl::vtxStripGroup_t*)((uint8_t*)&mesh + mesh.stripGroupHeaderOffset); + for ( int sg = 0; sg < mesh.numStripGroups; ++sg ) { + const impl::vtxStripGroup_t& stripGroup = stripGroups[sg]; - const uint16_t* indices = (const uint16_t*)((uint8_t*)&stripGroup + stripGroup.indexOffset); - const impl::vtxVertex_t* vtxVerts = (const impl::vtxVertex_t*)((uint8_t*)&stripGroup + stripGroup.vertOffset); + const uint16_t* indices = (const uint16_t*)((uint8_t*)&stripGroup + stripGroup.indexOffset); + const impl::vtxVertex_t* vtxVerts = (const impl::vtxVertex_t*)((uint8_t*)&stripGroup + stripGroup.vertOffset); - for ( int i = 0; i < stripGroup.numIndices; i += 3 ) { - uint32_t tri[3]; - for ( int j = 0; j < 3; ++j ) { - uint16_t localVertIndex = indices[i + j]; - const impl::vtxVertex_t& vtxVert = vtxVerts[localVertIndex]; - uint16_t originalVvdID = vtxVert.origMeshVertID; + for ( int i = 0; i < stripGroup.numIndices; i += 3 ) { + uint32_t tri[3]; + for ( int j = 0; j < 3; ++j ) { + uint16_t localVertIndex = indices[i + j]; + const impl::vtxVertex_t& vtxVert = vtxVerts[localVertIndex]; + uint16_t originalVvdID = vtxVert.origMeshVertID; - if ( vertRemap.find(originalVvdID) == vertRemap.end() ) { - vertRemap[originalVvdID] = meshlet.vertices.size(); - auto& vert = meshlet.vertices.emplace_back(); + if ( vertRemap.find(originalVvdID) == vertRemap.end() ) { + vertRemap[originalVvdID] = meshlet.vertices.size(); + auto& vert = meshlet.vertices.emplace_back(); - const auto& srcVert = lod0Vertices[mdlMesh.vertexoffset + originalVvdID]; + const auto& srcVert = lod0Vertices[mdlMesh.vertexoffset + originalVvdID]; - vert.position = impl::convertPos( srcVert.m_vecPosition ); - vert.normal = uf::vector::normalize( impl::convertPos( srcVert.m_vecNormal, 1.0f ) ); - if ( !lod0Tangents.empty() ) { - vert.tangent = uf::vector::normalize( impl::convertPos( lod0Tangents[mdlMesh.vertexoffset + originalVvdID], 1.0f ) ); - } else { - vert.tangent = {}; - } + vert.position = impl::convertPos( srcVert.m_vecPosition ); + vert.normal = uf::vector::normalize( impl::convertPos( srcVert.m_vecNormal, 1.0f ) ); - vert.uv = srcVert.m_vecTexCoord; - vert.color = {1.0f, 1.0f, 1.0f, 1.0f}; - vert.joints.x = srcVert.m_BoneWeights.numbones > 0 ? std::max(0, srcVert.m_BoneWeights.bone[0]) : 0; - vert.joints.y = srcVert.m_BoneWeights.numbones > 1 ? std::max(0, srcVert.m_BoneWeights.bone[1]) : 0; - vert.joints.z = srcVert.m_BoneWeights.numbones > 2 ? std::max(0, srcVert.m_BoneWeights.bone[2]) : 0; - vert.joints.w = 0; + if ( !lod0Tangents.empty() ) { + vert.tangent = uf::vector::normalize( impl::convertPos( lod0Tangents[mdlMesh.vertexoffset + originalVvdID], 1.0f ) ); + } - vert.weights.x = srcVert.m_BoneWeights.numbones > 0 ? srcVert.m_BoneWeights.weight[0] : 1.0f; - vert.weights.y = srcVert.m_BoneWeights.numbones > 1 ? srcVert.m_BoneWeights.weight[1] : 0.0f; - vert.weights.z = srcVert.m_BoneWeights.numbones > 2 ? srcVert.m_BoneWeights.weight[2] : 0.0f; - vert.weights.w = 0.0f; + vert.uv = srcVert.m_vecTexCoord; + vert.color = {1.0f, 1.0f, 1.0f, 1.0f}; + vert.joints.x = srcVert.m_BoneWeights.numbones > 0 ? std::max(0, srcVert.m_BoneWeights.bone[0]) : 0; + vert.joints.y = srcVert.m_BoneWeights.numbones > 1 ? std::max(0, srcVert.m_BoneWeights.bone[1]) : 0; + vert.joints.z = srcVert.m_BoneWeights.numbones > 2 ? std::max(0, srcVert.m_BoneWeights.bone[2]) : 0; + vert.joints.w = 0; - // Bounds calculation - auto& bounds = meshlet.primitive.instance.bounds; - if ( vertRemap.size() == 1 ) { - bounds.min = bounds.max = vert.position; - } else { - bounds.min = uf::vector::min( bounds.min, vert.position ); - bounds.max = uf::vector::max( bounds.max, vert.position ); - } - } + vert.weights.x = srcVert.m_BoneWeights.numbones > 0 ? srcVert.m_BoneWeights.weight[0] : 1.0f; + vert.weights.y = srcVert.m_BoneWeights.numbones > 1 ? srcVert.m_BoneWeights.weight[1] : 0.0f; + vert.weights.z = srcVert.m_BoneWeights.numbones > 2 ? srcVert.m_BoneWeights.weight[2] : 0.0f; + vert.weights.w = 0.0f; - meshlet.indices.push_back(tri[j] = vertRemap[originalVvdID]); - } + auto& bounds = meshlet.primitive.instance.bounds; + if ( vertRemap.size() == 1 ) { + bounds.min = bounds.max = vert.position; + } else { + bounds.min = uf::vector::min( bounds.min, vert.position ); + bounds.max = uf::vector::max( bounds.max, vert.position ); + } + } - if ( lod0Tangents.empty() ) { - auto& v0 = meshlet.vertices[tri[0]]; - auto& v1 = meshlet.vertices[tri[1]]; - auto& v2 = meshlet.vertices[tri[2]]; + meshlet.indices.push_back(tri[j] = vertRemap[originalVvdID]); + } + } + } - pod::Vector3f edge1 = v1.position - v0.position; - pod::Vector3f edge2 = v2.position - v0.position; - pod::Vector2f duv1 = v1.uv - v0.uv; - pod::Vector2f duv2 = v2.uv - v0.uv; - - float f = 1.0f / (duv1.x * duv2.y - duv2.x * duv1.y); - pod::Vector3f tangent = { - f * (duv2.y * edge1.x - duv1.y * edge2.x), - f * (duv2.y * edge1.y - duv1.y * edge2.y), - f * (duv2.y * edge1.z - duv1.y * edge2.z) - }; - - v0.tangent += tangent; - v1.tangent += tangent; - v2.tangent += tangent; - } - } - } - - if ( lod0Tangents.empty() ) { - for ( auto& vert : meshlet.vertices ) { - vert.tangent = uf::vector::normalize(vert.tangent - vert.normal * uf::vector::dot(vert.normal, vert.tangent)); - } - } + if ( lod0Tangents.empty() ) uf::mesh::tangents( meshlet.vertices, meshlet.indices ); size_t materialID = 0; uf::stl::string matName = "missing_texture"; diff --git a/engine/src/ext/valve/vtf.cpp b/engine/src/ext/valve/vtf.cpp index e9854a00..4d485b0c 100644 --- a/engine/src/ext/valve/vtf.cpp +++ b/engine/src/ext/valve/vtf.cpp @@ -12,6 +12,7 @@ namespace impl { constexpr uint32_t IMAGE_FORMAT_DXT5 = 15; constexpr uint32_t TEXTUREFLAGS_ENVMAP = 0x00002000; + constexpr uint32_t TEXTUREFLAGS_NORMAL = 0x00000080; #pragma pack(push, 1) struct VTFHeader { @@ -284,5 +285,28 @@ bool ext::valve::loadVtf( pod::Image& image, const uf::stl::string& filename ) { } } + if ( (header->flags & impl::TEXTUREFLAGS_NORMAL) != 0 && header->highResImageFormat == impl::IMAGE_FORMAT_DXT5 ) { + size_t pixelCount = image.pixels.size() / 4; + for ( size_t i = 0; i < pixelCount; ++i ) { + uint8_t& r = image.pixels[i * 4 + 0]; + uint8_t& g = image.pixels[i * 4 + 1]; + uint8_t& b = image.pixels[i * 4 + 2]; + uint8_t& a = image.pixels[i * 4 + 3]; + + float x = (a / 255.0f) * 2.0f - 1.0f; + float y = (g / 255.0f) * 2.0f - 1.0f; + + y = -y; + + float z = std::sqrt(std::max(1.0f - (x * x + y * y), 0.0f)); + + r = (uint8_t)((x * 0.5f + 0.5f) * 255.0f); + g = (uint8_t)((y * 0.5f + 0.5f) * 255.0f); + b = (uint8_t)((z * 0.5f + 0.5f) * 255.0f); + + a = 255; + } + } + return true; } \ No newline at end of file diff --git a/engine/src/ext/vulkan/graphic.cpp b/engine/src/ext/vulkan/graphic.cpp index f823f7f2..fed111d9 100644 --- a/engine/src/ext/vulkan/graphic.cpp +++ b/engine/src/ext/vulkan/graphic.cpp @@ -447,7 +447,8 @@ void ext::vulkan::Pipeline::record( const Graphic& graphic, VkCommandBuffer comm struct PushConstant { uint32_t pass; uint32_t draw; - } pushConstant = { pass, draw }; + uint32_t aux; + } pushConstant = { pass, draw, descriptor.aux }; vkCmdPushConstants( commandBuffer, pipelineLayout, shader->descriptor.stage, 0, sizeof(pushConstant), &pushConstant ); } else if ( !shader->pushConstants.empty() ) { auto& pushConstant = shader->pushConstants.front(); diff --git a/engine/src/ext/vulkan/rendermodes/deferred.cpp b/engine/src/ext/vulkan/rendermodes/deferred.cpp index b436dde0..f6906b9c 100644 --- a/engine/src/ext/vulkan/rendermodes/deferred.cpp +++ b/engine/src/ext/vulkan/rendermodes/deferred.cpp @@ -29,8 +29,6 @@ #endif namespace { - const uf::stl::string DEFERRED_MODE = "compute"; - namespace postprocesses { struct { ext::vulkan::Buffer atomicCounter; @@ -243,25 +241,6 @@ void ext::vulkan::DeferredRenderMode::initialize( Device& device ) { /*.layer = */0, /*.autoBuildPipeline =*/ true ); - if ( DEFERRED_MODE == "fragment" ) { - renderTarget.addPass( - /*.*/ VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_ACCESS_INPUT_ATTACHMENT_READ_BIT, - /*.colors =*/ { attachments.color, attachments.scratch, attachments.motion }, - #if BARYCENTRIC - #if !BARYCENTRIC_CALCULATE - /*.inputs =*/ { attachments.id, attachments.depth, attachments.bary }, - #else - /*.inputs =*/ { attachments.id, attachments.depth }, - #endif - #else - /*.inputs =*/ { attachments.id, attachments.depth, attachments.uv, attachments.normal }, - #endif - /*.resolve =*/{}, - /*.depth = */attachments.depth, - /*.layer = */0, - /*.autoBuildPipeline =*/ false - ); - } // metadata.outputs.emplace_back(metadata.attachments["output"]); renderTarget.initialize( device ); @@ -339,34 +318,19 @@ void ext::vulkan::DeferredRenderMode::initialize( Device& device ) { } if ( settings::pipelines::deferred ) { - if ( DEFERRED_MODE == "compute" ) { - uf::stl::string computeShaderFilename = "comp.spv"; { - std::pair settings[] = { - { uf::renderer::settings::pipelines::rt, "rt.comp" }, - { uf::renderer::settings::pipelines::vxgi, "vxgi.comp" }, - { msaa > 1, "msaa.comp" }, - }; - FOR_ARRAY( settings ) if ( settings[i].first ) computeShaderFilename = uf::string::replace( computeShaderFilename, "comp", settings[i].second ); - } - computeShaderFilename = uf::io::root+"/shaders/display/deferred/comp/" + computeShaderFilename; - blitter.material.attachShader(uf::io::resolveURI(computeShaderFilename), uf::renderer::enums::Shader::COMPUTE, "deferred"); - UF_MSG_DEBUG("Using deferred shader: {}", computeShaderFilename); - } else if ( DEFERRED_MODE == "fragment" ) { - uf::stl::string vertexShaderFilename = "vert.spv"; - uf::stl::string fragmentShaderFilename = "frag.spv"; { - std::pair settings[] = { - { uf::renderer::settings::pipelines::rt, "rt.frag" }, - { uf::renderer::settings::pipelines::vxgi, "vxgi.frag" }, - { msaa > 1, "msaa.frag" }, - }; - FOR_ARRAY( settings ) if ( settings[i].first ) fragmentShaderFilename = uf::string::replace( fragmentShaderFilename, "frag", settings[i].second ); - } - fragmentShaderFilename = uf::io::root+"/shaders/display/deferred/frag/" + fragmentShaderFilename; - blitter.material.attachShader(uf::io::resolveURI(fragmentShaderFilename), uf::renderer::enums::Shader::FRAGMENT, "deferred"); - UF_MSG_DEBUG("Using deferred shader: {}", fragmentShaderFilename); + uf::stl::string computeShaderFilename = "comp.spv"; { + std::pair settings[] = { + { uf::renderer::settings::pipelines::rt, "rt.comp" }, + { uf::renderer::settings::pipelines::vxgi, "vxgi.comp" }, + { msaa > 1, "msaa.comp" }, + }; + FOR_ARRAY( settings ) if ( settings[i].first ) computeShaderFilename = uf::string::replace( computeShaderFilename, "comp", settings[i].second ); } + computeShaderFilename = uf::io::root+"/shaders/display/deferred/comp/" + computeShaderFilename; + blitter.material.attachShader(uf::io::resolveURI(computeShaderFilename), uf::renderer::enums::Shader::COMPUTE, "deferred"); + UF_MSG_DEBUG("Using deferred shader: {}", computeShaderFilename); - auto& shader = blitter.material.getShader(DEFERRED_MODE, "deferred"); + auto& shader = blitter.material.getShader("compute", "deferred"); size_t maxLights = uf::config["engine"]["scenes"]["lights"]["max"].as(512); size_t maxTextures2D = uf::config["engine"]["scenes"]["textures"]["max"]["2D"].as(512); @@ -561,8 +525,8 @@ void ext::vulkan::DeferredRenderMode::build( bool resized ) { } // (re)bind aliases - if ( blitter.material.hasShader(DEFERRED_MODE, "deferred") ) { - auto& shader = blitter.material.getShader(DEFERRED_MODE, "deferred"); + if ( blitter.material.hasShader("compute", "deferred") ) { + auto& shader = blitter.material.getShader("compute", "deferred"); shader.metadata.aliases.buffers.clear(); shader.aliasBuffer( storage.buffers.camera ); @@ -587,15 +551,10 @@ void ext::vulkan::DeferredRenderMode::build( bool resized ) { descriptor.bind.height = height; descriptor.bind.depth = 1; - if ( settings::pipelines::deferred && blitter.material.hasShader(DEFERRED_MODE, "deferred") ) { + if ( settings::pipelines::deferred && blitter.material.hasShader("compute", "deferred") ) { descriptor.pipeline = "deferred"; - if ( DEFERRED_MODE == "fragment" ) { - descriptor.subpass = 1; - descriptor.bind.point = VK_PIPELINE_BIND_POINT_GRAPHICS; - } else if ( DEFERRED_MODE == "compute" ) { - descriptor.subpass = 0; - descriptor.bind.point = VK_PIPELINE_BIND_POINT_COMPUTE; - } + descriptor.subpass = 0; + descriptor.bind.point = VK_PIPELINE_BIND_POINT_COMPUTE; blitter.update( descriptor ); } @@ -894,48 +853,57 @@ void ext::vulkan::DeferredRenderMode::createCommandBuffers( const uf::stl::vecto vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); vkCmdSetViewport(commandBuffer, 0, 1, &viewport); vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + // render to geometry buffers { size_t currentPass = 0; size_t currentDraw = 0; + // render skybox geometry + for ( auto graphic : graphics ) { + if ( graphic->descriptor.renderMode != this->getName() ) continue; + if ( graphic->descriptor.renderTarget != 0 /*this->getName()*/ ) continue; + if ( graphic->descriptor.aux != 1 ) continue; + ext::vulkan::GraphicDescriptor descriptor = bindGraphicDescriptor(graphic->descriptor, currentSubpass); + device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::GENERIC, ::fmt::format("graphic[skybox][{}]", currentDraw) ); + graphic->record( commandBuffer, descriptor, 0, currentDraw++, frame ); + } + // clear depth buffer + if ( currentDraw > 0 ) { + VkClearAttachment clearDepth = {}; + clearDepth.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + clearDepth.clearValue.depthStencil = { depthClear, 0 }; + + VkClearRect clearRect = {}; + clearRect.rect.offset = { 0, 0 }; + clearRect.rect.extent = { width, height }; + clearRect.baseArrayLayer = 0; + clearRect.layerCount = metadata.eyes > 0 ? metadata.eyes : 1; + + vkCmdClearAttachments(commandBuffer, 1, &clearDepth, 1, &clearRect); + } + // render normal geometry for ( auto graphic : graphics ) { // only draw graphics that are assigned to this type of render mode if ( graphic->descriptor.renderMode != this->getName() ) continue; if ( graphic->descriptor.renderTarget != 0 /*this->getName()*/ ) continue; + if ( graphic->descriptor.aux != 0 ) continue; ext::vulkan::GraphicDescriptor descriptor = bindGraphicDescriptor(graphic->descriptor, currentSubpass); device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::GENERIC, ::fmt::format("graphic[{}]", currentDraw) ); graphic->record( commandBuffer, descriptor, 0, currentDraw++, frame ); } } - // skip deferred pass if RT is enabled, we still process geometry for a depth buffer - if ( DEFERRED_MODE == "fragment" ) { - device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::GENERIC, "nextSubpass" ); - vkCmdNextSubpass(commandBuffer, VK_SUBPASS_CONTENTS_INLINE); ++currentSubpass; - - size_t currentPass = 0; - size_t currentDraw = 0; - { - ext::vulkan::GraphicDescriptor descriptor = blitter.descriptor; // = bindGraphicDescriptor(blitter.descriptor, currentSubpass); - descriptor.renderMode = ""; - descriptor.pipeline = "deferred"; - descriptor.subpass = currentSubpass; - descriptor.bind.point = VK_PIPELINE_BIND_POINT_GRAPHICS; - device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::GENERIC, "deferred" ); - blitter.record(commandBuffer, descriptor, 0, currentDraw++, frame); - } - } device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::END, "renderPass[end]" ); vkCmdEndRenderPass(commandBuffer); #if 1 - if ( settings::pipelines::deferred && DEFERRED_MODE == "compute" && blitter.material.hasShader(DEFERRED_MODE, "deferred") ) { + if ( settings::pipelines::deferred && blitter.material.hasShader("compute", "deferred") ) { VkMemoryBarrier computeBarrier = {}; computeBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; computeBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; computeBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; vkCmdPipelineBarrier( commandBuffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 1, &computeBarrier, 0, nullptr, 0, nullptr ); - auto& shader = blitter.material.getShader(DEFERRED_MODE, "deferred"); + auto& shader = blitter.material.getShader("compute", "deferred"); ext::vulkan::GraphicDescriptor descriptor = blitter.descriptor; descriptor.renderMode = ""; descriptor.pipeline = "deferred";