从曲面细分到计算着色器:我的 Vulkan 实验记录

发布时间:2026/10/15 4:38:10

从曲面细分到计算着色器:我的 Vulkan 实验记录 一、曲面细分Tessellation做了什么给鸭子模型加了一条细分管线。细分是在渲染管线里插两个新阶段细分控制着色器TCStessellation control和细分求值着色器TEStessellation evaluation位置在顶点着色器之后。UI 上加了开关可以在 Solid正常绘制和 Tess细分绘制之间切换。卡住的地方这部分我一开始连main.tesc都没写 —— 因为完全没理解控制阶段要干什么感觉无从下手也不想抄。后来把概念理清才补上。几个当时搞混的概念layout(vertices3) out;不是 输出 3 个顶点而是声明一个 patch 输出几个控制点。我画的鸭子模型每个 patch 就是三角形所以是 3。gl_TessLevelOuter/gl_TessLevelInner是 GLSL 内部变量built-inTCS 里给它们赋值驱动就按这个数值决定把一个 patch 切成多少小三角形。Outer 管外环每条边一个值Inner 管内部。gl_TessCoord是重心坐标 (u, v, w)TES 里用它把三个控制点插值成最终顶点位置。distance()那个函数是我在 shader 里自己写的辅助函数不是 GLSL 内建函数 —— 距离近的边细分等级高远的低。踩的坑Tess 模式模型混乱第一次跑通Solid 模式ALL鸭子正常切到 Tess 模式整个模型是乱的后来改完又变成完全变形。查的方向是Tess 模式走的是另一条渲染管线顶点输入和传递到后级阶段的数据结构必须和主管线保持一致。main.vert里有PerVertex结构被后面阶段共用改动它会影响其他模式的绘制所以要非常小心。把数据结构统一之后ALL 和 Tess 两个模式都正常了。学到的东西细分让同一个低精度控制网格在 GPU 上按需加密离得近的地方细分等级高、边缘更圆滑不用 CPU 反复改顶点数据。gl_TessLevelOuter/Inner是驱动读取的 切几刀 指令控制点和重心坐标是它的输入输出。这是以后做地形、头发、动态 LOD 曲面会用到的东西。二、顶点压缩与间接绘制C5-07做了什么两件事把顶点数据压缩以及用一条命令绘制多个模型含 LOD。顶点压缩32B → 20B原来的顶点结构是 pos normal uv 各一个float3/float3/float232 字节。压缩后pos Float3 12B // 位置保持精度不能动 normal Int_2_10_10_10_REV 4B // xyz 各 10 bit 有符号整数 uv HalfFloat2 4B // 半精度 总计 20B省了 37.5%Int_2_10_10_10_REV这个格式就是 32 bit 打包三个 10 bit 分量外加 2 bit 空余专门给法线这类 够用就行 的数据。法线压成 10 bit 肉眼看不出区别UV 用半精度对 8K 纹理也完全够。要记住的是压缩顺序和解压顺序必须一致shader 里怎么 unpackCPU 侧写入就得怎么 pack顺序反了数据全乱。LOD 与多 meshmeshopt 提供两档简化meshopt_simplify正常减面simplifySloppy更激进只减索引不减顶点模型绘制不会出错只是面更大。远处用 sloppy近处用严格版本。拆 LOD 的条件targetIndicesCount 1024 LOD 8够大才拆最多 8 级。多个 mesh 合进同一个顶点 / 索引缓冲靠indexOffset、vertexOffset定位每个 mesh 的位置每个 mesh 自己的 LOD 用lodOffset/lodCount管理。getLODIndicesCount在偏移数组末尾多放一个值用来标记整个索引区域的结束从而能推算出每级 LOD 的大小 —— 这个设计一开始没看懂。drawIndexedIndirect把绘制参数放进 GPU bufferCPU 只提交一次命令GPU 自己读参数画多个模型。这和 CPU 逐个cmdDrawIndexed的区别是一个是一次性批量提交一个是多次调用。踩的坑compact 模式模型错乱索引和顶点的对应关系没对上绑错了数据源理解了 索引必须指向正确的顶点位置 后才修好。compact 模型纹理不显示UV 方向问题做了翻转处理。processLods里处理顶点流时步长写错过/2还是/3的问题顶点流每份数据的字段数要核对清楚。exterior.obj资源没下载到重解压才拿到。主程序越来越长拆出了 mesh.h/cpp、pipelines.h/cpp、ui.h、input.h拆的过程中发现顶点结构定义有重复去重了一遍。三、计算纹理C5-08做了什么用计算着色器compute shader往一张纹理里写数据做出等离子效果plasmaUI 上加开关切换是否使用这张计算纹理。知识点计算着色器不属于渲染管线是通用并行计算。layout(local_size_x 16)声明每个工作组group有 16 个线程。计算纹理用的是 storage imageshader 里直接imageStore往纹理写像素。gl_GlobalInvocationID是所有线程里的全局索引gl_LocalInvocationID是当前工作组内的索引。用学校打比方学校是全局、班级是工作组、学生是线程16 是组内线程数64 是总线程数。等离子效果的数学核心是fract配合时间参数用它做颜色阈值和混合随时间推移画面就流动起来。踩的坑等离子不动一开始效果是静态的画面完全不动。原因是时间没真正参与进计算或者压根没传进 shader。把时间传进去、让颜色的参数随时间变化后波纹就流动起来了。先写后读的顺序也要注意compute 写完纹理渲染阶段再去采样中间需要同步。四、计算网格C5-09compute 生成波浪平面做了什么用计算着色器每帧生成一个波浪平面的全部顶点写进顶点缓冲再走一条独立的渲染管线画出来。UI 开关useComputeMesh打开就用波浪平面替代模型。这是整段里踩坑最多的一章。数学上很简单平面铺在 XZ 上40×40每个顶点y sin(x*0.8 time) * 1.5法线用导数求dy_dx cos(...) * 1.2法线normalize(vec3(-dy_dx, 1, 0))。网格 128×128每线程写一个顶点dispatch的组数 (numU*numV 15) / 16。踩的坑按出现顺序坑 1波浪面悬浮乱转、形状奇怪。根因波浪平面错误继承了模型矩阵。原来的模型数据带着缩放、绕 X 翻转、绕 Y 旋转动画m2*m波浪复用这套矩阵后也跟着旋转缩放。解决给波浪建了独立的渲染管线push constant 只推p*v一个矩阵坐标完全独立。坑 2shader 解析失败但没有任何错误日志。排查很久最后发现 LVK 桌面构建的LLOGW宏是空的shader 编译错误根本不打印。用 PowerShell 对文件做字节扫描才发现wave.comp里有一段中文注释是 GBK 编码glslang 解析失败。改成 ASCII 注释后通过。教训shader 文件编码必须干净。坑 3不规则多边形 左侧尖刺。这是 compute 写顶点 → 渲染读顶点 缺了内存可见性同步。LVK 的做法是在cmdBeginRendering的参数里列出依赖的 buffer{ .buffers { rectVertexBuf } }它内部会插一道 COMPUTE → VERTEX_INPUT 的 barrier。加了一行就正常了。坑 4结构体还是错位查得最久的一个。想用标准结构体写法struct VertexData { vec3 pos; vec3 normal; vec2 uv; }; layout(buffer_reference) buffer VertexBuf { VertexData vertices[]; };vec3 对齐 16成员偏移是 pos0、normal16、uv32内容大小 40 字节。我一开始让 CPU 侧也用 40 字节对齐结果图形仍然不规则。后来才搞清楚std430 里结构体数组的元素 stride 不是 内容大小而是要对齐到结构体对齐16的倍数 —— 所以是 48 字节不是 40。内容大小 40 → 对齐 16 → 数组 stride 48 CPU 按 40 读GPU 按 48 写每顶点错 8 字节顶点一多数据全乱为什么 float 数组和 vec4 数组不会出这个事因为数组元素的 stride 就是它自己的大小float 4B、vec4 16B没有结构体整体对齐这一层。正确的 CPU 侧写法是显式凑到 48 字节并且用编译期断言锁死struct VertexData48 { glm::vec3 pos; // 0 float pad0; // 12 glm::vec3 normal; // 16 float pad1; // 28 glm::vec2 uv; // 32 float pad2; // 40 float pad3; // 44 }; static_assert(sizeof(VertexData48) 48, std430 struct stride must be 48);顶点输入描述里 stride 用sizeof(VertexData48)偏移用offsetof(VertexData48, ...)而不是手写数字结构体以后加字段也不会错位。坑 5边缘锯齿的误判。修好之后还有一轮 锯齿 疑虑。后来分清两种锯齿三角形网格边缘的阶梯状轮廓正常现象提高网格分辨率或上 MSAA 才能改善和数据错位导致的形状撕裂才是 bug。当时那个 不规则多边形 其实是坑 4 的错位不是锯齿。学到的东西buffer_referenceshader 持有一个 GPU 地址像指针一样解引用类型名(地址).成员访问。可以把地址缓存成局部变量再用。compute 写完的 buffer 要渲染读必须显式做同步barrierLVK 里就是cmdBeginRendering的buffers依赖参数。std430 布局完整规则vec3 对齐 16 之外结构体数组 stride 还要对齐到 16 的倍数—— 这是最容易漏的一层。工程习惯C 侧static_assert锁布局、offsetof取偏移改结构体不再担心静默错位。回头看的几点布局错位是 GPU 开发最阴的 bug编译能过、不崩溃只是画面乱。static_assertoffsetof是便宜又有效的防线。compute → 渲染的同步不能省先写后读必须 barrier否则时序抽风。shader 编码文件必须是干净的 ASCII/UTF-8中文注释用 GBK 保存会直接让解析失败而且日志还不一定打出来。数组没有结构体对齐结构体有float/vec4 数组怎么写都对一旦用结构体共享数据stride 对齐 16 这层不能忘。四章走下来从 往 shader 传几个矩阵 到 GPU 自己生成顶点、自己写纹理算是摸到 Vulkan 里数据流动的核心了。
延伸阅读

更多相关文章

2026/10/15 4:38:10

AI应用开发工程师是2026年最紧缺的技术岗位之一

AI应用开发工程师是2026年最紧缺的技术岗位之一。与传统算法工程师不同,这个角色不需要从零训练模型,而是专注于将大语言模型(LLM)能力与业务场景深度结合,构建可落地的AI产品。核心定位:以Python为主要语言…

2026/10/15 4:38:10

权重分配算法全解析:从加权随机到平滑加权轮询的工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 4:33:10

2025薪酬报告:用分位值、职级映射与离职率校准真实人力成本

简介:本资源为华德士发布的《2025年中国薪酬报告》权威PDF版,面向企业招聘经理、HR从业者、职场人士及行业分析师,聚焦多行业薪资基准制定、人才招聘策略优化与中长期市场趋势研判。报告覆盖信息技术与转型、医疗健康、制药、法务合规、工程运…

2026/10/15 5:43:12

Agent开发省钱指南:少踩坑比省Token更值得投入

1. 为什么“少踩坑”比“省Token”更值得投入做过Agent项目的人大概都有过这种体验:上线前反复压缩Prompt,把系统提示词从两千字砍到八百字,把few-shot示例从十个减到三个,看着后台的Token消耗曲线往下掉,心里挺美。结…

2026/10/15 5:43:12

2026 Java面试高频考点速查清单:集合、并发、JVM、Spring全覆盖

2026年了,Java面试还是躲不开八股。与其抱怨“面试造火箭、工作拧螺丝”,不如换个角度想:八股题考察的恰恰是知识体系的完整性和深度,而这两点在大型项目里直接决定了你排查问题的上限。我自己近两年帮团队做技术面试,…

2026/10/15 5:43:12

考研分数线预测与院校推荐系统:Django+Vue全栈实现指南

做计算机毕业设计这些年,见过太多同学一上来就急着找代码、跑项目,结果一到答辩就被问得哑口无言。考研分数线预测与院校推荐这个题目,是这几年大数据方向里少有的“数据价值能讲清楚”的选题,同时难度适中,适合本科阶…

2026/10/15 5:43:12

PyTorch从入门到精通:动态图、自动求导与显存管理实战

1. 为什么“pytorch”这个标题值得单独拎出来聊如果你在技术社区里闲逛,看到有人只甩出“pytorch”这一个词当标题,大概率会以为这是一篇入门安装教程。但我第一眼看到这个标题时,脑子里冒出来的却是另一幅画面:一个已经跑通了几个…

2026/10/15 5:43:12

组合模式实战:用一致接口优雅处理树形层级数据

设计模式里有一类很特别的结构型模式,它看起来不起眼,但一旦遇到"树形层级数据",它几乎就是最优雅的解法——这就是组合模式。组合模式的核心思想用一句话讲:把对象组织成树形结构,表示"部分-整体"…

2026/10/14 11:31:03

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/14 17:41:07

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/14 2:32:55

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/15 0:02:22

防关联浏览器哪个好?三款产品实测差异解析

新手做多店铺,最纠结的就是防关联浏览器选哪个。飞跨、网易、站斧这三款被问得最多,先给结论:要一体化省心、长期便宜,选站斧;要网易招牌和稳,选网易;预算紧做东南亚 Lazada,选飞跨。…

2026/10/15 0:02:22

影刀RPA深度实战:大模型Fine-tuning定制RPA专用Prompt与指令生成

影刀RPA深度实战:大模型Fine-tuning定制RPA专用Prompt与指令生成 一、技术背景 大模型(LLM)在通用场景下表现强大,但当我们将 GPT-4 或 Qwen 直接用于生成影刀RPA操作指令和 XPath 定位时,准确率通常只有 40%-60%。原因…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑