Formalize cubemap skybox pipeline across backends
This commit is contained in:
@@ -48,6 +48,9 @@ target_compile_definitions(rendering_integration_skybox_scene PRIVATE
|
||||
)
|
||||
|
||||
add_custom_command(TARGET rendering_integration_skybox_scene POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Res
|
||||
$<TARGET_FILE_DIR:rendering_integration_skybox_scene>/Res
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${CMAKE_SOURCE_DIR}/tests/RHI/integration/compare_ppm.py
|
||||
$<TARGET_FILE_DIR:rendering_integration_skybox_scene>/
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
@@ -0,0 +1,11 @@
|
||||
Skybox source:
|
||||
Poly Haven - "farm_field_puresky"
|
||||
Page: https://polyhaven.com/a/farm_field_puresky
|
||||
Direct download used for preprocessing:
|
||||
https://dl.polyhaven.org/file/ph-assets/HDRIs/extra/Tonemapped%20JPG/farm_field_puresky.jpg
|
||||
|
||||
License:
|
||||
CC0 1.0 Universal (public domain), as provided by Poly Haven.
|
||||
|
||||
Repository note:
|
||||
The file committed here is a resized derivative used only for automated rendering tests.
|
||||
@@ -1,3 +1,6 @@
|
||||
#define NOMINMAX
|
||||
#include <windows.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "../RenderingIntegrationMain.h"
|
||||
@@ -19,12 +22,16 @@
|
||||
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||
#include <XCEngine/Resources/Shader/Shader.h>
|
||||
#include <XCEngine/Resources/Texture/Texture.h>
|
||||
#include <XCEngine/Resources/Texture/TextureImportSettings.h>
|
||||
#include <XCEngine/Resources/Texture/TextureLoader.h>
|
||||
#include <XCEngine/RHI/RHITexture.h>
|
||||
#include <XCEngine/Scene/Scene.h>
|
||||
|
||||
#include "../../../RHI/integration/fixtures/RHIIntegrationFixture.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
@@ -42,78 +49,134 @@ constexpr const char* kOpenGLScreenshot = "skybox_scene_opengl.ppm";
|
||||
constexpr const char* kVulkanScreenshot = "skybox_scene_vulkan.ppm";
|
||||
constexpr uint32_t kFrameWidth = 1280;
|
||||
constexpr uint32_t kFrameHeight = 720;
|
||||
constexpr uint32_t kSkyboxFaceSize = 256;
|
||||
constexpr float kPi = 3.14159265358979323846f;
|
||||
|
||||
Texture* CreatePanoramicSkyboxTexture() {
|
||||
constexpr uint32_t kTextureWidth = 256;
|
||||
constexpr uint32_t kTextureHeight = 128;
|
||||
std::filesystem::path GetExecutableDirectory() {
|
||||
char exePath[MAX_PATH] = {};
|
||||
const DWORD length = GetModuleFileNameA(nullptr, exePath, MAX_PATH);
|
||||
if (length == 0 || length >= MAX_PATH) {
|
||||
return std::filesystem::current_path();
|
||||
}
|
||||
|
||||
return std::filesystem::path(exePath).parent_path();
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveRuntimePath(const char* relativePath) {
|
||||
return GetExecutableDirectory() / relativePath;
|
||||
}
|
||||
|
||||
Vector3 ComputeCubemapDirection(uint32_t faceIndex, float s, float t) {
|
||||
switch (faceIndex) {
|
||||
case 0:
|
||||
return Vector3(1.0f, -t, -s).Normalized();
|
||||
case 1:
|
||||
return Vector3(-1.0f, -t, s).Normalized();
|
||||
case 2:
|
||||
return Vector3(s, 1.0f, t).Normalized();
|
||||
case 3:
|
||||
return Vector3(s, -1.0f, -t).Normalized();
|
||||
case 4:
|
||||
return Vector3(s, -t, 1.0f).Normalized();
|
||||
case 5:
|
||||
default:
|
||||
return Vector3(-s, -t, -1.0f).Normalized();
|
||||
}
|
||||
}
|
||||
|
||||
void SamplePanoramaNearest(
|
||||
const Texture& panorama,
|
||||
float u,
|
||||
float v,
|
||||
unsigned char outRgba[4]) {
|
||||
const uint32_t width = panorama.GetWidth();
|
||||
const uint32_t height = panorama.GetHeight();
|
||||
const auto* pixels = static_cast<const unsigned char*>(panorama.GetPixelData());
|
||||
if (pixels == nullptr || width == 0 || height == 0) {
|
||||
outRgba[0] = 255;
|
||||
outRgba[1] = 255;
|
||||
outRgba[2] = 255;
|
||||
outRgba[3] = 255;
|
||||
return;
|
||||
}
|
||||
|
||||
u = u - std::floor(u);
|
||||
v = std::clamp(v, 0.0f, 1.0f);
|
||||
|
||||
const uint32_t x = std::min<uint32_t>(
|
||||
static_cast<uint32_t>(u * static_cast<float>(width - 1) + 0.5f),
|
||||
width - 1);
|
||||
const uint32_t y = std::min<uint32_t>(
|
||||
static_cast<uint32_t>(v * static_cast<float>(height - 1) + 0.5f),
|
||||
height - 1);
|
||||
const size_t pixelIndex = (static_cast<size_t>(y) * static_cast<size_t>(width) + x) * 4u;
|
||||
outRgba[0] = pixels[pixelIndex + 0];
|
||||
outRgba[1] = pixels[pixelIndex + 1];
|
||||
outRgba[2] = pixels[pixelIndex + 2];
|
||||
outRgba[3] = pixels[pixelIndex + 3];
|
||||
}
|
||||
|
||||
Texture* CreateCubemapSkyboxTexture(const std::filesystem::path& panoramaPath) {
|
||||
TextureLoader loader;
|
||||
TextureImportSettings settings;
|
||||
settings.SetSRGB(true);
|
||||
|
||||
LoadResult loadResult = loader.Load(panoramaPath.string().c_str(), &settings);
|
||||
if (!loadResult || loadResult.resource == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Texture> panorama(static_cast<Texture*>(loadResult.resource));
|
||||
if (!panorama || panorama->GetPixelData() == nullptr || panorama->GetWidth() == 0 || panorama->GetHeight() == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* texture = new Texture();
|
||||
IResource::ConstructParams params = {};
|
||||
params.name = "SkyboxPanoramicTexture";
|
||||
params.path = "Tests/Rendering/SkyboxScene/skybox_panorama.texture";
|
||||
params.name = "SkyboxCubemapTexture";
|
||||
params.path = "Tests/Rendering/SkyboxScene/skybox_cubemap.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);
|
||||
std::vector<unsigned char> pixels(
|
||||
static_cast<size_t>(kSkyboxFaceSize) * static_cast<size_t>(kSkyboxFaceSize) * 4u * 6u,
|
||||
255u);
|
||||
for (uint32_t faceIndex = 0; faceIndex < 6u; ++faceIndex) {
|
||||
for (uint32_t y = 0; y < kSkyboxFaceSize; ++y) {
|
||||
for (uint32_t x = 0; x < kSkyboxFaceSize; ++x) {
|
||||
const float s = (2.0f * (static_cast<float>(x) + 0.5f) / static_cast<float>(kSkyboxFaceSize)) - 1.0f;
|
||||
const float t = (2.0f * (static_cast<float>(y) + 0.5f) / static_cast<float>(kSkyboxFaceSize)) - 1.0f;
|
||||
const Vector3 direction = ComputeCubemapDirection(faceIndex, s, t);
|
||||
|
||||
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 u = std::atan2(direction.z, direction.x) / (2.0f * kPi) + 0.5f;
|
||||
const float v = std::acos(std::clamp(direction.y, -1.0f, 1.0f)) / kPi;
|
||||
|
||||
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;
|
||||
unsigned char rgba[4] = {};
|
||||
SamplePanoramaNearest(*panorama, u, v, rgba);
|
||||
|
||||
if (u > 0.62f && u < 0.68f && v > 0.46f && v < 0.82f) {
|
||||
color = Vector3(0.10f, 0.52f, 0.96f);
|
||||
const size_t pixelIndex =
|
||||
((static_cast<size_t>(faceIndex) * static_cast<size_t>(kSkyboxFaceSize) *
|
||||
static_cast<size_t>(kSkyboxFaceSize)) +
|
||||
(static_cast<size_t>(y) * static_cast<size_t>(kSkyboxFaceSize) + x)) * 4u;
|
||||
pixels[pixelIndex + 0] = rgba[0];
|
||||
pixels[pixelIndex + 1] = rgba[1];
|
||||
pixels[pixelIndex + 2] = rgba[2];
|
||||
pixels[pixelIndex + 3] = 255u;
|
||||
}
|
||||
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);
|
||||
const size_t pixelDataSize = pixels.size();
|
||||
if (!texture->Create(
|
||||
kTextureWidth,
|
||||
kTextureHeight,
|
||||
kSkyboxFaceSize,
|
||||
kSkyboxFaceSize,
|
||||
1,
|
||||
1,
|
||||
XCEngine::Resources::TextureType::Texture2D,
|
||||
XCEngine::Resources::TextureFormat::RGBA32_FLOAT,
|
||||
XCEngine::Resources::TextureType::TextureCube,
|
||||
panorama->GetFormat(),
|
||||
pixels.data(),
|
||||
pixelDataSize)) {
|
||||
pixelDataSize,
|
||||
6)) {
|
||||
delete texture;
|
||||
return nullptr;
|
||||
}
|
||||
@@ -184,14 +247,14 @@ Material* CreateMaterial(
|
||||
Material* CreateSkyboxMaterial(Texture* texture) {
|
||||
auto* material = new Material();
|
||||
IResource::ConstructParams params = {};
|
||||
params.name = "SkyboxPanoramicMaterial";
|
||||
params.path = "Tests/Rendering/SkyboxScene/skybox_panorama.mat";
|
||||
params.name = "SkyboxCubemapMaterial";
|
||||
params.path = "Tests/Rendering/SkyboxScene/skybox_cubemap.mat";
|
||||
params.guid = ResourceGUID::Generate(params.path);
|
||||
material->Initialize(params);
|
||||
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinSkyboxShaderPath()));
|
||||
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
|
||||
material->SetTexture("_Tex", ResourceHandle<Texture>(texture));
|
||||
material->SetFloat4("_Tint", Vector4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
material->SetFloat("_Exposure", 1.05f);
|
||||
material->SetFloat("_Exposure", 1.0f);
|
||||
material->SetFloat("_Rotation", 0.0f);
|
||||
return material;
|
||||
}
|
||||
@@ -341,7 +404,7 @@ void SkyboxSceneTest::BuildScene() {
|
||||
camera->SetFarClipPlane(20.0f);
|
||||
camera->SetClearColor(XCEngine::Math::Color(0.01f, 0.01f, 0.01f, 1.0f));
|
||||
camera->SetSkyboxEnabled(true);
|
||||
Texture* skyboxTexture = CreatePanoramicSkyboxTexture();
|
||||
Texture* skyboxTexture = CreateCubemapSkyboxTexture(ResolveRuntimePath("Res/skyboxes/farm_field_puresky_1024.jpg"));
|
||||
ASSERT_NE(skyboxTexture, nullptr);
|
||||
Material* skyboxMaterial = CreateSkyboxMaterial(skyboxTexture);
|
||||
ASSERT_NE(skyboxMaterial, nullptr);
|
||||
|
||||
Reference in New Issue
Block a user