3.2 in、out 与 uniform:跨阶段数据通道


3.2 in、out 与 uniform:跨阶段数据通道

本节摘要:顶点着色器的输出并不直接成为片元着色器的输入——中间隔着光栅化的插值。本节讲清插值的机制与视觉影响、接口配对的规则与高频报错、三类插值限定符的取舍,以及 uniform 数组与块的用法,把顶点阶段的"出口业务"一次办齐。

插值:片元手里的数值从哪来

先看一段最直观的实验代码:让每个顶点携带自己的颜色,看片元阶段拿到什么。

// 顶点着色器 #version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aColor; // 每个顶点一个颜色 out vec3 vColor; void main() { vColor = aColor; gl_Position = vec4(aPos, 1.0); }
// 片元着色器 #version 330 core in vec3 vColor; // 名字、类型与顶点侧的 out 完全一致 out vec4 FragColor; void main() { FragColor = vec4(vColor, 1.0); }

把红、绿、蓝三个颜色赋给三角形的顶点,屏幕上会看到一幅平滑过渡的渐变色——可顶点着色器只跑过三回,颜色从哪来的?答案就是插值:光栅化在生成每个片元时,按该片元在三角形内的位置,对三个顶点的 vColor 做加权平均(权重即重心坐标),把混合结果交给片元着色器。所有 out/in 变量无一例外走这条流程,UV、法线、世界坐标都是被插值过的——理解这一点,第 4 章解释"为什么法线要重新归一化""为什么 UV 会拉伸"才有根基。

插值天然是线性的,这带来一个重要推论:透视投影下线性插值与屏幕观察并不完全一致(近处密远处疏),GPU 默认会做透视校正插值修正这一点,一般无需操心;但在做某些屏幕空间效果时,知道这层机制能省掉大把困惑。

接口配对:规则与高频报错

顶点 out 与片元 in 的配对规则只有两条:名字一致类型一致。违反的后果直接体现在链接阶段:

// 顶点侧声明 out vec4 vColor; // vec4 // 片元侧接错 in vec3 vColor; // vec3 —— 类型不匹配,链接报错 in vec3 vColour; // 拼写不同 —— 片元拿不到数据,读出全零

第二类问题更隐蔽:片元侧漏接。顶点 out 了变量而片元没写对应 in,多数驱动会把这个变量剪掉,不算错误——但如果你本意是片元要用它,画面就会以诡异的方式不对(比如法线全零导致光照全黑)。排查这类"静默剪枝"的方法:在片元着色器里临时把可疑变量直接输出成颜色,看屏幕反应,第 9 章调试节会系统化这个技巧。

还有一个容易忽略的口径问题:gl_Position 不需要你声明 out——它是内置输出,属于管线的"公务通道";自己声明的 out 才需要配对。别把两者混为一谈。

插值限定符:smooth、flat 与 noperspective

默认插值叫 smooth(透视校正的线性插值)。GLSL 允许显式改换方式:

限定符 插值方式 典型用途
smooth(默认) 透视校正线性插值 UV、法线、颜色等绝大多数属性
flat 不插值,取 provoking vertex 的值 低多边形纯色面、面ID、调试可视化
noperspective 屏幕空间线性插值 少数屏幕空间效果

flat 最值得单独演示。低多边形(low-poly)美术风格要求每个三角面是整块纯色,而不是渐变——把颜色限定符改成 flat 即可:

// 顶点着色器:flat 出 flat out vec3 vFaceColor; // 片元着色器:flat 入 flat in vec3 vFaceColor; // 整个面用同一个值,棱角分明的低模风

flat 模式下片元拿到的值来自该图元的"激发顶点"(provoking vertex,通常约定为最后一个顶点),因此用 flat 时顶点数据的组织方式会影响最终每面的颜色归属。除风格化渲染外,flat 还常用于调试:把三角形编号、实例 ID 传下去,屏幕上每面显示一个颜色块,谁是谁一目了然。

💡 关键直觉:插值限定符是"顶点数据到达片元时是否被平均"的开关。看到渐变不对、面块不纯、屏幕空间效果抖动这类症状,先想插值限定符,再想着色器逻辑。

uniform 的进阶用法:数组与块

光源常常不止一盏,材质参数常常成组——uniform 也支持数组和结构化组织:

#define MAX_LIGHTS 8 uniform int uLightCount; uniform vec3 uLightPos[MAX_LIGHTS]; // uniform 数组:循环边界用常量 uniform vec3 uLightColor[MAX_LIGHTS]; // 片元着色器里遍历(循环上限是编译期常量,驱动友好) vec3 total = vec3(0.0); for (int i = 0; i < MAX_LIGHTS; i++) { if (i >= uLightCount) break; total += uLightColor[i]; // 此处略去距离衰减,第 6 章展开 }

更大的参数集适合用接口块(interface block)打包,GLSL 里写作 uniform 块:

uniform PerFrame { // 一次绑定,整块上传 mat4 view; mat4 proj; vec3 cameraPos; float time; }; // 使用时直接写 view、time,像普通变量一样

块的好处是把"每帧常量"与"每材质常量"在 CPU 侧分池管理,一次更新整块生效。这块硬件资源在 HLSL 里对应常量缓冲区(cbuffer),第 8 章对照语法时会再相遇。

本节要点回顾

  • 插值是默认行为:所有跨阶段变量按片元在图元内的位置加权平均,UV、法线、颜色概莫能外;
  • 配对双条件:跨阶段接口名字与类型必须一致,漏接会被静默剪枝,要用"临时输出成颜色"的手段验证;
  • 限定符三选:smooth 服务常规属性,flat 造纯色面与调试可视化,noperspective 留给屏幕空间特例;
  • uniform 组织:数组配常量循环边界,接口块按更新频率分池,为引擎级参数管理铺路。

顶点阶段到此全部打通。下一章站到流水线下游:片元着色器,效果的真正主场。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U