曲面着色器初试(部分细节不完善)
文章目录
前言
在图形管线基础(一)中我们介绍OpenGL渲染管线的基本流程以及基本概念。本次章节将对图形管线中的曲面细分着色器做一个小Demo,有不完善的地方,仅提供思路。也有一个问题尚未解决,如果你们有解决方案也请私聊我。
一、曲面细分着色器基本概念
1.整体概念
曲面细分着色流程位于顶点着色器之后,几何着色器之前。细分曲面是将高阶基元(opengl中成为贴片[patch])分解为许多更小,更简单的基元进行渲染的过程。例如拆分为多个三角形。Opengl包含一个固定功能的、可配置的细分曲面引擎。可将多个四边形、三角形和线分解为大量更小的点,线或三角形,这些点、线或三角形可有管线中常规光栅器硬件直接使用。从逻辑上讲,细分曲面阶段位于Opengl管线中顶点着阶段之后,主要由三部分组成:细分曲面控制器(Tessellaction Control Shader)、固定函数细分曲面引擎(Tessellation Primitive Gen)和细分曲面评估着色器(Tessellation Eval Shader),这三个部分具体概念之前的文章有讲过,这里不再赘述。
我们可以从图中可以看到,曲面着色器中,有两个模块是可以编辑模块,分别是曲面控制器以及曲面细分评估着色器。曲面细分引擎是固定函数不可编辑。在实际编写曲面着色器时,这并不像其他着色器添加一个其他程序那么简单。我们需要一个外壳程序和域程序。如下图所示:‘不知道我的理解有没有错误,我认为Hull Program外壳函数就是我们渲染流程图中的细分曲面控制器,主要负责输出控制点以及细分因子。Tessellation就是我们的细分引擎,不可编辑。Domain Program作为我们的域函数就是我们的细分评估着色器,主要执行顶点赋值操作。介绍了大致概念,我们开始来做一个demo。
二、曲面着色器小试牛刀
1.大致需求
我们希望在地形上记录人物行走轨迹,使用曲面着色器细分地形,然后做一个地形下拉,完成人物轨迹的制作。在不使用曲面着色器的情况下,地形顶点较少,下拉后形状呈倒三角,过于平整,不好看。使用曲面细分后,顶点增加,随之面数也增加,下拉后形状趋于渐变,自然。本次demo会用使用两种着色器实现,一种是表面着色器,代码量少,几句话就能实现,一种Unlit着色器,需要手动实现曲面细分着色器,代码量多,但是效果是一样的。
1-1.实现效果
2.实现步骤
2-0.大体思路
整体思路很简单,用一个额外摄像机记录人物轨迹,摄像机数据不清除。将摄像机拍摄的内容放到我们的RT中,在shader中我们先使用细分曲面着色器根据线长度细分地形,增加更多的顶点以及面片,再采样RT,根据人物轨迹做y轴上的地形下拉,完成效果。
2-1.场景组合
场景构成如上,箭头标注1,2,3.
箭头标注1:是一个扁平化球体,他将挂在人物脚步位置,随着角色移动,将作为记录人物运动轨迹的标签。
箭头标注2:就是我们结合人物运动轨迹使用曲面细分着色器制作的地形下拉效果。
箭头标注3:将作为我们拍摄角色运动标签的摄像机,摄像机位于人物头顶上方,跟小地图制作方式差不多。使用正交投影,摄像机设置如下:关键参数已经标注。
我们可以切到网状视图下,看细分的效果
2-2.shader代码实现(使用Unlit)
Shader "Unlit/Tessellation"
{
Properties
{
_Color ("Tint", Color) = (1, 1, 1, 1)
_MainTex ("Albedo", 2D) = "white" {}
_SnowTex("SnowTex", 2D) = "white" {}
_SnowFactor("SnowFactor", float) = 1
_SnowColor("SnowColor", Color) = (1,1,1,1)
_TessellationEdgeLength ("Tessellation Edge Length", Range(5, 100)) = 50
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(1.0, 500)) = 20
}
CGINCLUDE
ENDCG
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass {
Tags {
"LightMode" = "ForwardBase"
}
CGPROGRAM
#pragma target 4.0
#pragma vertex MyTessellationVertexProgram
#pragma fragment frag
#pragma hull MyHullProgram
#pragma domain MyDomainProgram
#include "UnityCG.cginc"
#include "Lighting.cginc"
float _TessellationEdgeLength;
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _SnowColor;
sampler2D _SnowTex;
float4 _SnowTex_ST;
float _SnowFactor;
fixed4 _Specular;
float _Gloss;
struct InterpolatorsVertex {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float3 normal : TEXCOORD1;
float3 worldPos : TEXCOORD2;
float3 worldNormal : TEXCOORD3 ;
};
struct VertexData {
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct TessellationControlPoint {
float4 vertex : INTERNALTESSPOS;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct TessellationFactors {
float edge[3] : SV_TessFactor;
float inside : SV_InsideTessFactor;
};
TessellationControlPoint MyTessellationVertexProgram (VertexData v) {
TessellationControlPoint p;
p.vertex = v.vertex;
p.normal = v.normal;
p.uv = v.uv;
return p;
}
float TessellationEdgeFactor (float3 p0, float3 p1) {
float edgeLength = distance(p0, p1);
float3 edgeCenter = (p0 + p1) * 0.5;
float viewDistance = distance(edgeCenter, _WorldSpaceCameraPos);
return edgeLength * _ScreenParams.y /
(_TessellationEdgeLength * viewDistance);
}
TessellationFactors MyPatchConstantFunction (
InputPatch<TessellationControlPoint, 3> patch
) {
float3 p0 = mul(unity_ObjectToWorld, patch[0].vertex).xyz;
float3 p1 = mul(unity_ObjectToWorld, patch[1].vertex).xyz;
float3 p2 = mul(unity_ObjectToWorld, patch[2].vertex).xyz;
TessellationFactors f;
f.edge[0] = TessellationEdgeFactor(p1, p2);
f.edge[1] = TessellationEdgeFactor(p2, p0);
f.edge[2] = TessellationEdgeFactor(p0, p1);
f.inside =
(TessellationEdgeFactor(p1, p2) +
TessellationEdgeFactor(p2, p0) +
TessellationEdgeFactor(p0, p1)) * (1 / 3.0);
return f;
}
[UNITY_domain("tri")]
[UNITY_outputcontrolpoints(3)]
[UNITY_outputtopology("triangle_cw")]
[UNITY_partitioning("fractional_odd")]
[UNITY_patchconstantfunc("MyPatchConstantFunction")]
TessellationControlPoint MyHullProgram (
InputPatch<TessellationControlPoint, 3> patch,
uint id : SV_OutputControlPointID
) {
return patch[id];
}
InterpolatorsVertex MyVertexProgram (VertexData v) {
InterpolatorsVertex i;
v.vertex.y -= tex2Dlod(_SnowTex, float4(v.uv.xy,0,0)).r * _SnowFactor;
i.pos = UnityObjectToClipPos(v.vertex);
i.worldPos.xyz = mul(unity_ObjectToWorld, v.vertex);
i.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
i.worldNormal = UnityObjectToWorldNormal(v.normal);
return i;
}
[UNITY_domain("tri")]
InterpolatorsVertex MyDomainProgram (
TessellationFactors factors,
OutputPatch<TessellationControlPoint, 3> patch,
float3 barycentricCoordinates : SV_DomainLocation
) {
VertexData data;
#define MY_DOMAIN_PROGRAM_INTERPOLATE(fieldName) data.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(uv)
return MyVertexProgram(data);
}
fixed4 frag (InterpolatorsVertex i) : SV_Target
{
float snow = tex2D(_SnowTex, i.uv).r ;
fixed4 col = tex2D(_MainTex, i.uv) + _SnowColor * snow;
float3 normalizeResult123 = normalize( ( cross( ddx( i.worldPos ) , ddy( i.worldPos ) ) + float3( 1E-09,0,0 ) ) );
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * col;
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 diffuse = _LightColor0.rgb * col.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse , 1.0);
}
ENDCG
}
}
}
2-3.shader代码实现(使用surf)
Shader "Custom/Tessellation2"
{
Properties
{
_EdgeLength ( "Edge length", Range( 2, 50 ) ) = 15
_MainTex ("Texture", 2D) = "white" {}
_BumpTex("BumpTex", 2D) = "bump" {}
_SnowTex("SnowTex", 2D) = "white" {}
_SnowFactor("SnowFactor", float) = 1
_SnowColor("SnowColor", Color) = (1,1,1,1)
[HideInInspector] _texcoord( "", 2D ) = "white" {}
}
SubShader
{
Tags{ "RenderType" = "Opaque" "Queue" = "Geometry+0" }
Cull Back
CGPROGRAM
#include "Tessellation.cginc"
#pragma target 4.6
#pragma surface surf Standard keepalpha addshadow fullforwardshadows vertex:vertexDataFunc tessellate:tessFunction
struct Input
{
float2 uv_texcoord;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpTex;
float4 _BumpTex_ST;
sampler2D _SnowTex;
float4 _SnowTex_ST;
float _SnowFactor;
float4 _SnowColor;
uniform float _EdgeLength;
float4 tessFunction( appdata_full v0, appdata_full v1, appdata_full v2 )
{
return UnityEdgeLengthBasedTess (v0.vertex, v1.vertex, v2.vertex, _EdgeLength);
}
void vertexDataFunc( inout appdata_full v )
{
v.vertex.y -= tex2Dlod(_SnowTex, float4(v.texcoord.xy,0,0)).r * _SnowFactor;
}
void surf( Input i , inout SurfaceOutputStandard o )
{
float2 uv_Albedo = i.uv_texcoord * _MainTex_ST.xy + _MainTex_ST.zw;
float snow = tex2D(_SnowTex, float2(i.uv_texcoord)).r ;
o.Albedo =tex2D(_MainTex, uv_Albedo).rgb + _SnowColor * snow;
o.Alpha = 1;
}
ENDCG
}
Fallback "Diffuse"
}
总结
以上就是今天要讲的内容,本文仅仅简单介绍了曲面着色器的大致应用。我们可以看到使用表面着色器和使用Unlit着色器,代码量上的差距,表面着色器,unity帮我们实现了大部分逻辑,仅仅暴露出细分因子函数,可手动修改的部分比较少。而且据说unity自带的细分因子函数有Bug,在某些复杂地形下细分会出现空隙。Unlit则需要我们补足每个函数逻辑,手动化更高,编辑性更强。但是本次demo除了细节上不足之外,有个致命的问题,就是法线信息没有随着曲面细分,顶点下拉而改变,导致他光线计算有误,没有亮暗的交替变化,这个问题我还不知道怎么解决,也希望有知道这方面的大佬能私信我,将感激不尽。接下来自己将进入urp的学习。