2026/6/11 4:37:07
网站建设
项目流程
php网站开发实战视频,wordpress 鼠标特效,新网站建设信息,wordpress照相馆主题光栅化就是#xff1a;
把一个个三角形#xff0c;变成一堆“待定像素点候选人”#xff08;片元 Fragment#xff09;#xff0c;
每个候选人身上还挂着“位置、UV、法线、颜色”等一堆信息#xff0c;
等着后面片元着色器给它“发不发录取通知书#xff08;最终像素把一个个三角形变成一堆“待定像素点候选人”片元 Fragment每个候选人身上还挂着“位置、UV、法线、颜色”等一堆信息等着后面片元着色器给它“发不发录取通知书最终像素”。你给 GPU 的是一个个三角形坐标、UV、法线等等光栅化给后面片元阶段的是一堆像素候选人片元。这篇我们就用大白话把这几点掰开讲透三角形从哪儿来的到光栅化之前已经长什么样光栅化具体在干啥怎么判断哪些像素应该成为“候选片元”片元Fragment到底是什么和真正屏幕上的像素有啥区别GPU 是怎么给每个片元“插值”出 UV、法线、颜色等的屏幕像素为什么是离散的而三角形是连续的这个鸿沟怎么弥合在 Unity / 常见渲染 API 里这个阶段你虽然看不见但怎么“感知到”它的存在你可以把这篇当作“一个三角形是怎么被 GPU 拆成一堆待面试的像素候选人片元的故事”。一、先回顾一下三角形是怎么被送到光栅化这一步的我们简单把前面几步过一遍保证脑子里有条线CPU 阶段你提供顶点数据位置、法线、UV、颜色、索引……调用 DrawXXX 告诉 GPU“画这堆三角形”。顶点着色器Vertex Shader对每个顶点独立计算模型空间 → 世界空间 → 视空间 → 裁剪空间输出gl_Position/SV_Positionclip space 坐标顺便把需要插值的属性打包传下去比如vUV、vNormal等。图元装配 裁剪根据索引把顶点拼成一个个三角形裁剪掉完全在视野外的对穿过裁剪体的三角形在边界切一刀重新生成更“小而合规”的三角形。然后就轮到今天的主角——光栅化Rasterization把这些“在屏幕上合法可见的三角形”变成一堆“覆盖哪些像素”的候选点每个候选点就是一个“片元Fragment”。后面还有片元着色器Fragment/Pixel Shader对每个片元算颜色、光照、纹理采样、阴影。各种测试 混合深度测试挡在后面的片元被丢弃透明混合多个片元颜色按规则叠加最终写入 Framebuffer变成真正显示的像素。所以你可以先记一句光栅化 三角形 → 片元片元着色 片元 → 颜色测试 混合 颜色 → 屏幕最终像素今天我们盯死中间这块三角形如何变成片元。二、屏幕是一个“像素格子”三角形是连续的怎么对得上先把本质的冲突摆出来三角形是一个连续的几何形状在数学上可以无限细分屏幕是一个离散的像素格子一格一格的点光栅化做的事就是“决定这些连续的三角形覆盖了哪些格子然后在每个格子上生成一个或多个片元候选像素”。2.1 屏幕像素 规则网格小格子假设你有一个 1920×1080 的屏幕每个屏幕“像素”的中心可以想成(cx, cy)比如cx 0.5, 1.5, 2.5 ...整个屏幕就是一块二维网格纸每个小格子有一个中心点。2.2 三角形画在这张“格子纸”上你把一个三角形的三个顶点投影到屏幕上得到三点P0(x0, y0), P1(x1, y1), P2(x2, y2)这三个点连起来就是屏幕上的一个三角形轮廓。现在问题变成在这张像素格子纸上哪些“格子中心点”落在这个三角形的内部或边界附近这些点就是光栅化要生成“片元”的位置。三、光栅化的核心任务给三角形“盖章”盖到哪些像素格子上可以用一个大白话版本形容 GPU 这一步的工作算出这个三角形在屏幕上能覆盖的“包围盒”一个矩形区域只在这个矩形范围内挨个检查格子中心这个像素中心在三角形内部吗是的话为这个像素生成一个“片元”不是就跳过这个像素。这里面有几个重要点为什么要包围盒为了不去遍历整个屏幕太浪费只在有可能被三角形覆盖的区域里检查。怎么判断“在三角形里面”用重心坐标或边函数之类的数学工具后面讲。每个片元里要记啥屏幕坐标 深度 插值得到的 UV / 法线 / 颜色等。四、先讲“片元”是什么再说怎么生成4.1 片元 ≠ 像素片元是“候选像素”一个很容易混淆的点片元Fragment不是最终屏幕上的像素它是一种“候选人”。对某个屏幕像素位置 (i, j)可能有 0 个片元没有任何三角形盖到这里可能有 1 个片元有一个三角形盖到这里也可能有多个片元多个三角形重叠到这个像素位置。举例地面三角形生成一套片元墙面三角形生成另一套片元角色三角形生成第三套片元它们可能都覆盖屏幕上的某个像素 (100,200)。那这个像素位置对应就有多个“片元候选人”地面片元、墙面片元、角色片元……后面深度测试会决定谁活下来透明混合会决定谁如何叠加。所以光栅化阶段生成的是片元片元着色器之后经过测试才变成最终“屏幕像素”。4.2 一个片元里有什么典型一个 Fragment 会带这些信息它“出生”时就得有屏幕坐标(x_screen, y_screen)或像素中心坐标深度值z或depth用于深度测试插值后的属性UV 坐标用来采样纹理法线用于做光照颜色顶点颜色插值自定义 varyings比如用于某种特效计算的参数这些值来自于顶点着色器的输出变量通过光栅化阶段的插值算出来。后面片元着色器拿到的输入就是这些插值属性、加上屏幕坐标深度等信息。五、光栅化“检查每个像素中心是否在三角形里”的思路从概念上讲光栅化可以想成算三角形的屏幕空间包围盒minX, maxX, minY, maxY遍历这个矩形内的所有像素中心 (x, y)对于每个 (x, y)判断点 (x, y) 是否落在三角形 P0,P1,P2 之内在的话生成一个片元然后算这个点在三角形里的重心坐标α, β, γ做属性插值。不过真实 GPU 不会一个一个写循环它有专门硬件和算法优化但逻辑上你可以这样理解。5.1 包围盒减少检查范围假设三角形三个顶点在屏幕上的坐标是P0 (x0, y0) P1 (x1, y1) P2 (x2, y2)那么这个三角形在屏幕上的最小包围矩形是minX floor(min(x0, x1, x2)) maxX ceil (max(x0, x1, x2)) minY floor(min(y0, y1, y2)) maxY ceil (max(y0, y1, y2))只要在 [minX, maxX] × [minY, maxY] 这个区域里遍历就行。这能显著减少工作量。5.2 判断点是否在三角形内部重心坐标 / 边函数常见方法是用重心坐标Barycentric Coordinates。给定三角形顶点 P0,P1,P2任意在三角形内部的点 P可以写成P α * P0 β * P1 γ * P2 其中α β γ 1且 α,β,γ 0如果你在上面的遍历里对某个像素中心 (x,y) 解出 (α,β,γ)如果 α,β,γ 都在 [0,1] 且和为 1 → (x,y) 在三角形内部或边上如果某个 0 或 1 → 不在三角形内部GPU 并不真的去每次“解方程”有更高效的推导和边函数算法但概念上可以这么想。一旦确定 (x,y) 在三角形内部就“恭喜这个像素位置成为一个片元候选人。”六、重点用重心坐标插值片元属性让每个片元有“自己的”UV/法线知道 (x,y) 在三角形内部后还要做一件非常重要的事把顶点着色器输出给每个顶点的那些属性UV/法线/颜色等按照 (α,β,γ)插值到这个片元上。6.1 顶点阶段输出的属性假设你的顶点着色器VS代码类似这样out VS_OUT { vec2 uv; vec3 normal; } vout; void main() { ... vout.uv a_UV; vout.normal worldNormal; gl_Position MVP * vec4(a_Position, 1.0); }对三角形的三个顶点你分别得到P0 - uv0, normal0 P1 - uv1, normal1 P2 - uv2, normal2这些数据被带到光栅化阶段。6.2 使用重心坐标插值属性对某个片元位置对应重心坐标 (α,β,γ)插值公式uv_fragment α * uv0 β * uv1 γ * uv2 normal_fragment α * normal0 β * normal1 γ * normal2 color_fragment α * color0 β * color1 γ * color2然后这一套“插值后”的属性会作为片元着色器Fragment Shader / Pixel Shader的输入in vec2 v_UV; in vec3 v_Normal; void main() { vec4 albedo texture(_MainTex, v_UV); // 用 v_Normal 算光照 ... }你可以把它理解为顶点阶段给每个顶点贴了“名片”UV、法线、颜色光栅化阶段则负责“在三角形内部按比例分配名片信息”每个片元都拿到自己那一点点“混合后的名片”。这就是插值后的属性的意义。6.3 注意透视正确插值Perspective Correct Interpolation严格讲透视投影之后不能简单用上面的线性插值会有畸变。实际 GPU 会把属性按attribute / w的形式插值然后再乘回来做“透视正确插值”。虽然细节有点数学味但你只需要记住GPU 自己会保证插值结果看起来“直的就是直的、纹理不会拉扯歪”这背后是 “重心坐标 透视修正” 在工作。七、片元在“出生现场”就知道自己的深度用来做深度测试除了 UV / 法线等属性片元还有一个非常重要的属性深度值 depth。7.1 深度怎么来的三角形三个顶点在 NDC 或 Clip Space 下都有 z / w 信息。这些 z或经过转换后的 depth同样可以用重心坐标插值到片元上depth_fragment α * depth0 β * depth1 γ * depth2再经过某些非线性映射最后写入深度缓冲Z-Buffer。7.2 这个深度拿来做啥后面的深度测试Depth Test会做对每个片元和当前深度缓冲里记录的“该像素位置的最前方深度”比较如果这个片元的 depth 更前更小或者更大视 API 约定则通过测试有机会成为最终像素否则丢弃被挡在后面。所以可以说光栅化阶段给每个片元一张“深度身份证”深度测试就是对比身份证上的“谁离摄像机更近”。八、简单对比一下光栅化 vs 光线追踪帮你更清楚地理解“像素候选”虽然你这次只问光栅化但稍微和光线追踪对比一下会更清楚“片元候选”的概念。8.1 光栅化的思路三角形主动去“盖”像素光栅化是从三角形出发看它覆盖哪一些像素格子然后在这些像素格子生成片元候选。可以打个比方三角形像盖章的印章屏幕是纸张上的格子光栅化就是“这块印章盖下去哪些格子被盖到了每个盖到的格子就是一个候选片元。”8.2 光线追踪的思路像素主动发射射线问“我看见谁”光线追踪则是反过来从每个像素发射一条多条射线问“我这条射线先撞到哪个三角形”撞到谁就用那块三角形的信息去算这个像素的颜色。所以光线追踪里没有“片元候选”的说法而是“每个像素有一条主射线 若干子射线”。两者的共同点最终都是要决定每个屏幕像素的颜色只是光栅化是三角形主动投影到像素上。光线追踪是像素射线主动找三角形。在光栅化中因为多个三角形能盖到同一个像素格子所以才有“一个像素位置存在多个片元候选”的概念。九、在 Unity / Shader 开发中你在哪里“遇见”光栅化和片元虽然光栅化是 GPU 固定功能你在 C# 里看不见它的源码但你可以从几个方面间接感到它的存在。9.1 顶点着色器输出的插值变量在片元着色器中出现例如 Unity 的一个简单 Shaderstruct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; }; v2f vert (appdata v) { v2f o; o.pos UnityObjectToClipPos(v.vertex); // 顶点位置变换交给光栅化 o.uv v.uv; // 顶点 uv return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col tex2D(_MainTex, i.uv); // 这里的 i.uv 是插值后的属性 return col; }这里发生了顶点阶段输出o.uv给每个顶点光栅化阶段对三角形内部的每个片元自动插值一个i.uv片元阶段你只管用i.uv去采样纹理它已经是“片元专属的”了。你看不见插值过程但它就发生在光栅化阶段。9.2 Clip Space / NDC / viewport transform → 片元坐标UnityObjectToClipPos得到的是 clip space 位置SV_POSITION。之后 GPU 会自动做透视除法ndc clip / w映射到 viewport屏幕坐标确定片元落在哪个像素格子上在那些像素格子上生成片元。你在 Shader 里能拿到的SV_POSITION在 Fragment 输入里其实已经是屏幕空间位置了。例如在一些 HLSL 中你可以做float2 screenPos i.pos.xy / i.pos.w;或者 Unity 提供一些宏帮你拿屏幕坐标这些都是在光栅化之后的结果。十、再用一段“动画感”的叙述把光栅化和片元的关系串起来你可以在脑海里想象一个三角形的旅程顶点阶段结束时三个顶点的位置都变换到了屏幕空间的某个区域每个顶点挂着自己的 UV、法线、颜色。图元装配之后“你们三个是一组组成一个三角形。”光栅化开始GPU 算出这个三角形在屏幕上的包围盒确定大概从像素 (x_min, y_min) 到 (x_max, y_max) 这块区域需要关注。对这个矩形里的每个像素中心点GPU像在心里自言自语“这个点在三角形内部吗”如果不在跳过如果在→ 计算这个点在三角形内部的重心坐标 (α,β,γ)→ 用它去插值UV αuv0 βuv1 γ*uv2normal αnormal0 βnormal1 γ*normal2color …depth 同理插值→ 生成一个片元Fragment片元 {屏幕位置, 深度, UV, 法线, 颜色, ……}片元着色器被调用对每个片元运行一次你在 Shader 里根据这些插值属性来算颜色、光照、纹理。深度测试 混合如果这个片元被判定挡在后面丢掉如果通过测试与当前 Framebuffer 上该像素的颜色进行混合考虑透明度等更新颜色和深度。最终每个屏幕像素位置可能经历了多个片元候选人的“竞争”最后只有一个或叠加结果成为最终显示的像素颜色。所以光栅化这一步就是给每个三角形找到它“打到屏幕上的那些像素格子”并且在每个格子上创造一个“待涂色的小方块片元”小方块身上的信息是从三角形三个顶点“均匀拉扯”过来的。十一、一点点更深入、但仍然大白话的补充边规则与填充约定有一个细节常常容易被忽略但很重要三角形在像素格子上“边界怎么算”比如两个相邻三角形共享同一条边如果处理不当中间那条边附近的像素有可能被两个三角形都画一遍导致重叠缝隙、锯齿或者两边都不画那一列像素出现“裂缝”。为了解决这个GPU 在光栅化时会采用一套统一的边规则Top-Left Rule 等简单来说对于在边上的像素规定“归哪边三角形管”另一边就不算。这保证两个三角形无缝拼接边界像素只画一次、不漏不重。你不需要死记规则只要知道光栅化阶段对“边界上的像素”有细致约定这是为了让模型渲染不出现裂缝、不出现边界重叠。十二、最后把“光栅化 片元候选”的关键点凝练成一张心智图输入已经经过顶点变换、裁剪好的三角形每个顶点有位置 各种属性UV、法线、颜色、自定义数据。光栅化要做的事在屏幕的像素格子中找到被三角形覆盖到的像素中心对每个被覆盖的像素位置生成一个“片元候选人”Fragment { x, y // 屏幕位置 depth // 深度 interpolated UV / normal / color / ... }片元 vs 像素片元是“候选人”一个三角形产生一堆片元多个三角形可在同一像素位置产生多个片元像素是“最后录取的那位或混合结果”。插值使用重心坐标α,β,γ在三角形三个顶点之间插值所有属性GPU 做透视正确插值保证纹理不歪斜。后续片元着色器用这些插值属性算颜色、光照深度/模板/混合阶段决定哪个片元真的变成屏幕上的像素。如果一定要用一句特别接地气的话总结光栅化光栅化就是“三角形发传单凡是我这块区域覆盖到的像素位置都给我站出来当片元候选人。每个候选人身上再贴上从我三个顶点插值出来的各种信息UV、法线、颜色。然后我把这些候选人交给后面的片元着色器和深度测试他们再决定谁能真正上屏幕。”理解了这个“发传单 候选人 插值”的过程你以后再看到 Fragment、光栅化、SV_POSITION、varyings 等概念就能自动在脑中串起来啊这就是那个“把三角形变成一堆待涂色小方块”的阶段。