Skip to content

Commit f389b35

Browse files
committed
implement skybox
1 parent 9249cfc commit f389b35

File tree

10 files changed

+277
-7
lines changed

10 files changed

+277
-7
lines changed

shaders/skybox.frag.hlsl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
struct VSOutput {
2+
float4 position : SV_Position;
3+
float3 texCoord : TEXCOORD0;
4+
};
5+
6+
#pragma pack_matrix(column_major)
7+
8+
// For SPIR-V Fragment Shaders, Sampled Textures are in Set 2 (space2)
9+
[[vk::binding(0, 2)]] SamplerState skySampler : register(s0, space2);
10+
[[vk::binding(0, 2)]] TextureCube skyTexture : register(t0, space2);
11+
12+
float4 main(VSOutput input) : SV_Target {
13+
return skyTexture.Sample(skySampler, input.texCoord);
14+
}

shaders/skybox.frag.spv

704 Bytes
Binary file not shown.

shaders/skybox.vert.hlsl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
struct VSInput {
2+
[[vk::location(0)]] float3 position : TEXCOORD0;
3+
};
4+
5+
struct VSOutput {
6+
float4 position : SV_Position;
7+
float3 texCoord : TEXCOORD0;
8+
};
9+
10+
#pragma pack_matrix(column_major)
11+
12+
// For SPIR-V Vertex Shaders, Uniform Buffers are in Set 1 (space1)
13+
cbuffer Uniforms : register(b0, space1) {
14+
float4x4 mvp;
15+
};
16+
17+
VSOutput main(VSInput input) {
18+
VSOutput output;
19+
output.position = mul(mvp, float4(input.position, 1.0));
20+
// xyww trick to ensure skybox is at the far plane
21+
output.position = output.position.xyww;
22+
output.texCoord = input.position;
23+
return output;
24+
}

shaders/skybox.vert.spv

956 Bytes
Binary file not shown.

src/Engine/Objects/Sky.hpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ namespace Nova {
1616
namespace Props {
1717
struct SkyProps {
1818
rfl::Flatten<InstanceProps> base;
19-
std::string SkyboxBk = "rbxasset://textures/sky/sky_bk.png";
20-
std::string SkyboxDn = "rbxasset://textures/sky/sky_dn.png";
21-
std::string SkyboxFt = "rbxasset://textures/sky/sky_ft.png";
22-
std::string SkyboxLf = "rbxasset://textures/sky/sky_lf.png";
23-
std::string SkyboxRt = "rbxasset://textures/sky/sky_rt.png";
24-
std::string SkyboxUp = "rbxasset://textures/sky/sky_up.png";
19+
std::string SkyboxBk = "rbxasset://textures/sky/null_plainsky512_bk.jpg";
20+
std::string SkyboxDn = "rbxasset://textures/sky/null_plainsky512_dn.jpg";
21+
std::string SkyboxFt = "rbxasset://textures/sky/null_plainsky512_ft.jpg";
22+
std::string SkyboxLf = "rbxasset://textures/sky/null_plainsky512_lf.jpg";
23+
std::string SkyboxRt = "rbxasset://textures/sky/null_plainsky512_rt.jpg";
24+
std::string SkyboxUp = "rbxasset://textures/sky/null_plainsky512_up.jpg";
2525
int StarCount = 3000;
2626
};
2727
}

src/Engine/Rendering/Renderer.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ namespace Nova {
7272
}
7373

7474
Renderer::~Renderer() {
75+
if (skyboxPipeline) SDL_ReleaseGPUGraphicsPipeline(device, skyboxPipeline);
76+
if (skyboxTexture) SDL_ReleaseGPUTexture(device, skyboxTexture);
7577
SDL_ReleaseGPUGraphicsPipeline(device, basePipeline);
7678
SDL_ReleaseGPUBuffer(device, cubeBuffer);
7779
SDL_ReleaseGPUBuffer(device, instanceBuffer);
@@ -126,6 +128,7 @@ namespace Nova {
126128

127129
if (SDL_WaitAndAcquireGPUSwapchainTexture(cmd, window, &swapchainTexture, &w, &h)) {
128130
fb.Refresh(device, w, h);
131+
UpdateSkybox(workspace);
129132

130133
float aspect = (float)w / (float)h;
131134
glm::mat4 proj = glm::perspectiveRH_ZO(glm::radians(70.0f), aspect, 0.1f, 10000.0f);
@@ -213,6 +216,26 @@ namespace Nova {
213216
depthTarget.store_op = SDL_GPU_STOREOP_DONT_CARE;
214217

215218
auto pass = SDL_BeginGPURenderPass(cmd, &colorTarget, 1, &depthTarget);
219+
220+
if (skyboxPipeline && skyboxTexture) {
221+
SDL_BindGPUGraphicsPipeline(pass, skyboxPipeline);
222+
223+
// View matrix without translation
224+
// HLSL mul(mvp, pos) with column_major expects the same layout as GLM
225+
glm::mat4 skyView = glm::mat4(glm::mat3(view));
226+
glm::mat4 skyMVP = proj * skyView;
227+
228+
SDL_PushGPUVertexUniformData(cmd, 0, &skyMVP, sizeof(glm::mat4));
229+
230+
SDL_GPUTextureSamplerBinding skyBinding = { .texture = skyboxTexture, .sampler = surfaceSampler };
231+
SDL_BindGPUFragmentSamplers(pass, 0, &skyBinding, 1);
232+
233+
SDL_GPUBufferBinding vBinding = { .buffer = cubeBuffer, .offset = 0 };
234+
SDL_BindGPUVertexBuffers(pass, 0, &vBinding, 1);
235+
236+
SDL_DrawGPUPrimitives(pass, 36, 1, 0, 0);
237+
}
238+
216239
if (!instances.empty()) {
217240
SDL_BindGPUGraphicsPipeline(pass, basePipeline);
218241
SDL_GPUBufferBinding vBinding = { .buffer = cubeBuffer, .offset = 0 };

src/Engine/Rendering/Renderer.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,23 @@ namespace Nova {
4545
const Frustum& frustum,
4646
std::vector<InstanceData>& outData);
4747

48+
void UpdateSkybox(std::shared_ptr<Workspace> workspace);
49+
void LoadSkyboxTexture(const std::vector<std::string>& paths);
50+
4851
SDL_GPUDevice* device;
4952
SDL_Window* window;
5053
SDL_GPUGraphicsPipeline* basePipeline;
54+
SDL_GPUGraphicsPipeline* skyboxPipeline;
5155
SDL_GPUBuffer* cubeBuffer;
5256
SDL_GPUBuffer* instanceBuffer;
5357
SDL_GPUTransferBuffer* instanceTransferBuffer = nullptr;
5458

5559
SDL_GPUTexture* surfaceTexture = nullptr;
5660
SDL_GPUSampler* surfaceSampler = nullptr;
5761

62+
SDL_GPUTexture* skyboxTexture = nullptr;
63+
std::string currentSkyboxPaths[6];
64+
5865
Framebuffer fb;
5966

6067
std::vector<uint8_t> LoadSPIRV(const std::string& path);

src/Engine/Rendering/Renderer_Pipeline.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,5 +84,53 @@ namespace Nova {
8484
basePipeline = SDL_CreateGPUGraphicsPipeline(device, &pInfo);
8585
SDL_ReleaseGPUShader(device, vShader);
8686
SDL_ReleaseGPUShader(device, fShader);
87+
SDL_Log("Base pipeline initialized.");
88+
89+
// Skybox Pipeline
90+
auto skyVCode = LoadSPIRV("shaders/skybox.vert.spv");
91+
auto skyFCode = LoadSPIRV("shaders/skybox.frag.spv");
92+
93+
if (!skyVCode.empty() && !skyFCode.empty()) {
94+
SDL_Log("Skybox shaders loaded (%zu, %zu bytes).", skyVCode.size(), skyFCode.size());
95+
SDL_GPUShaderCreateInfo skyVInfo = {
96+
.code_size = skyVCode.size(),
97+
.code = skyVCode.data(),
98+
.entrypoint = "main",
99+
.format = SDL_GPU_SHADERFORMAT_SPIRV,
100+
.stage = SDL_GPU_SHADERSTAGE_VERTEX,
101+
.num_uniform_buffers = 1 // MVP
102+
};
103+
SDL_GPUShaderCreateInfo skyFInfo = {
104+
.code_size = skyFCode.size(),
105+
.code = skyFCode.data(),
106+
.entrypoint = "main",
107+
.format = SDL_GPU_SHADERFORMAT_SPIRV,
108+
.stage = SDL_GPU_SHADERSTAGE_FRAGMENT,
109+
.num_samplers = 1 // Cubemap
110+
};
111+
112+
SDL_GPUShader* skyVShader = SDL_CreateGPUShader(device, &skyVInfo);
113+
SDL_GPUShader* skyFShader = SDL_CreateGPUShader(device, &skyFInfo);
114+
115+
SDL_GPUGraphicsPipelineCreateInfo skyPInfo = pInfo;
116+
skyPInfo.vertex_shader = skyVShader;
117+
skyPInfo.fragment_shader = skyFShader;
118+
skyPInfo.depth_stencil_state.enable_depth_write = false;
119+
skyPInfo.depth_stencil_state.compare_op = SDL_GPU_COMPAREOP_ALWAYS;
120+
skyPInfo.rasterizer_state.cull_mode = SDL_GPU_CULLMODE_NONE;
121+
122+
skyboxPipeline = SDL_CreateGPUGraphicsPipeline(device, &skyPInfo);
123+
if (skyboxPipeline) {
124+
SDL_Log("Skybox pipeline created successfully.");
125+
} else {
126+
SDL_Log("Failed to create skybox pipeline: %s", SDL_GetError());
127+
}
128+
129+
SDL_ReleaseGPUShader(device, skyVShader);
130+
SDL_ReleaseGPUShader(device, skyFShader);
131+
} else {
132+
skyboxPipeline = nullptr;
133+
SDL_Log("Skybox shaders not found (expected shaders/skybox.vert.spv and shaders/skybox.frag.spv). Skybox will be disabled.");
134+
}
87135
}
88136
}

src/Engine/Rendering/Renderer_Resources.cpp

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,144 @@ namespace Nova {
9999
SDL_ReleaseGPUTransferBuffer(device, tBuf);
100100
SDL_ReleaseGPUTransferBuffer(device, ltBuf);
101101
}
102+
103+
void Renderer::UpdateSkybox(std::shared_ptr<Workspace> workspace) {
104+
std::shared_ptr<Sky> sky = nullptr;
105+
106+
// Find Sky in Lighting (standard location)
107+
if (auto parent = workspace->GetParent()) {
108+
for (auto& child : parent->GetChildren()) {
109+
if (auto lighting = std::dynamic_pointer_cast<Lighting>(child)) {
110+
for (auto& lChild : lighting->GetChildren()) {
111+
if (auto s = std::dynamic_pointer_cast<Sky>(lChild)) {
112+
sky = s;
113+
break;
114+
}
115+
}
116+
}
117+
}
118+
}
119+
120+
if (!sky) {
121+
return;
122+
}
123+
124+
bool changed = false;
125+
std::string newPaths[6] = {
126+
sky->props.SkyboxRt, sky->props.SkyboxLf,
127+
sky->props.SkyboxUp, sky->props.SkyboxDn,
128+
sky->props.SkyboxBk, sky->props.SkyboxFt
129+
};
130+
131+
if (currentSkyboxPaths[0].empty()) changed = true; // Initial load
132+
133+
for (int i = 0; i < 6; i++) {
134+
if (newPaths[i] != currentSkyboxPaths[i]) {
135+
changed = true;
136+
break;
137+
}
138+
}
139+
140+
if (changed) {
141+
SDL_Log("Skybox changed or initializing...");
142+
std::vector<std::string> paths;
143+
for (int i = 0; i < 6; i++) {
144+
currentSkyboxPaths[i] = newPaths[i];
145+
std::string path = newPaths[i];
146+
if (path.starts_with("rbxasset://textures/sky/")) {
147+
path = "resources/sky/" + path.substr(24);
148+
}
149+
paths.push_back(path);
150+
SDL_Log(" Face %d: %s", i, path.c_str());
151+
}
152+
LoadSkyboxTexture(paths);
153+
}
154+
}
155+
156+
void Renderer::LoadSkyboxTexture(const std::vector<std::string>& paths) {
157+
if (paths.size() < 6) return;
158+
159+
SDL_Surface* surfaces[6];
160+
bool success = true;
161+
for (int i = 0; i < 6; i++) {
162+
surfaces[i] = IMG_Load(paths[i].c_str());
163+
if (!surfaces[i]) {
164+
SDL_Log("Failed to load skybox texture %s: %s", paths[i].c_str(), SDL_GetError());
165+
success = false;
166+
} else {
167+
SDL_Surface* rgba = SDL_ConvertSurface(surfaces[i], SDL_PIXELFORMAT_RGBA32);
168+
SDL_DestroySurface(surfaces[i]);
169+
surfaces[i] = rgba;
170+
}
171+
}
172+
173+
if (!success) {
174+
SDL_Log("Skybox loading failed due to missing textures.");
175+
for (int i = 0; i < 6; i++) if (surfaces[i]) SDL_DestroySurface(surfaces[i]);
176+
return;
177+
}
178+
179+
if (skyboxTexture) SDL_ReleaseGPUTexture(device, skyboxTexture);
180+
181+
SDL_GPUTextureCreateInfo texInfo = {
182+
.type = SDL_GPU_TEXTURETYPE_CUBE,
183+
.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM,
184+
.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER,
185+
.width = (uint32_t)surfaces[0]->w,
186+
.height = (uint32_t)surfaces[0]->h,
187+
.layer_count_or_depth = 6,
188+
.num_levels = 1
189+
};
190+
skyboxTexture = SDL_CreateGPUTexture(device, &texInfo);
191+
192+
uint32_t layerSize = surfaces[0]->w * surfaces[0]->h * 4;
193+
SDL_GPUTransferBufferCreateInfo ltInfo = { .usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = layerSize * 6 };
194+
auto ltBuf = SDL_CreateGPUTransferBuffer(device, &ltInfo);
195+
uint8_t* texData = (uint8_t*)SDL_MapGPUTransferBuffer(device, ltBuf, false);
196+
197+
uint32_t width = (uint32_t)surfaces[0]->w;
198+
uint32_t height = (uint32_t)surfaces[0]->h;
199+
200+
for (int i = 0; i < 6; i++) {
201+
std::memcpy(texData + (i * layerSize), surfaces[i]->pixels, layerSize);
202+
203+
if (i == 2 || i == 3) {
204+
uint32_t* pixels = (uint32_t*)(texData + (i * layerSize));
205+
std::vector<uint32_t> temp(width * height);
206+
std::memcpy(temp.data(), pixels, layerSize);
207+
208+
for (uint32_t y = 0; y < height; y++) {
209+
for (uint32_t x = 0; x < width; x++) {
210+
uint32_t srcIdx = y * width + x;
211+
uint32_t dstIdx;
212+
213+
if (i == 3) { // Bottom: 90 degrees clockwise
214+
dstIdx = x * width + (width - 1 - y);
215+
} else { // Top: 90 degrees counter-clockwise
216+
dstIdx = (width - 1 - x) * width + y;
217+
}
218+
219+
pixels[dstIdx] = temp[srcIdx];
220+
}
221+
}
222+
}
223+
224+
SDL_DestroySurface(surfaces[i]);
225+
}
226+
SDL_UnmapGPUTransferBuffer(device, ltBuf);
227+
228+
auto cmd = SDL_AcquireGPUCommandBuffer(device);
229+
auto copy = SDL_BeginGPUCopyPass(cmd);
230+
for (uint32_t i = 0; i < 6; i++) {
231+
SDL_GPUTextureTransferInfo texSrc = { ltBuf, layerSize * i };
232+
SDL_GPUTextureRegion texDst = { skyboxTexture, 0, i, 0, 0, 0, width, height, 1 };
233+
SDL_UploadToGPUTexture(copy, &texSrc, &texDst, false);
234+
}
235+
SDL_EndGPUCopyPass(copy);
236+
auto fence = SDL_SubmitGPUCommandBufferAndAcquireFence(cmd);
237+
SDL_WaitForGPUFences(device, true, &fence, 1);
238+
SDL_ReleaseGPUFence(device, fence);
239+
SDL_ReleaseGPUTransferBuffer(device, ltBuf);
240+
SDL_Log("Skybox cubemap created successfully (%dx%d).", width, height);
241+
}
102242
}

src/main.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#include "Engine/TaskScheduler.hpp"
55
#include "Engine/Nova.hpp"
66
#include "Engine/Reflection/LevelLoader.hpp"
7-
#include "Engine/Services/PhysicsService.hpp"
7+
#include "Engine/Objects/Sky.hpp"
88
#include <SDL3_image/SDL_image.h>
99
#include <tracy/Tracy.hpp>
1010

@@ -33,6 +33,20 @@ int main(int argc, char* argv[]) {
3333
lighting->props.TopAmbientV9 = { 0.5f, 0.5f, 0.5f };
3434
lighting->props.BottomAmbientV9 = { 0.2f, 0.2f, 0.2f };
3535
}
36+
37+
// Add default Sky if none exists
38+
bool hasSky = false;
39+
for (auto& child : lighting->GetChildren()) {
40+
if (std::dynamic_pointer_cast<Nova::Sky>(child)) {
41+
hasSky = true;
42+
break;
43+
}
44+
}
45+
if (!hasSky) {
46+
auto sky = std::make_shared<Nova::Sky>();
47+
sky->SetParent(lighting);
48+
SDL_Log("Added default Sky instance.");
49+
}
3650
}
3751

3852
// MOVE CAMERA OUT OF THE FLOOR

0 commit comments

Comments
 (0)