【Unity3D】Shader变体管理流程-变体剔除

news2024/11/15 11:00:44

一、什么是Shader变体管理

  想要回答这个问题,要看看什么是Shader变体。

1. 变体

  我们用ShaderLab编写Unity中的Shader,当我们需要让Shader同时满足多个需求,例如说,这个是否支持阴影,此时就需要加keyword(关键字),例如在代码中#pragma multi_compile SHADOW_ON SHADOW_OFF,对逻辑上有差异的地方用#ifdef SHADOW_ON#if defined(SHADOW_ON)区分(#if defined()的好处是可以有多个条件,用与、或逻辑运算连接起来):

Light mainLight = GetMainLight();
float shadowAtten = 1;
#ifdef SHADOW_ON
    shadowAtten = CalculateShadow(shadowCoord);
#endif
float3 color = albedo * max(0, dot(mainLight.direction, normalWS)) * shadowAtten;

 

然后对需要的材质进行material.EnableKeyword("SHADOW_ON")material.DisableKeyword("SHADOW_ON")开关关键字,或者用Shader.EnableKeyword("SHADOW_ON")对全场景包含这一keyword的物体进行设置
  上述情况是开关的设置,还有设置配置的情况,例如说我希望高配光照计算用PBR基于物理的光照计算方式,而低配用Blinn-Phong,其他计算例如阴影、雾效完全一致,也可以将光照计算用变体的方式分隔。

如果是shader编写的新手,可能有两个问题:
①我不能直接传递个变量到shader里,用if实时判断吗?
答:不可以,简单来说,由于gpu程序需要高度并行,shader中的分支判断需要将if else两个分支都走一遍,假如你的两个需求都有不短的代码,这样的开销太大且不合理。
②我不可以直接将shader复制一份出来改吗?
答:不是很好,例如你现在复制一份shader出来,还需要对应脚本去找到需要替换的shader然后替换。更重要的是,当你的shader同时包含很多需要切换的效果:阴影、雾效、光照计算、附加光源、溶解、反射等等,总不能有一个需求就shader*2是吧

  当你有多组关键字,阴影是否开关,是否有雾效时,你可能会写出下面这样的关键字声明:

#pragma multi_compile SHADOW_OFF SHADOW_ON
#pragma multi_compile FOG_OFF FOG_ON
#pragma multi_compile ADDLIGHT_OFF ADDLIGHT_ON
#pragma multi_compile REFLECT_OFF REFLECT_ON
//something keyword ...

 

这种写法属于比较死亡的写法,别在意,后面自然会说出各种写法中不好的地方并提出回避建议。

  而对于当前材质,就会利用上述的关键字进行排列组合,例如一个“不希望接受阴影,希望有雾,需要附加光源,不带反射”,得到的Keyword组合就是:SHADOW_OFF FOG_ON ADDLIGHT_ON REFLECT_OFF,这个Keyword组合就是一个变体。对于上面这个例子,可以得到2的4次方16个变体。

 我们知道了什么是变体,再来回答为什么要变体管理。
  可以发现上述例子中,每多一条都会乘2,实际上一列keyword声明可以不止两个,声明三个、甚至更多也是可能的。
  但不管怎么说,随着#pragma multi_compile的增加,变体数量会指数增长。这样会带来什么问题呢?
  这时候需要了解下shader到底是什么。

2. Shader

  我就当大家都知道,ShaderLab其实不是很底层的东西,它封装了图形API的Shader,以及一堆渲染命令。对于图形API,Shader是gpu的程序,不同API上传shader略有区别,例如OpenGL:

GLuint vertex_shader;
GLchar * vertex_shader_source[];//glsl源码
//创建并将源码传递给GPU
vertex_shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex_shader, 1, vertex_shader_source, NULL);
//编译
glCompileShader(vertex_shader);
//绑定
glAttachShader(program, vertex_shader);

 DX12/Vulkan的编译方式有很多,可以提前编译成二进制/中间语言的dxbc/spirv,也可以用hlsl/glsl实时生成dxbc/spirv传递给GPU,例如DX12使用D3DCompileFromFile实时编译hlsl到dxbc:

ComPtr<ID3DBlob> byteCode = nullptr;//二进制dxbc
D3DCompileFromFile(filename.c_str(), defines, D3D_COMPILE_STANDARD_FILE_INCLUDE,
        entrypoint.c_str(), target.c_str(), compileFlags, 0, &byteCode, nullptr);

 对于现在我们来说主要关注前两个参数,第一个是读取的文件名没什么好说的,第二个是D3D_SHADER_MACRO的数组:

typedef struct _D3D_SHADER_MACRO
{
    LPCSTR Name;
    LPCSTR Definition;
}   D3D_SHADER_MACRO;

实际上传入类似这样:

const D3D_SHADER_MACRO defines[] =
{
    "FOG", "1",
    "ALPHA_TEST", "1",
    NULL, NULL
};

这个就是变体的底层所在,也就是说,每有一个变体,都会构造这么一个defines,然后调用编译程序编译shader为dxbc。
  我们在引擎层面说的变体,就是这些底层的Shader,是OpenGL的glsl、DirectX的dxbc/dxil、Vulkan的spirv;而变体指数级增长,相当于这些底层的这些shader指数级增长。



  变体数太多对开发模式可能没有什么,最多是开编辑器时多喝点茶,但项目需要打包、上线就不是这样了。
  别看这些都能Shader实时用Shader编译生成,但引擎不会这么做,而是在打包时就需要知道所有可能用到的变体,将其打包出来。
  很浅显的原因是shader编译的时间也不短,Unity/UE这些引擎为了方便用户编写,主要编写的语言是hlsl,如果你的游戏是DX11/DX12,实际运行会将hlsl编译为dxbc,单个的时间不长,但达到一定数量就会有明显卡顿,如果场景出现一些附加光源,突然多出来这些变体shader需要实时生成,这个时间说不定会是几秒。
  如果你的API是OpenGL,为了获取到glsl,unity用hlslcc将hlsl变成glsl,然后再编译程序;如果API是VulKan,前面按照OpenGL一样先生成glsl,然后再用glslang生成spirv。对于UE,这个流程会有区别。
  对于DX12和VK这样的现代API,新生成Shader意味着要生成PSO(管线状态对象),这又是一比超级大的开销。
  如果不提前将ShaderBuild好,你现在打包时编译Shader的时间,就是你未来用户第一次进入游戏的时间,想想这个酸爽。总之确定了一件事,在打包时,预计用到的Shader变体(dxbc/glsl/spirv)就会全都打入包中。
  变体数量对包体的影响倒是未必很大,因为AssetBundle有压缩,而你的变体之间只是略有差异,很可能200MB的shader文件,压缩后不到2MB。
  真正进入游戏中,游戏会先将Shader从AssetBundle中解压出来放到CPU先准备着,当GPU需要用到变体时,再送入GPU。重点是解压后Shader的大小就不是那么理想了,你可以用你完全没有Shader管理的游戏项目打个包,然后Unity到Window>Analysis>Profiler。

连接adb到手机,然后点击内存>Take Sample AndroidPlayer>Other>Rendering>ShaderLab查看: 

未经过管理的变体可能导致ShaderLab占用内存一个多G,这显然是不可接受的。
  这是内存上的问题,此外还有运行时加载的问题。
  但现在还是上述的情景,假如场景中突然出现一盏附加光源,需要对已有的shader都开启新的变体,这些变体CPU中都存在,因为你打包时已经打入了,你省下了将hlsl生成为dxbc/glsl/spirv的时间,但是将dxbc/glsl/spirv送入gpu、生成pso的时间却是省不下的,这依旧可能会造成卡顿。
  结合上述问题,所以我们需要对Shader变体做管理。

二、如何对Shader变体进行管理

  上面描述了keyword组合造成的变体数量爆炸,想要将变体控制下来,需要从两方面出发,分为个人和项目。

1. 个人角度对Shader变体管理

  个人是指TA、引擎、图程以及其他Shader开发者,在编写Shader时就要注意变体的问题。
  首先,该用if用if,之前虽然说在GPU执行分支开销不低,但只是相对而言的,如果你的ifelse执行的是整个光照计算,那显然是不可接受的,但假如ifelse加起来没两行代码,那显然是无所谓的,要是在变体极多的时候去掉个keyword,变体数直接砍半,对项目的好处是极大的,这需要开发者自己权衡。
  其次,之前的例子都用的是multi_compile,但实际上不一定需要multi_compile,某些情况下用shader_feature是可以的。

1.1 multi_compile和shader_feature的区别

  用multi_compile声明的keyword是全排列组合,例如:

#pragma multi_compile A B
#pragma multi_compile C D E

 组合出来就是AC AD AE BC BD BE6个,如果再来一个#pragma multi_compile F G显然会直接翻倍为12个。
  shader_feature则不同,它打包时,找到打包资源对变体的引用,最普通能对变体引用的资源是Material(例如场景用了一个MeshRenderer,MeshRenderer用了这个材质,材质用了这个Shader的一个变体)。
  在Inspector窗口右上角将Normal换成Debug模式,可以看到材质引用的Keyword组合:

假如将上述multi_compile替换为shader_feature: 

#pragma shader_feature A B
#pragma shader_feature C D E

我打包只打一个材质,这个材质用到了变体组合AC,那么打包时只会将AC打出来。

  如果我的材质引用的是AE,那么会打出AC和AE,因为C是第二个keyword声明组的默认keyword,当你的材质用了这个Shader,却没有发现没有引用这一声明组的任何一个keyword(比如上面CDE都没引用),就会退化成第一个默认keyword(上面的例子是C)。
  所以一般声明keyword组如果包含默认keyword、关闭keyword不会声明XXX_OFF,而是声明成
#pragma multi_compile _ C D,这样如果材质引用AD,则会打出 A和AD,不会减少变体数量,但可以减少Global Keyword的数量(Unity2020及以下版本只能有384个Global Keyword,2021之上有42亿个。)

1.2 打包规则

  打包时会将multi_compile和shader_feature分为两堆,分别计算组合数,然后两者再组合,例如:

#pragma multi_compile A B
#pragma multi_compile C D
#pragma shader_feature E F
#pragma shader_feature G H

当你只打两个材质,引用的变体分别是ADEG和ACFH,前两个multi_compile组直接组合成4个变体,后面两个shaderfeature组分别引用到了EG和FH,然后两组组合4*2,最后打出8个变体。

1.3 编写建议

  对于个人来说,较为通用的编写方式是,multi_compile建议用于声明可能实时切换的keyword声明组,例如阴影、全局雾效、雨、雪。因为一个物体可能在多个场景使用,材质也就会在多个场景用到,一个场景有雾,另一个场景有雨,而材质只能引用一组变体,为了能实时切换,就需要把变体也打入包中;而对于材质静态的keyword声明组就可以用shader_feature,例如这个材质是否用到了NormalMap,是否有视差计算,这个在打包时就确定好的,运行时不会动态改变,即可声明为shader_feature。
  multi_compile_local适合解决打包时不确定变体,需要在运行时动态切换单个材质变体的需求,例如某些建筑、角色需要运行时溶解;溶解只针对当前角色的材质而不是全局的,需要Material.EnableKeyword,所以用local;并且需要溶解的材质被打入包中,所以需要multi_compile,组合起来就是multi_compile_local。

小贴士:
  shader_feature和multi_compile后面也可以加其他条件,例如如果确定一组keyword声明只会导致vertexshader有变化,即可再后面加_vertex,例如shader_feature_vertex。
  shader_feature_local的_local声明和变体数无关,是Unity2021之前为了解决GlobalKeyword数量问题出现的解决方案,声明为local keyword不会占用global keyword数,建议是如果keyword声明组是需要材质手动勾选的参数,声明为_local;当keyword为local时,Shader.EnableKeyword或CommandBuffer.EnableKeyword这种全局开启keyword方式,无法启用当前材质的关键字,只能由材质开启。
  有些声明是Unity内置的,例如#pragma multi_compile_instancing相当于#pragma multi_compile _ INSTANCING_ON,#pragma multi_compile_fog则会声明几个雾相关的keyword。

2. 项目角度的变体管理

  有些问题从个人开发角度是难以规避的。
  希望Shader的开发者都能从个人编写角度做好变体管理,往往是不现实的,Shader开发者水平有高有低,或许某个实习生或客户端为了快速实现效果,就从网上Copy下来一段代码,运行一下效果没问题就不管了;再或者某个美术导入了一个插件,而插件的编写者没有考虑过变体的问题等等。

2.1 变体剔除

  Unity提供了IPreprocessShaders接口,让用户自定义剔除条件。
  自定义的类继承IPreprocessShaders后,需要实现void OnProcessShader(Shader shader, ShaderSnippetData snippet, IList<ShaderCompilerData> inputData)方法,这是一个回调函数,当打包时,所有shader变体都会送进来进行判断。
  三个参数中,第一个是UnityShader对象本体,没什么好说的。
  第二个存了底层Shader类型和Pass类型,ShaderType包括Vertex、Fragment、Geometry等;PassType存了Pass类型,例如BuildIn Shader一般有ForwardBase、ForwardAdd,SRP的SRP、SRPDefaultUnlit等。
  第三个参数是ShaderCompilerData的List,ShaderCompilerData包含了当前变体包含哪些keyword、变体所需的api特性级别、变体的api(只要PlayerSetting里添加了平台对应的API,可以同时打出多个图形API所需的Shader),可以将一个ShaderCompilerData视作一个变体。
  这些参数包含变体的全部条件,用户可以根据项目需要自行编写剔除逻辑,当判断需要剔除一个Shader变体时,只需要将ShaderCompilerDatainputData这个list中删除即可。
  下面是一个简单实例,如果我们想剔除所有包含INSTANCING_ON keyword的变体时应该如何编写:

class StripInstancingOnKeyword : IPreprocessShaders
{
    public void OnProcessShader(Shader shader, ShaderSnippetData snippet, IList<ShaderCompilerData> inputData)
    {
        for (int i = inputData.Count - 1; i >= 0; i--)
        {
            ShaderCompilerData input = inputData[i];
            //Global And Local Keyword
            if (input.shaderKeywordSet.IsEnabled(new ShaderKeyword("INSTANCING_ON")) || input.shaderKeywordSet.IsEnabled(new ShaderKeyword(shader, "INSTANCING_ON")))
            {
                inputData.RemoveAt(i);
            }
        }
    }
}

一般情况下,项目会编写一个配置文件,里面记录各种需要剔除的变体条件,比如URP项目不需要BuildIn下的ForwardBasePass、DeferredPass,可以直接将这些Pass剔除掉,防止项目中有BuildIn下残留的变体。
  有些shader抄案例时,附带了#pragma multi_compile_fog等Unity自动生成的关键字,而实际上Shader可能用不到,可以通过项目整体剔除来抵消项目人员犯错。
  还可以根据项目需求编写条件,比如说项目中角色Shader带有高配和低配关键字,用于区分着色计算,高配用于展示,低配用于战斗,能确定战斗效果(例如溶解、石化)变体不可能出现高配变体上,因此可以判断当同时出现高模Keyword和战斗效果Keyword时剔除变体。
  在我们项目中,通过变体剔除,能将占用上GB内存的ShaderLab降低到20多MB,可见变体剔除的必要性。
  有时候需要注意,一些库(比如高版本的URP)也会自带变体剔除,了解项目时,先全局搜下继承IPreprocessShaders的类,防止变体在自己不知道的时候被剔掉。
  此外项目设置里也有一套变体剔除,在ProjectSetting>Graphics的Shader Stripping项下,当Modes是Custom时,只有勾选的会被打入包中。例如下图,只勾选了Baked Directional,会导致烘焙Lightmap的Shader中,如果有LIGHTMAP_ON但没有DIRLIGHTMAP_COMBINED的变体都被剔除。

 

2.2 变体预热、

  第二个问题是变体是只有被用到才会被送入GPU,这样会导致卡顿。解决方法也很简单,既然运行时按需加载会导致卡顿,那么提前一次性加载好即可。
  引擎一般提供预热功能,在Unity中是ShaderVariantCollection.WarmUpShaderWarmup.WarmupShaderFromCollectionShader.WarmupAllShaders,前两个方法会将变体收集内的全部变体提前送入GPU,省下了这一部分时间。
  现在讨论下变体收集文件(ShaderVariantCollection)的使用方法。
  变体收集文件是Unity资产,可通过右键Create>Shader>Shader Variant Collection创建,或通过跑变体收集保存一个变体收集文件。
  变体收集文件包含很多Shader,每个Shader对应一个List,List中每个项是PassType-KeywordSet的组合,用户可以根据需求手动添加Shader和变体组合。
  不过第一次使用时,建议跑一遍变体收集,之后再根据需要手动添加删除。
  使用方法是在ProjectSetting>Graphics的最下面,先Clear掉当前的记录,然后进行游戏,尽量覆盖大多数游戏内容,之后点击Save to asset保存。

我个人认为变体收集的主要作用就是用来预热变体,实际上还有个作用,就是引用变体;和材质一样,记录在变体收集内的变体会被记做引用打到包中。
  因此一些人在管理变体时喜欢将所有keyword声明为shader_feature,然后跑变体收集增加被引用到的变体,这样能被打入到包中的变体,都是会被用到的。
  但我个人不推荐这种做法,因为利用变体收集引用来管理打包会很混乱,往往跑游戏很难覆盖到所有位置,导致少收集一些变体,而一些效果不太明显,连测试都看不出来区别,可能上线后都发现不了。当Shader拥有庞大变体集合时,再增减Keyword,更新变体收集文件会导致事倍功半。
  我个人推荐的做法是,keyword完全按使用方法来声明,会动态切换的声明multi_compile,对材质静态的声明shader_feature,不可能出现的条件组合用变体剔除去掉。
  而变体收集的作用只是用来预热,因为跑变体收集后,大部分常用功能都被提前加载到GPU,而小部分未提前加载的变体也无伤大雅。

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

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

相关文章

强化学习p1-基本概念

Terminologies(名词) 状态(State) 每个时刻&#xff0c;环境有一个状态 (state)&#xff0c;可以理解为对当前时刻环境的概括 状态(State) 有时也被称为观测(Observation)&#xff0c;因为有时智能体并不能观测到环境改变后的全部&#xff0c;只能观测到部分。 环境(Environm…

计算机毕业论文内容参考|软件工程|网络流量异常信息分析方法研究

文章目录 导文文章重点摘要前言绪论课题背景国内外现状与趋势课题内容相关技术与方法介绍技术分析技术设计技术实现总结与展望导文 网络流量异常信息分析方法研究 文章重点 摘要 本课题针对网络安全中网络流量异常的分析方法进行研究,提出一种基于机器学习和深度学习的异常检…

OpenCV教程——处理图像像素及图像掩膜

1.像素值 像素值是图像被数字化时由计算机赋予的值&#xff0c;代表了图像中某一小方块&#xff08;即【像素点】&#xff09;的平均亮度信息。 灰度图像通常用8位表示一个像素&#xff0c;这样总共有256个灰度等级&#xff08;像素值在0&#xff5e;255之间&#xff09;。 …

最值得推荐的免费分区管理软件

磁盘管理是 Windows 的内置工具&#xff0c;用于操作硬盘上的分区&#xff0c;但自 Windows XP 以来直到Windowa11 该程序几乎没有变化。个人测试了五个备选方案&#xff0c;以了解它们之间的比较。 奇客分区 默认的分区个数根据磁盘大小&#xff1f;需要重新划分分区&#xf…

2023/5/8总结

JAVA基础知识&#xff08;2&#xff09; 1.方法 1、方法定义 格式&#xff1a;public static void 方法名&#xff08;&#xff09;{ //方法体 } 2、方法调用 格式&#xff1a;方法名&#xff08;&#xff09;&#xff1b; 3、方法的通用格式 public static 返回值类型方法名&…

Camtasia2023官方中文版免费下载

在现在的网络互联网时代&#xff0c;越来越多的人走上了自媒体的道路。有些自媒体人会自己在网络上录制精彩视频&#xff0c;也有一些人会将精彩、热门的电影剪辑出来再加上自己给它的配音&#xff0c;做成大家喜欢看的电影剪辑片段。相信不管大家是自己平时有独特的爱好也好、…

【机组组合】基于数据驱动的模型预测控制电力系统机组组合优化【IEEE24节点】(Matlab代码实现)

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

MySQL ---- 事务

事务 1、事务简介 事务是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一个整体一起向系统提交或撒销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失败。 在实际的开发过程中&#xff0c;一个业务操作如&am…

如何创建可引导的 ESXi USB 安装介质 (macOS, Linux, Windows)

如何创建可引导的 ESXi USB 安装介质 (macOS, Linux, Windows) 如何制作 ESXi USB 启动盘 请访问原文链接&#xff1a;https://sysin.org/blog/create-bootable-esxi-usb-installer/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysi…

第四十六章 Unity 布局(上)

学习了UI元素的使用&#xff0c;并不能构建出一个完整的UI界面&#xff0c;我们需要使用一些方法将这些UI元素按照“设计稿”的效果&#xff0c;将其摆放到对应的位置上。如何摆放这些UI元素&#xff0c;就是我们需要讲的“布局”&#xff0c;当然这需要借助一些布局组件来完成…

SpringBootFoundation

SpringBootFoundation 一. 项目创建1.1 创建项目1.2 并调整pom.xml1.3 创建子项目1.4 配置子项目pom1.4.1 使用`csmall-server`作为父项目 并且 添加依赖项1.5 关于聚合项目二. YAML语法的配置2.1 关于YAML语法一. 项目创建 1.1 创建项目 本项目是一个聚合项目的结构,首先,创…

clickhouse集群部署指南

一、前言&#xff1a; 上篇文章介绍了clickhouse单机部署指南&#xff0c;本章重点介绍下集群部署指南&#xff0c;文章重在实战&#xff0c;相关知识请自行学习。 二、环境准备&#xff1a; CentOS7 4核16G 3台(172.25.16.82、172.25.16.83、172.25.16.84) 三、安装包准备…

第三方ipad笔哪个牌子好用?ipad触控笔推荐平价

至于选择苹果原装的电容笔&#xff0c;还是平替的电容笔&#xff0c;要看个人的需求而定&#xff0c;比如画图用的&#xff0c;可以用Apple Pencil&#xff1b;比如学习记笔记用的&#xff0c;可以用平替电容笔&#xff0c;目前的平替电容笔无论是品质还是性能&#xff0c;都非…

【Linux】Linux环境下安装Jenkins(图文解说详细版)

文章目录 前言1、在根目录下创建文件夹2、下载linux jenkins3、 进入jenkins目录解压&#xff0c;解压命令&#xff1a;4、启动5、访问一下,浏览器访问&#xff1a;http://服务器IP:8080/ 前言 首先安装Jenkins之前&#xff0c;linux中必须安装好了JDK和Maven&#xff0c;如果…

MVI架构理解

回顾MVC MVP MVVM MVC MVC架构主要分为以下几部分&#xff1a; View层: 对应于xm布局文件和java代码动态view部分。 Controller层: 主要负责业务逻辑&#xff0c;在android中由Activity承担&#xff0c;但xml视图能力太弱&#xff0c;所以Activity既要负责视图的显示又要加入…

Node框架 【Koa】之 【静态资源管理、模板引擎、连接数据库】

文章目录 &#x1f31f;前言&#x1f31f;静态资源托管&#x1f31f;安装&#x1f31f;使用 &#x1f31f;Koa视图&#x1f31f;EJS模板引擎使用&#x1f31f;安装&#x1f31f;配置&#x1f31f;使用&#x1f31f;模板渲染方法&#x1f31f;使用案例 &#x1f31f;数据库&…

OBS直播时编码器、码率控制器、分辨率帧率是什么以及如何向第三方推流

内容摘要&#xff1a;OBS直播时编码器、码率控制器、分辨率、帧率到底是什么&#xff0c;以及OBS向第三方直播平推流时&#xff0c;要注意什么。 图&#xff1a;OBS直播时输出界面参数设定 OBS编码器 1. 软编&#xff1a;x264 使用CPU进行编码&#xff0c;占用CPU资源多&…

m3u8文件

#EXTM3U&#xff1a;m3u文件头&#xff0c;必须放在第一行&#xff0c;起标示作用&#xff1b; #EXT-X-VERSION&#xff1a;播放列表文件的兼容版本。若不存在此标记&#xff0c;则默认为协议的第一个版本&#xff1b; #EXT-X-MEDIA-SEQUENCE&#xff1a; 播放列表中的每个媒…

第1章计算机系统漫游之 “操作系统管理硬件“

7、操作系统管理硬件 回到 hello 程序的例子。当 shell 加载和运行 hello 程序时&#xff0c;当 hello 程序输出自己的消息时&#xff0c;程序没有直接访问键盘、显示器、磁盘或主存储器。取而代之的是&#xff0c;它们依靠操作系统提供的服务。 可以把操作系统看成是应用程序…

王琤:当数据治理遇上ChatGPT

以ChatGPT为代表的人工智能等技术正在“狂飙”&#xff0c;为全球带来一场翻天覆地的变革。4月27日在2023数据治理新实践峰会上&#xff0c;Datablau数语科技创始人&CEO王琤先生以《数据治理新实践与人工智能》为主题进行了分享&#xff0c;与参会同仁共同探索当数据治理遇…