自由学习记录(63)

发布于:2025-06-21 ⋅ 阅读:(19) ⋅ 点赞:(0)

编码全称:AV1(Alliance for Open Media Video 1)。

算力消耗大:目前(截至 2025 年中)软件解码 AV1 的 CPU 开销非常高,如果没有专门的硬件解码单元,播放高清视频时会很吃 CPU,容易出现卡顿、发热或掉帧。

  • 在许多视频/图像场景里,压缩率常常表示为“源文件大小 ÷ 压缩后文件大小”,比率越大代表压缩得越厉害;但更直观常用的是“码率”(bitrate),比如视频的码率是 4 Mbps,或者图片的 JPEG 质量参数 90%。

  • 一般情况下,码率越高(即单位时间/单位像素所分配的数据越多),可还原的画面细节就越丰富,相对的画质也会更好。压缩率越大则对应码率越低,画面细节和锐度就更容易丢失,出现模糊、块状或色带等伪影。

如果不在意流量消耗,就让播放器调度尽可能高的 AV1 码率(比如 4K 甚至 8K 的高码流)来保证最丰富的细节

ShaderForge介绍_哔哩哔哩_bilibili

明天了解一下吧

和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 上,使用 halffixed 可以减少寄存器压力提高并行度

  • 在 Pixel Shader 中使用 fixed 做颜色计算可以节省资源。

  • GPU 内部的向量化 SIMD 单元更适合处理中低精度数据。

→ 所以手动标记可以让你显式告诉编译器:这个变量不需要高精度,编译器可以帮你优化。

  • 如果你在高精度 float 上做了大量运算,再赋值给一个低精度 fixed可能造成明显的精度丢失(颜色值断层、光照闪烁等)。

  • 所以手动标记数据类型,能让你显式暴露这种意图,并对精度损失行为负责

half用给插值出来的值,fixed用来装颜色的,,这是优雅的写法了(pc上统一float32,不影响)

“在手机上 fixed 是小于 half 的”
“颜色需要的精度更少”

为什么 Unity/Shader 写颜色用 fixed

fixedlowp ≈ 用于颜色值

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 一键拉取更新。

  • 如果你:

    1. 主要在 GitHub 上做开源、个人项目;

    2. 希望操作直观、界面干净;

    3. 不想花时间学命令行或太多 Git 细节;
      用 GitHub Desktop 就够了。

  • 如果你:

    1. 想用 GUI 方式做深入的 Git 操作(如多级分支管理、隐式多分支合并、交互式 rebase、子模块、stash 等);

    2. 项目不一定只在 GitHub,还可能在 GitLab、自建私库;

    3. 喜欢一次性把常用 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 在语法上用的关键词是 attributevaryinggl_Position,这在 Unity 的 CG/HLSL 中并不出现。

  • attribute 关键字表示顶点输入属性(顶点位置、UV、法线等),这是 GLSL 里的写法;varying 用来把顶点着色器算出的数据(例如顶点法线、坐标插值)传给片元着色器;gl_Position 则是 OpenGL 里必须写的输出变量,决定当前顶点在屏幕上的裁剪空间位置。

这些在 Unity 的 ShaderLab/CG 里都不使用。Unity 要输出给固定流水线的是 o.pos 或者 SV_POSITION(在 HLSL/DirectX 中)。所以从关键字 attributevaryinggl_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

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/渲染接口也就是底层跟显卡打交道、驱动硬件加速的标准。

UnityUnrealGodot(结合对应平台 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 里完成。

这样设计的好处有两个:

  1. 性能: 把所有高频的、与硬件交互密集的部分都留在 C++ 层;C# ↔ C++ 的调用只在脚本逻辑层面(每帧仅仅几百次)发生,而不是每绘制一个三角形、每次设置一个 GPU 状态就 P/Invoke。

  2. 稳定性与可维护性: 驱动和 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++ 的“中转”调用并不是在每个三角形、每次状态切换、每个像素着色时都发生的——那样的开销肯定会大得不可接受。它真正的调用频率,大致是:

  1. 每帧几十到几百次 的 “脚本层面”渲染命令(DrawMesh、SetGlobalFloat、EnableKeyword……)

  2. Unity 内部把这些命令 累积到一套命令缓冲(Command Buffer)

  3. 在 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) 本身:

  1. .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 渲染(延迟渲染)怎么做的

延迟渲染把上面两个阶段分开,变成:

  1. 第一次:填 G-Buffer

    • 把场景里的所有像素信息(深度、法线、材质参数)写到多个渲染目标上(称为 G-Buffer)。

    • 实际上 GPU 会同时写出几张“贴图”:

      • 一张存 深度

      • 一张存 法线

      • 一张存 漫反射颜色

      • 可能还有张存 金属度/粗糙度,……

  2. 第二次:屏幕空间光照

    • 屏幕上每个像素已经有了上一步的所有必要数据,接下来只需在「屏幕四边形」上做一次遍历:

      • 读取 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

  1. 写入 G-Buffer(Geometry Pass)
    每帧要向显存写入:

    1920 × 1080 ≈ 2.07 × 10^6 像素 × 20 B/像素 ≈ 41.5 MB/帧

  2. 读取 G-Buffer(Lighting Pass)
    同样地,又要从显存读出这 41.5 MB/帧。

  3. 总带宽消耗

    41.5 MB (写入) + 41.5 MB (读取) = 83 MB 每帧

  4. 不同帧率下的带宽需求

    帧率 带宽需求
    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 里随意“拆掉”或“重组”它们。

为什么看起来是“定下来了”

  1. 引擎级集成

    • Lumen 和 Nanite 从 5.0+ 开始就是 UE5 的默认渲染子系统,你在项目设置里只能打开或关闭它们,全流程(从 G-Buffer、光线追踪、体素缓存到最终合成)都在引擎核心代码里。

  2. 无 SRP 式脚本化接口

    • Unity 的 HDRP/URP 是建立在 Scriptable Render Pipeline 之上,整个渲染流程的每一步(剔除、Shadow Pass、G-Buffer、Lighting Pass、Post)都暴露给 C#,方便你按需插拔。

    • UE5 则是基于 Render GraphPipeline State Object (PSO) 的 C++ 实现,对项目开放的扩展点主要在“添加”而非“替换”已有步骤。

虽然不能“拆掉整个 Lumen/Nanite”,但如果你有足够 C++ 资源和愿意编译引擎,也能做一些深度定制:

  1. 修改 Renderer 源码

    • 在引擎源码的 Source/Runtime/Renderer/Private 目录下,Lumen、Nanite 相关的模块都是 .cpp/.h 文件,你可以 fork UE5 源码,删改这些实现,重新编译你的定制化渲染管线。

  2. 自定义 Render Graph 节点

    • UE5 的渲染流程是基于 Render Graph 构建的。你可以在 FDeferredShadingSceneRenderer::Render() 等入口,插入自定义的 Render Graph Pass,比如在 Lumen GI 前后做一次额外合成。

  3. 自定义 Shading Model

    • 如果你只想改材质层面的光照模型,可以在 Engine/Shaders/Private/MaterialShared.usf 里新增一个 EBlendMode::Custom 的 Shading Model,改写 GetBaseLightingGetLightingModel 之类函数,仍然保留 Lumen 的 GI。

  4. 插件 + 模块化源码

    • 将你的定制放到一个引擎插件里,并在 .uproject.uplugin 里覆盖 Renderer 模块,便于在不同项目间复用。

对于“风格化效果”——并不推荐改源码

  • 很多定制效果(卡通、像素化、怪诞色调、线框渲染、特定后期合成)都可以用:

    1. 自定义材质 + 后处理(Post Process)

    2. Custom Shading Model / Custom Depth + Stencil

    3. 插件级 Render Graph Pass(在现有渲染流程里插入一个额外 Pass)

  • 这些方式都在引擎开放的高层 API(蓝图/C++ 渲染接口)范围内,不用重新编译整个引擎,升级版本也更方便。

只有在这些情况下才该动源码

  • 性能:你确定自己的游戏在目标平台上已经用尽了所有可调 API,只有改内核才能再提升 10% 帧率。

  • 功能缺失:引擎高层接口确实做不到你想要的底层操作(非常罕见)。

  • 团队人手:有专门的渲染工程师维护一个私有 UE 源码分支,并能承担后续合并和升级的成本。

否则,只为一个风格化 Shader 效果去改动这么庞大的管线,不但风险极高(升级、Bug、兼容性),也大大降低了迭代效率和可维护性。建议优先在现有可扩展接口里做自定义渲染,再视情况决定是不是真的需要“动刀”到引擎级别。

(P2)OBS参数设置_哔哩哔哩_bilibili

音频比特率同理

录制视频到底是哪一轨会被写进去?

这取决于你在“输出设置”中的“录制格式和音轨配置”:

  • 如果你在设置中选择 只启用音轨1 进行录制,那么最终导出的视频就只包含音轨1中的内容。

  • 如果你启用了 多音轨录制(比如 mp4 + 轨道1~3),那么导出的视频将会携带多个音轨,你可以在后期剪辑软件中单独调出每条轨道。

你录了一个教学视频:

  • 轨道1:合成音轨(用于预览听感)

  • 轨道2:桌面音频(PPT 声音 + 视频素材)

  • 轨道3:麦克风(你讲解的声音)

后期你发现某一段麦克风杂音太重、或咳嗽了。
如果你只录了一个合成轨道(桌面 + 麦克风),你没法分离出来,只能忍着或者剪掉整段。

但有了独立轨道:

  • 你可以只剪掉麦克风这段,

  • 或者加上降噪插件处理,

  • 桌面声音完全不受影响。

无论是 .mp4.mkv 还是 .mov,一旦音轨写入进去,它们都是标准化的数据结构

  • 可以被读取 ✅

  • 可以单独静音、剪切、提取、替换 ✅

  • 可以重新封装(不重新编码) ✅

.mkv 是编辑友好的首选格式

(P3)OBS直播(基础篇)_哔哩哔哩_bilibili

转场特效是场景转换的时候产生的过渡

点击开始直播(也就是推流,推流可能更加形象一些),想停止,就停止推流,然后去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 不影响主程序运行


网站公告

今日签到

点亮在社区的每一天
去签到