【UE5】将2D切片图渲染为体积纹理,最终实现使用RT实时绘制体积纹理【第三篇-着色器光照】

news2024/11/18 6:03:32

在前两篇文章中,我们分别拆解描述了实现原理,并进行了基础的着色器制作。在这一篇文章中,我们将为它实现光照效果
在这里插入图片描述


简单的概述

当光线射入体积时,随着光线射入距离的增加,体积中的介质会对光线产生反射和吸收作用,使其逐步损失能量。当路径上的能量降为零时,光线将无法再进入相机。如果光线仍有剩余能量,则这部分能量会进入相机。
这就是透射率的概念。
在这里插入图片描述

同时,为了降低复杂性,我们在这里不考虑光在介质内反弹并最终恰巧弹回相机的可能性。考虑这些情况会使问题变得过于复杂,会变得不幸,我们有比尔-朗伯定律。
在这里插入图片描述
核心原理就是在上一章中,用步长(step)为射线累计体积密度的同时,额外进行一次计算,计算光线在这一步时所剩余的能量。作为优化,我们只在边界内(紫色框)进行计算,并且当透射率低于某个阈值时,我们就不再继续计算(红色圆圈),如下图所示。
在这里插入图片描述

而且你会注意到这个光源是平行光,也就是太阳。如果你想要使用点光源,在计算光线路径时,你需要让黄色光线指向点,并且别忘记考虑点光源的自然衰减等特性。本章节我们将使用最简单的平行光。

完善Shader

在开始之前

在本节,着色器会变得逐渐复杂。因此在继续之前,我们有必要先对当前的工作做一些整理。

1.制作路由

在意大利面的复杂性继续增长之前,为了不必要的混乱,需要把一些面条制作成 路由 ,顾名思义,就是个“无线”的面条
在这里插入图片描述
我们先为StepSizeLocalCamVec制作路由
在这里插入图片描述
使用起来像这样
在这里插入图片描述
清爽多了

2.为Custom命名

后面会出现多个Custom,为避免混淆,需要给它们起名啦(之前忘了:| )
起名为RayMarching
在这里插入图片描述

修改 RayMarching 实现光影

1.光线步进计算光影

我们回到 RayMarching 。
之前我们的密度用了一个通道,而光照的颜色需要三个通道,总计四个。因此将输出类型改为四通道。
在这里插入图片描述

接下来我们为其增加5个输入,分别是

输入说明
LightVector平行光射入方向
ShadowSteps阴影的步数
ShadowStepSize步大小
ShadowDensity对阴影密度的额外控制
ShadowThreshold阈值,优化掉小于阈值的计算
Density需要将计算介质吸收的BeersLaw函数(布格-朗伯-比尔)移入内部

老样子,这些可以直接右键粘贴到输入:

((InputName="Tex"),(InputName="XYFrames"),(InputName="NumFrames"),(InputName="MaxSteps"),(InputName="StepSize"),(InputName="LocalCamVec"),(InputName="CurPos"),(InputName="FinalStepSize"),(InputName="LightVector",Input=(Mask=1,MaskR=1,MaskG=1,MaskB=1)),(InputName="ShadowSteps"),(InputName="ShadowStepSize"),(InputName="ShadowDensity"),(InputName="ShadowThreshold"),(InputName="Density"))

可以看到它和相机方向的光线步进很像(其实它才是真正的光线步进不是吗)
现在样子如下:
在这里插入图片描述

现在修改RayMarching的Code,在for的内外加入了shadow部分,且使用新的结果作为返回:

在这里插入图片描述
上图中的代码如下:

// Code...代码呢?急急国王先别急。
// 下一步还有一个小修改,然后给这阶段完整的代码。
// 不然就成了纯凑字数

我们做出了很多修改,具体修改内容都标注在了注释上。
总的来说,我们把阴影的计算合并了进去,在每次采样密度时进行了一次光线的采样
在这里插入图片描述

注意:

  1. LocalCamVecStepSize 是刚才说的“路由”,别漏看现在有个乘法。
  2. RayMarching 的输出不要忘记改为4通道。
  3. 介质吸收的函数已经在 Custom 内实现(因为要写进 for)。

在这里插入图片描述
Done
现在阴影已经可以正确渲染了。目前,光照方向是手动输入的,稍后我们会使用场景中的阳光方向。但在此之前,我们先实现光源颜色。

2.光源颜色

为RayMarching增加一个输入

输入说明
LightColor光源颜色

计算阴影的同时已经计算了光能,因此我们可以在末尾直接乘以颜色:

lightenergy += exp(-shadowdist * ShadowDensity) * cursample * transmittance * LightColor;

修改后的代码:

//创建变量,从0开始累加沿相机方向步进过程中的总密度
float accumdens = 0;

//Shadow部分
//创建变量,透射率和光线的能量
float transmittance =1;
float3 lightenergy = 0;
//基本和相机方向步进一样,但这些都是常量,不需要写进for里
Density *= StepSize;
LightVector *= ShadowStepSize;
ShadowDensity *= ShadowStepSize;
//一个对数来计算阈值,用来判断光线是否还值得计算
float shadowthresh = -log(ShadowThreshold)/ShadowDensity;

//使用 MaxSteps 作为最大步数进行循环,每次循环执行以下操作
for (int i = 0; i < MaxSteps; i++)
{
	// 在当前步进位置进行纹理采样,采样的是 R 通道
    // PseudoVolumeTexture 函数用于伪体积纹理采样,函数需要的参数在括号内传递
    float cursample = PseudoVolumeTexture(Tex, TexSampler, saturate(CurPos), XYFrames, NumFrames).r;

    //Shadow部分
    if(cursample > 0.001)//如果采样位置没有密度,则跳过
    {
        float3 Lpos = CurPos;//Lpos将作为光线步进的起始位置
        float shadowdist = 0;//和之前的accumdens一样,积累阴影
        for(int s = 0; s < ShadowSteps; s++)
        {
            Lpos += LightVector;//移动步进位置
            float Lsample = PseudoVolumeTexture(Tex, TexSampler, saturate(Lpos), XYFrames, NumFrames).r;//采样
            
            //判断是否在框内,不是则直接break退出for
            float3 shadowboxtest = floor( 0.5+ (abs(0.5-Lpos)));
            //float exitshadowbox = shadowboxtest.x + shadowboxtest.y + shadowboxtest.z;
            float exitshadowbox = dot(shadowboxtest,1);//简短的通道相加
            if(shadowdist > shadowthresh || exitshadowbox >= 1) break;

            shadowdist += Lsample;//累计
        }

        //更新样本和光能,算法是BeersLaw函数
        cursample = 1 -exp(-cursample * Density);
        lightenergy += exp(-shadowdist * ShadowDensity) * cursample * transmittance * LightColor;
        transmittance *= 1-cursample;     
    
    }


    // 将当前采样到的密度值累加到总密度中
    // 乘以步长是为了将采样密度与步进距离相匹配
    //accumdens += cursample * StepSize;
    // 为下次循环更新射线位置,沿着相机方向步进
    //CurPos += -LocalCamVec * StepSize;
    //将StepSize放custom外面了
    CurPos += -LocalCamVec;
}

//修复阶梯,在循环后再进行一次额外采样
/* 目前先注释掉这些,这样我们不必每次修改后都改一次这里。等全部完成后,再重新编写这些内容。
CurPos -= -LocalCamVec * StepSize;
CurPos += -LocalCamVec * StepSize * FinalStepSize;
float cursample = PseudoVolumeTexture(Tex, TexSampler, saturate(CurPos), XYFrames, NumFrames).r;
accumdens += cursample * StepSize * FinalStepSize;
*/

//返回累计结果
//return accumdens;
//现在返回
return float4(lightenergy, transmittance);

在这里插入图片描述

在这里插入图片描述
现在我们实现了"光"和"影"
Done

3.将光照方向和颜色与场景匹配

之前的光照方向和颜色都是手动输入的,如果你有特殊的效果实现,这样做刚好。但如果你希望它能够与场景完美融合,能够使用场景信息进行自动化当然是理想选择。

1.有天空大气时

在这里插入图片描述
如果你的场景中包含“SkyAtmosphere”,那么可以使用以下方法:

可以通过 SkyAtmosphereLightDirectionSkyAtmosphereLightIlluminance 分别获取“SkyAtmosphere”的光照方向和颜色。
请注意,SkyAtmosphereLightDirection 需要转换为本地空间。

在这里插入图片描述

2.未使用天空大气时

如果你未使用“SkyAtmosphere”,或者想摆脱对其的依赖,但仍需要Shader与场景融合,则可以使用自定义Custom来直接获取平行光的参数。

新建一个材质函数,命名为 DirectionalLight,并定义两个输出,分别是方向和颜色。为它们分别创建Custom节点,代码如下:

方向:

ResolvedView.DirectionalLightDirection

颜色:

ResolvedView.DirectionalLightColor

函数如下
在这里插入图片描述
在这里插入图片描述

注意:
1.代码可能会随版本变动,为确保未来的兼容性,最好是使用UE自带的
2.代码没有额外参数(如这里使用的)时,光源索引是0
3.同样需要转为本地空间

现在Shader可以自动匹配环境光照了
在这里插入图片描述

3.阴影颜色

现在我们将制作阴影颜色。在此之前,ShadowDensity 是由一个浮点数驱动的,它代表了介质对光的吸收。

现在我们将 ShadowDensity 从浮点值改为三通道的 RGB 颜色,这意味着我们可以针对吸收的波长进行更精细的控制。
在这里插入图片描述
介质对波长的吸收是什么关系?如何通过控制ShadowDensity 调整颜色?

介质对波长的吸收与波长的物理性质有关。一般来说,短波长(蓝色)光会比长波长(红色)光更容易被吸收。这种吸收可以通过调整 ShadowDensity 来控制。

ShadowDensity 现在是一个三通道的 RGB 颜色值,用来表示介质对不同波长的光的吸收程度。每个通道的数值越大,表示对该波长的光吸收越多。

例如,ShadowDensity 值为 8, 16, 32,这意味着:

  • 对红色光的吸收是 8
  • 对绿色光的吸收是 16
  • 对蓝色光的吸收是 32

可以想象,当光线穿过介质时,蓝色光会被大量吸收,绿色光中等程度吸收,而红色光吸收最少。因此,更多的红色光最终会穿透介质进入相机,从而呈现出红色。

通过调整 ShadowDensity 的 RGB 值,你可以控制介质对不同波长光的吸收程度,从而改变最终的颜色表现。

在这里插入图片描述
Done

4.环境光照颜色

到目前为止,我们只处理了单个光源的散射效果。这种方法通常效果不佳,因为如果光源完全被遮挡,或者根本没有主光源,体积阴影区域就会显得很平淡。为了改善这一点,我们需要引入环境光。

但是,环境光照并不是简单地加一个代表环境光线的颜色就能搞定的。实际上,我们需要从垂直方向对介质采样三个额外的偏移样本。这样做可以帮助我们估计出环境光遮蔽的效果,从而让阴影区域显得更加柔和自然。

为RayMarching增加输入

输入说明
AmbientDensity环境光阴影密度
SkyColor光源颜色

在RayMarching里增加三次采样:

在这里插入图片描述

更新后全部代码如下:

//创建变量,从0开始累加沿相机方向步进过程中的总密度
float accumdens = 0;

//Shadow部分
//创建变量,透射率和光线的能量
float transmittance =1;
float3 lightenergy = 0;
//基本和相机方向步进一样,但这些都是常量,不需要写进for里
Density *= StepSize;
LightVector *= ShadowStepSize;
ShadowDensity *= ShadowStepSize;
//一个对数来计算阈值,用来判断光线是否还值得计算
float shadowthresh = -log(ShadowThreshold)/ShadowDensity;

//使用 MaxSteps 作为最大步数进行循环,每次循环执行以下操作
for (int i = 0; i < MaxSteps; i++)
{
	// 在当前步进位置进行纹理采样,采样的是 R 通道
    // PseudoVolumeTexture 函数用于伪体积纹理采样,函数需要的参数在括号内传递
    float cursample = PseudoVolumeTexture(Tex, TexSampler, saturate(CurPos), XYFrames, NumFrames).r;

    //Shadow部分
    if(cursample > 0.001)//如果采样位置没有密度,则跳过
    {
        float3 Lpos = CurPos;//Lpos将作为光线步进的起始位置
        float shadowdist = 0;//和之前的accumdens一样,积累阴影
        for(int s = 0; s < ShadowSteps; s++)
        {
            Lpos += LightVector;//移动步进位置
            float Lsample = PseudoVolumeTexture(Tex, TexSampler, saturate(Lpos), XYFrames, NumFrames).r;//采样
            
            //判断是否在框内,不是则直接break退出for
            float3 shadowboxtest = floor( 0.5+ (abs(0.5-Lpos)));
            //float exitshadowbox = shadowboxtest.x + shadowboxtest.y + shadowboxtest.z;
            float exitshadowbox = dot(shadowboxtest,1);//简短的通道相加
            if(shadowdist > shadowthresh || exitshadowbox >= 1) break;

            shadowdist += Lsample;//累计
        }

        //更新样本和光能,算法是BeersLaw函数
        cursample = 1 -exp(-cursample * Density);
        lightenergy += exp(-shadowdist * ShadowDensity) * cursample * transmittance * LightColor;
        transmittance *= 1-cursample;
        
        //环境光照部分
        shadowdist = 0;//重置一下阴影距离,继续利用它计算光照

        Lpos = CurPos + float3(0,0,0.025);//新位置
        float Lsample = PseudoVolumeTexture(Tex, TexSampler, saturate(Lpos), XYFrames, NumFrames).r;//采样
        shadowdist += Lsample;

        Lpos = CurPos + float3(0,0,0.05);
        Lsample = PseudoVolumeTexture(Tex, TexSampler, saturate(Lpos), XYFrames, NumFrames).r;//采样
        shadowdist += Lsample;

        Lpos = CurPos + float3(0,0,0.15);
        Lsample = PseudoVolumeTexture(Tex, TexSampler, saturate(Lpos), XYFrames, NumFrames).r;//采样
        shadowdist += Lsample;

        lightenergy += exp(-shadowdist * AmbientDensity) *cursample * SkyColor * transmittance;//累计到光

   
    }


    // 将当前采样到的密度值累加到总密度中
    // 乘以步长是为了将采样密度与步进距离相匹配
    //accumdens += cursample * StepSize;
    // 为下次循环更新射线位置,沿着相机方向步进
    //CurPos += -LocalCamVec * StepSize;
    //将StepSize放custom外面了
    CurPos += -LocalCamVec;
}

//修复阶梯,在循环后再进行一次额外采样
/* 目前先注释掉这些,这样我们不必每次修改后都改一次这里。等全部完成后,再重新编写这些内容。
CurPos -= -LocalCamVec * StepSize;
CurPos += -LocalCamVec * StepSize * FinalStepSize;
float cursample = PseudoVolumeTexture(Tex, TexSampler, saturate(CurPos), XYFrames, NumFrames).r;
accumdens += cursample * StepSize * FinalStepSize;
*/

//返回累计结果
//return accumdens;
//现在返回
return float4(lightenergy, transmittance);

在这里插入图片描述
在这里插入图片描述
现在我们的体积有柔和的环境光啦
Done

将环境光的颜色匹配

同样的,如果你有特殊的效果实现,就继续使用SkyColor作为输入。如果你希望它能够与场景完美融合,就做如下步骤:

1.有天空大气时

使用SkyAtmosphereDistantLightScatteredLuminance取得环境光
在这里插入图片描述

2.没有天空大气时
1.SkyLightEnvMapSample

可以使用SkyLightEnvMapSample,沿垂直方向(0,0,-1)采样

在这里插入图片描述

2.ResolvedView.SkyLightColor

创建Custom,并使用

ResolvedView.SkyLightColor

获取天光光源颜色
在这里插入图片描述
要注意它获取的是“光源颜色”
在这里插入图片描述

关于匹配场景颜色的Tip:
预览窗没有“SkyAtmosphere”
因此不依赖“SkyAtmosphere”的方案,可以在材质的预览窗口中预览
在这里插入图片描述
当场景有“SkyAtmosphere”,则最好使用依赖“SkyAtmosphere”的方案,能更贴合场景实际的效果

本章总结

代码

float accumdens = 0;

//Shadow部分
float transmittance =1;
float3 lightenergy = 0;

Density *= StepSize;
LightVector *= ShadowStepSize;
ShadowDensity *= ShadowStepSize;

float shadowthresh = -log(ShadowThreshold)/ShadowDensity;

//光线步进
for (int i = 0; i < MaxSteps; i++)
{
    float cursample = PseudoVolumeTexture(Tex, TexSampler, saturate(CurPos), XYFrames, NumFrames).r;

    //Shadow部分
    if(cursample > 0.001)
    {
        float3 Lpos = CurPos;
        float shadowdist = 0;
        for(int s = 0; s < ShadowSteps; s++)
        {
            Lpos += LightVector;
            float Lsample = PseudoVolumeTexture(Tex, TexSampler, saturate(Lpos), XYFrames, NumFrames).r;//采样
            
            float3 shadowboxtest = floor( 0.5+ (abs(0.5-Lpos)));
            float exitshadowbox = dot(shadowboxtest,1);//三通道求和
            if(shadowdist > shadowthresh || exitshadowbox >= 1) break;

            shadowdist += Lsample;//累计
        }

        //更新样本和光能,BeersLaw
        cursample = 1 -exp(-cursample * Density);
        lightenergy += exp(-shadowdist * ShadowDensity) * cursample * transmittance * LightColor;
        transmittance *= 1-cursample;
        
        //环境光照
        shadowdist = 0;

        Lpos = CurPos + float3(0,0,0.025);//新位置
        float Lsample = PseudoVolumeTexture(Tex, TexSampler, saturate(Lpos), XYFrames, NumFrames).r;
        shadowdist += Lsample;

        Lpos = CurPos + float3(0,0,0.05);
        Lsample = PseudoVolumeTexture(Tex, TexSampler, saturate(Lpos), XYFrames, NumFrames).r;
        shadowdist += Lsample;

        Lpos = CurPos + float3(0,0,0.15);
        Lsample = PseudoVolumeTexture(Tex, TexSampler, saturate(Lpos), XYFrames, NumFrames).r;
        shadowdist += Lsample;

        lightenergy += exp(-shadowdist * AmbientDensity) *cursample * SkyColor * transmittance;

   
    }

    CurPos += -LocalCamVec;
}
return float4(lightenergy, transmittance);

蓝图

在这里插入图片描述

结果

Done
这章我们完成了Shader的光照部分,先看看成果:

在这里插入图片描述

画饼

下章我们继续制作“阴影投射”的部分

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2162427.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

springboot 接口接收及响应xml数据

1.实体类 import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement;XmlRootElement XmlAccessorType(XmlAccessType.FIELD) // …

gitlab添加CI自动测试

gitlab 配置 yml 语法 ** https://docs.gitlab.com/17.3/ee/ci/yaml/index.html ** 添加.gitlab-ci.yml文件并配置 &#xff1a; ## 定义几个阶段 stages: # List of stages for jobs, and their order of execution- build- test- deploy- ## 示例job &#xff0…

Linux 简易shell编写

shell shell是壳&#xff0c;外壳的意思&#xff0c;一般我们使用linux系统有用图形化界面的也有使用命令行界面的&#xff0c;这两个都是一种shell&#xff0c;以命令行为例&#xff1a; 如图这个就是我这里的命令行格式&#xff0c;在$符后面写的就是执行的指令&#xff0c;…

携手SelectDB,观测云实现性能与成本的双重飞跃

在刚刚落下帷幕的2024云栖大会上&#xff0c;观测云又一次迎来了全面革新。携手SelectDB&#xff0c;实现了技术的飞跃&#xff0c;这不仅彰显了观测云在监控观测领域的技术实力&#xff0c;也预示着我们可以为全球用户提供更加高效、稳定的数据监测与分析服务。这一技术升级&a…

同等学力英语用什么app背单词

同等学力申硕的意义和作用 授予同等学力人员硕士学位是国家为同等学力人员开辟的获得学位的渠道&#xff0c;对于在职人员业务素质的提高和干部队伍建设起到积极作用。它为那些没有传统学历背景但具有相应学术水平的人员提供了获取学位的机会&#xff0c;有助于提升他们的职业竞…

llamafactory0.9.0微调qwen2.5

llama_factory微调QWen1.5_llama factory qwen-CSDN博客文章浏览阅读2.9k次,点赞36次,收藏10次。本文介绍了如何使用LLaMA-Factory微调Qwen1.5模型,包括1.8B和0.5B版本的训练细节。在数据、训练、LORA融合及推理等方面进行了探讨,同时也分享了微调后模型在不同任务上的表现…

Linux usb主机控制器HC阅读

intel的UHCI 一种usb主机控制器的接口规范,遵守它的硬件称为UHCI主机控制器,Linux中,把这种硬件叫做HC,host controller,与之对应的软件,叫做HCD,hc driver, depends on usb & pci: 它的内核软件模块代码是uhci-hcd.c uhci_hcd_init初始化开始: usb_disable函数:…

408选择题笔记|自用|随笔记录

文章目录 B树&#xff1a;访问节点建堆&#xff01;将结点插入空堆广义指令求每个子网可容纳的主机数量虚拟内存的实现方式文件目录项FCB和文件安全性管理级别索引文件三种存取方式及适用器件成组分解访问磁盘次数 C语言标识符 最小帧长物理传输层介质 局域网&广域网考点总…

基于BeagleBone Black的网页LED控制功能(flask+gpiod)

目录 项目介绍硬件介绍项目设计开发环境功能实现控制LED外设构建Webserver 功能展示项目总结 &#x1f449; 【Funpack3-5】基于BeagleBone Black的网页LED控制功能 &#x1f449; Github: EmbeddedCamerata/BBB_led_flask_web_control 项目介绍 基于 BeagleBoard Black 开发板…

ubuntu 安裝 Poetry 示例

ubuntu 安裝 Poetry 示例 一、前言 poetry 是一个命令行工具&#xff0c;安装之后就可以使用 poetry 指令。可以将其安装全局环境或者是虚拟环境&#xff0c;我推荐安装在全局环境&#xff0c;这样在后面使用时不需要单独激活虚拟环境。 &#xff08;1&#xff09;安装 Poet…

ubuntu22.04磁盘挂载(多磁盘和单磁盘挂载)

多磁盘挂载到同一个目录 # 如果没有安装逻辑卷管理系统工具sudo apt install lvm2 # 查看磁盘分区sudo fdisk -l # 新建物理卷sudo pvcreate /dev/nvme0n1 /dev/nvme1n1 # 查看现有物理卷信息sudo pvdisplay # 新建物理卷sudo vgcreate dnyjy_vg /dev/nvme0n1 /dev/nvme1n1…

STM32F407之超声波模块使用

#include "sys.h" #include "delay.h" #include "usart.h" #include "includes.h" #include "HC_SR04.h"int main() {OS_ERR err;//错误uart_init(9600);//串口初始化//超声波初始化HC_SR04();//OS初始化 他是第一个运行的函…

【VUE3.0】动手做一套像素风的前端UI组件库---Message

目录 引言自己整一个UI设计稿代码编写1. 设计信息窗口基础样式2. 设置打开和关闭的方法3. 编写实例化组件的js文件4. 看下最终效果5. 组件完整代码6. 组件调用方式 总结 引言 本教程基于前端UI样式库 NES.css 的UI设计&#xff0c;自行研究复现。欢迎大家交流优化实现方法~ 此次…

Flask建立的Web网站的can‘t open file C_Program问题的分析

前言 想自己制作一个Web网站对接私有化的大模型。考虑到私有化的大模型都是Python编写为主的。所以&#xff0c;就打算用Python建立一个Web网站。目前&#xff0c;选定的是Flask框架&#xff08;Python3.11&#xff09;。但是&#xff0c;用PyCharm进行调试的时候却出现了问题。…

努比亚z17努比亚NX563j原厂固件卡刷包下载_刷机ROM固件包下载-原厂ROM固件-安卓刷机固件网

努比亚z17努比亚NX563j原厂固件卡刷包下载_刷机ROM固件包下载-原厂ROM固件-安卓刷机固件网 统版本&#xff1a;官方软件作者&#xff1a;热心网友rom大小&#xff1a;911MB发布日期&#xff1a;2018-12-23 努比亚z17努比亚NX563j原厂固件卡刷包下载_刷机ROM固件包下载-原厂RO…

【工作流集成】springboot+vue集成工作流activiti,flowable,智能审批系统,集成方案(源码)

基于Javavue开发的智能审批系统&#xff0c;低代码平台 软件资料清单列表部分文档清单&#xff1a;工作安排任务书&#xff0c;可行性分析报告&#xff0c;立项申请审批表&#xff0c;产品需求规格说明书&#xff0c;需求调研计划&#xff0c;用户需求调查单&#xff0c;用户需…

多智能体笔记本专家系统:集成CrewAI、Ollama和自定义Text-to-SQL工具

在这个项目中&#xff0c;我们的目标是创建一个由多智能体架构和本地大语言模型&#xff08;LLM&#xff09;驱动的个人笔记本电脑专家系统。该系统将使用一个SQL数据库&#xff0c;包含有关笔记本电脑的全面信息&#xff0c;包括价格、重量和规格。用户可以根据自己的特定需求…

数据结构3——双向链表

在上篇文章数据结构2——单链表中&#xff0c;我们了解了什么是链表&#xff0c;以及单链表的实现&#xff0c;接着上篇文章&#xff0c;本篇文章介绍一下比单链表更常用的链表——双向循环链表。 目录 1.双向链表的实现 1.1节点 1.2 初始化节点 1.3 动态开辟新节点 1.4 遍…

美团中间件C++一面-面经总结

1、TCP和UDP 的区别&#xff1f; 速记标识符&#xff1a;连靠刘墉宿营 解释&#xff1a; 面向连接vs无连接 可靠传输vs不保证可靠 字节流vs报文传输 拥塞控制流量控制vs无 速度慢vs速度快 应用场景自己描述 2、服务端处于close wait是什么情况&#xff0c;是由什么造成的&…

【算法】模拟:(leetcode)495.提莫攻击(easy)

目录 题目链接 题目介绍 解法 代码 题目链接 495. 提莫攻击 - 力扣&#xff08;LeetCode&#xff09; 题目介绍 解法 模拟 分情况讨论。 当寒冰再次中毒时&#xff0c;上次「中毒」是否已经结束。 ​ 当上次中毒已经结束了&#xff0c;那么上次「中毒」维持的时间就是 …