顶点着色器会同时处理一个模型空间下 的所有顶点的变换计算,确保模型的所有点都正确变化。
再往底层就是 GPU 的硬件执行了吗?
yes:
- GPU 指令层:在 GPU 内部,这些矩阵变换会被转换为 SIMD(单指令多数据)指令,并由 GPU 计算单元(如 NVIDIA CUDA Core、AMD Compute Unit)并行执行。
- 流水线硬件:GPU 具有专门的流水线架构,每个阶段(如顶点处理、光栅化、片元处理等)都有固定的硬件单元负责计算。
- 寄存器与 ALU:GPU 使用寄存器存储矩阵数据,执行 浮点运算(如 4x4 矩阵乘法)。
- 电路和电压:最底层就是 GPU 的电路结构,如 ALU(算术逻辑单元)、FP32 计算单元,最终表现为硬件的逻辑门开关、电压变化和晶体管运作。
矩阵变换处于渲染管线的哪个层级?
在现代 GPU 渲染管线中,模型的顶点数据需要经过一系列变换才能映射到最终的屏幕上。这里的 模型空间 → 世界空间 → 观察空间 → 裁剪空间 → 屏幕空间,涉及的变换主要由以下矩阵运算实现:
- 模型矩阵(Model Matrix):将局部坐标(模型空间)变换到世界坐标。
- 视图矩阵(View Matrix):将世界坐标变换到相机(观察)坐标。
- 投影矩阵(Projection Matrix):将观察坐标变换到裁剪空间(标准化设备坐标 NDC)。
- 视口变换(Viewport Transform):将裁剪空间变换到屏幕坐标。
如果目标是游戏 TA(Technical Artist),一般不会涉及GPU 电路级别的东西,而是会关注:
- 如何优化这些矩阵变换(减少计算开销)
- 如何高效使用着色器(减少 Draw Call,优化渲染效率)
- 如何利用 GPU Profiler 诊断性能问题(查看瓶颈)
- **如何使用图形 API(如 OpenGL / Vulkan / DirectX / Metal)**来高效控制这些变换
屏幕空间(Screen Space)
屏幕空间(Screen Space)是计算机图形学中的概念
指渲染结果在屏幕上显示的坐标空间
三维坐标经过一系列转换后会转换到最终的二维屏幕坐标空间中
使得图像可以在屏幕上进行展示
齐次裁剪空间 通过 将摄像机的视锥体投影到一个规范化的立方体,为了让我们可以更通用、便捷的来进行裁剪工作
摄像机为正交投影时:
1.将视锥体中心位移到 观察空间原点中心
2.将 长方体视锥体 的xyz坐标范围 映射到(-1,1)长宽高为2的正方体中
摄像机为透视投影时:
1.将透视视锥体变成一个长方体
2.将视锥体中心位移到观察空间原点中心
3.将长方体视锥体的xyz坐标范围映射到(-1,1)长宽高为2的正方体中
视锥体、透视除法 (Clip Space → NDC)
在 OpenGL 和 DirectX 里,投影变换 (Projection Transform) 是适应各自的渲染机制的,它们有不同的处理方式,但本质上都是 在观察空间 (View Space) 里进行的变换,确保 GPU 可以正确执行裁剪、光栅化和深度测试。
不同的渲染 API (OpenGL / DirectX) 采用了不同的裁剪深度规则,因此:
- 它们的投影矩阵 (Projection Matrix) 会有所不同。
- 目的是让最终的裁剪空间 (Clip Space) 满足各自的 GPU 标准。
观察空间 (View Space) 物体坐标 | 裁剪空间 (Clip Space) 变换 | NDC 归一化 (透视除法后) | |
---|---|---|---|
OpenGL | z ∈ [-near, -far] |
z ∈ [-1,1] |
z ∈ [-1,1] |
DirectX | z ∈ [near, far] |
z ∈ [0,1] |
z ∈ [0,1] |
不同的投影矩阵会调整 z
归一化方式,以适应各自的 GPU 处理方式。但不管是 [-1,1]
(OpenGL) 还是 [0,1]
(DirectX),最终都要经过 GPU 的光栅化和深度测试。
"摄像机视锥体的正交投影空间转换到齐次坐标裁剪空间",意思就是 从观察空间 (View Space) 转换到 标准化设备坐标 (NDC, Normalized Device Coordinates),这个转换过程通常称为 投影变换 (Projection Transform)。
在计算机图形学的渲染流程中,这一步的核心作用是 把 3D 空间的点归一化到 [-1,1]
的标准范围,以便 GPU 进行裁剪、光栅化和深度测试。
正交投影变换 (Orthographic Projection Transform) 的作用就是把观察空间 (View Space) 里的各个点变换到标准化视锥体 (NDC, Normalized Device Coordinates)。
在观察空间 (View Space) 中,物体的坐标仍然是以摄像机为中心的 真实世界单位,但是:
x
轴的范围是[-width/2, width/2]
y
轴的范围是[-height/2, height/2]
z
轴的范围是[near, far]
(通常是负值,如near=-1
,far=-10
)
由于这些数值仍然是具体的单位制 (meter, cm, etc.),它们没有标准化,所以我们需要 正交投影矩阵 来归一化这些坐标,转换成 [-1,1]
的标准化视锥体坐标。
把观察空间 正交投影变换(也就是标准化视椎体)
总的变化矩阵因此可以为:
标准化视椎体是为了让下一阶段的渲染变的更加规范,不受原视椎体里具体的每个点的坐标影响,因为标准化成了这个范围,只提供一个比例就可以达到把东西都渲染出来的效果
计算机图形学的渲染流程,它必须逐步简化和标准化坐标系,最终把三维世界的点转换到屏幕像素坐标,才能正确地渲染出来
屏幕上的像素是 2D 的,而顶点是 3D 的,必须进行投影变换
透视投影需要进行归一化,否则三维点无法正确映射到屏幕
现代 GPU 使用标准化设备坐标 (NDC) 进行光栅化,需要统一的变换方式
- 透视投影需要调整 深度信息,否则远近物体不会有正确的透视感。
- 现代 GPU 需要标准化的 [-1,1] 空间才能进行光栅化和裁剪。
步骤 | 变换类型 | 作用 |
---|---|---|
1. 模型变换 (Model Space → World Space) | M_model |
把顶点从局部坐标变换到世界坐标 |
2. 观察空间变换 (World Space → View Space) | M_view |
以摄像机为中心,调整世界坐标 |
3. 投影变换 (View Space → Clip Space) | M_projection |
透视投影,转换为标准化设备坐标 (NDC) |
4. 透视除法 (Clip Space → NDC) | /w |
把透视投影后的坐标转换为 [-1,1] 区间 |
5. 屏幕映射 (NDC → Screen Space) | Viewport Transform | 转换到屏幕像素坐标系 |
归一化坐标 (Normalized Device Coordinates, NDC) 是 3D 渲染管线中投影变换 (Projection Transform) 后的一个中间坐标系
范围固定在:
- X 轴范围:
[-1,1]
(左到右) - Y 轴范围:
[-1,1]
(下到上) - Z 轴范围:
[-1,1]
或[0,1]
(远到近,OpenGL 是[-1,1]
,DirectX 是[0,1]
)
范围对应的是标准化的视锥体 (Standardized View Frustum),它的作用是让 GPU 可以高效地裁剪、光栅化和深度测试。
unity是这样规定的,也说明别的软件中可以设置不同的结构来表达观察空间
得到高之后配合屏幕比例的调整,就可以得到宽
摄像机视锥体的投影方式
透视投影:视锥体内顶点和原点连接,在近裁剪面的交点为投影点
正交投影:视锥体内顶点向近裁剪面做左右裁剪面平行线,在近裁剪面交点为投影点
视锥体
视锥体是在三维空间中表示摄像机可见区域的虚拟体积
决定了哪些内容能被渲染,哪些内容会被裁剪
但是,如果直接使用视锥体定义的空间来进行裁剪,那不同的视锥体就需要不同的处理过程,而且对于透视投影的视锥体来说,判断顶点是否在范围内相对较麻烦。
就需要将观察空间(摄像机空间)中的数据转换到齐次裁剪空间中
齐次裁剪空间
齐次裁剪空间是通过将摄像机的视锥体投影到一个规范化的立方体而转换来的。
这个立方体就是齐次裁剪空间。是为了让我们可以更通用、便捷的来进行裁剪
观察空间(View Space)
在进行观察空间变换之前,将模型空间下的顶点数据先变换到世界空间下
也就是需要先进行模型空间变换
------
对于观察空间(View Space),是摄像机将自己的视野范围内的物体 进行以自己为坐标系的新定transform(以渲染为目的)
摄像机本身还是作为一个游戏物体,遵循unity内的模型空间
观察空间中,摄像机始终位于 (0,0,0)
,并且通常朝向 -Z
轴。
Unity的观察空间中,观察空间是遵循右手坐标系原则的,因此它的坐标轴方向有所不同。
观察空间中的x、y、z轴的正方向分别对应摄像机的右、上、后方。
主要原因:
在计算机图形学的OpenGL中,为了统一处理场景中物体的渲染和投影
通常使用右手坐标系,为了方便之后的数据处理,因此在Unity当中
的观察空间,也遵循右手坐标系
-----
本质上 position
、rotation
和 scale
只是数值,它们在数学上都是 子坐标系中的参数,最终由 子坐标系的变换矩阵 统一决定。
而当变换坐标系时,这些值都会进行同样的矩阵乘法来改变自身的值。
Unity 中,父子物体(Parent-Child Relationship)不仅影响 Position(位置),还会影响 Rotation(旋转) 和 Scale(缩放)。所有子物体的变换(Transform)都是 相对于父物体 计算的。
轴方向向量(Axis Direction Vector) 是指某个坐标系的 X、Y、Z 轴在另一个坐标系中的表示方式。
1.在进行平移、旋转、缩放的复合运算时
绝大多数情况下,我们约定的变换顺序为:先缩放、具再旋转、后平移
2.在进行x轴、y轴、z轴旋转的复合运算时
绝大多数情况下,我们约定的变换顺序为:z->x->y
之后我们在Unity中进行Shader开发时,遵从这两个规则即可。
在进行复合运算时,变换的结果依赖于变换的顺序
原因:
矩阵乘法不满足交换律
也就是说,不同的变换顺序得到的计算结果也可能是不一样的
比如:
一个人先往前一步,再左转,和 一个人先左转,再往前一步
得到的结果是不一样的,在矩阵运算的乘法运算中也会出现这样的情况
----
主要区别在于是否保持直线的平行性和原点位置
线性变换:保持直线的平行性和原点位置不变,比如缩放、方旋转等操作
简而意之就是只对向量进行旋转、缩放等操作,而不影响方向和原点的位置
仿射变换:由一个线性变换和一个平移组成,比如缩放后、旋转后再平移
简而言之就是缩放后、旋转后平移,会改变原点的位置
产生的意义,新概念引入带来便利的表示
缩写 | 含义 | 说明 |
---|---|---|
R | Row | 行(表格的横向部分,如 Row 1, Row 2) |
C | Column | 列(表格的纵向部分,如 Column A, Column B) |
标准正交基的定义
如果一个 n 维空间的 基向量 v1,v2,...,vnv_1, v_2, ..., v_nv1,v2,...,vn 满足:
- 单位向量(长度为 1):
- 相互正交(点积为 0):
那么这组向量就是标准正交基(Orthonormal Basis)。
基向量是一组特别选取的向量,它们的组合可以唯一地表示空间中的任何向量。
n 个自由度 的空间
在数学中,n 维空间(n-Dimensional Space) 指的是具有 n 个自由度 的空间,例如:
- 1 维空间(线):可以用一个数 xxx 唯一表示,如数轴。
- 2 维空间(平面):需要两个数 (x,y)(x, y)(x,y) 唯一表示,如直角坐标系。
- 3 维空间(立体):需要三个数 (x,y,z)(x, y, z)(x,y,z) 唯一表示,如物理世界的三维坐标。
扩展到 n 维:
- 一个 nnn 维向量 vvv 需要 n 个数 来唯一确定: v=(x1,x2,...,xn)v = (x_1, x_2, ..., x_n)v=(x1,x2,...,xn)
正交矩阵
为什么旋转后长度不变?
旋转后的夹角是相对于原坐标系的 X、Y
两个向量同时乘以Q
任意矩阵只要保持长度不变就是正交矩阵吗?
是的,需要满足所有向量的长度都不变。
1.正交矩阵的每一行,都是单位向量(自己点乘自己结果为1)
2.正交矩阵的每一行,都彼此互相垂直(彼此点乘结果为0)
-------------
不是所有矩阵都有逆矩阵(矩阵的行列式如果为0,则该矩阵不可逆)
代数余子式矩阵,每一个地方都计算余子矩阵的行列式后,组成的矩阵
矩阵的行列式的计算
不同图形接口程序对Shader开发的影响
开发语言不同 坐标系原点不同
学习Shader开发,主要要学习
数学相关知识语法相关知识、着色器开发相关知识等
在Unity当中的Shader开发,我们需要学习
Unity中的ShaderLab语法
着色开发的CG语言等
Shader开发本质就是对
渲染管线(流水线)中上一阶段传递过来的
数据进行自定义处理后
再传递给下一阶段
通过自定义处理5让图形图像最终能够以我们想要的方式显示到屏幕上
通过对渲染管线中的数据进行自定义处理来决定最终的渲染效果
简而言之
通过Shader代码来处理渲染数据
简单理解为片元就是像素,但还包含了一些其他的信息
GPU 的最主要的工作就是是顶点处理,坐标转换,裁剪画面外图元等等
在几何阶段中,我们主要通过自定义顶点着色器阶段
----先跟着唐老师学shader吧
-----这样太低效了,,也没有明确的强度,,都有点像自嗨,,一定要学点重要的,,不然太亏了
伟大蓝图是要做个什么样的,,,,,,迷茫了
- 被 C# 垃圾回收 (GC) 回收
- 被
Resources.UnloadUnusedAssets()
释放(如果它是从Resources.Load()
加载的)
如果 List<AudioClip>
是唯一引用 AudioClip
的地方,当你 RemoveAt(0);
之后,该 AudioClip
就没有任何引用了,可能会被 垃圾回收 (GC) 或 Resources.UnloadUnusedAssets()
释放。
在 Process.Start()
之后监视的是什么?
你监视的默认是 cmd.exe
本身的进程,但 cmd.exe
内部执行的 .bat
脚本可能会再启动一个新的进程(比如 python.exe
或 go-webuiAPI.exe
),这些才是真正占用端口的进程。
****但实际使用的时候,这个必须为true,不然打不开cmd
- 如果
myscript.bat
里有start
这种 Shell 相关命令,可能不会正常执行。 - 如果
cmd.exe
运行时有环境变量,Shell 可能不会继承这些变量。
⚠️ 可能导致意外行为!
运行 .bat
,必须用 cmd.exe
作为解释器
cmd为需要开启的进程时,shell就可以等于false了,因为目的是使用cmd了,但等于true也影响不大
当 UseShellExecute = false
时:
- 不会使用 Windows Shell 来执行命令,而是直接调用进程。
- 一些 Shell 相关的命令(如
start
、assoc
、ftype
)可能无法正确执行。
/c
运行完就关闭,/k
运行完保持打开,,意思是传入的所有命令做完了的情况
在 C# 里找到并关闭 占用某个端口的进程(比如 9880
),使用 netstat
命令(Window)
--大致流程是可以实现的,但效果并不会好太多,也有更重要的东西去学,所以这个就不做了,只用一个端口去读取,队列的读取和释放资源,做到这就可以了
最多就是在SoVITS上检测HistoryText的符号,如果发现了,就拆分开,先放到APi里去处理,然后把返回的audioclip资源挂到自己的队列里,,
角色的语音播放则根据这不断切换需要读的内容
那在这块可以用多个端口的功能,把对GPT 的文本处理速度再提升一些,,不过会麻烦很多,还是不够划算
出现这个情况,是端口被占 了,也就是之前的服务还在没有关,而想创建新的API使用就失效了
/c是打开之后会关闭命令行窗口
但.bat
文件里有 pause
命令,pause
会让命令行停住,等待你按键,所以会常驻cmd
要在startinfo里设置
.bat
需要在某个 工作目录(Working Directory) 里运行
CreateNoWindow = true
不会显示创建的进程窗口,但进程仍然在后台运行。
"/k"
让 cmd
运行后保持打开状态
对要打开的文件格式没有要求,只要是正常可以打开的就是可以的
,现在是对SoVITS打开的优化
整体已经没有问题了
Forget测试有效
暂时不用太注意一些自己想做的优化,,因为可能这样的重要性并不高,有更好的解决办法,但只是目前还不知道
在数学和计算机科学中,delta
(Δ) 表示两个数值之间的差异(变化量)
实际的产出,还需要熟悉lua的使用,在自己做的项目里体现出来,以及ab包的知识点,场景之间切换的性能管控的知识点
布局管理组件
像文本这类有内容物的,control size之后,再force expand,
即使后面都是空的也会把尺寸拉成Content的大小(如果有多个子对象尺寸,这个挂了LayoutGroup的组件父对象,就会对子对象的尺寸进行整体调控,基于自己这个父对象的尺寸内)
父对象添加了VerticalLayoutGroup组件之后,子对象的尺寸值就会被接管
padding是和自己的框的四条边的 向内束死,里面的元素都 只能被挤压 到这个里面
锚点 (Anchor
) 主要决定子对象相对自己的 参考位置 ,
pivot决定自己这张图的定位和旋转中心
很少用代码改里面的尺寸,都是动态生成的情况下比较多
可以直接把滚动条对象删了,但要注意viewport是不是要顶满整个区域,和置空scroll rect的引用
两者的隐藏差距也是在这里
滚动条隐藏之后会自动拓展viewport 的锚点,把整个scroll view的父对象区域完全填充
改变scroll view尺寸,viewport会跟着拉动 (也是上面设置了很多锚点的原因)
Unity ScrollView
结构设计的核心在于 Viewport
和 Content
的分工,确保滚动行为正常
HttpClient.SendAsync 方法 (System.Net.Http) | Microsoft Learn
HttpClient.PostAsync 方法 (System.Net.Http) | Microsoft Learn
只要 stream
还没结束,while
循环就会不断 await reader.ReadLineAsync()
,直到服务器发送 "finish_reason":"stop"
结束流
StreamReader
本身就是stream
的实时引用,但要真正做到流式读取,关键是await reader.ReadLineAsync()
必须运行在循环里,这样它就能一行一行读取服务器的新数据,而不是一次性读取所有内容。