【Unity SRP】实现基础的Temporal AA(未完)

news2024/11/18 13:30:05

写在前面

【技术美术图形部分】简述主流及新的抗锯齿技术,花了点时间盘点了一些主流AA技术,再在SRP下的URP管线中实现一下目前游戏用得比较多的TAA。参考Unity的TAA(比较容易懂)以及sienaiwun的实现思路,也参考了很多文章(主要是这位大佬),可以说这次实现其实是对目前能找得到的实现思路的大融合!

本文叙述会尽可能多地体现自己的理解,也算是一次学习。

前置知识

SRP?

随意找了篇介绍SRP的,可以看看:Unity URP/SRP 渲染管线浅入深出【匠】

全称,可编程渲染管线。

相信很多人跟我一样,入门阶段一直是基于Unity内置管线(Build-in)开展学习,直到后面接触到了HDRP(高清渲管线)和URP(通用管线),才发现要学的东西还有好多,,,而且不止这些,甚至还要学习和这些光栅化管线完全不同的光追管线,,,自以为“入门了”但其实人还在坑里呢,现在逃还来得及!!!(不是)

回到正题!其实SRP并不可怕,只需要知道有了它我们可以通过C#脚本调用封装的API来创建自定义管线,渲染流程可自定和修改——就能实现管线定制了,没有Build-in那么死板。URP和HDRP就是两个Unity基于SRP为我们拓展的管线模板,URP难就难在一些函数名称完全被更改,还需要重写函数。

我学习SRP的思路一直都是以实现某种功能为目标,尝试在SRP搭建管线(之前做过URP下实现毛玻璃),以及这一篇SRP下自定义管线实现TAA。刚入门啥都不会的跟着官方阿b发的视频做一次:URP系列教程 | 如何使用Scriptable Renderer Feature来自定义后处理效果

说得挺乱的,总之一句话:SRP和固定管线绝对不是完全割裂的,遇到新管线不需要害怕,学就完事了。

Volume组件

这部分可以看看:如何扩展Unity URP的后处理Volume组件

实现TAA还需要了解URP的Volume组件——URP实现屏幕后处理的核心组件,我们可以通过Volume组件下的Add Override添加屏幕后处理效果:

我们如果想实现TAA,也需要添加一个类似“TAA开关”的东西,意味着我们需要去拓展Volume Overide,简单来讲就是再写一个TAA,cs的实现脚本。

TAA实现思路

强烈建议看完这个篇文章:DX12渲染管线(2) - 时间性抗锯齿(TAA)


RenderFeature实现TAA

TemporalAA需要处理静止和动态画面,而动态需要解决残影问题,参考大部分文章的思路,本文也将通过RendererFeature去实现TAA,实现过程的话需要以下三个部分,

  • 处理渲染管线
  • 处理Global Volume
  • Shader实现TAA混合

整体框架如下:

截图自 GDC Vault - Temporal Reprojection Anti-Aliasing in INSIDE

大概思路:首先,我们需要创建TAARendererFeature.csRenderer

再把RendererFeature给Add一下Renderer, 

然后在这个RenderFeature里生成相机抖动值,通过一个TAARenderPass.cs实现最终的画面渲染 。还需要一个专门的抖动相机的Pass,CameraSettingPass.cs,用于改变相机的透视变换矩阵(后面会讲到)。此外还需要给Global Volume加上自定义的TAA,那就又要创建一个TAA.cs文件,作为一个TAA开关。

于是细化的话,步骤主体包括以下脚本

  • 一个RendererFeature类:TAARendererFeature.cs
  • 一个RenderPass类:TAARenderPass.cs
  • 一个RenderPass类:CameraSettingPass.cs
  • 与Global Volume搭接:TAA.cs

对框架做一个简单的概述后,开始从实现静态场景的TAA出发,一点一点写脚本和shader:

1 静态场景

静态场景理解起来很容易,需要在下一帧渲染时将采样上一帧的子像素点偏移,确保每一帧偏移不同位置,最终取全部帧的平均值,整个过程涉及到以下两点,

  • 抖动采样——如何进行采样偏移?
  • 历史帧混合——如何混合历史帧得到平均值?

1.1 抖动采样

采样方法

在采样点个数上,TAA和传统超采样的想法是一致的——生成更多的采样点以获得更加细致的采样效果,但因为是逐帧抖动再取所有的均值,除了每一帧的空间上采样还涉及逐帧的时间上采样,所以采样方法也涉及到两个方面

  • 空间上——我们希望采样点不能太过随机,不然总会有堆叠的情况
  • 时间上——我们希望逐帧也能够均匀分布

最后我选取的采样方法参考了这篇文章:采用Stratified sampler进行空间上的采样(有时间的话还可以拿box和泊松给它对比一下),时间上选择了前8个halton(2,3)进行相机抖动:

截图自 EPIC UE4
截图自 NVIDA TXAA

先看看如何生成Halton序列吧,我并没有单独设一个Halton数列的Class,而是参考大佬思路囊括进了RendererFeature中,浅看一下:

    // 抖动用的Halton
    // 这里直接照搬大佬设置的函数了,没多余时间细究了orz
    private float HaltonSeq(int prime, int index = 1/* NOT! zero-based */)
    {
        float r = 0.0f;
        float f = 1.0f;
        int i = index;
        while (i > 0)
        {
            f /= prime;
            r += f * (i % prime);
            i = (int)Mathf.Floor(i / (float)prime);
        }
        return r;
    }

上述代码的生成Halton序列的方法中i指的就是序列的第几位数,再根据Halton数列计算抖动值,实现是在TAARendererFeature的AddRenderPasses()方法中实现,其中haltonIndex初始值为0:

// 获取Offset值
            if(++haltonIndex >= max_SampleCount)
            {
                haltonIndex = 0;
            }
            haltonIndex = (haltonIndex + 1) & 1023;
            Vector2 offset = new Vector2(
                HaltonSeq(2, haltonIndex + 1) - 0.5f,
                HaltonSeq(3, haltonIndex + 1) - 0.5f);

【偏题】采样在图形学中是十分常见的部分,随机数影响着样本的分布,我参考的文章中提到了这篇文章:低差异序列(一)- 常见序列的定义及性质,打开新世界大门,这里展示出来,也给自己码一下吧,有机会一定拜读。

Jitter视锥体

我们需要逐帧偏移采样点,这个offset需要发生在几何阶段之后的光栅化阶段,也就是屏幕映射之后。上述的“相机抖动”并不是偏移相机的位置,而是视锥中心动、基于视锥底部偏移一定的Offset,还原到世界空间就是下图:

图源 GDC Vault - Temporal Reprojection Anti-Aliasing in INSIDE

所以说,我们改动的其实是Project矩阵,搬运其他大佬对偏移如何转化为修改矩阵的解释:

截图自 Unity TAA实现杂记 | Blurred code

实现这个点,我们需要在TAARendererFeature中封装一个变换矩阵函数:

// 变换矩阵
    private Matrix4x4 GetJitteredProjectionMatrix(Camera camera, Vector2 offset, Vector2 jitterIntensity)
    {
        Matrix4x4 originalProjMatrix = camera.nonJitteredProjectionMatrix;

        float near = camera.nearClipPlane;
        float far = camera.farClipPlane;
        Vector2 matrixOffset = offset * new Vector2(1f / camera.pixelWidth, 1f / camera.pixelHeight) * jitterIntensity;
        //[row, column]
        originalProjMatrix[0, 2] = matrixOffset.x;
        originalProjMatrix[1, 2] = matrixOffset.y;
        return originalProjMatrix;
    }

接下来就是在渲染场景前,通过CommandBuffer.SetViewProjectionMatrices修改相机的VP矩阵了,这部分在CameraSettingPass里实现,

// 修改用于渲染的VP矩阵
            cmd.SetViewProjectionMatrices(cameraData.camera.worldToCameraMatrix, m_TAAData.jitteredProj);

这里的m_TAAData是额外的一个用于传递参数的类,既然这里涉及了,那也把TAAData的脚本展示一下:

脚本TAAData.cs

其中jitteredProj就是在TAARendererFeature中定义的,把变换矩阵传递给CameraSettingPass

public class TAAData
{
    public Vector2 offset;
    public Vector2 lastOffset; // 储存上一帧的Offset
    public Matrix4x4 lastProj;
    public Matrix4x4 lastView;
    public Matrix4x4 jitteredProj;
    public Matrix4x4 currentView;

    public void Initialize()
    {
        offset = Vector2.zero;
        lastOffset = Vector2.zero;
        lastProj = Matrix4x4.identity;
        lastView = Matrix4x4.identity;
        jitteredProj = Matrix4x4.identity;
        currentView = Matrix4x4.identity;
    }
}

再把计算得到的Offset值、变换矩阵、传递给TAAData就行了,这个部分还需要结合接下来要说1.2 混合历史帧。

脚本CameraSettingPass.cs

至此可以展示一下整个CameraSettingPass的样子了:

public class CameraSettingPass : ScriptableRenderPass
{
    // Profiling上显示
    ProfilingSampler m_ProfilingSampler;
    string m_ProfilerTag = "CameraSetting";
    TAAData m_TAAData;

    private TAARendererFeature.TAAData taaData;

    internal CameraSettingPass()
    {
        renderPassEvent = RenderPassEvent.BeforeRenderingOpaques;
    }

    // 非RenderPass的重写函数,是我们自定义的,用以传递参数
    // 这里传递的是TAAData
    internal void Setup(TAAData data)
    {
        m_TAAData = data;
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        CommandBuffer cmd = CommandBufferPool.Get(m_ProfilerTag);

        using (new ProfilingScope(cmd, m_ProfilingSampler))
        {
            context.ExecuteCommandBuffer(cmd);
            cmd.Clear();
            CameraData cameraData = renderingData.cameraData;
            // 修改用于渲染的VP矩阵
            cmd.SetViewProjectionMatrices(cameraData.camera.worldToCameraMatrix, taaData.jitteredProj);
        }
        // 执行
        context.ExecuteCommandBuffer(cmd);
        // 回收
        CommandBufferPool.Release(cmd);
    }
}

脚本TAARendererFeature.cs

1.2 历史帧混合

混合方式

已经实现了相机抖动,最终我们还需要将不同采样点渲染的画面混合,也就是混合N个历史帧。

那么采取什么样的混合方式呢?

最最简单的方法就是直接取均值,(第8帧+前7帧结果)/8,这样开销很大。换一种思路:取当前帧的结果与前一帧的结果按一定比例混合,有递归那味儿了!

那么如何实现?按什么比例?为什么能这样按比例混合?还是搬运其他大佬的解释:

截图自 参考文章

这里混合系数\alpha对效果也是有影响的。

RT储存历史帧

为了储存历史帧,需要一张能长期使用的RT——Motion Vector RT,Motion Vector是一个RGHalf(或RGFloat)的双通道贴图,储存的是当前像素点与上一帧的区别。通过这个RT找到上一帧的当前像素信息,混合。

2 动态场景

静态场景直接supersampling就可以解决了,那动态场景涉及到两个方面,

  • 摄像机移动
  • 场景中物体移动

一旦画面有移动,场景中某点位置会发生变化。如果继续用原屏幕位置混合,会出现问题,这个时候怎么实现抗锯齿?

这牵扯到很多技术,我们一步一步来。

2.1 Reprojection

考虑镜头移动:

这里就要用到Reprojection方法:渲染当前帧(N位置)时需要乘以当前帧的VP矩阵的逆,再乘以上一帧(N-1位置)的VP矩阵,齐次除法变换到上一帧的裁剪空间,就知道当前帧的N位置(UV)在上一帧画面中的位置(lastUV)了。这一步体现在我们最后的TAAShader的fragment shader里:

// Reprojeciton
float4 worldPos = mul(UNITY_MATRIX_I_VP, positionNDC);
worldPos /= worldPos.w;
float4 lastPositionCS = mul(_LastViewProj, worldPos);
float2 lastUV = lastPositionCS.xy / lastPositionCS.w;
lastUV = lastUV * 0.5 + 0.5;

消除Jitter影响

还需要还原抖动,不然画面会模糊,这时TAAData里储存的lastOffset就派上用场啦!如下(直接复制的格式乱套了orz):

float2 sampleUV = input.texcoord;
                float2 currentOffset = _TAAOffsets.xy; // 上一帧的Offset
                float2 lastOffset = _TAAOffsets.zw; // 当前帧Offset

                float2 unJitteredUV = sampleUV - 0.5 * currentOffset; // 还原Offset
                ...

                // 采样当前深度贴图
                float depthTexture = _CameraDepthTexture.SampleLevel(sampler_PointClamp, unJitteredUV, 0).r;
                float4 positionNDC = float4(sampleUV * 2 - 1, depthTexture, 1);
                #if UNITY_UV_STARTS_AT_TOP
                positionNDC.y = -positionNDC.y;
                #endif

                // Reprojeciton
                float4 worldPos = mul(UNITY_MATRIX_I_VP, positionNDC);
                worldPos /= worldPos.w;
                float4 lastPositionCS = mul(_LastViewProj, worldPos);
                float2 lastUV = lastPositionCS.xy / lastPositionCS.w;
                lastUV = lastUV * 0.5 + 0.5;
                // 用当前帧在上一帧(累积帧)的位置采样累积帧画面
                float3 accumTexture = _AccumTexture.SampleLevel(sampler_LinearClamp, lastUV, 0).rgb;

但是这有个问题,,只是简单的使用上一帧的VP矩阵进行reprojection,仅适用于静态场景下的动态摄像机。对于动态场景下的动态摄像机该怎么办?

2.2 Neighborhood Clipping

镜头移动还要考虑一种情况:遮挡问题,如果不考虑遮挡,会出现残影:

下图解释了这个现象出现的原因: 

由于我们直接混合了历史帧,导致上一帧被遮挡的东西这一帧突然出现,或者这一帧本来应该有的东西被遮挡。

解决这一问题,基于邻近像素色彩的 Neighborhood Clamping 是目前比较主流的 TAA 历史帧约束方案:就是限制历史采样的颜色范围,把历史帧采样结果clamp到一个范围(AABB给它包围起来)。

进一步优化:NIVIDA又提出了更好的方法,Variance clipping,缩小了AABB的尺寸。具体方法详见DX12渲染管线(2) - 时间性抗锯齿(TAA)和在 Unity SRP 实现 Temporal Anti-aliasing,理论方面不太想赘述了,,直接上代码:

            float3 clip_aabb(float3 aabb_min, float3 aabb_max, float3 avg, float3 input_texel)
            {
                // clip to center:
                float3 p_clip = 0.5 * (aabb_max + aabb_min);
                float3 e_clip = 0.5 * (aabb_max - aabb_min) + FLT_EPS;
                
                float3 v_clip = input_texel - p_clip;
                float3 v_unit = v_clip / e_clip;
                float3 a_unit = abs(v_unit);
                float ma_unit = max(a_unit.x, max(a_unit.y, a_unit.z));

                if (ma_unit > 1.0){
                    return p_clip + v_clip / ma_unit;
                } else{
                    return input_texel; // 在AABB里
                }
            }

Variance clipping:

参考了DX12渲染管线(2) - 时间性抗锯齿(TAA)的思路:

                // Variance clip
                float3 m1 = 0, m2 = 0;
                for(int k=0; k<9; k++) {
                    float3 c = _MainTex.Sample(sampler_PointClamp, unJitteredUV, kOffsets3x3[k]);
                    m1 += c;
                    m2 += c * c;
                }
                float3 mu = m1 / 9;
                // 估算的 sigma
                // 关于正确的sigma可以参考:https://gist.github.com/BlurryLight/145131dbacac34345908c529a3488e8f
                float3 sigma = sqrt(abs(m2 / 9 - mu * mu));

                #define VarianceClipGamma 1.0F
                float3 minc = mu - VarianceClipGamma * sigma;
                float3 maxc = mu + VarianceClipGamma * sigma;

                prevColor = ClipAABB(minc, maxc, prevColor, mu);

其中,

            float du = _TextureSize.z;
            float dv = _TextureSize.w;

            float2 kOffsets3x3[9] =
            {
                float2(-du, -dv),
                float2(0, -dv),
                float2(du, -dv),
                float2(-du, 0),
                float2(0, 0),
                float2(du, 0),
                float2(-du, dv),
                float2(0, dv),
                float2(du, dv)
            }

2.3 Fluckering 高光闪烁问题

镜头不动的时候,会有高光(高频着色区域)闪烁问题。

这个问题其实我也只是复述了各大文章里提到的,其实自己并没有真的看到过,计划给他实现之后看看能不能贴个闪烁的图出来吧。

参考

大佬文章

在Unity SRP中实现TAA效果 | ZZNEWCLEAR13

在 Unity SRP 实现 Temporal Anti-aliasing - 知乎 (zhihu.com)

Unity Temporal AA的改进与提高 - 知乎 (zhihu.com)

Unity TAA实现杂记 | Blurred code

Raphael2048/AntiAliasing (github.com)

DX12渲染管线(2) - 时间性抗锯齿(TAA) - 知乎 (zhihu.com)

处理方案

EPIC:TAA

在SIGGRAPH2014上分享了UE4的TAA抗锯齿技术:

High Quality Temporal Supersampling

NIVIDA:TXAA

GDC2016上分享了TXAA,是优化版的TAA吧,解决了一些TAA

Slide 1 (nvidia.cn)

PLAYDEAD

GDC Vault - Temporal Reprojection Anti-Aliasing in INSIDE

PlayDead提供了TAA源码

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

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

相关文章

OpenCV转换HDR图像与源码分析

我们常见的图像位深一般是8bit&#xff0c;颜色范围[0, 255]&#xff0c;称为标准动态范围SDR(Standard Dynamic Range)。SDR的颜色值有限&#xff0c;如果要图像色彩更鲜艳&#xff0c;那么就需要10bit&#xff0c;甚至12bit&#xff0c;称为高动态范围HDR(High Dynamic Range…

Docker部署ES集群、kibana、RabbitMq和chrome安装elasticsearch-head插件

文章目录 [toc] 1.安装ES集群和kibana1.1安装ES集群1.1.1 准备挂载目录1.1.2 准备配置文件1.1.3 启动命令1.1.3.0 启动前设置系统环境变量1.1.3.1 Windows10环境启动命令1.1.3.2 Linux环境启动命令 1.2安装kibana1.2.1 准备挂载目录1.2.2 准备配置文件1.2.3 启动命令1.2.3.1 Wi…

Spring IOC基于XML和注解管理Bean(一)

Spring IOC基于XML和注解管理Bean&#xff08;二&#xff09; 文章目录 1、IoC容器1.1、控制反转&#xff08;IoC&#xff09;1.2、依赖注入1.3、IoC容器在Spring的实现 2、基于XML管理Bean2.1、搭建模块spring-first2.2、实验一&#xff1a;获取bean①方式一&#xff1a;根据i…

过滤器和拦截器实现

说明&#xff1a;当用户未经登录&#xff0c;直接访问后台网址时&#xff0c;为了避免可以直接访问后台内容&#xff0c;就需要使用过滤器或拦截器将此类请求在服务器响应数据之前做核对&#xff0c;如果未登录&#xff0c;则驳回请求&#xff0c;返回登录页面&#xff0c;如果…

PyQt5桌面应用开发(20):界面设计结果自动测试(一)

本文目录 PyQt5桌面应用系列PyQt5的测试驱动开发&#xff08;Test-Driven Development&#xff0c;TDD&#xff09;QTestUI动作函数信号测试 最平凡的例子unittest框架总结 PyQt5桌面应用系列 PyQt5桌面应用开发&#xff08;1&#xff09;&#xff1a;需求分析 PyQt5桌面应用开…

【Apache Pinot】简单聊聊前面没讲的 Deep Store 和 Cluster

背景 前面3篇文章讲解了 Pinot 用的最多的几个组件&#xff0c;现在就聊最后剩下的两个&#xff0c;一个是 Cluster&#xff0c;另外一个就是 Deep Store。 Cluster 其实 Cluster 比较简单&#xff0c;就是一个概念的集合&#xff0c;他说有 Server&#xff0c;Broker 和 Co…

代码随想录算法训练营第五十六天 | 力扣 583. 两个字符串的删除操作, 72. 编辑距离

583. 两个字符串的删除操作 题目 583. 两个字符串的删除操作 给定两个单词 word1 和 word2 &#xff0c;返回使得 word1 和 word2 相同所需的最小步数。 每步 可以删除任意一个字符串中的一个字符。 解析 1.确定dp数组&#xff08;dp table&#xff09;以及下标的含义 …

学会这个Python库,做接口测试不是手拿把掐吗?

我们在做接口测试时&#xff0c;大多数返回的都是json属性&#xff0c;我们需要通过接口返回的json提取出来对应的值&#xff0c;然后进行做断言或者提取想要的值供下一个接口进行使用。 但是如果返回的json数据嵌套了很多层&#xff0c;通过查找需要的词&#xff0c;就很不方便…

三、Typora软件的介绍及安装

1、Typora软件的介绍 (1)Typora时一款Markdown编辑器和阅读器。 (2)Typora使用起来十分简洁&#xff0c;十分方便&#xff0c;可用于记录日常的笔记等。 (3)Markdown 是一种轻量级标记语言&#xff0c;它允许人们使用易读易写的纯文本格式编写文档。 2、Typora软件的安装 …

都说未来AI测试辅助自动化测试,难道手工测试真的要被淘汰了吗?

目录 前言 AI测试的迷思 第一个问题&#xff1a;AI辅助测试真的能用吗&#xff1f; 第二个问题&#xff1a;AI辅助测试已经发展到什么程度了&#xff1f; 第三个问题&#xff1a;哪些软件系统能用AI辅助测试&#xff1f; 总结 总结&#xff1a; 前言 近年来&#xff0c;…

FPGA实现简易的自动售货机模型

文章目录 前言一、系统设计1、模块框图2、状态机框图3、RTL视图 二、源码1.蜂鸣器驱动模块2.按键消抖模块3、PWM模块4、sale_goods模块(状态机部分)5、数码管驱动模块6、Sales(顶层模块) 三、效果四、总结五、参考资料 前言 环境&#xff1a; 1、Quartus18.1 2、vscode 3、板子…

华为OD机试 JavaScript 实现【简单密码】【牛客练习题 HJ21】,附详细解题思路

一、题目描述 现在有一种密码变换算法。 九键手机键盘上的数字与字母的对应&#xff1a; 1--1&#xff0c; abc--2, def--3, ghi--4, jkl--5, mno--6, pqrs--7, tuv--8 wxyz--9, 0--0&#xff0c;把密码中出现的小写字母都变成九键键盘对应的数字&#xff0c;如&#xff1a;a …

Python实现面向对象版学员管理系统

如有错误&#xff0c;敬请谅解&#xff01; 此文章仅为本人学习笔记&#xff0c;仅供参考&#xff0c;如有冒犯&#xff0c;请联系作者删除&#xff01;&#xff01; 1.1需求分析 1.1.1使用面向对象编程思想完成学员管理系统的开发&#xff0c;具体如下&#xff1a; 系统要求…

城镇供水产销差问题分析与对策

城镇自来水与其它商品的经营活动一样存在着产销差&#xff0c;产销差的高低&#xff0c;直接影响着供水企业的经济效益。供水企业的经营活动中不单考虑企业的经济效益&#xff0c;还要考虑社会效益。产销差是客观存在的&#xff0c;造成产销差的原因是多样的&#xff0c;复杂的…

初探图神经网络——GNN

title: 图神经网络(GNN) date: tags: 随笔知识点 categories:[学习笔记] 初探图神经网络(GNN) 文章来源&#xff1a;https://distill.pub/2021/gnn-intro/ 前言&#xff1a;说一下为什么要写这篇文章&#xff0c;因为自己最近一直听说“图神经网络”&#xff0c;但是一直不了…

【LeetCode】24.两两交换链表中的节点

24.两两交换链表中的节点&#xff08;中等&#xff09; 方法一&#xff1a;递归 思路 代码 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), n…

聊一聊mysql的MVC

技术主题 在mysql世纪使用中&#xff0c;经常涉及到MVCC的概念&#xff08;Multi-Vsersion Concurrency Control&#xff09;&#xff0c;即多版本并发控制&#xff0c;一种并发控制方法&#xff0c;根本目的是主为了提升数据库的并发性能。 mvcc为什么产生 数据库最原生的锁…

解开索引迷局:聚簇索引与非聚簇索引的差异大揭秘!

大家好&#xff0c;我是小米&#xff01;今天我们来聊一聊数据库中的索引&#xff0c;具体地说就是聚簇索引和非聚簇索引。这两者在数据库中扮演着重要的角色&#xff0c;对于我们理解数据库的存储和查询机制非常有帮助。下面就让我来给大家详细解释一下它们的区别吧&#xff0…

为不同的调制方案设计一个单载波系统(映射器-信道-去映射器)(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

RPC介绍

RPC介绍 1 介绍1.1 概述1.2 RPC的分裂发展 2 历史发展1969年11月&#xff0c;ARPAnet 开始建立。1974年&#xff1a;Jon Postel 和 Jim White发表了RFC6741975年&#xff1a;RFC684 作为RFC 674 的注释发表&#xff0c;对RFC 674 的争议进行回复。1976年&#xff1a;RFC 707 发…