В поверхностных шейдерах есть некоторая поддержка GPU тесселяции DirectX 11. Идея такова:
tessellate:FunctionName
. Эта функция рассчитывает факторы тесселяции на границе и внутри треугольника.vertex:FunctionName
) для с каждой сгенерированной вершины в доменном шейдере. Здесь вы обычно совершили бы наложение карт смещения.Текущие ограничения поддержки тесселяции:
Давайте начнём с поверхностного шейдера, который совершает наложение карт смещения без тесселяции. Он просто двигает вершины вдоль их нормалей, на основе значений, считываемых из карты смещения:
Shader "Tessellation Sample" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_DispTex ("Disp Texture", 2D) = "gray" {}
_NormalMap ("Normalmap", 2D) = "bump" {}
_Displacement ("Displacement", Range(0, 1.0)) = 0.3
_Color ("Color", color) = (1,1,1,0)
_SpecColor ("Spec color", color) = (0.5,0.5,0.5,0.5)
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 300
CGPROGRAM
#pragma surface surf BlinnPhong addshadow fullforwardshadows vertex:disp nolightmap
#pragma target 5.0
struct appdata {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
sampler2D _DispTex;
float _Displacement;
void disp (inout appdata v)
{
float d = tex2Dlod(_DispTex, float4(v.texcoord.xy,0,0)).r * _Displacement;
v.vertex.xyz += v.normal * d;
}
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
sampler2D _NormalMap;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Specular = 0.2;
o.Gloss = 1.0;
o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));
}
ENDCG
}
FallBack "Diffuse"
}
Шейдер, приведённый выше, весьма стандартен, интересные места:
disp
совершает выборку из карты смещений и сдвигает вершины вдоль их нормалей.appdata
) вместо структуры по умолчанию appdata_full
. Пока она не требуется, но для тесселяции эффективней использовать как можно меньшую структуру.nolightmap
для исключения карт освещения.Вот как некоторые простые объекты будут выглядеть при использовании этого шейдера:
Давайте добавим фиксированное количество тесселяции, т.е. одинаковый уровень тесселяции для всего меша. Этот подход пригоден в случае, если на экране полигоны вашей модели примерно одинакового размера. Тогда некоторый код мог бы изменять уровень тесселяции на основе расстояния от камеры.
Shader "Tessellation Sample" {
Properties {
_Tess ("Tessellation", Range(1,32)) = 4
_MainTex ("Base (RGB)", 2D) = "white" {}
_DispTex ("Disp Texture", 2D) = "gray" {}
_NormalMap ("Normalmap", 2D) = "bump" {}
_Displacement ("Displacement", Range(0, 1.0)) = 0.3
_Color ("Color", color) = (1,1,1,0)
_SpecColor ("Spec color", color) = (0.5,0.5,0.5,0.5)
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 300
CGPROGRAM
#pragma surface surf BlinnPhong addshadow fullforwardshadows vertex:disp tessellate:tessFixed nolightmap
#pragma target 5.0
struct appdata {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
float _Tess;
float4 tessFixed()
{
return _Tess;
}
sampler2D _DispTex;
float _Displacement;
void disp (inout appdata v)
{
float d = tex2Dlod(_DispTex, float4(v.texcoord.xy,0,0)).r * _Displacement;
v.vertex.xyz += v.normal * d;
}
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
sampler2D _NormalMap;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Specular = 0.2;
o.Gloss = 1.0;
o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));
}
ENDCG
}
FallBack "Diffuse"
}
Функция тесселяции tessFixed
в нашем шейдере возвращает четыре фактора тесселяции в виде одного значения float4: три фактора для каждой стороны треугольника и один фактор для внутренне области треугольника. Здесь мы просто возвращаем постоянное значение, которое указано в свойствах материала.
Мы также можем менять уровень тесселяции в зависимости от расстояния до камеры. Например, мы могли бы объявить два значения расстояния; расстояние, на котором тесселяция максимальная (скажем, 10 метров), и расстояние, по мере приближения к которому уровень тесселяции плавно падает (скажем, 20 метров).
Shader "Tessellation Sample" {
Properties {
_Tess ("Tessellation", Range(1,32)) = 4
_MainTex ("Base (RGB)", 2D) = "white" {}
_DispTex ("Disp Texture", 2D) = "gray" {}
_NormalMap ("Normalmap", 2D) = "bump" {}
_Displacement ("Displacement", Range(0, 1.0)) = 0.3
_Color ("Color", color) = (1,1,1,0)
_SpecColor ("Spec color", color) = (0.5,0.5,0.5,0.5)
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 300
CGPROGRAM
#pragma surface surf BlinnPhong addshadow fullforwardshadows vertex:disp tessellate:tessDistance nolightmap
#pragma target 5.0
#include "Tessellation.cginc"
struct appdata {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
float _Tess;
float4 tessDistance (appdata v0, appdata v1, appdata v2) {
float minDist = 10.0;
float maxDist = 25.0;
return UnityDistanceBasedTess(v0.vertex, v1.vertex, v2.vertex, minDist, maxDist, _Tess);
}
sampler2D _DispTex;
float _Displacement;
void disp (inout appdata v)
{
float d = tex2Dlod(_DispTex, float4(v.texcoord.xy,0,0)).r * _Displacement;
v.vertex.xyz += v.normal * d;
}
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
sampler2D _NormalMap;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Specular = 0.2;
o.Gloss = 1.0;
o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));
}
ENDCG
}
FallBack "Diffuse"
}
Здесь функция тесселяции получает три параметра; данные вершин трёх углов треугольника до тесселяции. Это необходимо для расчёта уровней тесселяции, которые зависят от текущего положения вершин. Мы включаем встроенный вспомогательный файл Tessellation.cginc
и вызываем из него функцию UnityDistanceBasedTess
для совершения всей работы. Эта функция рассчитывает расстояние до камеры каждой вершины и извлекает конечные факторы тесселяции.
Тесселяция, основанная только на расстоянии хороша только когда размеры треугольников довольно похожи. На изображении, представленном выше, вы можете заметить, что у объектов с маленькими треугольниками тесселяция слишком сильная, в то время как объекты с большими треугольниками недостаточно тесселированы.
Вместо этого, уровни тесселяции могут быть рассчитаны на основе длины стороны треугольника на экране. - чем длиннее сторона, тем больший должен применяться фактор тесселяции.
Shader "Tessellation Sample" {
Properties {
_EdgeLength ("Edge length", Range(2,50)) = 15
_MainTex ("Base (RGB)", 2D) = "white" {}
_DispTex ("Disp Texture", 2D) = "gray" {}
_NormalMap ("Normalmap", 2D) = "bump" {}
_Displacement ("Displacement", Range(0, 1.0)) = 0.3
_Color ("Color", color) = (1,1,1,0)
_SpecColor ("Spec color", color) = (0.5,0.5,0.5,0.5)
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 300
CGPROGRAM
#pragma surface surf BlinnPhong addshadow fullforwardshadows vertex:disp tessellate:tessEdge nolightmap
#pragma target 5.0
#include "Tessellation.cginc"
struct appdata {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
float _EdgeLength;
float4 tessEdge (appdata v0, appdata v1, appdata v2)
{
return UnityEdgeLengthBasedTess (v0.vertex, v1.vertex, v2.vertex, _EdgeLength);
}
sampler2D _DispTex;
float _Displacement;
void disp (inout appdata v)
{
float d = tex2Dlod(_DispTex, float4(v.texcoord.xy,0,0)).r * _Displacement;
v.vertex.xyz += v.normal * d;
}
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
sampler2D _NormalMap;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Specular = 0.2;
o.Gloss = 1.0;
o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));
}
ENDCG
}
FallBack "Diffuse"
}
Здесь снова, мы просто вызываем функцию UnityEdgeLengthBasedTess
из Tessellation.cginc
для совершения всей работы.
Для сохранения хорошей производительности, вместо приведенной выше функции рекомендуется вызывать функцию UnityEdgeLengthBasedTessCull
, которая совершит отсечение островков по пирамиде видимости. Это делает шейдер немного более ресурсоёмким, но сохраняет большое количество ресурсов GPU для частей меша, которые не попадают в область видимости камеры.
Phong тесселяция изменяет положения подразделённых полигонов так, чтобы результирующая поверхность немного следовала нормалям меша. Это довольно эффективный способ сглаживания низкополигональных мешей.
Поверхностные шейдеры Unity могут автоматически рассчитывать phong тесселяцию с помощью директивы компиляции tessphong:VariableName
. Вот пример шейдера:
Shader "Phong Tessellation" {
Properties {
_EdgeLength ("Edge length", Range(2,50)) = 5
_Phong ("Phong Strengh", Range(0,1)) = 0.5
_MainTex ("Base (RGB)", 2D) = "white" {}
_Color ("Color", color) = (1,1,1,0)
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 300
CGPROGRAM
#pragma surface surf Lambert vertex:dispNone tessellate:tessEdge tessphong:_Phong nolightmap
#include "Tessellation.cginc"
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
void dispNone (inout appdata v) { }
float _Phong;
float _EdgeLength;
float4 tessEdge (appdata v0, appdata v1, appdata v2)
{
return UnityEdgeLengthBasedTess (v0.vertex, v1.vertex, v2.vertex, _EdgeLength);
}
struct Input {
float2 uv_MainTex;
};
fixed4 _Color;
sampler2D _MainTex;
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
Вот сравнение между обычным шейдером (верхняя строка) и тем, который использует phong тесселяцию (нижняя строка). Вы можете заметить, что даже без какого-либо наложения карт смещения, поверхность становится более округлой.