【UE5】将2D切片图渲染为体积纹理,最终实现使用RT实时绘制体积纹理【第二篇-着色器制作】
在上一篇文章中,我们已经理顺了实现流程。
接下来,我们将在UE5中,从头开始一步一步地构建一次流程。
通过这种方法,我们可以借助一个熟悉的开发环境,使那些对着色器不太熟悉的朋友们更好地理解着色器的工作原理。
这篇文章中,我们将使用这样一个纹理,点此下载
我喜欢先实现着色器,因此先用这个贴图作为占位
这是一个体积纹理的二维展开,最终我们会用RT纹理来代替这种静态的贴图,在runtime用RT进行实时的绘制
创建材质
首先创建材质,并将混合模式调整为 半透明
,着色模式调整为 无光照
制作基础体积着色器
一、光线步进
采样贴图并通过步进对密度进行累计
首先,我们先创建Custom节点,并添加输入
Tip:
可以复制下方代码,然后在UE中,通过输入
右键鼠标,进行粘贴((InputName="Tex"),(InputName="XYFrames"),(InputName="NumFrames"),(InputName="MaxSteps"),(InputName="StepSize"),(InputName="LocalCamVec"),(InputName="CurPos"))
输入 | 说明 |
---|---|
Tex | 将要进行采样的2D切片纹理对象 |
XYFrames | float2 切片的XY数量 |
NumFrames | 切片总数 |
MaxSteps | 最大步进数 |
StepSize | 步大小 |
LocalCamVec | 本地空间相机方向 |
CurPos | 采样位置 |
接下来我们将编写一个 光线步进(Ray Marching) 着色器,当然在这里更准确的称呼应该是Camera Ray。
其基本原理是,让屏幕上的每个像素沿着相机的朝向发射射线。这条射线从起点到终点的过程被称为"步进(Step)"。在每一步中,我们采样纹理,如果纹理中有值,则将其累加,这个累加值称为每一步的密度。达到最大步数后,通过查看这条射线累积了多少密度,就可以确定这个像素的光线穿透程度。
在代码中粘贴如下代码,对每一步的功能进行了注释
//创建变量,从0开始累加沿相机方向步进过程中的总密度
float accumdens = 0;//使用 MaxSteps 作为最大步数进行循环,每次循环执行以下操作
for (int i = 0; i < MaxSteps; i++)
{// 在当前步进位置进行纹理采样,采样的是 R 通道// PseudoVolumeTexture 函数用于伪体积纹理采样,函数需要的参数在括号内传递float cursample = PseudoVolumeTexture(Tex, TexSampler, saturate(CurPos), XYFrames, NumFrames).r;// 将当前采样到的密度值累加到总密度中// 乘以步长是为了将采样密度与步进距离相匹配accumdens += cursample * StepSize;// 为下次循环更新射线位置,沿着相机方向步进CurPos += -LocalCamVec * StepSize;
}//返回累计结果
return accumdens;
创建对应变量输入,并将其连接到输出,就可以查看结果了
有几个新手要注意的点:
- Tex 输入的是“纹理对象”而不是“纹理采样”
- 使用TransfromVector函数将CameraVector由场景转换为本地
- 如果你是较早的UE5版本,Custom的代码要填在"这里"
- 代码中的PseudoVolumeTexture是内建的采样函数,运作原理在第一篇
- 函数
BoundingBoxBased_0-1_UVW
就像他的名字一样,输出bound体积中的每个位置,你可以将RGB
连接到输出查看
他会基于Bound提供位置
二、修复伪影
当从侧方看去,会发现有伪影
正面 | 侧面 |
---|---|
![]() | ![]() |
这里发生的事:
我们可以力大砖飞!
我们可以力大砖飞 的通过提高MaxSteps解决,但我们有更好的办法:
为了按照上图中的方式进行采样,我们将要使用这个函数:
他属于Volumetrics插件,如果你开启了这个插件就可以直接使用
注意:
- 如果并不想开启这个插件,接下来将制作这个函数
- 如果你启用了这个插件,也需要将此函数复制一个,因为后续需要对其修改
创建材质函数VolumeBoxIntersect
为VolumeBoxIntersect函数建立输入和输出
输入① | 输入说明 | 输出② | 输出说明 |
---|---|---|---|
Plane alignment | 对齐起始位置与平面间隔以减少采样伪影 | Box Distance | - |
Steps | 应该使用多少步进来进行平面对齐。应与步进匹配 | Intersection Position | - |
创建Custom函数
重命名为 Ray March Cube Setup
要注意:
- 输出类型为float4
- 在这里将Custom节点重命名
输入粘贴为:
((InputName="PlaneAlignment",Input=(Expression="/Script/Engine.MaterialExpressionFunctionInput'/Engine/Transient.MaterialFunction_0:MaterialExpressionFunctionInput_1'")),(InputName="MaxSteps",Input=(Expression="/Script/Engine.MaterialExpressionFunctionInput'/Engine/Transient.MaterialFunction_0:MaterialExpressionFunctionInput_0'")),(InputName="scenedepth",Input=(Expression="/Script/Engine.MaterialExpressionSceneDepth'/Engine/Transient.MaterialFunction_0:MaterialExpressionSceneDepth_1'",Mask=1,MaskR=1)))
代码为:
float localscenedepth = scenedepth;float3 camerafwd = mul(float3(0.00000000,0.00000000,1.00000000),(float3x3)ResolvedView.ViewToTranslatedWorld);float3 depthpos = ((Parameters.CameraVector * localscenedepth) / abs( dot( camerafwd, Parameters.CameraVector ) ) );depthpos = mul(depthpos, (float3x3)WSDemote(GetWorldToLocal(Parameters))).xyz;depthpos /= 256;localscenedepth = length(depthpos);//0-1
//localscenedepth /= (GetPrimitiveData(Parameters).LocalObjectBoundsMax.x * 2 * scale);
//localscenedepth /= abs( dot( camerafwd, Parameters.CameraVector ) );//bring vectors into local space to support object transforms
float3 localcampos = mul(float4( DFDemote(ResolvedView.WorldCameraOrigin),1.00000000), (WSDemote(GetWorldToLocal(Parameters)))).xyz;
float3 localcamvec = -normalize( mul(Parameters.CameraVector, (float3x3)WSDemote(GetWorldToLocal(Parameters))) );//make camera position 0-1
localcampos = (localcampos / (GetPrimitiveData(Parameters).LocalObjectBoundsMax.x * 2)) + 0.5;float3 invraydir = 1 / localcamvec;float3 firstintersections = (0 - localcampos) * invraydir;
float3 secondintersections = (1 - localcampos) * invraydir;
float3 closest = min(firstintersections, secondintersections);
float3 furthest = max(firstintersections, secondintersections);float t0 = max(closest.x, max(closest.y, closest.z));
float t1 = min(furthest.x, min(furthest.y, furthest.z));float planeoffset = 1-frac( ( t0 - length(localcampos-0.5) ) * MaxSteps );t0 += (planeoffset / MaxSteps) * PlaneAlignment;
t1 = min(t1, localscenedepth);
t0 = max(0, t0);float boxthickness = max(0, t1 - t0);float3 entrypos = localcampos + (max(0,t0) * localcamvec);return float4( entrypos, boxthickness );
计算过程上一篇有介绍,这里不赘述,效果如下
现在我们已经完成了对伪影的修复。可以看到,原本四周存在伪影的部分现在呈现出一种“扭曲”效果,这是因为当前的 MaxSteps
设定为 16。
为了获得更好的效果,可以将 MaxSteps
适当提升到 32 或 64,这样仍能保持不错的性能。
如果不进行伪影修复,而是通过增加步数的方法(力大砖飞)来提升细节质量,通常需要将
MaxSteps
调至 256 甚至更高,才能达到相同的细节水平。
而循环里目前仅仅只有三个步骤,后续扩展效果几乎不可能,因此这一步伪影处理是必须的
三、相机进入内部
现在,相机不可以进入box内部。因为着色器只在模型表面进行渲染
我们可以力大砖飞!
我们可以力大砖飞 的通过开启材质双面解决,但我们依旧有更好的办法:
创建法线向内的Box模型
使用建模模式,快速的创建一个法线向内的Box
(如果你有轴在中心的法线向内的box模型则可以跳过)
进入建模模式
如果你没找到建模模式,你需要开启引擎的插件:
创建Box
- 选择建模模式
- 选择创建功能
- 选择创建Box
- 枢纽点选择居中
- 在场景中单击,放置模型
- 点击接受创建模型
调整法线
- 保持选中模型
- 选择属性修改
- 选择法线修改
- 勾选翻转法线
- 接受修改
现在将材质放入
可以看到相机进入"内部"也不会超出模型承载的渲染范围
四、介质光吸收
将输出连入不透明度,再加上一个颜色。此时能更好的看到它的密度看上去非常“扁平”。
为此要使用布格-朗伯-比尔定律,来更好的计算介质对光的吸收
它的公式在上一篇中有介绍,这里不赘述,这里我们直接使用自带的函数BeersLaw
它将返回有多少光被材质吸收
现在看上去好多了。
虽然还没有阴影和光照,但在这之前,我们先去处理另外的问题
五、体积深度
我们目前还缺乏体积的深度,体积只是被渲染到了模型的内表面 ,通过这个对比可以看到发生了什么:
shader | mesh |
---|---|
![]() | ![]() |
1.禁用深度测试
现在我们为其重新制作深度
为此进入材质,禁用深度测试,接下来深度由我们说的算
2.制作深度
计算 MaxSteps
BoxDistance是框距离,相当于是射线穿透框的厚度,将其乘MaxSteps对其缩放,使用Floor取整,且使用clamp确保它不会超过限制
修改 VolumeBoxIntersect
可以看到当前的深度对的不多
现在要修复这个问题:
重新进入到 VolumeBoxIntersect 函数的 Custom
这里有几行被注释掉的内容,大体上是用于将局部场景深度转换到 0-1 范围内
为Custom增加输入 scale
scale = length(TransformLocalVectorToWorld(Parameters,float3( 1.0 , 0.0 , 0.0 )).xyz);
其蓝图也就是:
Tip:
Q:为什么不直接写进cutom?
A:尽最大可能的把计算过程写为蓝图,而不要在custom内计算
看编译后的HLSL就会知道,UE的材质编译器会对蓝图的内容进行接近疯狂的优化(而custom不会,写什么就是什么)
(后续会用蓝图重制这个custom的大部分内容,但目前先这样,当下还有很多工作没完成)
然后我们注释掉红框,并解除绿框的注释:
修改后的函数如下:
修改后的 Ray March Cube Setup
代码为:
float localscenedepth = scenedepth;float3 camerafwd = mul(float3(0.00000000,0.00000000,1.00000000),(float3x3)ResolvedView.ViewToTranslatedWorld);/*
float3 depthpos = ((Parameters.CameraVector * localscenedepth) / abs( dot( camerafwd, Parameters.CameraVector ) ) );depthpos = mul(depthpos, (float3x3)WSDemote(GetWorldToLocal(Parameters))).xyz;depthpos /= 256;localscenedepth = length(depthpos);
*///0-1
localscenedepth /= (GetPrimitiveData(Parameters).LocalObjectBoundsMax.x * 2 * scale);
localscenedepth /= abs( dot( camerafwd, Parameters.CameraVector ) );//bring vectors into local space to support object transforms
float3 localcampos = mul(float4( DFDemote(ResolvedView.WorldCameraOrigin),1.00000000), (WSDemote(GetWorldToLocal(Parameters)))).xyz;
float3 localcamvec = -normalize( mul(Parameters.CameraVector, (float3x3)WSDemote(GetWorldToLocal(Parameters))) );//make camera position 0-1
localcampos = (localcampos / (GetPrimitiveData(Parameters).LocalObjectBoundsMax.x * 2)) + 0.5;float3 invraydir = 1 / localcamvec;float3 firstintersections = (0 - localcampos) * invraydir;
float3 secondintersections = (1 - localcampos) * invraydir;
float3 closest = min(firstintersections, secondintersections);
float3 furthest = max(firstintersections, secondintersections);float t0 = max(closest.x, max(closest.y, closest.z));
float t1 = min(furthest.x, min(furthest.y, furthest.z));float planeoffset = 1-frac( ( t0 - length(localcampos-0.5) ) * MaxSteps );t0 += (planeoffset / MaxSteps) * PlaneAlignment;
t1 = min(t1, localscenedepth);
t0 = max(0, t0);float boxthickness = max(0, t1 - t0);float3 entrypos = localcampos + (max(0,t0) * localcamvec);return float4( entrypos, boxthickness );
深度已经正常,一切都好起来了…?
六、精度修复
当你试图缩放cube,你会发现它破碎了
眼看着美好的事物在你的操作下支离破碎,这可太令人崩溃了,究其原因在于
TransformVector
节点的精度不足,那么我们使用Custom手搓一个使用LWC精度的替代节点吧:
- 创建Custom,命名为
TransFromVector_WorldToLocal
- 添加输入
CameraVector
- 代码如下
return normalize(mul(CameraVector,(float3x3)LWCToFloat(GetPrimitiveData(Parameters).WorldToLocal)));
七、阶梯纹理修复
没有时间庆祝,接下来赶到战场的是阶梯问题。深度和缩放一旦被修复,图中画圈位置的阶梯状图形就会非常显眼。
这很明显这是由step产生的,因为我们的步(step)是预计算的,因此步与步之间就形成了这种阶梯纹理
我们可以力大砖飞!
我们可以力大砖飞 的通过增加MaxSteps数量,用更小的细分来解决,但总是有更好的办法:
实际上我们只需要在进行额外一次采样就可以了
- 使用frac取小数作为一小步
FinalStepSize
输入 - 在Custom中的for后再进行一次采样
修改后代码如下:
//创建变量,从0开始累加沿相机方向步进过程中的总密度
float accumdens = 0;//使用 MaxSteps 作为最大步数进行循环,每次循环执行以下操作
for (int i = 0; i < MaxSteps; i++)
{// 在当前步进位置进行纹理采样,采样的是 R 通道// PseudoVolumeTexture 函数用于伪体积纹理采样,函数需要的参数在括号内传递float cursample = PseudoVolumeTexture(Tex, TexSampler, saturate(CurPos), XYFrames, NumFrames).r;// 将当前采样到的密度值累加到总密度中// 乘以步长是为了将采样密度与步进距离相匹配accumdens += cursample * StepSize;// 为下次循环更新射线位置,沿着相机方向步进CurPos += -LocalCamVec * StepSize;
}//修复阶梯 额外在循环后再进行一次小步(FinalStepSize)的采样,累计到密度
CurPos -= -LocalCamVec * StepSize;
CurPos += -LocalCamVec * StepSize * FinalStepSize;
float cursample = PseudoVolumeTexture(Tex, TexSampler, saturate(CurPos), XYFrames, NumFrames).r;
accumdens += cursample * StepSize * FinalStepSize;//返回累计结果
return accumdens;
到此一个最基本的体积纹理渲染就做好了
在下一篇进行【制作进阶体积着色器】,继续为其实现光照等效果
相关文章:

【UE5】将2D切片图渲染为体积纹理,最终实现使用RT实时绘制体积纹理【第二篇-着色器制作】
在上一篇文章中,我们已经理顺了实现流程。 接下来,我们将在UE5中,从头开始一步一步地构建一次流程。 通过这种方法,我们可以借助一个熟悉的开发环境,使那些对着色器不太熟悉的朋友们更好地理解着色器的工作原理。 这篇…...

【OceanBase 诊断调优】—— GC问题根因分析
GC 流程涉及到 RS 的状态切换和 LS 的资源安全回收,流程上较长。且 GC 线程每个租户仅有一个,某个日志流 GC Hang 死时会卡住所有其余日志流的 GC,进而造成更大的影响。 本文档会帮助大家快速定位到 GC 故障的模块,直达问题核心。…...

图像面积计算一般方法及MATLAB实现
一、引言 在数字图像处理中,经常需要获取感兴趣区域的面积属性,下面给出图像处理的一般步骤。 1.读入的彩色图像 2.将彩色图像转化为灰度图像 3.灰度图像转化为二值图像 4.区域标记 5.对每个区域的面积进行计算和显示 二、程序代码 %面积计算 cle…...

指挥平台在应急场所中的主要表现有哪些
在应对自然灾害、公共安全事件等突发危机时,指挥平台作为应急管理体系的核心枢纽,其重要性不言而喻。它不仅承载着信息的快速汇聚、精准分析与高效调度功能,更在应急场所中有一定的关键表现。接下来就跟着北京嘉德立一起了解一下。 一、信息集…...

智能养殖场人机交互检测系统源码分享
智能养殖场人机交互检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Co…...

数据集-目标检测系列-海洋鱼类检测数据集 fish>> DataBall
数据集-目标检测系列-海洋鱼类检测数据集 fish>> DataBall 数据集-目标检测系列-海洋鱼类检测数据集 fish 数据量:1W 数据项目地址: gitcode: https://gitcode.com/DataBall/DataBall-detections-100s/overview github: https://github.com/…...

网络威慑战略带来的影响
文章目录 前言一、网络威慑的出现1、人工智能带来的机遇二、网络空间的威慑困境1、威慑概念的提出2、网络威慑的限度3、人类对网络威胁的认知变化4、网络空间的脆弱性总结前言 网络威慑是国家为应对网络空间风险和威胁而采取的战略。冷战时期核威慑路径难以有效复制至网络空间…...
决策树算法在机器学习中的应用
决策树算法在机器学习中的应用 决策树(Decision Tree)算法是一种基本的分类与回归方法,它通过树状结构对数据进行建模,以解决分类和回归问题。决策树算法在机器学习中具有广泛的应用,其直观性、易于理解和实现的特点使…...

Leetcode面试经典150题-39.组合总数进阶:40.组合总和II
本题是扩展题,真实考过,看这个题之前先看一下39题 Leetcode面试经典150题-39.组合总数-CSDN博客 给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的每个数…...

ProcessOn为什么导出有水印!!!(利用SVG转PNG)
processon-svg2png ProcessOn 一个非常好用的思维导图网站,但是为什么导出有水印!!!。 功能 支持按钮拖拽支持将流程图svg 转成 png下载支持修改自定义文字下载svg(开发中) 安装/使用方法 安装并使用…...
插入、更新与删除MySQL记录
在现代应用开发中,数据库操作是非常重要的一环。作为程序员,熟练掌握数据库的增删改功能,能够更有效地管理数据并提高开发效率。 本课程将围绕插入、更新与删除记录这三个操作展开,涵盖SQL中的常见语句:INSERT INTO、UPDATE 和 DELETE,并结合实际应用中的常见问题讨论如…...

【ARM】armv8的虚拟化深度解读
Type-1 hypervisor Type-1虚拟化也叫做Bare metal, standalone, Type1 Type2 hypervisor Type-2虚拟化也叫做hosted, Type-2 VM和vCPU(虚拟机和虚拟cpu) 在一个VM(虚拟机)中有多个vCPU,多个vCPU可能属于同一个Vritual Processor。 EL2…...

9/24作业
1. 分文件编译 分什么要分文件编译? 防止主文件过大,不好修改,简化编译流程 1) 分那些文件 头文件:所有需要提前导入的库文件,函数声明 功能函数:所有功能函数的定义 主函数:main函数&…...

Leetcode 106. 从中序与后序遍历序列构造二叉树
给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。 示例 1: 输入:inorder [9,3,15,20,7], postorder [9,15,7,20,3] 输出:[3…...

针对考研的C语言学习(定制化快速掌握重点1)
1.printf函数的几个要点 printf函数中所有的输出都是右对齐的,除非在%后面添加负号,则表示左对齐 #include<stdio.h> int main() {int num 10;int nums 100;float f 1000.2333333333;printf("%3d\n", nums);//%3d表示输出的总宽度至…...
【大数据入门 | Hive】DDL数据定义语言(数据库DataBase)
1. 数据库(DataBase) 1.1 创建数据库 语法: CREATE DATABASE [IF NOT EXISTS] database_name [COMMENT database_comment] [LOCATION hdfs_path] [WITH DBPROPERTIES (property_nameproperty_value, ...)]; 案例: (1)创建一个…...

CNVD漏洞和证书挖掘经验总结
前言 本篇文章主要是分享一下本人挖掘CVND漏洞碰到的一些问题,根据过往成功归档的漏洞和未归档的漏洞总结出的经验,也确实给审核的大佬们添了很多麻烦(主要真的没人教一下,闷着头尝试犯了好很多错误,希望各位以后交一个…...
阿里rtc旁路推流TypeScript版NODE运行
阿里云音视频服务云端录制typescript版本; 编译后可以使用 node index.js运行 package.json 版本 // npm install --save alicloud/rtc201801112.3.0 "alicloud/rtc20180111": "^2.3.0",引入 import Client, { StartCloudRecordRequest, StopCloudRecord…...
计算机书籍分享
0.简介 数据库系统概念、深入理解计算机系统、领域驱动设计、Linux高性能服务器编程 高清版本pdf 1.链接 数据库系统概念: 链接: https://pan.baidu.com/s/17zz7QFevV2Eni9qHJyLEGA 提取码: wfrx 深入理解计算机系统 链接: https://pan.baidu.com/s/19yiJG8GqHJR…...
处理ASAM-MDF格式的开源python库asammdf
asammdf是一个强大的Python库,专为处理ASAM(Association for Standardization of Automation and Measuring Systems)MDF(Measurement Data Format)文件而设计。MDF是一种用于存储测量和诊断数据的标准格式,…...

(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...

2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...

C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...

JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...
jmeter聚合报告中参数详解
sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample(样本数) 表示测试中发送的请求数量,即测试执行了多少次请求。 单位,以个或者次数表示。 示例:…...