2026-04-02 03:03:36 +08:00
|
|
|
#include <XCEngine/Resources/BuiltinResources.h>
|
|
|
|
|
|
|
|
|
|
#include <XCEngine/Core/Math/Bounds.h>
|
|
|
|
|
#include <XCEngine/Core/Math/Vector2.h>
|
|
|
|
|
#include <XCEngine/Core/Math/Vector3.h>
|
|
|
|
|
#include <XCEngine/Core/Asset/ResourceManager.h>
|
|
|
|
|
#include <XCEngine/Resources/Material/Material.h>
|
|
|
|
|
#include <XCEngine/Resources/Mesh/Mesh.h>
|
2026-04-02 19:00:48 +08:00
|
|
|
#include <XCEngine/Resources/Shader/Shader.h>
|
2026-04-02 23:04:59 +08:00
|
|
|
#include <XCEngine/Resources/Shader/ShaderLoader.h>
|
2026-04-02 03:03:36 +08:00
|
|
|
#include <XCEngine/Resources/Texture/Texture.h>
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <cmath>
|
2026-04-02 23:04:59 +08:00
|
|
|
#include <filesystem>
|
|
|
|
|
#include <system_error>
|
2026-04-02 03:03:36 +08:00
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
namespace XCEngine {
|
|
|
|
|
namespace Resources {
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
constexpr const char* kBuiltinPrefix = "builtin://";
|
|
|
|
|
constexpr const char* kBuiltinMeshPrefix = "builtin://meshes/";
|
|
|
|
|
constexpr const char* kBuiltinMaterialPrefix = "builtin://materials/";
|
2026-04-02 19:00:48 +08:00
|
|
|
constexpr const char* kBuiltinShaderPrefix = "builtin://shaders/";
|
2026-04-02 03:03:36 +08:00
|
|
|
constexpr const char* kBuiltinTexturePrefix = "builtin://textures/";
|
|
|
|
|
constexpr const char* kBuiltinDefaultPrimitiveMaterialPath = "builtin://materials/default-primitive";
|
2026-04-02 19:00:48 +08:00
|
|
|
constexpr const char* kBuiltinForwardLitShaderPath = "builtin://shaders/forward-lit";
|
2026-04-02 19:17:22 +08:00
|
|
|
constexpr const char* kBuiltinObjectIdShaderPath = "builtin://shaders/object-id";
|
2026-04-02 20:18:39 +08:00
|
|
|
constexpr const char* kBuiltinObjectIdOutlineShaderPath = "builtin://shaders/object-id-outline";
|
2026-04-02 19:32:15 +08:00
|
|
|
constexpr const char* kBuiltinInfiniteGridShaderPath = "builtin://shaders/infinite-grid";
|
2026-04-02 03:03:36 +08:00
|
|
|
constexpr const char* kBuiltinDefaultPrimitiveTexturePath = "builtin://textures/default-primitive-albedo";
|
|
|
|
|
constexpr float kPi = 3.14159265358979323846f;
|
|
|
|
|
|
|
|
|
|
struct MeshBuffers {
|
|
|
|
|
std::vector<StaticMeshVertex> vertices;
|
|
|
|
|
std::vector<Core::uint32> indices;
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-02 23:04:59 +08:00
|
|
|
size_t CalculateBuiltinShaderMemorySize(const Shader& shader);
|
|
|
|
|
|
|
|
|
|
constexpr const char* kBuiltinForwardLitShaderManifestRelativePath =
|
|
|
|
|
"engine/assets/builtin/shaders/forward-lit/forward-lit.shader";
|
2026-04-02 23:15:19 +08:00
|
|
|
constexpr const char* kBuiltinObjectIdShaderManifestRelativePath =
|
|
|
|
|
"engine/assets/builtin/shaders/object-id/object-id.shader";
|
|
|
|
|
constexpr const char* kBuiltinObjectIdOutlineShaderManifestRelativePath =
|
|
|
|
|
"engine/assets/builtin/shaders/object-id-outline/object-id-outline.shader";
|
|
|
|
|
constexpr const char* kBuiltinInfiniteGridShaderManifestRelativePath =
|
|
|
|
|
"engine/assets/builtin/shaders/infinite-grid/infinite-grid.shader";
|
2026-04-02 23:04:59 +08:00
|
|
|
|
|
|
|
|
Containers::String NormalizeBuiltinAssetPath(const std::filesystem::path& path) {
|
|
|
|
|
return Containers::String(path.lexically_normal().generic_string().c_str());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TryResolveBuiltinAssetPathFromAnchor(
|
|
|
|
|
const std::filesystem::path& anchor,
|
|
|
|
|
const std::filesystem::path& relativePath,
|
|
|
|
|
std::filesystem::path& outPath) {
|
|
|
|
|
if (anchor.empty()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::error_code ec;
|
|
|
|
|
std::filesystem::path current = anchor.lexically_normal();
|
|
|
|
|
if (std::filesystem::is_regular_file(current, ec)) {
|
|
|
|
|
current = current.parent_path();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (!current.empty()) {
|
|
|
|
|
const std::filesystem::path candidate = (current / relativePath).lexically_normal();
|
|
|
|
|
if (std::filesystem::exists(candidate, ec)) {
|
|
|
|
|
outPath = candidate;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::filesystem::path parent = current.parent_path();
|
|
|
|
|
if (parent == current) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
current = parent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 23:15:19 +08:00
|
|
|
bool TryResolveBuiltinShaderManifestPath(
|
|
|
|
|
const std::filesystem::path& relativePath,
|
|
|
|
|
Containers::String& outPath) {
|
2026-04-02 23:04:59 +08:00
|
|
|
std::filesystem::path resolvedPath;
|
|
|
|
|
std::error_code ec;
|
|
|
|
|
|
|
|
|
|
if (TryResolveBuiltinAssetPathFromAnchor(std::filesystem::current_path(ec), relativePath, resolvedPath)) {
|
|
|
|
|
outPath = NormalizeBuiltinAssetPath(resolvedPath);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
|
|
|
|
|
if (!resourceRoot.Empty() &&
|
|
|
|
|
TryResolveBuiltinAssetPathFromAnchor(std::filesystem::path(resourceRoot.CStr()), relativePath, resolvedPath)) {
|
|
|
|
|
outPath = NormalizeBuiltinAssetPath(resolvedPath);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (TryResolveBuiltinAssetPathFromAnchor(std::filesystem::path(__FILE__), relativePath, resolvedPath)) {
|
|
|
|
|
outPath = NormalizeBuiltinAssetPath(resolvedPath);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 23:15:19 +08:00
|
|
|
const char* GetBuiltinShaderManifestRelativePath(const Containers::String& builtinShaderPath) {
|
|
|
|
|
if (builtinShaderPath == Containers::String(kBuiltinForwardLitShaderPath)) {
|
|
|
|
|
return kBuiltinForwardLitShaderManifestRelativePath;
|
|
|
|
|
}
|
|
|
|
|
if (builtinShaderPath == Containers::String(kBuiltinObjectIdShaderPath)) {
|
|
|
|
|
return kBuiltinObjectIdShaderManifestRelativePath;
|
|
|
|
|
}
|
|
|
|
|
if (builtinShaderPath == Containers::String(kBuiltinObjectIdOutlineShaderPath)) {
|
|
|
|
|
return kBuiltinObjectIdOutlineShaderManifestRelativePath;
|
|
|
|
|
}
|
|
|
|
|
if (builtinShaderPath == Containers::String(kBuiltinInfiniteGridShaderPath)) {
|
|
|
|
|
return kBuiltinInfiniteGridShaderManifestRelativePath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TryResolveBuiltinShaderManifestPath(
|
|
|
|
|
const Containers::String& builtinShaderPath,
|
|
|
|
|
Containers::String& outPath) {
|
|
|
|
|
const char* relativePath = GetBuiltinShaderManifestRelativePath(builtinShaderPath);
|
|
|
|
|
if (relativePath == nullptr) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return TryResolveBuiltinShaderManifestPath(std::filesystem::path(relativePath), outPath);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 23:04:59 +08:00
|
|
|
Shader* LoadBuiltinShaderFromManifest(
|
|
|
|
|
const Containers::String& builtinPath,
|
|
|
|
|
const Containers::String& manifestPath) {
|
|
|
|
|
ShaderLoader shaderLoader;
|
|
|
|
|
LoadResult result = shaderLoader.Load(manifestPath);
|
|
|
|
|
if (!result || result.resource == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
shader->m_path = builtinPath;
|
|
|
|
|
shader->m_guid = ResourceGUID::Generate(builtinPath);
|
|
|
|
|
shader->m_memorySize = CalculateBuiltinShaderMemorySize(*shader);
|
|
|
|
|
return shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 23:15:19 +08:00
|
|
|
Shader* TryLoadBuiltinShaderFromManifest(const Containers::String& builtinPath) {
|
|
|
|
|
Containers::String manifestPath;
|
|
|
|
|
if (!TryResolveBuiltinShaderManifestPath(builtinPath, manifestPath)) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return LoadBuiltinShaderFromManifest(builtinPath, manifestPath);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 19:00:48 +08:00
|
|
|
const char kBuiltinForwardHlsl[] = R"(
|
|
|
|
|
Texture2D gBaseColorTexture : register(t1);
|
|
|
|
|
SamplerState gLinearSampler : register(s1);
|
|
|
|
|
|
|
|
|
|
cbuffer PerObjectConstants : register(b1) {
|
|
|
|
|
float4x4 gProjectionMatrix;
|
|
|
|
|
float4x4 gViewMatrix;
|
|
|
|
|
float4x4 gModelMatrix;
|
|
|
|
|
float4x4 gNormalMatrix;
|
|
|
|
|
float4 gMainLightDirectionAndIntensity;
|
|
|
|
|
float4 gMainLightColorAndFlags;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
cbuffer MaterialConstants : register(b2) {
|
|
|
|
|
float4 gBaseColorFactor;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct VSInput {
|
|
|
|
|
float3 position : POSITION;
|
|
|
|
|
float3 normal : NORMAL;
|
|
|
|
|
float2 texcoord : TEXCOORD0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct PSInput {
|
|
|
|
|
float4 position : SV_POSITION;
|
|
|
|
|
float3 normalWS : TEXCOORD0;
|
|
|
|
|
float2 texcoord : TEXCOORD1;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
PSInput MainVS(VSInput input) {
|
|
|
|
|
PSInput output;
|
|
|
|
|
float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0f));
|
|
|
|
|
float4 positionVS = mul(gViewMatrix, positionWS);
|
|
|
|
|
output.position = mul(gProjectionMatrix, positionVS);
|
|
|
|
|
output.normalWS = mul((float3x3)gNormalMatrix, input.normal);
|
|
|
|
|
output.texcoord = input.texcoord;
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float4 MainPS(PSInput input) : SV_TARGET {
|
|
|
|
|
float4 baseColor = gBaseColorTexture.Sample(gLinearSampler, input.texcoord) * gBaseColorFactor;
|
|
|
|
|
if (gMainLightColorAndFlags.a < 0.5f) {
|
|
|
|
|
return baseColor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float3 normalWS = normalize(input.normalWS);
|
|
|
|
|
float3 directionToLightWS = normalize(gMainLightDirectionAndIntensity.xyz);
|
|
|
|
|
float diffuse = saturate(dot(normalWS, directionToLightWS));
|
|
|
|
|
float3 lighting = float3(0.28f, 0.28f, 0.28f) +
|
|
|
|
|
gMainLightColorAndFlags.rgb * (diffuse * gMainLightDirectionAndIntensity.w);
|
|
|
|
|
return float4(baseColor.rgb * lighting, baseColor.a);
|
|
|
|
|
}
|
|
|
|
|
)";
|
|
|
|
|
|
|
|
|
|
const char kBuiltinForwardVertexShader[] = R"(#version 430
|
|
|
|
|
layout(location = 0) in vec3 aPosition;
|
|
|
|
|
layout(location = 1) in vec3 aNormal;
|
|
|
|
|
layout(location = 2) in vec2 aTexCoord;
|
|
|
|
|
|
|
|
|
|
layout(std140, binding = 1) uniform PerObjectConstants {
|
|
|
|
|
mat4 gProjectionMatrix;
|
|
|
|
|
mat4 gViewMatrix;
|
|
|
|
|
mat4 gModelMatrix;
|
|
|
|
|
mat4 gNormalMatrix;
|
|
|
|
|
vec4 gMainLightDirectionAndIntensity;
|
|
|
|
|
vec4 gMainLightColorAndFlags;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
out vec3 vNormalWS;
|
|
|
|
|
out vec2 vTexCoord;
|
|
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
|
vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0);
|
|
|
|
|
vec4 positionVS = gViewMatrix * positionWS;
|
|
|
|
|
gl_Position = gProjectionMatrix * positionVS;
|
|
|
|
|
vNormalWS = mat3(gNormalMatrix) * aNormal;
|
|
|
|
|
vTexCoord = aTexCoord;
|
|
|
|
|
}
|
|
|
|
|
)";
|
|
|
|
|
|
|
|
|
|
const char kBuiltinForwardFragmentShader[] = R"(#version 430
|
|
|
|
|
layout(binding = 1) uniform sampler2D uBaseColorTexture;
|
|
|
|
|
|
|
|
|
|
layout(std140, binding = 1) uniform PerObjectConstants {
|
|
|
|
|
mat4 gProjectionMatrix;
|
|
|
|
|
mat4 gViewMatrix;
|
|
|
|
|
mat4 gModelMatrix;
|
|
|
|
|
mat4 gNormalMatrix;
|
|
|
|
|
vec4 gMainLightDirectionAndIntensity;
|
|
|
|
|
vec4 gMainLightColorAndFlags;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
layout(std140, binding = 2) uniform MaterialConstants {
|
|
|
|
|
vec4 gBaseColorFactor;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
in vec3 vNormalWS;
|
|
|
|
|
in vec2 vTexCoord;
|
|
|
|
|
|
|
|
|
|
layout(location = 0) out vec4 fragColor;
|
|
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
|
vec4 baseColor = texture(uBaseColorTexture, vTexCoord) * gBaseColorFactor;
|
|
|
|
|
if (gMainLightColorAndFlags.w < 0.5) {
|
|
|
|
|
fragColor = baseColor;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vec3 normalWS = normalize(vNormalWS);
|
|
|
|
|
vec3 directionToLightWS = normalize(gMainLightDirectionAndIntensity.xyz);
|
|
|
|
|
float diffuse = max(dot(normalWS, directionToLightWS), 0.0);
|
|
|
|
|
vec3 lighting = vec3(0.28) +
|
|
|
|
|
gMainLightColorAndFlags.rgb * (diffuse * gMainLightDirectionAndIntensity.w);
|
|
|
|
|
fragColor = vec4(baseColor.rgb * lighting, baseColor.a);
|
|
|
|
|
}
|
|
|
|
|
)";
|
|
|
|
|
|
|
|
|
|
const char kBuiltinForwardVulkanVertexShader[] = R"(#version 450
|
|
|
|
|
layout(location = 0) in vec3 aPosition;
|
|
|
|
|
layout(location = 1) in vec3 aNormal;
|
|
|
|
|
layout(location = 2) in vec2 aTexCoord;
|
|
|
|
|
|
|
|
|
|
layout(set = 1, binding = 0, std140) uniform PerObjectConstants {
|
|
|
|
|
mat4 gProjectionMatrix;
|
|
|
|
|
mat4 gViewMatrix;
|
|
|
|
|
mat4 gModelMatrix;
|
|
|
|
|
mat4 gNormalMatrix;
|
|
|
|
|
vec4 gMainLightDirectionAndIntensity;
|
|
|
|
|
vec4 gMainLightColorAndFlags;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
layout(location = 0) out vec3 vNormalWS;
|
|
|
|
|
layout(location = 1) out vec2 vTexCoord;
|
|
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
|
vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0);
|
|
|
|
|
vec4 positionVS = gViewMatrix * positionWS;
|
|
|
|
|
gl_Position = gProjectionMatrix * positionVS;
|
|
|
|
|
vNormalWS = mat3(gNormalMatrix) * aNormal;
|
|
|
|
|
vTexCoord = aTexCoord;
|
|
|
|
|
}
|
|
|
|
|
)";
|
|
|
|
|
|
|
|
|
|
const char kBuiltinForwardVulkanFragmentShader[] = R"(#version 450
|
|
|
|
|
layout(set = 3, binding = 0) uniform texture2D uBaseColorTexture;
|
|
|
|
|
layout(set = 4, binding = 0) uniform sampler uLinearSampler;
|
|
|
|
|
|
|
|
|
|
layout(set = 1, binding = 0, std140) uniform PerObjectConstants {
|
|
|
|
|
mat4 gProjectionMatrix;
|
|
|
|
|
mat4 gViewMatrix;
|
|
|
|
|
mat4 gModelMatrix;
|
|
|
|
|
mat4 gNormalMatrix;
|
|
|
|
|
vec4 gMainLightDirectionAndIntensity;
|
|
|
|
|
vec4 gMainLightColorAndFlags;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
layout(set = 2, binding = 0, std140) uniform MaterialConstants {
|
|
|
|
|
vec4 gBaseColorFactor;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
layout(location = 0) in vec3 vNormalWS;
|
|
|
|
|
layout(location = 1) in vec2 vTexCoord;
|
|
|
|
|
|
|
|
|
|
layout(location = 0) out vec4 fragColor;
|
|
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
|
vec4 baseColor = texture(sampler2D(uBaseColorTexture, uLinearSampler), vTexCoord) * gBaseColorFactor;
|
|
|
|
|
if (gMainLightColorAndFlags.w < 0.5) {
|
|
|
|
|
fragColor = baseColor;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vec3 normalWS = normalize(vNormalWS);
|
|
|
|
|
vec3 directionToLightWS = normalize(gMainLightDirectionAndIntensity.xyz);
|
|
|
|
|
float diffuse = max(dot(normalWS, directionToLightWS), 0.0);
|
|
|
|
|
vec3 lighting = vec3(0.28) +
|
|
|
|
|
gMainLightColorAndFlags.rgb * (diffuse * gMainLightDirectionAndIntensity.w);
|
|
|
|
|
fragColor = vec4(baseColor.rgb * lighting, baseColor.a);
|
|
|
|
|
}
|
|
|
|
|
)";
|
|
|
|
|
|
2026-04-02 19:17:22 +08:00
|
|
|
const char kBuiltinObjectIdHlsl[] = R"(
|
|
|
|
|
cbuffer PerObjectConstants : register(b0) {
|
|
|
|
|
float4x4 gProjectionMatrix;
|
|
|
|
|
float4x4 gViewMatrix;
|
|
|
|
|
float4x4 gModelMatrix;
|
|
|
|
|
float4 gObjectIdColor;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct VSInput {
|
|
|
|
|
float3 position : POSITION;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct PSInput {
|
|
|
|
|
float4 position : SV_POSITION;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
PSInput MainVS(VSInput input) {
|
|
|
|
|
PSInput output;
|
|
|
|
|
float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0));
|
|
|
|
|
float4 positionVS = mul(gViewMatrix, positionWS);
|
|
|
|
|
output.position = mul(gProjectionMatrix, positionVS);
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float4 MainPS(PSInput input) : SV_TARGET {
|
|
|
|
|
return gObjectIdColor;
|
|
|
|
|
}
|
|
|
|
|
)";
|
|
|
|
|
|
|
|
|
|
const char kBuiltinObjectIdVertexShader[] = R"(#version 430
|
|
|
|
|
layout(location = 0) in vec3 aPosition;
|
|
|
|
|
|
|
|
|
|
layout(std140, binding = 0) uniform PerObjectConstants {
|
|
|
|
|
mat4 gProjectionMatrix;
|
|
|
|
|
mat4 gViewMatrix;
|
|
|
|
|
mat4 gModelMatrix;
|
|
|
|
|
vec4 gObjectIdColor;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
|
vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0);
|
|
|
|
|
vec4 positionVS = gViewMatrix * positionWS;
|
|
|
|
|
gl_Position = gProjectionMatrix * positionVS;
|
|
|
|
|
}
|
|
|
|
|
)";
|
|
|
|
|
|
|
|
|
|
const char kBuiltinObjectIdFragmentShader[] = R"(#version 430
|
|
|
|
|
layout(std140, binding = 0) uniform PerObjectConstants {
|
|
|
|
|
mat4 gProjectionMatrix;
|
|
|
|
|
mat4 gViewMatrix;
|
|
|
|
|
mat4 gModelMatrix;
|
|
|
|
|
vec4 gObjectIdColor;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
layout(location = 0) out vec4 fragColor;
|
|
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
|
fragColor = gObjectIdColor;
|
|
|
|
|
}
|
|
|
|
|
)";
|
|
|
|
|
|
|
|
|
|
const char kBuiltinObjectIdVulkanVertexShader[] = R"(#version 450
|
|
|
|
|
layout(location = 0) in vec3 aPosition;
|
|
|
|
|
|
|
|
|
|
layout(set = 0, binding = 0, std140) uniform PerObjectConstants {
|
|
|
|
|
mat4 gProjectionMatrix;
|
|
|
|
|
mat4 gViewMatrix;
|
|
|
|
|
mat4 gModelMatrix;
|
|
|
|
|
vec4 gObjectIdColor;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
|
vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0);
|
|
|
|
|
vec4 positionVS = gViewMatrix * positionWS;
|
|
|
|
|
gl_Position = gProjectionMatrix * positionVS;
|
|
|
|
|
}
|
|
|
|
|
)";
|
|
|
|
|
|
|
|
|
|
const char kBuiltinObjectIdVulkanFragmentShader[] = R"(#version 450
|
|
|
|
|
layout(set = 0, binding = 0, std140) uniform PerObjectConstants {
|
|
|
|
|
mat4 gProjectionMatrix;
|
|
|
|
|
mat4 gViewMatrix;
|
|
|
|
|
mat4 gModelMatrix;
|
|
|
|
|
vec4 gObjectIdColor;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
layout(location = 0) out vec4 fragColor;
|
|
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
|
fragColor = gObjectIdColor;
|
|
|
|
|
}
|
|
|
|
|
)";
|
|
|
|
|
|
2026-04-02 20:18:39 +08:00
|
|
|
const char kBuiltinObjectIdOutlineHlsl[] = R"(
|
|
|
|
|
cbuffer OutlineConstants : register(b0) {
|
|
|
|
|
float4 gViewportSizeAndTexelSize;
|
|
|
|
|
float4 gOutlineColor;
|
|
|
|
|
float4 gSelectedInfo;
|
|
|
|
|
float4 gSelectedObjectColors[256];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Texture2D gObjectIdTexture : register(t0);
|
|
|
|
|
|
|
|
|
|
struct VSOutput {
|
|
|
|
|
float4 position : SV_POSITION;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
VSOutput MainVS(uint vertexId : SV_VertexID) {
|
|
|
|
|
static const float2 positions[3] = {
|
|
|
|
|
float2(-1.0, -1.0),
|
|
|
|
|
float2(-1.0, 3.0),
|
|
|
|
|
float2( 3.0, -1.0)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
VSOutput output;
|
|
|
|
|
output.position = float4(positions[vertexId], 0.0, 1.0);
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int2 ClampPixelCoord(int2 pixelCoord) {
|
|
|
|
|
const int2 maxCoord = int2(
|
|
|
|
|
max((int)gViewportSizeAndTexelSize.x - 1, 0),
|
|
|
|
|
max((int)gViewportSizeAndTexelSize.y - 1, 0));
|
|
|
|
|
return clamp(pixelCoord, int2(0, 0), maxCoord);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float4 LoadObjectId(int2 pixelCoord) {
|
|
|
|
|
return gObjectIdTexture.Load(int3(ClampPixelCoord(pixelCoord), 0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsSelectedObject(float4 objectIdColor) {
|
|
|
|
|
if (objectIdColor.a <= 0.0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const int selectedCount = min((int)gSelectedInfo.x, 256);
|
|
|
|
|
[loop]
|
|
|
|
|
for (int i = 0; i < selectedCount; ++i) {
|
|
|
|
|
const float4 selectedColor = gSelectedObjectColors[i];
|
|
|
|
|
if (all(abs(objectIdColor - selectedColor) <= float4(
|
|
|
|
|
0.0025,
|
|
|
|
|
0.0025,
|
|
|
|
|
0.0025,
|
|
|
|
|
0.0025))) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float4 MainPS(VSOutput input) : SV_TARGET {
|
|
|
|
|
const int2 pixelCoord = int2(input.position.xy);
|
|
|
|
|
const bool debugSelectionMask = gSelectedInfo.y > 0.5;
|
|
|
|
|
const bool centerSelected = IsSelectedObject(LoadObjectId(pixelCoord));
|
|
|
|
|
|
|
|
|
|
if (debugSelectionMask) {
|
|
|
|
|
return centerSelected ? float4(1.0, 1.0, 1.0, 1.0) : float4(0.0, 0.0, 0.0, 1.0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (centerSelected) {
|
|
|
|
|
discard;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const int outlineWidth = max((int)gSelectedInfo.z, 1);
|
|
|
|
|
float outline = 0.0;
|
|
|
|
|
[loop]
|
|
|
|
|
for (int y = -2; y <= 2; ++y) {
|
|
|
|
|
[loop]
|
|
|
|
|
for (int x = -2; x <= 2; ++x) {
|
|
|
|
|
if (x == 0 && y == 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float distancePixels = length(float2((float)x, (float)y));
|
|
|
|
|
if (distancePixels > outlineWidth) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!IsSelectedObject(LoadObjectId(pixelCoord + int2(x, y)))) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float weight = saturate(1.0 - ((distancePixels - 1.0) / max((float)outlineWidth, 1.0)));
|
|
|
|
|
outline = max(outline, weight);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (outline <= 0.001) {
|
|
|
|
|
discard;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return float4(gOutlineColor.rgb, gOutlineColor.a * outline);
|
|
|
|
|
}
|
|
|
|
|
)";
|
|
|
|
|
|
2026-04-02 19:32:15 +08:00
|
|
|
const char kBuiltinInfiniteGridHlsl[] = R"(
|
|
|
|
|
cbuffer GridConstants : register(b0) {
|
|
|
|
|
float4x4 gViewProjectionMatrix;
|
|
|
|
|
float4 gCameraPositionAndScale;
|
|
|
|
|
float4 gCameraRightAndFade;
|
|
|
|
|
float4 gCameraUpAndTanHalfFov;
|
|
|
|
|
float4 gCameraForwardAndAspect;
|
|
|
|
|
float4 gViewportNearFar;
|
|
|
|
|
float4 gGridTransition;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct VSOutput {
|
|
|
|
|
float4 position : SV_POSITION;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
VSOutput MainVS(uint vertexId : SV_VertexID) {
|
|
|
|
|
static const float2 positions[3] = {
|
|
|
|
|
float2(-1.0, -1.0),
|
|
|
|
|
float2(-1.0, 3.0),
|
|
|
|
|
float2( 3.0, -1.0)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
VSOutput output;
|
|
|
|
|
output.position = float4(positions[vertexId], 0.0, 1.0);
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float PristineGridLine(float2 uv) {
|
|
|
|
|
float2 deriv = max(fwidth(uv), float2(1e-6, 1e-6));
|
|
|
|
|
float2 uvMod = frac(uv);
|
|
|
|
|
float2 uvDist = min(uvMod, 1.0 - uvMod);
|
|
|
|
|
float2 distInPixels = uvDist / deriv;
|
|
|
|
|
float2 lineAlpha = 1.0 - smoothstep(0.0, 1.0, distInPixels);
|
|
|
|
|
float density = max(deriv.x, deriv.y);
|
|
|
|
|
float densityFade = 1.0 - smoothstep(0.5, 1.0, density);
|
|
|
|
|
return max(lineAlpha.x, lineAlpha.y) * densityFade;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float AxisLineAA(float coord, float deriv) {
|
|
|
|
|
float distInPixels = abs(coord) / max(deriv, 1e-6);
|
|
|
|
|
return 1.0 - smoothstep(0.0, 1.5, distInPixels);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct GridLayer {
|
|
|
|
|
float minor;
|
|
|
|
|
float major;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
GridLayer SampleGridLayer(float2 worldPos2D, float baseScale) {
|
|
|
|
|
GridLayer layer;
|
|
|
|
|
const float2 gridCoord1 = worldPos2D / baseScale;
|
|
|
|
|
const float2 gridCoord10 = worldPos2D / (baseScale * 10.0);
|
|
|
|
|
const float grid1 = PristineGridLine(gridCoord1);
|
|
|
|
|
const float grid10 = PristineGridLine(gridCoord10);
|
|
|
|
|
const float2 deriv1 = fwidth(gridCoord1);
|
|
|
|
|
const float lodFactor = smoothstep(0.3, 0.6, max(deriv1.x, deriv1.y));
|
|
|
|
|
|
|
|
|
|
layer.major = max(grid10, grid1 * 0.35);
|
|
|
|
|
layer.minor = grid1 * (1.0 - lodFactor);
|
|
|
|
|
return layer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct PSOutput {
|
|
|
|
|
float4 color : SV_TARGET0;
|
|
|
|
|
float depth : SV_Depth;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
PSOutput MainPS(VSOutput input) {
|
|
|
|
|
const float2 viewportSize = max(gViewportNearFar.xy, float2(1.0, 1.0));
|
|
|
|
|
const float scale = max(gCameraPositionAndScale.w, 1e-4);
|
|
|
|
|
const float fadeDistance = max(gCameraRightAndFade.w, scale * 10.0);
|
|
|
|
|
const float tanHalfFov = max(gCameraUpAndTanHalfFov.w, 1e-4);
|
|
|
|
|
const float aspect = max(gCameraForwardAndAspect.w, 1e-4);
|
|
|
|
|
const float transitionBlend = saturate(gGridTransition.x);
|
|
|
|
|
const float nearClip = gViewportNearFar.z;
|
|
|
|
|
const float sceneFarClip = gViewportNearFar.w;
|
|
|
|
|
|
|
|
|
|
const float2 ndc = float2(
|
|
|
|
|
(input.position.x / viewportSize.x) * 2.0 - 1.0,
|
|
|
|
|
1.0 - (input.position.y / viewportSize.y) * 2.0);
|
|
|
|
|
|
|
|
|
|
const float3 cameraPosition = gCameraPositionAndScale.xyz;
|
|
|
|
|
const float3 rayDirection = normalize(
|
|
|
|
|
gCameraForwardAndAspect.xyz +
|
|
|
|
|
ndc.x * aspect * tanHalfFov * gCameraRightAndFade.xyz +
|
|
|
|
|
ndc.y * tanHalfFov * gCameraUpAndTanHalfFov.xyz);
|
|
|
|
|
|
|
|
|
|
if (abs(rayDirection.y) < 1e-5) {
|
|
|
|
|
discard;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float t = -cameraPosition.y / rayDirection.y;
|
|
|
|
|
if (t <= nearClip) {
|
|
|
|
|
discard;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float3 worldPosition = cameraPosition + rayDirection * t;
|
|
|
|
|
float depth = 0.999999;
|
|
|
|
|
if (t < sceneFarClip) {
|
|
|
|
|
const float4 clipPosition = mul(gViewProjectionMatrix, float4(worldPosition, 1.0));
|
|
|
|
|
if (clipPosition.w <= 1e-6) {
|
|
|
|
|
discard;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
depth = clipPosition.z / clipPosition.w;
|
|
|
|
|
if (depth <= 0.0 || depth >= 1.0) {
|
|
|
|
|
discard;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float radialFade =
|
|
|
|
|
1.0 - smoothstep(fadeDistance * 0.3, fadeDistance, length(worldPosition - cameraPosition));
|
|
|
|
|
const float normalFade = smoothstep(0.0, 0.15, abs(rayDirection.y));
|
|
|
|
|
const float fadeFactor = radialFade * normalFade;
|
|
|
|
|
if (fadeFactor < 1e-3) {
|
|
|
|
|
discard;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float2 worldPos2D = worldPosition.xz;
|
|
|
|
|
const GridLayer baseLayer = SampleGridLayer(worldPos2D, scale);
|
|
|
|
|
const GridLayer nextLayer = SampleGridLayer(worldPos2D, scale * 10.0);
|
|
|
|
|
const float minorGridIntensity = lerp(baseLayer.minor, nextLayer.minor, transitionBlend);
|
|
|
|
|
const float majorGridIntensity = lerp(baseLayer.major, nextLayer.major, transitionBlend);
|
|
|
|
|
float3 finalColor = float3(0.56, 0.56, 0.56);
|
|
|
|
|
float finalAlpha = max(
|
|
|
|
|
0.13 * minorGridIntensity * fadeFactor,
|
|
|
|
|
0.28 * majorGridIntensity * fadeFactor);
|
|
|
|
|
|
|
|
|
|
const float2 worldDeriv = max(fwidth(worldPos2D), float2(1e-6, 1e-6));
|
|
|
|
|
const float xAxisAlpha = AxisLineAA(worldPos2D.y, worldDeriv.y) * fadeFactor;
|
|
|
|
|
const float zAxisAlpha = AxisLineAA(worldPos2D.x, worldDeriv.x) * fadeFactor;
|
|
|
|
|
|
|
|
|
|
const float axisAlpha = max(xAxisAlpha, zAxisAlpha);
|
|
|
|
|
finalAlpha = max(finalAlpha, 0.34 * saturate(axisAlpha));
|
|
|
|
|
|
|
|
|
|
if (finalAlpha < 1e-3) {
|
|
|
|
|
discard;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PSOutput output;
|
|
|
|
|
output.color = float4(finalColor, finalAlpha);
|
|
|
|
|
output.depth = depth;
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
)";
|
|
|
|
|
|
2026-04-02 03:03:36 +08:00
|
|
|
Math::Bounds ComputeBounds(const std::vector<StaticMeshVertex>& vertices) {
|
|
|
|
|
if (vertices.empty()) {
|
|
|
|
|
return Math::Bounds();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Math::Vector3 min = vertices.front().position;
|
|
|
|
|
Math::Vector3 max = vertices.front().position;
|
|
|
|
|
for (const StaticMeshVertex& vertex : vertices) {
|
|
|
|
|
min.x = std::min(min.x, vertex.position.x);
|
|
|
|
|
min.y = std::min(min.y, vertex.position.y);
|
|
|
|
|
min.z = std::min(min.z, vertex.position.z);
|
|
|
|
|
max.x = std::max(max.x, vertex.position.x);
|
|
|
|
|
max.y = std::max(max.y, vertex.position.y);
|
|
|
|
|
max.z = std::max(max.z, vertex.position.z);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Math::Bounds bounds;
|
|
|
|
|
bounds.SetMinMax(min, max);
|
|
|
|
|
return bounds;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
StaticMeshVertex MakeVertex(
|
|
|
|
|
const Math::Vector3& position,
|
|
|
|
|
const Math::Vector3& normal,
|
|
|
|
|
const Math::Vector3& tangent,
|
|
|
|
|
const Math::Vector2& uv) {
|
|
|
|
|
StaticMeshVertex vertex;
|
|
|
|
|
vertex.position = position;
|
|
|
|
|
vertex.normal = normal.Normalized();
|
|
|
|
|
vertex.tangent = tangent.Normalized();
|
|
|
|
|
vertex.bitangent = Math::Vector3::Cross(vertex.normal, vertex.tangent).Normalized();
|
|
|
|
|
vertex.uv0 = uv;
|
|
|
|
|
return vertex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AppendQuad(
|
|
|
|
|
MeshBuffers& buffers,
|
|
|
|
|
const Math::Vector3& bottomLeft,
|
|
|
|
|
const Math::Vector3& bottomRight,
|
|
|
|
|
const Math::Vector3& topRight,
|
|
|
|
|
const Math::Vector3& topLeft,
|
|
|
|
|
const Math::Vector3& normal,
|
|
|
|
|
const Math::Vector3& tangent) {
|
|
|
|
|
const Core::uint32 baseIndex = static_cast<Core::uint32>(buffers.vertices.size());
|
|
|
|
|
buffers.vertices.push_back(MakeVertex(bottomLeft, normal, tangent, Math::Vector2(0.0f, 0.0f)));
|
|
|
|
|
buffers.vertices.push_back(MakeVertex(bottomRight, normal, tangent, Math::Vector2(1.0f, 0.0f)));
|
|
|
|
|
buffers.vertices.push_back(MakeVertex(topRight, normal, tangent, Math::Vector2(1.0f, 1.0f)));
|
|
|
|
|
buffers.vertices.push_back(MakeVertex(topLeft, normal, tangent, Math::Vector2(0.0f, 1.0f)));
|
|
|
|
|
|
|
|
|
|
buffers.indices.push_back(baseIndex + 0);
|
|
|
|
|
buffers.indices.push_back(baseIndex + 1);
|
|
|
|
|
buffers.indices.push_back(baseIndex + 2);
|
|
|
|
|
buffers.indices.push_back(baseIndex + 0);
|
|
|
|
|
buffers.indices.push_back(baseIndex + 2);
|
|
|
|
|
buffers.indices.push_back(baseIndex + 3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FlipTriangleWinding(MeshBuffers& buffers) {
|
|
|
|
|
for (size_t index = 0; index + 2 < buffers.indices.size(); index += 3) {
|
|
|
|
|
std::swap(buffers.indices[index + 1], buffers.indices[index + 2]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MeshBuffers CreateCubeMeshBuffers() {
|
|
|
|
|
MeshBuffers buffers;
|
|
|
|
|
const float half = 0.5f;
|
|
|
|
|
|
|
|
|
|
AppendQuad(
|
|
|
|
|
buffers,
|
|
|
|
|
Math::Vector3(-half, -half, half),
|
|
|
|
|
Math::Vector3(half, -half, half),
|
|
|
|
|
Math::Vector3(half, half, half),
|
|
|
|
|
Math::Vector3(-half, half, half),
|
|
|
|
|
Math::Vector3::Forward(),
|
|
|
|
|
Math::Vector3::Right());
|
|
|
|
|
AppendQuad(
|
|
|
|
|
buffers,
|
|
|
|
|
Math::Vector3(half, -half, -half),
|
|
|
|
|
Math::Vector3(-half, -half, -half),
|
|
|
|
|
Math::Vector3(-half, half, -half),
|
|
|
|
|
Math::Vector3(half, half, -half),
|
|
|
|
|
Math::Vector3::Back(),
|
|
|
|
|
Math::Vector3::Left());
|
|
|
|
|
AppendQuad(
|
|
|
|
|
buffers,
|
|
|
|
|
Math::Vector3(-half, -half, -half),
|
|
|
|
|
Math::Vector3(-half, -half, half),
|
|
|
|
|
Math::Vector3(-half, half, half),
|
|
|
|
|
Math::Vector3(-half, half, -half),
|
|
|
|
|
Math::Vector3::Left(),
|
|
|
|
|
Math::Vector3::Forward());
|
|
|
|
|
AppendQuad(
|
|
|
|
|
buffers,
|
|
|
|
|
Math::Vector3(half, -half, half),
|
|
|
|
|
Math::Vector3(half, -half, -half),
|
|
|
|
|
Math::Vector3(half, half, -half),
|
|
|
|
|
Math::Vector3(half, half, half),
|
|
|
|
|
Math::Vector3::Right(),
|
|
|
|
|
Math::Vector3::Back());
|
|
|
|
|
AppendQuad(
|
|
|
|
|
buffers,
|
|
|
|
|
Math::Vector3(-half, half, half),
|
|
|
|
|
Math::Vector3(half, half, half),
|
|
|
|
|
Math::Vector3(half, half, -half),
|
|
|
|
|
Math::Vector3(-half, half, -half),
|
|
|
|
|
Math::Vector3::Up(),
|
|
|
|
|
Math::Vector3::Right());
|
|
|
|
|
AppendQuad(
|
|
|
|
|
buffers,
|
|
|
|
|
Math::Vector3(-half, -half, -half),
|
|
|
|
|
Math::Vector3(half, -half, -half),
|
|
|
|
|
Math::Vector3(half, -half, half),
|
|
|
|
|
Math::Vector3(-half, -half, half),
|
|
|
|
|
Math::Vector3::Down(),
|
|
|
|
|
Math::Vector3::Right());
|
|
|
|
|
|
|
|
|
|
return buffers;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MeshBuffers CreateQuadMeshBuffers() {
|
|
|
|
|
MeshBuffers buffers;
|
|
|
|
|
AppendQuad(
|
|
|
|
|
buffers,
|
|
|
|
|
Math::Vector3(-0.5f, -0.5f, 0.0f),
|
|
|
|
|
Math::Vector3(0.5f, -0.5f, 0.0f),
|
|
|
|
|
Math::Vector3(0.5f, 0.5f, 0.0f),
|
|
|
|
|
Math::Vector3(-0.5f, 0.5f, 0.0f),
|
|
|
|
|
Math::Vector3::Forward(),
|
|
|
|
|
Math::Vector3::Right());
|
|
|
|
|
return buffers;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MeshBuffers CreatePlaneMeshBuffers() {
|
|
|
|
|
MeshBuffers buffers;
|
|
|
|
|
constexpr int kSegments = 10;
|
|
|
|
|
constexpr float kSize = 10.0f;
|
|
|
|
|
const float halfSize = kSize * 0.5f;
|
|
|
|
|
const float step = kSize / static_cast<float>(kSegments);
|
|
|
|
|
|
|
|
|
|
for (int z = 0; z <= kSegments; ++z) {
|
|
|
|
|
for (int x = 0; x <= kSegments; ++x) {
|
|
|
|
|
const float px = -halfSize + static_cast<float>(x) * step;
|
|
|
|
|
const float pz = -halfSize + static_cast<float>(z) * step;
|
|
|
|
|
const float u = static_cast<float>(x) / static_cast<float>(kSegments);
|
|
|
|
|
const float v = static_cast<float>(z) / static_cast<float>(kSegments);
|
|
|
|
|
buffers.vertices.push_back(MakeVertex(
|
|
|
|
|
Math::Vector3(px, 0.0f, pz),
|
|
|
|
|
Math::Vector3::Up(),
|
|
|
|
|
Math::Vector3::Right(),
|
|
|
|
|
Math::Vector2(u, v)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const int rowStride = kSegments + 1;
|
|
|
|
|
for (int z = 0; z < kSegments; ++z) {
|
|
|
|
|
for (int x = 0; x < kSegments; ++x) {
|
|
|
|
|
const Core::uint32 i0 = static_cast<Core::uint32>(z * rowStride + x);
|
|
|
|
|
const Core::uint32 i1 = i0 + 1;
|
|
|
|
|
const Core::uint32 i2 = i0 + static_cast<Core::uint32>(rowStride);
|
|
|
|
|
const Core::uint32 i3 = i2 + 1;
|
|
|
|
|
|
|
|
|
|
buffers.indices.push_back(i0);
|
|
|
|
|
buffers.indices.push_back(i3);
|
|
|
|
|
buffers.indices.push_back(i1);
|
|
|
|
|
buffers.indices.push_back(i0);
|
|
|
|
|
buffers.indices.push_back(i2);
|
|
|
|
|
buffers.indices.push_back(i3);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return buffers;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MeshBuffers CreateUvSphereMeshBuffers() {
|
|
|
|
|
MeshBuffers buffers;
|
|
|
|
|
constexpr int kLongitudeSegments = 24;
|
|
|
|
|
constexpr int kLatitudeSegments = 16;
|
|
|
|
|
constexpr float kRadius = 0.5f;
|
|
|
|
|
|
|
|
|
|
for (int latitude = 0; latitude <= kLatitudeSegments; ++latitude) {
|
|
|
|
|
const float v = static_cast<float>(latitude) / static_cast<float>(kLatitudeSegments);
|
|
|
|
|
const float theta = v * kPi;
|
|
|
|
|
const float sinTheta = std::sin(theta);
|
|
|
|
|
const float cosTheta = std::cos(theta);
|
|
|
|
|
|
|
|
|
|
for (int longitude = 0; longitude <= kLongitudeSegments; ++longitude) {
|
|
|
|
|
const float u = static_cast<float>(longitude) / static_cast<float>(kLongitudeSegments);
|
|
|
|
|
const float phi = u * (2.0f * kPi);
|
|
|
|
|
const float sinPhi = std::sin(phi);
|
|
|
|
|
const float cosPhi = std::cos(phi);
|
|
|
|
|
|
|
|
|
|
const Math::Vector3 normal(cosPhi * sinTheta, cosTheta, sinPhi * sinTheta);
|
|
|
|
|
Math::Vector3 tangent(-sinPhi, 0.0f, cosPhi);
|
|
|
|
|
if (tangent.SqrMagnitude() <= 0.000001f) {
|
|
|
|
|
tangent = Math::Vector3::Right();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
buffers.vertices.push_back(MakeVertex(
|
|
|
|
|
normal * kRadius,
|
|
|
|
|
normal,
|
|
|
|
|
tangent,
|
|
|
|
|
Math::Vector2(u, 1.0f - v)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const int stride = kLongitudeSegments + 1;
|
|
|
|
|
for (int latitude = 0; latitude < kLatitudeSegments; ++latitude) {
|
|
|
|
|
for (int longitude = 0; longitude < kLongitudeSegments; ++longitude) {
|
|
|
|
|
const Core::uint32 i0 = static_cast<Core::uint32>(latitude * stride + longitude);
|
|
|
|
|
const Core::uint32 i1 = i0 + 1;
|
|
|
|
|
const Core::uint32 i2 = i0 + static_cast<Core::uint32>(stride);
|
|
|
|
|
const Core::uint32 i3 = i2 + 1;
|
|
|
|
|
|
|
|
|
|
buffers.indices.push_back(i0);
|
|
|
|
|
buffers.indices.push_back(i2);
|
|
|
|
|
buffers.indices.push_back(i1);
|
|
|
|
|
buffers.indices.push_back(i1);
|
|
|
|
|
buffers.indices.push_back(i2);
|
|
|
|
|
buffers.indices.push_back(i3);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return buffers;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MeshBuffers CreateCylinderMeshBuffers() {
|
|
|
|
|
MeshBuffers buffers;
|
|
|
|
|
constexpr int kRadialSegments = 24;
|
|
|
|
|
constexpr float kRadius = 0.5f;
|
|
|
|
|
constexpr float kHalfHeight = 1.0f;
|
|
|
|
|
|
|
|
|
|
for (int ring = 0; ring <= 1; ++ring) {
|
|
|
|
|
const float y = ring == 0 ? -kHalfHeight : kHalfHeight;
|
|
|
|
|
const float v = static_cast<float>(ring);
|
|
|
|
|
for (int segment = 0; segment <= kRadialSegments; ++segment) {
|
|
|
|
|
const float u = static_cast<float>(segment) / static_cast<float>(kRadialSegments);
|
|
|
|
|
const float angle = u * (2.0f * kPi);
|
|
|
|
|
const float cosAngle = std::cos(angle);
|
|
|
|
|
const float sinAngle = std::sin(angle);
|
|
|
|
|
const Math::Vector3 normal(cosAngle, 0.0f, sinAngle);
|
|
|
|
|
const Math::Vector3 tangent(-sinAngle, 0.0f, cosAngle);
|
|
|
|
|
buffers.vertices.push_back(MakeVertex(
|
|
|
|
|
Math::Vector3(cosAngle * kRadius, y, sinAngle * kRadius),
|
|
|
|
|
normal,
|
|
|
|
|
tangent,
|
|
|
|
|
Math::Vector2(u, v)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const int sideStride = kRadialSegments + 1;
|
|
|
|
|
for (int segment = 0; segment < kRadialSegments; ++segment) {
|
|
|
|
|
const Core::uint32 i0 = static_cast<Core::uint32>(segment);
|
|
|
|
|
const Core::uint32 i1 = i0 + 1;
|
|
|
|
|
const Core::uint32 i2 = i0 + static_cast<Core::uint32>(sideStride);
|
|
|
|
|
const Core::uint32 i3 = i2 + 1;
|
|
|
|
|
|
|
|
|
|
buffers.indices.push_back(i0);
|
|
|
|
|
buffers.indices.push_back(i2);
|
|
|
|
|
buffers.indices.push_back(i1);
|
|
|
|
|
buffers.indices.push_back(i1);
|
|
|
|
|
buffers.indices.push_back(i2);
|
|
|
|
|
buffers.indices.push_back(i3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto appendCap = [&](bool topCap) {
|
|
|
|
|
const float y = topCap ? kHalfHeight : -kHalfHeight;
|
|
|
|
|
const Math::Vector3 normal = topCap ? Math::Vector3::Up() : Math::Vector3::Down();
|
|
|
|
|
const Core::uint32 centerIndex = static_cast<Core::uint32>(buffers.vertices.size());
|
|
|
|
|
buffers.vertices.push_back(MakeVertex(
|
|
|
|
|
Math::Vector3(0.0f, y, 0.0f),
|
|
|
|
|
normal,
|
|
|
|
|
Math::Vector3::Right(),
|
|
|
|
|
Math::Vector2(0.5f, 0.5f)));
|
|
|
|
|
|
|
|
|
|
for (int segment = 0; segment <= kRadialSegments; ++segment) {
|
|
|
|
|
const float u = static_cast<float>(segment) / static_cast<float>(kRadialSegments);
|
|
|
|
|
const float angle = u * (2.0f * kPi);
|
|
|
|
|
const float cosAngle = std::cos(angle);
|
|
|
|
|
const float sinAngle = std::sin(angle);
|
|
|
|
|
buffers.vertices.push_back(MakeVertex(
|
|
|
|
|
Math::Vector3(cosAngle * kRadius, y, sinAngle * kRadius),
|
|
|
|
|
normal,
|
|
|
|
|
Math::Vector3::Right(),
|
|
|
|
|
Math::Vector2(cosAngle * 0.5f + 0.5f, sinAngle * 0.5f + 0.5f)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int segment = 0; segment < kRadialSegments; ++segment) {
|
|
|
|
|
const Core::uint32 rim0 = centerIndex + 1 + static_cast<Core::uint32>(segment);
|
|
|
|
|
const Core::uint32 rim1 = rim0 + 1;
|
|
|
|
|
if (topCap) {
|
|
|
|
|
buffers.indices.push_back(centerIndex);
|
|
|
|
|
buffers.indices.push_back(rim0);
|
|
|
|
|
buffers.indices.push_back(rim1);
|
|
|
|
|
} else {
|
|
|
|
|
buffers.indices.push_back(centerIndex);
|
|
|
|
|
buffers.indices.push_back(rim1);
|
|
|
|
|
buffers.indices.push_back(rim0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
appendCap(true);
|
|
|
|
|
appendCap(false);
|
|
|
|
|
return buffers;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MeshBuffers CreateCapsuleMeshBuffers() {
|
|
|
|
|
MeshBuffers buffers;
|
|
|
|
|
constexpr int kRadialSegments = 24;
|
|
|
|
|
constexpr int kHemisphereSegments = 8;
|
|
|
|
|
constexpr float kRadius = 0.5f;
|
|
|
|
|
constexpr float kHalfCylinderHeight = 0.5f;
|
|
|
|
|
|
|
|
|
|
struct RingDefinition {
|
|
|
|
|
float y = 0.0f;
|
|
|
|
|
float radius = 0.0f;
|
|
|
|
|
Math::Vector3 normalBase = Math::Vector3::Zero();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::vector<RingDefinition> rings;
|
|
|
|
|
rings.reserve(static_cast<size_t>(kHemisphereSegments * 2 + 2));
|
|
|
|
|
|
|
|
|
|
for (int step = 0; step <= kHemisphereSegments; ++step) {
|
|
|
|
|
const float t = static_cast<float>(step) / static_cast<float>(kHemisphereSegments);
|
|
|
|
|
const float angle = -0.5f * kPi + t * (0.5f * kPi);
|
|
|
|
|
const float ringRadius = std::cos(angle) * kRadius;
|
|
|
|
|
const float y = std::sin(angle) * kRadius - kHalfCylinderHeight;
|
|
|
|
|
const Math::Vector3 normalBase(0.0f, std::sin(angle), 0.0f);
|
|
|
|
|
rings.push_back({ y, ringRadius, normalBase });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int step = 0; step <= kHemisphereSegments; ++step) {
|
|
|
|
|
const float t = static_cast<float>(step) / static_cast<float>(kHemisphereSegments);
|
|
|
|
|
const float angle = t * (0.5f * kPi);
|
|
|
|
|
const float ringRadius = std::cos(angle) * kRadius;
|
|
|
|
|
const float y = std::sin(angle) * kRadius + kHalfCylinderHeight;
|
|
|
|
|
const Math::Vector3 normalBase(0.0f, std::sin(angle), 0.0f);
|
|
|
|
|
rings.push_back({ y, ringRadius, normalBase });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (size_t ringIndex = 0; ringIndex < rings.size(); ++ringIndex) {
|
|
|
|
|
const float v = rings.size() > 1
|
|
|
|
|
? static_cast<float>(ringIndex) / static_cast<float>(rings.size() - 1)
|
|
|
|
|
: 0.0f;
|
|
|
|
|
for (int segment = 0; segment <= kRadialSegments; ++segment) {
|
|
|
|
|
const float u = static_cast<float>(segment) / static_cast<float>(kRadialSegments);
|
|
|
|
|
const float angle = u * (2.0f * kPi);
|
|
|
|
|
const float cosAngle = std::cos(angle);
|
|
|
|
|
const float sinAngle = std::sin(angle);
|
|
|
|
|
const Math::Vector3 radial(cosAngle, 0.0f, sinAngle);
|
|
|
|
|
|
|
|
|
|
Math::Vector3 normal(
|
|
|
|
|
radial.x * rings[ringIndex].radius,
|
|
|
|
|
rings[ringIndex].normalBase.y * kRadius,
|
|
|
|
|
radial.z * rings[ringIndex].radius);
|
|
|
|
|
normal = normal.Normalized();
|
|
|
|
|
if (normal.SqrMagnitude() <= 0.000001f) {
|
|
|
|
|
normal = rings[ringIndex].y >= 0.0f ? Math::Vector3::Up() : Math::Vector3::Down();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Math::Vector3 tangent(-sinAngle, 0.0f, cosAngle);
|
|
|
|
|
buffers.vertices.push_back(MakeVertex(
|
|
|
|
|
Math::Vector3(radial.x * rings[ringIndex].radius, rings[ringIndex].y, radial.z * rings[ringIndex].radius),
|
|
|
|
|
normal,
|
|
|
|
|
tangent,
|
|
|
|
|
Math::Vector2(u, 1.0f - v)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const int stride = kRadialSegments + 1;
|
|
|
|
|
for (size_t ringIndex = 0; ringIndex + 1 < rings.size(); ++ringIndex) {
|
|
|
|
|
for (int segment = 0; segment < kRadialSegments; ++segment) {
|
|
|
|
|
const Core::uint32 i0 = static_cast<Core::uint32>(ringIndex * stride + static_cast<size_t>(segment));
|
|
|
|
|
const Core::uint32 i1 = i0 + 1;
|
|
|
|
|
const Core::uint32 i2 = i0 + static_cast<Core::uint32>(stride);
|
|
|
|
|
const Core::uint32 i3 = i2 + 1;
|
|
|
|
|
|
|
|
|
|
buffers.indices.push_back(i0);
|
|
|
|
|
buffers.indices.push_back(i2);
|
|
|
|
|
buffers.indices.push_back(i1);
|
|
|
|
|
buffers.indices.push_back(i1);
|
|
|
|
|
buffers.indices.push_back(i2);
|
|
|
|
|
buffers.indices.push_back(i3);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return buffers;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Mesh* BuildMeshResource(
|
|
|
|
|
const Containers::String& path,
|
|
|
|
|
const char* displayName,
|
|
|
|
|
MeshBuffers&& buffers) {
|
|
|
|
|
if (buffers.vertices.empty() || buffers.indices.empty()) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto* mesh = new Mesh();
|
|
|
|
|
IResource::ConstructParams params;
|
|
|
|
|
params.name = Containers::String(displayName);
|
|
|
|
|
params.path = path;
|
|
|
|
|
params.guid = ResourceGUID::Generate(path);
|
|
|
|
|
params.memorySize = 0;
|
|
|
|
|
mesh->Initialize(params);
|
|
|
|
|
|
|
|
|
|
mesh->SetVertexData(
|
|
|
|
|
buffers.vertices.data(),
|
|
|
|
|
buffers.vertices.size() * sizeof(StaticMeshVertex),
|
|
|
|
|
static_cast<Core::uint32>(buffers.vertices.size()),
|
|
|
|
|
sizeof(StaticMeshVertex),
|
|
|
|
|
VertexAttribute::Position |
|
|
|
|
|
VertexAttribute::Normal |
|
|
|
|
|
VertexAttribute::Tangent |
|
|
|
|
|
VertexAttribute::Bitangent |
|
|
|
|
|
VertexAttribute::UV0);
|
|
|
|
|
|
|
|
|
|
if (buffers.vertices.size() > 65535u) {
|
|
|
|
|
mesh->SetIndexData(
|
|
|
|
|
buffers.indices.data(),
|
|
|
|
|
buffers.indices.size() * sizeof(Core::uint32),
|
|
|
|
|
static_cast<Core::uint32>(buffers.indices.size()),
|
|
|
|
|
true);
|
|
|
|
|
} else {
|
|
|
|
|
std::vector<Core::uint16> compactIndices;
|
|
|
|
|
compactIndices.reserve(buffers.indices.size());
|
|
|
|
|
for (Core::uint32 index : buffers.indices) {
|
|
|
|
|
compactIndices.push_back(static_cast<Core::uint16>(index));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mesh->SetIndexData(
|
|
|
|
|
compactIndices.data(),
|
|
|
|
|
compactIndices.size() * sizeof(Core::uint16),
|
|
|
|
|
static_cast<Core::uint32>(compactIndices.size()),
|
|
|
|
|
false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Math::Bounds bounds = ComputeBounds(buffers.vertices);
|
|
|
|
|
mesh->SetBounds(bounds);
|
|
|
|
|
|
|
|
|
|
MeshSection section = {};
|
|
|
|
|
section.baseVertex = 0;
|
|
|
|
|
section.vertexCount = static_cast<Core::uint32>(buffers.vertices.size());
|
|
|
|
|
section.startIndex = 0;
|
|
|
|
|
section.indexCount = static_cast<Core::uint32>(buffers.indices.size());
|
|
|
|
|
section.materialID = 0;
|
|
|
|
|
section.bounds = bounds;
|
|
|
|
|
mesh->AddSection(section);
|
|
|
|
|
|
|
|
|
|
return mesh;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 19:00:48 +08:00
|
|
|
void AddBuiltinShaderStageVariant(
|
|
|
|
|
Shader& shader,
|
|
|
|
|
const Containers::String& passName,
|
|
|
|
|
ShaderType stage,
|
|
|
|
|
ShaderLanguage language,
|
|
|
|
|
ShaderBackend backend,
|
|
|
|
|
const char* sourceCode,
|
|
|
|
|
const char* entryPoint,
|
|
|
|
|
const char* profile) {
|
|
|
|
|
ShaderStageVariant variant = {};
|
|
|
|
|
variant.stage = stage;
|
|
|
|
|
variant.language = language;
|
|
|
|
|
variant.backend = backend;
|
|
|
|
|
variant.sourceCode = Containers::String(sourceCode);
|
|
|
|
|
variant.entryPoint = Containers::String(entryPoint);
|
|
|
|
|
variant.profile = Containers::String(profile);
|
|
|
|
|
shader.AddPassVariant(passName, variant);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t CalculateBuiltinShaderMemorySize(const Shader& shader) {
|
|
|
|
|
size_t memorySize = sizeof(Shader) + shader.GetName().Length() + shader.GetPath().Length();
|
|
|
|
|
for (const ShaderPass& pass : shader.GetPasses()) {
|
|
|
|
|
memorySize += pass.name.Length();
|
|
|
|
|
for (const ShaderPassTagEntry& tag : pass.tags) {
|
|
|
|
|
memorySize += tag.name.Length();
|
|
|
|
|
memorySize += tag.value.Length();
|
|
|
|
|
}
|
|
|
|
|
for (const ShaderStageVariant& variant : pass.variants) {
|
|
|
|
|
memorySize += variant.entryPoint.Length();
|
|
|
|
|
memorySize += variant.profile.Length();
|
|
|
|
|
memorySize += variant.sourceCode.Length();
|
|
|
|
|
memorySize += variant.compiledBinary.Size();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return memorySize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Shader* BuildBuiltinForwardLitShader(const Containers::String& path) {
|
2026-04-02 23:15:19 +08:00
|
|
|
if (Shader* shader = TryLoadBuiltinShaderFromManifest(path)) {
|
|
|
|
|
return shader;
|
2026-04-02 23:04:59 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-02 19:00:48 +08:00
|
|
|
auto* shader = new Shader();
|
|
|
|
|
IResource::ConstructParams params;
|
|
|
|
|
params.name = Containers::String("Builtin Forward Lit");
|
|
|
|
|
params.path = path;
|
|
|
|
|
params.guid = ResourceGUID::Generate(path);
|
|
|
|
|
params.memorySize = 0;
|
|
|
|
|
shader->Initialize(params);
|
|
|
|
|
|
|
|
|
|
const Containers::String passName("ForwardLit");
|
|
|
|
|
shader->SetPassTag(passName, "LightMode", "ForwardBase");
|
|
|
|
|
|
|
|
|
|
AddBuiltinShaderStageVariant(
|
|
|
|
|
*shader,
|
|
|
|
|
passName,
|
|
|
|
|
ShaderType::Vertex,
|
|
|
|
|
ShaderLanguage::HLSL,
|
|
|
|
|
ShaderBackend::D3D12,
|
|
|
|
|
kBuiltinForwardHlsl,
|
|
|
|
|
"MainVS",
|
|
|
|
|
"vs_5_0");
|
|
|
|
|
AddBuiltinShaderStageVariant(
|
|
|
|
|
*shader,
|
|
|
|
|
passName,
|
|
|
|
|
ShaderType::Fragment,
|
|
|
|
|
ShaderLanguage::HLSL,
|
|
|
|
|
ShaderBackend::D3D12,
|
|
|
|
|
kBuiltinForwardHlsl,
|
|
|
|
|
"MainPS",
|
|
|
|
|
"ps_5_0");
|
|
|
|
|
|
|
|
|
|
AddBuiltinShaderStageVariant(
|
|
|
|
|
*shader,
|
|
|
|
|
passName,
|
|
|
|
|
ShaderType::Vertex,
|
|
|
|
|
ShaderLanguage::GLSL,
|
|
|
|
|
ShaderBackend::OpenGL,
|
|
|
|
|
kBuiltinForwardVertexShader,
|
|
|
|
|
"main",
|
|
|
|
|
"vs_4_30");
|
|
|
|
|
AddBuiltinShaderStageVariant(
|
|
|
|
|
*shader,
|
|
|
|
|
passName,
|
|
|
|
|
ShaderType::Fragment,
|
|
|
|
|
ShaderLanguage::GLSL,
|
|
|
|
|
ShaderBackend::OpenGL,
|
|
|
|
|
kBuiltinForwardFragmentShader,
|
|
|
|
|
"main",
|
|
|
|
|
"fs_4_30");
|
|
|
|
|
|
|
|
|
|
AddBuiltinShaderStageVariant(
|
|
|
|
|
*shader,
|
|
|
|
|
passName,
|
|
|
|
|
ShaderType::Vertex,
|
|
|
|
|
ShaderLanguage::GLSL,
|
|
|
|
|
ShaderBackend::Vulkan,
|
|
|
|
|
kBuiltinForwardVulkanVertexShader,
|
|
|
|
|
"main",
|
|
|
|
|
"vs_4_50");
|
|
|
|
|
AddBuiltinShaderStageVariant(
|
|
|
|
|
*shader,
|
|
|
|
|
passName,
|
|
|
|
|
ShaderType::Fragment,
|
|
|
|
|
ShaderLanguage::GLSL,
|
|
|
|
|
ShaderBackend::Vulkan,
|
|
|
|
|
kBuiltinForwardVulkanFragmentShader,
|
|
|
|
|
"main",
|
|
|
|
|
"fs_4_50");
|
|
|
|
|
|
|
|
|
|
shader->m_memorySize = CalculateBuiltinShaderMemorySize(*shader);
|
|
|
|
|
return shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 19:17:22 +08:00
|
|
|
Shader* BuildBuiltinObjectIdShader(const Containers::String& path) {
|
2026-04-02 23:15:19 +08:00
|
|
|
if (Shader* shader = TryLoadBuiltinShaderFromManifest(path)) {
|
|
|
|
|
return shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 19:17:22 +08:00
|
|
|
auto* shader = new Shader();
|
|
|
|
|
IResource::ConstructParams params;
|
|
|
|
|
params.name = Containers::String("Builtin Object Id");
|
|
|
|
|
params.path = path;
|
|
|
|
|
params.guid = ResourceGUID::Generate(path);
|
|
|
|
|
params.memorySize = 0;
|
|
|
|
|
shader->Initialize(params);
|
|
|
|
|
|
|
|
|
|
const Containers::String passName("ObjectId");
|
|
|
|
|
shader->SetPassTag(passName, "LightMode", "ObjectId");
|
|
|
|
|
|
|
|
|
|
AddBuiltinShaderStageVariant(
|
|
|
|
|
*shader,
|
|
|
|
|
passName,
|
|
|
|
|
ShaderType::Vertex,
|
|
|
|
|
ShaderLanguage::HLSL,
|
|
|
|
|
ShaderBackend::D3D12,
|
|
|
|
|
kBuiltinObjectIdHlsl,
|
|
|
|
|
"MainVS",
|
|
|
|
|
"vs_5_0");
|
|
|
|
|
AddBuiltinShaderStageVariant(
|
|
|
|
|
*shader,
|
|
|
|
|
passName,
|
|
|
|
|
ShaderType::Fragment,
|
|
|
|
|
ShaderLanguage::HLSL,
|
|
|
|
|
ShaderBackend::D3D12,
|
|
|
|
|
kBuiltinObjectIdHlsl,
|
|
|
|
|
"MainPS",
|
|
|
|
|
"ps_5_0");
|
|
|
|
|
|
|
|
|
|
AddBuiltinShaderStageVariant(
|
|
|
|
|
*shader,
|
|
|
|
|
passName,
|
|
|
|
|
ShaderType::Vertex,
|
|
|
|
|
ShaderLanguage::GLSL,
|
|
|
|
|
ShaderBackend::OpenGL,
|
|
|
|
|
kBuiltinObjectIdVertexShader,
|
|
|
|
|
"main",
|
|
|
|
|
"vs_4_30");
|
|
|
|
|
AddBuiltinShaderStageVariant(
|
|
|
|
|
*shader,
|
|
|
|
|
passName,
|
|
|
|
|
ShaderType::Fragment,
|
|
|
|
|
ShaderLanguage::GLSL,
|
|
|
|
|
ShaderBackend::OpenGL,
|
|
|
|
|
kBuiltinObjectIdFragmentShader,
|
|
|
|
|
"main",
|
|
|
|
|
"fs_4_30");
|
|
|
|
|
|
|
|
|
|
AddBuiltinShaderStageVariant(
|
|
|
|
|
*shader,
|
|
|
|
|
passName,
|
|
|
|
|
ShaderType::Vertex,
|
|
|
|
|
ShaderLanguage::GLSL,
|
|
|
|
|
ShaderBackend::Vulkan,
|
|
|
|
|
kBuiltinObjectIdVulkanVertexShader,
|
|
|
|
|
"main",
|
|
|
|
|
"vs_4_50");
|
|
|
|
|
AddBuiltinShaderStageVariant(
|
|
|
|
|
*shader,
|
|
|
|
|
passName,
|
|
|
|
|
ShaderType::Fragment,
|
|
|
|
|
ShaderLanguage::GLSL,
|
|
|
|
|
ShaderBackend::Vulkan,
|
|
|
|
|
kBuiltinObjectIdVulkanFragmentShader,
|
|
|
|
|
"main",
|
|
|
|
|
"fs_4_50");
|
|
|
|
|
|
|
|
|
|
shader->m_memorySize = CalculateBuiltinShaderMemorySize(*shader);
|
|
|
|
|
return shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 19:32:15 +08:00
|
|
|
Shader* BuildBuiltinInfiniteGridShader(const Containers::String& path) {
|
2026-04-02 23:15:19 +08:00
|
|
|
if (Shader* shader = TryLoadBuiltinShaderFromManifest(path)) {
|
|
|
|
|
return shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 19:32:15 +08:00
|
|
|
auto* shader = new Shader();
|
|
|
|
|
IResource::ConstructParams params;
|
|
|
|
|
params.name = Containers::String("Builtin Infinite Grid");
|
|
|
|
|
params.path = path;
|
|
|
|
|
params.guid = ResourceGUID::Generate(path);
|
|
|
|
|
params.memorySize = 0;
|
|
|
|
|
shader->Initialize(params);
|
|
|
|
|
|
|
|
|
|
const Containers::String passName("InfiniteGrid");
|
|
|
|
|
shader->SetPassTag(passName, "LightMode", "InfiniteGrid");
|
|
|
|
|
|
|
|
|
|
AddBuiltinShaderStageVariant(
|
|
|
|
|
*shader,
|
|
|
|
|
passName,
|
|
|
|
|
ShaderType::Vertex,
|
|
|
|
|
ShaderLanguage::HLSL,
|
|
|
|
|
ShaderBackend::D3D12,
|
|
|
|
|
kBuiltinInfiniteGridHlsl,
|
|
|
|
|
"MainVS",
|
|
|
|
|
"vs_5_0");
|
|
|
|
|
AddBuiltinShaderStageVariant(
|
|
|
|
|
*shader,
|
|
|
|
|
passName,
|
|
|
|
|
ShaderType::Fragment,
|
|
|
|
|
ShaderLanguage::HLSL,
|
|
|
|
|
ShaderBackend::D3D12,
|
|
|
|
|
kBuiltinInfiniteGridHlsl,
|
|
|
|
|
"MainPS",
|
|
|
|
|
"ps_5_0");
|
|
|
|
|
|
|
|
|
|
shader->m_memorySize = CalculateBuiltinShaderMemorySize(*shader);
|
|
|
|
|
return shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 20:18:39 +08:00
|
|
|
Shader* BuildBuiltinObjectIdOutlineShader(const Containers::String& path) {
|
2026-04-02 23:15:19 +08:00
|
|
|
if (Shader* shader = TryLoadBuiltinShaderFromManifest(path)) {
|
|
|
|
|
return shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 20:18:39 +08:00
|
|
|
auto* shader = new Shader();
|
|
|
|
|
IResource::ConstructParams params;
|
|
|
|
|
params.name = Containers::String("Builtin Object Id Outline");
|
|
|
|
|
params.path = path;
|
|
|
|
|
params.guid = ResourceGUID::Generate(path);
|
|
|
|
|
params.memorySize = 0;
|
|
|
|
|
shader->Initialize(params);
|
|
|
|
|
|
|
|
|
|
const Containers::String passName("ObjectIdOutline");
|
|
|
|
|
shader->SetPassTag(passName, "LightMode", "ObjectIdOutline");
|
|
|
|
|
|
|
|
|
|
AddBuiltinShaderStageVariant(
|
|
|
|
|
*shader,
|
|
|
|
|
passName,
|
|
|
|
|
ShaderType::Vertex,
|
|
|
|
|
ShaderLanguage::HLSL,
|
|
|
|
|
ShaderBackend::D3D12,
|
|
|
|
|
kBuiltinObjectIdOutlineHlsl,
|
|
|
|
|
"MainVS",
|
|
|
|
|
"vs_5_0");
|
|
|
|
|
AddBuiltinShaderStageVariant(
|
|
|
|
|
*shader,
|
|
|
|
|
passName,
|
|
|
|
|
ShaderType::Fragment,
|
|
|
|
|
ShaderLanguage::HLSL,
|
|
|
|
|
ShaderBackend::D3D12,
|
|
|
|
|
kBuiltinObjectIdOutlineHlsl,
|
|
|
|
|
"MainPS",
|
|
|
|
|
"ps_5_0");
|
|
|
|
|
|
|
|
|
|
shader->m_memorySize = CalculateBuiltinShaderMemorySize(*shader);
|
|
|
|
|
return shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 03:03:36 +08:00
|
|
|
Material* BuildDefaultPrimitiveMaterial(const Containers::String& path) {
|
|
|
|
|
auto* material = new Material();
|
|
|
|
|
IResource::ConstructParams params;
|
|
|
|
|
params.name = Containers::String("Default Primitive Material");
|
|
|
|
|
params.path = path;
|
|
|
|
|
params.guid = ResourceGUID::Generate(path);
|
|
|
|
|
params.memorySize = 0;
|
|
|
|
|
material->Initialize(params);
|
|
|
|
|
|
|
|
|
|
MaterialRenderState renderState = {};
|
|
|
|
|
renderState.cullMode = MaterialCullMode::Back;
|
|
|
|
|
material->SetRenderState(renderState);
|
|
|
|
|
material->SetRenderQueue(MaterialRenderQueue::Geometry);
|
2026-04-02 19:00:48 +08:00
|
|
|
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
|
2026-04-02 03:03:36 +08:00
|
|
|
material->SetTexture(
|
|
|
|
|
Containers::String("baseColorTexture"),
|
|
|
|
|
ResourceManager::Get().Load<Texture>(GetBuiltinDefaultPrimitiveTexturePath()));
|
|
|
|
|
material->RecalculateMemorySize();
|
|
|
|
|
return material;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Texture* BuildDefaultPrimitiveTexture(const Containers::String& path) {
|
|
|
|
|
static const unsigned char kTexturePixels[4] = { 214, 214, 214, 255 };
|
|
|
|
|
|
|
|
|
|
auto* texture = new Texture();
|
|
|
|
|
IResource::ConstructParams params;
|
|
|
|
|
params.name = Containers::String("Default Primitive Albedo");
|
|
|
|
|
params.path = path;
|
|
|
|
|
params.guid = ResourceGUID::Generate(path);
|
|
|
|
|
params.memorySize = 0;
|
|
|
|
|
texture->Initialize(params);
|
|
|
|
|
|
|
|
|
|
if (!texture->Create(
|
|
|
|
|
1,
|
|
|
|
|
1,
|
|
|
|
|
1,
|
|
|
|
|
1,
|
|
|
|
|
TextureType::Texture2D,
|
|
|
|
|
TextureFormat::RGBA8_UNORM,
|
|
|
|
|
kTexturePixels,
|
|
|
|
|
sizeof(kTexturePixels))) {
|
|
|
|
|
delete texture;
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return texture;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
bool IsBuiltinResourcePath(const Containers::String& path) {
|
|
|
|
|
return path.StartsWith(kBuiltinPrefix);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsBuiltinMeshPath(const Containers::String& path) {
|
|
|
|
|
return path.StartsWith(kBuiltinMeshPrefix);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsBuiltinMaterialPath(const Containers::String& path) {
|
|
|
|
|
return path.StartsWith(kBuiltinMaterialPrefix);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 19:00:48 +08:00
|
|
|
bool IsBuiltinShaderPath(const Containers::String& path) {
|
|
|
|
|
return path.StartsWith(kBuiltinShaderPrefix);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 03:03:36 +08:00
|
|
|
bool IsBuiltinTexturePath(const Containers::String& path) {
|
|
|
|
|
return path.StartsWith(kBuiltinTexturePrefix);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char* GetBuiltinPrimitiveDisplayName(BuiltinPrimitiveType primitiveType) {
|
|
|
|
|
switch (primitiveType) {
|
|
|
|
|
case BuiltinPrimitiveType::Cube: return "Cube";
|
|
|
|
|
case BuiltinPrimitiveType::Sphere: return "Sphere";
|
|
|
|
|
case BuiltinPrimitiveType::Capsule: return "Capsule";
|
|
|
|
|
case BuiltinPrimitiveType::Cylinder: return "Cylinder";
|
|
|
|
|
case BuiltinPrimitiveType::Plane: return "Plane";
|
|
|
|
|
case BuiltinPrimitiveType::Quad: return "Quad";
|
|
|
|
|
default: return "Primitive";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Containers::String GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType primitiveType) {
|
|
|
|
|
switch (primitiveType) {
|
|
|
|
|
case BuiltinPrimitiveType::Cube: return Containers::String("builtin://meshes/cube");
|
|
|
|
|
case BuiltinPrimitiveType::Sphere: return Containers::String("builtin://meshes/sphere");
|
|
|
|
|
case BuiltinPrimitiveType::Capsule: return Containers::String("builtin://meshes/capsule");
|
|
|
|
|
case BuiltinPrimitiveType::Cylinder: return Containers::String("builtin://meshes/cylinder");
|
|
|
|
|
case BuiltinPrimitiveType::Plane: return Containers::String("builtin://meshes/plane");
|
|
|
|
|
case BuiltinPrimitiveType::Quad: return Containers::String("builtin://meshes/quad");
|
|
|
|
|
default: return Containers::String();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Containers::String GetBuiltinDefaultPrimitiveMaterialPath() {
|
|
|
|
|
return Containers::String(kBuiltinDefaultPrimitiveMaterialPath);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 19:00:48 +08:00
|
|
|
Containers::String GetBuiltinForwardLitShaderPath() {
|
|
|
|
|
return Containers::String(kBuiltinForwardLitShaderPath);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 19:17:22 +08:00
|
|
|
Containers::String GetBuiltinObjectIdShaderPath() {
|
|
|
|
|
return Containers::String(kBuiltinObjectIdShaderPath);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 20:18:39 +08:00
|
|
|
Containers::String GetBuiltinObjectIdOutlineShaderPath() {
|
|
|
|
|
return Containers::String(kBuiltinObjectIdOutlineShaderPath);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 19:32:15 +08:00
|
|
|
Containers::String GetBuiltinInfiniteGridShaderPath() {
|
|
|
|
|
return Containers::String(kBuiltinInfiniteGridShaderPath);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 03:03:36 +08:00
|
|
|
Containers::String GetBuiltinDefaultPrimitiveTexturePath() {
|
|
|
|
|
return Containers::String(kBuiltinDefaultPrimitiveTexturePath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TryParseBuiltinPrimitiveType(const Containers::String& path, BuiltinPrimitiveType& outPrimitiveType) {
|
|
|
|
|
if (path == GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Cube)) {
|
|
|
|
|
outPrimitiveType = BuiltinPrimitiveType::Cube;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (path == GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Sphere)) {
|
|
|
|
|
outPrimitiveType = BuiltinPrimitiveType::Sphere;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (path == GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Capsule)) {
|
|
|
|
|
outPrimitiveType = BuiltinPrimitiveType::Capsule;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (path == GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Cylinder)) {
|
|
|
|
|
outPrimitiveType = BuiltinPrimitiveType::Cylinder;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (path == GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Plane)) {
|
|
|
|
|
outPrimitiveType = BuiltinPrimitiveType::Plane;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (path == GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Quad)) {
|
|
|
|
|
outPrimitiveType = BuiltinPrimitiveType::Quad;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LoadResult CreateBuiltinMeshResource(const Containers::String& path) {
|
|
|
|
|
BuiltinPrimitiveType primitiveType = BuiltinPrimitiveType::Cube;
|
|
|
|
|
if (!TryParseBuiltinPrimitiveType(path, primitiveType)) {
|
|
|
|
|
return LoadResult(Containers::String("Unknown builtin mesh: ") + path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MeshBuffers buffers;
|
|
|
|
|
switch (primitiveType) {
|
|
|
|
|
case BuiltinPrimitiveType::Cube:
|
|
|
|
|
buffers = CreateCubeMeshBuffers();
|
|
|
|
|
break;
|
|
|
|
|
case BuiltinPrimitiveType::Sphere:
|
|
|
|
|
buffers = CreateUvSphereMeshBuffers();
|
|
|
|
|
break;
|
|
|
|
|
case BuiltinPrimitiveType::Capsule:
|
|
|
|
|
buffers = CreateCapsuleMeshBuffers();
|
|
|
|
|
break;
|
|
|
|
|
case BuiltinPrimitiveType::Cylinder:
|
|
|
|
|
buffers = CreateCylinderMeshBuffers();
|
|
|
|
|
break;
|
|
|
|
|
case BuiltinPrimitiveType::Plane:
|
|
|
|
|
buffers = CreatePlaneMeshBuffers();
|
|
|
|
|
break;
|
|
|
|
|
case BuiltinPrimitiveType::Quad:
|
|
|
|
|
buffers = CreateQuadMeshBuffers();
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return LoadResult(Containers::String("Unsupported builtin mesh: ") + path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FlipTriangleWinding(buffers);
|
|
|
|
|
|
|
|
|
|
Mesh* mesh = BuildMeshResource(path, GetBuiltinPrimitiveDisplayName(primitiveType), std::move(buffers));
|
|
|
|
|
if (mesh == nullptr) {
|
|
|
|
|
return LoadResult(Containers::String("Failed to create builtin mesh: ") + path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return LoadResult(mesh);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LoadResult CreateBuiltinMaterialResource(const Containers::String& path) {
|
|
|
|
|
if (path != GetBuiltinDefaultPrimitiveMaterialPath()) {
|
|
|
|
|
return LoadResult(Containers::String("Unknown builtin material: ") + path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Material* material = BuildDefaultPrimitiveMaterial(path);
|
|
|
|
|
if (material == nullptr) {
|
|
|
|
|
return LoadResult(Containers::String("Failed to create builtin material: ") + path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return LoadResult(material);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 19:00:48 +08:00
|
|
|
LoadResult CreateBuiltinShaderResource(const Containers::String& path) {
|
2026-04-02 19:17:22 +08:00
|
|
|
Shader* shader = nullptr;
|
|
|
|
|
if (path == GetBuiltinForwardLitShaderPath()) {
|
|
|
|
|
shader = BuildBuiltinForwardLitShader(path);
|
|
|
|
|
} else if (path == GetBuiltinObjectIdShaderPath()) {
|
|
|
|
|
shader = BuildBuiltinObjectIdShader(path);
|
2026-04-02 20:18:39 +08:00
|
|
|
} else if (path == GetBuiltinObjectIdOutlineShaderPath()) {
|
|
|
|
|
shader = BuildBuiltinObjectIdOutlineShader(path);
|
2026-04-02 19:32:15 +08:00
|
|
|
} else if (path == GetBuiltinInfiniteGridShaderPath()) {
|
|
|
|
|
shader = BuildBuiltinInfiniteGridShader(path);
|
2026-04-02 19:17:22 +08:00
|
|
|
} else {
|
2026-04-02 19:00:48 +08:00
|
|
|
return LoadResult(Containers::String("Unknown builtin shader: ") + path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shader == nullptr) {
|
|
|
|
|
return LoadResult(Containers::String("Failed to create builtin shader: ") + path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return LoadResult(shader);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 03:03:36 +08:00
|
|
|
LoadResult CreateBuiltinTextureResource(const Containers::String& path) {
|
|
|
|
|
if (path != GetBuiltinDefaultPrimitiveTexturePath()) {
|
|
|
|
|
return LoadResult(Containers::String("Unknown builtin texture: ") + path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Texture* texture = BuildDefaultPrimitiveTexture(path);
|
|
|
|
|
if (texture == nullptr) {
|
|
|
|
|
return LoadResult(Containers::String("Failed to create builtin texture: ") + path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return LoadResult(texture);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace Resources
|
|
|
|
|
} // namespace XCEngine
|