当前位置: 首页 > news >正文

自由学习记录(76)

那网络游戏中呢,比如王者荣耀的摇杆移动,如果GPU就是渲染的太慢了,1秒只渲染了一帧呢?cpu还可以做到完整的接受玩家的移动输入吗

是的,即使 GPU 卡得要死,CPU 仍然可以正常接收并处理玩家输入,只是最终“反馈显示”会延迟甚至卡住

OS层:Android/iOS 的 input 系统
   ↓
Unity / 自研引擎 input 系统(Update 阶段轮询)
   ↓
逻辑处理:移动角色、发起技能
这些都发生在 CPU 主线程 → 脚本 Update → 不需要 GPU 参与

所以哪怕 GPU 完全卡住了、没画出来、只有 1FPS,输入本身是能被接收到的。

王者荣耀等网络游戏还加入了输入预测与网络补偿

尤其是竞技游戏,为了保证响应性和“可玩性”,引擎中会做:

  • 客户端输入预测:即使服务器没回,你的角色也先在本地“动起来”

  • 逻辑帧驱动 vs 渲染帧解耦:服务器按固定逻辑帧推进,不依赖客户端帧率

  • 输入缓冲机制:即便你掉帧,系统也会采集并缓存你的多个输入点

所以你摇杆一滑,即使你画面卡着不动,后台仍在接收、处理、同步、移动角色逻辑位置,只是你没看到而已。

tangent.w切线空间正交性所必需的信息,它的值 = 副切线方向的符号(+1 或 -1)绝对不能省略,也不能由 shader 自己“推出来”。

Unity 不封装它,是因为:

只有模型导入时能计算出这个符号信息(w 分量),shader 运行时无法推导出来。

也就是说,这个w决定的是点乘这个公式的产生方向,是可以颠覆原来的顺逆时针的规则的

开启点光源之后,里面的save by batching完全消失了

多个pass的shader里,在需要应用多个光照的时候,就破坏了unity的动态批处理

因为不能batching了,所以四个物体,每个物体被directional 和point light影响,产生了2x4=8个dc

static的结果

Unity 的某些版本确实在 Profiler 里显示过 VBO Total(Vertex Buffer Objects 总数)**这一指标,尤其是在 Unity 5.x ~ 2019 LTS 之间的一些编辑器版本中

Unity 2020.x 开始:

  • Unity 渐渐弱化对 OpenGL 概念模型的暴露

  • Universal Render Pipeline (URP)SRP 更加抽象化了底层渲染结构

  • 加上现代 GPU 的 buffer 管理越来越统一(多个 Mesh 可共享 VBO 区块)

  • Unity Profiler 的 Rendering 模块逐步统一为更通用的指标(如 batches、vertices、GPU memory)

因此,VBO Total 被移除或隐藏,你现在用的是 2020 或以上版本,就基本看不到它了。

GPU粒子的实现原理和ComputeShader GPU Instance 的基础应用。花最少的时间,了解粒子系统的核心奥义_哔哩哔哩_bilibili

看爽了,Compute Shader 基础知识点:

_particlesData = new Particle[_particlePoolSize];

  • 你需要先在 C# 端用结构体 Particle 创建粒子池数据

  • 每个 Particle 是你自定义的结构体,必须能和 HLSL 里的结构一一对齐(字段顺序、对齐方式要匹配)。

ResetParticle(ref particle);
_particlesData[i] = particle;
把每个粒子结构体初始化。通常会设置位置、速度、生命周期等。

_particlesBuffer = new ComputeBuffer(count, stride);
count:粒子数量。

stride:单个粒子结构体的字节大小,用 Marshal.SizeOf<Particle>() 来获取。

这是把结构体数组包装成 GPU 可访问的 buffer,作为 Compute Shader 的输入或输出

_particlesBuffer.SetData(_particlesData);
这一步是关键:把 CPU 上的数组数据传入 GPU 的 ComputeBuffer

后面你在 compute shader 里就可以通过 StructuredBuffer<Particle>RWStructuredBuffer<Particle> 来读写它了。

  1. 如果你要从 compute shader 回传结果回 CPU,你该怎么做?

  2. ComputeBuffer 用完后需要做什么释放操作?

所以说最重要的,目前是知道自己可以实现什么,这个过程再继续深入,,如果没有想做的结果出来,一切都白谈

所以视频重要的点是告诉我这个方向可以去实现,而不是一直躲在基础知识后面反复,,,,

自己要有出效果的思考重心,如果可以作为技术效果,那才纳入知识区域的一种高度分明

看更多的案例,,

Aras' website

冯说的固定管线相关的人

xxx

开启和关闭透明度测试,和透明度混合

首先处理的对象是当前的这个pass,对于当前的颜色缓冲区里的颜色的处理方式

一个是通过pass内判断alpha值,然后决定是否丢弃当前的这个像素

blend则是选择与颜色缓冲里的值的处理方式,少了灵感,

但至少来说,核心的pass可以写成一个,一个shader里执行一个pass,要么就是2个,另一个额外做辅助的效果

所以pass数量是可以估计,pass是有核心的,是可以间隔shader之间的分析的

顶点的位置是可以修改的,即使是surface shader里面,顶点shader 的vertex函数也是存在的

不在定点上移动,一般来说是不合适的

还有finalcolor的修改函数,在单个shader之中

那整体来说是单个pass的,也就是说surface shader的使用范围就是基本的现实物体,如果要自定义风格的去做效果出来,则很不方便

默认情况下,一个方向光

带顶点动画的shadowpass要自己修改

surface shader里的关键词就是所有的想法去看一遍

xxxx

想象中也不会太难,这倒是很简单就做掉了

应该想想别的要实现的东西了,,交互水,交互雪地,交互草地,交互火,粒子效果,鱼群的效果

真是有够多的,,

Unity常用的粒子的Shader的解读_哔哩哔哩_bilibili

官方教程实现

Logo消融特效

https://connect.unity.com/p/shi-yong-li-zi-shi-xian-logoxiao-rong-xiao-guo

倒放粒子特效

https://connect.unity.com/p/dao-fang-li-zi-xi-tong

【Unity Shader】3D模型的粒子消散_哔哩哔哩_bilibili

溶解的过程中加上粒子的dissolve 和额外的一个粒子组件,凑在一起达成效果,但这是拼凑出来的

赛尔达里面的传送也是这样做的,也不会完全感觉到异常的

源码:https://gitee.com/luoxiaoc/tutorial-source-code/raw/master/Disintegration.unitypackage

guan方最权威参考:Microsoft Learning

Geometry-Shader Object(几何着色器函数)

涵盖了如下内容:

  • [maxvertexcount(n)] 声明了几何着色器 最多输出 n 顶点,编译器据此分配资源和验证输出数量;

  • 函数签名格式:

Flat and Wireframe Shading

To make the triangles appear as flat as they really are,

we have to use the surface normals of the actual triangles.

 It will give meshes a faceted appearance, known as flat shading. 

还是太难看了,语义不同,

如何声明输出三角形流以及两大内置方法:

  • Append(...):发送一个顶点输出;

  • RestartStrip():结束当前三角形条,开始一条新的;

  • 类型示例:inout TriangleStream<g2f>,表示输出类型为 struct g2f(vs→gs→fs 过渡);

  • TriangleStream 只用于几何着色器(SM4.0+),Unity CG/ShaderLab 中使用普遍。

Stream-Output Object - Win32 apps | Microsoft Learn

Unity 上实战解析:Catlike Coding(Advanced Rendering 系列)

该系列从 Unity ShaderLab + CG 的角度,详细展示了完整 geometry shader 编写流程:

  • 第一步说明如何通过 #pragma geometry 启用 GS;

  • 接着演示为何 必须写 [maxvertexcount(3)] 才能通过编译;

  • 然后用 triangle MyInType v2g[3] 来接收三角形;

  • 接着用 inout TriangleStream<MyInType> 输出一组三角形顶点;

  • 最后演示 .Append(i[0]).Append(i[2]) 发送顶点。
    这些解析位于其教程中 GS 代码部分,清楚展示每行含义:

启用 Geometry Shader 阶段

为了使用几何着色器(Geometry Shader),Shader必须设置为至少 Shader Model 4.0。在 Unity 中,你可以这样显式指定:

#pragma target 4.0

#pragma geometry MyGeometryProgram

这样做会告诉编译器,将 MyGeometryProgram 纳入到渲染管线中作为 Geometry Shader 使用。

Geometry Shader 每次被调用时需要声明 最多能输出多少顶点。例如:

[maxvertexcount(3)]
void MyGeometryProgram(...) { … }
这意味着:当前命令最多产生 3 个顶点(即一个三角形)。Unity 使用这个注解来验证输出是否合法,并为着色器分配相应的输出缓冲区。

[maxvertexcount(7)] 是告诉编译器:这个几何着色器(Geometry Shader)在每次被调用时,最多能输出 7 个顶点。这个信息 HLSL 在编译时用来为输出流(如 TriangleStream<…>)预先分配缓冲区,运行时也会检查 .Append(...) 的数量不能超过这个限制。”

Geometry-Shader Object - Win32 apps | Microsoft Learn

  • 资源预分配:GPU / 驱动程序必须在调用 GS 之前知道最大潜在输出顶点数,以分配临时缓冲。

  • 运行时检查:如果你 .Append(...) 的次数超过了这个数,编译器/驱动会报错(尤其在 Direct3D 环境下)。

  • 即使实际输出比上限少,也不会出错 —— 只是不能超过。

假设你写的几何着色器是这样的:

  • 输入一个三角形(3 顶点)

  • 然后扩展为一个 Quad(用 2 个三角形 = 6 顶点)

  • 最后还要再保留原三角形中的一个顶点(或其他顶点)

总顶点数 = 6(Quad) + 1 = 7

这时就必须设置:

[maxvertexcount(7)]
void geom(triangle v2g IN[3], inout TriangleStream<g2f> triStream)
{ ... }

这样你的 .Append(...) 调用不能写超过 7 次。

这就是“声明最大输出顶点数”真正的含义。如果你觉得自己 .Append(...) 顺序不稳定,也可以先估算最大可能值写大一点(比如 12、24),但不建议超过 1024(DirectX 的资源限制)

There is a resource limit in directX that limits the number of scalar components that can be emitted by a geometry shader instance to 1024

maxvertexcount(N) 是一个最大上限声明;但你绝对不能输出超过 N 顶点,否则程序会拒绝执行。

同一次 Geometry Shader 调用,隐藏存在一个由编译器/驱动预分配的“输出缓存”;

编译期,它需要知道“最多可能输出多少顶点”;

.Append() 超限,就容易破坏缓存机制,驱动必须检测并报错

只真的输出少量顶点,低于上限——这是允许也是正常的流程

dcl_maxOutputVertexCount (sm4 - asm) - Win32 apps | Microsoft Learn

[maxvertexcount(7)]
void geom(triangle v2g IN[3], inout TriangleStream<g2f> triStream)
{// 假设你只拼 2 个三角形共 6 顶点for (int i = 0; i < 6; i++)triStream.Append(...);triStream.RestartStrip();
}
这段代码中 .Append(...) 总数是 6(少于 max=7)
→ ✅ 编译与运行都没问题。

for (int i = 0; i < 8; i++)
    triStream.Append(...);
因为 8 > 7,编译器或 GPU 将报错(运行要么崩要么报 “Exceeded max vertex count”)。

  • 实际输出顶点数量 × 每顶点的标量属性数量(如位置 + UV + 法线…)有绝对上限

  • 一旦 .Append() 超出限定数量,编译器或 GPU 驱动就会报错或忽略输出;

  • 若真的需要吐出成百上千个三角形,你必须靠 Shader Model 5 的 GS Instancing 多次调⽤。

https://stackoverflow.com/questions/24082570/geometry-shader-maxvertexcount-cannot-be-known

GPU 必须保持输出三角形 输入-输出的执行顺序一致,以满足管线的帧同步和驱动的排序需求。

设定的上限远远高于实际使用(比如设成 256 但你只输出 4),GPU 为它留了空白,但不会高效利用;反过来,设定太小(甚至等于实际),未能应对某些 edge case,也会报错。

 void geom(triangle v2g IN[3], inout TriangleStream<g2f> triStream){float2 avgUV = (IN[0].uv + IN[1].uv + IN[2].uv) / 3;float3 avgPos = (IN[0].objPos + IN[1].objPos + IN[2].objPos) / 3;float3 avgNormal = (IN[0].normal + IN[1].normal + IN[2].normal) / 3;//随噪声溶解效果的消散float dissolve_value = tex2Dlod(_DissolveTexture, float4(avgUV, 0, 0)).r;float t = clamp(_Weight * 2 - dissolve_value, 0, 1);//产生更多的随机性float2 flowUV = TRANSFORM_TEX(mul(unity_ObjectToWorld, avgPos).xz, _FlowMap);float4 flowVector = remapFlowTexture(tex2Dlod(_FlowMap, float4(flowUV, 0, 0)));float3 pseudoRandomPos = (avgPos) + _Direction;pseudoRandomPos += (flowVector.xyz * _Exapnd);//控制位置和大小随消散效果变化float3 p = lerp(avgPos, pseudoRandomPos, t);float radius = lerp(_ParticleRadius, 0, t);//构建一个广告牌QUADif (t > 0) //消散的部分{//让消散的粒子始终朝向摄像机,使用摄像机空间的坐标轴float3 right = UNITY_MATRIX_IT_MV[0].xyz;float3 up = UNITY_MATRIX_IT_MV[1].xyz;//得到QUAD四个点的位置(沿着摄像机空间的坐标轴偏移)float4 v[4];v[0] = float4(p + radius * right - radius * up, 1.0f);v[1] = float4(p + radius * right + radius * up, 1.0f);v[2] = float4(p - radius * right - radius * up, 1.0f);v[3] = float4(p - radius * right + radius * up, 1.0f);//构建QUAD的顶点:位置和UV一一对应g2f vert;vert.pos = UnityObjectToClipPos(v[0]);vert.uv = float2(1.0f, 0.0f);vert.normal = UnityObjectToWorldNormal(avgNormal);vert.worldPos = mul(unity_ObjectToWorld, v[0]);triStream.Append(vert);vert.pos = UnityObjectToClipPos(v[1]);vert.uv = float2(1.0f, 1.0f);vert.normal = UnityObjectToWorldNormal(avgNormal);vert.worldPos = mul(unity_ObjectToWorld, v[1]);triStream.Append(vert);vert.pos = UnityObjectToClipPos(v[2]);vert.uv = float2(0.0f, 0.0f);vert.normal = UnityObjectToWorldNormal(avgNormal);vert.worldPos = mul(unity_ObjectToWorld, v[2]);triStream.Append(vert);vert.pos = UnityObjectToClipPos(v[3]);vert.uv = float2(0.0f, 1.0f);vert.normal = UnityObjectToWorldNormal(avgNormal);vert.worldPos = mul(unity_ObjectToWorld, v[3]);triStream.Append(vert);}else //原本的顶点,并把世界坐标的w设置为-1作为区分{for (int j = 0; j < 3; j++){g2f o;o.pos = UnityObjectToClipPos(IN[j].objPos);o.uv = TRANSFORM_TEX(IN[j].uv, _MainTex);o.normal = UnityObjectToWorldNormal(IN[j].normal);o.worldPos = float4(mul(unity_ObjectToWorld, IN[j].objPos).xyz, -1);triStream.Append(o);}}}

都是 Microsoft Direct3D(或包含 Cg/HLSL)规范里的标准,与你是否在 Unity 无关,只要你的编译器支持 SM4.0+ 都能运行。

核心特性包括:

语法或关键字说明
[maxvertexcount(7)]声明每次 Geometry Shader 最多输出 7 个顶点,用于输出缓冲区预分配和运行时检查 Unity Documentation+7Unity Documentation+7Unity Documentation+7Unity Documentation+1Unity Documentation+1
void geom(triangle v2g IN[3], inout TriangleStream<g2f> triStream)标准 HLSL 的几何着色器签名:接收三角形输入数组,输出顶点流 Microsoft LearnMicrosoft Learn
.Append(...).RestartStrip()Geometry Shader 特有函数,控制顶点流生成和 Primitive Strip 划分 Microsoft LearnMicrosoft Learn
triangleTriangleStream<T>、数组 [3]规范定义的输入原语(Primitive Type)和输出流机制 Microsoft LearnWikipedia

这些是 Unity 为了简化 Shader 写法,封装在 UnityCG.cgincCore.hlsl 等内置 include 文件里的宏名、矩阵名、统一变量,Unity 以外就不存在

  • UNITY_MATRIX_IT_MVunity_ObjectToWorldUNITY_MATRIX_MVP 等由 Unity 自动传入的内置 MVP、Model、IT‑MV 矩阵变量;

  • UnityObjectToClipPos(...)UnityObjectToWorldNormal(...) 等 Unity 内置函数宏,经编译器会被替换成矩阵 * 顶点 向量等操作 Unity Documentation+8Unity Documentation+8Unity Documentation+8;

  • TRANSFORM_TEX(...),自动处理 tiling+offset 的 Unity 纹理坐标宏,同样只存在 Unity 内置宏库中 Unity Documentation+4Unity Documentation+4Unity Documentation+4;

  • Unity 特有 ShaderLab property 变量如 _Exapnd_FlowMap_Weight_ParticleRadius 等,在 Unity 材质 inspector 中定义的属性;

  • tex2Dlod(...) 在 Unity 中是合法的(CG 语法兼容),但它作为 Unity Shader 的内嵌函数,其工作效果需要结合 Unity 绑定的 Texture 在 SRP/Builtin RP 管线中生效。

换言之,你如果把完整 shader 源码复制到普通的 D3D11 工程中(不带 Unity 头文件),所有 Unity 宏不可识别,需要手动替换为:

  • 自己的矩阵 uniform(如 uniform float4x4 UNITY_MATRIX_MVP; 替换为你自己传入的 modelViewProj 矩阵);

  • 纹理坐标处理和 tex2Dlod() 调用换为 HLSL 的 Texture2D.SampleLevel(...),并使用 Texture2D _DissolveTexture; SamplerState _Sampler;

  • 世界空间和法线变换,用 mul(...) 手工写等价于 UnityObjectToWorldNormal。

tex2Dlod(u, float4(tex, 0, 0))
HLSL 标准函数,用于按绝对 LOD(mipmap)纹理采样。常用于 Vertex 或 Geometry 阶段中的纹理获取,因为普通 tex2D 在这些阶段常无法使用。

Shader 中确实只有结构体没有类(class)

triangle

  • 不是结构体、类,也不是你自己写的类型

  • 它是 HLSL 语法中的一个关键字 (PrimitiveType),用来指定 几何着色器 (Geometry Shader) 接收的输入顶点原语类型。

  • triangle IN[3] 表示该几何着色器每次被调用会接收到一个三角形——也就是3 个顶点数据组成的数组。

  • 这个关键字属于 HLSL/Direct3D 的 Shader Model 4 规范,是编译器语义解析的一部分,不允许用作变量名或结构体名。Microsoft Learn+1Microsoft Learn+1Microsoft Learn+5Microsoft Learn+5Microsoft Learn+5

TriangleStream<g2f>

  • 也不是类或结构体,而是一种 HLSL 提供的预定义“输出流类型”(Stream‑Output Object)。

  • 使用 模板语法TriangleStream<g2f> 指的是一种 按三角形结构输出顶点数据的输出管道g2f 是开发者自定义的结构体,定义了每个输出顶点的属性(如位置、UV、世界法线等)。

  • 在几何着色器中,通过调用 triStream.Append(...) 向该流中逐个添加顶点;通过 triStream.RestartStrip() 重启条带(Strip)以控制三角形拓扑结构。Microsoft Learn+4Microsoft Learn+4Microsoft Learn+4

  • HLSL 共定义了三种此类流对象:

    • PointStream<T> — 用于输出点列表;

    • LineStream<T> — 用于输出线条条带;

    • TriangleStream<T> — 用于输出三角形条带。Microsoft Learn+5Microsoft Learn+5Microsoft Learn+5

名称类型来源 & 作用
triangle关键字HLSL 原生关键字,告诉编译器当前输入原语是三角形
inout TriangleStream<g2f>模板类型HLSL 内置输出流类型,用于从 GS 向后续阶段输出顶点
  • Vertex Shader 输出一系列顶点数据,带有 v2g 结构体标签;

  • Geometry Shader 接受每组三个 v2g 为输入(由 triangle 标识):

void geom(triangle v2g IN[3], inout TriangleStream<g2f> triStream)

geom() 内部,你可以:

triStream.Append(v0);
triStream.Append(v1);
triStream.Append(v2);
triStream.RestartStrip();
来构建条带拓扑,每组三角形都必须不超过 [maxvertexcount(...)] 的预设上限。

  • triangle ➝ 是写在函数定义里的 固定语法关键字,不是 struct/union 等“数据类型”;

  • TriangleStream<g2f> ➝ 是 HLSL 所定义的 输出流类型,相当于一个“泛型容器”,不是结构体,而是一种管线控制层面的类型声明;

这正是几何着色器语义中必不可少的要素:定义输入原语类型 + 声明输出流类型与输出数据结构

支持的 PrimitiveType 类型与对应输入数组长度如下:

PrimitiveType描述数组长度 NumElements
point点原语[1]
line线段/线带[2]
triangle三角形/三角带[3]
lineadj附属线带[4]
triangleadj附属三角形(拓扑)[6]

TriangleStream<g2f>,,,,TriangleStream<T>定义 Geometry Shader 的 输出流对象(Stream-Output Object)

构造好的顶点“流式推送”到 GPU 渲染管线中的下一阶段(通常是 Rasterizer → Fragment Shader)

不是 struct/class:它是语义层面上的“输出流类型”,类似 C# 的泛型容器,但无法用来封装逻辑。

inout TriangleStream<g2f> triStream 表明:输出由你定义的结构体 g2f(通常含 pos, normal, uv 等)构成顶点格式,每次 .Append(vert) 都将一个顶点加入输出流中。

struct v2g { … };    // 从 Vertex 到 Geometry 的输入格式
struct g2f { … };    // 从 Geometry 到 Fragment 的输出格式

[maxvertexcount(N)]
void geom(
  triangle v2g IN[3],                            // 输入为三角形(3个顶点)
  inout TriangleStream<g2f> triStream            // 输出三角形流
)
{
  ...
  triStream.Append( g2f_vertex );
  ...
  triStream.RestartStrip();
}

  • triangle 控制输入原语;

  • IN[3] 指明数组长度(primitive size);

  • TriangleStream<g2f> 是对输出顶点组类型的封装;

  • .Append(...) 添加输出顶点;

  • .RestartStrip() 标志三角形条带结束,准备下一个条带。

  • 整个函数没有返回值;输出由 triStream 驱动
    都是 Geometry Shader 特有的设计语义。

“为什么 geom(...) 的输入是三角形(triangle IN[3])?”

这个问题的核心,在于 Geometry Shader(GS)阶段操作的是“整个原语(primitive)”,而非单个顶点。其中 triangle 在 HLSL 中是一个关键字,用来明确 GS 接收的原语类型是三角形(3 个顶点为一组)。

GPU 渲染中,Vertex Shader 完成顶点变换后,会进入 Primitive Assembly 阶段

将顶点组装成基本图元Primitive

常见的图元就有:

  • Points(点)

  • Lines(线段)

  • Triangles(三角形)---三个顶点总能组成平面

Geometry Shader 位于顶点与光栅化之间,它接受 一个完整的原语作为输入

比如,若顶点着色器输出的是一个三角形列表(TriangleList),那么 GS 每次调用就会得到 整组三角形顶点(共3个)

triangle v2g IN[3] 也就是说如果是line v2g IN里就是2,因为一个单独的primitive就是两个顶点组成的?是的,你理解得完全正确——

当输入 primitive 类型改为 line 时,几何着色器(Geometry Shader)中的输入数组长度也会从 3 改为 2。也就是说,如果你希望以线段为输入,就得写:

void geom(line v2g IN[2], inout LineStream<g2f> stream)

IN[2] 表示:每次调用 geom() 会接收到一个线段原语,由两个顶点组成;

LineStream<g2f> 则是输出线流类型,对应 2D “线”的输出拓扑。

https://www.youtube.com/watch?v=CaPYw5ts2Yg

可交互的樱花特效,樱花特效随机移动,然后角色的移动会扰动樱花花瓣的移动

geom()(Geometry Shader)被调用的次数确实与 vert(Vertex Shader)和 frag(Fragment Shader)不同,其调用频率取决于 所处理的“原语 (primitive)”数量,而不是顶点或像素数量。

Shader 阶段调用频率依据示例说明
Vertex Shader (vert)每个顶点一次(appdata 输入)若你渲染 6 个顶点,VS 会被调用 6 次
Geometry Shader (geom)每个原语一次(由拓扑类型决定)渲染三角形:每 3 个顶点构成一个三角形 → GS 调用一次
Fragment Shader (frag)每个像素/样本一次(裁切 & 采样后)取决于图元覆盖的屏幕像素数,会调用大量次
  • 只要你画的网格是 TriangleList(Unity 默认),那么每三个顶点组成一个三角形原语;

  • 如果你渲染了 1000 个三角形,geom() 就会被调用 1000 次;

  • frag() 的调用次数则更高,取决于每个三角形覆盖了多少像素;超过 100 万像素的画面很常见。

渲染流程中,VS → (Tessellation)→ GS → Rasterizer → FS 是静态固定顺序 —— GS 必须等 VS 输出完毕,FS 必须等 GS 输出后才能执行

你的 HLSL 代码中,每个 triStream.Append(...) 输出顶点的顺序,必须与你的 .RestartStrip() 分隔形式对应原语的想法一致。最终 GPU 保证按你在同一次 GS 调用里写入数据的顺序发往 rasterizer,而不会被其他 GS 调用插入。输出顺序封装在 SMT 调度层面。不过,不同 GS 调用之间的执行顺序不保证稳序;系统可能提前执行调用 B 的附加代码响应任务调度。

  • 视图空间进行 Tile-Based、面片缓存合并和 Early Z 测试

  • GPU 在进行 FS 时,会对同一像素可能来自多个三角形的 fragment 运行深度/stencil 测试,才能判断哪一个最终写入。

  • 开启 Depth-Pre-Pass / MSAA / Conservative Rasterization 等设置会影响 FS 的调用频率与时机。

渲染排序(如“画深度近的先”或启用“Multi-Layer”)会调整 FS 处理顺序,但仍受 GPU 决定。

  • Dissolve 驱动:每个三角形片段采样噪声贴图,决定该片段是否“该消散”;

  • 位置偏移:当进入溶解阶段(t > 0),粒子逐渐从 avgPos 朝着 pseudoRandomPos 位移;

  • 尺寸渐变:半径按 t 缩小,对粒子视觉上产生“消散减淡”的感觉;

  • Flow + Direction 作用是避免所有粒子都向同一方向移动而造成单调感;

  • 最终在 GS 中用 billboard quad 来输出粒子时,就把 pradius 当顶点位移。这样效果更自然、有风动感,也更有层次。

Implementing a Dissolve Effect with Shaders and Particles in Three.js | Codrops

对于 triangle 原语,geometry shader 每次接收整组三角形,不会拆分位置或重排序。
每个调用的 IN[0]~IN[2] 是 draw call 中连续的三角形顶点,最后不满 3 顶点的部分会被忽略 Stack Overflow

假设你的 mesh 有 6 个顶点索引(TriList):[0, 1, 2, 3, 4, 5]

  • VS 会被调用 6 次,分别处理索引 0~5 的顶点;

  • Geometry Shader 被调用 2 次:

    • 第一次得到 IN = { v2g(vert0), v2g(vert1), v2g(vert2) }

    • 第二次得到 IN = { v2g(vert3), v2g(vert4), v2g(vert5) }

  • 每个 geom() 你拿这三者算 avgPos / avgUV / avgNorm,生成 billboard quad 或 dissolve 粒子;

  • Fragment Shader 在实际光栅化的每个像素被调用无数次,由三角形覆盖区域和采样率决定(与 GS 调用次数完全不同);

底层 GPU 调度可能是大批量 dispatch VS / GS / FS,但每个 geom() invocation 内部的 IN[0]~IN[2] 从属关系与顺序则是精确和稳定的

Geo 阶段的定位 & 负责什么 🎯

  • 在标准 GPU 渲染流水线中:
    Vertex Shader → 入栈 Primitive → Rasterizer → Fragment Shader
    如果加上 Geometry Shader(GS),流程变为:
    Vertex → GS → Rasterizer → Fragment
    GS 接收 整组 Primitive(例如:3 个顶点构成一个三角形),它可以决定:摒弃它、原样传递、或用更多/更少的 Primitive 替换它 —— 也就是所谓的“几何扩展”能力 ogldev.org。

  • 声明 [maxvertexcount(7)] triangle … IN[3]
    表明这个 GS 将“每次”接收一个 triangle primitive(即 3 个 v2g 顶点),最多可以调用 triStream.Append(...) 输出 7 次,但输出 topology 固定为 TriangleStream<g2f> —— 意味着后续这些被 Append 的顶点会被按一个三角形条带(triangle strip)送入片段阶段(也可用 RestartStrip() 重启条带)Microsoft LearnMicrosoft Learn。

float t = clamp(_Weight * 2 - dissolve_value, 0, 1);
if (t > 0) {
    // 构建摄像机对齐的广告牌 quad(由 4 个顶点组成
    // triStream.Append 了四次 → 代表一个 quad,后续(传给frag时)被当作两三个三角形碎片渲染出来
} else {
    // 只传递原三角形的三个顶点,一样通过 triStream.Append
}

  • t > 0(物体开始 dissolve)
    你计算一个围绕 avgPos 的四边形,将四个角顶点的 pos, uv, normal, worldPos 填入 g2f,然后连续 Append() 四次。(你没显式 RestartStrip(),但默认每个 GS 调用一个条带,一般能正确输出 quad)

  • t ≤ 0
    你只是把原来传入的 3 个三角形顶点封装为 g2f,再依次 Append() 三次,并worldPos.w = -1 用作片段 shader 里的 flag,表示这部分是未 dissolve 的原始三角形

 所以说GS 就是负责“组装最终三角形”的阶段。

Fragment Shader 会被这些最终三角形中的每个 fragment 调用一次。它不关心这些顶点是 VS 直接来,还是 GS 重写后来的,只关心最终 rasterized geometry。

在没有 GS 的情况下,是 VS 输出的那三角形被 rasterize;但一旦有 GS,真正送到 frag 的顶点来自 GS 的输出。VS 的数据仅是输入来源之一

GS 唯一的作用确实就是它可以“重新组织 Primitive 拆分/合并、决定是否输出”,这在 VS 和 FS 中都做不到

Tutorial 27 - Billboarding and the Geometry Shader

http://www.dtcms.com/a/312501.html

相关文章:

  • Python 的标准库 bisect 模块
  • 源码交易平台排行榜
  • 机器学习 决策树基本介绍
  • Mysql的MVCC是什么
  • HCIE-Datacom题库_07_设备【道题】
  • 《深入解析 Python 的 `*args` 和 `**kwargs`:从基础使用到高级应用》
  • 【数据结构】哈希表实现
  • 网关和BFF是如何演化的
  • uniapp 跨端开发
  • 基于Springboot+UniApp+Ai实现模拟面试小工具八:管理端基础功能实现
  • (论文速读)探索多模式大型语言模型的视觉缺陷
  • DeepSeek 论文夺冠,智谱开源 GLM-4.5,OpenAI 学习模式上线!| AI Weekly 7.28-8.3
  • 基于机器学习的Web应用漏洞分析与预测系统,使用django框架,三种机器学习模型
  • 深入探讨AI在测试领域的三大核心应用:自动化测试框架、智能缺陷检测和A/B测试优化,并通过代码示例、流程图和图表详细解析其实现原理和应用场景。
  • 关于Web前端安全防御之内容安全策略(CSP)
  • 知识蒸馏 - 基于KL散度的知识蒸馏 HelloWorld 示例 采用PyTorch 内置函数F.kl_div的实现方式
  • 【Linux系统】进程间通信:匿名管道
  • AI 时代的 IT 从业者:共生而非替代
  • 人声伴奏分离API:音乐智能处理的强大工具
  • Spring AI 项目实战(二十二):Spring Boot + AI +DeepSeek实现智能合同数据问答助手​(附完整源码)
  • 小白学OpenCV系列2-理解图像
  • MySQL--高可用MHA集群详解及演练
  • SelectDB数据库,新一代实时数据仓库的全面解析与应用
  • CICD--自动化部署--jinkins
  • 深度学习中的三种Embedding技术详解
  • OSPF知识点整理
  • [Oracle] 获取系统当前日期
  • ABP VNext + Quartz.NET vs Hangfire:灵活调度与任务管理
  • 35.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--数据缓存
  • Petalinux 23.2 构建过程中常见下载错误及解决方法总结