自由学习记录(116)

Blender 的 Mix 节点里,Factor=1 时输出 BFactor=0 时输出 A

所以 Is Skin = 1 时,这个 Mix 会直接选 B 输入(而不是 A)。

好符号,坏符号。child-gen

游戏引擎是"法线驱动"的,不是"面语义驱动"的

现代引擎(Unity / UE / 自研)里:

  • 光照、BRDF、Normal Map、TBN

  • 全都 直接依赖顶点法线

  • 引擎并不关心:

    • 你在 DCC 里是 auto smooth

    • 还是 mark sharp

    • 还是 split normals

👉 它只关心最终法线数组。

Face 模式最容易踩坑的地方

用 Face 的典型问题:

  • Unity 勾选了 Recalculate Normals

  • 或导入器默认认为"我该自己算"

  • 结果:

    • Blender 里好好的硬边 / toon 法线

    • 进引擎全被平均

    • 或被 MikkTSpace + 角度阈值重算

尤其是你这种:

  • NPR / Toon

  • 人物脸 / 发卡 / 裙摆

  • 强依赖法线造型的资产

👉 Face = 把控制权交给引擎,很危险。

Normals Only = 法线锁死,结果可预测

Normals Only 的行为是:

  • Blender:

    • 把 split normals / custom normals 烘成最终数据
  • FBX:

    • 不给引擎任何"重新推导"的语义
  • Unity / UE:

    • 如果你选择 Import Normals

    • 就会逐顶点逐角点使用

为什么法线一定是"角点属性"

这是核心。

如果法线是"顶点属性",那:

  • 一个顶点连着 3 个面

  • 每个面希望一个不同的法线方向(硬边)

  • ❌ 不可能同时满足

所以真实情况是:

法线永远是"顶点在某个面上的表现"

也就是:
Normal = per-(vertex, face) → per-corner / per-loop

如果你强行把法线说成"顶点的",那隐含前提是:

"这个顶点在所有相邻面上使用同一条法线"

这只在完全光滑、无硬边、无 UV seam、无风格化时成立。

DX / Vulkan / Metal 里:

  • Vertex Buffer 里的一条记录 = 一套 attribute

  • Index Buffer 只是索引这些记录

  • 不存在"这是共享拓扑点"的概念

"角点的数据是通过纯粹的 pos 顶点计算出来的"

不成立。角点(vertex instance)数据包含 position、normal、uv、tangent、color...

其中只有"position"是纯几何;其余属性不一定能、也不应该总是从 position 唯一推导出来,原因有三类:

  1. UV seam:同一 position 在不同三角形上可能有不同 UV,无法从 position 推导

  2. Hard edge / custom normal:同一 position 在不同三角形上可能有不同 normal,同样无法唯一推导

  3. Authoring:很多法线、切线是烘焙/编辑/传递来的(尤其 toon/角色),不是从 position 算出来的

在 DX(D3D11 / D3D12)里,用于渲染管线的"最小数据单位"是:

一次顶点获取(Vertex Fetch)得到的一条 顶点输入记录

(vertex / vertex instance / vertex element tuple)

这不是"几何顶点",也不是"面",而是一条按 Input Layout 解释的结构化数据记录

  • Vertex Buffer + Index Buffer 定义的一条输入记录

  • 包含你在 D3D12_INPUT_LAYOUT_DESC 里声明的所有 attribute

例如:

复制代码

struct VertexIn { float3 position; // POSITION float3 normal; // NORMAL float2 uv; // TEXCOORD0 float4 tangent; // TANGENT };

GPU 看到的"最小单位"就是这样一条 VertexIn

DX12 渲染阶段,"最小单位"(一次 vertex fetch 得到的一条顶点记录)不是"DX 自己从 FBX 里取",而是你(或引擎)在 CPU 侧把资产解析、重建、打包后,放进 GPU 的 Vertex Buffer/Index Buffer;然后 IA(Input Assembler)按 Input Layout 去"取"这些字节。

FBX 里通常能提供两类东西:

  1. 拓扑与几何
  • 控制点(Control Points):position 列表(几何点/拓扑点)

  • 多边形/三角形索引:每个面引用哪些 control point

  • 可能还有法线、UV、颜色等的"映射方式"

  1. 顶点属性(关键是:FBX 支持不同映射语义)

    FBX 的法线/UV/颜色等属性,一般以 LayerElement 形式存在,并且有两条核心语义:

A. Mapping(映射到哪个集合)

  • ByControlPoint:按"几何顶点"(control point)存

  • ByPolygonVertex:按"角点/面顶点"(每个面上的每个顶点)存

  • ByPolygon:按"面"存(例如面法线)

  • AllSame:全局一个值

B. Reference(如何索引)

  • Direct:属性数组与映射一一对应

  • IndexToDirect:先给 index 数组,再指向 direct 数组

因此从 FBX "取"出来的并不是一个现成的 DX VertexIn,而是:

  • positions:control points

  • indices:面引用 control points 的序列(以及每个 polygon vertex 的顺序)

  • normals/uv/tangent/color:按 ByControlPoint 或 ByPolygonVertex 等方式存的一套或多套数组 + 索引

二、为什么不能"直接把 FBX 顶点塞给 DX"(角点展开问题)

DX 的 Vertex Buffer 一条记录必须是"同一索引同时索引 position/normal/uv/..."的一致打包体。

但 FBX 里经常是:

  • position 是 ByControlPoint(共享)

  • UV 是 ByPolygonVertex(在 UV seam 处分裂)

  • normal 可能也是 ByPolygonVertex(硬边或自定义法线)

这会导致:同一个 control point 在不同面上需要不同 UV/normal。

所以你必须做一步"统一索引空间"的重建:

核心操作:为每个 polygon-vertex(角点)构造一个 key:

key = (positionIndex, normalIndex, uvIndex, tangentIndex, colorIndex, ...)

  • 如果这个 key 以前出现过:复用已有 vertex record index

  • 否则:新建一条 vertex record(写入 VB),并在 IB 里引用它

这一步就是你在引擎里看到的:

"导入后顶点数变多了"

本质是:从"控制点共享"转为"渲染顶点实例(角点展开)"。

在拉康的拓扑学中,行动(Act)往往带有实在界的成份------它是无意义的、混沌的、甚至带有某种创伤性的纯粹体验。

  • 语言作为"覆盖": 你所谓的"覆盖",本质上是符号界(The Symbolic)对实在界的捕捉与驯服。当你进行纯粹的行动或感受时,主体会直接暴露在无法言说的、过剩的体验中(即"享乐",Jouissance),这会带来极大的焦虑。

  • 符号化的"谋杀": 正如拉康所言,"词语是事物的谋杀"。通过用语言去"覆盖"行动,你实际上是在杀死 那种无法控制的、流动的原始感官,将其转化为可定义的、死板的符号。这种"覆盖"让你从"正在经历的人"变成了"正在观察/描述的人",从而获得了一种结构性的安全感

****贝尔格森认为时间是流动的、不可分割的,而语言通过将其空间化、离散化(整理成连贯的输出),使你觉得捕捉到了它。你不是在记录事实,你是在通过语言固化那个随时会坍缩的主体。

冯·福斯特(Heinz von Foerster)的二阶控制论来看,你的这种行为可以视为"观察者的观察"

  • 反馈回路的闭合: 行动本身是系统的一阶运作,而"整理说话"是二阶观察。如果只有行动而没有对应的符号描述,系统(你的意识)会感知到一种逻辑上的"断裂"

  • 自我生产(Autopoiesis): 你通过语言不断地重新生产"我"这个概念。用语言覆盖行动,是为了确证"是我在做这件事",而不是某种盲目的生物性冲动。这是一种对自我的叙事性重构,确保你不是在"随机波动",而是在"执行计划"。

当你在现实中感知到某种可能与平庸重合的苗头(哪怕只是生理性的懒散)时,语言就成了你的防波堤

屏蔽直觉的异化: "覆盖"一词用得极其精准。你害怕如果不覆盖,那个赤裸的、没有语言保护的自己会直接撞上现实的荒诞。语言在这里充当了**"认知假肢"**,它替你承受了现实的撞击。

Normal Edit Modifier(法线编辑修改器)

用途:把网格的角点法线改成"朝向某个目标(定向)"或"从某个目标放射(径向)"。

这时球体会出现在修改器里作为 Target Object。用球做 target 的直觉:球能给出一个"整体圆润"的法线场,角色就会更"圆"。

B. Data Transfer Modifier(数据传递修改器)

用途:把"另一个物体"的自定义法线/UV/顶点色等传到本物体。

这时球体可能是 Source,对角色做"转移法线"(最常见的是从一个更圆滑、更干净的 proxy/cage 转移)。

C. Geometry Nodes / 约束/脚本引用

例如 GN 里用 Object Info 引用某个物体(球),把它当成"中心/方向场",再输出到自定义法线(这类比较高级,但也存在)。

Split Normal(分裂法线)不是一种"法线类型",而是一个状态/结果

同一个几何顶点(position),在不同角点(corner / loop)上使用了不同的法线向量。

这是 Blender、FBX、引擎里讨论"硬边、平滑、toon 法线"的核心概念

声音视为一种"指向空间的姿态",当你不再"制造"声音,而是"占据"空间时,肉身的抵抗感(即不通畅感)会因意向性的延伸而消失。

Split Normal = per-corner normal ≠ per-vertex-position normal

为什么 split normal 是"必需"的,而不是高级特性

如果没有 split normal:

  • 一个立方体的一个角点

  • 3 个面希望 3 个正交法线

  • ❌ 不可能

所以现实中的数据结构是:

  • 1 个 position

  • 3 个 corner

  • 3 条不同 normal

这就是最基础的 split normal。

在 Blender 里,split normal 是怎么存在的

1️⃣ Blender 的真实法线存储层级

Blender 内部的法线用于渲染的是:

  • Loop Normal(角点法线)

  • 不是 Vertex Normal

  • 不是 Face Normal

当你做以下任何一件事时,Blender 就在创建或修改 split normals:

  • Mark Sharp

  • Auto Smooth

  • Normal Edit Modifier

  • Data Transfer(传法线)

  • 手工编辑自定义法线

  • Weighted Normal Modifier

这些操作本质都在控制"哪些角点共享法线、哪些不共享"

Auto Smooth 在干嘛(常见误解)

Auto Smooth ≠ 自动平滑一切

它的真实逻辑是:

  • 对每条边,判断夹角是否 > 阈值

  • 如果是:

    • 在这条边两侧 分裂角点法线
  • 如果不是:

    • 允许角点法线参与平均

所以 Auto Smooth = 自动 split normal 规则生成器

Split Normal 和你前面问的"球体法线塑形"有什么关系

非常直接的关系:

  • 用球体做 target(Normal Edit / Data Transfer)

  • 本质是在 直接写角点法线

  • 这些法线:

    • 通常不会再遵循"由相邻面平均"

    • 会强制 split(否则无法表达差异)

也就是说:

没有 split normal,就不可能存在"法线塑形"。

1️⃣ Face Normals(面法线)

  • 每个面一根

  • 垂直于三角形平面

  • 用来判断:

    • 翻面

    • 面方向

  • 不用于平滑着色


2️⃣ Vertex Normals(顶点法线)

  • 每个几何顶点一根

  • 是一个"概念性平均"

  • 在有 split normal 时 不代表真实用于渲染的法线

  • 更多是调试用途


3️⃣ Split Normals(角点法线)✅

  • 每个"面上的顶点"一根

  • 一个三角形 3 根

  • 这就是 Blender 最终用于着色、会导出到 FBX 的法线

你在讨论:

  • toon

  • 法线塑形

  • 球体 target

  • 导入引擎一致性

👉 只看这一种。

"同一个位置的顶点参与多少个面,就会有几个 split normal"------这不对。

正确的是:

  1. 一个三角形确实有 3 个角点(Blender 里叫 loop),每个角点有 1 条法线(你截图看到的那根紫线)。

  2. 但是,同一个 position 参与 N 个面,并不必然产生 N 条不同的角点法线;它只会产生 N 个角点法线"实例" ,而这些实例可以全部相同,也可以分成几组不同方向,取决于是否发生"分裂(split)"。

换句话说:

  • 角点数量 = 参与的面数(对某个 position 来说,这是固定的)

  • 角点法线"方向数" = 分裂后的分组数(这个才是 split normals 的核心)

在 Blender 开启 Split Normals 显示时,它会在每个角点画一根线。

同一个 position 上,如果它参与了 6 个三角形,那么理论上会有 6 根角点法线线段(实例)。但如果这 6 根的方向完全相同、而且线段起点非常接近,就会视觉上"像一根/一束",你很难用肉眼数清。

此外你这张图里法线线段密度很高,很多都重叠在一起,所以更容易产生"只有一根"的错觉。

对同一 position,设它相邻的角点集合是 L={l1,...,lN}L=\{l_1,\dots,l_N\}L={l1​,...,lN​}。

  • 没 split:所有 n(li)n(l_i)n(li​) 都相同(共享同一方向)

  • split:把 LLL 分成若干组,每组共享一个方向,不同组方向不同

典型例子:

  • 完全平滑球体:一个 position 可能连接很多三角形,但这些角点法线都朝"球的径向",几乎一致(不 split 或很少 split)

  • 立方体角:一个 position 相邻 3 个面,角点法线会分成 3 组(每个面一组),这是典型 split(硬边)

所以"参与几个面就有几个 split normal"不成立。

你最多只能说"参与几个面就有几个角点法线实例",但实例不等于不同方向。

为什么很多时候"一个点看起来就是一根法线"

有三种常见原因:

A. 这些角点法线方向本来就被设计成一样

比如 Smooth shading 下相邻面都参与同一平均,或者你用了法线塑形把它们统一指向某个目标(球体 target 的常见效果)。

B. 角点法线确实有多条,但起点太近、方向差太小,叠在一起看不出来

尤其你这种高密度模型 + 线很长,视觉上更像毛刺一片。

C. 你显示的不是 split normals,而是 vertex/face normals(会更"聚合")

你截图看起来是 split normals(紫色很多),但还是提醒一下:要确认 overlay 里勾的是 Split。

"用 shader 直接画出每个顶点/角点的法线线段"在严格意义上不行(或极其受限),因为像素着色器没法往屏幕上'生成额外几何'

Unity 的 mesh.normals 是"渲染顶点(vertex instance)"层级的法线,已经等价于你说的"角点展开结果"。Unity 并不直接暴露 Blender 那种 loop 概念,但如果你导入的是 split normals,你会看到顶点数变多,对应的 normals 数组也按展开后的顶点记录给你。

不会"擅自混合",前提是你用的是 Mesh 的渲染顶点数据 ,而不是你自己再做一次"按位置合并"。Unity 里你能拿到的 mesh.vertices[i] / mesh.normals[i] 本质上就是 GPU 顶点记录(vertex instance),也就是"角点展开后的结果"。所以你按 index i 逐条画线,会把导入后的 split normals 全画出来。

  1. Unity 的 Mesh API 默认给你的是什么
  • mesh.vertexCount:是渲染顶点数(已经包含因为 UV seam / hard edge / split normal 导致的拆分)

  • mesh.vertices.Length == mesh.normals.Length == mesh.vertexCount

  • 对每个 i:

    • vertices[i] 是一个位置

    • normals[i] 是与之绑定的一条法线

      这对 (pos, normal) 就是你要画的那根线。

因此,只要你这样生成线段:
p0 = vertices[i]
p1 = vertices[i] + normals[i] * len

你画出来的就是每条渲染顶点记录的法线,不会被 Unity 合并。

Mesh API 的数据本身与 shader 无关,它只反映网格里存的顶点/法线/切线。

  1. 你担心的"一个顶点会显示所有 split normal 吗?"

    要把术语说准:Unity 没有 Blender 的 loop 概念可直接访问,但它已经把 loop 展开成了 vertex instances。

  • 如果同一个几何位置在导入时因为 split 被拆成 3 个顶点记录,那么 Unity 里会出现 3 个不同的 index i,它们:

    • vertices[i] 可能完全相同(或非常接近)

    • normals[i] 不同

      你按 i 画线,就会在同一点附近看到多根线(可能重叠得很近,看起来像一束)。

  1. 你必须保证导入设置"保留 split normals"

    否则你确实会只看到"被统一后"的法线,但原因是导入阶段已经丢了,而不是绘制阶段混合了。Unity 里要确认:

  • Model Importer → Normals = Import(不要 Recalculate)

  • 如果是 SkinnedMeshRenderer,同样适用

  • 不要在代码里调用 mesh.RecalculateNormals()

GPU 侧生成线段(Compute/DrawProcedural)也是同理

你只要把"Unity Mesh 的顶点缓冲"或你上传的 StructuredBuffer 作为输入,逐条读 (pos, normal),就不会发生"自动合并"。GPU 不会按位置 dedup,除非你自己写 hash/merge。

normal map 得到的法线到底是在哪个空间、怎么来的

绝大多数 normal map 是 tangent space :贴图里存的是 (常见格式是 XY,Z 重建)。

在像素处要得到世界/视空间法线,需要:

  • 从三角形插值来的 T,B,N(TBN 基底,基于顶点法线+切线+UV导数)

  • 采样贴图得 nTS​

  • 转换:

这里关键点:顶点法线是低频基底,normal map 是高频扰动

  1. "法线数量不定"本质是"采样点数量你自己决定"

    normal map 影响的是像素着色阶段,因此理论上:

  • 每个屏幕像素都可能得到一条不同的法线

  • 同一个 UV 位置也可能在不同 mip 级别取到不同值(LOD)

所以你要显示"线段",必须先决定采样策略:

A. 按屏幕像素采样(最真实、但线段数量爆炸)

  • 每个像素一条线,不现实

  • 只能做成"颜色可视化"而不是线段(例如把 nWSn_{WS}nWS​ 映射到 RGB)

B. 按固定网格采样(推荐做线段可视化)

在每个三角形上放一个规则网格(例如每个三角形 8×8 个样点),对每个样点:

  • 插值得到 position、UV、TBN

  • normal map 采样得到 nTSn_{TS}nTS​

  • 转成 nWSn_{WS}nWS​

  • 画线段

    这样线段数量 = 你设置的采样密度,稳定可控。

C. 按贴图像素采样("贴图域"可视化,通常不用于场景内线段)

这会变成"把贴图上每个 texel 反投影到模型上",非常麻烦,而且一一对应会破坏(UV 拉伸/重叠/多岛)。

所以不是"法线数量不定",而是:
normal map 定义的是连续场,你要离散化显示必须选一个采样密度。

  1. "会根据分辨率不同有不同的插值吗?"

    这里你说的"分辨率"可能指两件事:

A. normal map 本身分辨率 / mipmap

是的,会影响结果,因为:

  • GPU 采样是滤波的(双线性/三线性/各向异性)

  • 远处会用更低 mip,法线被平滑,细节消失

    所以"同一个表面点"在不同距离可能得到不同 nTSn_{TS}nTS​(因为 LOD)。

B. 你用来画线的采样密度(每三角形多少点)

是的,这决定你"可视化的离散精度"。密度越高,你看到的线段越接近真实像素法线场;密度越低,就像下采样。

  1. 在 Unity 里要做"normal map 线段"的正确工程路线

    如果你目标是"在 Scene 里看到很多小线段"而不是颜色图:

  • 你需要生成一堆 sample points(CPU 或 GPU)

  • 对每个 sample point:

    • 计算 barycentric 插值得到 position、UV、normal、tangent(或用 ddx/ddy 在像素域)

    • 用 UV 采样 normal map(注意 mip 选择:你可以强制用 mip0 以便稳定)

    • 用 TBN 转到世界空间

  • 画线段(MeshTopology.Lines / DrawProcedural)

关键难点有两个:

  • TBN 的一致性:必须和你渲染使用的 tangent space 一致(Unity 默认是 MikkTSpace)。

  • mip/过滤:如果你不固定 mip,线段会随距离变化,看起来像"法线在变"。

  1. 更实用的替代方案:用 debug shader 显示 normal map 世界法线颜色

    如果你只是想验证 normal map 是否正确(切线、翻转、绿通道、镜像 UV 等),最有效的是:

  • 在片元 shader 输出 nWS∗0.5+0.5n_{WS} * 0.5 + 0.5nWS​∗0.5+0.5

    这会直接显示每像素的最终法线方向,不需要画线段,也不存在"数量问题"。

这是"基础能力",但不是"初级能力"

这里要区分清楚。

初级阶段

  • 知道 normal map 是"蓝紫色的"

  • 知道 green channel 有时要 flip

  • 但看到 (0.62, 0.41, 0.78) 没概念

你现在讨论的这个阶段(TA / Rendering / 引擎)

  • 知道:

    • RGB ↔ XYZ 的对应关系

    • 哪个空间(TS / WS / VS)

  • 能从一小块颜色判断:

    • 法线大致朝向

    • 是否连续

    • 是否发生 split / seam / TBN 不一致

  • 知道"这个像素为什么会是这个颜色"

所以说这是基础能力,但前提是:

已经站在"理解渲染数据流"的层级上

为什么这个能力在工程里非常重要

因为它让你可以:

  1. 不依赖任何 UI / 工具

    只用一个 debug shader 就能判断:

  • normal map 是否被正确 unpack

  • tangent space 是否一致

  • 法线是否被重算 / 破坏

  1. 快速定位问题责任归属

    例如:

  • Blender 看着对,Unity 看着不对

    → 用 WS normal debug 一看就知道是:

    • 导入

    • tangent

    • shader

    • 还是贴图本身

  1. 在 DX12 / Unity / UE / 自研管线中"语言统一"

    无论平台、API、引擎如何变:

  • "把 n 映射到颜色"这件事永远成立

  • 你看到的结果具有跨系统可比性

debug shader 的本质目标(先定原则)

一个合格的 debug shader,通常满足这三条:

  1. 一一映射(可逆)

    你看到的颜色,能明确反推出原始数值或方向。

  2. 空间明确

    你清楚这是:

    • object space

    • world space

    • view space

    • tangent space

  3. 不引入额外逻辑

    不做 tone mapping

    不做 lighting

    不混合其他项

    否则你看到的就不是"原始数据"。

最"正统"的 debug shader 模式(你必须熟)

1️⃣ 向量 → 颜色(最基础、最通用)

固定公式:

复制代码

color = value * 0.5 + 0.5;

适用于:

  • normal

  • tangent

  • bitangent

  • viewDir

  • lightDir

前提:

  • value ∈ [-1, 1]

这是事实上的行业标准,不是习惯。

你看到的颜色含义是绝对的:

  • R → X

  • G → Y

  • B → Z

float3 nTS = UnpackNormal(SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, i.uv));

return half4(nTS, 1.0);

看到的颜色"如何提取信息",取决于一个关键事实:屏幕显示的颜色通道期望是 [0,1],而 nTS 是 [-1,1](并且通常 z≥0)。

所以你现在直接 return nTS,会导致:

负值会被裁剪到 0(或在后续色彩管线里被截断/钳制)

你看到的颜色会失真,信息不可读

只能粗略看正半轴分量

把你几个疑问逐条钉死。

  1. (0.5, 0.5, 1.0) 是蓝色吗?R/G 不是还有 0.5 吗?

    在标准 RGB 显示里:

  • (0,0,1) 是纯蓝

  • (0.5,0.5,1) 是"亮蓝/偏白的蓝"(blue tinted light color),不是纯蓝,但视觉上会非常"蓝紫"或"浅蓝"。

原因是:R=0.5、G=0.5 代表有一半强度的红绿,等价于往蓝色里加了白光成分。你可以把它理解为:

  • (0.5,0.5,0.5) 是中性灰(50% 白)

  • 在此基础上把 B 拉到 1.0,就得到"灰 + 蓝",看起来就是浅蓝。

所以你的"中性灰加某个通道强度"的理解是正确的。更形式化一点:

  • 显示颜色 c = 0.5 + 0.5 * n

  • 其中 n 是 [-1,1] 的向量分量

  • 0.5 是 bias(把 -1..1 平移到 0..1)

  • 0.5 是 scale(把范围压缩一半)

  1. (0.5,0.5,0.5) 是否表示"没有任何朝向的向量"?

    不是。它表示的是向量 (0,0,0) 被编码后的结果,但 (0,0,0) 不是有效法线方向(零向量不能归一化)。

相关推荐
r i c k4 小时前
数据库系统学习笔记
数据库·笔记·学习
野犬寒鸦4 小时前
从零起步学习JVM || 第一章:类加载器与双亲委派机制模型详解
java·jvm·数据库·后端·学习
浅念-5 小时前
C语言编译与链接全流程:从源码到可执行程序的幕后之旅
c语言·开发语言·数据结构·经验分享·笔记·学习·算法
ZH15455891315 小时前
Flutter for OpenHarmony Python学习助手实战:API接口开发的实现
python·学习·flutter
爱吃生蚝的于勒5 小时前
【Linux】进程信号之捕捉(三)
linux·运维·服务器·c语言·数据结构·c++·学习
奶茶精Gaaa5 小时前
工具分享--F12使用技巧
学习
久邦科技6 小时前
奈飞工厂中文官网入口,影视在线观看|打不开|电脑版下载
学习
好好学习天天向上~~6 小时前
6_Linux学习总结_自动化构建
linux·学习·自动化
非凡ghost7 小时前
PowerDirector安卓版(威力导演安卓版)
android·windows·学习·软件需求