光栅化渲染:优化技巧

我们在前面的章节中介绍的所有技术都是光栅化算法的基础。 不过,我们只是以非常基本的方式实现了这些技术。 GPU 渲染管道和其他基于光栅化的生产渲染器使用相同的概念,但它们使用这些算法的高度优化版本。 展示用于加速算法的所有不同技巧远远超出了介绍的范围。 我们现在将快速回顾其中的一些内容,但我们计划将来专门针对该主题开设一课。

NSDT工具推荐: Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎

1、抗锯齿

首先,让我们考虑 3D 渲染的一个基本问题。 如果放大我们在上一章中渲染的三角形图像,你会发现三角形的边缘并不规则(实际上,它不是特定于三角形的边缘,你还可以看到 棋盘图案在正方形的边缘是不规则的)。 你可以在图 1 中轻松看到的台阶称为锯齿(jaggies):

图 1:使用抗锯齿功能可以减少锯齿状边缘像素伪影

这些锯齿状边缘或阶梯状边缘(取决于你喜欢如何称呼它们)并不是人工制品,这只是三角形被分解为像素的结果。 我们对光栅化过程所做的是将连续表面(三角形)分解为离散元素(像素),这是我们在渲染简介中已经提到的过程。 这个问题类似于尝试用乐高积木表示连续的曲线或曲面。 这根本做不到,你总是会看到砖块(图 2):

图 2:当使用一个样本时,三角形会丢失。 但通过使用子像素,我们可以检测到该像素至少部分与三角形重叠。 像素颜色等于子像素颜色的总和除以子像素或样本的总数(在本例中为 16 个样本)。

渲染中解决此问题的方法称为抗锯齿(也表示为 AA:AntiAlias)。 我们不是每个像素仅渲染 1 个样本(通过测试像素中心的点是否覆盖三角形来检查像素是否与三角形重叠),而是将像素拆分为子像素并对每个子像素重复覆盖测试 。 当然,每个子像素只不过是另一个砖块,因此这并不能完全解决问题。 尽管如此,它仍然可以更精确地捕捉物体的边缘。 大多数情况下,像素被划分为 N × N 个子像素,其中 N 通常是 2 的幂(2、4、8 等),尽管从技术上讲它可以采用任何大于或等于 1 的值( 1、2、3、4、5 等)。 有多种方法可以解决此别名问题。 我们描述的方法属于基于采样的抗锯齿方法的范畴。

像素最终颜色的计算方式为所有子像素颜色的总和除以子像素总数。 让我们举一个例子(与像素一样,如果子像素或样本覆盖了一个三角形,那么它就会呈现该三角形的颜色,否则它就会呈现通常为黑色的背景颜色)。 想象一下三角形是白色的。 如果只有 2 或 4 个样本与三角形重叠,则最终像素颜色将等于 (0+0+1+1)/4=0.5。 像素不会是全白的,但也不会是全黑的。 因此,该过渡更加渐进,而不是在三角形边缘和背景之间具有“二元”过渡,这减少了阶梯式像素伪影。 这就是我们所说的抗锯齿。 要了解抗锯齿,你需要研究信号处理理论,这本身也是一个非常庞大且相当复杂的主题。

之所以最好选择N作为2的幂,是因为现在大多数处理器都可以并行运行多条指令,并且并行运行的指令数一般也是2的幂。你可以看一下 Web 适用于 CPU 特有的 SSE 指令集等事物,但 GPU 使用相同的概念。 SSE 是大多数现代 CPU 上都提供的一项功能,通常可用于同时(在一个周期内)运行 4 或 8 个浮点计算。 所有这一切意味着,以 1 次浮点运算的价格,你可以免费获得 3 或 7 次浮点运算。 理论上,这可以将渲染时间加快 4 或 8 倍(尽管永远无法达到该性能水平,因为你需要为设置这些指令付出一些代价)。 例如,你可以使用 SSE 指令以计算 1 像素的成本来渲染 2x2 超像素,因此,可以“免费”获得更平滑的边缘(阶梯状边缘不太明显)。

2、渲染像素块

加速光栅化的另一种常见技术是渲染像素块(blocks of pixels),但我们不是测试块中包含的所有像素,而是首先开始测试块角处的像素。 GPU 算法可以使用 8x8 像素块。 所使用的技术更加复杂,并且基于图块的概念,但我们在这里不做详细介绍。

如果该 8x8 网格的所有四个角都覆盖了三角形,那么网格的其他像素也必然会覆盖矩形(如图 7 所示):

图 7:如果 8x8 像素网格的 4 个角与三角形重叠,则网格的所有剩余像素也会覆盖该三角形

在这种情况下,无需测试所有其他像素,从而节省时间。 它们可以用三角形的颜色填充。 如果顶点属性需要跨像素块进行插值,这也很简单,因为如果你在块的角点计算了它们,那么需要做的就是在两个方向(水平和垂直)上线性插值它们。 此优化仅在三角形靠近屏幕且屏幕空间较大时才有效。 小三角形不会从这种技术中受益。

3、优化边缘函数

边缘函数也可以被优化。 我们再看一下函数的实现:

int orient2d(const Point2D& a, const Point2D& b, const Point2D& c)
{return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
}

回想一下,该函数中的 a 和 b 是三角形顶点,c 是像素坐标(在光栅空间中)。 需要注意的一件有趣的事情是,将为三角形边界框中包含的每个像素调用此函数。 尽管当我们迭代多个像素时,只有 c 发生变化。 变量 a 和 b 保持不变。 假设我们对方程求值一次并得到结果 w0:

w0 = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);

然后 c.x 增加一个量 s(每像素步长)。 w0 的新值将是:

w0_new = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x + s - a.x);

用第二个方程减去第一个方程,我们得到:

w0_new - w0 = -(b.y - a.y) * s;

项 -(b.y - a.y) * s 是给定三角形的常数值,因为 s 每次都是相同的量(一个像素),并且已经提到的 a 和 b 也是常数。 我们可以计算一次并将其存储在变量中(称为 w0_step),然后计算减少为:

w0_new = w0 + w0step;

你可以对 w1 和 w2 执行此操作,也可以对 c.y 步骤执行类似的操作。

边缘函数使用 2 个乘数和 5 个子数,但通过这个技巧,它可以简化为简单的加法(当然你需要计算初始值)。 这种技术在互联网上有详细记录。 我们不会在本课程中使用它,但我们将更详细地研究它,并在另一节专门介绍高级光栅化技术的课程中实现它。

4、使用定点坐标

最后,作为本节的总结,我们将简要讨论在光栅化阶段之前将最初以浮点格式定义的顶点坐标转换为定点格式的技术。 定点(fixed point)是整数的花哨词(实际上是正确的技术术语)。

当顶点坐标从 NDC 转换为栅格空间时,它们也会从浮点数转换为定点数。 我们为什么要这么做? 这个问题没有简单快速的答案。 但简而言之,我们只能说 GPU 使用定点运算,因为从计算的角度来看,操作整数比操作浮点数或双精度数更容易、更快(它只需要逻辑位运算)。 同样,这只是一个非常通用的解释。 从浮点到整数坐标的转换以及如何使用整数坐标实现光栅化过程是一个庞大而复杂的主题,在互联网上没有记录(你会发现关于这个主题的信息很少,考虑到这个过程是现代 GPU 工作方式的核心,就会感到非常奇怪)。

转换步骤涉及将顶点坐标四舍五入到最接近的整数。 不过,如果只这样做,那么你就会将顶点坐标捕捉到最近的像素角坐标。 当渲染静态图像时,这并不是什么大问题,但它会产生动画的视觉伪影(顶点在帧与帧之间被捕捉到不同的像素)。 解决方法是将数字转换为最小整数值,但同时保留一些位来编码顶点的子像素位置(顶点位置的小数部分)。

GPU 通常使用 4 位来编码子像素精度(你可以在图形 API 文档中搜索术语sub-pixel precision)。 换句话说,在 32 位整数上,1 位可能用于编码数字的符号,27 位用于编码顶点整数位置,4 位用于编码像素内顶点的小数位置。 这意味着顶点位置“捕捉”到 16x16 子像素网格的最近角,如图 8 所示(使用 4 位,可以表示 [1:15] 范围内的任何整数):

图 8:定点坐标

不知何故,顶点位置仍然会捕捉到某个网格角,但在这种情况下,捕捉的问题比顶点捕捉到像素坐标时的问题要小。 此转换过程会导致许多其他问题,其中之一是整数溢出(当算术运算的结果产生的数字大于可用位数编码的数字时,就会发生溢出)。 发生这种情况的原因是整数比浮点数覆盖的值范围更小。 当抗锯齿混入其中时,事情也会变得有点复杂。 详细探讨这个主题本身就需要一堂课。

固定点坐标可以进一步加速光栅化过程和边缘函数。 这就是将顶点坐标转换为整数的原因之一。 这些优化技术将在另一节课中介绍。

5、关于光栅化算法实现的注意事项

最后,我们将快速回顾一下源代码章节中提供的代码。 以下是其主要组件的描述:

我们将使用函数 computeScreenCooperatives 来计算屏幕坐标,使用针孔相机模型课程中详细介绍的方法。 这本质上是为了确保我们的渲染输出可以与同样使用基于物理的相机模型的 Maya 渲染进行比较:

float t, b, l, r; computeScreenCoordinates( filmApertureWidth, filmApertureHeight, imageWidth, imageHeight, kOverscan, nearClippingPLane, focalLength, t, b, l, r); 

函数 convertToRaster()是我们将三角形顶点坐标从相机空间转换到光栅空间的地方。 该函数类似于我们在上一课中实现的函数 computePixelCooperatives()。 请记住,在本课的第二章中,我们学习了一种将坐标从屏幕空间转换到 NDC 空间的方法(请记住,在 GPU 世界中,NDC 空间中的坐标在 [-1,1] 范围内)。 我们将在此函数中使用相同的重新映射方法。

另一件需要记住的重要事情是,直到上一课,投影点都是二维点。 从现在开始,它们将需要是 3D 点。 在这些点的 x 和 y 坐标中,我们将存储投影点在屏幕空间中的坐标。 在 z 坐标中,我们将存储顶点相机空间 z 坐标。 需要 z 坐标来解决可见性问题,如本课程第四章中所述:

convertToRaster(v0, worldToCamera, l, r, t, b, nearClippingPLane, imageWidth, imageHeight, v0Raster); 
convertToRaster(v1, worldToCamera, l, r, t, b, nearClippingPLane, imageWidth, imageHeight, v1Raster); 
convertToRaster(v2, worldToCamera, l, r, t, b, nearClippingPLane, imageWidth, imageHeight, v2Raster);

不要忘记,与三角形顶点关联的所有顶点属性也需要被这些顶点的 z 坐标“预除”(这是透视正确插值所需要的)。 它通常在三角形渲染之前完成(就在迭代像素的循环之前)。 但要小心,因为在代码中顶点 z 坐标设置为其倒数(以加快样本深度的计算)。 因此,我们将使用乘法而不是除法:

// precompute reciprocal of the vertex z-coordinate
v0Raster.z = 1 / v0Raster.z, 
v1Raster.z = 1 / v1Raster.z, 
v2Raster.z = 1 / v2Raster.z; Vec2f st0 = st[stindices[i * 3]]; 
Vec2f st1 = st[stindices[i * 3 + 1]]; 
Vec2f st2 = st[stindices[i * 3 + 2]]; // This is needed for perspective correct interpolation
st0 *= v0Raster.z, st1 *= v1Raster.z, st2 *= v2Raster.z;

该函数包含两个循环。 第一个迭代场景中的所有三角形(外部循环),第二个迭代与正在渲染的三角形重叠的边界框中包含的所有像素(内部循环)。 请注意,内循环中的一些变量是恒定的,因此可以预先计算。 例如,三角形顶点的 z 坐标的倒数就是这种情况,对于覆盖三角形的每个像素(以及需要除以它们各自的 z 坐标的顶点属性)进行线性插值:

// Outer loop. Loop over triangles
for (uint32_t i = 0; i < ntris; ++i) {...// Inner loop. Loop over pixelsfor (uint32_t y = y0; y <= y1; ++y) { for (uint32_t x = x0; x <= x1; ++x) {...}}
}

程序的其余部分很简单。 使用边缘函数技术测试每个像素的覆盖范围。 如果一个像素覆盖一个三角形,我们就计算像素的重心坐标。 然后我们使用这些坐标来计算样本的深度。 如果我们通过了深度缓冲区测试,我们就会使用新的深度值更新缓冲区,并使用三角形颜色更新帧缓冲区:

Vec3f pixelSample(x + 0.5, y + 0.5, 0); 
float w0 = edgeFunction(v1Raster, v2Raster, pixelSample); 
float w1 = edgeFunction(v2Raster, v0Raster, pixelSample); 
float w2 = edgeFunction(v0Raster, v1Raster, pixelSample); 
if (w0 >= 0 && w1 >= 0 && w2 >= 0) { w0 /= area; w1 /= area; w2 /= area; // linearly interpolate sample depthfloat oneOverZ = v0Raster.z * w0 + v1Raster.z * w1 + v2Raster.z * w2; float z = 1 / oneOverZ; // do we pass the depth buffer test?if (z < depthBuffer[y * imageWidth + x]) { depthBuffer[y * imageWidth + x] = z;// update frame buffer...}
}

为了使其在视觉上更有趣,我们将使用多种着色技术。 我们使用的模型只有一个顶点属性:st 或纹理坐标。 纹理坐标可用于创建棋盘图案,然后将其与称为面比的简单着色技巧相结合。 面向比采用三角形法线(我们可以通过三角形任意两条边之间的简单叉积来计算)和视图方向之间的点积。 视图方向只是由正在着色的三角形上的点 P 和相机位置 E 定义的向量。由于程序中该点的所有点都是在相机空间中定义的,因此相机位置(或眼睛位置)为 简单的 E=(0,0,0)。 因此,视图方向可以简单地计算为 -P,然后需要对其进行归一化。 点积可能为负,因此我们需要限制它(我们只需要正值):

Vec3f n = (v1Cam - v0Cam).crossProduct(v2Cam - v0Cam); 
n.normalize(); 
Vec3f viewDirection = -pt; 
viewDirection.normalize(); 
// facing ratio 
float nDotView =  std::max(0.f, n.dotProduct(viewDirection));
// Get triangle vertices in camera space.
Vec3f v0Cam, v1Cam, v2Cam; 
worldToCamera.multVecMatrix(v0, v0Cam); 
worldToCamera.multVecMatrix(v1, v1Cam); 
worldToCamera.multVecMatrix(v2, v2Cam); // Divide them by the respective z-coordinate as with any other vertex attribute and interpolate using 
// barycentric coordinates.
float px = (v0Cam.x/-v0Cam.z) * w0 + (v1Cam.x/-v1Cam.z) * w1 + (v2Cam.x/-v2Cam.z) * w2; 
float py = (v0Cam.y/-v0Cam.z) * w0 + (v1Cam.y/-v1Cam.z) * w1 + (v2Cam.y/-v2Cam.z) * w2; // P in camera space
Vec3f pt(px * z, py * z, -z);

最后,帧缓冲区的内容存储在PPM文件中。

这是程序应该产生的结果。 在左侧,可以看到 Maya 中对象的渲染。 右边是我们的渲染。 正如你所看到的,结果在对象位置和阴影方面是相同的。 Maya 使用一种称为随机采样的技术,该技术可以使锯齿伪像比我们的渲染中稍微不那么明显,但差异很微妙:

正如你所看到的,渲染并没有什么神奇之处。 当了解规则后,你可以重现专业应用程序生成的图像。

作为奖励,我们导出了图像中每个像素重叠的三角形上的点在世界空间中的位置。 然后我们在 3D 查看器中显示所有点。 您可以在下面看到结果。 毫不奇怪,点仅出现在相机直接可见的对象部分上。 这表明深度缓冲技术按预期工作。 对象背面被另一个三角形隐藏的每个三角形都不会被渲染。 右图显示了同一点集的特写:

6、结束语

光栅化的主要优点是简单和速度。 主要缺点是它仅对解决可见性问题有用。 请记住,渲染涉及两个步骤:可见性和着色。 该算法在着色方面没有用处。

在本课中,我们已经介绍了有关光栅化算法所需了解的所有信息。 你可以从中学到的其他内容并不是关于技术本身,而是更多关于优化它(如何并行运行它、如何使其高效、如何在 GPU 上运行它等)。

本系列教程的源码可以从这里下载。


原文链接:光栅化算法优化 - BimAnt

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

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

相关文章

富时中国a50指数期货:探寻涨跌脉络

富时中国A50指数期货是衡量中国大陆50家最具代表性上市公司的指数。作为衍生品交易市场的重要一环&#xff0c;A50指数期货是投资者关注的焦点之一。其涨跌走势直接反映了中国大陆股市的整体表现和投资者情绪。 A50指数期货简介 富时中国A50指数期货由新加坡交易所&#xff0…

免费且好用的 MySQL 客户端

DBeaver 支持 Mac、Windows、Linux&#xff0c;提供 Eclipse 插件。社区版免费&#xff0c;支持主流的关系型数据库 官网地址&#xff1a;DBeaver Community | Free Universal Database Tool MySQL WorkBench MySQL WorkBench 是官方出品的客户端&#xff0c;支持 Mac、Windo…

AntDB数据库参加ACDU中国行杭州站,分享数据库运维实践与经验

关于ACDU与中国行&#xff1a; ACDU是由墨天轮社区举办的中国数据库联盟的品牌活动之一&#xff0c;在线下汇集数据库领域的行业知名人士&#xff0c;共同探讨数据库前沿技术及其应用&#xff0c;促进行业发展和创新的平台&#xff0c;也为开发者们提供友好交流的机会。 AntD…

SpringCloud微服务(简略笔记二)

Docker 概念 docker和虚拟机的差异 * docker是一个系统进程&#xff1b;虚拟机是在操作系统中的操作系统 * docker体积小&#xff0c;启动速度&#xff0c;性能好&#xff0c;虚拟机体积大&#xff0c;启动速度慢&#xff0c;性能一般 镜像和容器 镜像&#xff08;image&…

使用 Python 实现简单的爬虫框架

爬虫是一种自动获取网页内容的程序&#xff0c;它可以帮助我们从网络上快速收集大量信息。在本文中&#xff0c;我们将学习如何使用 Python 编写一个简单的爬虫框架。 一、请求网页 首先&#xff0c;我们需要请求网页内容。我们可以使用 Python 的 requests 库来发送 HTTP 请…

【STM32F103】ADC 模拟数字转换器

ADC ADC&#xff08;Analog-to-Digital Converter&#xff09;&#xff0c;模拟-数字转换器&#xff0c;也叫模数转换器&#xff0c;可以将连续变化的模拟信号转换为离散的数字信号。 我们可以外接上将采集信号转为模拟信号的模块&#xff0c;如光敏电阻传感器&#xff0c;热…

仓库拣货应用案例

多个订单的光挑选 PTL用于WIP&#xff08;正在工作&#xff09;机架 首先&#xff0c;我们的过程。 制造工厂很大&#xff0c;有大量的制造工具&#xff0c;非常密集&#xff0c;而且大量的高设备可能会阻碍无线电 晶片储存在一个密封的载体中&#xff0c;它们从一台机器运输…

Windows CMD cd命令无法使用,路径无法更改解决方法

故障表现&#xff1a; 1.cmd使用命令cd更改路径后&#xff0c;路径仍然显示为初始路径 解决方法&#xff1a; 1.先更改至指定盘符&#xff0c;再使用cd命令 2.使用cd命令后&#xff0c;再更改至指定盘符

35.@Component, @Controller, @Repository, @Service 有何区别

@Component, @Controller, @Repository, @Service 有何区别 @Component:这将 java 类标记为 bean。它是任何 Spring 管理组件的通用构造型。spring 的组件扫描机制现在可以 将其拾取并将其拉入应用程序环境中。@Controller:这将一个类标记为 Spring Web MVC 控制器。标有它的…

GO EASY 游戏框架 之 GRPC 扩展篇 04

1 Overview 此章节是对第三章的一个补充。同样属于RPC的篇章。是专门为了类似游戏服务这种需要指定RPC服务端具体地址&#xff0c;也就是具体是哪台机器的某应用进程&#xff0c;通信的方式。笔者简单的封装了下保证基本的服务稳定&#xff0c;且具备服务发现的属性即可&#…

[C++进阶]---AVL树模拟实现

目录 1.AVL树的概念2.AVL树模拟实现2.1AVL树节点的定义2.2AVL的插入2.3AVL树的旋转2.3.1左单旋2.3.2右单旋2.3.3右左双旋2.3.3.1旋转情况分析2.3.3.2平衡因子更新分析 2.3.4右左双旋2.3.4.1旋转情况分析2.3.4.2平衡因子更新分析 2.3.5AVL树的验证 3.AVL模拟实现源码4.总结 1.AV…

jetpack compose 学习(2)

jetpack compose 学习(1) 学会了如何创建一个compose项目,并成功run了起来 今天学习run起来的界面配置 找启动界面,当然是先找到界面的配置文件: androidManifest.xml 这个文件,然后通过启动项找到主界面, 第二步 按住ctrl 鼠标指向MainActivity 即可跳转主界面 进入后的界面…

_try_finally原理探究

无论try结构体中是什么代码&#xff0c;都会执行finally里面的代码 局部展开 当try里面没有异常&#xff0c;而是return、continue、break等语句时&#xff0c;就不会走_except_handle3这个函数&#xff0c;而是调用_local_unwind2进行展开 然后调用[ebx esi*4 8] 跟进去就到…

MySQL低版本中:字符串中的数字、英文字符、汉字提取

我们如何提醒一个字段中的汉字和数字呢 高版本指mysql8.0以上 使用sql语句 SELECT REGEXP_REPLACE(column_name, [^\\p{Han}], ) AS chinese_characters FROM table_name;其中 column_name指名称列&#xff0c;table_name是表名 2.低版本使用 需要新建函数 DELIMITER $$DR…

Java调用百度翻译API和调用有道翻译API进行翻译

目录 界面编写 调用百度API 调用有道API 源代码 界面编写 我们首先需要设计出这个翻译程序的GUI界面&#xff0c;我们写一个类继承自JFrame类&#xff0c;用来展示程序的主窗口&#xff0c;设置好窗口的名称和大小&#xff0c;设置在关闭窗口时终止程序&#xff0c;为了界…

高效数组处理的Numpy入门总结

NumPy是Python中一个重要的数学库&#xff0c;它提供了高效的数组操作和数学函数&#xff0c;是数据科学、机器学习、科学计算等领域的重要工具。下面是一个简单的NumPy学习教程&#xff0c;介绍了NumPy的基本用法和常用函数。 安装NumPy 在使用NumPy之前&#xff0c;需要先安…

使用Highcharts创建动态图表

Highcharts是一个功能强大且易于使用的JavaScript图表库&#xff0c;本文记录下创建&#xff0c;使用标题&#xff0c;副标题等功能&#xff1b; 首先&#xff0c;我们需要在HTML页面中引入Highcharts库和相关的样式和脚本文件。请将以下代码添加到页面的头部&#xff1a; HTML…

删除Tencent files

QQ或者TIM卸载后,它还会残留在电脑上.要修改会说需要权限,修改权限又会重新变回去.这时候可以把整个文档放到其它盘,然后就可以删除Tencent files 方法是右键文档 选中属性–位置 然后就可以改掉了

【后端学前端】第三天 css动画 动态搜索框(定位、动态设置宽度)

1、学习信息 视频地址&#xff1a;css动画 动态搜索框&#xff08;定位、动态设置宽度&#xff09;_哔哩哔哩_bilibili 2、源码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>test3</title>…

14:00面试,14:08就出来了,问的问题有点变态。。。。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到5月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%…