编码全称:AV1(Alliance for Open Media Video 1)。
算力消耗大:目前(截至 2025 年中)软件解码 AV1 的 CPU 开销非常高,如果没有专门的硬件解码单元,播放高清视频时会很吃 CPU,容易出现卡顿、发热或掉帧。
在许多视频/图像场景里,压缩率常常表示为“源文件大小 ÷ 压缩后文件大小”,比率越大代表压缩得越厉害;但更直观常用的是“码率”(bitrate),比如视频的码率是 4 Mbps,或者图片的 JPEG 质量参数 90%。
一般情况下,码率越高(即单位时间/单位像素所分配的数据越多),可还原的画面细节就越丰富,相对的画质也会更好。压缩率越大则对应码率越低,画面细节和锐度就更容易丢失,出现模糊、块状或色带等伪影。
如果不在意流量消耗,就让播放器调度尽可能高的 AV1 码率(比如 4K 甚至 8K 的高码流)来保证最丰富的细节。
明天了解一下吧
和Graph差不多的,但是built-in
在 Photoshop 中,“线性减淡(Linear Dodge)”与“线性减淡(添加)(Linear Dodge (Add))”实际上是同一个 Blend Mode,Photoshop 官方简称:“Linear Dodge (Add)”。它的核心思路就是把两张图像做加法,然后对结果做饱和剪裁(clamp)
滤色(Screen)融合模式通常被称为“反相相乘”(Inverse Multiply)。数学表达式是:
C = 1 - (1 - A) * (1 - B)
更柔和的叠加效果:与 Add/Linear Dodge “一旦相加就饱和到白”相比,Screen 会在低亮度区累加时更明显,在高亮度区饱和时更平滑。
高光到达白色的过程是渐进、滑顺的,不会像简单的加法
Linear Dodge 一旦 A+B > 1,就直接剪裁为 1,区域会瞬间变白。
Screen 则是在 A+B 越来越大时,按照 A+B−A*B 的方式渐进逼近 1,在值接近 1 之前都会有细微过渡。
暗部细节保存更好
内发光和外发光,内发光不能超出模型的边界
用了之后任何功能都需要自己连出来,不连就没有,右边一下子就少了很多接口,变灰不让接上了
unity中的fixed用宏定义了half,换了一种表示方法
在 Unity Shader 中,half
并不直接代表“固定 16 位浮点”,而是“编译器抽象”→等同于 fixed
。
在移动 GPU 上,使用
half
或fixed
可以减少寄存器压力、提高并行度。在 Pixel Shader 中使用
fixed
做颜色计算可以节省资源。GPU 内部的向量化 SIMD 单元更适合处理中低精度数据。
→ 所以手动标记可以让你显式告诉编译器:这个变量不需要高精度,编译器可以帮你优化。
如果你在高精度 float 上做了大量运算,再赋值给一个低精度
fixed
,可能造成明显的精度丢失(颜色值断层、光照闪烁等)。所以手动标记数据类型,能让你显式暴露这种意图,并对精度损失行为负责。
half用给插值出来的值,fixed用来装颜色的,,这是优雅的写法了(pc上统一float32,不影响)
“在手机上 fixed 是小于 half 的”
“颜色需要的精度更少”
为什么 Unity/Shader 写颜色用 fixed
?
fixed
≈ lowp
≈ 用于颜色值
fixed
最早其实不是 Unity 发明的,而是从旧版 Cg/HLSL 中延续而来的术语,它的名字原义是:
fixed
表示 固定精度范围 的数值(比如 -2 到 2),比 half 更低精度,但更小更快。
不同的平台,取模可能要用规定的函数,*也是一样的,乘要用规定的函数
static
在 Shader 里,
static
表示“静态变量”,也就是该变量只在当前编译单元(通常是当前.cginc
/.hlsl
文件)中可见,外部无法访问或修改它。典型场景:
你在同一个 Shader 文件里定义了一段公用函数,而这个函数里面需要一个只读的临时常量或缓存,你可以用
static
声明它,确保不被其他包(pass、include)意外重写。static
并不是“静态内存”那种概念(不像 C/C++ 全局变量会驻留在固定内存地址)。在 GPU 编译时,static
只是一个作用域限制:告诉编译器“外面别来碰我”。
uniform
跨阶段共享:一个
uniform
(如 CBUFFER 里的某个结构体、或者 samplers/纹理)能同时被顶点着色器(VS)和片段着色器(PS)访问。外部驱动:它真正的值来源于 CPU 端代码(C#、C++)或渲染框架主循环,在每次 Draw Call 时由引擎更新。
在 Shader 里只能读取它们,不能在 Shader 里写回赋值。
const
编译期展开:一旦你写了 const float PI = 3.14159;
,编译器会在预处理阶段将所有 PI
的引用替换为 3.14159f
,并且不会占用寄存器或 uniform 缓存。
对性能友好:因为它根本不占用寄存器或常量寄存器,所有对它的引用都被直接打入字节码里,相当于纯粹的“文本替换”。
只读:与 uniform
一样,在 Shader 代码中不能给 const
变量重新赋值;但区别在于,它根本不是由外部赋值,而是在编译时就固定了。
GPU 在遇到 if else 时会把两个分支都执行一遍
因为现代 GPU(无论是桌面级的 NVIDIA/AMD,还是移动端的 ARM Mali/Qualcomm Adreno)内部都是基于SIMD(或 SIMT)并行架构
简单来说,GPU 会把若干个线程(Thread)组织成一个“线程束”(在 NVIDIA 中叫 Warp、在 AMD 中叫 Wavefront),比如一个 Warp 通常是 32 条线程。然后这 32 条线程在硬件上会同步执行同一条指令。
什么是 Mipmap?
假设你有一张原始纹理贴图(比如 1024×1024 像素)。Mipmap 会在这张贴图之上再依次生成几个尺寸越来越小的版本,比如:
1024×1024 → 512×512 → 256×256 → 128×128 → 64×64 → … → 一直到 1×1
每一级的图像都是对上一层用双线性/三线性滤波之类的方法预先缩小或预过滤得到的。所以当你把同一张纹理应用到远处(或尺寸很小)的物体时,GPU 不必去采样原始的 1024×1024,而是直接采样一个更低分辨率的级别(比如 128×128 或 64×64),这样能够减少锯齿、闪烁和纹理闪变现象,同时提高采样效率。
https://en.wikibooks.org/wiki/Cg_Programming/Unity/Minimal_Shader
https://enjoyphysics.cn/%E6%96%87%E4%BB%B6/soft/Hlsl/GPU-Programming-AndCgLanguage-Primer.pdf
使用 HTTPS 协议,不需要专门配置 SSH Key,即使没配置 SSH 也能正常克隆/拉取代码。
在公司网络或学校网络里,HTTPS 通常不被屏蔽,更容易成功下载。
GitHub Desktop 是 GitHub 官方推出的一款图形化桌面客户端(Windows 和 macOS 均支持),可以让不熟悉命令行的用户用可视化界面来管理本地 Git 仓库。
当你点击“Open with GitHub Desktop”时,如果本地安装并打开了 GitHub Desktop 应用,Launcher 会自动在 GitHub Desktop 里创建一个“克隆”任务,让你选择存放路径,然后把仓库克隆到你指定的本地文件夹。
“Download ZIP” 按钮
优点:
极其简单,连命令行都不需要,适合只想“拿来直接看代码/文档”的场景。
不用担心本地有没有安装 Git;只要会解压就行。
缺点:
你无法继承该仓库的版本控制历史,也不能向远端推送更新、创建分支,甚至都拿不到原来的
.gitignore
、.gitattributes
等元信息。如果后续该项目在 GitHub 上更新了,你需要手动重新下载最新的 ZIP,无法用
git pull
一键拉取更新。
如果你:
主要在 GitHub 上做开源、个人项目;
希望操作直观、界面干净;
不想花时间学命令行或太多 Git 细节;
→ 用 GitHub Desktop 就够了。
如果你:
想用 GUI 方式做深入的 Git 操作(如多级分支管理、隐式多分支合并、交互式 rebase、子模块、stash 等);
项目不一定只在 GitHub,还可能在 GitLab、自建私库;
喜欢一次性把常用 Git 操作都放在一个软件里(不用每次都切命令行);
→ 用 GitExtensions 会更专业,也更灵活。
shader中的函数性质
GLSL(OpenGL Shading Language)**的顶点着色器(Vertex Shader)代码,通常用在桌面或者移动端的原生 OpenGL 项目里
Unity 里我们平时写的 Surface Shader、Vertex/Fragment Shader,文件后缀一般是
.shader
(里面会混合 ShaderLab 语法和 CGPROGRAM/ENDCG 段落)或者.cginc
、.hlsl
、.compute
(Compute Shader),它们的语法看起来像 HLSL/CG。你这里看到的文件名是
vert.glsl
(以及右边大概率还有frag.glsl
),这明确表明它是 OpenGL/GLSL 的程序。GLSL 在语法上用的关键词是attribute
、varying
、gl_Position
,这在 Unity 的 CG/HLSL 中并不出现。
attribute
关键字表示顶点输入属性(顶点位置、UV、法线等),这是 GLSL 里的写法;varying
用来把顶点着色器算出的数据(例如顶点法线、坐标插值)传给片元着色器;gl_Position
则是 OpenGL 里必须写的输出变量,决定当前顶点在屏幕上的裁剪空间位置。
这些在 Unity 的 ShaderLab/CG 里都不使用。Unity 要输出给固定流水线的是 o.pos
或者 SV_POSITION
(在 HLSL/DirectX 中)。所以从关键字 attribute
、varying
、gl_Position
就能看出这是原生 GLSL,不是 Unity 的。
// 顶点属性:从 CPU 端传过来的每个顶点数据
attribute vec3 VaPos; // 顶点的世界/模型空间位置 (x, y, z)
attribute vec2 VaUV; // 原始贴图 UV 坐标 (u, v)
attribute vec3 VaNormal; // 顶点法线
// 统一变量(Uniform),从 CPU 端传过来,在绘制一批顶点时统一不变
uniform mat4 MVP; // Model-View-Projection 矩阵,用于把顶点从模型空间 → 世界空间 → 视图空间 → 裁剪空间
uniform int rows; // 多重纹理拼接时的行数
uniform int cols; // 多重纹理拼接时的列数
uniform int index; // 当前要使用哪张子纹理(编号)
// 这是要传给片元着色器的插值数据(每个顶点算一次,片元阶段会自动插值)
varying vec3 VNormal;
varying vec2 VUV;
void main() {
// 1. 把原始顶点法线直接传给片元着色器
VNormal = VaNormal;
// 2. 计算新的 UV —— 例如把一张大贴图分割成 rows×cols 几块子图,再根据 index 选其中一块
vec2 temp = vec2(VaUV.x / float(rows), VaUV.y / float(cols));
vec2 ranks = vec2(0.0, 0.0);
ranks.x = mod(float(index), float(rows)); // 第几列
ranks.y = floor(float(index) / float(cols)); // 第几行
temp.x += ranks.x * 1.0 / float(cols);
temp.y += ranks.y * 1.0 / float(rows);
VUV = temp; // 这样最终传到片元阶段的 VUV 就是“在大贴图里选取子图的坐标”
// 3. 计算最终 gl_Position(OpenGL 固定变量),告诉 GPU 该顶点在裁剪空间里的坐标
gl_Position = MVP * vec4(VaPos, 1.0);
}
(把一张大贴图拆为 rows×cols 个小纹理,然后根据 index 去采样)
shader forge
“应用阶段的流水线化”指的就是 把渲染管线里原本只在 CPU 上按帧串行做的那块“应用”工序(场景遍历、剔除、动画、排序、命令构建),拆分成多个子阶段或分成多条命令缓冲,并且跟后面的 GPU 渲染阶段错开时间、同时执行。这样才能让 CPU+GPU 两端都保持高吞吐、低空闲,从而推高帧率和抗卡顿能力。
Shader Graph 里,UV 坐标并 不 被系统强制“钳制”到 [0,1] 区间——它只是一个浮点向量,可以读到任意值。只要你的采样模式是 Repeat/Tiled(而不是 Clamp),UV 超出 0–1 时就会按小数部分去“重复”取样(也就是把纹理无缝平铺)。
“Light Attenuation”(光照衰减) 节点,它负责根据当前光源类型和距离,给你算出一个 0~1 之间的衰减系数,用来模拟灯光在空间里“随距离衰减”的效果。
“固定函数”着色器(Fixed-Function Shader)其实并不是一种你要手写的 shader 脚本,而是指在早期图形 API(如 OpenGL 2.x、Direct3D 9 以前)中,GPU 内置的一整套“流水线功能”,你只需要用一组固定的命令和参数就能完成顶点变换、光照计算、纹理映射等,而不用自己写顶点/片元程序。
替换着色器(Replacement Shader)
当你用
Camera.SetReplacementShader(shader, "RenderType")
时,Unity 会在所有渲染物体上查找他们 SubShader 里带"RenderType"="..."
的 Pass,并用你提供的替换着色器去渲染这一类物体。典型例子:后期效果里做“基于不透明物体的深度/法线图”时,就用 Replacement Shader 只渲染
Opaque
物体。
xyzw四维,w维度判断是什么类型的光源,如果是方向光,那就xyz是方向向量,如果是点光源,则是具体的世界位置
尽量写小数的0.0,(一些奇葩机型可能自动转换出错)
在 Unity 编辑器里,只要让鼠标焦点在 Scene 视图窗口上,按下:
Shift + Space
就可以切换该窗口的最大化/恢复(全屏化)状态。
一般从光照节点开始分析一个新的shader
scene绕着某个物体旋转自己当前的视角----Alt+左键拖拽
Graphics API/渲染接口也就是底层跟显卡打交道、驱动硬件加速的标准。
Unity、Unreal、Godot(结合对应平台 API:DX12、Vulkan、Metal)
GPU 驱动只实现「API 规范」,并不区分高层语言
显卡厂商(NVIDIA/AMD/Intel/Apple 等)在驱动里只负责实现底层的 Graphics API(如 DirectX、Vulkan、OpenGL、Metal)的二进制接口——这些接口在本质上是 C/C++ 的 ABI(应用二进制接口)。
驱动并不会同时编译出一堆 “C++ 版”、“C# 版”、“Java 版” 的驱动库。它导出的是一套底层的函数和数据结构,供任何能调用 C 接口的语言来用。
各种编程语言通过「绑定」来调用这些 C 接口
C/C++:可以 直接
#include <d3d12.h>
或者#include <vulkan/vulkan.h>
,在代码里无缝调用驱动导出的函数。C#:通常使用 P/Invoke(
[DllImport]
)或者像 SharpDX、VulkanSharp 这样的第三方封装库,把底层 DLL 导出的 C 接口“翻译”成 C# 的类和方法。Java/Python/Go:同样可以通过 JNI、ctypes、cgo 等方式,把驱动的 C 接口映射到各自语言里。比如 LWJGL(Java),PyOpenGL(Python)都是用这种思路。
游戏引擎和框架通常做更高一层封装
Unity、Unreal、Godot 等引擎,底层已经把对 DirectX/Vulkan/Metal 的调用都写好 C++ 代码了,然后再在引擎层给脚本语言(C#, Blueprint, GDScript)提供更易用的接口。
比如在 Unity 里你调用
Graphics.DrawMesh()
,它背后跑的是 C++、再到 DirectX/Vulkan 驱动,整个过程对 C# 脚本透明。
Unity 引擎核心 是用 C++ 写的,它直接通过诸如 DirectX、Vulkan、Metal 这些“C 语言风格”的 Graphics API 去驱动 GPU。
你在 Unity 里写的 C# 脚本(例如
Graphics.DrawMesh()
或者修改材质属性)并不是直接 P/Invoke 调用这些 Graphics API,而是调用了 Unity C++ 引擎提供的“脚本绑定”(bindings)。这些绑定通常是通过 Unity 自己的宿主进程(UnityPlayer.dll)把 C# 的调用转成内部的 C++ 函数,一次跨语言切换后,后续都在 C++ → 驱动 API 里完成。
这样设计的好处有两个:
性能: 把所有高频的、与硬件交互密集的部分都留在 C++ 层;C# ↔ C++ 的调用只在脚本逻辑层面(每帧仅仅几百次)发生,而不是每绘制一个三角形、每次设置一个 GPU 状态就 P/Invoke。
稳定性与可维护性: 驱动和 Graphics API 的改动都在 C++ 层处理,对 C# 脚本层透明,不用让每个 C# 项目都去适配不同平台的 P/Invoke 签名。
为什么 Unity 不让 C# 直接 P/Invoke?
减少跨语言调用次数:Unity 把常见的渲染操作都集合成了一套“C++ 原生接口”,C# 只调用这些接口一次进入本地层,之后所有渲染都在 C++ 到驱动的环节。
API 统一与封装:无论你是用 C#、ShaderLab 还是通过 Visual Effect Graph/Shader Graph,最终都走同一个 C++ 实现,不用重复写绑定。
安全与稳定:让脚本层不直接接触底层 DLL,也防止用户误用不安全的 P/Invoke 导致崩溃或安全漏洞。
其实在 Unity 里,C# → C++ 的“中转”调用并不是在每个三角形、每次状态切换、每个像素着色时都发生的——那样的开销肯定会大得不可接受。它真正的调用频率,大致是:
每帧几十到几百次 的 “脚本层面”渲染命令(DrawMesh、SetGlobalFloat、EnableKeyword……)
Unity 内部把这些命令 累积到一套命令缓冲(Command Buffer) 中
在 C++ 层一次性向 GPU 提交整个缓冲区(提交 DrawCall 批次,而不是单个三角形)
你在 C# 里写
Graphics.DrawMesh(mesh, matrix, material, layer);
每次这句脚本执行时,会做一次 C#→C++ 的入口调用。但它并不立刻跑到底层的 GPU 提交,而是 把参数存到 C++ 的渲染队列里。
当一帧结束、所有绘制逻辑跑完后,Unity C++ 层会把这些“渲染命令列表”批量通过一次或几次
vkQueueSubmit
/ID3D12CommandQueue::ExecuteCommandLists
/MTLCommandBuffer commit
提交给显卡。
也就是说,你在 C# 里可能调用了 50 次 DrawMesh,但这 50 次调用只会在本地层合并成几次底层 GPU 提交。P/Invoke 的开销只体现在那 50 次调用上,而不是每个三角片、每个状态切换都 P/Invoke 一次。
如果你直接在 C# 里一个 DrawCall 对应一个 P/Invoke,再对每个三角形循环,那肯定会慢;但 Unity 已经帮你把这些细节都封装在 C++ 里了,你在 C# 里看到的只是高级接口。
C# 这边的“驱动”其实就是 .NET 运行时/CLR(Common Language Runtime) 本身:
.NET 运行时=语言的“驱动”
当你安装了 .NET Framework、.NET Core/5+ 或 Mono,里面就包含了 CLR、垃圾回收、JIT 编译器、Base Class Library(BCL) 等,负责把你的 C# IL 转成机器码并执行。
这就类似于显卡厂商编写的 GPU 驱动:它实现了 Graphics API 规范,把高层调用(DrawCall)翻译给硬件;而 CLR 则实现了 ECMA-335 标准,把 C#、VB、F# 编译出来的 IL 翻译给 CPU。
跨语言调用=互操作(Interop)
C# 想调用 C++ 写的库,一般通过 P/Invoke(
[DllImport]
) 或 C++/CLI、或 COM,本质上也是 CLR 调用本地 DLL 导出的 C 风格函数。这套互操作机制由 CLR 提供,调用时也会牵涉“托管 ↔ 非托管”上下文切换、参数封送(marshalling)等,就像 GPU API 驱动要封装命令缓冲、驱动开销一样。
不需要给每种语言都写一个“驱动”
CLR 是同一套运行时/规范,不管是 C#、VB 还是 F#,都跑在同一个 CLR 上。
你只需要安装一次对应平台的 .NET 运行时,就能运行任意 .NET 语言编译的程序,不用为每个语言再各写一份“驱动”。
你写的 Java 源码(.java)首先被 javac 编译成 Java 字节码(.class 文件),这是一种平台无关的中间格式,不是机器码,也不是 C++。
Java JVM | .NET CLR / Mono | |
---|---|---|
输入 | .class (Java 字节码) |
.dll/.exe (IL 中间语言) |
解释执行 | 解释器 + JIT | 解释器 + JIT |
AOT 支持 | GraalVM Native Image 等 | .NET Native / Mono AOT |
调用本地库(Interop) | JNI(Java Native Interface) | P/Invoke / C++/CLI |
生态 | Spring、Android Runtime、GraalVM 等 | Unity、ASP.NET、Xamarin、Mono |
可编程化与可定制化:Scriptable Render Pipeline(SRP)
传统引擎(如 Unity 5 之前的内置管线,Unreal 4.XX 的默认前向/延迟管线)对渲染流程是“黑盒”式的,只有少量参数可调。
次世代管线(Unity 的 URP/HDRP、Unreal Engine 5 的自定义管线、Godot 4 的 Vulkan 渲染)都采用了可脚本化渲染管线(Scriptable Render Pipeline)理念
次世代管线几乎都把基于物理的渲染(PBR, Physically Based Rendering)作为核心:
统一材质模型:F0、粗糙度(Roughness)、金属度(Metallic)、法线、环境光遮蔽等统一标准;
能量守恒:确保光的反射和吸收满足真实物理规律;
一致性:无论场景光照如何变化,材质表现都保持物理自洽。
高效的光照计算:延迟+Clustered/Tiled/Nanite
延迟渲染(Deferred Rendering):先把几何信息写入 G‐Buffer,在屏幕空间做光照,适合大批点光源;
Tiled/Clustered Shading:把屏幕或视锥体划分成小块或体素,把光源分桶管理,只对相关瓦片/簇做光照,提高效率;
GPU 驱动渲染(GPU Driven):把剔除、实例化、Draw Call 构建都放到 GPU Compute Shader 中,减轻 CPU 负担;
虚拟化几何(Nanite):Unreal 5 的 Nanite 利用海量细分三角阵列在 GPU 上动态 LOD,实时渲染数十亿三角。
新一代光照与全局光照:光线追踪+Lumen
硬件光线追踪(RTX/DXR/Metal Ray Tracing):实时计算反射、折射、阴影;
软件全局光照(Lumen、SVOGI、Probe Grid):混合光栅与光线追踪做间接照明,既快速又真实;
混合渲染:在不同场景阶段(GI 探针、后期 SSDO、实时 RT)中动态切换,兼顾性能与画质。
渲染剖析与工具链
渲染图(Frame Graph):所有 Render Pass 都以节点化/依赖图方式管理,可视化调试和自动合并;
GPU Profiler & RenderDoc:细粒度分析每个 Pass、每个 DrawCall 的性能,支持管线统计与着色器调试;
资产打包与宏:自动提取 Shader Variant、分批预编译常用 Shader,减少运行时编译阻塞。
Unity HDRP(High Definition Render Pipeline)
专为高端 PC/主机打造:支持物理光照、延迟+Tiled 光照、体积雾、光线追踪特效。
用户可通过 C# 脚本定制 Frame Graph,增删 Pass;材质系统与 Shader Graph 深度集成。
Unity URP(Universal Render Pipeline)
面向移动端和跨平台应用:轻量级延迟或前向+Tiled 渲染,支持 Shader Instancing、轻量级后期。
Unreal Engine 5
Nanite + Lumen 双核技术,自动化虚拟化几何与全局光照,无需手工烘焙,实时打造电影级场景。
Godot 4 Vulkan
集成 Clustered Shading、GI 探针、屏幕空间环境光遮蔽,且引擎代码开源可定制。
次世代渲染管线 = 可编程化+物理真实感+高效光照+混合光线追踪 的一整套现代实时图形架构。
它不仅要在“画质”上接近电影工业级效果,更要在“性能”上充分利用多核 CPU、GPU Compute 和硬件光线追踪等能力,为跨平台的游戏/实时应用提供可扩展、可定制、高性能的渲染解决方案。
“Tiled 光照” 是一种在实时渲染中优化大量点光源/聚光灯的方法,核心思路是把屏幕空间分成一个一个的小“瓦片”(Tile),然后只对每个瓦片中实际影响到的光源做光照计算,而不用让每个像素都跑遍所有光源。这样能大幅减少光照计算量。
屏幕拆分成瓦片
比如把 1920×1080 的屏幕划分为 16×16 像素的小块,得到 120×68 共 8160 个瓦片。
光源—瓦片关联
在一个专门的 Compute Shader(或 CPU 线程)里,遍历场景里所有的点光/聚光,判断它们的影响范围(球体或锥体),再把每个光源加入到与之相交的那些瓦片的“光源列表”里。
最终每个瓦片得到一个「仅包含真正会影响该区域的光源索引」的小列表。
瓦片内像素光照
在主渲染 Pass(通常是延迟光照或前向+Clustered)里,每个像素先定位到它属于哪个瓦片,然后只从该瓦片的光源列表里取出那些光源,逐一做漫反射/镜面反射计算。
这样即使场景有几百、上千个光源,大多数像素最多只要计算几十盏真正可见/有效的光源。
可扩展:场景光源越多,瓦片大小/分辨率可调;也可以升级到“Clustered”三维分块,连同深度一起分区。
跨管线:既能用在延迟渲染(Deferred)里,也能在前向渲染(Forward)中做“Forward+”或“Clustered Forward”优化。
传统做法是在几何阶段就给每个物体算好光照(把灯光的影响一起算进去),只需要一次 Pipeline 就完成。但如果场景有多盏灯,就要对每个物体分别跑几遍顶点→片元,效率会急剧下降。
eferred 渲染(延迟渲染)怎么做的
延迟渲染把上面两个阶段分开,变成:
第一次:填 G-Buffer
把场景里的所有像素信息(深度、法线、材质参数)写到多个渲染目标上(称为 G-Buffer)。
实际上 GPU 会同时写出几张“贴图”:
一张存 深度;
一张存 法线;
一张存 漫反射颜色;
可能还有张存 金属度/粗糙度,……
第二次:屏幕空间光照
屏幕上每个像素已经有了上一步的所有必要数据,接下来只需在「屏幕四边形」上做一次遍历:
读取 G-Buffer 里的深度+法线+材质,
针对每个像素列举出少量相关的灯(用 Tiled/Clustered)
计算漫反射+高光+阴影……
这样就不用再跑几何阶段,把所有光照都集中在屏幕上一次性完成。
为什么说“但 G-Buffer 的读写会带来很高的带宽消耗”
写入 G-Buffer:在几何阶段,GPU 要向显存写入多张大贴图(深度、法线、颜色……),这就是“多通道写入”。
读取 G-Buffer:在光照阶段,GPU 又要从显存里读回这些贴图数据(多通道读取),才能拿到每个像素的深度、法线、材质参数。
显存带宽:显卡内存的读写速度相比它计算速度要慢得多,频繁的大规模读写就会成为瓶颈。
假设你的游戏目标是 1080p(1920×1080),在一个典型的 Deferred 渲染中,你可能会用到如下的 G-Buffer:
G-Buffer 通道 格式 每像素字节数 深度(Depth) 32-bit 4 B 法线(Normal) RGBA16F 8 B 漫反射颜色(Albedo) RGBA8 4 B 金属度/粗糙度/AO(MRAO) RGBA8 4 B 合计 — 20 B/px
写入 G-Buffer(Geometry Pass)
每帧要向显存写入:
1920 × 1080 ≈ 2.07 × 10^6 像素 × 20 B/像素 ≈ 41.5 MB/帧
读取 G-Buffer(Lighting Pass)
同样地,又要从显存读出这 41.5 MB/帧。总带宽消耗
41.5 MB (写入) + 41.5 MB (读取) = 83 MB 每帧
不同帧率下的带宽需求
帧率 带宽需求 60FPS 83 MB × 60 ≈ 4.98 GB/s 144FPS 83 MB × 144 ≈ 11.9 GB/s 240FPS 83 MB × 240 ≈ 19.9 GB/s 对比一下市面上典型显卡的理论显存带宽:
GTX 1080:≈320 GB/s
RTX 3060:≈360 GB/s
Radeon RX 6700 XT:≈384 GB/s
虽然看起来 60FPS 只用了 ~5 GB/s(“才”占了带宽的 1–2%),但这只是写读 G-Buffer的部分开销,还不包括:
后续所有的材质贴图采样、环境贴图读写;
Shadow Map 的写入和读取;
后期特效(Bloom、TAA、DOF 等)的多轮读写;
Compute Shader、Stream-Out、Copy 等其它显存操作。
把这些加起来,显存带宽就很快被攥紧了,尤其在更高分辨率(1440p/4K)或更多 G-Buffer 通道(HDR、法线空间贴图、动量贴图等)时,带宽需求几何倍上升,延迟渲染的“G-Buffer 二次流式读写”成为了最大的性能瓶颈之一。
GPU 核心时钟(Core/Boost Clock)
Core Clock(基础频率):GPU 在正常负载下的运行频率。
Boost Clock(加速频率):当温度、功耗和负载都允许时,GPU 会自动把核心时钟「超」到更高的值,以提高每秒能执行的着色器指令和渲染单元操作的吞吐量。
这两个时钟域只管 GPU “算力” 的快慢——它不决定显存的读写速度。
显存时钟(Memory Clock)和总线宽度(Bus Width)
Memory Clock:显存芯片(GDDR6/GDDR5/HBM 等)自身的工作频率。通常厂商会给一个有效速率,比如 14 000 MHz(也叫 14 Gbps)。
Bus Width:GPU 与显存之间的数据通道宽度,比如 128 bit、192 bit、256 bit。
它们共同决定了显存带宽(Memory Bandwidth),即每秒能搬多少数据给 GPU 核心用。
针对显存(Memory Clock)的工作频率
如果显存标为 14 Gbps(Gigabits per second),可以理解为:
它每秒钟能“嘀嗒”14 × 10⁹ 次,每次在那根数据线上传输 1 bit(比特)信息。
GDDR6 中通常有很多并行数据线(一根数据线按 Memory Interface Width,比如 128 bit,总共就是 128 条线同时在跑这个频率)。
为什么说 14 Gbps 而不是 14 GHz?
Gbps 是“千兆比特/秒”,是一种经常用在高速串行接口(把宽总线看作一条条并行线)的标法,和 GHz(10⁹ 周期/秒)在含义上很接近,但针对的是“每条线每秒能跑多少比特”。
14 Gbps ≈ 14 × 10⁹ Hz,也就是说那根线每秒钟能“嘀嗒”14 billion 次。
Unreal Engine 的 Lumen 和 Nanite 并不是像 HDRP、URP 那样“脚本化管线”里的一组可插拔节点,而是深度内置在引擎渲染架构里的两大核心子系统。它们在源码层面就和 Deferred/Forward 渲染、材质系统、光线追踪支持等紧密耦合,所以从“普通项目”层面,确实没法像 Unity 那样在 C# 或 Shader Graph 里随意“拆掉”或“重组”它们。
为什么看起来是“定下来了”
引擎级集成
Lumen 和 Nanite 从 5.0+ 开始就是 UE5 的默认渲染子系统,你在项目设置里只能打开或关闭它们,全流程(从 G-Buffer、光线追踪、体素缓存到最终合成)都在引擎核心代码里。
无 SRP 式脚本化接口
Unity 的 HDRP/URP 是建立在 Scriptable Render Pipeline 之上,整个渲染流程的每一步(剔除、Shadow Pass、G-Buffer、Lighting Pass、Post)都暴露给 C#,方便你按需插拔。
UE5 则是基于 Render Graph 和 Pipeline State Object (PSO) 的 C++ 实现,对项目开放的扩展点主要在“添加”而非“替换”已有步骤。
虽然不能“拆掉整个 Lumen/Nanite”,但如果你有足够 C++ 资源和愿意编译引擎,也能做一些深度定制:
修改 Renderer 源码
在引擎源码的
Source/Runtime/Renderer/Private
目录下,Lumen、Nanite 相关的模块都是.cpp/.h
文件,你可以 fork UE5 源码,删改这些实现,重新编译你的定制化渲染管线。
自定义 Render Graph 节点
UE5 的渲染流程是基于 Render Graph 构建的。你可以在
FDeferredShadingSceneRenderer::Render()
等入口,插入自定义的 Render Graph Pass,比如在 Lumen GI 前后做一次额外合成。
自定义 Shading Model
如果你只想改材质层面的光照模型,可以在
Engine/Shaders/Private/MaterialShared.usf
里新增一个EBlendMode::Custom
的 Shading Model,改写GetBaseLighting
、GetLightingModel
之类函数,仍然保留 Lumen 的 GI。
插件 + 模块化源码
将你的定制放到一个引擎插件里,并在
.uproject
或.uplugin
里覆盖 Renderer 模块,便于在不同项目间复用。
对于“风格化效果”——并不推荐改源码
很多定制效果(卡通、像素化、怪诞色调、线框渲染、特定后期合成)都可以用:
自定义材质 + 后处理(Post Process)
Custom Shading Model / Custom Depth + Stencil
插件级 Render Graph Pass(在现有渲染流程里插入一个额外 Pass)
这些方式都在引擎开放的高层 API(蓝图/C++ 渲染接口)范围内,不用重新编译整个引擎,升级版本也更方便。
只有在这些情况下才该动源码
性能:你确定自己的游戏在目标平台上已经用尽了所有可调 API,只有改内核才能再提升 10% 帧率。
功能缺失:引擎高层接口确实做不到你想要的底层操作(非常罕见)。
团队人手:有专门的渲染工程师维护一个私有 UE 源码分支,并能承担后续合并和升级的成本。
否则,只为一个风格化 Shader 效果去改动这么庞大的管线,不但风险极高(升级、Bug、兼容性),也大大降低了迭代效率和可维护性。建议优先在现有可扩展接口里做自定义渲染,再视情况决定是不是真的需要“动刀”到引擎级别。
音频比特率同理
录制视频到底是哪一轨会被写进去?
这取决于你在“输出设置”中的“录制格式和音轨配置”:
如果你在设置中选择 只启用音轨1 进行录制,那么最终导出的视频就只包含音轨1中的内容。
如果你启用了 多音轨录制(比如 mp4 + 轨道1~3),那么导出的视频将会携带多个音轨,你可以在后期剪辑软件中单独调出每条轨道。
你录了一个教学视频:
轨道1:合成音轨(用于预览听感)
轨道2:桌面音频(PPT 声音 + 视频素材)
轨道3:麦克风(你讲解的声音)
后期你发现某一段麦克风杂音太重、或咳嗽了。
如果你只录了一个合成轨道(桌面 + 麦克风),你没法分离出来,只能忍着或者剪掉整段。
但有了独立轨道:
你可以只剪掉麦克风这段,
或者加上降噪插件处理,
桌面声音完全不受影响。
无论是 .mp4
、.mkv
还是 .mov
,一旦音轨写入进去,它们都是标准化的数据结构:
可以被读取 ✅
可以单独静音、剪切、提取、替换 ✅
可以重新封装(不重新编码) ✅
.mkv
是编辑友好的首选格式
转场特效是场景转换的时候产生的过渡
点击开始直播(也就是推流,推流可能更加形象一些),想停止,就停止推流,然后去bzhan上设置停止直播的开关
直播时长,录制时长
总之,需要直播的时候,就使用obs,拿b站或者其他平台给的推流码,填入之后就可以直播自己的电脑屏幕,可以用已经了解的obs的规则去管控自己的直播
视频录制如果要录
obs的文件部分可以转换文件格式,不用担心mkv保存了最好的音轨调整性,然后转别的格式不方便
听到“糊声”,不是酷狗变了,而是 OBS 改变了“你整个系统的音频处理路径”。
想要听到原来的音质,需要从驱动层、音频通道、采样率上做出限制或隔离。
这样就改了
禁用不需要的音频通道
OBS 设置 → 音频:
麦克风/辅助音频1
→ 只启用你实际用的桌面音频
→ 启用你系统默认的那一个其他通道 → 统统设为“禁用”
这样 OBS 就不会偷偷“激活”多个音频输入或输出接口。
OBS 打开后会触发自动采样转换,造成插值失真
改用独立声卡或虚拟音频设备隔离 OBS
如果你有:
外置 USB 声卡
虚拟音频路由(如 VB-Cable、VoiceMeeter)
可以设置:
酷狗 → 播放到真实声卡(耳机)
OBS → 捕获虚拟声卡 / 第二设备,互不干扰
这样你听到的不会是 OBS “截获后的信号”。
蓝牙耳机的“听”和“说”功能之间存在模式冲突,只有一个能高质量运行。
模式 | 名称 | 用途 | 特征 |
---|---|---|---|
🎵 A2DP | 高质量立体声传输模式 | 听音乐、看视频 | 音质好,但不能用麦克风 |
🎙 HFP/HSP | 通话模式(耳机+麦克风) | 打语音、开麦克风 | 可以说话,但音质极差,像电话音 |
所以结论正确:
🟢 如果你外接一个独立麦克风(USB 或 3.5mm 插口),
🔁 就可以让蓝牙耳机 只用于播放(A2DP),系统不会切换到 HFP 模式,
🎧 你就能持续享受高质量的音频输出。
的蓝牙音质问题不是 OBS 特有的
这个问题会在所有场景下触发,比如:
微信语音 → 蓝牙瞬间变糊
QQ语音 → 音乐变差
Discord 开启麦克 → YouTube 音质崩坏
它是 蓝牙协议层面的问题,不是 OBS 的 bug。
声卡就是外接麦克风设备吗?
不是。
有些外接麦克风设备(比如 USB 麦)内置声卡 → 是二合一设备。
但真正意义上的“外置声卡”,是可以连接多个音源、调节电平、具备专业音频处理功能的设备,不一定自带麦克风。
阶段 | 技能模块 | 目标 |
---|---|---|
① 基础图形理解 | Unity 渲染流程、URP/HDRP管线结构、ShaderGraph基本逻辑 | 能够看懂节点、了解 SRP 是干什么的 |
② 真实光照原理 | 漫反射、高光、阴影类型(硬/软/SSAO)、光照探针、光照贴图、SH光照 | 能合理解释光照表现背后的机制 |
③ 性能分析 | Profiler、Frame Debugger 使用,DrawCall 分析、Batches、RenderTexture 代价 | 能识别“这个场景为什么卡、卡在哪” |
④ ShaderGraph 实战 | 节点功能实际应用:边缘光、渐变、遮罩、透明、噪声、流动、特效写法 | 能实现效果,能解释原理(如基于法线/UV/世界空间) |
⑤ 渲染管线控制 | 自定义 SRP、RenderFeature、CommandBuffer、Blit 操作、后处理流程 | 对渲染顺序、通道、混合有清晰认知,能做高级控制 |
⑥ 跨模块优化思维 | 与美术/程序协作,场景组织优化(LOD、烘焙、阴影距离)、动态光源管理 | 能在团队中定位渲染瓶颈,提出合理建议 |
xxxxxxxxxxxxxxx
一片 1K×4 芯片,一共可以存储 1024 × 4 = 4096 bit = 512 byte
。
8 片 1K×4
存储芯片,组成一个 4K×8
的存储器
利用了 地址译码器 + 芯片分组 + 数据线并联 的方法来实现容量与字位的同时扩展。
译码器,它根据地址的高位来决定哪一组芯片工作。
防止多个芯片同时干活冲突
总线是共享的资源,所有芯片的数据线通常都是接在 同一条数据总线上。同一时刻,只能有一个芯片处于“工作”状态,其他芯片都要“闭嘴”(高阻状态)。不加片选控制,多个芯片都接在数据总线上,一起输出东西➡️ 数据总线上谁说了算?电压会乱跳,变成垃圾数据甚至损坏芯片。
元件 | 访问延迟(大概数量级) | 说明 |
---|---|---|
寄存器 | 1 个 CPU 时钟周期 | CPU 内部,最快 |
L1 Cache | ~3-4 个时钟周期 | 非常快,但容量小 |
L2 Cache | ~10 个周期 | 稍慢一些,容量大些 |
内存(RAM) | ~100 个周期或更多 | 太慢了,CPU等得焦躁 |
硬盘(HDD) | 几百万周期 | 简直是“乌龟” |
Memory Hierarchy(存储层次结构),就是为了在速度、容量、成本之间做平衡。
CPU 内核内部,电子信号的传输几乎不需要走线;
没有中介,不需要“找地址”、“走总线”、“排队”;
是专为执行指令准备的高速电路(SRAM结构);
L1/L2 Cache:也在芯片里,但要做“内容匹配”,L1 Cache 也在 CPU 芯片上,但要根据地址查找数据是否命中
现代 CPU(以及 GPU)有专门的模块叫:
FPU(Floating Point Unit)浮点运算单元
它在硬件层面执行 IEEE-754 定义的操作。
大多数2D虚拟主播不需要穿戴动捕设备,只使用摄像头 +面部追踪软件即可实现面部表情和头部动作的捕捉。
但如果追求全身动作/复杂动态,才可能用到动捕服或外部设备。
游戏主程序通常是 C++,Lua 只是逻辑脚本,改 Lua 不影响主程序运行