mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-24 13:44:58 +00:00
feat(materialx): Integrate MaterialX support for PBR parameters and enhance shader functionality
This commit is contained in:
@@ -60,19 +60,42 @@
|
||||
"bgfx": {
|
||||
"renderer": "vulkan"
|
||||
},
|
||||
"materialx": {
|
||||
"enabled": false,
|
||||
"parameters_enabled": true,
|
||||
"document": "MaterialX/resources/Materials/Examples/StandardSurface/standard_surface_carpaint.mtlx",
|
||||
"shader_key": "materialx",
|
||||
"material": "Car_Paint",
|
||||
"library_path": "MaterialX/libraries",
|
||||
"library_folders": [
|
||||
"stdlib",
|
||||
"pbrlib",
|
||||
"lights",
|
||||
"bxdf",
|
||||
"cmlib",
|
||||
"nprlib",
|
||||
"targets"
|
||||
],
|
||||
"use_constant_color": false,
|
||||
"constant_color": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"atmospherics": {
|
||||
"ambient_strength": 0.01,
|
||||
"fog_density": 0.003,
|
||||
"fog_color": [0.05, 0.05, 0.08],
|
||||
"sky_color": [0.04, 0.05, 0.08],
|
||||
"ambient_strength": 0.006,
|
||||
"fog_density": 0.006,
|
||||
"fog_color": [0.03, 0.04, 0.06],
|
||||
"sky_color": [0.02, 0.03, 0.05],
|
||||
"gamma": 2.2,
|
||||
"exposure": 1.0,
|
||||
"exposure": 1.15,
|
||||
"enable_tone_mapping": true,
|
||||
"enable_shadows": true,
|
||||
"enable_ssgi": true,
|
||||
"enable_volumetric_lighting": true,
|
||||
"pbr_roughness": 0.3,
|
||||
"pbr_metallic": 0.1
|
||||
"pbr_roughness": 0.28,
|
||||
"pbr_metallic": 0.08
|
||||
},
|
||||
"gui_opacity": 1.0,
|
||||
"config_file": "config/seed_runtime.json"
|
||||
|
||||
@@ -564,7 +564,7 @@ local function create_skybox()
|
||||
end
|
||||
|
||||
local function create_spinning_cube()
|
||||
log_debug("Spinning cube shader=default (rainbow wrap)")
|
||||
log_debug("Spinning cube shader=pbr (MaterialX-driven)")
|
||||
local function compute_model_matrix(time)
|
||||
local rotation = math3d.rotation_y(time * rotation_speed)
|
||||
local scale = scale_matrix(1.5, 1.5, 1.5) -- Make cube 3x3x3 units
|
||||
@@ -576,7 +576,7 @@ local function create_spinning_cube()
|
||||
vertices = cube_vertices,
|
||||
indices = (#cube_indices_double_sided > 0) and cube_indices_double_sided or cube_indices,
|
||||
compute_model_matrix = compute_model_matrix,
|
||||
shader_key = "default",
|
||||
shader_key = "pbr",
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -141,6 +141,51 @@ local function build_shader_parameter_overrides(config, log_debug)
|
||||
return parameters
|
||||
end
|
||||
|
||||
local function load_materialx_parameters(config, log_debug)
|
||||
if type(config) ~= "table" then
|
||||
return nil
|
||||
end
|
||||
local materialx = config.materialx
|
||||
if type(materialx) ~= "table" then
|
||||
return nil
|
||||
end
|
||||
local enabled = materialx.enabled
|
||||
if type(materialx.parameters_enabled) == "boolean" then
|
||||
enabled = materialx.parameters_enabled
|
||||
end
|
||||
if not enabled then
|
||||
return nil
|
||||
end
|
||||
if type(materialx.document) ~= "string" or materialx.document == "" then
|
||||
log_debug("MaterialX enabled but document path is missing")
|
||||
return nil
|
||||
end
|
||||
if type(materialx_get_surface_parameters) ~= "function" then
|
||||
log_debug("MaterialX loader unavailable (materialx_get_surface_parameters missing)")
|
||||
return nil
|
||||
end
|
||||
|
||||
local material_name = nil
|
||||
if type(materialx.material) == "string" and materialx.material ~= "" then
|
||||
material_name = materialx.material
|
||||
end
|
||||
|
||||
local ok, result, err = pcall(materialx_get_surface_parameters, materialx.document, material_name)
|
||||
if not ok then
|
||||
log_debug("MaterialX parameter load failed: %s", tostring(result))
|
||||
return nil
|
||||
end
|
||||
if result == nil then
|
||||
log_debug("MaterialX parameter load failed: %s", tostring(err))
|
||||
return nil
|
||||
end
|
||||
if type(result) ~= "table" then
|
||||
log_debug("MaterialX parameter load returned unexpected result")
|
||||
return nil
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
local function resolve_skybox_color(config, default_color)
|
||||
if type(config) ~= "table" then
|
||||
return default_color
|
||||
@@ -241,6 +286,29 @@ function M.build_cube_variants(config, log_debug, base_skybox_color)
|
||||
local logger = get_logger(log_debug)
|
||||
local skybox_color = resolve_skybox_color(config, base_skybox_color or {0.04, 0.05, 0.08})
|
||||
local shader_parameters = build_shader_parameter_overrides(config, logger)
|
||||
local materialx_parameters = load_materialx_parameters(config, logger)
|
||||
if materialx_parameters then
|
||||
local entry = shader_parameters.pbr or {}
|
||||
local albedo = resolve_color3_optional(materialx_parameters.material_albedo)
|
||||
local roughness = resolve_number_optional(materialx_parameters.material_roughness)
|
||||
local metallic = resolve_number_optional(materialx_parameters.material_metallic)
|
||||
if albedo ~= nil then
|
||||
entry.material_albedo = albedo
|
||||
end
|
||||
if roughness ~= nil then
|
||||
entry.material_roughness = roughness
|
||||
end
|
||||
if metallic ~= nil then
|
||||
entry.material_metallic = metallic
|
||||
end
|
||||
if next(entry) ~= nil then
|
||||
shader_parameters.pbr = entry
|
||||
logger("MaterialX PBR overrides: albedo=%s roughness=%s metallic=%s",
|
||||
format_optional_color(albedo),
|
||||
format_optional_number(roughness),
|
||||
format_optional_number(metallic))
|
||||
end
|
||||
end
|
||||
|
||||
local ok, toolkit = pcall(require, "shader_toolkit")
|
||||
if not ok then
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
#include <lua.hpp>
|
||||
#include <MaterialXCore/Document.h>
|
||||
#include <MaterialXCore/Types.h>
|
||||
#include <MaterialXFormat/File.h>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <rapidjson/document.h>
|
||||
#include <rapidjson/error/en.h>
|
||||
@@ -12,11 +15,191 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <filesystem>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
namespace mx = MaterialX;
|
||||
|
||||
struct MaterialXSurfaceParameters {
|
||||
std::array<float, 3> albedo = {1.0f, 1.0f, 1.0f};
|
||||
float roughness = 0.3f;
|
||||
float metallic = 0.0f;
|
||||
bool hasAlbedo = false;
|
||||
bool hasRoughness = false;
|
||||
bool hasMetallic = false;
|
||||
};
|
||||
|
||||
std::filesystem::path ResolveMaterialXPath(const std::filesystem::path& path,
|
||||
const std::filesystem::path& scriptDirectory) {
|
||||
if (path.empty()) {
|
||||
return {};
|
||||
}
|
||||
if (path.is_absolute()) {
|
||||
return path;
|
||||
}
|
||||
if (!scriptDirectory.empty()) {
|
||||
auto projectRoot = scriptDirectory.parent_path();
|
||||
if (!projectRoot.empty()) {
|
||||
std::error_code ec;
|
||||
auto resolved = std::filesystem::weakly_canonical(projectRoot / path, ec);
|
||||
if (!ec) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::error_code ec;
|
||||
auto resolved = std::filesystem::weakly_canonical(path, ec);
|
||||
if (ec) {
|
||||
return {};
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
mx::FileSearchPath BuildMaterialXSearchPath(const sdl3cpp::services::MaterialXConfig& config,
|
||||
const std::filesystem::path& scriptDirectory) {
|
||||
mx::FileSearchPath searchPath;
|
||||
std::filesystem::path libraryPath = ResolveMaterialXPath(config.libraryPath, scriptDirectory);
|
||||
if (libraryPath.empty() && !scriptDirectory.empty()) {
|
||||
auto fallback = scriptDirectory.parent_path() / "MaterialX" / "libraries";
|
||||
if (std::filesystem::exists(fallback)) {
|
||||
libraryPath = fallback;
|
||||
}
|
||||
}
|
||||
if (!libraryPath.empty()) {
|
||||
searchPath.append(mx::FilePath(libraryPath.string()));
|
||||
}
|
||||
return searchPath;
|
||||
}
|
||||
|
||||
mx::DocumentPtr LoadMaterialXDocument(const std::filesystem::path& documentPath,
|
||||
const sdl3cpp::services::MaterialXConfig& config,
|
||||
const std::filesystem::path& scriptDirectory) {
|
||||
mx::DocumentPtr document = mx::createDocument();
|
||||
mx::FileSearchPath searchPath = BuildMaterialXSearchPath(config, scriptDirectory);
|
||||
mx::readFromXmlFile(document, mx::FilePath(documentPath.string()), &searchPath);
|
||||
|
||||
if (!config.libraryFolders.empty()) {
|
||||
mx::DocumentPtr stdLib = mx::createDocument();
|
||||
mx::FilePathVec folders;
|
||||
folders.reserve(config.libraryFolders.size());
|
||||
for (const auto& folder : config.libraryFolders) {
|
||||
folders.emplace_back(folder);
|
||||
}
|
||||
mx::loadLibraries(folders, searchPath, stdLib);
|
||||
document->importLibrary(stdLib);
|
||||
}
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
mx::NodePtr ResolveSurfaceNode(const mx::DocumentPtr& document, const std::string& materialName) {
|
||||
if (!materialName.empty()) {
|
||||
mx::NodePtr candidate = document->getNode(materialName);
|
||||
if (candidate && candidate->getCategory() == "surfacematerial") {
|
||||
mx::NodePtr surfaceNode = candidate->getConnectedNode("surfaceshader");
|
||||
if (surfaceNode) {
|
||||
return surfaceNode;
|
||||
}
|
||||
}
|
||||
if (candidate && (candidate->getCategory() == "standard_surface"
|
||||
|| candidate->getCategory() == "usd_preview_surface")) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& node : document->getNodes()) {
|
||||
if (node->getCategory() == "surfacematerial") {
|
||||
mx::NodePtr surfaceNode = node->getConnectedNode("surfaceshader");
|
||||
if (surfaceNode) {
|
||||
return surfaceNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& node : document->getNodes()) {
|
||||
if (node->getCategory() == "standard_surface"
|
||||
|| node->getCategory() == "usd_preview_surface") {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool TryReadColor3(const mx::NodePtr& node, const char* name, std::array<float, 3>& outColor) {
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
mx::InputPtr input = node->getInput(name);
|
||||
if (!input || !input->hasValueString()) {
|
||||
return false;
|
||||
}
|
||||
mx::ValuePtr value = input->getValue();
|
||||
if (!value || !value->isA<mx::Color3>()) {
|
||||
return false;
|
||||
}
|
||||
const mx::Color3& color = value->asA<mx::Color3>();
|
||||
outColor = {color[0], color[1], color[2]};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryReadFloat(const mx::NodePtr& node, const char* name, float& outValue) {
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
mx::InputPtr input = node->getInput(name);
|
||||
if (!input || !input->hasValueString()) {
|
||||
return false;
|
||||
}
|
||||
mx::ValuePtr value = input->getValue();
|
||||
if (!value || !value->isA<float>()) {
|
||||
return false;
|
||||
}
|
||||
outValue = value->asA<float>();
|
||||
return true;
|
||||
}
|
||||
|
||||
MaterialXSurfaceParameters ReadStandardSurfaceParameters(const mx::NodePtr& node) {
|
||||
MaterialXSurfaceParameters parameters;
|
||||
std::array<float, 3> baseColor = parameters.albedo;
|
||||
bool hasBaseColor = TryReadColor3(node, "base_color", baseColor);
|
||||
if (!hasBaseColor) {
|
||||
hasBaseColor = TryReadColor3(node, "diffuse_color", baseColor);
|
||||
}
|
||||
float baseStrength = 1.0f;
|
||||
bool hasBaseStrength = TryReadFloat(node, "base", baseStrength);
|
||||
if (hasBaseColor) {
|
||||
parameters.albedo = {
|
||||
baseColor[0] * baseStrength,
|
||||
baseColor[1] * baseStrength,
|
||||
baseColor[2] * baseStrength
|
||||
};
|
||||
parameters.hasAlbedo = true;
|
||||
} else if (hasBaseStrength) {
|
||||
parameters.albedo = {baseStrength, baseStrength, baseStrength};
|
||||
parameters.hasAlbedo = true;
|
||||
}
|
||||
|
||||
float roughness = parameters.roughness;
|
||||
if (TryReadFloat(node, "specular_roughness", roughness)
|
||||
|| TryReadFloat(node, "roughness", roughness)) {
|
||||
parameters.roughness = roughness;
|
||||
parameters.hasRoughness = true;
|
||||
}
|
||||
|
||||
float metallic = parameters.metallic;
|
||||
if (TryReadFloat(node, "metalness", metallic)
|
||||
|| TryReadFloat(node, "metallic", metallic)) {
|
||||
parameters.metallic = metallic;
|
||||
parameters.hasMetallic = true;
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
bool TryParseMouseButtonName(const char* name, uint8_t& button) {
|
||||
if (!name) {
|
||||
return false;
|
||||
@@ -302,6 +485,7 @@ void ScriptEngineService::RegisterBindings(lua_State* L) {
|
||||
bind("input_get_text", &ScriptEngineService::InputGetText);
|
||||
bind("config_get_json", &ScriptEngineService::ConfigGetJson);
|
||||
bind("config_get_table", &ScriptEngineService::ConfigGetTable);
|
||||
bind("materialx_get_surface_parameters", &ScriptEngineService::MaterialXGetSurfaceParameters);
|
||||
bind("window_get_size", &ScriptEngineService::WindowGetSize);
|
||||
bind("window_set_title", &ScriptEngineService::WindowSetTitle);
|
||||
bind("window_is_minimized", &ScriptEngineService::WindowIsMinimized);
|
||||
@@ -673,6 +857,87 @@ int ScriptEngineService::ConfigGetTable(lua_State* L) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ScriptEngineService::MaterialXGetSurfaceParameters(lua_State* L) {
|
||||
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
|
||||
auto logger = context ? context->logger : nullptr;
|
||||
if (!context || !context->configService) {
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, "Config service not available");
|
||||
return 2;
|
||||
}
|
||||
|
||||
const char* documentArg = luaL_checkstring(L, 1);
|
||||
const char* materialArg = luaL_optstring(L, 2, "");
|
||||
std::filesystem::path scriptDirectory = context->configService->GetScriptPath().parent_path();
|
||||
if (logger) {
|
||||
logger->Trace("ScriptEngineService", "MaterialXGetSurfaceParameters",
|
||||
"document=" + std::string(documentArg ? documentArg : "") +
|
||||
", material=" + std::string(materialArg ? materialArg : ""));
|
||||
}
|
||||
|
||||
std::filesystem::path documentPath = ResolveMaterialXPath(documentArg ? documentArg : "", scriptDirectory);
|
||||
if (documentPath.empty()) {
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, "MaterialX document path could not be resolved");
|
||||
return 2;
|
||||
}
|
||||
if (!std::filesystem::exists(documentPath)) {
|
||||
lua_pushnil(L);
|
||||
std::string message = "MaterialX document not found: " + documentPath.string();
|
||||
lua_pushstring(L, message.c_str());
|
||||
return 2;
|
||||
}
|
||||
|
||||
const auto& materialConfig = context->configService->GetMaterialXConfig();
|
||||
mx::DocumentPtr document;
|
||||
try {
|
||||
document = LoadMaterialXDocument(documentPath, materialConfig, scriptDirectory);
|
||||
} catch (const std::exception& ex) {
|
||||
lua_pushnil(L);
|
||||
std::string message = "MaterialX document load failed: ";
|
||||
message += ex.what();
|
||||
lua_pushstring(L, message.c_str());
|
||||
return 2;
|
||||
}
|
||||
|
||||
mx::NodePtr surfaceNode = ResolveSurfaceNode(document, materialArg ? materialArg : "");
|
||||
if (!surfaceNode) {
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, "MaterialX document has no standard_surface material");
|
||||
return 2;
|
||||
}
|
||||
|
||||
MaterialXSurfaceParameters parameters = ReadStandardSurfaceParameters(surfaceNode);
|
||||
if (!parameters.hasAlbedo && !parameters.hasRoughness && !parameters.hasMetallic) {
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, "MaterialX material does not expose supported PBR parameters");
|
||||
return 2;
|
||||
}
|
||||
|
||||
lua_newtable(L);
|
||||
if (parameters.hasAlbedo) {
|
||||
lua_newtable(L);
|
||||
lua_pushnumber(L, parameters.albedo[0]);
|
||||
lua_rawseti(L, -2, 1);
|
||||
lua_pushnumber(L, parameters.albedo[1]);
|
||||
lua_rawseti(L, -2, 2);
|
||||
lua_pushnumber(L, parameters.albedo[2]);
|
||||
lua_rawseti(L, -2, 3);
|
||||
lua_setfield(L, -2, "material_albedo");
|
||||
}
|
||||
if (parameters.hasRoughness) {
|
||||
lua_pushnumber(L, parameters.roughness);
|
||||
lua_setfield(L, -2, "material_roughness");
|
||||
}
|
||||
if (parameters.hasMetallic) {
|
||||
lua_pushnumber(L, parameters.metallic);
|
||||
lua_setfield(L, -2, "material_metallic");
|
||||
}
|
||||
|
||||
lua_pushnil(L);
|
||||
return 2;
|
||||
}
|
||||
|
||||
int ScriptEngineService::WindowGetSize(lua_State* L) {
|
||||
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
|
||||
if (!context || !context->windowService) {
|
||||
|
||||
@@ -77,6 +77,7 @@ private:
|
||||
static int InputGetText(lua_State* L);
|
||||
static int ConfigGetJson(lua_State* L);
|
||||
static int ConfigGetTable(lua_State* L);
|
||||
static int MaterialXGetSurfaceParameters(lua_State* L);
|
||||
static int WindowGetSize(lua_State* L);
|
||||
static int WindowSetTitle(lua_State* L);
|
||||
static int WindowIsMinimized(lua_State* L);
|
||||
|
||||
Reference in New Issue
Block a user