【笔记】【百人计划】图形3.3 曲面细分与几何着色器

一、背景

1.1应用

  • 曲面细分着色器
    • 海浪(曲面细分+顶点位移动画)
    • (交互)雪地(曲面细分+顶点位移)
    • 置换贴图(曲面细分+顶点位移)
  • 几何着色器
    • 几何动画(几何图元)
    • 草地等(与曲面细分着色器结合)

1.2渲染管线

  • 顶点着色器
  • 曲面细分着色器
    • 细分控制着色器Hull Shader(TCS)
    • Tessellation Primitive Generator(不可编程)
    • 细分计算着色器Domain Shader(TES)
  • 几何着色器
  • 片元着色器

1.3曲面细分着色器

1.3.1TESS的输入与输出
  • Hull Stage

    • Hull function
      • 每个patch每个顶点运行一次
      • 输入
        • Patch,可以看成是多个顶点的集合,包含每个顶点的属性,可以指定一个Patch包含的顶点数以及自己的属性
        • Index,指定hull shader输出patch中的哪一个顶点
      • 输出patch内对应顶点
    • Patch Constant Function
      • 输入Patch
      • 每个patch运行一次
      • 输出Tessellation Factor
    • 两个阶段是并行
    • 功能
      • 将图元细分(三角形、矩形等)
    • 输出
      • 细分后的顶点数据
  • Tessellation Primitive Generator

    • 为新网格的所有顶点生成重心坐标
  • Domain stage

    • 输入重心坐标、Patch
    • 为细分后曲面的所有顶点运行一次
    • 通常在顶点着色器的逻辑操作的部分,应该放在这里
  • (Geometry Stage)

1.3.2HULL shader参数
  • Tessellation Factor
    • 决定将一条边分成几部分
    • equal_Spacing
    • fractional_even_spacing
    • fractional_odd_spacing

image-20220818002411909

  • Inner Tessellation Factor
    • (与上面的参数是同一等级)将边等分后,向内延伸相交,直至内部没有交点。

image-20220818002434236

1.4几何着色器

1.4.1几何着色器的输入与输出
  • 输入为图元(三角形、矩形、边)
    • 根据图元不同,shader中会出现对应不同数量的顶点
  • 输出同样为图元(一个或多个),需要自己从顶点构建,顺序很重要,同时定义最大输出的顶点数

二、曲面细分

  • 将一个Quad细分
  • 与置换贴图结合
    • 注意使用置换贴图不在fs中,也是在domain shader中的vert函数进行的,GPU无法获取mipmap信息,需要使用tex2Dlod等来读取图片。
    • 法线也需要重新计算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#pragma hull hullProgram
#pragma domain ds

#pragma vertex tessvert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Tessellation.cginc"

...
struct VertexInput;
struct VertexOutput{
float4 vertex: SV_POSITION;
float3 normal;
float3 tangent;
float2 uv;
};

VertexOutput vert(VertexInput v)
{
VertexOutput o;
o.uv = TRANSFORM_TEX(v.uv,_MainTex);
o.vertex = UnityObjectToClipPos(v.vertex);
o.normal = v.normal;
o.tangent = v.tangent;
}//顶点着色器的函数,但这里没有拿给顶点着色器,而是用来在DomainShader进行空间转换

//只有在能够使用曲面细分着色器的平台才能使用
#ifdef UNITY_CAN_COMPIE_TESSELLATION
struct TessVertex {
float4 vertex: INTERNALTESSPOS;
float3 normal: NORMAL;
float4 tangent: TANGENT;
float2 uv: TEXCOORD0;
};
struct OutputPatchConstant {//不同的图元,该结构会有不同
float edge[3]: SV_TESSFACTOR;
float inside: SV_INSIDETESSFACTOR;
};
TessVertex tessvert(VertexInput v){
TessVertex o;
o.vertex = v.vertex;
o.normal = v.normal;
o.tangent = v.tangent;
o.uv = v.uv;
return o;
}//顶点着色器,注意这里没有进行空间转换,而是在domianshader里进行的空间转换

float _TessellationUniform;
OutputPatchConstant hsconst(InputPatch<TessVertex, 3> patch) {
OutputPatchConstant o;
o.edge[0] = _TessellationUniform;
o.edge[1] = _TessellationUniform;
o.edge[2] = _TessellationUniform;
o.inside = _TessellationUniform;
}

//定义Hull shader的函数
[UNITY_domian("tri")]//指定图元
[UNITY_partitioning("fractional_odd")]//拆分edge的方式,equal_spacing等
[UNITY_outputtopology("triangl_cw")]
[UNITY_patchconstantfunc("hsconst")]//指定patch const function。一个patch一共有三个点,但是这三个点都共用这个函数
[UNITY_outputcontrolpoints(3)]//不同图元会对应不同控制点
TessVertex hullProgram(InputPatch<TessVertex, 3> patch, uint id : SV_OutputControlPointID){
return patch[id];
}

//定义Domian shader的函数
[UNITY_domain("tri")]//同样指定图元
VertexOutput ds (OutputPatchConstant tessFactors, const OutputPatch<TessVertex, 3> patch, float3 bary : SV_DomainLocation) {
//bary重心坐标
//计算模型空间位置
VertexInput v;
v.vertex = patch[0].vertex*bary.x + patch[1].vertex*bary.y + patch[2].vertex*bary.z;
v.tangent = patch[0].tangent*bary.x + patch[1].tangent*bary.y + patch[2].tangent*bary.z;
v.normal = patch[0].normal*bary.x + patch[1].normal*bary.y + patch[2].normal*bary.z;
v.uv = patch[0].uv*bary.x + patch[1].uv*bary.y + patch[2].uv*bary.z;
VertexOutput o = vert(v);//调用原本该在顶点着色器中的转换函数。
return o;
}//这里才终于把顶点数据拿给下一阶段

#ENDIF

float4 frag...

三、几何着色器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#pragma vertex vert
#pragma fragment frag
#pragma geometry geo
...
struct VertexInput;
struct VertexOutput{
float4 vertex: SV_POSITION;
float3 normal;
float3 tangent;
};
//注意这次的顶点着色器是vert函数,上次是tessvert,同样不做任何操作
VertexOutput vert;
struct geometryOutput{
float4 pos: SV_POSITION;
float2 uv: TEXCOORD0;
}
geopetryOutput CreateGeoOutput(float3 pos, float2 uv){
geometryOutput o;
o.pos = UnityObjectToClipPos;
o.uv = uv;
return o;
}//在几何着色器中完成空间转换,如果同时使用曲面细分和几何着色器,也要放到几何着色器来进行


[maxvertexcount(3)]
void geo(triangle vertexOutput IN[3]: SV_POSITION, inout TriangleStream<geometryOutput> triStream){
float pos = IN[0].vertex;
float vNormal = IN[0].normal;
float vTangent = IN[0].tangent;
float vBinormal = cross(vNormal,vTangent)*vTangent.w;

float height = ...
float width = ...

...
geometryOutput o;
triStream.Append(CreatGeoOutput(dosomething(pos),dosomething(uv));
triStream.Append(CreatGeoOutput(dosomething(pos),dosomething(uv));
triStream.Append(CreatGeoOutput(dosomething(pos),dosomething(uv));//添加了一个新的三角形图元
}

也可以添加更多图元,如:

image-20220818013037250

image-20220818013127627

三角形的构建是triStream自动完成的。(uv是自己计算的。)

作业

使用曲面细分、几何着色器做一些有意思的Demo

1661109050656

看了一圈,基本上大家都是参考那两个教程,就不多写什么了。(做到这个效果还挺想复刻一下Clannad的片头的,说不定挺适合)

这里主要完成了基本的曲面细分-几何着色器多pass渲染带阴影和光照的草地-风的扰动。

事实上,几何着色器构建的图元会覆盖原来的三角形图元,原来的表面其实消失了。这里可以再加一个pass渲染。

并且地面pass可以不做细分来节省开销

还可以进一步处理的部分:

  • 基于距离的曲面细分
  • 基于RT或物体位置的草地交互

继续往后做这两个方向感觉就需要考虑偏向may佬说的在项目中整体方案的思路了。

暂时就放一放

参考资料

[1] https://www.bilibili.com/video/BV1XX4y1A7Ns

【技术美术百人计划】图形 3.3 曲面细分与几何着色器 大规模草渲染

[2] https://catlikecoding.com/unity/tutorials/advanced-rendering/tessellation/

[3] https://roystan.net/articles/grass-shader.html