栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 游戏开发 > 其他

曲面细分着色器学习记录

其他 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

曲面细分着色器学习记录

曲面细分着色器学习记录

前言:
今天终于走通了Catlike大佬的曲面细分着色器教程,感慨一番,自己的学习之路还很漫长呢
今天的博客内容只是简单的记录一下Catlike大佬的教程
先上个链接:Catlike的曲面细分教程

什么是曲面细分

学习过建模的朋友们都一定知道曲面细分,在Blender中也有曲面细分修改器工具,用来细分图元,帮助我们获得更细腻的模型表面

闫令琪在Games101中的Geometry章节中也降到了曲面细分的原理(Loop细分和Catmull-Clark细分,我回顾了一下感觉和今天的原理不太一样)


渲染管线中的曲面细分着色器

1. 渲染流水线

乐乐女神在《入门精要》中说明,曲面细分着色器处于几何阶段,并且可编程控制,同时这是一个可以选的shader

2. 输入与输出:
  • 输入:patch以及三角形索引,patch就是多个顶点的集合,可以指定一个patch包含几个顶点
  • 输出:细分之后的顶点
3. shader组成

在曲面细分着色器中,包含三个部分:
Hull shader
Tessellation
Domain shader
这三者中,Tessellation操作是计算机进行的,我们不可以操作,因此,我们需要配置的就是Hull shader和Domain shader
Hull shader:准备曲面细分的方法等基础数据,传递给Tessellation后进行细分操作
Dmain shader:对细分后的点进行处理,将其从重心坐标转到相应的空间(可以转到模型空间、投影空间等等)


Hull shader

Hull shader的结构与vert 或 frag shader的结构明显不同,除了一个主要的方法外,还需要在前定义5个属性,用来说明Hull应如何准备数据。
属性说明:
1.[UNITY_domain("tri")]:这个属性可以指定patch的类型,除了tri(三角形)之外,还包括quad、isoline。
2.[UNITY_outputcontrolpoints(3)]:输出的控制点的数量,不一定与输入数量相同,也可以新增控制点。
3.[UNITY_outputtopology("triangle_cw")]:输出拓扑结构。有三种:triangle_cw(顺时针)、trianglle_ccw(逆时针)、line(线段)。
4.[UNITY_partitioning("fractional_odd")]:分割模式。有三种:integer、fractional_even、fractional_odd。说白了就是integer只可以整数分割,剩余两者可以小数分割,一般都用odd,分割更自然。
5.[UNITY_patchconstantfunc("patchEdgeFunction")]:一个函数,patchEgdeFunction是教程里的函数名,这个函数需要在前面自行定义,用来说明细分中,边和图元里面的点内的细分数量。
6.[maxtessfactor(64.0f)]:上面的代码里没写,但是有一位知乎大佬说明了,在这里补充以下。最大细分度,告知驱动程序shader用到的最大细分度,硬件可能会针对这个做出优化。Direct3D 11和OpenGL Core都至少支持64。

Patch Constant Function
这个函数指定了一个patch如何细分,每个patch只调用一次这个函数,而不是每个控制点调用一次。Patch Constant Function与Hull shader并行运行。

为了确定如何细分三角形,GPU使用四个细分因子,三角形的每条边都有一个因子,三角形内部也有一个因子,这两种细分因子都具有相应的语义。
因此,需要为这个细分因子创建一个结构体:

struct TessellationFactors 
{
	float edge[3] : SV_TessFactor;
	float inside : SV_InsideTessFactor;
};

现在,我们可以定义Patch Constant Function函数了:

TessellationFactors MyPatchConstantFunction (InputPatch patch) {
	TessellationFactors f;
    f.edge[0] = 1;
    f.edge[1] = 1;
    f.edge[2] = 1;
	f.inside = 1;
	return f;
}

上述的代码中,MyPatchConstantFunction是函数的名称,传入的数据是patch,在函数中定义了每条边以及三角形内部的细分因子,最后输出TessellationFactors。这里的细分因子的参数只是暂时的,可以根据不同需求更改:
不同的Tessellation Factors计算:
1 . _Tessellation:使用_Tessellation变量要求我们在shader中定义相应的Properties。此时,MyPatchConstantFunction 应如下:

TessellationFactors MyPatchConstantFunction (InputPatch patch) {
	TessellationFactors f;
    f.edge[0] = _Tessellation;
    f.edge[1] = _Tessellation;
    f.edge[2] = _Tessellation;
	f.inside = _Tessellation;
	return f;
}

在这里,edge与inside上的细分因子是相同的,他们不同的时候,有着不同的效果,具体可以参照原文。
2 .使用观察空间中的距离设置相应的因子。因此,还需要定义一个设置edge上细分因子的函数,这个函数需要根据每个三角形点的参数进行设置(因为需要计算顶点与相机之间的距离)。因此,代码应该包含两部分:

float TessellationEdgeFactor(TessellationControlPoint cp0, TessellationControlPoint cp1){
	float3 p0 = mul(unity_ObjectToWorld, float4(cp0.vertex.xyz, 1)).xyz;
	float3 p1 = mul(unity_ObjectToWorld, float4(cp1.vertex.xyz, 1)).xyz;
	float edgeLength = distance(p0, p1);

	float3 edgeCenter = (p0 + p1) * 0.5;
	float viewDistance = distance(edgeCenter, _WorldSpaceCameraPos);
	return edgeLength * _ScreenParams.y / (_TessellationEdgeLength * viewDistance);
}
TessellationFactors patchEdgeFunction (InputPatch patch){
	TessellationFactors f;

	f.edge[0] = TessellationEdgeFactor(patch[1], patch[2]);
	f.edge[1] = TessellationEdgeFactor(patch[2], patch[0]);
	f.edge[2] = TessellationEdgeFactor(patch[0], patch[1]);
	
	f.inside = (f.edge[0] + f.edge[1] + f.edge[2]) / 3;

	return f;
}

由于上述的距离是在世界坐标中进行计算的,所以需要进行一个缩放,才能在屏幕中看到正确的效果。这里只乘以一个_ScreenParams.y进行一个简化的缩放。
至此,一个完整的Hull shader应包含以下部分:

// 曲面细分因素
struct TessellationFactors 
{
	float edge[3] : SV_TessFactor;
	float inside : SV_InsideTessFactor;
};

struct TessellationControlPoint{
	float4 vertex : POSITION;
	float3 normal : NORMAL;
	float4 tangent : TANGENT;
};

vertexOutput TessellationVertex (MyTessVertex v){
	return v;
}

float TessellationEdgeFactor(TessellationControlPoint cp0, TessellationControlPoint cp1){
	float3 p0 = mul(unity_ObjectToWorld, float4(cp0.vertex.xyz, 1)).xyz;
	float3 p1 = mul(unity_ObjectToWorld, float4(cp1.vertex.xyz, 1)).xyz;
	float edgeLength = distance(p0, p1);

	float3 edgeCenter = (p0 + p1) * 0.5;
	float viewDistance = distance(edgeCenter, _WorldSpaceCameraPos);
	return edgeLength * _ScreenParams.y / (_TessellationEdgeLength * viewDistance);
}
TessellationFactors patchEdgeFunction (InputPatch patch){
	TessellationFactors f;

	f.edge[0] = TessellationEdgeFactor(patch[1], patch[2]);
	f.edge[1] = TessellationEdgeFactor(patch[2], patch[0]);
	f.edge[2] = TessellationEdgeFactor(patch[0], patch[1]);
	
	f.inside = (f.edge[0] + f.edge[1] + f.edge[2]) / 3;

	return f;
}

[UNITY_domain("tri")]
[UNITY_outputcontrolpoints(3)]
[UNITY_outputtopology("triangle_cw")]
[UNITY_partitioning("fractional_odd")]
[UNITY_patchconstantfunc("patchEdgeFunction")]
TessellationControlPoint myHullProgram (InputPatch patch, uint id : SV_OutputControlPointID)
{
	return patch[id];
}

上述的代码中,有一个TessellationControlPoint 结构体,在原作者中的描述是说可能会产生语义上的错误,专门为曲面细分过程中的顶点进行一个INTERNALTESSPOS语义的定义:

TessellationControlPoint MyTessellationVertexProgram (VertexData v) {
	TessellationControlPoint p;
	p.vertex = v.vertex;
	p.normal = v.normal;
	p.tangent = v.tangent;
	return p;
}

但是不知道为什么,我按照作者的意思却一直不能显示正确的结果,所以我还是使用原始的顶点的结构体。估计是平台兼容的问题?


Domain shader

已经完成了Hull shader,接下来就是定义Domain shader。
Tessellation阶段为我们提供了重心坐标,我们需要在Domain shader中将其转换导出正确空间下的顶点。

[UNITY_domain("tri")]
vertexOutput myDomainProgram(TessellationFactors factors, 
OutputPatch patch, 
float3 barycentricCoordinates : SV_DomainLocation)
{
	vertexOutput v;

	#define MY_DOMAIN_PROGRAM_INTERPOLATE(fieldName) v.fieldName = 
		patch[0].fieldName * barycentricCoordinates.x + 
		patch[1].fieldName * barycentricCoordinates.y + 
		patch[2].fieldName * barycentricCoordinates.z;

	MY_DOMAIN_PROGRAM_INTERPOLATE(vertex)
	MY_DOMAIN_PROGRAM_INTERPOLATE(normal)
	MY_DOMAIN_PROGRAM_INTERPOLATE(tangent)

	return TessellationVert(v);
}

完整代码如上,还是有几点需要注意:

  • Domain shader前面仍然需要添加[UNITY_domain(“tri”)]
  • Domain shader的输入包括:细分因子、patch数据、重心坐标
  • shader内部的重心计算可以设置为一个宏,方便我们计算其他的信息,如顶点、法线、切线等
  • 目前位置,我们没有进行任何将顶点转换到裁剪空间中的操作,原作者是将domain shader的计算结果返回到一个将顶点转换到裁剪空间中的shader,再进行空间变换。知乎大佬也是直接在domain shader中将顶点变换至裁剪空间,这里面在哪里变换选择很多。
  • 还有一点是Hull shader的返回,似乎Hull shader已经计算好了所有的数据,各位大佬返回什么的都有。这里只返回了patch的id

参考:

1 . https://zhuanlan.zhihu.com/p/42550699
2 . https://catlikecoding.com/unity/tutorials/advanced-rendering/tessellation/

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/901800.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号