Hazel游戏引擎(100)Vulkan、SPIR-V和新Shader系统

2023-10-13 04:20

本文主要是介绍Hazel游戏引擎(100)Vulkan、SPIR-V和新Shader系统,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文中若有代码、术语等错误,欢迎指正

文章目录

  • 前言
  • 介绍SPIR-V
  • 项目改变
  • 代码流程
    • 给GPU的Uniform缓冲区上传数据
    • SPIR-V编译
  • 项目遇到耗费我一天半的BUG
    • BUG信息以及解决方法
    • 我解决此BUG的路线
  • 运行脚本安装Vulkan遇到的问题

前言

  • 前前言

    • Cherno未来想做的

      当前项目以后Cherno打算支持vulkan,由于vulkan着色器代码也支持glsl语言,但是和Opengl的glsl标准不一

      因为Vulkan中存在OpenGL的不存在的东西(反之亦然),所以着色器代码肯定不同。

    • vulkan和opengl的glsl对比-以Uniform为例

      • 主要不同

        在于,opengl支持uniform,vulkan不支持uniform,而是支持uniform缓冲区(push_constant、存储缓冲区)这比OpenGL的glsl更好。

        opengl的uniform

        uniform mat4 m_transform;
        

        vulkan的uniform缓冲区

        layout(std140, binding=2) uniform Transform{mat4 Transform;
        }
        
      • Opengl支持的uniform哪里不好

        场景有很多个物体,这样一个物体调用一次drawcall(不是批处理模式),每个物体需要显示都需要上传摄像机的投影视图矩阵,那么10000个物体就有10000个uniform更新,但其实摄像机的投影视图矩阵在当前帧不变的,这样就会造成性能的下降。

      • vulkan哪里好

        vulkan的uniform是在GPU开辟的一块缓冲区,每个物体需要摄像机的投影视图矩阵,只需将这块缓冲区放入投影矩阵的值,然后每个drawcall都去访问这块缓冲区,得到投影视图矩阵就行,性能更好。

      • 但后面我发现,OpenGL也有uniform缓冲区,不知是否我理解错了。

  • 此节目的

    重新写shader系统,使其能支持vulkan和opengl两种glsl

  • 如何实现

    使用SPIR-V,作为中间表示语言,即可支持vulkan也支持opengl的glsl

介绍SPIR-V

  • 介绍SPIR-V

    vulkanApi要求以SPIR-V组件的形式提供着色器,而这个SPIR-V相当于“中间表示语言

    1. 可以将写好的glsl、hlsl转换为SPIR-V
    2. 也可以将SPIR-V转换为glsl、hlsl。

    使得可以完成只写次shader,自动生成各种不同版本的shader语言

  • SPIR-V什么工作方式:(可能我理解错了了)

    Cherno说,将vulkan的glsl用SPIR-V编译成SPIR-V二进制文件,然后可以用SPIR-V的交叉编译这个二进制文件可以成hlsl、metal,以及兼容OpenGL的glsl。

  • 实现过程中的重要功能

    使用SPIR-V加入着色器缓存功能,不用每次都编译着色器,节省时间

以上很有可能说的不正确,我有点迷糊,东拼西凑的写完以上内容,但大概意思是这样。

项目改变

  • 下载vulkan

    地址

    需要下载最新,安装的时候每个组件最好都点上,需要有这几个文件

  • 环境变量

    请添加图片描述

    特别是VULKAN_SDK,接下来的premake会获取这个环境变量

  • 修改premake

    ......
    outputdir = "%{cfg.buildcfg}-%{cfg.system}-%{cfg.architecture}"VULKAN_SDK = os.getenv("VULKAN_SDK")-- Include directories relative to root folder (solution directory)
    IncludeDir = {}
    IncludeDir["GLFW"] = "GameEngineLightWeight/vendor/GLFW/include"
    ......
    IncludeDir["ImGuizmo"] = "GameEngineLightWeight/vendor/ImGuizmo" 
    IncludeDir["VulkanSDK"] = "%{VULKAN_SDK}/Include"LibraryDir = {}LibraryDir["VulkanSDK"] = "%{VULKAN_SDK}/Lib"
    LibraryDir["VulkanSDK_Debug"] = "%{wks.location}/GameEngineLightWeight/vendor/VulkanSDK/Lib"Library = {}
    Library["Vulkan"] = "%{LibraryDir.VulkanSDK}/vulkan-1.lib"
    Library["VulkanUtils"] = "%{LibraryDir.VulkanSDK}/VkLayer_utils.lib"Library["ShaderC_Debug"] = "%{LibraryDir.VulkanSDK_Debug}/shaderc_sharedd.lib"
    Library["SPIRV_Cross_Debug"] = "%{LibraryDir.VulkanSDK_Debug}/spirv-cross-cored.lib"
    Library["SPIRV_Cross_GLSL_Debug"] = "%{LibraryDir.VulkanSDK_Debug}/spirv-cross-glsld.lib"
    Library["SPIRV_Tools_Debug"] = "%{LibraryDir.VulkanSDK_Debug}/SPIRV-Toolsd.lib"Library["ShaderC_Release"] = "%{LibraryDir.VulkanSDK}/shaderc_shared.lib"
    Library["SPIRV_Cross_Release"] = "%{LibraryDir.VulkanSDK}/spirv-cross-core.lib"
    Library["SPIRV_Cross_GLSL_Release"] = "%{LibraryDir.VulkanSDK}/spirv-cross-glsl.lib"group "Dependencies"include "GameEngineLightWeight/vendor/GLFW"
    ......
    project "GameEngineLightWeight"location "GameEngineLightWeight"includedirs{......"%{IncludeDir.ImGuizmo}","%{IncludeDir.VulkanSDK}"}......filter "configurations:Debug"......links{"%{Library.ShaderC_Debug}","%{Library.SPIRV_Cross_Debug}","%{Library.SPIRV_Cross_GLSL_Debug}"}filter "configurations:Release"......links{"%{Library.ShaderC_Release}","%{Library.SPIRV_Cross_Release}","%{Library.SPIRV_Cross_GLSL_Release}"}
    

    注意,项目要取消动态链接改为静态链接了,可能是vulkan的那几个lib文件是静态的

    kind "StaticLib"
    -- staticruntime "on"
    staticruntime "off"
    

代码流程

给GPU的Uniform缓冲区上传数据

  • 写vulkan的glsl

    // 纹理的glsl
    #type vertex
    #version 450 corelayout(location = 0) in vec3 a_Position;
    layout(location = 1) in vec4 a_Color;
    layout(location = 2) in vec2 a_TexCoord;
    layout(location = 3) in float a_TexIndex;
    layout(location = 4) in float a_TilingFactor;
    layout(location = 5) in int a_EntityID;//
    // uniform缓冲区///
    // 使用0号缓冲区///
    layout(std140, binding = 0) uniform Camera{ // std140是布局mat4 u_ViewProjection;
    };
    // uniform mat4 u_ViewProjection;
    struct VertexOutput {vec4 Color;vec2 TexCoord;float TexIndex;float TilingFactor;
    };
    layout(location = 0) out VertexOutput Output;
    layout(location = 4) out flat int v_EntityID; // 4 是因为TilingFactor 是3
    //out vec4 v_Color;
    //out vec2 v_TexCoord;
    //out float v_TexIndex;
    //out float v_TilingFactor;
    //out flat int v_EntityId;void main() {/*v_Color = a_Color;v_TexCoord = a_TexCoord;v_TexIndex = a_TexIndex;v_TilingFactor = a_TilingFactor;v_EntityId = a_EntityId;*/Output.Color = a_Color;Output.TexCoord = a_TexCoord;Output.TexIndex = a_TexIndex;Output.TilingFactor = a_TilingFactor;v_EntityID = a_EntityID;gl_Position = u_ViewProjection * vec4(a_Position, 1.0);
    }#type fragment
    #version 450 corelayout(location = 0) out vec4 color;
    layout(location = 1) out int color2;struct VertexOutput {vec4 Color;vec2 TexCoord;float TexIndex;float TilingFactor;
    };
    layout(location = 0) in VertexOutput Input;
    layout(location = 4) in flat int v_EntityID; // 4 是因为TilingFactor 是3//in vec4 v_Color;
    //in vec2 v_TexCoord;
    //in float v_TexIndex;
    //in float v_TilingFactor;
    //in flat int v_EntityId;layout(binding = 0) uniform sampler2D u_Textures[32];
    //uniform sampler2D u_Textures[32];void main() {color = texture(u_Textures[int(Input.TexIndex)], Input.TexCoord * Input.TilingFactor) * Input.Color;color2 = v_EntityID;// 给顶点包围的区域每个像素都设置为实体ID
    }
    
    • std140是什么

      是一种布局,点这了解

    • 注意被注释的是OpenGL的Glsl写法

  • 新建UniformBuffer类

    #pragma once
    #include "Hazel/Core/Core.h"namespace Hazel {class UniformBuffer{public:virtual ~UniformBuffer(){}virtual void SetData(const void* data, uint32_t size, uint32_t offset = 0) = 0;static Ref<UniformBuffer> Create(uint32_t size, uint32_t binding);};
    }
    
    #include "hzpch.h"
    #include "OpenGLUniformBuffer.h"
    #include <glad/glad.h>namespace Hazel {//// Uniform缓冲区/OpenGLUniformBuffer::OpenGLUniformBuffer(uint32_t size, uint32_t binding){// GPU创建缓冲区,并且返回缓冲区号m_RendererIDglCreateBuffers(1, &m_RendererID);// 声明缓冲区的数据glNamedBufferData(m_RendererID, size, nullptr, GL_DYNAMIC_DRAW); // TODO:investigate usage hint// 将在glsl上设置的bingding 0号缓冲区与真正的缓冲区m_RendererID联系起来glBindBufferBase(GL_UNIFORM_BUFFER, binding, m_RendererID);/*	 glsl代码中声明使用0号的uniform缓冲区layout(std140, binding = 0) uniform Camera*/}OpenGLUniformBuffer::~OpenGLUniformBuffer(){glDeleteBuffers(1, &m_RendererID);}// 上传数据给缓冲区///void OpenGLUniformBuffer::SetData(const void* data, uint32_t size, uint32_t offset){// 上传数据给m_RendererID号缓冲区吧,实则给GPU的bingding号缓冲区glNamedBufferSubData(m_RendererID, offset, size, data);}
    }

    由上,glBindBufferBase,将使glsl上设置的bingding 0号缓冲区与真正的缓冲区m_RendererID联系起来

  • Renderer2D.cpp上传摄像机的投影视图矩阵给Uniform缓冲区

    struct Renderer2DData
    {.....struct CameraData{glm::mat4 ViewProjection;};CameraData CameraBuffer;Ref<UniformBuffer> CameraUniformBuffer;
    };void Renderer2D::Init()
    {.....// 初始化CameraUniformBuffer实例,在构造函数中就将调用上面的glBindBufferBase函数s_Data.CameraUniformBuffer = UniformBuffer::Create(sizeof(Renderer2DData::CameraData), 0);
    }
    void Renderer2D::BeginScene(const EditorCamera& camera)
    {HZ_PROFILE_FUNCTION();s_Data.CameraBuffer.ViewProjection = camera.GetViewProjection();// 上传数据给缓冲区///// 使用CameraUniformBuffer的SetData才真正的上传投影视图矩阵给GPU的Uniform缓冲区s_Data.CameraUniformBuffer->SetData(&s_Data.CameraBuffer, sizeof(Renderer2DData::CameraData));StartBatch();
    }
    

    给CameraUniformBuffer的m_RendererID缓冲区上传数据,也是上传到glsl上声明使用0号uniform缓冲区上

    layout(std140, binding = 0) uniform Camera

SPIR-V编译

  • 将vulkan的glsl编译成SPIR-V二进制文件,并且保存在本地作为缓存

    void OpenGLShader::CompileOrGetVulkanBinaries(const std::unordered_map<GLenum, std::string>& shaderSources)
    {GLuint program = glCreateProgram();shaderc::Compiler compiler;shaderc::CompileOptions options;options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_2);const bool optimize = true;if (optimize) {// 优化:性能优先options.SetOptimizationLevel(shaderc_optimization_level_performance);}// 生成二进制的缓存目录std::filesystem::path cacheDirectory = Utils::GetCacheDirectory();auto& shaderData = m_VulkanSPIRV;shaderData.clear();for (auto&& [stage, source] : shaderSources){std::filesystem::path shaderFilePath = m_FilePath;std::filesystem::path cachedPath = cacheDirectory / (shaderFilePath.filename().string() + Utils::GLShaderStageCachedOpenGLFileExtension(stage));std::ifstream in(cachedPath, std::ios::in | std::ios::binary);// 缓存是否存在if (in.is_open()) {// 存在打开加载in.seekg(0, std::ios::end);auto size = in.tellg();in.seekg(0, std::ios::beg);auto& data = shaderData[stage];// ?data.resize(size / sizeof(uint32_t));in.read((char*)data.data(), size);}else {/// 将Vulkan的glsl编译成SPIR-V二进制文件shaderc::SpvCompilationResult module = compiler.CompileGlslToSpv(source, Utils::GLShaderStageToShaderC(stage), m_FilePath.c_str(), options);if (module.GetCompilationStatus() != shaderc_compilation_status_success) {HZ_CORE_ERROR(module.GetErrorMessage());HZ_CORE_ASSERT(false);}shaderData[stage] = std::vector<uint32_t>(module.cbegin(), module.cend());std::ofstream out(cachedPath, std::ios::out | std::ios::binary);if (out.is_open()) {auto& data = shaderData[stage];out.write((char*)data.data(), data.size() * sizeof(uint32_t));out.flush();out.close();}}}for (auto&& [stage, data] : shaderData)Reflect(stage, data);
    }
    
  • 将Vulkan的glsl的SPIR-V二进制文件转换为OpenGL的glsl源文件字符串

    再将OpenGL的glsl源文件字符串转换为SPIR-V二进制文件,并且保存在本地作为缓存

    void OpenGLShader::CompileOrGetOpenGLBinaries()
    {auto& shaderData = m_OpenGLSPIRV;shaderc::Compiler compiler;shaderc::CompileOptions options;options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_3);const bool optimize = true;if (optimize) {// 优化:性能优先options.SetOptimizationLevel(shaderc_optimization_level_performance);}// 生成二进制的缓存目录std::filesystem::path cacheDirectory = Utils::GetCacheDirectory();shaderData.clear();m_OpenGLSourceCode.clear();for (auto&& [stage, spirv] : m_VulkanSPIRV){std::filesystem::path shaderFilePath = m_FilePath;std::filesystem::path cachedPath = cacheDirectory / (shaderFilePath.filename().string() + Utils::GLShaderStageCachedOpenGLFileExtension(stage));std::ifstream in(cachedPath, std::ios::in | std::ios::binary);// 缓存是否存在if (in.is_open()) {// 存在打开加载in.seekg(0, std::ios::end);auto size = in.tellg();in.seekg(0, std::ios::beg);auto& data = shaderData[stage];// ?data.resize(size / sizeof(uint32_t));in.read((char*)data.data(), size);}else {/// 将Vulkan的glsl的SPIR-V二进制文件转换为OpenGL的glsl源文件字符串spirv_cross::CompilerGLSL glslCompiler(spirv);m_OpenGLSourceCode[stage] = glslCompiler.compile();auto& source = m_OpenGLSourceCode[stage];/// 再将OpenGL的glsl源文件字符串转换为SPIR-V二进制文件,并且保存在本地作为缓存shaderc::SpvCompilationResult module = compiler.CompileGlslToSpv(source, Utils::GLShaderStageToShaderC(stage), m_FilePath.c_str(), options);if (module.GetCompilationStatus() != shaderc_compilation_status_success) {HZ_CORE_ERROR(module.GetErrorMessage());HZ_CORE_ASSERT(false);}shaderData[stage] = std::vector<uint32_t>(module.cbegin(), module.cend());std::ofstream out(cachedPath, std::ios::out | std::ios::binary);if (out.is_open()) {auto& data = shaderData[stage];out.write((char*)data.data(), data.size() * sizeof(uint32_t));out.flush();out.close();}}}
    }
    
  • 用OpenGL的API编译链接OpenGL版本的glsl的SPIR-V二进制文件

    与原始编译链接OpenGL的GLSL着色器代码不太一样

    void OpenGLShader::CreateProgram()
    {GLuint program = glCreateProgram();std::vector<GLuint> shaderIDs;for (auto&& [stage, spirv] : m_OpenGLSPIRV){GLuint shaderID = shaderIDs.emplace_back(glCreateShader(stage));glShaderBinary(1, &shaderID, GL_SHADER_BINARY_FORMAT_SPIR_V, spirv.data(), spirv.size() * sizeof(uint32_t));glSpecializeShader(shaderID, "main", 0, nullptr, nullptr);glAttachShader(program, shaderID);}glLinkProgram(program);GLint isLinked;glGetProgramiv(program, GL_LINK_STATUS, &isLinked);if (isLinked == GL_FALSE){GLint maxLength;glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);std::vector<GLchar> infoLog(maxLength);glGetProgramInfoLog(program, maxLength, &maxLength, infoLog.data());HZ_CORE_ERROR("Shader linking failed ({0}):\n{1}", m_FilePath, infoLog.data());glDeleteProgram(program);for (auto id : shaderIDs)glDeleteShader(id);}for (auto id : shaderIDs){glDetachShader(program, id);glDeleteShader(id);}m_RendererID = program;
    }
    

项目遇到耗费我一天半的BUG

BUG信息以及解决方法

  • 错误如下

    0x00007FFE06F0FDB6 (atio6axx.dll)处(位于 GameEngine-Editor.exe 中)引发的异常: 0xC0000005: 写入位置 0x0000000000000008 时发生访问冲突。

  • 错误原因

    AMD显卡运行由OpenGL的SPIR-V二进制编译的shader会报这个错,是AMD显卡的问题!

  • 解决方法一

    显卡设置用英伟达显卡运行此程序解决

    请添加图片描述

    请添加图片描述

  • 解决方法二:适合只有AMD显卡的电脑

    来自Github上Hazel的ISSUE:点这

    解决方法来自ISSUE fixed:点这

    写openglshader代码时,用Vulkan的SPIR-V二进制编译shader。

    大意是:使用 SpirV 和反射的唯一解决方案是使用 SpirV-Cross 为 OpenGL 生成的 sharder 源,而不是 spirV 二进制文件。

    For now the only solution i found to keep using SpirV and the reflection is by using sharder sources generated by SpirV-Cross for OpenGL instead fof the spirV binary.

    GLuint program = glCreateProgram();std::vector<GLuint> glShadersIDs;int glShaderIDIndex = 0;
    for (auto&& [stage, spirv] : m_VulkanSPIRV) {spirv_cross::CompilerGLSL glslCompiler(spirv);auto& source = glslCompiler.compile();GLuint shader;shader = glCreateShader(stage);const GLchar* sourceCStr = source.c_str();glShaderSource(shader, 1, &sourceCStr, 0);glCompileShader(shader);int isCompiled = 0;glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);if (isCompiled == GL_FALSE) {int maxLength = 0;glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);std::vector<char> infoLog(maxLength);glGetShaderInfoLog(shader, maxLength, &maxLength, &infoLog[0]);glDeleteShader(shader);HZ_CORE_ERROR("{0}", infoLog.data());HZ_CORE_ASSERT(false, "[OpenGL] Shader compilation failure!");break;}glAttachShader(program, shader);glShadersIDs[glShaderIDIndex++] = shader;
    }
    

    我还没完整运行起来。

    第二种方法比较靠谱:检测是amd显卡就换

我解决此BUG的路线

  • 以为是代码写错

    下载了TheCherno的项目并且修复好运行起来

    • 克隆Hazel项目到本地

      git clone https://github.com/TheCherno/Hazel
      

      2023年2月8日补:

      上面的git命令因为缺少–recursive所以没成功拷贝子模块,用下面的命令,可以省去下面修 复子模块 的步骤

      git clone --recursive https://github.com/TheCherno/Hazel
      
    • 回退到第100集版本

      到Hazel项目的提交历史复制SHA

      运行以下命令,即可回退

      git checkout sha
      
    • 修复子模块

      将.gitmodules文件替换为正确的url地址

      [submodule "Hazel/vendor/spdlog"]path = Hazel/vendor/spdlogurl = https://github.com/gabime/spdlog
      [submodule "Hazel/vendor/GLFW"]path = Hazel/vendor/GLFWurl = https://github.com/TheCherno/glfw
      [submodule "Hazel/vendor/imgui"]path = Hazel/vendor/imguiurl = https://github.com/TheCherno/imgui
      [submodule "Hazel/vendor/glm"]path = Hazel/vendor/glmurl = https://github.com/g-truc/glm
      [submodule "Hazel/vendor/yaml-cpp"]path = Hazel/vendor/yaml-cppurl = https://github.com/TheCherno/yaml-cpp
      [submodule "Hazel/vendor/ImGuizmo"]path = Hazel/vendor/ImGuizmourl = https://github.com/TheCherno/ImGuizmo
      

      再运行下载子模块git命令

      git submodule init
      git submodule update
      
    • 然后拷贝vulkan的lib,重新指向premake脚本,即可

    运行发现还是报同样的错,遂放弃。

  • 以为是vulkan版本错误

    卸载最新的1.3.236.0版本

    • 安装1.2.170.0版本

      但是发现这个版本竟没有Cherno要求的shaderc_sharedd.lib,即后带d的lib文件,于是混用1.3.236.0的shaderc_sharedd.lib文件,运行不出意外还是报错,直接运行不起来,即卸载

      难怪Cherno的python脚本写了验证是否有shaderc_sharedd.lib文件

      @classmethod
      def CheckVulkanSDKDebugLibs(cls):vulkanSDK = os.environ.get("VULKAN_SDK")shadercdLib = Path(f"{vulkanSDK}/Lib/shaderc_sharedd.lib")return shadercdLib.exists()
      
    • 安装1.3.216.0版本

      问题和上一样没有shaderc_sharedd.lib,混用1.3.236.0的shaderc_sharedd.lib文件,报错。即卸载

      error LNK2001: 无法解析的外部符号 "protected: virtual void __cdecl spirv_cross::CompilerGLSL::declare_undefined_values(void)" (?declare_undefined_values@CompilerGLSL@spirv_cross@@MEAAXXZ)
      
  • 收获

    1. 解决方法来自Hazel的GitHub的ISSUE,而我却忘记遇到bug应该在这里找类似的问题。
    2. 搜索谷歌问题有技巧,应该用atio6axx.dll关键字搜索,我用一整段错误信息搜索找不到合适解决方法。

    即:最好先好好查谷歌、GitHub的issue再折腾其它方法。

运行脚本安装Vulkan遇到的问题

  • SyntaxError: (unicode error) ‘utf-8’ codec can’t decode byte 0xa3

    python程序中使用了UTF-8编码,而没有在脚本上申明要使用UTF-8编码。说的更加直接一点,就是用了中文字符,一般我们习惯使用中文作为注释。

    在python脚本程序中,主动申明我们使用UTF-8编码方式。

    申明的方法如下,在程序最上面增加以下语句,尤其是第二句。

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    
  • ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:1129)

    搜索一番发现是因为电脑开了代理(科学上网工具)的原因,但是实际上代理是可以正常使用的。

    方法:关闭科学上网

  • 安装了Vulkan但是python脚本却检测不到

    确定在环境变量设置了VULKAN_SDK名称的环境变量

    VULKAN_SDK
    D:\3DGameSDK\Vulkan\1.3.236.0
    

    如果设置了,还是检测不到,就重启IDE

这篇关于Hazel游戏引擎(100)Vulkan、SPIR-V和新Shader系统的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/200722

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【区块链 + 人才服务】可信教育区块链治理系统 | FISCO BCOS应用案例

伴随着区块链技术的不断完善,其在教育信息化中的应用也在持续发展。利用区块链数据共识、不可篡改的特性, 将与教育相关的数据要素在区块链上进行存证确权,在确保数据可信的前提下,促进教育的公平、透明、开放,为教育教学质量提升赋能,实现教育数据的安全共享、高等教育体系的智慧治理。 可信教育区块链治理系统的顶层治理架构由教育部、高校、企业、学生等多方角色共同参与建设、维护,支撑教育资源共享、教学质量评估、

软考系统规划与管理师考试证书含金量高吗?

2024年软考系统规划与管理师考试报名时间节点: 报名时间:2024年上半年软考将于3月中旬陆续开始报名 考试时间:上半年5月25日到28日,下半年11月9日到12日 分数线:所有科目成绩均须达到45分以上(包括45分)方可通过考试 成绩查询:可在“中国计算机技术职业资格网”上查询软考成绩 出成绩时间:预计在11月左右 证书领取时间:一般在考试成绩公布后3~4个月,各地领取时间有所不同

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能

国产游戏崛起:技术革新与文化自信的双重推动

近年来,国产游戏行业发展迅猛,技术水平和作品质量均得到了显著提升。特别是以《黑神话:悟空》为代表的一系列优秀作品,成功打破了过去中国游戏市场以手游和网游为主的局限,向全球玩家展示了中国在单机游戏领域的实力与潜力。随着中国开发者在画面渲染、物理引擎、AI 技术和服务器架构等方面取得了显著进展,国产游戏正逐步赢得国际市场的认可。然而,面对全球游戏行业的激烈竞争,国产游戏技术依然面临诸多挑战,未来的