From f80e37bd61e447f1d66fbb5eb4c1ab7a8a77cf0f Mon Sep 17 00:00:00 2001 From: skal Date: Sun, 1 Feb 2026 11:31:00 +0100 Subject: feat: Add seamless bump mapping with procedural noise - Replaced white noise with smooth value-like noise. - Implemented periodic texture generation (seam blending). - Integrated bump mapping into Renderer3D using finite difference of displaced SDF. - Updated test_3d_render with noise texture and multiple SDF shapes (Box, Sphere, Torus). --- src/tests/test_3d_render.cc | 193 +++++++++++++++++++++++--------------------- 1 file changed, 100 insertions(+), 93 deletions(-) (limited to 'src/tests/test_3d_render.cc') diff --git a/src/tests/test_3d_render.cc b/src/tests/test_3d_render.cc index 4be7153..e4477a0 100644 --- a/src/tests/test_3d_render.cc +++ b/src/tests/test_3d_render.cc @@ -5,11 +5,13 @@ #include "3d/object.h" #include "3d/renderer.h" #include "3d/scene.h" +#include "gpu/texture_manager.h" #include "platform.h" -#include -#include +#include "procedural/generator.h" #include #include +#include +#include #if defined(DEMO_CROSS_COMPILE_WIN32) #include @@ -19,6 +21,7 @@ // Global State static Renderer3D g_renderer; +static TextureManager g_textures; static Scene g_scene; static Camera g_camera; static WGPUDevice g_device = nullptr; @@ -29,18 +32,18 @@ static WGPUTextureFormat g_format = WGPUTextureFormat_Undefined; static int g_width = 1280; static int g_height = 720; -// Reimplementing basic WebGPU init here +// ... (init_wgpu implementation same as before) void init_wgpu() { WGPUInstance instance = wgpuCreateInstance(nullptr); if (!instance) { - std::cerr << "Failed to create WGPU instance." << std::endl; - exit(1); + std::cerr << "Failed to create WGPU instance." << std::endl; + exit(1); } - + g_surface = platform_create_wgpu_surface(instance); if (!g_surface) { - std::cerr << "Failed to create WGPU surface." << std::endl; - exit(1); + std::cerr << "Failed to create WGPU surface." << std::endl; + exit(1); } WGPURequestAdapterOptions adapter_opts = {}; @@ -50,21 +53,17 @@ void init_wgpu() { #if defined(DEMO_CROSS_COMPILE_WIN32) auto on_adapter = [](WGPURequestAdapterStatus status, WGPUAdapter adapter, const char* message, void* userdata) { - if (status == WGPURequestAdapterStatus_Success) { - *(WGPUAdapter*)userdata = adapter; - } else { - std::cerr << "Adapter Error: " << (message ? message : "null") << std::endl; - } + if (status == WGPURequestAdapterStatus_Success) { + *(WGPUAdapter*)userdata = adapter; + } }; wgpuInstanceRequestAdapter(instance, &adapter_opts, on_adapter, &g_adapter); #else auto on_adapter = [](WGPURequestAdapterStatus status, WGPUAdapter adapter, WGPUStringView message, void* userdata, void* user2) { - if (status == WGPURequestAdapterStatus_Success) { - *(WGPUAdapter*)userdata = adapter; - } else { - std::cerr << "Adapter Error: " << (message.data ? message.data : "null") << std::endl; - } + if (status == WGPURequestAdapterStatus_Success) { + *(WGPUAdapter*)userdata = adapter; + } }; WGPURequestAdapterCallbackInfo adapter_cb = {}; adapter_cb.mode = WGPUCallbackMode_WaitAnyOnly; @@ -73,34 +72,33 @@ void init_wgpu() { wgpuInstanceRequestAdapter(instance, &adapter_opts, adapter_cb); #endif - // Spin wait for adapter #if !defined(DEMO_CROSS_COMPILE_WIN32) while (!g_adapter) { - wgpuInstanceProcessEvents(instance); + wgpuInstanceProcessEvents(instance); } #endif - + if (!g_adapter) { - std::cerr << "Failed to get adapter." << std::endl; - exit(1); + std::cerr << "Failed to get adapter." << std::endl; + exit(1); } WGPUDeviceDescriptor device_desc = {}; - + #if defined(DEMO_CROSS_COMPILE_WIN32) auto on_device = [](WGPURequestDeviceStatus status, WGPUDevice device, const char* message, void* userdata) { - if (status == WGPURequestDeviceStatus_Success) { - *(WGPUDevice*)userdata = device; - } + if (status == WGPURequestDeviceStatus_Success) { + *(WGPUDevice*)userdata = device; + } }; wgpuAdapterRequestDevice(g_adapter, &device_desc, on_device, &g_device); #else auto on_device = [](WGPURequestDeviceStatus status, WGPUDevice device, WGPUStringView message, void* userdata, void* user2) { - if (status == WGPURequestDeviceStatus_Success) { - *(WGPUDevice*)userdata = device; - } + if (status == WGPURequestDeviceStatus_Success) { + *(WGPUDevice*)userdata = device; + } }; WGPURequestDeviceCallbackInfo device_cb = {}; device_cb.mode = WGPUCallbackMode_WaitAnyOnly; @@ -108,19 +106,18 @@ void init_wgpu() { device_cb.userdata1 = &g_device; wgpuAdapterRequestDevice(g_adapter, &device_desc, device_cb); #endif - + #if !defined(DEMO_CROSS_COMPILE_WIN32) - // Poll until device is ready (WaitAny is unimplemented in current wgpu-native build) while (!g_device) { - wgpuInstanceProcessEvents(instance); + wgpuInstanceProcessEvents(instance); } #endif if (!g_device) { - std::cerr << "Failed to get device." << std::endl; - exit(1); + std::cerr << "Failed to get device." << std::endl; + exit(1); } - + g_queue = wgpuDeviceGetQueue(g_device); WGPUSurfaceCapabilities caps = {}; @@ -140,95 +137,105 @@ void init_wgpu() { void setup_scene() { g_scene.clear(); - // Center Red Cube (Wireframe Proxy) - Object3D center(ObjectType::CUBE); + Object3D center(ObjectType::BOX); center.position = vec3(0, 0, 0); center.color = vec4(1, 0, 0, 1); g_scene.add_object(center); - // Orbiting Objects for (int i = 0; i < 8; ++i) { ObjectType type = ObjectType::SPHERE; - if (i % 3 == 1) type = ObjectType::TORUS; - if (i % 3 == 2) type = ObjectType::BOX; - + if (i % 3 == 1) + type = ObjectType::TORUS; + if (i % 3 == 2) + type = ObjectType::BOX; + Object3D obj(type); float angle = (i / 8.0f) * 6.28318f; obj.position = vec3(std::cos(angle) * 4.0f, 0, std::sin(angle) * 4.0f); obj.scale = vec3(0.5f, 0.5f, 0.5f); - - if (type == ObjectType::SPHERE) obj.color = vec4(0, 1, 0, 1); - else if (type == ObjectType::TORUS) obj.color = vec4(0, 0.5, 1, 1); - else obj.color = vec4(1, 1, 0, 1); - + + if (type == ObjectType::SPHERE) + obj.color = vec4(0, 1, 0, 1); + else if (type == ObjectType::TORUS) + obj.color = vec4(0, 0.5, 1, 1); + else + obj.color = vec4(1, 1, 0, 1); + g_scene.add_object(obj); } } +// Wrapper to generate periodic noise +void gen_periodic_noise(uint8_t* buffer, int w, int h, const float* params, + int num_params) { + procedural::gen_noise(buffer, w, h, params, num_params); + float p_params[] = {0.1f}; // 10% overlap + procedural::make_periodic(buffer, w, h, p_params, 1); +} + int main() { platform_init_window(false); - init_wgpu(); - + g_renderer.init(g_device, g_queue, g_format); g_renderer.resize(g_width, g_height); - + + g_textures.init(g_device, g_queue); + ProceduralTextureDef noise_def; + noise_def.width = 256; + noise_def.height = 256; + noise_def.gen_func = gen_periodic_noise; + noise_def.params = {1234.0f, + 16.0f}; // Seed, Frequency (Increased for more detail) + g_textures.create_procedural_texture("noise", noise_def); + + g_renderer.set_noise_texture(g_textures.get_texture_view("noise")); + setup_scene(); - + g_camera.position = vec3(0, 5, 10); g_camera.target = vec3(0, 0, 0); - + float time = 0.0f; while (!platform_should_close()) { platform_poll(); - - time += 0.016f; // Approx 60fps - - // Animate Objects - for (size_t i = 1; i < g_scene.objects.size(); ++i) { - g_scene.objects[i].rotation = quat::from_axis(vec3(0, 1, 0), time * 2.0f + i); - g_scene.objects[i].position.y = std::sin(time * 3.0f + i) * 1.5f; - } - - // Animate Camera Height and Radius + time += 0.016f; + float cam_radius = 10.0f + std::sin(time * 0.3f) * 4.0f; float cam_height = 5.0f + std::cos(time * 0.4f) * 3.0f; - g_camera.set_look_at( - vec3(std::sin(time * 0.5f) * cam_radius, cam_height, std::cos(time * 0.5f) * cam_radius), - vec3(0, 0, 0), - vec3(0, 1, 0) - ); - - // Render Frame + g_camera.set_look_at(vec3(std::sin(time * 0.5f) * cam_radius, cam_height, + std::cos(time * 0.5f) * cam_radius), + vec3(0, 0, 0), vec3(0, 1, 0)); + + for (size_t i = 1; i < g_scene.objects.size(); ++i) { + g_scene.objects[i].rotation = + quat::from_axis(vec3(0, 1, 0), time * 2.0f + i); + g_scene.objects[i].position.y = std::sin(time * 3.0f + i) * 1.5f; + } + WGPUSurfaceTexture surface_tex; wgpuSurfaceGetCurrentTexture(g_surface, &surface_tex); - - if (surface_tex.status == WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal) { - WGPUTextureViewDescriptor view_desc = {}; - view_desc.format = g_format; - view_desc.dimension = WGPUTextureViewDimension_2D; - view_desc.baseMipLevel = 0; - view_desc.mipLevelCount = 1; - view_desc.baseArrayLayer = 0; - view_desc.arrayLayerCount = 1; - view_desc.aspect = WGPUTextureAspect_All; - WGPUTextureView view = wgpuTextureCreateView(surface_tex.texture, &view_desc); - - g_renderer.render(g_scene, g_camera, time, view); - - wgpuTextureViewRelease(view); - wgpuSurfacePresent(g_surface); - wgpuTextureRelease(surface_tex.texture); + if (surface_tex.status == + WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal) { + WGPUTextureViewDescriptor view_desc = {}; + view_desc.format = g_format; + view_desc.dimension = WGPUTextureViewDimension_2D; + view_desc.baseMipLevel = 0; + view_desc.mipLevelCount = 1; + view_desc.baseArrayLayer = 0; + view_desc.arrayLayerCount = 1; + view_desc.aspect = WGPUTextureAspect_All; + WGPUTextureView view = + wgpuTextureCreateView(surface_tex.texture, &view_desc); + g_renderer.render(g_scene, g_camera, time, view); + wgpuTextureViewRelease(view); + wgpuSurfacePresent(g_surface); + wgpuTextureRelease(surface_tex.texture); } - -#if !defined(DEMO_CROSS_COMPILE_WIN32) - // Poll events for wgpu-native to ensure callbacks fire and frame presents? - // We don't have easy access to instance here unless we store it globally. - // Let's just assume Present handles enough synchronization for this demo. -#endif } - + g_renderer.shutdown(); + g_textures.shutdown(); platform_shutdown(); return 0; } -- cgit v1.2.3