#include #include #include #include #include #include #include #include #include const char shaderSource[] = R"( option red: bool; [layout(std140)] struct Data { projectionMatrix: mat4, worldMatrix: mat4, viewMatrix: mat4 } [set(0)] external { [binding(0)] viewerData: uniform, } [set(1)] external { [binding(0)] tex: sampler2D } struct VertIn { [location(0)] position: vec3, [location(1)] normal: vec3, [location(2)] uv: vec2 } struct VertOut { [builtin(position)] position: vec4, [location(0)] normal: vec3, [location(1)] uv: vec2 } struct FragOut { [location(0)] color: vec4 } [entry(frag)] fn main(fragIn: VertOut) -> FragOut { let lightDir = vec3(0.0, 0.707, 0.707); let lightFactor = dot(fragIn.normal, lightDir); let fragOut: FragOut; fragOut.color = lightFactor * tex.Sample(fragIn.uv) * select_opt(red, vec4(1.0, 0.0, 0.0, 1.0), vec4(1.0, 1.0, 1.0, 1.0)); return fragOut; } [entry(vert)] fn main(vertIn: VertIn) -> VertOut { let vertOut: VertOut; vertOut.position = viewerData.projectionMatrix * viewerData.viewMatrix * viewerData.worldMatrix * vec4(vertIn.position, 1.0); vertOut.normal = vertIn.normal; vertOut.uv = vertIn.uv; return vertOut; } )"; int main() { std::filesystem::path resourceDir = "resources"; if (!std::filesystem::is_directory(resourceDir) && std::filesystem::is_directory(".." / resourceDir)) resourceDir = ".." / resourceDir; Nz::Renderer::Config rendererConfig; std::cout << "Run using Vulkan? (y/n)" << std::endl; if (std::getchar() == 'y') rendererConfig.preferredAPI = Nz::RenderAPI::Vulkan; else rendererConfig.preferredAPI = Nz::RenderAPI::OpenGL; Nz::Modules nazara(rendererConfig); Nz::RenderWindow window; Nz::MeshParams meshParams; meshParams.center = true; meshParams.matrix = Nz::Matrix4f::Rotate(Nz::EulerAnglesf(0.f, -90.f, 0.f)) * Nz::Matrix4f::Scale(Nz::Vector3f(0.002f)); meshParams.vertexDeclaration = Nz::VertexDeclaration::Get(Nz::VertexLayout::XYZ_Normal_UV); std::shared_ptr device = Nz::Renderer::Instance()->InstanciateRenderDevice(0); std::string windowTitle = "Render Test"; if (!window.Create(device, Nz::VideoMode(800, 600, 32), windowTitle)) { std::cout << "Failed to create Window" << std::endl; return __LINE__; } Nz::ShaderWriter::States states; states.enabledOptions = 0; states.optimize = true; auto fragVertShader = device->InstantiateShaderModule(Nz::ShaderStageType::Fragment | Nz::ShaderStageType::Vertex, Nz::ShaderLanguage::NazaraShader, shaderSource, sizeof(shaderSource), states); if (!fragVertShader) { std::cout << "Failed to instantiate shader" << std::endl; return __LINE__; } std::shared_ptr drfreak = Nz::Mesh::LoadFromFile(resourceDir / "Spaceship/spaceship.obj", meshParams); if (!drfreak) { NazaraError("Failed to load model"); return __LINE__; } std::shared_ptr spaceshipMesh = std::static_pointer_cast(drfreak->GetSubMesh(0)); const std::shared_ptr& meshVB = spaceshipMesh->GetVertexBuffer(); const std::shared_ptr& meshIB = spaceshipMesh->GetIndexBuffer(); // Index buffer std::cout << "Index count: " << meshIB->GetIndexCount() << std::endl; // Vertex buffer std::cout << "Vertex count: " << meshVB->GetVertexCount() << std::endl; // Texture Nz::TextureParams texParams; texParams.renderDevice = device; texParams.loadFormat = Nz::PixelFormat::RGBA8_SRGB; std::shared_ptr texture = Nz::Texture::LoadFromFile(resourceDir / "Spaceship/Texture/diffuse.png", texParams); std::shared_ptr textureSampler = device->InstantiateTextureSampler({}); struct { Nz::Matrix4f projectionMatrix; Nz::Matrix4f modelMatrix; Nz::Matrix4f viewMatrix; } ubo; Nz::Vector2ui windowSize = window.GetSize(); ubo.projectionMatrix = Nz::Matrix4f::Perspective(Nz::DegreeAnglef(70.f), float(windowSize.x) / windowSize.y, 0.1f, 1000.f); ubo.viewMatrix = Nz::Matrix4f::Translate(Nz::Vector3f::Backward() * 1); ubo.modelMatrix = Nz::Matrix4f::Translate(Nz::Vector3f::Forward() * 2); Nz::UInt32 uniformSize = sizeof(ubo); Nz::RenderPipelineLayoutInfo pipelineLayoutInfo; auto& uboBinding = pipelineLayoutInfo.bindings.emplace_back(); uboBinding.setIndex = 0; uboBinding.bindingIndex = 0; uboBinding.shaderStageFlags = Nz::ShaderStageType::Vertex; uboBinding.type = Nz::ShaderBindingType::UniformBuffer; std::shared_ptr basePipelineLayout = device->InstantiateRenderPipelineLayout(pipelineLayoutInfo); auto& textureBinding = pipelineLayoutInfo.bindings.emplace_back(); textureBinding.setIndex = 1; textureBinding.bindingIndex = 0; textureBinding.shaderStageFlags = Nz::ShaderStageType::Fragment; textureBinding.type = Nz::ShaderBindingType::Texture; std::shared_ptr renderPipelineLayout = device->InstantiateRenderPipelineLayout(std::move(pipelineLayoutInfo)); Nz::ShaderBindingPtr viewerShaderBinding = basePipelineLayout->AllocateShaderBinding(0); Nz::ShaderBindingPtr textureShaderBinding = renderPipelineLayout->AllocateShaderBinding(1); std::shared_ptr uniformBuffer = device->InstantiateBuffer(Nz::BufferType::Uniform); if (!uniformBuffer->Initialize(uniformSize, Nz::BufferUsage::DeviceLocal | Nz::BufferUsage::Dynamic)) { NazaraError("Failed to create uniform buffer"); return __LINE__; } viewerShaderBinding->Update({ { 0, Nz::ShaderBinding::UniformBufferBinding { uniformBuffer.get(), 0, uniformSize } } }); textureShaderBinding->Update({ { 0, Nz::ShaderBinding::TextureBinding { texture.get(), textureSampler.get() } } }); Nz::RenderPipelineInfo pipelineInfo; pipelineInfo.pipelineLayout = renderPipelineLayout; pipelineInfo.faceCulling = true; pipelineInfo.depthBuffer = true; pipelineInfo.shaderModules.emplace_back(fragVertShader); auto& vertexBuffer = pipelineInfo.vertexBuffers.emplace_back(); vertexBuffer.binding = 0; vertexBuffer.declaration = meshVB->GetVertexDeclaration(); std::shared_ptr pipeline = device->InstantiateRenderPipeline(pipelineInfo); Nz::RenderDevice* renderDevice = window.GetRenderDevice().get(); Nz::RenderWindowImpl* windowImpl = window.GetImpl(); std::shared_ptr commandPool = windowImpl->CreateCommandPool(Nz::QueueType::Graphics); Nz::RenderBuffer* renderBufferIB = static_cast(meshIB->GetBuffer()->GetImpl()); Nz::RenderBuffer* renderBufferVB = static_cast(meshVB->GetBuffer()->GetImpl()); if (!renderBufferIB->Synchronize(renderDevice)) { NazaraError("Failed to synchronize render buffer"); return __LINE__; } if (!renderBufferVB->Synchronize(renderDevice)) { NazaraError("Failed to synchronize render buffer"); return __LINE__; } Nz::AbstractBuffer* indexBufferImpl = renderBufferIB->GetHardwareBuffer(renderDevice); Nz::AbstractBuffer* vertexBufferImpl = renderBufferVB->GetHardwareBuffer(renderDevice); Nz::Vector3f viewerPos = Nz::Vector3f::Zero(); Nz::EulerAnglesf camAngles(0.f, 0.f, 0.f); Nz::Quaternionf camQuat(camAngles); window.EnableEventPolling(true); Nz::Clock updateClock; Nz::Clock secondClock; unsigned int fps = 0; bool uboUpdate = true; Nz::Mouse::SetRelativeMouseMode(true); while (window.IsOpen()) { Nz::WindowEvent event; while (window.PollEvent(&event)) { switch (event.type) { case Nz::WindowEventType::Quit: window.Close(); break; case Nz::WindowEventType::MouseMoved: // La souris a bougé { // Gestion de la caméra free-fly (Rotation) float sensitivity = 0.3f; // Sensibilité de la souris // On modifie l'angle de la caméra grâce au déplacement relatif sur X de la souris camAngles.yaw = camAngles.yaw - event.mouseMove.deltaX * sensitivity; camAngles.yaw.Normalize(); // Idem, mais pour éviter les problèmes de calcul de la matrice de vue, on restreint les angles camAngles.pitch = Nz::Clamp(camAngles.pitch - event.mouseMove.deltaY*sensitivity, -89.f, 89.f); camQuat = camAngles; uboUpdate = true; break; } case Nz::WindowEventType::Resized: { Nz::Vector2ui windowSize = window.GetSize(); ubo.projectionMatrix = Nz::Matrix4f::Perspective(Nz::DegreeAnglef(70.f), float(windowSize.x) / windowSize.y, 0.1f, 1000.f); uboUpdate = true; break; } default: break; } } if (updateClock.GetMilliseconds() > 1000 / 60) { float cameraSpeed = 2.f * updateClock.GetSeconds(); updateClock.Restart(); if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Up) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Z)) viewerPos += camQuat * Nz::Vector3f::Forward() * cameraSpeed; // Si la flèche du bas ou la touche S est pressée, on recule if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Down) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::S)) viewerPos += camQuat * Nz::Vector3f::Backward() * cameraSpeed; // Etc... if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Left) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Q)) viewerPos += camQuat * Nz::Vector3f::Left() * cameraSpeed; // Etc... if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Right) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::D)) viewerPos += camQuat * Nz::Vector3f::Right() * cameraSpeed; // Majuscule pour monter, notez l'utilisation d'une direction globale (Non-affectée par la rotation) if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::LShift) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::RShift)) viewerPos += Nz::Vector3f::Up() * cameraSpeed; // Contrôle (Gauche ou droite) pour descendre dans l'espace global, etc... if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::LControl) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::RControl)) viewerPos += Nz::Vector3f::Down() * cameraSpeed; uboUpdate = true; } Nz::RenderFrame frame = windowImpl->Acquire(); if (!frame) continue; ubo.viewMatrix = Nz::Matrix4f::ViewMatrix(viewerPos, camAngles); if (uboUpdate) { auto& allocation = frame.GetUploadPool().Allocate(uniformSize); std::memcpy(allocation.mappedPtr, &ubo, sizeof(ubo)); frame.Execute([&](Nz::CommandBufferBuilder& builder) { builder.BeginDebugRegion("UBO Update", Nz::Color::Yellow); { builder.PreTransferBarrier(); builder.CopyBuffer(allocation, uniformBuffer.get()); builder.PostTransferBarrier(); } builder.EndDebugRegion(); }, Nz::QueueType::Transfer); uboUpdate = false; } frame.Execute([&](Nz::CommandBufferBuilder& builder) { Nz::Recti renderRect(0, 0, window.GetSize().x, window.GetSize().y); Nz::CommandBufferBuilder::ClearValues clearValues[2]; clearValues[0].color = Nz::Color::Black; clearValues[1].depth = 1.f; clearValues[1].stencil = 0; builder.BeginDebugRegion("Main window rendering", Nz::Color::Green); { builder.BeginRenderPass(windowImpl->GetFramebuffer(frame.GetFramebufferIndex()), windowImpl->GetRenderPass(), renderRect, { clearValues[0], clearValues[1] }); { builder.BindIndexBuffer(indexBufferImpl); builder.BindPipeline(*pipeline); builder.BindVertexBuffer(0, vertexBufferImpl); builder.BindShaderBinding(0, *viewerShaderBinding); builder.BindShaderBinding(1, *textureShaderBinding); builder.SetScissor(Nz::Recti{ 0, 0, int(windowSize.x), int(windowSize.y) }); builder.SetViewport(Nz::Recti{ 0, 0, int(windowSize.x), int(windowSize.y) }); builder.DrawIndexed(meshIB->GetIndexCount()); } builder.EndRenderPass(); } builder.EndDebugRegion(); }, Nz::QueueType::Graphics); frame.Present(); window.Display(); // On incrémente le compteur de FPS improvisé fps++; if (secondClock.GetMilliseconds() >= 1000) // Toutes les secondes { // Et on insère ces données dans le titre de la fenêtre window.SetTitle(windowTitle + " - " + Nz::NumberToString(fps) + " FPS"); /* Note: En C++11 il est possible d'insérer de l'Unicode de façon standard, quel que soit l'encodage du fichier, via quelque chose de similaire à u8"Cha\u00CEne de caract\u00E8res". Cependant, si le code source est encodé en UTF-8 (Comme c'est le cas dans ce fichier), cela fonctionnera aussi comme ceci : "Chaîne de caractères". */ // Et on réinitialise le compteur de FPS fps = 0; // Et on relance l'horloge pour refaire ça dans une seconde secondClock.Restart(); } } return EXIT_SUCCESS; }