cubemap handling from vbsp parser (eventually to be used for PBR irradiance or whatever i guess)
This commit is contained in:
parent
7034da71de
commit
d5f374350e
@ -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
|
||||
|
||||
@ -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],
|
||||
|
||||
@ -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": {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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" }
|
||||
]
|
||||
}
|
||||
@ -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 );
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -34,8 +34,6 @@ namespace pod {
|
||||
uf::stl::vector<uf::stl::string> images; //
|
||||
uf::stl::vector<uf::stl::string> materials; //
|
||||
uf::stl::vector<uf::stl::string> textures; //
|
||||
|
||||
uf::stl::vector<uf::stl::string> texture2Ds; //
|
||||
uf::stl::vector<uf::stl::string> samplers; //
|
||||
|
||||
// Lighting information
|
||||
@ -104,6 +102,8 @@ namespace pod {
|
||||
uf::stl::KeyMap<uf::stl::vector<pod::Matrix4f>> joints;
|
||||
uf::stl::KeyMap<uf::Entity*> entities;
|
||||
|
||||
uf::stl::vector<uf::renderer::TextureCube> cubemaps;
|
||||
|
||||
uf::stl::vector<uf::renderer::Texture2D> shadow2Ds;
|
||||
uf::stl::vector<uf::renderer::TextureCube> shadowCubes;
|
||||
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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 );
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 );
|
||||
|
||||
@ -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 );
|
||||
|
||||
@ -30,6 +30,12 @@
|
||||
// to-do: fix LOD1+ breaking
|
||||
|
||||
namespace {
|
||||
struct TextureDescriptor {
|
||||
bool srgb = false;
|
||||
size_t layers = 1;
|
||||
size_t references = 0;
|
||||
};
|
||||
|
||||
uf::stl::string keyedID( size_t id ) {
|
||||
return ::fmt::format("{}", id);
|
||||
}
|
||||
@ -55,28 +61,11 @@ namespace {
|
||||
|
||||
auto& storage = uf::graph::getStorage( graph );
|
||||
|
||||
for ( auto& key : storage.images.keys ) graphic.material.textures.emplace_back().aliasTexture( storage.images.map[key].handle );
|
||||
|
||||
// bind scene's voxel texture
|
||||
#if 0 && UF_USE_VULKAN
|
||||
if ( uf::renderer::settings::pipelines::vxgi ) {
|
||||
auto& scene = uf::scene::getCurrentScene();
|
||||
auto& sceneTextures = scene.getComponent<pod::SceneTextures>();
|
||||
for ( auto& t : sceneTextures.voxels.id ) graphic.material.textures.emplace_back().aliasTexture(t);
|
||||
for ( auto& t : sceneTextures.voxels.normal ) graphic.material.textures.emplace_back().aliasTexture(t);
|
||||
for ( auto& t : sceneTextures.voxels.radiance ) graphic.material.textures.emplace_back().aliasTexture(t);
|
||||
for ( auto& t : sceneTextures.voxels.output ) graphic.material.textures.emplace_back().aliasTexture(t);
|
||||
/*
|
||||
auto& shader = graphic.material.getShader("fragment", uf::renderer::settings::pipelines::names::vxgi);
|
||||
shader.textures.clear();
|
||||
|
||||
for ( auto& t : sceneTextures.voxels.id ) shader.textures.emplace_back().aliasTexture(t);
|
||||
for ( auto& t : sceneTextures.voxels.normal ) shader.textures.emplace_back().aliasTexture(t);
|
||||
for ( auto& t : sceneTextures.voxels.radiance ) shader.textures.emplace_back().aliasTexture(t);
|
||||
for ( auto& t : sceneTextures.voxels.output ) shader.textures.emplace_back().aliasTexture(t);
|
||||
*/
|
||||
for ( auto& key : storage.images.keys ) {
|
||||
auto& texture = storage.images.map[key].handle;
|
||||
if ( texture.viewType != uf::renderer::enums::Image::VIEW_TYPE_2D ) continue;
|
||||
graphic.material.textures.emplace_back().aliasTexture( texture );
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void bindShaders( pod::Graph& graph, uf::Object& entity, uf::Mesh& mesh ) {
|
||||
@ -91,8 +80,10 @@ namespace {
|
||||
uf::stl::string root = uf::io::directory( graph.name );
|
||||
size_t texture2Ds = 0;
|
||||
size_t texture3Ds = 0;
|
||||
size_t textureCubes = 0;
|
||||
for ( auto& texture : graphic.material.textures ) {
|
||||
if ( texture.width > 1 && texture.height > 1 && texture.depth == 1 && texture.layers == 1 ) ++texture2Ds;
|
||||
else if ( texture.width > 1 && texture.height > 1 && texture.depth == 1 && texture.layers == 6 ) ++textureCubes;
|
||||
else if ( texture.width > 1 && texture.height > 1 && texture.depth > 1 && texture.layers == 1 ) ++texture3Ds;
|
||||
}
|
||||
|
||||
@ -814,8 +805,8 @@ void uf::graph::process( pod::Graph& graph ) {
|
||||
} );
|
||||
}
|
||||
|
||||
//
|
||||
uf::stl::unordered_map<uf::stl::string, bool> isSrgb;
|
||||
// stores temporary metadata for textures (that can be deduced at runtime)
|
||||
uf::stl::unordered_map<uf::stl::string, TextureDescriptor> textureDescriptors;
|
||||
|
||||
// process lightmap
|
||||
UF_DEBUG_TIMER_MULTITRACE("Parsing lightmaps");
|
||||
@ -823,7 +814,7 @@ void uf::graph::process( pod::Graph& graph ) {
|
||||
if ( storage.textures.map.count("lightmap_atlas") > 0 ) {
|
||||
graphMetadataJson["lights"]["lightmap"] = true;
|
||||
graphMetadataJson["baking"]["enabled"] = false;
|
||||
isSrgb["lightmap_atlas"] = false;
|
||||
textureDescriptors["lightmap_atlas"].srgb = false;
|
||||
} else {
|
||||
constexpr const char* UF_GRAPH_DEFAULT_LIGHTMAP = "./lightmap.%i.png";
|
||||
uf::stl::unordered_map<size_t, uf::stl::string> filenames;
|
||||
@ -899,7 +890,7 @@ void uf::graph::process( pod::Graph& graph ) {
|
||||
graphMetadataJson["lights"]["lightmaps"][i] = f;
|
||||
graphMetadataJson["baking"]["enabled"] = false;
|
||||
|
||||
isSrgb[f] = false;
|
||||
textureDescriptors[f].srgb = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -912,16 +903,27 @@ void uf::graph::process( pod::Graph& graph ) {
|
||||
}
|
||||
}
|
||||
|
||||
// process cubemaps
|
||||
ext::json::forEach( graph.metadata["cubemaps"], [&]( const ext::json::Value& value ) {
|
||||
auto name = value.as<uf::stl::string>();
|
||||
textureDescriptors[name].layers = 6;
|
||||
});
|
||||
|
||||
// figure out what texture is what exactly
|
||||
UF_DEBUG_TIMER_MULTITRACE("Determining format of textures");
|
||||
for ( auto& key : graph.materials ) {
|
||||
auto& material = storage.materials[key];
|
||||
auto ID = material.indexAlbedo;
|
||||
|
||||
if ( !(0 <= ID && ID < graph.textures.size()) ) continue;
|
||||
if ( 0 <= material.indexCubemap && material.indexCubemap < graph.textures.size() ) {
|
||||
auto texName = graph.textures[material.indexCubemap];
|
||||
textureDescriptors[texName].layers = 6;
|
||||
textureDescriptors[texName].srgb = true;
|
||||
}
|
||||
|
||||
auto texName = graph.textures[ID];
|
||||
isSrgb[texName] = true;
|
||||
if ( (0 <= material.indexAlbedo && material.indexAlbedo < graph.textures.size() ) ) {
|
||||
auto texName = graph.textures[material.indexAlbedo];
|
||||
textureDescriptors[texName].srgb = true;
|
||||
}
|
||||
}
|
||||
|
||||
UF_DEBUG_TIMER_MULTITRACE("Processing images...");
|
||||
@ -949,7 +951,10 @@ void uf::graph::process( pod::Graph& graph ) {
|
||||
|
||||
texture.sampler.descriptor.filter.min = filter;
|
||||
texture.sampler.descriptor.filter.mag = filter;
|
||||
texture.srgb = isSrgb[key];
|
||||
texture.layers = textureDescriptors[key].layers;
|
||||
texture.srgb = textureDescriptors[key].srgb;
|
||||
|
||||
image.size.y /= texture.layers;
|
||||
|
||||
texture.loadFromImage( image );
|
||||
#if UF_ENV_DREAMCAST
|
||||
@ -1072,6 +1077,25 @@ void uf::graph::process( pod::Graph& graph ) {
|
||||
|
||||
// remap textures->images IDs
|
||||
UF_DEBUG_TIMER_MULTITRACE("Remapping texture -> image IDs");
|
||||
|
||||
// separate texture2Ds and textureCubes from images
|
||||
uf::stl::vector<int32_t> gpuIndex2D(storage.images.keys.size(), -1);
|
||||
uf::stl::vector<int32_t> gpuIndexCube(storage.images.keys.size(), -1);
|
||||
|
||||
int32_t count2D = 0;
|
||||
int32_t countCube = 0;
|
||||
for ( size_t i = 0; i < storage.images.keys.size(); ++i ) {
|
||||
auto& key = storage.images.keys[i];
|
||||
auto& image = storage.images.map[key].handle;
|
||||
|
||||
if ( image.viewType == uf::renderer::enums::Image::VIEW_TYPE_CUBE ) {
|
||||
gpuIndexCube[i] = countCube++;
|
||||
} else {
|
||||
gpuIndex2D[i] = count2D++;
|
||||
}
|
||||
}
|
||||
UF_MSG_DEBUG("texture2Ds={}, textureCubes={}", count2D, countCube);
|
||||
|
||||
for ( auto& name : graph.textures ) {
|
||||
auto& texture = storage.textures[name];
|
||||
auto& keys = storage.images.keys;
|
||||
@ -1080,7 +1104,15 @@ void uf::graph::process( pod::Graph& graph ) {
|
||||
if ( !(0 <= texture.index && texture.index < graph.images.size()) ) continue;
|
||||
|
||||
auto& needle = graph.images[texture.index];
|
||||
texture.index = indices[needle];
|
||||
int32_t storageImageIndex = indices[needle];
|
||||
|
||||
auto& image = storage.images.map[keys[storageImageIndex]].handle;
|
||||
if ( image.viewType == uf::renderer::enums::Image::VIEW_TYPE_CUBE ) {
|
||||
texture.index = gpuIndexCube[storageImageIndex];
|
||||
} else {
|
||||
texture.index = gpuIndex2D[storageImageIndex];
|
||||
}
|
||||
UF_MSG_DEBUG("name={}, index={}, layers={}, type={}", name, texture.index, image.layers, image.viewType == uf::renderer::enums::Image::VIEW_TYPE_CUBE ? "cube" : "2D" );
|
||||
}
|
||||
|
||||
// remap materials->texture IDs
|
||||
@ -1089,7 +1121,7 @@ void uf::graph::process( pod::Graph& graph ) {
|
||||
auto& material = storage.materials[name];
|
||||
auto& keys = storage.textures.keys;
|
||||
auto& indices = storage.textures.indices;
|
||||
int32_t* IDs[] = { &material.indexAlbedo, &material.indexNormal, &material.indexEmissive, &material.indexOcclusion, &material.indexMetallicRoughness };
|
||||
int32_t* IDs[] = { &material.indexAlbedo, &material.indexNormal, &material.indexEmissive, &material.indexOcclusion, &material.indexMetallicRoughness, &material.indexCubemap };
|
||||
for ( auto* pointer : IDs ) {
|
||||
auto& ID = *pointer;
|
||||
if ( !(0 <= ID && ID < graph.textures.size()) ) continue;
|
||||
@ -1122,6 +1154,15 @@ void uf::graph::process( pod::Graph& graph ) {
|
||||
auto& needle = graph.textures[instance.lightmapID];
|
||||
instance.lightmapID = indices[needle];
|
||||
}
|
||||
if ( 0 <= instance.cubemapID && instance.cubemapID < graph.textures.size() ) {
|
||||
auto& keys = storage.textures.keys;
|
||||
auto& indices = storage.textures.indices;
|
||||
|
||||
if ( !(0 <= instance.cubemapID && instance.cubemapID < graph.textures.size()) ) continue;
|
||||
|
||||
auto& needle = graph.textures[instance.cubemapID];
|
||||
instance.cubemapID = indices[needle];
|
||||
}
|
||||
|
||||
// remap a skinID as an actual jointID
|
||||
if ( 0 <= instance.jointID && instance.jointID < graph.skins.size() ) {
|
||||
@ -1148,6 +1189,13 @@ void uf::graph::process( pod::Graph& graph ) {
|
||||
auto& needle = graph.textures[instance.lightmapID];
|
||||
instance.lightmapID = indices[needle];
|
||||
}
|
||||
if ( 0 <= instance.cubemapID && instance.cubemapID < graph.textures.size() ) {
|
||||
auto& keys = storage.textures.keys;
|
||||
auto& indices = storage.textures.indices;
|
||||
auto& needle = graph.textures[instance.cubemapID];
|
||||
instance.cubemapID = indices[needle];
|
||||
}
|
||||
|
||||
if ( 0 <= instance.jointID && instance.jointID < graph.skins.size() ) {
|
||||
auto& skinName = graph.skins[instance.jointID];
|
||||
instance.jointID = 0;
|
||||
@ -1898,6 +1946,7 @@ void uf::graph::destroy( pod::Graph::Storage& storage, bool soft ) {
|
||||
}
|
||||
for ( auto& t : storage.shadow2Ds ) t.destroy();
|
||||
for ( auto& t : storage.shadowCubes ) t.destroy();
|
||||
for ( auto& t : storage.cubemaps ) t.destroy();
|
||||
|
||||
for ( auto pair : storage.atlases.map ) pair.second.clear();
|
||||
for ( auto pair : storage.meshes.map ) pair.second.destroy();
|
||||
@ -1916,6 +1965,7 @@ void uf::graph::destroy( pod::Graph::Storage& storage, bool soft ) {
|
||||
storage.entities.clear();
|
||||
storage.shadow2Ds.clear();
|
||||
storage.shadowCubes.clear();
|
||||
storage.cubemaps.clear();
|
||||
|
||||
// cleanup storage buffers
|
||||
if ( !soft ) {
|
||||
@ -2195,13 +2245,13 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
// lightmaps are not sRGB, while textures (usually) are
|
||||
#define INCREMENT_TEXTURE_REFCOUNT( ID, isSRGB ) if ( 0 <= ID && ID < graph.textures.size() ) {\
|
||||
auto& key = graph.textures[ID];\
|
||||
textureReferences[key] += visible ? 1 : 0;\
|
||||
isSrgb[key] = isSRGB;\
|
||||
textureDescriptors[key].srgb = isSRGB;\
|
||||
textureDescriptors[key].references += visible ? 1 : 0;\
|
||||
textureDescriptors[key].layers = 1;\
|
||||
}
|
||||
|
||||
uf::stl::unordered_map<uf::stl::string, bool> isSrgb; // cringe
|
||||
uf::stl::unordered_map<uf::stl::string, size_t> textureReferences;
|
||||
// determine which textures are in use or not
|
||||
uf::stl::unordered_map<uf::stl::string, TextureDescriptor> textureDescriptors;
|
||||
for ( size_t drawID = 0; drawID < primitives.size(); ++drawID ) {
|
||||
auto& primitive = primitives[drawID];
|
||||
auto& instance = primitive.instance;
|
||||
@ -2223,10 +2273,10 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
}
|
||||
|
||||
// iterate through our ref counts
|
||||
for ( auto& [ key, count ] : textureReferences ) {
|
||||
for ( auto& [ key, descriptor ] : textureDescriptors ) {
|
||||
auto& image = storage.images[key].data;
|
||||
auto& texture = storage.images[key].handle;
|
||||
bool visible = count > 0;
|
||||
bool visible = descriptor.references > 0;
|
||||
|
||||
if ( visible && (!texture.generated() || texture.aliased) ) {
|
||||
// load image
|
||||
@ -2257,7 +2307,8 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
|
||||
texture.sampler.descriptor.filter.min = filter;
|
||||
texture.sampler.descriptor.filter.mag = filter;
|
||||
texture.srgb = isSrgb[key];
|
||||
texture.layers = descriptor.layers;
|
||||
texture.srgb = descriptor.srgb;
|
||||
|
||||
texture.loadFromImage( image );
|
||||
#if UF_ENV_DREAMCAST
|
||||
|
||||
@ -165,6 +165,11 @@ namespace impl {
|
||||
float alpha;
|
||||
};
|
||||
|
||||
struct BspCubemap {
|
||||
pod::Vector3i origin;
|
||||
int32_t size;
|
||||
};
|
||||
|
||||
struct BspTexInfo {
|
||||
pod::Vector4f textureVecs[2];
|
||||
pod::Vector4f lightmapVecs[2];
|
||||
@ -214,7 +219,7 @@ namespace impl {
|
||||
uf::stl::vector<impl::BspDispVert> dispverts;
|
||||
// disptris
|
||||
uf::stl::vector<uint8_t> pakfile;
|
||||
// cubemaps
|
||||
uf::stl::vector<impl::BspCubemap> cubemaps;
|
||||
// overlay
|
||||
uf::stl::vector<uint8_t> lighting;
|
||||
// ambient lighting
|
||||
@ -225,6 +230,7 @@ namespace impl {
|
||||
|
||||
uf::stl::vector<int32_t> modelToMesh;
|
||||
uf::stl::vector<int32_t> texdataToMaterial;
|
||||
uf::stl::vector<int32_t> cubemapIDs;
|
||||
pod::Atlas lightmapAtlas;
|
||||
};
|
||||
|
||||
@ -252,6 +258,28 @@ namespace impl {
|
||||
return io;
|
||||
}
|
||||
|
||||
int32_t findClosestCubemap( const impl::BspContext& context, const pod::Vector3f& position ) {
|
||||
if ( context.cubemapIDs.empty() ) return -1;
|
||||
|
||||
int32_t bestID = context.cubemapIDs[0];
|
||||
float minDistance = std::numeric_limits<float>::max();
|
||||
|
||||
for ( size_t i = 0; i < context.cubemaps.size(); ++i ) {
|
||||
pod::Vector3f cubePos = impl::convertPos(pod::Vector3f{
|
||||
context.cubemaps[i].origin.x,
|
||||
context.cubemaps[i].origin.y,
|
||||
context.cubemaps[i].origin.z
|
||||
});
|
||||
|
||||
float distSq = uf::vector::distanceSquared( position, cubePos );
|
||||
if ( distSq < minDistance ) {
|
||||
minDistance = distSq;
|
||||
bestID = context.cubemapIDs[i];
|
||||
}
|
||||
}
|
||||
return bestID;
|
||||
};
|
||||
|
||||
pod::Atlas::hash_t faceHash( size_t i ) {
|
||||
return ::fmt::format("face_{}", i);
|
||||
}
|
||||
@ -436,33 +464,46 @@ namespace impl {
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
uf::stl::vector<T> extractLump( const uf::stl::vector<uint8_t>& buffer, const impl::BspLump& lump ) {
|
||||
uf::stl::vector<T> data;
|
||||
if ( lump.length == 0 || lump.offset >= buffer.size() ) return data;
|
||||
void extractLump( const uf::stl::vector<uint8_t>& buffer, const impl::BspLump& lump, uf::stl::vector<T>& data ) {
|
||||
if ( lump.length == 0 || lump.offset >= buffer.size() ) return;
|
||||
|
||||
size_t count = lump.length / sizeof(T);
|
||||
data.resize(count);
|
||||
std::copy(buffer.data() + lump.offset, buffer.data() + lump.offset + lump.length, (uint8_t*)(data.data()) );
|
||||
}
|
||||
template<typename T>
|
||||
uf::stl::vector<T> extractLump( const uf::stl::vector<uint8_t>& buffer, const impl::BspLump& lump ) {
|
||||
uf::stl::vector<T> data;
|
||||
impl::extractLump<T>( buffer, lump, data );
|
||||
return data;
|
||||
}
|
||||
|
||||
template<>
|
||||
uf::stl::vector<impl::BspGameLump> extractLump( const uf::stl::vector<uint8_t>& buffer, const impl::BspLump& lump ) {
|
||||
uf::stl::vector<impl::BspGameLump> data;
|
||||
if ( lump.length == 0 || lump.offset >= buffer.size() ) return data;
|
||||
void extractLump( const uf::stl::vector<uint8_t>& buffer, const impl::BspLump& lump, uf::stl::vector<impl::BspGameLump>& data ) {
|
||||
if ( lump.length == 0 || lump.offset >= buffer.size() ) return;
|
||||
|
||||
const uint8_t* glData = buffer.data() + lump.offset;
|
||||
int32_t lumpCount = *(const int32_t*)glData;
|
||||
|
||||
data.resize(lumpCount);
|
||||
std::copy(glData + 4, glData + 4 + (lumpCount * sizeof(impl::BspGameLump)), (uint8_t*)(data.data()));
|
||||
}
|
||||
|
||||
template<>
|
||||
uf::stl::vector<impl::BspGameLump> extractLump( const uf::stl::vector<uint8_t>& buffer, const impl::BspLump& lump ) {
|
||||
uf::stl::vector<impl::BspGameLump> data;
|
||||
impl::extractLump( buffer, lump, data );
|
||||
return data;
|
||||
}
|
||||
|
||||
uf::stl::string extractLumpString( const uf::stl::vector<uint8_t>& buffer, const impl::BspLump& lump ) {
|
||||
void extractLumpString( const uf::stl::vector<uint8_t>& buffer, const impl::BspLump& lump, uf::stl::string& str ) {
|
||||
auto data = impl::extractLump<char>( buffer, lump );
|
||||
return uf::stl::string( data.data(), data.size() );
|
||||
str = uf::stl::string( data.data(), data.size() );
|
||||
}
|
||||
uf::stl::string extractLumpString( const uf::stl::vector<uint8_t>& buffer, const impl::BspLump& lump ) {
|
||||
uf::stl::string str;
|
||||
impl::extractLumpString( buffer, lump, str );
|
||||
return str;
|
||||
}
|
||||
|
||||
void processNodes( pod::Graph& graph, const impl::BspContext& context, float scale = impl::sourceToMeters ) {
|
||||
@ -638,21 +679,22 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co
|
||||
graph.root.index = -1;
|
||||
|
||||
impl::BspContext context;
|
||||
context.vertices = impl::extractLump<impl::BspVertex>(buffer, header->lumps[impl::BspLump::LUMP_VERTICES]);
|
||||
context.edges = impl::extractLump<impl::BspEdge>(buffer, header->lumps[impl::BspLump::LUMP_EDGES]);
|
||||
context.surfedges = impl::extractLump<int32_t>(buffer, header->lumps[impl::BspLump::LUMP_SURFEDGES]);
|
||||
context.faces = impl::extractLump<impl::BspFace>(buffer, header->lumps[impl::BspLump::LUMP_FACES]);
|
||||
context.texinfos = impl::extractLump<impl::BspTexInfo>(buffer, header->lumps[impl::BspLump::LUMP_TEXINFO]);
|
||||
context.texdatas = impl::extractLump<impl::BspTexData>(buffer, header->lumps[impl::BspLump::LUMP_TEXDATA]);
|
||||
context.stringTable = impl::extractLump<int32_t>(buffer, header->lumps[impl::BspLump::LUMP_TEXDATA_STRING_TABLE]);
|
||||
context.stringData = impl::extractLumpString(buffer, header->lumps[impl::BspLump::LUMP_TEXDATA_STRING_DATA]);
|
||||
context.models = impl::extractLump<impl::BspModel>(buffer, header->lumps[impl::BspLump::LUMP_MODELS]);
|
||||
context.entities = impl::extractLump<int8_t>(buffer, header->lumps[impl::BspLump::LUMP_ENTITIES]);
|
||||
context.gameLumps = impl::extractLump<impl::BspGameLump>(buffer, header->lumps[impl::BspLump::LUMP_GAME_LUMP]);
|
||||
context.dispinfos = impl::extractLump<impl::BspDispInfo>(buffer, header->lumps[impl::BspLump::LUMP_DISPINFO]);
|
||||
context.dispverts = impl::extractLump<impl::BspDispVert>(buffer, header->lumps[impl::BspLump::LUMP_DISP_VERTS]);
|
||||
context.pakfile = impl::extractLump<uint8_t>(buffer, header->lumps[impl::BspLump::LUMP_PAKFILE]);
|
||||
context.lighting = impl::extractLump<uint8_t>(buffer, header->lumps[impl::BspLump::LUMP_LIGHTING]);
|
||||
impl::extractLump<impl::BspVertex>(buffer, header->lumps[impl::BspLump::LUMP_VERTICES], context.vertices);
|
||||
impl::extractLump<impl::BspEdge>(buffer, header->lumps[impl::BspLump::LUMP_EDGES], context.edges);
|
||||
impl::extractLump<int32_t>(buffer, header->lumps[impl::BspLump::LUMP_SURFEDGES], context.surfedges);
|
||||
impl::extractLump<impl::BspFace>(buffer, header->lumps[impl::BspLump::LUMP_FACES], context.faces);
|
||||
impl::extractLump<impl::BspTexInfo>(buffer, header->lumps[impl::BspLump::LUMP_TEXINFO], context.texinfos);
|
||||
impl::extractLump<impl::BspTexData>(buffer, header->lumps[impl::BspLump::LUMP_TEXDATA], context.texdatas);
|
||||
impl::extractLump<int32_t>(buffer, header->lumps[impl::BspLump::LUMP_TEXDATA_STRING_TABLE], context.stringTable);
|
||||
impl::extractLumpString(buffer, header->lumps[impl::BspLump::LUMP_TEXDATA_STRING_DATA], context.stringData);
|
||||
impl::extractLump<impl::BspModel>(buffer, header->lumps[impl::BspLump::LUMP_MODELS], context.models);
|
||||
impl::extractLump<int8_t>(buffer, header->lumps[impl::BspLump::LUMP_ENTITIES], context.entities);
|
||||
impl::extractLump<impl::BspGameLump>(buffer, header->lumps[impl::BspLump::LUMP_GAME_LUMP], context.gameLumps);
|
||||
impl::extractLump<impl::BspDispInfo>(buffer, header->lumps[impl::BspLump::LUMP_DISPINFO], context.dispinfos);
|
||||
impl::extractLump<impl::BspDispVert>(buffer, header->lumps[impl::BspLump::LUMP_DISP_VERTS], context.dispverts);
|
||||
impl::extractLump<uint8_t>(buffer, header->lumps[impl::BspLump::LUMP_PAKFILE], context.pakfile);
|
||||
impl::extractLump<impl::BspCubemap>(buffer, header->lumps[impl::BspLump::LUMP_CUBEMAPS], context.cubemaps);
|
||||
impl::extractLump<uint8_t>(buffer, header->lumps[impl::BspLump::LUMP_LIGHTING], context.lighting);
|
||||
|
||||
context.modelToMesh.assign( context.models.size(), -1 );
|
||||
context.texdataToMaterial.assign( context.texdatas.size(), -1 );
|
||||
@ -660,6 +702,31 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co
|
||||
// mount pakfile
|
||||
size_t pakfileMount = uf::vfs::mount( ext::zlib::createZipMount(::fmt::format("pakfile://{}", filename), context.pakfile, 1000 ) );
|
||||
|
||||
// deduce mapname
|
||||
uf::stl::string mapName = filename; {
|
||||
auto slashPos = mapName.find_last_of("/\\");
|
||||
if (slashPos != uf::stl::string::npos) mapName = mapName.substr(slashPos + 1);
|
||||
auto dotPos = mapName.find_last_of('.');
|
||||
if (dotPos != uf::stl::string::npos) mapName = mapName.substr(0, dotPos);
|
||||
}
|
||||
|
||||
// load baked cubemaps
|
||||
for ( const auto& cube : context.cubemaps ) {
|
||||
auto matName = ::fmt::format("maps/{}/c{}_{}_{}", mapName, cube.origin.x, cube.origin.y, cube.origin.z);
|
||||
auto vtfPath = ::fmt::format("materials/{}.vtf", matName);
|
||||
graph.metadata["cubemaps"].emplace_back( matName );
|
||||
|
||||
int32_t textureID = -1;
|
||||
impl::addMaterial( graph, matName, textureID );
|
||||
|
||||
auto& image = storage.images[matName].data;
|
||||
auto& texture = storage.images[matName].handle;
|
||||
if ( ext::valve::loadVtf( image, vtfPath ) ) {
|
||||
UF_MSG_DEBUG("Loaded cubemap={}", vtfPath);
|
||||
context.cubemapIDs.emplace_back( storage.materials[matName].indexCubemap = textureID );
|
||||
}
|
||||
}
|
||||
|
||||
// read materials
|
||||
for ( int32_t texDataID = 0; texDataID < context.texdatas.size(); ++texDataID ) {
|
||||
const auto& data = context.texdatas[texDataID];
|
||||
@ -673,7 +740,9 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co
|
||||
}
|
||||
}
|
||||
|
||||
context.texdataToMaterial[texDataID] = impl::addMaterial( graph, matName );
|
||||
int32_t textureID = -1;
|
||||
context.texdataToMaterial[texDataID] = impl::addMaterial( graph, matName, textureID );
|
||||
storage.materials[matName].indexAlbedo = textureID;
|
||||
}
|
||||
|
||||
// read lightmaps
|
||||
@ -726,6 +795,7 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co
|
||||
if ( texDataID < 0 || texDataID >= context.texdatas.size() ) continue;
|
||||
|
||||
size_t materialID = context.texdataToMaterial[texDataID];
|
||||
auto& matName = graph.materials[materialID];
|
||||
|
||||
// read brush
|
||||
auto& meshlet = meshlets[materialID];
|
||||
@ -734,6 +804,14 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co
|
||||
if ( 0 <= face.lightofs ) {
|
||||
meshlet.primitive.instance.lightmapID = atlasTextureID;
|
||||
}
|
||||
|
||||
if ( !context.cubemapIDs.empty() ) {
|
||||
int32_t pivotSurfEdge = context.surfedges[face.firstedge];
|
||||
uint16_t pivotVertID = pivotSurfEdge >= 0 ? context.edges[pivotSurfEdge].x : context.edges[-pivotSurfEdge].y;
|
||||
pod::Vector3f p0 = impl::convertPos( context.vertices[pivotVertID] );
|
||||
|
||||
meshlet.primitive.instance.cubemapID = impl::findClosestCubemap( context, p0 );
|
||||
}
|
||||
|
||||
if ( face.dispinfo != -1 ) {
|
||||
impl::buildDisplacement( context, meshlet, faceID );
|
||||
@ -923,32 +1001,96 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co
|
||||
auto vtfPath = ::fmt::format("materials/{}.vtf", matName);
|
||||
auto& image = storage.images[matName].data;
|
||||
auto& material = storage.materials[matName];
|
||||
|
||||
|
||||
if ( !image.getPixels().empty() ) continue;
|
||||
if ( !ext::valve::loadVmt( vmt, vmtPath ) ) goto PEETAH;
|
||||
|
||||
material.factorMetallic = vmt["$metalness"].as<float>(0.0f);
|
||||
material.factorRoughness = vmt["$roughness"].as<float>(1.0f);
|
||||
|
||||
if ( vmt["$envmap"].as<uf::stl::string>() != "" ) material.factorRoughness = 0.3f;
|
||||
if ( vmt["$phong"].as<int>(0) == 1 ) material.factorRoughness = std::min(material.factorRoughness, 0.5f);
|
||||
|
||||
if ( vmt["$phong"].as<int>(0) == 1 ) {
|
||||
material.factorRoughness = std::min(material.factorRoughness, 0.5f);
|
||||
}
|
||||
if ( vmt["$translucent"].as<int>(0) == 1 ) {
|
||||
material.modeAlpha = pod::Material::AlphaMode::BLEND;
|
||||
} else if ( vmt["$alphatest"].as<int>(0) == 1 ) {
|
||||
}
|
||||
if ( vmt["$alphatest"].as<int>(0) == 1 ) {
|
||||
material.modeAlpha = pod::Material::AlphaMode::MASK;
|
||||
material.factorAlphaCutoff = vmt["$alphatestreference"].as<float>(0.5f);
|
||||
}
|
||||
if ( vmt["$nocull"].as<int>(0) == 1 ) material.modeCull = pod::Material::CullMode::NONE;
|
||||
if ( vmt["$nocull"].as<int>(0) == 1 ) {
|
||||
material.modeCull = pod::Material::CullMode::NONE;
|
||||
}
|
||||
|
||||
if ( vmt["$selfillum"].as<int>(0) == 1 ) {
|
||||
material.colorEmissive = { 1.0f, 1.0f, 1.0f, 1.0f };
|
||||
material.modeAlpha = pod::Material::AlphaMode::EMISSIVE;
|
||||
}
|
||||
if ( !vmt["$basetexture"].is<uf::stl::string>() ) goto PEETAH;
|
||||
// cubemap
|
||||
if ( vmt["$envmap"].as<uf::stl::string>() != "" ) {
|
||||
auto matName = uf::string::lowercase(vmt["$envmap"].as<uf::stl::string>());
|
||||
auto vtfPath = ::fmt::format("materials/{}.vtf", matName);
|
||||
|
||||
// retrieve
|
||||
if ( matName == "env_cubemap" ) {
|
||||
// is handled on an instance level
|
||||
} else if ( storage.images.map.count(matName) > 0 ) {
|
||||
material.indexCubemap = storage.textures[matName].index;
|
||||
} else {
|
||||
int32_t textureID = -1;
|
||||
impl::addMaterial( graph, matName, textureID );
|
||||
graph.metadata["cubemaps"].emplace_back( matName );
|
||||
|
||||
auto& image = storage.images[matName].data;
|
||||
auto& texture = storage.images[matName].handle;
|
||||
texture.viewType = uf::renderer::enums::Image::VIEW_TYPE_CUBE;
|
||||
|
||||
if ( ext::valve::loadVtf( image, vtfPath ) ) {
|
||||
UF_MSG_DEBUG("Loaded cubemap={}", matName);
|
||||
material.indexCubemap = textureID;
|
||||
}
|
||||
}
|
||||
}
|
||||
// normal map
|
||||
if ( vmt["$bumpmap"].is<uf::stl::string>() ) {
|
||||
auto matName = uf::string::lowercase(vmt["$bumpmap"].as<uf::stl::string>());
|
||||
auto vtfPath = ::fmt::format("materials/{}.vtf", matName);
|
||||
|
||||
// retrieve
|
||||
if ( storage.images.map.count(matName) > 0 ) {
|
||||
material.indexNormal = storage.textures[matName].index;
|
||||
} else {
|
||||
int32_t textureID = -1;
|
||||
impl::addMaterial( graph, matName, textureID );
|
||||
|
||||
auto& image = storage.images[matName].data;
|
||||
if ( ext::valve::loadVtf( image, vtfPath ) ) material.indexNormal = textureID;
|
||||
}
|
||||
}
|
||||
// metallic/roughness/occlusion map
|
||||
if ( vmt["$mrao"].is<uf::stl::string>() ) {
|
||||
auto matName = uf::string::lowercase(vmt["$mrao"].as<uf::stl::string>());
|
||||
auto vtfPath = ::fmt::format("materials/{}.vtf", matName);
|
||||
|
||||
// retrieve
|
||||
if ( storage.images.map.count(matName) > 0 ) {
|
||||
material.indexMetallicRoughness = storage.textures[matName].index;
|
||||
} else {
|
||||
int32_t textureID = -1;
|
||||
impl::addMaterial( graph, matName, textureID );
|
||||
|
||||
auto& image = storage.images[matName].data;
|
||||
if ( ext::valve::loadVtf( image, vtfPath ) ) material.indexMetallicRoughness = textureID;
|
||||
}
|
||||
}
|
||||
// albedo map
|
||||
if ( vmt["$basetexture"].is<uf::stl::string>() ) {
|
||||
vtfPath = ::fmt::format("materials/{}.vtf", vmt["$basetexture"].as<uf::stl::string>());
|
||||
if ( ext::valve::loadVtf( image, vtfPath ) ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
vtfPath = ::fmt::format("materials/{}.vtf", vmt["$basetexture"].as<uf::stl::string>());
|
||||
if ( !ext::valve::loadVtf( image, vtfPath ) ) goto PEETAH;
|
||||
continue;
|
||||
PEETAH:
|
||||
image.loadFromBuffer( missing_pixels, { 2, 2 }, 8, 4 );
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -11,6 +11,8 @@ namespace impl {
|
||||
constexpr uint32_t IMAGE_FORMAT_DXT1 = 13;
|
||||
constexpr uint32_t IMAGE_FORMAT_DXT5 = 15;
|
||||
|
||||
constexpr uint32_t TEXTUREFLAGS_ENVMAP = 0x00002000;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct VTFHeader {
|
||||
char signature[4]; // "VTF\0"
|
||||
@ -152,56 +154,134 @@ bool ext::valve::loadVtf( pod::Image& image, const uf::stl::string& filename ) {
|
||||
const impl::VTFHeader* header = (const impl::VTFHeader*)(buffer.data());
|
||||
if ( strncmp(header->signature, "VTF", 3) != 0 ) return false;
|
||||
|
||||
bool isCubemap = (header->flags & impl::TEXTUREFLAGS_ENVMAP) != 0;
|
||||
int numFrames = std::max<int>(1, header->frames);
|
||||
|
||||
size_t singleFaceSize = 0;
|
||||
for ( int mip = header->mipmapCount - 1; mip >= 0; --mip ) {
|
||||
int mipWidth = std::max(1, header->width >> mip);
|
||||
int mipHeight = std::max(1, header->height >> mip);
|
||||
int blocksX = (mipWidth + 3) / 4;
|
||||
int blocksY = (mipHeight + 3) / 4;
|
||||
|
||||
if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT1 ) singleFaceSize += blocksX * blocksY * 8;
|
||||
else if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT5 ) singleFaceSize += blocksX * blocksY * 16;
|
||||
else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGRA8888 ) singleFaceSize += mipWidth * mipHeight * 4;
|
||||
}
|
||||
|
||||
size_t offset = header->headerSize;
|
||||
if ( header->lowResImageFormat != 0xFFFFFFFF ) {
|
||||
offset += std::max<size_t>(1, (header->lowResImageWidth * header->lowResImageHeight) / 2);
|
||||
}
|
||||
|
||||
for ( int mip = header->mipmapCount - 1; mip > 0; --mip ) {
|
||||
int mipWidth = std::max(1, header->width >> mip);
|
||||
int mipHeight = std::max(1, header->height >> mip);
|
||||
size_t remainingBytes = buffer.size() - offset;
|
||||
size_t bytesPerFace = singleFaceSize * numFrames;
|
||||
int actualFaces = bytesPerFace > 0 ? (remainingBytes / bytesPerFace) : 1;
|
||||
|
||||
if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT1 ) {
|
||||
offset += std::max(8, (mipWidth / 4) * (mipHeight / 4) * 8);
|
||||
} else if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT5 ) {
|
||||
offset += std::max(16, (mipWidth / 4) * (mipHeight / 4) * 16);
|
||||
} else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGRA8888 ) {
|
||||
offset += mipWidth * mipHeight * 4;
|
||||
if ( actualFaces == 6 || actualFaces == 7 ) {
|
||||
isCubemap = true;
|
||||
}
|
||||
|
||||
int numFaces = 1;
|
||||
if ( isCubemap ) {
|
||||
if ( actualFaces >= 6 ) {
|
||||
numFaces = actualFaces;
|
||||
} else {
|
||||
isCubemap = false;
|
||||
numFaces = 1;
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t* data = buffer.data() + offset;
|
||||
image.size = { header->width, header->height };
|
||||
for ( int mip = header->mipmapCount - 1; mip > 0; --mip ) {
|
||||
int mipWidth = std::max(1, header->width >> mip);
|
||||
int mipHeight = std::max(1, header->height >> mip);
|
||||
int blocksX = (mipWidth + 3) / 4;
|
||||
int blocksY = (mipHeight + 3) / 4;
|
||||
|
||||
size_t mipSize = 0;
|
||||
if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT1 ) mipSize = blocksX * blocksY * 8;
|
||||
else if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT5 ) mipSize = blocksX * blocksY * 16;
|
||||
else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGRA8888 ) mipSize = mipWidth * mipHeight * 4;
|
||||
|
||||
offset += mipSize * numFrames * numFaces;
|
||||
}
|
||||
|
||||
int outputFaces = isCubemap ? 6 : 1;
|
||||
|
||||
image.size = { header->width, header->height * outputFaces };
|
||||
image.channels = 4;
|
||||
image.bpp = 8 * 4;
|
||||
image.pixels.resize( header->width * header->height * 4 );
|
||||
image.pixels.resize( header->width * header->height * 4 * outputFaces );
|
||||
|
||||
int dataSize = 0;
|
||||
if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT1 ) {
|
||||
int blockCountX = (header->width + 3) / 4;
|
||||
int blockCountY = (header->height + 3) / 4;
|
||||
for ( int by = 0; by < blockCountY; ++by ) {
|
||||
for ( int bx = 0; bx < blockCountX; ++bx ) {
|
||||
impl::decompressDXT1Block(data + (by * blockCountX + bx) * 8, image.pixels.data(), bx * 4, by * 4, header->width, header->height);
|
||||
int faceSizePixels = header->width * header->height;
|
||||
int blocksX = (header->width + 3) / 4;
|
||||
int blocksY = (header->height + 3) / 4;
|
||||
|
||||
const int faceMap[6] = { 4, 5, 0, 1, 2, 3 };
|
||||
const int faceModes[6] = { 2, 0, 6, 1, 1, 3 };
|
||||
for ( int face = 0; face < outputFaces; ++face ) {
|
||||
const uint8_t* data = buffer.data() + offset;
|
||||
int mappedFace = (isCubemap && face < 6) ? faceMap[face] : face;
|
||||
uint8_t* outPixels = image.pixels.data() + (mappedFace * faceSizePixels * 4);
|
||||
|
||||
if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT1 ) {
|
||||
for ( int by = 0; by < blocksY; ++by ) {
|
||||
for ( int bx = 0; bx < blocksX; ++bx ) {
|
||||
impl::decompressDXT1Block(data + (by * blocksX + bx) * 8, outPixels, bx * 4, by * 4, header->width, header->height);
|
||||
}
|
||||
}
|
||||
offset += blocksX * blocksY * 8;
|
||||
} else if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT5 ) {
|
||||
for ( int by = 0; by < blocksY; ++by) {
|
||||
for ( int bx = 0; bx < blocksX; ++bx) {
|
||||
impl::decompressDXT5Block(data + (by * blocksX + bx) * 16, outPixels, bx * 4, by * 4, header->width, header->height);
|
||||
}
|
||||
}
|
||||
offset += blocksX * blocksY * 16;
|
||||
} else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGRA8888 ) {
|
||||
for ( auto i = 0; i < faceSizePixels; ++i ) {
|
||||
outPixels[i * 4 + 0] = data[i * 4 + 2];
|
||||
outPixels[i * 4 + 1] = data[i * 4 + 1];
|
||||
outPixels[i * 4 + 2] = data[i * 4 + 0];
|
||||
outPixels[i * 4 + 3] = data[i * 4 + 3];
|
||||
}
|
||||
offset += faceSizePixels * 4;
|
||||
}
|
||||
|
||||
if ( isCubemap ) {
|
||||
int mode = faceModes[mappedFace];
|
||||
if ( mode == 0 ) continue;
|
||||
uf::stl::vector<uint8_t> temp(faceSizePixels * 4);
|
||||
memcpy(temp.data(), outPixels, faceSizePixels * 4);
|
||||
|
||||
int size = header->width;
|
||||
int max = size - 1;
|
||||
|
||||
for ( int y = 0; y < size; ++y ) {
|
||||
for ( int x = 0; x < size; ++x ) {
|
||||
int srcX = x;
|
||||
int srcY = y;
|
||||
|
||||
switch ( mode ) {
|
||||
case 1: srcX = y; srcY = max - x; break; // 90 CW
|
||||
case 2: srcX = max - x; srcY = max - y; break; // 180
|
||||
case 3: srcX = max - y; srcY = x; break; // 90 CCW
|
||||
case 4: srcX = max - x; srcY = y; break; // Flip Horizontal
|
||||
case 5: srcX = x; srcY = max - y; break; // Flip Vertical
|
||||
case 6: srcX = y; srcY = x; break; // Transpose (Diagonal Flip)
|
||||
case 7: srcX = max - y; srcY = max - x; break; // Anti-Transpose
|
||||
}
|
||||
|
||||
int srcIdx = (srcY * size + srcX) * 4;
|
||||
int dstIdx = (y * size + x) * 4;
|
||||
|
||||
outPixels[dstIdx + 0] = temp[srcIdx + 0];
|
||||
outPixels[dstIdx + 1] = temp[srcIdx + 1];
|
||||
outPixels[dstIdx + 2] = temp[srcIdx + 2];
|
||||
outPixels[dstIdx + 3] = temp[srcIdx + 3];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT5 ) {
|
||||
int blockCountX = (header->width + 3) / 4;
|
||||
int blockCountY = (header->height + 3) / 4;
|
||||
for ( int by = 0; by < blockCountY; ++by) {
|
||||
for ( int bx = 0; bx < blockCountX; ++bx) {
|
||||
impl::decompressDXT5Block(data + (by * blockCountX + bx) * 16, image.pixels.data(), bx * 4, by * 4, header->width, header->height);
|
||||
}
|
||||
}
|
||||
} else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGRA8888 ) {
|
||||
for ( auto i = 0; i < header->width * header->height; ++i ) {
|
||||
image.pixels[i * 4 + 0] = data[i * 4 + 2];
|
||||
image.pixels[i * 4 + 1] = data[i * 4 + 1];
|
||||
image.pixels[i * 4 + 2] = data[i * 4 + 0];
|
||||
image.pixels[i * 4 + 3] = data[i * 4 + 3];
|
||||
}
|
||||
} else {
|
||||
UF_MSG_WARNING("Unsupported VTF format: {}", header->highResImageFormat);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user