Global Illumination_Exponential Variance Shadow Maps(EVSM)

news2024/9/25 13:25:21

最近工程中需要集成高质量阴影(效率、效果),介于项目非循环渲染所以CSM无法使用,但动态建模中还需要快速增删改场景,阴影还必须重新生成,奈何之前简单SM+PCF无法满足效率、效果要求,于是调研RVT等软件,发现其采用的为单帧EVSM算法,遂集成实现。
想要了解Exponential Variance Shadow Maps(EVSM),就需要先了解Variance Shadow Map(VSM)算法,想要了解VSM,就需要知道常规的Shadow Map(SM算法),本文便不再赘述SM了,期间也不再赘述中间优化算法Layered Variance Shadow Maps(LVSM)。
首先我们来看一下EVSM算法的优点吧,毕竟算法有优势我们才会优先选择集成到项目嘛。

先来看一张EVSM原文里的效果对比(1080卡),详见下参考文章链接:https://www.martincap.io/project_detail.php?project_id=9
在这里插入图片描述

首先VSM相对于SM的优缺点如下:

  • 优点: 在处理软阴影时效率更快;没有shadow acne现象
  • 缺点: 需要额外通道来记录深度平方;高方差区域会产生漏光

其次EVSM相对于VSM的优缺点如下:

  • 优点: 最大程度上减缓漏光问题
  • 缺点: 极端情况下,仍然会有artifacts;会占用较多带宽(EVSM4)

首先我们来看一下VSM。多说一句Demo主要参照http://mynameismjp.wordpress.com/大神介绍,某些汪汪不想看的可以出门右拐,求别再喷粪嗷嗷,感谢合作。

一、VSM

1.1 算法原理

根据SM原理我们知道,在处理软阴影的时候,我们通常需要使用PCF来处理一个区域内的多个深度比较结果来确定一个合适的软硬程度值。
简单说一下PCF的原理:把当前屏幕空间像素范围投射为阴影贴图,并对得到的区域采样,这个过程和标准的纹理过滤很相似。然后把每一个样本和一个参考的深度进行对比,产 生一个二元结果。接着,结合所有的深度对比来计算过滤区域中比参考深度更加靠近(closer)的纹理像素的百分比。这个百分比就被用来减弱光线。一般提高过滤区域的大小可以软化阴影的边缘程序。

虽然PCF的品质挺不错,但是需要大量的采样。对于标准的纹理过滤来说,阴影角部的表面还需要求大量的各向异性过滤区域。在最坏的情况下,我们必须对阴影贴图中的每一个纹理像素进行采样和对比,以计算每一个帧缓冲像素的光衰减。这样过程会很慢,而且存在shadow acne现象。

针对上边的问题于是便有了VSM算法,其主要思路是:按照一种可以线性过滤的方法来表示深度数据,这样我们就可以使用适用于颜色和其他线性数据的算法和硬件。本算法和标准的阴影贴图算法很相似,只不过我们把深度和深度平方写到一个二分量R32G32_FLOAT的差值阴影贴图中,而不是简单地把深度写入到阴影贴图中。通过对某些区域进行过滤,我们恢复了该区域在M1时刻和M2时刻的深度分布,其中M1/M2代表的是该区域的均值数据,即:
在这里插入图片描述
本质上就是对深度和深度平方贴图做一个滤波(如盒型滤波或高斯滤波等)。
在这里插入图片描述

抑或是直接采用MSAA处理一下之后采用线型采样器直接取值即可,之后算出均值(平均准)和方差(统计学经典公式)。
在这里插入图片描述
紧接着我们就可以使用这个差值,应用(Chebyshev)切比雪夫不等式来计算当前待求阴影的表面(深度为1)被遮挡的概率的上界:
在这里插入图片描述

在这里插入图片描述
Chebyshev不等式的“积差相关系数”版本只在t>μ时有效。如果t≤μ,那么Pmax=1,代表的就是这个表面被完全照亮了,虽然不准,但是就这么用就行。

以上就是全部理论,就这么简单。但是在实现中还有几点小坑,直接看代码吧。

1.2 算法实现

1.2.1 阴影生成

常规阴影算法,不再赘述,生成如下图所示:
在这里插入图片描述

1.2.2 深度与深度平方期望计算

一个DrawCall三角形将上述深度图转换存储,HLSL中PS代码如下:

struct VSOutput
{
    float4 Position : SV_Position;
    float2 TexCoord : TEXCOORD;
};
VSOutput FullScreenVS(in uint VertexID : SV_VertexID)
{
    VSOutput output;

    if(VertexID == 0)
    {
        output.Position = float4(-1.0f, 1.0f, 1.0f, 1.0f);
        output.TexCoord = float2(0.0f, 0.0f);
    }
    else if(VertexID == 1)
    {
        output.Position = float4(3.0f, 1.0f, 1.0f, 1.0f);
        output.TexCoord = float2(2.0f, 0.0f);
    }
    else
    {
        output.Position = float4(-1.0f, -3.0f, 1.0f, 1.0f);
        output.TexCoord = float2(0.0f, 2.0f);
    }

    return output;
}

float4 ConvertToVSM(in VSOutput input) : SV_Target0
{
    float sampleWeight = 1.0f / float(MSAASamples_);
    uint2 coords = uint2(input.Position.xy);


    float4 average = float4(0.0f, 0.0f, 0.0f, 0.0f);

    // Sample indices to Load() must be literal, so force unroll
    [unroll]
    for(uint i = 0; i < MSAASamples_; ++i)
    {
        // Convert to EVSM representation
        #if MSAASamples_ > 1
            float depth = ShadowMap.Load(coords, i);
        #else
            float depth = ShadowMap[coords];
        #endif
         average += sampleWeight * float4(vsmDepth.xy, vsmDepth.xy * vsmDepth.xy);
    }
    return average.xzxz;
}

转换后贴图如下所示:
在这里插入图片描述
除了上述说的,直接采用MSAA和线型采样器来处理得到深度与深度平方的均值,即(M1与M2),还可以采用滤波的方式。
可参照这篇文章介绍https://graphics.stanford.edu/~mdfisher/Shadows.html,流程效果图如下:
在这里插入图片描述

滤波处理代码可参照:

struct VSOutput
{
    float4 Position : SV_Position;
    float2 TexCoord : TEXCOORD;
};
VSOutput FullScreenVS(in uint VertexID : SV_VertexID)
{
    VSOutput output;

    if(VertexID == 0)
    {
        output.Position = float4(-1.0f, 1.0f, 1.0f, 1.0f);
        output.TexCoord = float2(0.0f, 0.0f);
    }
    else if(VertexID == 1)
    {
        output.Position = float4(3.0f, 1.0f, 1.0f, 1.0f);
        output.TexCoord = float2(2.0f, 0.0f);
    }
    else
    {
        output.Position = float4(-1.0f, -3.0f, 1.0f, 1.0f);
        output.TexCoord = float2(0.0f, 2.0f);
    }

    return output;
}

float4 BlurSample(in float2 screenPos, in float offset, in float2 mapSize)
{
    #if Vertical_
        float2 samplePos = screenPos;
        samplePos.y = clamp(screenPos.y + offset, 0, mapSize.y);
        return VSMMap[uint2(samplePos)];
    #else
        float2 samplePos = screenPos;
        samplePos.x = clamp(screenPos.x + offset, 0, mapSize.x);
        return VSMMap[uint3(samplePos, 0)];
    #endif
}

float4 BlurVSM(in VSOutput input) : SV_Target0
{
    #if Vertical_
        float scale = abs(CascadeScale.y);
        float maxFilterSize = MaxKernelSize / abs(Cascade0Scale.y);
    #else
        float scale = abs(CascadeScale.x);
        float maxFilterSize = MaxKernelSize / abs(Cascade0Scale.x);
    #endif

    const float KernelSize = clamp(min(FilterSize, maxFilterSize) * scale, 1.0f, MaxKernelSize);
    const float Radius = KernelSize / 2.0f;

    #if GPUSceneSubmission_
        [branch]
        if(KernelSize > 1.0f)
        {
            const int SampleRadius = int(round(Radius));

            float4 sum = 0.0f;

            [loop]
            for(int i = -SampleRadius; i <= SampleRadius; ++i)
            {
                float4 sample = BlurSample(input.Position.xy, i, ShadowMapDimensions);

                sample *= saturate((Radius + 0.5f) - abs(i));

                sum += sample;
            }

            return sum / KernelSize;
        }
        else
        {
            return BlurSample(input.Position.xy, 0, ShadowMapDimensions);
        }
    #else
        float4 sum = 0.0f;

        [unroll]
        for(int i = -SampleRadius_; i <= SampleRadius_; ++i)
        {
            float4 sample = BlurSample(input.Position.xy, i, ShadowMapDimensions);

            sample *= saturate((Radius + 0.5f) - abs(i));

            sum += sample;
        }

        return sum / KernelSize;
    #endif
}

总结一下:由于阴影贴图是线性可过滤的,那么我们就可以使用很多技术和算法。最明显的是,我们可以简单地使用纹理细化、三线过滤和各向异性过滤,甚至是多样本反锯齿(同时要渲染阴影贴图)。相比于使用标准的阴影贴图以及常量过滤percentage-closer过滤而言,它自己就可以极大地提高阴影贴图的质量。当然还有盒型滤波或高斯滤波等、还有就是区域求和表(SAT)算法也可以。有兴趣的可以自行尝试。

当然,为了更精确的算法深度平方的均值,还可以给其加一个偏离:
在这里插入图片描述
此处主要是把阴影贴图纹理像素当成一个局部平面分布考虑来优化(GPU Gems 3中相关优化技术),有兴趣的自行深入探索吧。

1.2.3 计算阴影

经过上边的处理我们就很容易得到深度与深度平方的期望值了,接下来就是在阴影计算的时候使用切比雪夫不等式了:


//  http://mynameismjp.wordpress.com/


// 减少漏光
float Linstep(float a, float b, float v)
{
    return saturate((v - a) / (b - a));
}

float ReduceLightBleeding(float pMax, float amount)
{
  // 溢出[0, amount] 的尾部并线型地缩放到[amount, 1].
   return Linstep(amount, 1.0f, pMax);
}

float ChebyshevUpperBound(float2 moments, float mean, float minVariance,
                          float lightBleedingReduction)
{
    // 计算方差
    float variance = moments.y - (moments.x * moments.x);
    variance = max(variance, minVariance);//防除0

    // 计算概率上界(切比雪夫不等式)
    float d = mean - moments.x;
    float pMax = variance / (variance + (d * d));

	//减轻漏光
    pMax = ReduceLightBleeding(pMax, lightBleedingReduction);

    // 单侧切比雪夫处理
    return (mean <= moments.x ? 1.0f : pMax);
}

float SampleShadowMapVSM(in float3 shadowPos, in float3 shadowPosDX,
                         in float3 shadowPosDY, uint cascadeIdx)
{
    float depth = shadowPos.z;

    float2 occluder = ShadowMap.SampleGrad(VSMSampler, float3(shadowPos.xy, cascadeIdx),
                                           shadowPosDX.xy, shadowPosDY.xy).xy;

    return ChebyshevUpperBound(occluder, depth, VSMBias * 0.01, LightBleedingReduction);
}

float3 ShadowVisibility(in float3 positionWS, in float depthVS, in float nDotL, in float3 normal,
                        in uint2 screenPos)
{
	float3 shadowVisibility = 1.0f;
	uint cascadeIdx = NumCascades - 1;
    float3 projectionPos = mul(float4(positionWS, 1.0f), ShadowMatrix).xyz;

    // Apply offset
    float3 offset = GetShadowPosOffset(nDotL, normal) / abs(CascadeScales[cascadeIdx].z);

    // Project into shadow space
    float3 samplePos = positionWS + offset;
	float3 shadowPosition = mul(float4(samplePos, 1.0f), ShadowMatrix).xyz;
    float3 shadowPosDX = ddx_fine(shadowPosition);
    float3 shadowPosDY = ddy_fine(shadowPosition);

	shadowVisibility = SampleShadowCascade(shadowPosition, shadowPosDX, shadowPosDY,
                                           cascadeIdx, screenPos);

	return shadowVisibility;
}

得到的结果如下所示:
在这里插入图片描述
看着上边代码不多,除了切比雪夫近似应用外,其实在一定程度上还解决了漏光问题(VSM最大的问题在于漏光现象),那么算法为什么会产生漏光呢?我们具体看一下(其实就是切比雪夫不等式的近似导致的,我们从数学公式的角度上分析更容易看出原因)。
在这里插入图片描述
假设三个物体从上到下标记为A、B、C,对应的深度值为a、b、c。只有物体A和B处于滤波区域,C则作为receiver被两个物体挡住本应是看不到光源的。我们假定当前着色点位于C中滤波区域的中心我们可以得到下面两个矩:
在这里插入图片描述
然后我们可以算出均值和方差:
在这里插入图片描述
上图中有Δx=b−a和Δy=c−b,因此运用切比雪夫不等式计算中间区域的可见性函数为:
在这里插入图片描述
以Δy/Δx作为变量,可以得到函数曲线为:
在这里插入图片描述
因此Δy/Δx越小,整体可见性越是接近于0.5,所以导致了漏光现象的出现。

因此解决漏光问题的算法便是上述代码中的:

// 减少漏光
float Linstep(float a, float b, float v)
{
    return saturate((v - a) / (b - a));
}

float ReduceLightBleeding(float pMax, float amount)
{
  // 溢出[0, amount] 的尾部并线型地缩放到[amount, 1].
   return Linstep(amount, 1.0f, pMax);
}

其原理很简单:一些重要的数据观察结果是如果深度t的一个表面完全遮挡了某些平均深度为μ的过滤区域,那么t>μ。因此(t-μ)²>0,根据Chebyshev不等式,Pmax<1。简单地说就是,完全遮挡的表面上错误的半影绝对不会达到完全的亮度。因此我们可以通过修改Pmax来移除这些区域,这样所有小于某些最小亮度的值都会映射为0,然后重新调节其他的值,这样它们就映射到(0,1)区间。 也就是上边代码中的实现。

为了进一步提升精度更大程度上解决漏光问题,便衍生了LVSM、EVSM等方法,接下来我们就直接以效果最好的EVSM来看吧。

二、EVSM

2.1 算法原理

正如前面所说的,漏光现象在Δy和Δx的比值非常小的时候会特别明显。我们可以考虑使用一些对x和y的wrap来尝试提升Δy和Δx的比值。
例如我们可以使用上面e(cx)的wrapper,这里c是一个正数。然后对ecx求均值和方差,然后使用切比雪夫不等式求Pmax。
这样原来Δy/Δx就变成了e(Δy−Δx)

经过e(cx)的wrapper后,可以有效抑制漏光情况,但是随着c增大,远处场景也会出现问题,因此需要一个反向抑制,即-e(-cx)
在这里插入图片描述
这两个wrapper一起使用时,叫做EVSM4,即需要使用四通道纹理,只使用正向的叫EVSM2。由于e(cx)和-e(-cx)都是单调递增函数,这两个wrapper都可以使用切比雪夫不等式,最后取两个上限概率之中的最小值即可。这时候artifacts就会随着c值的增加而极大限度的减缓漏光问题。

2.2 算法实现

2.2.1 EVSM4阴影转换

代码如下:

static const uint SMFormat16Bit = 0;
static const uint SMFormat32Bit = 1;

float2 GetEVSMExponents(in float positiveExponent, in float negativeExponent, in uint vsmFormat)
{
    const float maxExponent = vsmFormat == SMFormat16Bit ? 5.54f : 42.0f;

    float2 lightSpaceExponents = float2(positiveExponent, negativeExponent);

    // 逼近至fp32/fp16的最大范围,以防止溢出
    return min(lightSpaceExponents, maxExponent);
}

// 对阴影贴图深度应用指数扭曲,输入深度应为[0,1]
float2 WarpDepth(float depth, float2 exponents)
{
    // Rescale depth into [-1, 1]
    depth = 2.0f * depth - 1.0f;
    float pos =  exp( exponents.x * depth);
    float neg = -exp(-exponents.y * depth);
    return float2(pos, neg);
}

float4 ConvertToVSM(in VSOutput input) : SV_Target0
{
    float sampleWeight = 1.0f / float(MSAASamples_);
    uint2 coords = uint2(input.Position.xy);
	//40.0f,5.0f
    float2 exponents = GetEVSMExponents(PositiveExponent, NegativeExponent, SMFormat);


    float4 average = float4(0.0f, 0.0f, 0.0f, 0.0f);

    [unroll]
    for(uint i = 0; i < MSAASamples_; ++i)
    {
        // Convert to EVSM representation
        float depth = ShadowMap[coords];
        float2 vsmDepth = WarpDepth(depth, exponents);
        average += sampleWeight * float4(vsmDepth.xy, vsmDepth.xy * vsmDepth.xy);
    }
    return average;
}

原始阴影贴图:
在这里插入图片描述
转换后的EVSM4阴影贴图:
在这里插入图片描述

2.2.2 阴影计算

static const uint SMFormat16Bit = 0;
static const uint SMFormat32Bit = 1;

float2 GetEVSMExponents(in float positiveExponent, in float negativeExponent, in uint vsmFormat)
{
    const float maxExponent = vsmFormat == SMFormat16Bit ? 5.54f : 42.0f;

    float2 lightSpaceExponents = float2(positiveExponent, negativeExponent);

    // 逼近至fp32/fp16的最大范围,以防止溢出
    return min(lightSpaceExponents, maxExponent);
}

// 对阴影贴图深度应用指数扭曲,输入深度应为[0,1]
float2 WarpDepth(float depth, float2 exponents)
{
    // Rescale depth into [-1, 1]
    depth = 2.0f * depth - 1.0f;
    float pos =  exp( exponents.x * depth);
    float neg = -exp(-exponents.y * depth);
    return float2(pos, neg);
}

float SampleShadowMapEVSM(in float3 shadowPos, in float3 shadowPosDX,
                          in float3 shadowPosDY)
{
    float2 exponents = GetEVSMExponents(PositiveExponent, NegativeExponent, SMFormat);
    float2 warpedDepth = WarpDepth(shadowPos.z, exponents);

    float4 occluder = ShadowMap.SampleGrad(VSMSampler, float3(shadowPos.xy, 0.),
                                            shadowPosDX.xy, shadowPosDY.xy);

    // 采样深度翘曲
    float2 depthScale = VSMBias * 0.01f * exponents * warpedDepth;
    float2 minVariance = depthScale * depthScale;

    #if ShadowMode_ == ShadowModeEVSM4_
        float posContrib = ChebyshevUpperBound(occluder.xz, warpedDepth.x, minVariance.x, LightBleedingReduction);
        float negContrib = ChebyshevUpperBound(occluder.yw, warpedDepth.y, minVariance.y, LightBleedingReduction);
        return min(posContrib, negContrib);
    #else
        // Positive only
        return ChebyshevUpperBound(occluder.xy, warpedDepth.x, minVariance.x, LightBleedingReduction);
    #endif
}


float ShadowVisibility(in float3 positionWS, in float depthVS, in float nDotL, in float3 normal,
                        in uint2 screenPos)
{
	float shadowVisibility = 1.0f;
    // Project into shadow space
    float3 samplePos = positionWS;
	float3 shadowPosition = mul(float4(samplePos, 1.0f), ShadowMatrix).xyz;
    float3 shadowPosDX = ddx_fine(shadowPosition);
    float3 shadowPosDY = ddy_fine(shadowPosition);

	shadowVisibility = SampleShadowMapEVSM(shadowPosition, shadowPosDX, shadowPosDY, screenPos);

	return shadowVisibility;
}

生成及如果如下:
在这里插入图片描述

2.2.3 高斯滤波(软阴影生成)

最后说一下无论是VSM、LVSM、ESM还是EVSM都是没办法直接产生软阴影的,但是它相对于SM单点采样直接非0即1的结果来说已经很好了,对比如下图:

单次SampleCmpLevelZero采样阴影边缘:

在这里插入图片描述
EVSM单次采样阴影边缘:
在这里插入图片描述

所以一般还需要一个高斯滤波使其能够模糊边缘区域,实现软阴影。

简单放一个5*5高斯算法示意如下:

// Kernel from: https://computergraphics.stackexchange.com/questions/39/how-is-gaussian-blur-implemented
// I presume it is approximate using the Pascal pyramid
//const float blurKernel[25] = float[](
//    1.0 / 256.0,  4.0 / 256.0,  6.0 / 256.0,  4.0 / 256.0, 1.0 / 256.0,
//	4.0 / 256.0, 16.0 / 256.0, 24.0 / 256.0, 16.0 / 256.0, 4.0 / 256.0,
//	6.0 / 256.0, 24.0 / 256.0, 36.0 / 256.0, 24.0 / 256.0, 6.0 / 256.0,
//	4.0 / 256.0, 16.0 / 256.0, 24.0 / 256.0, 16.0 / 256.0, 4.0 / 256.0,
//    1.0 / 256.0,  4.0 / 256.0,  6.0 / 256.0,  4.0 / 256.0, 1.0 / 256.0
//);

// Kernel generated at: http://dev.theomader.com/gaussian-kernel-calculator/
const float blurKernel[25] = float[](
0.023528,	0.033969,	0.038393,	0.033969,	0.023528,
0.033969,	0.049045,	0.055432,	0.049045,	0.033969,
0.038393,	0.055432,	0.062651,	0.055432,	0.038393,
0.033969,	0.049045,	0.055432,	0.049045,	0.033969,
0.023528,	0.033969,	0.038393,	0.033969,	0.023528
);


float4 main(in VSOutput input) : SV_Target0
{
	float3 finalColor = float3 (0.0);
	float2 u_TexelSize=input.texelSize;
	for (int x = -2; x <= 2; x++) {
		for (int y = -2; y <= 2; y++) {
			finalColor += InputTexture.Sample(sampler, float2(v_TexCoords.x + u_TexelSize.x * x, v_TexCoords.y + u_TexelSize.y * y)).rgb * blurKernel[x + 2 + (y + 2) * 5];
		}
	}
	FragColor = float4 (finalColor, 1.0);
}

至此结束,至于如何运用到CSM(一般我们叫级联阴影,也有一个阴影算法叫Convolution Shadow Maps,也俗称CSM,仁者见仁吧)上,很简单,每级阴影皆执行EVSM即可。

参考文章:
http://developer.download.nvidia.com/presentations/2008/GDC/GDC08_SoftShadowMapping.pdf
https://learn.microsoft.com/zh-cn/windows/win32/dxtecharts/cascaded-shadow-maps?redirectedfrom=MSDN
https://graphics.stanford.edu/~mdfisher/Shadows.html
https://www.martincap.io/project_detail.php?project_id=9
https://graphics.stanford.edu/~mdfisher/Shadows.html

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

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

相关文章

十、Linux的root用户、用户和用户组的问题

目录 1、Linux的root用户 &#xff08;1&#xff09;基础 &#xff08;2&#xff09;如何进入root模式 &#xff08;3&#xff09;如何给普通用户配置root权限&#xff1f; 注意点&#xff1a; 配置方法&#xff1a; 2、用户/用户组问题 &#xff08;1&#xff09;用户/用…

【网络安全必看】如何提升自身 WEB 渗透能力?

前言 web 渗透这个东西学起来如果没有头绪和路线的话&#xff0c;是非常烧脑的。 理清 web 渗透学习思路&#xff0c;把自己的学习方案和需要学习的点全部整理&#xff0c;你会发现突然渗透思路就有点眉目了。 程序员之间流行一个词&#xff0c;叫 35 岁危机&#xff0c;&am…

宁德时代与陕汽签署十年战略合作协议,助力商用车电动化进程

据报道&#xff0c;宁德时代新能源科技股份有限公司与陕西汽车控股集团有限公司已经签署了一项为期十年的战略合作协议。双方的合作旨在推动商用车电池技术的发展&#xff0c;并面向商用车全领域应用。 这次战略合作具有重要意义&#xff0c;为宁德时代和陕汽启动了全面合作的序…

iOS开发 - Swift Codable协议实战:快速、简单、高效地完成JSON和Model转换!

前言 Codable 是 Swift 4.0 引入的一种协议&#xff0c;它是一个组合协议&#xff0c;由 Decodable 和 Encodable 两个协议组成。它的作用是将模型对象转换为 JSON 或者是其它的数据格式&#xff0c;也可以反过来将 JSON 数据转换为模型对象。 Encodable 和 Decodable 分别定…

【数据结构】 单链表面试题讲解

文章目录 引言反转单链表题目描述示例&#xff1a;题解思路代码实现&#xff1a; 移除链表元素题目描述&#xff1a;示例思路解析&#xff1a; 链表的中间结点题目描述&#xff1a;示例&#xff1a;思路解析代码实现如下&#xff1a; 链表中倒数第k个结点题目描述示例思路解析&…

OpenFOAM的fvOptions

采用OpenFoam中的fvOptions /*--------------------------------*- C -*----------------------------------*\ |\\ / F ield | OpenFOAM: The Open Source CFD Toolbox\\ / O peration | Website: https://openfoam.org\\ / A n…

CentOS7.6安装mysql8.0.34

一、查看服务器相关信息 cat /etc/redhat-release cat /proc/version [rootlocalhost ~]# cat /etc/redhat-release CentOS Linux release 7.6.1810 (Core) [rootlocalhost ~]# cat /proc/version Linux version 3.10.0-957.el7.x86_64 (mockbuildkbuilder.bsys.centos.org) …

不用插拔网线鼠标点击自动切换网线和WIFI

因为之前在zf单位工作,政务内网需要插网线,而访问外网又需要连wifi,切换就需要拔掉网线插上网线很麻烦,旁边老哥教我了一手.bat程序自动切换方法, .bat文件代码如下: 以下代码的.bat文件执行后会切换到以太网,同时关闭掉wifi和以太网4 echo off %1 mshta vbscript:CreateObjec…

YOLOX算法调试记录

YOLOX是在YOLOv3基础上改进而来&#xff0c;具有与YOLOv5相媲美的性能&#xff0c;其模型结构如下&#xff1a; 由于博主只是要用YOLOX做对比试验&#xff0c;因此并不需要对模型的结构太过了解。 先前博主调试过YOLOv5,YOLOv7&#xff0c;YOLOv8,相比而言&#xff0c;YOLOX的环…

Linux:shell脚本数组和脚本免交互

目录 一、shell数组的定义 二、定义数组的方式 &#xff08;1&#xff09;数组名(value1 value2 value3 value4 ...) &#xff08;2&#xff09;获取数组的长度 &#xff08;3&#xff09;获取数组下标对应的值 &#xff08;4&#xff09;数组的遍历 &#xff08;5&#x…

Ubuntu20.04安装Nvidia显卡驱动教程

1、禁用nouveau 1、创建文件&#xff0c;如果没有下载vim编辑器&#xff0c;将vim换成gedit即可 $ sudo vim /etc/modprobe.d/blacklist-nouveau.conf 2、在文件中插入以下内容&#xff0c;将nouveau加入黑名单&#xff0c;默认不开启 blacklist nouveau options nouveau m…

Linux学习之基本指令二

-----紧接上文 在了解cat指令之前&#xff0c;我们首先要了解到Linux下一切皆文件&#xff0c;在学习c语言时我们就已经了解到了 对文件输入以及读入的操作&#xff08;向显示器打印&#xff0c;从键盘读取数据&#xff09;&#xff0c;对于Linux下文件的操作&#xff0c;也是…

make系列之入门

一.欢迎来到我的酒馆 在本章节介绍make工具。 目录 一.欢迎来到我的酒馆二.什么是make三.make与Makefile四.如何写Makefile 二.什么是make 你可能会遇到一些名词&#xff1a;GNU&#xff0c;Linux&#xff0c;make。它们是什么&#xff0c;又有什么样的联系&#xff1f; …

mongodb.使用自带命令工具导出导入数据

在一次数据更新中&#xff0c;同事把老数据进行了清空操作&#xff0c;但是新的逻辑数据由于某种原因&#xff08;好像是她的电脑中病毒了&#xff09;&#xff0c;一直无法正常连接数据库进行数据插入&#xff0c;然后下午2点左右要给甲方演示&#xff0c;所以要紧急恢复本地的…

【图论】Floyd算法

一.简介 Floyd算法&#xff0c;也称为Floyd-Warshall算法&#xff0c;是一种用于解决所有节点对最短路径问题的动态规划算法。它可以在有向图或带权图中找到任意两个节点之间的最短路径。 Floyd算法的基本思想是通过中间节点逐步优化路径长度。它使用一个二维数组来存储任意两…

挖漏洞竟能赚取百万美金?来认识一下这 6 位百万美元白帽黑客

在黑客世界&#xff0c;有三种人&#xff1a;白帽&#xff08;黑客&#xff09;、灰帽&#xff08;黑客&#xff09;和黑帽&#xff08;黑客&#xff09;。其中&#xff0c;白帽黑客&#xff0c;即 White Hat Hacker&#xff0c;又称白帽子&#xff0c;它们用自己的黑客技术来维…

redux的介绍、安装、三大核心与执行流程

redux的介绍、安装、三大核心与执行流程 一、redux的基本介绍二、redux的安装三、redux核心概念3.1 action3.2 reducer3.3 store 四、Redux代码执行流程五、加减案例练习 一、redux的基本介绍 redux中文官网Redux 是 React 中最常用的状态管理工具&#xff08;状态容器&#x…

B树和B+树MySQL为什么用B+树?

文章目录 B树和B树B树B树的定义B树的插入操作删除操作 B树B树的定义B树的插入操作删除操作 B树和B树的区别?MySQL数据库为啥用B树作为索引&#xff0c;而不用B树? B树和B树 原文链接&#xff1a;https://blog.csdn.net/jinking01/article/details/115130286 B树 B树的定义…

深入理解python虚拟机:程序执行的载体——栈帧

栈帧&#xff08;Stack Frame&#xff09;是 Python 虚拟机中程序执行的载体之一&#xff0c;也是 Python 中的一种执行上下文。每当 Python 执行一个函数或方法时&#xff0c;都会创建一个栈帧来表示当前的函数调用&#xff0c;并将其压入一个称为调用栈&#xff08;Call Stac…

RT1052的EPWM

文章目录 1 EPWM介绍1.1 引脚1.2 时钟1.3 比较寄存器 2 函数 1 EPWM介绍 RT1052 具有 4 个 eFlexPWM(eFlexWM1~eFlex_PWM4)。 每个 eFlexPWM 可以产生四路互补 PWM即产生 8 个 PWM&#xff0c;也可以产生相互独立的 PWM 波。四路分别是模块0-3每个 eFlexPWM 具有各自的故障检…