Formalize material-driven panoramic skybox path

This commit is contained in:
2026-04-06 00:39:08 +08:00
parent 8151be0f45
commit 66a6818b89
18 changed files with 860 additions and 82 deletions

View File

@@ -1,6 +1,8 @@
// XC_BUILTIN_SKYBOX_OPENGL_PS
#version 430
layout(binding = 0) uniform sampler2D uSkyboxTexture;
layout(std140, binding = 0) uniform EnvironmentConstants {
vec4 gSkyboxTopColor;
vec4 gSkyboxHorizonColor;
@@ -10,10 +12,45 @@ layout(std140, binding = 0) uniform EnvironmentConstants {
vec4 gCameraForwardAndUnused;
};
layout(std140, binding = 1) uniform MaterialConstants {
vec4 gSkyboxTintAndExposure;
vec4 gSkyboxRotationAndMode;
};
in vec2 vNdc;
layout(location = 0) out vec4 fragColor;
const float XC_PI = 3.14159265358979323846;
const float XC_INV_PI = 0.31830988618379067154;
const float XC_INV_TWO_PI = 0.15915494309189533577;
vec3 EvaluateProceduralSkybox(vec3 viewRay) {
float vertical = clamp(viewRay.y, -1.0, 1.0);
vec3 color = gSkyboxHorizonColor.rgb;
if (vertical >= 0.0) {
color = mix(gSkyboxHorizonColor.rgb, gSkyboxTopColor.rgb, pow(clamp(vertical, 0.0, 1.0), 0.65));
} else {
color = mix(gSkyboxHorizonColor.rgb, gSkyboxBottomColor.rgb, pow(clamp(-vertical, 0.0, 1.0), 0.55));
}
return color;
}
vec2 ComputePanoramicUv(vec3 viewRay) {
float rotation = gSkyboxRotationAndMode.x;
float sinTheta = sin(rotation);
float cosTheta = cos(rotation);
vec3 rotatedRay = normalize(vec3(
viewRay.x * cosTheta - viewRay.z * sinTheta,
viewRay.y,
viewRay.x * sinTheta + viewRay.z * cosTheta));
float u = fract(atan(rotatedRay.z, rotatedRay.x) * XC_INV_TWO_PI + 0.5);
float v = acos(clamp(rotatedRay.y, -1.0, 1.0)) * XC_INV_PI;
return vec2(u, clamp(v, 0.0, 1.0));
}
void main() {
float tanHalfFov = gCameraRightAndTanHalfFov.w;
float aspect = gCameraUpAndAspect.w;
@@ -23,12 +60,12 @@ void main() {
vNdc.x * aspect * tanHalfFov * gCameraRightAndTanHalfFov.xyz +
vNdc.y * tanHalfFov * gCameraUpAndAspect.xyz);
float vertical = clamp(viewRay.y, -1.0, 1.0);
vec3 color = gSkyboxHorizonColor.rgb;
if (vertical >= 0.0) {
color = mix(gSkyboxHorizonColor.rgb, gSkyboxTopColor.rgb, pow(clamp(vertical, 0.0, 1.0), 0.65));
} else {
color = mix(gSkyboxHorizonColor.rgb, gSkyboxBottomColor.rgb, pow(clamp(-vertical, 0.0, 1.0), 0.55));
vec3 color = EvaluateProceduralSkybox(viewRay);
if (gSkyboxRotationAndMode.y > 0.5) {
vec2 uv = ComputePanoramicUv(viewRay);
color = texture(uSkyboxTexture, uv).rgb *
gSkyboxTintAndExposure.rgb *
gSkyboxTintAndExposure.w;
}
fragColor = vec4(color, 1.0);

View File

@@ -10,10 +10,48 @@ layout(set = 0, binding = 0, std140) uniform EnvironmentConstants {
vec4 gCameraForwardAndUnused;
};
layout(set = 1, binding = 0, std140) uniform MaterialConstants {
vec4 gSkyboxTintAndExposure;
vec4 gSkyboxRotationAndMode;
};
layout(set = 2, binding = 0) uniform texture2D uSkyboxTexture;
layout(set = 3, binding = 0) uniform sampler uLinearSampler;
layout(location = 0) in vec2 vNdc;
layout(location = 0) out vec4 fragColor;
const float XC_PI = 3.14159265358979323846;
const float XC_INV_PI = 0.31830988618379067154;
const float XC_INV_TWO_PI = 0.15915494309189533577;
vec3 EvaluateProceduralSkybox(vec3 viewRay) {
float vertical = clamp(viewRay.y, -1.0, 1.0);
vec3 color = gSkyboxHorizonColor.rgb;
if (vertical >= 0.0) {
color = mix(gSkyboxHorizonColor.rgb, gSkyboxTopColor.rgb, pow(clamp(vertical, 0.0, 1.0), 0.65));
} else {
color = mix(gSkyboxHorizonColor.rgb, gSkyboxBottomColor.rgb, pow(clamp(-vertical, 0.0, 1.0), 0.55));
}
return color;
}
vec2 ComputePanoramicUv(vec3 viewRay) {
float rotation = gSkyboxRotationAndMode.x;
float sinTheta = sin(rotation);
float cosTheta = cos(rotation);
vec3 rotatedRay = normalize(vec3(
viewRay.x * cosTheta - viewRay.z * sinTheta,
viewRay.y,
viewRay.x * sinTheta + viewRay.z * cosTheta));
float u = fract(atan(rotatedRay.z, rotatedRay.x) * XC_INV_TWO_PI + 0.5);
float v = acos(clamp(rotatedRay.y, -1.0, 1.0)) * XC_INV_PI;
return vec2(u, clamp(v, 0.0, 1.0));
}
void main() {
float tanHalfFov = gCameraRightAndTanHalfFov.w;
float aspect = gCameraUpAndAspect.w;
@@ -23,12 +61,12 @@ void main() {
vNdc.x * aspect * tanHalfFov * gCameraRightAndTanHalfFov.xyz +
vNdc.y * tanHalfFov * gCameraUpAndAspect.xyz);
float vertical = clamp(viewRay.y, -1.0, 1.0);
vec3 color = gSkyboxHorizonColor.rgb;
if (vertical >= 0.0) {
color = mix(gSkyboxHorizonColor.rgb, gSkyboxTopColor.rgb, pow(clamp(vertical, 0.0, 1.0), 0.65));
} else {
color = mix(gSkyboxHorizonColor.rgb, gSkyboxBottomColor.rgb, pow(clamp(-vertical, 0.0, 1.0), 0.55));
vec3 color = EvaluateProceduralSkybox(viewRay);
if (gSkyboxRotationAndMode.y > 0.5) {
vec2 uv = ComputePanoramicUv(viewRay);
color = texture(sampler2D(uSkyboxTexture, uLinearSampler), uv).rgb *
gSkyboxTintAndExposure.rgb *
gSkyboxTintAndExposure.w;
}
fragColor = vec4(color, 1.0);

View File

@@ -1,4 +1,7 @@
// XC_BUILTIN_SKYBOX_D3D12_PS
Texture2D gSkyboxTexture : register(t0);
SamplerState gLinearSampler : register(s0);
cbuffer EnvironmentConstants : register(b0) {
float4 gSkyboxTopColor;
float4 gSkyboxHorizonColor;
@@ -8,11 +11,46 @@ cbuffer EnvironmentConstants : register(b0) {
float4 gCameraForwardAndUnused;
}
cbuffer MaterialConstants : register(b1) {
float4 gSkyboxTintAndExposure;
float4 gSkyboxRotationAndMode;
}
struct PSInput {
float4 position : SV_POSITION;
float2 ndc : TEXCOORD0;
};
static const float XC_PI = 3.14159265358979323846f;
static const float XC_INV_PI = 0.31830988618379067154f;
static const float XC_INV_TWO_PI = 0.15915494309189533577f;
float3 EvaluateProceduralSkybox(float3 viewRay) {
const float vertical = clamp(viewRay.y, -1.0f, 1.0f);
float3 color = gSkyboxHorizonColor.rgb;
if (vertical >= 0.0f) {
color = lerp(gSkyboxHorizonColor.rgb, gSkyboxTopColor.rgb, pow(saturate(vertical), 0.65f));
} else {
color = lerp(gSkyboxHorizonColor.rgb, gSkyboxBottomColor.rgb, pow(saturate(-vertical), 0.55f));
}
return color;
}
float2 ComputePanoramicUv(float3 viewRay) {
const float rotation = gSkyboxRotationAndMode.x;
const float sinTheta = sin(rotation);
const float cosTheta = cos(rotation);
const float3 rotatedRay = normalize(float3(
viewRay.x * cosTheta - viewRay.z * sinTheta,
viewRay.y,
viewRay.x * sinTheta + viewRay.z * cosTheta));
const float u = frac(atan2(rotatedRay.z, rotatedRay.x) * XC_INV_TWO_PI + 0.5f);
const float v = acos(clamp(rotatedRay.y, -1.0f, 1.0f)) * XC_INV_PI;
return float2(u, saturate(v));
}
float4 MainPS(PSInput input) : SV_Target {
const float tanHalfFov = gCameraRightAndTanHalfFov.w;
const float aspect = gCameraUpAndAspect.w;
@@ -22,12 +60,12 @@ float4 MainPS(PSInput input) : SV_Target {
input.ndc.x * aspect * tanHalfFov * gCameraRightAndTanHalfFov.xyz +
input.ndc.y * tanHalfFov * gCameraUpAndAspect.xyz);
const float vertical = clamp(viewRay.y, -1.0f, 1.0f);
float3 color = gSkyboxHorizonColor.rgb;
if (vertical >= 0.0f) {
color = lerp(gSkyboxHorizonColor.rgb, gSkyboxTopColor.rgb, pow(saturate(vertical), 0.65f));
} else {
color = lerp(gSkyboxHorizonColor.rgb, gSkyboxBottomColor.rgb, pow(saturate(-vertical), 0.55f));
float3 color = EvaluateProceduralSkybox(viewRay);
if (gSkyboxRotationAndMode.y > 0.5f) {
const float2 uv = ComputePanoramicUv(viewRay);
color = gSkyboxTexture.Sample(gLinearSampler, uv).rgb *
gSkyboxTintAndExposure.rgb *
gSkyboxTintAndExposure.w;
}
return float4(color, 1.0f);

View File

@@ -1,5 +1,12 @@
Shader "Builtin Skybox"
{
Properties
{
_Tint ("Tint", Color) = (1,1,1,1) [Semantic(Tint)]
_Exposure ("Exposure", Float) = 1.0 [Semantic(Exposure)]
_Rotation ("Rotation", Float) = 0.0 [Semantic(Rotation)]
_MainTex ("Panoramic", 2D) = "white" [Semantic(SkyboxTexture)]
}
SubShader
{
Pass
@@ -9,6 +16,9 @@ Shader "Builtin Skybox"
Resources
{
EnvironmentConstants (ConstantBuffer, 0, 0) [Semantic(Environment)]
MaterialConstants (ConstantBuffer, 1, 0) [Semantic(Material)]
SkyboxTexture (Texture2D, 2, 0) [Semantic(SkyboxTexture)]
LinearClampSampler (Sampler, 3, 0) [Semantic(LinearClampSampler)]
}
HLSLPROGRAM
#pragma vertex MainVS

View File

@@ -1,8 +1,13 @@
#pragma once
#include <XCEngine/Components/Component.h>
#include <XCEngine/Core/Asset/AssetRef.h>
#include <XCEngine/Core/Asset/ResourceHandle.h>
#include <XCEngine/Core/Math/Color.h>
#include <XCEngine/Core/Math/Rect.h>
#include <XCEngine/Resources/Material/Material.h>
#include <string>
namespace XCEngine {
namespace Components {
@@ -67,6 +72,14 @@ public:
bool IsSkyboxEnabled() const { return m_skyboxEnabled; }
void SetSkyboxEnabled(bool value) { m_skyboxEnabled = value; }
const Resources::ResourceHandle<Resources::Material>& GetSkyboxMaterialHandle() const { return m_skyboxMaterial; }
Resources::Material* GetSkyboxMaterial() const { return m_skyboxMaterial.Get(); }
const std::string& GetSkyboxMaterialPath() const { return m_skyboxMaterialPath; }
const Resources::AssetRef& GetSkyboxMaterialAssetRef() const { return m_skyboxMaterialRef; }
void SetSkyboxMaterialPath(const std::string& materialPath);
void SetSkyboxMaterial(const Resources::ResourceHandle<Resources::Material>& material);
void SetSkyboxMaterial(Resources::Material* material);
const Math::Color& GetSkyboxTopColor() const { return m_skyboxTopColor; }
void SetSkyboxTopColor(const Math::Color& value) { m_skyboxTopColor = value; }
@@ -93,6 +106,9 @@ private:
Math::Rect m_viewportRect = Math::Rect(0.0f, 0.0f, 1.0f, 1.0f);
Math::Color m_clearColor = Math::Color(0.192f, 0.302f, 0.475f, 1.0f);
bool m_skyboxEnabled = false;
Resources::ResourceHandle<Resources::Material> m_skyboxMaterial;
std::string m_skyboxMaterialPath;
Resources::AssetRef m_skyboxMaterialRef;
Math::Color m_skyboxTopColor = Math::Color(0.18f, 0.36f, 0.74f, 1.0f);
Math::Color m_skyboxHorizonColor = Math::Color(0.78f, 0.84f, 0.92f, 1.0f);
Math::Color m_skyboxBottomColor = Math::Color(0.92f, 0.93f, 0.95f, 1.0f);

View File

@@ -4,12 +4,19 @@
#include <cstdint>
namespace XCEngine {
namespace Resources {
class Material;
} // namespace Resources
} // namespace XCEngine
namespace XCEngine {
namespace Rendering {
enum class RenderEnvironmentMode : uint32_t {
None = 0,
ProceduralSkybox
ProceduralSkybox,
MaterialSkybox
};
struct ProceduralSkyboxData {
@@ -18,13 +25,30 @@ struct ProceduralSkyboxData {
Math::Color bottomColor = Math::Color(0.92f, 0.93f, 0.95f, 1.0f);
};
struct MaterialSkyboxData {
const Resources::Material* material = nullptr;
bool IsValid() const {
return material != nullptr;
}
};
struct RenderEnvironmentData {
RenderEnvironmentMode mode = RenderEnvironmentMode::None;
ProceduralSkyboxData skybox = {};
MaterialSkyboxData materialSkybox = {};
bool HasProceduralSkybox() const {
return mode == RenderEnvironmentMode::ProceduralSkybox;
}
bool HasMaterialSkybox() const {
return mode == RenderEnvironmentMode::MaterialSkybox && materialSkybox.IsValid();
}
bool HasSkybox() const {
return HasProceduralSkybox() || HasMaterialSkybox();
}
};
} // namespace Rendering

View File

@@ -16,6 +16,12 @@ struct BuiltinForwardMaterialData {
Math::Vector4 baseColorFactor = Math::Vector4::One();
};
struct BuiltinSkyboxMaterialData {
Math::Vector4 tint = Math::Vector4::One();
float exposure = 1.0f;
float rotationDegrees = 0.0f;
};
struct MaterialConstantLayoutView {
const Resources::MaterialConstantFieldDesc* fields = nullptr;
size_t count = 0;
@@ -134,6 +140,129 @@ inline BuiltinForwardMaterialData BuildBuiltinForwardMaterialData(const Resource
return data;
}
inline const Resources::Texture* ResolveSkyboxTexture(const Resources::Material* material) {
if (material == nullptr) {
return nullptr;
}
if (const Resources::ShaderPropertyDesc* property = FindShaderPropertyBySemantic(material, "SkyboxTexture")) {
const Resources::ResourceHandle<Resources::Texture> textureHandle = material->GetTexture(property->name);
if (textureHandle.Get() != nullptr && textureHandle->IsValid()) {
return textureHandle.Get();
}
}
static const char* kSkyboxTexturePropertyNames[] = {
"_MainTex",
"_PanoramicTex",
"_SkyboxTexture",
"panoramicTexture",
"skyboxTexture",
"texture"
};
for (const char* propertyName : kSkyboxTexturePropertyNames) {
const Resources::ResourceHandle<Resources::Texture> textureHandle =
material->GetTexture(Containers::String(propertyName));
if (textureHandle.Get() != nullptr && textureHandle->IsValid()) {
return textureHandle.Get();
}
}
return nullptr;
}
inline Math::Vector4 ResolveSkyboxTint(const Resources::Material* material) {
if (material == nullptr) {
return Math::Vector4::One();
}
if (const Resources::ShaderPropertyDesc* property = FindShaderPropertyBySemantic(material, "Tint")) {
if (material->HasProperty(property->name) &&
(property->type == Resources::ShaderPropertyType::Color ||
property->type == Resources::ShaderPropertyType::Vector)) {
return material->GetFloat4(property->name);
}
}
static const char* kTintPropertyNames[] = {
"_Tint",
"tint",
"_Color",
"color"
};
for (const char* propertyName : kTintPropertyNames) {
if (material->HasProperty(Containers::String(propertyName))) {
return material->GetFloat4(Containers::String(propertyName));
}
}
return Math::Vector4::One();
}
inline float ResolveSkyboxExposure(const Resources::Material* material) {
if (material == nullptr) {
return 1.0f;
}
if (const Resources::ShaderPropertyDesc* property = FindShaderPropertyBySemantic(material, "Exposure")) {
if (material->HasProperty(property->name) &&
(property->type == Resources::ShaderPropertyType::Float ||
property->type == Resources::ShaderPropertyType::Range)) {
return material->GetFloat(property->name);
}
}
static const char* kExposurePropertyNames[] = {
"_Exposure",
"exposure"
};
for (const char* propertyName : kExposurePropertyNames) {
if (material->HasProperty(Containers::String(propertyName))) {
return material->GetFloat(Containers::String(propertyName));
}
}
return 1.0f;
}
inline float ResolveSkyboxRotationDegrees(const Resources::Material* material) {
if (material == nullptr) {
return 0.0f;
}
if (const Resources::ShaderPropertyDesc* property = FindShaderPropertyBySemantic(material, "Rotation")) {
if (material->HasProperty(property->name) &&
(property->type == Resources::ShaderPropertyType::Float ||
property->type == Resources::ShaderPropertyType::Range)) {
return material->GetFloat(property->name);
}
}
static const char* kRotationPropertyNames[] = {
"_Rotation",
"rotation"
};
for (const char* propertyName : kRotationPropertyNames) {
if (material->HasProperty(Containers::String(propertyName))) {
return material->GetFloat(Containers::String(propertyName));
}
}
return 0.0f;
}
inline BuiltinSkyboxMaterialData BuildBuiltinSkyboxMaterialData(const Resources::Material* material) {
BuiltinSkyboxMaterialData data = {};
data.tint = ResolveSkyboxTint(material);
data.exposure = ResolveSkyboxExposure(material);
data.rotationDegrees = ResolveSkyboxRotationDegrees(material);
return data;
}
inline MaterialConstantPayloadView ResolveSchemaMaterialConstantPayload(const Resources::Material* material) {
if (material == nullptr || material->GetShader() == nullptr) {
return {};

View File

@@ -109,6 +109,11 @@ private:
Math::Vector4 cameraForwardAndUnused = Math::Vector4::Zero();
};
struct SkyboxMaterialConstants {
Math::Vector4 tintAndExposure = Math::Vector4(1.0f, 1.0f, 1.0f, 1.0f);
Math::Vector4 rotationRadiansAndMode = Math::Vector4::Zero();
};
struct PassLayoutKey {
const Resources::Shader* shader = nullptr;
Containers::String passName;
@@ -243,9 +248,11 @@ private:
void DestroyPassResourceLayout(PassResourceLayout& passLayout);
const Resources::Texture* ResolveTexture(const Resources::Material* material) const;
RHI::RHIResourceView* ResolveTextureView(const Resources::Texture* texture);
RHI::RHIResourceView* ResolveTextureView(const VisibleRenderItem& visibleItem);
static LightingConstants BuildLightingConstants(const RenderLightingData& lightingData);
static AdditionalLightConstants BuildAdditionalLightConstants(const RenderAdditionalLightData& lightData);
bool HasSkybox(const RenderSceneData& sceneData) const;
bool HasProceduralSkybox(const RenderSceneData& sceneData) const;
bool BeginForwardScenePass(const RenderPassContext& context);
void EndForwardScenePass(const RenderPassContext& context);
@@ -282,8 +289,11 @@ private:
RHI::RHIResourceView* m_fallbackTextureView = nullptr;
RHI::RHIPipelineLayout* m_skyboxPipelineLayout = nullptr;
RHI::RHIPipelineState* m_skyboxPipelineState = nullptr;
RHI::RHIDescriptorPool* m_skyboxConstantPool = nullptr;
RHI::RHIDescriptorSet* m_skyboxConstantSet = nullptr;
OwnedDescriptorSet m_skyboxEnvironmentSet = {};
OwnedDescriptorSet m_skyboxMaterialSet = {};
OwnedDescriptorSet m_skyboxTextureSet = {};
OwnedDescriptorSet m_skyboxSamplerSet = {};
RHI::RHIResourceView* m_skyboxBoundTextureView = nullptr;
RenderPassSequence m_passSequence;
};

View File

@@ -1,11 +1,49 @@
#include "Components/CameraComponent.h"
#include "Core/Asset/ResourceManager.h"
#include "Resources/Material/Material.h"
#include <algorithm>
#include <sstream>
namespace XCEngine {
namespace Components {
namespace {
std::string ToStdString(const Containers::String& value) {
return std::string(value.CStr());
}
bool HasVirtualPathScheme(const std::string& path) {
return path.find("://") != std::string::npos;
}
std::string EncodeAssetRef(const Resources::AssetRef& assetRef) {
if (!assetRef.IsValid()) {
return std::string();
}
return ToStdString(assetRef.assetGuid.ToString()) + "," +
std::to_string(assetRef.localID) + "," +
std::to_string(static_cast<int>(assetRef.resourceType));
}
bool TryDecodeAssetRef(const std::string& value, Resources::AssetRef& outRef) {
const size_t firstComma = value.find(',');
const size_t secondComma = firstComma == std::string::npos ? std::string::npos : value.find(',', firstComma + 1);
if (firstComma == std::string::npos || secondComma == std::string::npos) {
return false;
}
outRef.assetGuid = Resources::AssetGUID::ParseOrDefault(Containers::String(value.substr(0, firstComma).c_str()));
outRef.localID = static_cast<Resources::LocalID>(std::stoull(value.substr(firstComma + 1, secondComma - firstComma - 1)));
outRef.resourceType = static_cast<Resources::ResourceType>(std::stoi(value.substr(secondComma + 1)));
return outRef.IsValid();
}
} // namespace
void CameraComponent::SetFieldOfView(float value) {
m_fieldOfView = std::clamp(value, 1.0f, 179.0f);
}
@@ -35,7 +73,64 @@ void CameraComponent::SetViewportRect(const Math::Rect& value) {
m_viewportRect = Math::Rect(x, y, right - x, bottom - y);
}
void CameraComponent::SetSkyboxMaterialPath(const std::string& materialPath) {
m_skyboxMaterialPath = materialPath;
m_skyboxMaterialRef.Reset();
if (materialPath.empty()) {
m_skyboxMaterial.Reset();
return;
}
m_skyboxMaterial = Resources::ResourceManager::Get().Load<Resources::Material>(materialPath.c_str());
if (!Resources::ResourceManager::Get().TryGetAssetRef(
materialPath.c_str(),
Resources::ResourceType::Material,
m_skyboxMaterialRef)) {
m_skyboxMaterialRef.Reset();
}
}
void CameraComponent::SetSkyboxMaterial(const Resources::ResourceHandle<Resources::Material>& material) {
m_skyboxMaterial = material;
m_skyboxMaterialPath =
material.Get() != nullptr
? ToStdString(material->GetPath())
: std::string();
if (m_skyboxMaterialPath.empty() ||
!Resources::ResourceManager::Get().TryGetAssetRef(
m_skyboxMaterialPath.c_str(),
Resources::ResourceType::Material,
m_skyboxMaterialRef)) {
m_skyboxMaterialRef.Reset();
}
}
void CameraComponent::SetSkyboxMaterial(Resources::Material* material) {
SetSkyboxMaterial(Resources::ResourceHandle<Resources::Material>(material));
}
void CameraComponent::Serialize(std::ostream& os) const {
Resources::AssetRef serializedSkyboxMaterialRef = m_skyboxMaterialRef;
std::string serializedSkyboxMaterialPath = m_skyboxMaterialPath;
if (serializedSkyboxMaterialPath.empty() && m_skyboxMaterial.Get() != nullptr) {
serializedSkyboxMaterialPath = ToStdString(m_skyboxMaterial->GetPath());
}
if (!serializedSkyboxMaterialRef.IsValid() &&
!serializedSkyboxMaterialPath.empty() &&
!HasVirtualPathScheme(serializedSkyboxMaterialPath) &&
Resources::ResourceManager::Get().TryGetAssetRef(
serializedSkyboxMaterialPath.c_str(),
Resources::ResourceType::Material,
serializedSkyboxMaterialRef)) {
}
if (serializedSkyboxMaterialRef.IsValid() || !HasVirtualPathScheme(serializedSkyboxMaterialPath)) {
serializedSkyboxMaterialPath.clear();
}
os << "projection=" << static_cast<int>(m_projectionType) << ";";
os << "fov=" << m_fieldOfView << ";";
os << "orthoSize=" << m_orthographicSize << ";";
@@ -49,13 +144,21 @@ void CameraComponent::Serialize(std::ostream& os) const {
os << "viewportRect=" << m_viewportRect.x << "," << m_viewportRect.y << "," << m_viewportRect.width << "," << m_viewportRect.height << ";";
os << "clearColor=" << m_clearColor.r << "," << m_clearColor.g << "," << m_clearColor.b << "," << m_clearColor.a << ";";
os << "skyboxEnabled=" << (m_skyboxEnabled ? 1 : 0) << ";";
os << "skyboxMaterialPath=" << serializedSkyboxMaterialPath << ";";
os << "skyboxMaterialRef=" << EncodeAssetRef(serializedSkyboxMaterialRef) << ";";
os << "skyboxTopColor=" << m_skyboxTopColor.r << "," << m_skyboxTopColor.g << "," << m_skyboxTopColor.b << "," << m_skyboxTopColor.a << ";";
os << "skyboxHorizonColor=" << m_skyboxHorizonColor.r << "," << m_skyboxHorizonColor.g << "," << m_skyboxHorizonColor.b << "," << m_skyboxHorizonColor.a << ";";
os << "skyboxBottomColor=" << m_skyboxBottomColor.r << "," << m_skyboxBottomColor.g << "," << m_skyboxBottomColor.b << "," << m_skyboxBottomColor.a << ";";
}
void CameraComponent::Deserialize(std::istream& is) {
m_skyboxMaterial.Reset();
m_skyboxMaterialPath.clear();
m_skyboxMaterialRef.Reset();
std::string token;
std::string pendingSkyboxMaterialPath;
Resources::AssetRef pendingSkyboxMaterialRef;
while (std::getline(is, token, ';')) {
if (token.empty()) {
continue;
@@ -101,6 +204,10 @@ void CameraComponent::Deserialize(std::istream& is) {
ss >> m_clearColor.r >> m_clearColor.g >> m_clearColor.b >> m_clearColor.a;
} else if (key == "skyboxEnabled") {
m_skyboxEnabled = (std::stoi(value) != 0);
} else if (key == "skyboxMaterialPath") {
pendingSkyboxMaterialPath = value;
} else if (key == "skyboxMaterialRef") {
TryDecodeAssetRef(value, pendingSkyboxMaterialRef);
} else if (key == "skyboxTopColor") {
std::replace(value.begin(), value.end(), ',', ' ');
std::istringstream ss(value);
@@ -115,6 +222,28 @@ void CameraComponent::Deserialize(std::istream& is) {
ss >> m_skyboxBottomColor.r >> m_skyboxBottomColor.g >> m_skyboxBottomColor.b >> m_skyboxBottomColor.a;
}
}
if (pendingSkyboxMaterialRef.IsValid()) {
m_skyboxMaterialRef = pendingSkyboxMaterialRef;
m_skyboxMaterial = Resources::ResourceManager::Get().Load<Resources::Material>(pendingSkyboxMaterialRef);
if (m_skyboxMaterial.Get() != nullptr) {
m_skyboxMaterialPath = ToStdString(m_skyboxMaterial->GetPath());
} else {
Containers::String resolvedPath;
if (Resources::ResourceManager::Get().TryResolveAssetPath(pendingSkyboxMaterialRef, resolvedPath)) {
SetSkyboxMaterialPath(ToStdString(resolvedPath));
m_skyboxMaterialRef = pendingSkyboxMaterialRef;
}
}
}
if (m_skyboxMaterial.Get() == nullptr && !pendingSkyboxMaterialPath.empty()) {
SetSkyboxMaterialPath(pendingSkyboxMaterialPath);
}
if (m_skyboxMaterial.Get() == nullptr && pendingSkyboxMaterialRef.IsValid()) {
m_skyboxMaterialRef = pendingSkyboxMaterialRef;
}
}
} // namespace Components

View File

@@ -2,6 +2,8 @@
#include <XCEngine/RHI/RHIEnums.h>
#include <algorithm>
namespace XCEngine {
namespace Rendering {
@@ -9,10 +11,22 @@ namespace {
RHI::Format ToRHITextureFormat(Resources::TextureFormat format) {
switch (format) {
case Resources::TextureFormat::R8_UNORM:
return RHI::Format::R8_UNorm;
case Resources::TextureFormat::RG8_UNORM:
return RHI::Format::R8G8_UNorm;
case Resources::TextureFormat::RGBA8_UNORM:
return RHI::Format::R8G8B8A8_UNorm;
case Resources::TextureFormat::RGBA8_SRGB:
return RHI::Format::R8G8B8A8_SRGB;
case Resources::TextureFormat::R16_FLOAT:
return RHI::Format::R16_Float;
case Resources::TextureFormat::R32_FLOAT:
return RHI::Format::R32_Float;
case Resources::TextureFormat::RGBA16_FLOAT:
return RHI::Format::R16G16B16A16_Float;
case Resources::TextureFormat::RGBA32_FLOAT:
return RHI::Format::R32G32B32A32_Float;
default:
return RHI::Format::Unknown;
}
@@ -34,6 +48,43 @@ RHI::TextureType ToRHITextureType(Resources::TextureType textureType) {
}
}
RHI::ResourceViewDimension ToRHITextureViewDimension(Resources::TextureType textureType) {
switch (textureType) {
case Resources::TextureType::Texture2DArray:
return RHI::ResourceViewDimension::Texture2DArray;
case Resources::TextureType::Texture3D:
return RHI::ResourceViewDimension::Texture3D;
case Resources::TextureType::TextureCube:
return RHI::ResourceViewDimension::TextureCube;
case Resources::TextureType::TextureCubeArray:
return RHI::ResourceViewDimension::TextureCubeArray;
case Resources::TextureType::Texture2D:
default:
return RHI::ResourceViewDimension::Texture2D;
}
}
uint32_t GetTextureBytesPerPixel(Resources::TextureFormat format) {
switch (format) {
case Resources::TextureFormat::R8_UNORM:
return 1;
case Resources::TextureFormat::RG8_UNORM:
return 2;
case Resources::TextureFormat::RGBA8_UNORM:
case Resources::TextureFormat::RGBA8_SRGB:
case Resources::TextureFormat::R32_FLOAT:
return 4;
case Resources::TextureFormat::R16_FLOAT:
return 2;
case Resources::TextureFormat::RGBA16_FLOAT:
return 8;
case Resources::TextureFormat::RGBA32_FLOAT:
return 16;
default:
return 0;
}
}
void ShutdownMesh(RenderResourceCache::CachedMesh& cachedMesh) {
if (cachedMesh.vertexBufferView != nullptr) {
cachedMesh.vertexBufferView->Shutdown();
@@ -216,14 +267,19 @@ bool RenderResourceCache::UploadTexture(
textureDesc.height = texture->GetHeight();
textureDesc.depth = texture->GetDepth();
textureDesc.mipLevels = texture->GetMipLevels();
textureDesc.arraySize = 1;
textureDesc.arraySize = std::max<uint32_t>(texture->GetArraySize(), 1u);
textureDesc.format = static_cast<uint32_t>(format);
textureDesc.textureType = static_cast<uint32_t>(ToRHITextureType(texture->GetTextureType()));
textureDesc.sampleCount = 1;
textureDesc.sampleQuality = 0;
textureDesc.flags = 0;
const uint32_t rowPitch = texture->GetWidth() * 4;
const uint32_t bytesPerPixel = GetTextureBytesPerPixel(texture->GetFormat());
if (bytesPerPixel == 0) {
return false;
}
const uint32_t rowPitch = texture->GetWidth() * bytesPerPixel;
cachedTexture.texture = device->CreateTexture(
textureDesc,
texture->GetPixelData(),
@@ -235,7 +291,7 @@ bool RenderResourceCache::UploadTexture(
RHI::ResourceViewDesc textureViewDesc = {};
textureViewDesc.format = static_cast<uint32_t>(format);
textureViewDesc.dimension = RHI::ResourceViewDimension::Texture2D;
textureViewDesc.dimension = ToRHITextureViewDimension(texture->GetTextureType());
textureViewDesc.mipLevel = 0;
cachedTexture.shaderResourceView = device->CreateShaderResourceView(cachedTexture.texture, textureViewDesc);
if (cachedTexture.shaderResourceView == nullptr) {

View File

@@ -252,6 +252,12 @@ RenderEnvironmentData BuildEnvironmentData(const CameraRenderRequest& request) {
return environment;
}
if (const Resources::Material* skyboxMaterial = request.camera->GetSkyboxMaterial()) {
environment.mode = RenderEnvironmentMode::MaterialSkybox;
environment.materialSkybox.material = skyboxMaterial;
return environment;
}
environment.mode = RenderEnvironmentMode::ProceduralSkybox;
environment.skybox.topColor = request.camera->GetSkyboxTopColor();
environment.skybox.horizonColor = request.camera->GetSkyboxHorizonColor();

View File

@@ -8,6 +8,7 @@
#include "Rendering/RenderSurface.h"
#include "Resources/BuiltinResources.h"
#include <algorithm>
#include <cstddef>
#include <cmath>
@@ -234,6 +235,11 @@ bool BuiltinForwardPipeline::HasProceduralSkybox(const RenderSceneData& sceneDat
sceneData.cameraData.perspectiveProjection;
}
bool BuiltinForwardPipeline::HasSkybox(const RenderSceneData& sceneData) const {
return sceneData.environment.HasSkybox() &&
sceneData.cameraData.perspectiveProjection;
}
bool BuiltinForwardPipeline::BeginForwardScenePass(const RenderPassContext& passContext) {
const RenderContext& context = passContext.renderContext;
const RenderSurface& surface = passContext.surface;
@@ -290,7 +296,7 @@ bool BuiltinForwardPipeline::BeginForwardScenePass(const RenderPassContext& pass
: sceneData.cameraData.clearColor;
const float clearValues[4] = { clearColor.r, clearColor.g, clearColor.b, clearColor.a };
if (HasRenderClearFlag(sceneData.cameraData.clearFlags, RenderClearFlags::Color) &&
!HasProceduralSkybox(sceneData)) {
!HasSkybox(sceneData)) {
for (RHI::RHIResourceView* renderTarget : renderTargets) {
if (renderTarget != nullptr) {
commandList->ClearRenderTarget(renderTarget, clearValues, 1, clearRects);
@@ -351,7 +357,7 @@ bool BuiltinForwardPipeline::ExecuteForwardSkyboxPass(const RenderPassContext& p
const RenderSurface& surface = passContext.surface;
const RenderSceneData& sceneData = passContext.sceneData;
if (!HasProceduralSkybox(sceneData)) {
if (!HasSkybox(sceneData)) {
return true;
}
if (surface.GetDepthAttachment() == nullptr) {
@@ -361,7 +367,10 @@ bool BuiltinForwardPipeline::ExecuteForwardSkyboxPass(const RenderPassContext& p
if (!EnsureSkyboxResources(context) ||
m_skyboxPipelineLayout == nullptr ||
m_skyboxPipelineState == nullptr ||
m_skyboxConstantSet == nullptr) {
m_skyboxEnvironmentSet.set == nullptr ||
m_skyboxMaterialSet.set == nullptr ||
m_skyboxTextureSet.set == nullptr ||
m_skyboxSamplerSet.set == nullptr) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
"BuiltinForwardPipeline skybox pass failed to initialize runtime skybox resources");
@@ -380,13 +389,46 @@ bool BuiltinForwardPipeline::ExecuteForwardSkyboxPass(const RenderPassContext& p
sceneData.cameraData.aspectRatio);
constants.cameraForwardAndUnused = Math::Vector4(sceneData.cameraData.worldForward, 0.0f);
m_skyboxConstantSet->WriteConstant(0, &constants, sizeof(constants));
const Resources::Material* skyboxMaterial = sceneData.environment.HasMaterialSkybox()
? sceneData.environment.materialSkybox.material
: nullptr;
const Resources::Texture* skyboxTexture = ResolveSkyboxTexture(skyboxMaterial);
RHI::RHIResourceView* skyboxTextureView = ResolveTextureView(skyboxTexture);
const bool useTexturedSkybox = sceneData.environment.HasMaterialSkybox() && skyboxTextureView != nullptr;
if (skyboxTextureView == nullptr) {
skyboxTextureView = m_fallbackTextureView;
}
const BuiltinSkyboxMaterialData skyboxMaterialData = BuildBuiltinSkyboxMaterialData(skyboxMaterial);
SkyboxMaterialConstants materialConstants = {};
materialConstants.tintAndExposure = Math::Vector4(
skyboxMaterialData.tint.x,
skyboxMaterialData.tint.y,
skyboxMaterialData.tint.z,
std::max(0.0f, skyboxMaterialData.exposure));
materialConstants.rotationRadiansAndMode = Math::Vector4(
Math::Radians(skyboxMaterialData.rotationDegrees),
useTexturedSkybox ? 1.0f : 0.0f,
0.0f,
0.0f);
m_skyboxEnvironmentSet.set->WriteConstant(0, &constants, sizeof(constants));
m_skyboxMaterialSet.set->WriteConstant(0, &materialConstants, sizeof(materialConstants));
if (m_skyboxBoundTextureView != skyboxTextureView) {
m_skyboxTextureSet.set->Update(0, skyboxTextureView);
m_skyboxBoundTextureView = skyboxTextureView;
}
RHI::RHICommandList* commandList = context.commandList;
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
commandList->SetPipelineState(m_skyboxPipelineState);
RHI::RHIDescriptorSet* descriptorSets[] = { m_skyboxConstantSet };
commandList->SetGraphicsDescriptorSets(0, 1, descriptorSets, m_skyboxPipelineLayout);
RHI::RHIDescriptorSet* descriptorSets[] = {
m_skyboxEnvironmentSet.set,
m_skyboxMaterialSet.set,
m_skyboxTextureSet.set,
m_skyboxSamplerSet.set
};
commandList->SetGraphicsDescriptorSets(0, 4, descriptorSets, m_skyboxPipelineLayout);
commandList->Draw(3, 1, 0, 0);
return true;
}
@@ -543,8 +585,10 @@ bool BuiltinForwardPipeline::CreatePipelineResources(const RenderContext& contex
bool BuiltinForwardPipeline::EnsureSkyboxResources(const RenderContext& context) {
if (m_skyboxPipelineLayout != nullptr &&
m_skyboxPipelineState != nullptr &&
m_skyboxConstantPool != nullptr &&
m_skyboxConstantSet != nullptr) {
m_skyboxEnvironmentSet.set != nullptr &&
m_skyboxMaterialSet.set != nullptr &&
m_skyboxTextureSet.set != nullptr &&
m_skyboxSamplerSet.set != nullptr) {
return true;
}
@@ -570,19 +614,52 @@ bool BuiltinForwardPipeline::CreateSkyboxResources(const RenderContext& context)
return false;
}
RHI::DescriptorSetLayoutBinding constantBinding = {};
constantBinding.binding = 0;
constantBinding.type = static_cast<uint32_t>(RHI::DescriptorType::CBV);
constantBinding.count = 1;
constantBinding.visibility = static_cast<uint32_t>(RHI::ShaderVisibility::All);
RHI::DescriptorSetLayoutBinding environmentBinding = {};
environmentBinding.binding = 0;
environmentBinding.type = static_cast<uint32_t>(RHI::DescriptorType::CBV);
environmentBinding.count = 1;
environmentBinding.visibility = static_cast<uint32_t>(RHI::ShaderVisibility::All);
RHI::DescriptorSetLayoutDesc constantLayout = {};
constantLayout.bindings = &constantBinding;
constantLayout.bindingCount = 1;
RHI::DescriptorSetLayoutBinding materialBinding = environmentBinding;
RHI::DescriptorSetLayoutBinding textureBinding = {};
textureBinding.binding = 0;
textureBinding.type = static_cast<uint32_t>(RHI::DescriptorType::SRV);
textureBinding.count = 1;
textureBinding.visibility = static_cast<uint32_t>(RHI::ShaderVisibility::All);
RHI::DescriptorSetLayoutBinding samplerBinding = {};
samplerBinding.binding = 0;
samplerBinding.type = static_cast<uint32_t>(RHI::DescriptorType::Sampler);
samplerBinding.count = 1;
samplerBinding.visibility = static_cast<uint32_t>(RHI::ShaderVisibility::All);
RHI::DescriptorSetLayoutDesc environmentLayout = {};
environmentLayout.bindings = &environmentBinding;
environmentLayout.bindingCount = 1;
RHI::DescriptorSetLayoutDesc materialLayout = {};
materialLayout.bindings = &materialBinding;
materialLayout.bindingCount = 1;
RHI::DescriptorSetLayoutDesc textureLayout = {};
textureLayout.bindings = &textureBinding;
textureLayout.bindingCount = 1;
RHI::DescriptorSetLayoutDesc samplerLayout = {};
samplerLayout.bindings = &samplerBinding;
samplerLayout.bindingCount = 1;
RHI::DescriptorSetLayoutDesc setLayouts[] = {
environmentLayout,
materialLayout,
textureLayout,
samplerLayout
};
RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {};
pipelineLayoutDesc.setLayouts = &constantLayout;
pipelineLayoutDesc.setLayoutCount = 1;
pipelineLayoutDesc.setLayouts = setLayouts;
pipelineLayoutDesc.setLayoutCount = 4;
m_skyboxPipelineLayout = m_device->CreatePipelineLayout(pipelineLayoutDesc);
if (m_skyboxPipelineLayout == nullptr) {
Debug::Logger::Get().Error(
@@ -592,27 +669,54 @@ bool BuiltinForwardPipeline::CreateSkyboxResources(const RenderContext& context)
return false;
}
RHI::DescriptorPoolDesc constantPoolDesc = {};
constantPoolDesc.type = RHI::DescriptorHeapType::CBV_SRV_UAV;
constantPoolDesc.descriptorCount = 1;
constantPoolDesc.shaderVisible = false;
m_skyboxConstantPool = m_device->CreateDescriptorPool(constantPoolDesc);
if (m_skyboxConstantPool == nullptr) {
auto createSkyboxDescriptorSet =
[this](const RHI::DescriptorSetLayoutDesc& layout,
RHI::DescriptorHeapType heapType,
bool shaderVisible,
OwnedDescriptorSet& ownedSet) -> bool {
RHI::DescriptorPoolDesc poolDesc = {};
poolDesc.type = heapType;
poolDesc.descriptorCount = 1;
poolDesc.shaderVisible = shaderVisible;
ownedSet.pool = m_device->CreateDescriptorPool(poolDesc);
if (ownedSet.pool == nullptr) {
return false;
}
ownedSet.set = ownedSet.pool->AllocateSet(layout);
return ownedSet.set != nullptr;
};
if (!createSkyboxDescriptorSet(
environmentLayout,
RHI::DescriptorHeapType::CBV_SRV_UAV,
false,
m_skyboxEnvironmentSet) ||
!createSkyboxDescriptorSet(
materialLayout,
RHI::DescriptorHeapType::CBV_SRV_UAV,
false,
m_skyboxMaterialSet) ||
!createSkyboxDescriptorSet(
textureLayout,
RHI::DescriptorHeapType::CBV_SRV_UAV,
true,
m_skyboxTextureSet) ||
!createSkyboxDescriptorSet(
samplerLayout,
RHI::DescriptorHeapType::Sampler,
true,
m_skyboxSamplerSet)) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
"BuiltinForwardPipeline failed to create skybox descriptor pool");
"BuiltinForwardPipeline failed to allocate skybox descriptor sets");
DestroySkyboxResources();
return false;
}
m_skyboxConstantSet = m_skyboxConstantPool->AllocateSet(constantLayout);
if (m_skyboxConstantSet == nullptr) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
"BuiltinForwardPipeline failed to allocate skybox descriptor set");
DestroySkyboxResources();
return false;
}
m_skyboxSamplerSet.set->UpdateSampler(0, m_sampler);
m_skyboxTextureSet.set->Update(0, m_fallbackTextureView);
m_skyboxBoundTextureView = m_fallbackTextureView;
m_skyboxPipelineState = m_device->CreatePipelineState(
CreateSkyboxPipelineDesc(
@@ -638,17 +742,11 @@ void BuiltinForwardPipeline::DestroySkyboxResources() {
m_skyboxPipelineState = nullptr;
}
if (m_skyboxConstantSet != nullptr) {
m_skyboxConstantSet->Shutdown();
delete m_skyboxConstantSet;
m_skyboxConstantSet = nullptr;
}
if (m_skyboxConstantPool != nullptr) {
m_skyboxConstantPool->Shutdown();
delete m_skyboxConstantPool;
m_skyboxConstantPool = nullptr;
}
DestroyOwnedDescriptorSet(m_skyboxSamplerSet);
DestroyOwnedDescriptorSet(m_skyboxTextureSet);
DestroyOwnedDescriptorSet(m_skyboxMaterialSet);
DestroyOwnedDescriptorSet(m_skyboxEnvironmentSet);
m_skyboxBoundTextureView = nullptr;
if (m_skyboxPipelineLayout != nullptr) {
m_skyboxPipelineLayout->Shutdown();

View File

@@ -497,18 +497,26 @@ const Resources::Texture* BuiltinForwardPipeline::ResolveTexture(const Resources
return ResolveBuiltinBaseColorTexture(material);
}
RHI::RHIResourceView* BuiltinForwardPipeline::ResolveTextureView(
const Resources::Texture* texture) {
if (texture == nullptr) {
return nullptr;
}
const RenderResourceCache::CachedTexture* cachedTexture = m_resourceCache.GetOrCreateTexture(m_device, texture);
if (cachedTexture != nullptr && cachedTexture->shaderResourceView != nullptr) {
return cachedTexture->shaderResourceView;
}
return nullptr;
}
RHI::RHIResourceView* BuiltinForwardPipeline::ResolveTextureView(
const VisibleRenderItem& visibleItem) {
const Resources::Material* material = ResolveMaterial(visibleItem);
const Resources::Texture* texture = ResolveTexture(material);
if (texture != nullptr) {
const RenderResourceCache::CachedTexture* cachedTexture = m_resourceCache.GetOrCreateTexture(m_device, texture);
if (cachedTexture != nullptr && cachedTexture->shaderResourceView != nullptr) {
return cachedTexture->shaderResourceView;
}
}
return m_fallbackTextureView;
RHI::RHIResourceView* textureView = ResolveTextureView(texture);
return textureView != nullptr ? textureView : m_fallbackTextureView;
}
BuiltinForwardPipeline::LightingConstants BuiltinForwardPipeline::BuildLightingConstants(

View File

@@ -2,6 +2,7 @@
#include <XCEngine/Components/CameraComponent.h>
#include <XCEngine/Components/LightComponent.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <sstream>
@@ -26,6 +27,8 @@ TEST(CameraComponent_Test, DefaultValues) {
EXPECT_FLOAT_EQ(camera.GetViewportRect().width, 1.0f);
EXPECT_FLOAT_EQ(camera.GetViewportRect().height, 1.0f);
EXPECT_FALSE(camera.IsSkyboxEnabled());
EXPECT_TRUE(camera.GetSkyboxMaterialPath().empty());
EXPECT_FALSE(camera.GetSkyboxMaterialAssetRef().IsValid());
EXPECT_FLOAT_EQ(camera.GetSkyboxTopColor().r, 0.18f);
EXPECT_FLOAT_EQ(camera.GetSkyboxHorizonColor().g, 0.84f);
EXPECT_FLOAT_EQ(camera.GetSkyboxBottomColor().b, 0.95f);
@@ -62,6 +65,7 @@ TEST(CameraComponent_Test, SerializeRoundTripPreservesViewportAndClearState) {
source.SetCullingMask(0x0000000Fu);
source.SetViewportRect(XCEngine::Math::Rect(0.25f, 0.125f, 0.5f, 0.625f));
source.SetSkyboxEnabled(true);
source.SetSkyboxMaterialPath(XCEngine::Resources::GetBuiltinDefaultPrimitiveMaterialPath().CStr());
source.SetSkyboxTopColor(XCEngine::Math::Color(0.12f, 0.21f, 0.64f, 1.0f));
source.SetSkyboxHorizonColor(XCEngine::Math::Color(0.71f, 0.76f, 0.88f, 1.0f));
source.SetSkyboxBottomColor(XCEngine::Math::Color(0.92f, 0.82f, 0.58f, 1.0f));
@@ -80,6 +84,7 @@ TEST(CameraComponent_Test, SerializeRoundTripPreservesViewportAndClearState) {
EXPECT_FLOAT_EQ(target.GetViewportRect().width, 0.5f);
EXPECT_FLOAT_EQ(target.GetViewportRect().height, 0.625f);
EXPECT_TRUE(target.IsSkyboxEnabled());
EXPECT_EQ(target.GetSkyboxMaterialPath(), XCEngine::Resources::GetBuiltinDefaultPrimitiveMaterialPath().CStr());
EXPECT_FLOAT_EQ(target.GetSkyboxTopColor().b, 0.64f);
EXPECT_FLOAT_EQ(target.GetSkyboxHorizonColor().g, 0.76f);
EXPECT_FLOAT_EQ(target.GetSkyboxBottomColor().r, 0.92f);

File diff suppressed because one or more lines are too long

View File

@@ -17,11 +17,14 @@
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/RHI/RHITexture.h>
#include <XCEngine/Scene/Scene.h>
#include "../../../RHI/integration/fixtures/RHIIntegrationFixture.h"
#include <cmath>
#include <memory>
#include <vector>
@@ -40,6 +43,84 @@ constexpr const char* kVulkanScreenshot = "skybox_scene_vulkan.ppm";
constexpr uint32_t kFrameWidth = 1280;
constexpr uint32_t kFrameHeight = 720;
Texture* CreatePanoramicSkyboxTexture() {
constexpr uint32_t kTextureWidth = 256;
constexpr uint32_t kTextureHeight = 128;
auto* texture = new Texture();
IResource::ConstructParams params = {};
params.name = "SkyboxPanoramicTexture";
params.path = "Tests/Rendering/SkyboxScene/skybox_panorama.texture";
params.guid = ResourceGUID::Generate(params.path);
texture->Initialize(params);
std::vector<float> pixels(static_cast<size_t>(kTextureWidth) * static_cast<size_t>(kTextureHeight) * 4u, 1.0f);
for (uint32_t y = 0; y < kTextureHeight; ++y) {
for (uint32_t x = 0; x < kTextureWidth; ++x) {
const float u = static_cast<float>(x) / static_cast<float>(kTextureWidth - 1u);
const float v = static_cast<float>(y) / static_cast<float>(kTextureHeight - 1u);
Vector3 color = Vector3(0.05f, 0.08f, 0.14f);
if (v < 0.42f) {
const float t = v / 0.42f;
color = Vector3(
0.08f + 0.42f * (1.0f - t),
0.16f + 0.50f * (1.0f - t),
0.34f + 0.58f * (1.0f - t));
} else if (v < 0.58f) {
const float t = (v - 0.42f) / 0.16f;
color = Vector3(
0.92f - 0.24f * t,
0.74f - 0.18f * t,
0.36f - 0.12f * t);
} else {
const float t = (v - 0.58f) / 0.42f;
color = Vector3(
0.12f - 0.06f * t,
0.10f - 0.05f * t,
0.09f - 0.04f * t);
}
const float sunDx = u - 0.22f;
const float sunDy = v - 0.34f;
const float sun = std::exp(-(sunDx * sunDx + sunDy * sunDy) * 1450.0f) * 4.0f;
color += Vector3(1.0f, 0.82f, 0.58f) * sun;
if (u > 0.62f && u < 0.68f && v > 0.46f && v < 0.82f) {
color = Vector3(0.10f, 0.52f, 0.96f);
}
if (u > 0.34f && u < 0.39f && v > 0.50f && v < 0.86f) {
color = Vector3(0.88f, 0.24f, 0.19f);
}
if (u > 0.78f && u < 0.92f && v > 0.60f && v < 0.78f) {
color = Vector3(0.22f, 0.70f, 0.28f);
}
const size_t pixelIndex = (static_cast<size_t>(y) * static_cast<size_t>(kTextureWidth) + x) * 4u;
pixels[pixelIndex + 0] = color.x;
pixels[pixelIndex + 1] = color.y;
pixels[pixelIndex + 2] = color.z;
pixels[pixelIndex + 3] = 1.0f;
}
}
const size_t pixelDataSize = pixels.size() * sizeof(float);
if (!texture->Create(
kTextureWidth,
kTextureHeight,
1,
1,
XCEngine::Resources::TextureType::Texture2D,
XCEngine::Resources::TextureFormat::RGBA32_FLOAT,
pixels.data(),
pixelDataSize)) {
delete texture;
return nullptr;
}
return texture;
}
Mesh* CreateQuadMesh() {
auto* mesh = new Mesh();
IResource::ConstructParams params = {};
@@ -100,6 +181,21 @@ Material* CreateMaterial(
return material;
}
Material* CreateSkyboxMaterial(Texture* texture) {
auto* material = new Material();
IResource::ConstructParams params = {};
params.name = "SkyboxPanoramicMaterial";
params.path = "Tests/Rendering/SkyboxScene/skybox_panorama.mat";
params.guid = ResourceGUID::Generate(params.path);
material->Initialize(params);
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinSkyboxShaderPath()));
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
material->SetFloat4("_Tint", Vector4(1.0f, 1.0f, 1.0f, 1.0f));
material->SetFloat("_Exposure", 1.05f);
material->SetFloat("_Rotation", 0.0f);
return material;
}
GameObject* CreateRenderableObject(
Scene& scene,
const char* name,
@@ -151,6 +247,7 @@ private:
std::unique_ptr<SceneRenderer> mSceneRenderer;
std::vector<RHIResourceView*> mBackBufferViews;
std::vector<Material*> mMaterials;
std::vector<Texture*> mTextures;
Mesh* mMesh = nullptr;
RHITexture* mDepthTexture = nullptr;
RHIResourceView* mDepthView = nullptr;
@@ -221,6 +318,11 @@ void SkyboxSceneTest::TearDown() {
}
mMaterials.clear();
for (Texture* texture : mTextures) {
delete texture;
}
mTextures.clear();
delete mMesh;
mMesh = nullptr;
@@ -239,9 +341,11 @@ void SkyboxSceneTest::BuildScene() {
camera->SetFarClipPlane(20.0f);
camera->SetClearColor(XCEngine::Math::Color(0.01f, 0.01f, 0.01f, 1.0f));
camera->SetSkyboxEnabled(true);
camera->SetSkyboxTopColor(XCEngine::Math::Color(0.11f, 0.29f, 0.64f, 1.0f));
camera->SetSkyboxHorizonColor(XCEngine::Math::Color(0.82f, 0.87f, 0.96f, 1.0f));
camera->SetSkyboxBottomColor(XCEngine::Math::Color(0.96f, 0.93f, 0.86f, 1.0f));
Texture* skyboxTexture = CreatePanoramicSkyboxTexture();
ASSERT_NE(skyboxTexture, nullptr);
Material* skyboxMaterial = CreateSkyboxMaterial(skyboxTexture);
ASSERT_NE(skyboxMaterial, nullptr);
camera->SetSkyboxMaterial(skyboxMaterial);
MaterialRenderState opaqueState = {};
opaqueState.cullMode = MaterialCullMode::Back;
@@ -269,7 +373,8 @@ void SkyboxSceneTest::BuildScene() {
Vector4(0.12f, 0.88f, 0.74f, 0.56f),
MaterialRenderQueue::Transparent,
transparentState);
mMaterials = { cubeMaterial, transparentMaterial };
mMaterials = { skyboxMaterial, cubeMaterial, transparentMaterial };
mTextures = { skyboxTexture };
CreateRenderableObject(
*mScene,

View File

@@ -156,13 +156,48 @@ TEST(BuiltinForwardPipeline_Test, BuiltinSkyboxShaderDeclaresExplicitEnvironment
const ShaderPass* pass = shader->FindPass("Skybox");
ASSERT_NE(pass, nullptr);
ASSERT_EQ(pass->resources.Size(), 1u);
ASSERT_EQ(pass->resources.Size(), 4u);
const ShaderPropertyDesc* tint = shader->FindProperty("_Tint");
ASSERT_NE(tint, nullptr);
EXPECT_EQ(tint->type, ShaderPropertyType::Color);
EXPECT_EQ(tint->semantic, "Tint");
const ShaderPropertyDesc* exposure = shader->FindProperty("_Exposure");
ASSERT_NE(exposure, nullptr);
EXPECT_EQ(exposure->type, ShaderPropertyType::Float);
EXPECT_EQ(exposure->semantic, "Exposure");
const ShaderPropertyDesc* rotation = shader->FindProperty("_Rotation");
ASSERT_NE(rotation, nullptr);
EXPECT_EQ(rotation->type, ShaderPropertyType::Float);
EXPECT_EQ(rotation->semantic, "Rotation");
const ShaderPropertyDesc* panoramic = shader->FindProperty("_MainTex");
ASSERT_NE(panoramic, nullptr);
EXPECT_EQ(panoramic->type, ShaderPropertyType::Texture2D);
EXPECT_EQ(panoramic->semantic, "SkyboxTexture");
EXPECT_EQ(pass->resources[0].semantic, "Environment");
EXPECT_EQ(pass->resources[0].type, ShaderResourceType::ConstantBuffer);
EXPECT_EQ(pass->resources[0].set, 0u);
EXPECT_EQ(pass->resources[0].binding, 0u);
EXPECT_EQ(pass->resources[1].semantic, "Material");
EXPECT_EQ(pass->resources[1].type, ShaderResourceType::ConstantBuffer);
EXPECT_EQ(pass->resources[1].set, 1u);
EXPECT_EQ(pass->resources[1].binding, 0u);
EXPECT_EQ(pass->resources[2].semantic, "SkyboxTexture");
EXPECT_EQ(pass->resources[2].type, ShaderResourceType::Texture2D);
EXPECT_EQ(pass->resources[2].set, 2u);
EXPECT_EQ(pass->resources[2].binding, 0u);
EXPECT_EQ(pass->resources[3].semantic, "LinearClampSampler");
EXPECT_EQ(pass->resources[3].type, ShaderResourceType::Sampler);
EXPECT_EQ(pass->resources[3].set, 3u);
EXPECT_EQ(pass->resources[3].binding, 0u);
delete shader;
}

View File

@@ -11,6 +11,7 @@
#include <XCEngine/Rendering/RenderPipelineAsset.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Scene/Scene.h>
#include <memory>
@@ -43,6 +44,9 @@ struct MockPipelineState {
XCEngine::RHI::RHIResourceView* lastShadowMap = nullptr;
XCEngine::Math::Matrix4x4 lastShadowViewProjection = XCEngine::Math::Matrix4x4::Identity();
XCEngine::Math::Vector4 lastShadowParams = XCEngine::Math::Vector4::Zero();
RenderEnvironmentMode lastEnvironmentMode = RenderEnvironmentMode::None;
bool lastHasSkybox = false;
const XCEngine::Resources::Material* lastSkyboxMaterial = nullptr;
std::vector<CameraComponent*> renderedCameras;
std::vector<RenderClearFlags> renderedClearFlags;
std::vector<XCEngine::Math::Color> renderedClearColors;
@@ -299,6 +303,9 @@ public:
m_state->lastShadowMap = sceneData.lighting.mainDirectionalShadow.shadowMap;
m_state->lastShadowViewProjection = sceneData.lighting.mainDirectionalShadow.viewProjection;
m_state->lastShadowParams = sceneData.lighting.mainDirectionalShadow.shadowParams;
m_state->lastEnvironmentMode = sceneData.environment.mode;
m_state->lastHasSkybox = sceneData.environment.HasSkybox();
m_state->lastSkyboxMaterial = sceneData.environment.materialSkybox.material;
m_state->renderedCameras.push_back(sceneData.camera);
m_state->renderedClearFlags.push_back(sceneData.cameraData.clearFlags);
m_state->renderedClearColors.push_back(sceneData.cameraData.clearColor);
@@ -576,6 +583,33 @@ TEST(CameraRenderer_Test, AppliesRequestClearColorOverrideToSceneData) {
EXPECT_FLOAT_EQ(state->lastClearColor.a, 1.0f);
}
TEST(CameraRenderer_Test, PromotesSkyboxMaterialIntoEnvironmentFrameData) {
Scene scene("CameraRendererSkyboxMaterialScene");
GameObject* cameraObject = scene.CreateGameObject("Camera");
auto* camera = cameraObject->AddComponent<CameraComponent>();
camera->SetPrimary(true);
camera->SetSkyboxEnabled(true);
XCEngine::Resources::Material skyboxMaterial;
camera->SetSkyboxMaterial(&skyboxMaterial);
auto state = std::make_shared<MockPipelineState>();
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
CameraRenderRequest request;
request.scene = &scene;
request.camera = camera;
request.context = CreateValidContext();
request.surface = RenderSurface(320, 200);
request.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
ASSERT_TRUE(renderer.Render(request));
EXPECT_TRUE(state->lastHasSkybox);
EXPECT_EQ(state->lastEnvironmentMode, RenderEnvironmentMode::MaterialSkybox);
EXPECT_EQ(state->lastSkyboxMaterial, &skyboxMaterial);
}
TEST(CameraRenderer_Test, ExecutesInjectedPreAndPostPassSequencesAroundPipelineRender) {
Scene scene("CameraRendererPassScene");