体渲染光线行进算法【NeRF必读】

news2025/1/11 9:02:51

为了积分由于内散射而沿射线产生的入射光,我们将射线穿过的体块分解为小体块元素,并将每个小体块元素对整个体块对象的贡献结合起来,有点像我们在 2D 编辑软件(例如 Photoshop)中将带有遮罩或 Alpha 通道(通常代表对象的不透明度)的图像彼此堆叠在一起。 这就是我们在第一章中讨论 Alpha 合成方法的原因。 这些小体块元素中的每一个都代表第一章中提到的黎曼和中的一个样本。

在这里插入图片描述

图 1:向后光线行进。 沿着射线以规则的小步向前行进,从 t1 到 t0。

该算法的工作原理如下:

找到 t0 和 t1 的值,即相机/眼睛光线进入和离开体块对象的点。

将 t0-t1 定义的段分成 X 个相同大小的较小段。 一般来说,我们通过选择所谓的步长来实现这一点,步长只不过是定义较小线段长度的浮点数。 例如,如果 t0=2.5,t1=8.3,步长 = 0.25,我们将把 t0-t1 定义的段除以 (8.3-2.5)/0.25=23 个较小的段(现在让我们保持简单,所以不要担心小数)。

接下来要做的就是从 t0 或 t1 开始,沿着摄像机光线“行进”X 次(参见要点#6)。
在这里插入图片描述

图 2:计算 Li(x) 需要沿着光的方向追踪射线,以了解光束必须穿过体块多远才能到达采样点。

每次迈出一步,我们都会从步的中间(我们的样本点)向光源发射一条“光线”。 我们计算光线与体积元素相交(离开)的位置,并使用比尔定律计算其对样本的贡献(由于内散射)。 请记住,来自光源的光在穿过体块到达采样点时会被体块吸收。 这就是我们在上一章提到的黎曼和中的Li(x)值。 不要忘记,我们需要将此值乘以黎曼和中的步长,对应于 dx 项,即矩形的宽度。 在伪代码中我们得到:

// compute Li(x) for current sample x
float lgt_t0, lgt_t1; // parametric distance to the points where the light ray intersects the sphere
volumeSphere->intersect(x, lgt_dir, t0, lgt_t1); // compute the intersection of the light ray with the sphere
color Li_x = exp(-lgt_t1 * sigma_a) * light_color * step_size; // step_size is our dx
...

如图 2 所示,射线与球体相交测试的 t0 应始终为 0,因为光线从球体内部开始,而 t1 是从样本位置 x 到光线与球体相交点的参数距离。 因此,我们可以使用比尔定律方程中的该值来计算体块物体吸收光的距离。

当然,穿过小体块元件(我们的样品)的光在穿过采样时也会衰减。 因此,我们使用步长作为比尔定律方程中光束穿过体块的距离值来计算样本的透射值。 然后将光量(内散射)衰减(乘以)该透射值。

最后,我们需要组合每个样本,以说明它们各自对体块对象的整体不透明度和“颜色”的贡献。 事实上,如果向后思考这个过程(如图 1 所示),第一个体块元素(从 t1 开始)被第二个体块元素遮挡,第二个体块元素本身又被第三个体块元素遮挡,依此类推,直到我们到达“队列”中的最后一个元素(紧邻 t0 的样本)。 如果“通过相机”射线查看,紧邻 t1 的元素会被它前面的所有元素遮挡。 紧邻 t0 的样本之后的样本被第一个样本遮挡,依此类推。

“光线行进”这个名字现在很容易理解:我们沿着射线行进,采取小的规则步骤,如图 1 所示(向后光线行进的示例)。 请注意,使用常规步骤并不是光线行进算法的条件。 步数也可以是不规则的,但为了让事情简单起见,让我们使用常规步数或跨步(肯·马斯格雷夫喜欢这样称呼它们)。 当使用常规步骤时,我们称之为统一光线处理(而不是自适应光线行进)。

我们可以通过两种方式组合样本:向后(从 t1 行进到 t0)或向前(从 t0 行进到 t1)。 一个比另一个更好(某种程度上)。 我们现在将描述它们是如何工作的。

1、后向射线行进

在向后射线行进中,我们将沿着射线从后向前行进。 换句话说,从 t1 到 t0。 这改变了我们组合样本来计算最终像素不透明度和颜色值的方式。

很自然,因为我们从体块对象(我们的球体)的背面开始,所以我们可以用背景颜色(我们的蓝色)初始化像素颜色(为相机光线返回的颜色)。 但在我们的实现中,我们只会在过程结束时将两者结合起来(一旦我们计算了体块对象颜色和不透明度),有点像我们在 2D 编辑软件中合成两个图像时。

我们将计算第一个样本(例如 X0)在体积中的贡献,从 t1 开始,然后回到 t0,采取常规步骤(由步长定义)。

在这里插入图片描述

图 3:为了计算样本,我们需要考虑来自背面的光(背景颜色)和由于内散射而来自光源的光。 然后考虑将吸收部分光贡献的小体块元素。 可以将其视为背景颜色和来自光源的颜色乘以小体块元素透明度值的相加

该样本的贡献是什么?

我们将计算内散射贡献(光源的贡献)Li(X0),如上所述(第 6 点):沿光的方向发射光线,然后使用比尔定律衰减光贡献,以计算当光从进入物体的点(我们的球体)传播到采样点 (X0) 时,有多少光被体块物体吸收。

然后,我们需要将该光乘以采样的透明度值(表示样品吸收了多少光)。 再次使用比尔定律计算采样透明度,使用步长作为光束穿过该采样的距离(图 3)。

...
color Li_x0 = exp(-lgt_t1 * sigma_a) * light_color * step_size; // step_size is our dx
color x0_contrib = Li_x0 * exp(-step_size * sigma_a);
...

我们刚刚计算了第一个样本 X0。 然后我们转向第二个样本 (X1),但现在我们需要考虑两个光源:来自第一个样本 X0 的光束(我们之前的结果),以及由于内散射而穿过第二个样本的光束 X1\。 我们已经计算了前者(正如我们刚才所说,这是我们之前的结果)并且我们知道如何渲染后者。 我们将它们相加,并将该总和乘以第二个样本传输值。 这成为我们的新结果。 我们不断地用 X2、X3 重复这个过程,直到我们最终到达 t0\。 最终结果是体块对象对当前相机光线像素颜色的贡献。 这个过程如下图所示。
在这里插入图片描述

从上图中请注意,我们计算两个值:体块整体颜色(存储在结果中)和整体透明度。 我们将此值初始化为 1(完全透明),然后当我们沿着光线向上(或向下)移动(从 t1 到 t0)时,使用每个样本透明度值来衰减该值。 然后(最终)我们可以使用这个整体透明度值将体块对象与背景颜色结合起来。 简单计算如下:

color final = background_color * transmission + result;

在合成术语中,我们会说“结果”项已经预先乘以体块整体透明度。 但如果这让你感到困惑,我们将在下一章中澄清这一点。 所以现在不要太关注这个。

另请注意,在上图中和下面的代码中,样本的衰减项始终相同:exp(-step_size * sigma_a)。 当然,这效率不高。 你应该计算该项一次,将其存储在变量中,然后使用该变量。 但清晰是我们的目标,而不是编写高性能代码。 此外,目前,当我们沿着射线行进时,该值是恒定的,但我们将在接下来的章节中发现它最终会因样本而异。

翻译成代码是这样的:

constexpr vec3 background_color{ 0.572f, 0.772f, 0.921f };

vec3 integrate(const vec3& ray_orig, const vec3& ray_dir, ...)
{
    const Object* hit_object = nullptr;
    IsectData isect;
    for (const auto& object : objects) {
        IsectData isect_object;
        if (object->intersect(ray_orig, ray_dir, isectObject)) {
            hit_object = object.get();
            isect = isect_object;
        }
    }

    if (!hit_object) 
        return background_color;

    float step_size = 0.2;
    float sigma_a = 0.1; // absorption coefficient
    int ns = std::ceil((isect.t1 - isect.t0) / step_size);
    step_size = (isect.t1 - isect.t0) / ns;

    vec3 light_dir{ 0, 1, 0 };
    vec3 light_color{ 1.3, 0.3, 0.9 };

    float transparency = 1; // initialize transparency to 1
    vec3 result{ 0 }; // initialize the volume color to 0

    for (int n = 0; n < ns; ++n) {
        float t = isect.t1 - step_size * (n + 0.5);
        vec3 sample_pos= ray_orig + t * ray_dir; // sample position (middle of the step)

        // compute sample transparency using Beer's law
        float sample_transparency = exp(-step_size * sigma_a);
        
        // attenuate global transparency by sample transparency
        transparency *= sample_transparency;

        // In-scattering. Find the distance traveled by light through 
        // the volume to our sample point. Then apply Beer's law.
        IsectData isect_vol;
        if (hitObject->intersect(sample_pos, light_dir, isect_vol) && isect_vol.inside) {
            float light_attenuation = exp(-isect_vol.t1 * sigma_a);
            result += light_color * light_attenuation * step_size;
        }

        // finally attenuate the result by sample transparency
        result *= sample_transparency;
    }

    // combine with background color and return
    return background_color* transparency + result;
}

但请注意这段代码。 目前还不准确。 它缺少一些我们将在下一章中讨论的术语。 现在,我们只想让你了解光线行进的原理。 然而这段代码将产生一个令人信服的图像。
在这里插入图片描述

请注意,在本例中,我们使用了自上而下的远距离光(光方向沿 y 轴向上)。 球体的微红色来自于浅色。 你可以看到球体的上半部分比下半部分更亮。 阴影效果已经可见。

让我们再次看看当我们沿着射线行进时样本会发生什么:
在这里插入图片描述

当我们完成循环时,如果你看看Li(X0)发生了什么,可以观察到它乘以样本衰减的某个幂。 我们沿着光线行进的次数越多,指数就越高(首先是 1,然后是 2,然后是 3,…),因此结果越小(因为衰减或样本透明度低于 1)。 换句话说,随着更多样本的积累,第一个样本对整个体积散射光的贡献会减少。

2、前进射线行进

在这里插入图片描述

图 4:前向射线行进。 沿着射线以规则的小步向前行进,从 t0 到 t1。

在计算 Li(x) 和样本的透射值时,后向射线行进没有区别。 不同的是我们如何组合样本,因为这一次,我们将从 t0 行进到 t1(从前到后)。 在前向射线行进中,样本散射光的贡献必须通过我们迄今为止处理的所有样本(包括当前样本)的总体透射(透明度)值来衰减:Li(X1) 通过样本 X0 和 X1 的透射值衰减,Li(X2) 通过样本 X0、X1 和 X2 的透射值遮挡,等等。以下是算法的说明:

步骤1:在进入射线行进循环之前:将整体透射(透明度)值初始化为1,将结果颜色变量初始化为0(存储当前相机光线的体积对象颜色的变量):float Transmission = 1; 颜色结果 = 0;。

步骤 2:对于光线行进循环中的每次迭代:

计算当前样本的内散射:Li(x)。

通过将其乘以当前样本透射值来更新总体透射(透明度)值:

transmission*=sample_transmission

将 Li(x) 乘以总透射(透明度)值:样本散射的光被我们迄今为止处理的所有样本(包括当前样本)遮挡。 将结果添加到存储当前相机光线体积颜色的全局变量中:

result += Li(x) * transmission.

在这里插入图片描述

翻译成代码:

vec3 integrate(const vec3& ray_orig, const vec3& ray_dir, ...)
{
    ...
    float transparency = 1; // initialize transparency to 1
    vec3 result{ 0 }; // initialize the volume color to 0

    for (int n = 0; n < ns; ++n) {
        float t = isect.t0 + step_size * (n + 0.5);
        vec3 sample_pos = ray_orig + t * ray_dir;

        // current sample transparency
        float sample_attenuation = exp(-step_size * sigma_a);

        // attenuate volume object transparency by current sample transmission value
        transparency *= sample_attenuation;

        // In-Scattering. Find the distance traveled by light through 
        // the volume to our sample point. Then apply Beer's law.
        if (hit_object->intersect(sample_pos, light_dir, isect_vol) && isect_vol.inside) {
            float light_attenuation = exp(-isect_vol.t1 * sigma_a);
            // attenuate in-scattering contrib. by the transmission of all samples accumulated so far
            result += transparency * light_color * light_attenuation * step_size;
        }
    }

    // combine background color and volumetric object color
    return background_color * transparency + result;
}

但请注意这段代码。 目前还不准确。 它缺少一些我们将在下一章中讨论的术语。 现在,我们只想让你了解光线行进的原理。 然而这段代码将产生一个令人信服的图像。

无需在此处显示图像。 如果我们做得正确,向后和向前的射线行进应该给出相同的结果。 好吧,我们知道您不会认为这是理所当然的,所以这是结果。
在这里插入图片描述

3、为什么前向射线行进比向后射线行进“更好”?

因为一旦体积的透明度非常接近 0,我们就可以停止光线行进(如果体积足够大和/或散射系数足够高,就会发生这种情况)。 只有当你以光线行进的方式前进时,这才有可能实现。

现在,渲染我们的球体相当快,但随着我们继续阅读章节,你会发现它最终会变慢。 因此,如果我们能够避免计算对像素颜色没有贡献的样本,因为我们沿着光线行进时到达了一个点,我们知道体块是不透明的,那么这是一个很好的优化。

我们将在下一章中实现这个想法。

4、选择步长

在这里插入图片描述

图 5:我们没有捕获体块中的小细节,因为我们的步长太小。 当然,这个例子是极端的,但它的目的是帮助你理解这个想法。
在这里插入图片描述

图 6:尽管示例也很极端(2 个样本可能永远不足以正确渲染体积对象的光照),但你可以看到我们没有足够的样本来捕获位于实体对象阴影中的体块对象的部分。 我们需要一个非常小的步长。

请记住,我们进行射线行进,从 t0 到 t1 采取小步长的原因是使用黎曼求和方法来估计积分(由于内散射而沿着相机光线向眼睛散射的光量)。 正如前一章和阴影数学课程中所解释的,用于估计积分的矩形越大(在我们的例子中,矩形的宽度由此处的步长大小定义),近似值就越不准确。 或者反过来:矩形越小(步长越小),估计就越准确,但计算时间当然也就越长。 目前,渲染球体的速度相当快,但随着我们学习本课程,您会发现它最终会变得慢得多。 这就是为什么选择步长是速度和准确性之间的权衡。

现在,我们假设体积密度也是均匀的。 在接下来的章节中,我们将看到为了渲染云或烟雾等体积密度,密度会随着空间的变化而变化。 这些体积由大频率特征和较小频率特征组成。 如果步长太大,最终可能无法捕获一些较小的频率特征(图 5)。 这是一个过滤问题,本身就是一个重要但复杂的主题。

可能会出现另一个需要调整步长的问题:阴影。 如果小固体对象在体积对象上投射阴影,如果步长太大,最终将错过它们(图 6)。

所有这些并没有告诉我们如何选择一个好的步长。 理论上来说,没有任何规则。 你基本上应该了解体积对象的大小。 例如,如果它是一个矩形,充满了某种均匀气氛的房间,你应该了解该房间的大小(以及使用的单位类型,例如 1 单位 = 10 厘米)。 因此,如果房间有 100 个单位大,则 0.1 的步长可能太小,而 1 或 2 可能是一个不错的起点。 然后,你需要像我们之前提到的那样,在速度和准确性之间找到一个良好的权衡。

现在看来,这也不完全正确。 在通过考虑场景中物体的大小来凭经验选择步长时,必须有一种更合理的方法来这样做。 一种可能的方法是考虑进入体积对象的距离处的像素“有多大”,并将步长设置为投影像素的尺寸。 事实上,作为离散对象的像素无法表示场景中小于其大小的细节。 我们不会在这里讨论更多细节,因为过滤本身就值得一课。 现在我们要说的是,一个好的步长接近于相机光线与体积相交点处的像素的投影大小。 这可以通过以下方式进行估计:

float projPixWidth = 2 * tanf(M_PI / 180 * fov / (2 * imageWidth)) * tmin;

如果你愿意,可以对其进行优化。 其中 tmin 是相机光线与体积对象相交的距离。 我们可以类似地计算光线离开体积的投影像素宽度,并在 tmin 和 tmax 处对投影像素宽度进行线性插值,以设置我们沿着射线行进时的步长。

5、其他感兴趣的考虑因素!

编写生产代码需要将光线不透明度和颜色与光线数据一起存储。 这样我们就可以首先对固体对象进行光线追踪,然后对体块对象进行光线追踪,并结合结果(与上例中将背景颜色与体积球体对象相结合的方式类似)。

请注意,相机光线的路径上可以有多个体块对象。 因此,有必要沿途存储不透明度,并在我们对连续体块对象进行光线行进时组合它们的不透明度和颜色。

体块对象可以由组合对象的集合组成,例如彼此重叠的立方体或球体。 在这种情况下,我们可能希望将它们组合成某种聚合结构。 对此类聚合进行光线行进需要特别小心地计算生成聚合的对象的相交边界。


原文链接:体渲染光线行进算法 — BimAnt

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

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

相关文章

ClickHouse(三):ClickHouse单节点搭建

进入正文前&#xff0c;感谢宝子们订阅专题、点赞、评论、收藏&#xff01;关注IT贫道&#xff0c;获取高质量博客内容&#xff01; &#x1f3e1;个人主页&#xff1a;含各种IT体系技术,IT贫道_Apache Doris,Kerberos安全认证,随笔-CSDN博客 &#x1f4cc;订阅&#xff1a;拥抱…

pytorch学习——线性神经网络——1线性回归

概要&#xff1a;线性神经网络是一种最简单的神经网络模型&#xff0c;它由若干个线性变换和非线性变换组成。线性变换通常表示为矩阵乘法&#xff0c;非线性变换通常是一个逐元素的非线性函数。线性神经网络通常用于解决回归和分类问题。 一.线性回归 线性回归是一种常见的机…

WebGPU(八):三角形渲染

WebGPU(八)&#xff1a;三角形渲染 三角形的渲染其实很简单&#xff0c;只是需要设置很详细的render pipeline以及shader。 // Select which render pipeline to use wgpuRenderPassEncoderSetPipeline(renderPass, pipeline); // Draw 1 instance of a 3-vertices shape wgp…

C# 全局响应Ctrl+Alt+鼠标右键

一、简述 某些应用&#xff0c;我们希望全局自定义热键。按键少了会和别的应用程序冲突&#xff0c;按键多了可定用户操作不变。因此我计划左手用CtrlAlt&#xff0c;右手用鼠标右键呼出我自定义的菜单。 我使用键盘和鼠标事件进行简单测试&#xff08;Ctrl鼠标右键&#xff…

TypeScript -- 函数

文章目录 TypeScript -- 函数JS -- 函数的两种表现形式函数声明函数的表达式es6 箭头函数 TS -- 定义一个函数TS -- 函数声明使用接口(定义)ts 定义参数可选参数写法 -- ?的使用TS函数 -- 设置剩余参数函数重载 TypeScript – 函数 JS – 函数的两种表现形式 我们熟知js有两…

MySQLExplain详解

Explain使用场景 查询性能优化&#xff1a;EXPLAIN可以帮助开发者分析查询语句的执行计划&#xff0c;判断是否有效地使用了索引、是否有可能导致全表扫描等性能问题。通过EXPLAIN的输出&#xff0c;可以找到潜在的性能瓶颈&#xff0c;并优化查询语句、创建合适的索引或调整表…

Win11虚拟机安装并使用

windows11 虚拟机安装 操作如下&#xff1a;1.进入微软官网2.打开虚拟机应用创建新虚拟机3.选择刚下载IOS文件4 设置虚拟机磁盘空间大小&#xff0c;这个数字可以随便写&#xff0c;反正都是虚拟的&#xff0c;但不可以低于64GB。下面的是否拆分磁盘文件&#xff0c;可更具需要…

大数据课程C4——ZooKeeper结构运行机制

文章作者邮箱&#xff1a;yugongshiyesina.cn 地址&#xff1a;广东惠州 ▲ 本章节目的 ⚪ 了解Zookeeper的特点和节点信息&#xff1b; ⚪ 掌握Zookeeper的完全分布式安装 ⚪ 掌握Zookeeper的选举机制、ZAB协议、AVRO&#xff1b; 一、Zookeeper-简介 1. 特点…

【计网】什么是三次握手四次挥手

文章目录 1、什么是TCP2、什么是TCP连接2.1、连接概念2.2、如何唯一确定一个TCP连接2.3、TCP最大连接数 3、三次握手3.1、为什么需要三次握手3.2、三次握手过程3.3、为什么一定是三次3.3.1、避免历史连接3.3.2、同步双方初始序列号3.3.3、避免资源浪费3.3.4、总结 3.4、握手丢失…

vue实现卡牌数字动态翻牌效果

vue实现卡牌数字动态翻牌效果 1. 实现效果2. 实现代码 1. 实现效果 在大屏项目中&#xff0c;我们尝尝会遇到卡牌式数字显示且能动态翻牌的效果&#xff0c;效果图如下&#xff1a; 2. 实现代码 <template><div class"days-box"><div class"op…

初探PID—速度闭环控制

由于在调PID时意外把板子烧了&#xff0c;目前只完成了比例调节的调试&#xff0c;整个程序也不太完善&#xff0c;本文当前仅作记录&#xff0c;后续会完善更改。 ——2023.07.26 文章目录 一、什么是PID二、PID有什么用三、PID程序实现 一、什么是PID PID是常用的一种控制算…

windows默认编码格式修改

1.命令提示符界面输入 chcp 936 对应 GBK 65001 对应 UTF-8 2.临时更改编码格式 chcp 936(或65001) 3.永久更改编码格式 依次开控制面板->时钟和区域->区域->管理->更改系统区域设置&#xff0c;然后按下图所示&#xff0c;勾选使用UTF-8语言支持。然后重启电脑。此…

上门小程序开发|上门服务小程序|上门家政小程序开发

随着移动互联网的普及和发展&#xff0c;上门服务成为了许多人生活中的一部分。上门小程序是一种基于小程序平台的应用程序&#xff0c;它提供了上门服务的在线平台&#xff0c;为用户提供了便捷的上门服务体验。下面将介绍一些适合开发上门小程序的商家。   家政服务商家&am…

帮助中心内容需要囊括什么?(内含案例分享)

给产品制作一个帮助中心&#xff0c;让用户能够通过访问帮助中心查看产品相关内容&#xff0c;尽快了解产品&#xff0c;熟悉操作。不仅仅局限于售后&#xff0c;在售中售前都能够发挥很大的作用&#xff0c;帮助用户全面了解产品&#xff0c;减少销售的工作量&#xff0c;节约…

数字孪生和 GIS 系统融合将为水利领域带来哪些变化?

随着科技的不断进步&#xff0c;数字孪生和 GIS 系统的融合应用逐渐成为了水利领域的新趋势。数字孪生是指通过数字化技术模拟物理实体和过程&#xff0c;将现实世界与虚拟世界相结合的技术&#xff0c;而 GIS 系统则是地理信息系统&#xff0c;用于收集、存储、管理和分析地理…

Mybatis快速入门,Mybatis的核心配置文件

Mybatis快速入门 一、Mybatis简介1.1Mybatis简化JDBC 二、Mybatis快速入门2.1创建user表&#xff0c;添加数据2.2创建模块&#xff0c;导入坐标2.3编写Mybatis核心配置文件 --> 替换连接信息&#xff0c;解决硬编码问题2.4编写SQL映射文件 --> 统一管理sql语句&#xff0…

7、Java入门教程【面向对象】

面向对象是Java编程的核心概念&#xff0c;如果不能充分了解面向对象的思想&#xff0c;那么会给你在实际的项目开发过程中&#xff0c;带来很多业务设计上的困扰。 一、构造器 我们在设计完一个类&#xff0c;在使用这个类去创建对象实例的时候&#xff0c;有些场景是需要对…

云曦暑期学习第二周——文件上传漏洞

1.文件上传 1.1原理 一些web应用程序中允许上传图片、视频、头像和许多其他类型的文件到服务器中。 文件上传漏洞就是利用服务端代码对文件上传路径变量过滤不严格将可执行的文件上传到一个到服务器中 &#xff0c;再通过URL去访问以执行恶意代码。 1.2为什么存在文件上传漏…

4-Linux组管理和权限管理

Linux组管理和权限管理 Linux组的基本介绍文件/目录的所有者组的创建文件/目录所在的组其它组改变用户所在的组权限的基本介绍第0-9位说明rwx权限详解rwx 修饰文件时rwx修饰目录时 修改权限第一种方式&#xff1a;、-、 变更权限第二种方式&#xff1a;通过数字变更权限 修改文…

第26天-秒杀服务(秒杀系统设计与实现)

1.秒杀设计 1.1.秒杀业务 秒杀具有瞬间高并发特点&#xff0c;针对这一特点&#xff0c;必须要做限流异步缓存&#xff08;页面静态化&#xff09;独立部署。 限流方式&#xff1a; 前端限流&#xff0c;一些高并发的网站直接在前端页面开始限流&#xff0c;例如&#xff1a…