左边是默认Cube在网格模式下经过曲面细分的结果,右边是原状态。
曲面细分着色器在顶点着色器、几何着色器之后,像素着色器之前。
它的作用时根据配置信息生成额外的顶点以切割原本的面片。
关于这部分有一个详细的英文教程,感兴趣可以看一下。
https://catlikecoding.com/unity/tutorials/advanced-rendering/tessellation/
以下是完整代码
Shader "Kerzh/KerzhCgShaderTemplate"
{
Properties
{
_Color("Color", Color) = (1,1,1,1)
_TessellationUniform ("Tessellation Uniform", Vector) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalRenderPipeline" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma hull hull
#pragma domain domain
#pragma geometry geom
#pragma fragment frag
#pragma target 4.6
#include "UnityCG.cginc"
#include "Assets/TA/ShaderLib/CgincInclude//CommonCgInc.cginc"
#include "Assets/TA/ShaderLib/CgincInclude//CustomTessellation.cginc"
MeshData vert (MeshData input)
{
return input;
}
//如果不正确配置会报错
[UNITY_domain("tri")] // 正在处理三角形 "tri", "quad", or "isoline"
[UNITY_outputcontrolpoints(3)] // 每个面片输出的顶点为3个
[UNITY_outputtopology("triangle_cw")] // 当创建三角形时应是顺时针还是逆时针,这里应是顺时针 "point", "line", "triangle_cw", or "triangle_ccw"
[UNITY_partitioning("integer")] // 如何细分切割面片 "integer", "pow2", "fractional_even", or "fractional_odd"
[UNITY_patchconstantfunc("patchConstantFunction")] // 细分切割部分还必须提供函数处理,每个面片调用一次
MeshData hull (InputPatch patch, uint id : SV_OutputControlPointID) // 每个顶点调用一次,如果是处理三角形就是调用三次
{
// 如果_TessellationUniform输入值为1,不产生任何变化,但当输入值为2时,具体发生了什么
// 对于一个三角形(因为我们配置的是处理三角形),根据三条边的二等分位置添加额外的一个顶点,如果是3就是三等分位置添加两个顶点
// 对于一个三角形(因为我们配置的是处理三角形),根据三条个顶点添加一个中心顶点
// 根据patchConstantFunction赋值分配生成顶点的插值权重
return patch[id];
}
// barycentricCoordinates分别代表各个点的插值权重,每个面片调用一次,对细分后的三角顶点形进行处理(也就是说原顶点不经过此处理?)
[UNITY_domain("tri")] // 正在处理三角形
MeshData domain(TessellationFactors factors, OutputPatch patch, float3 barycentricCoordinates : SV_DomainLocation)
{
MeshData 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(normalOS)
MY_DOMAIN_PROGRAM_INTERPOLATE(tangentOS)
MY_DOMAIN_PROGRAM_INTERPOLATE(uv1)
MY_DOMAIN_PROGRAM_INTERPOLATE(uv2)
MY_DOMAIN_PROGRAM_INTERPOLATE(vertexColor)
return data;
}
//最大的顶点数,这里比如你要生成三个三角形面片,那么一个面片需要三个顶点,就是9个顶点,一般来讲这里直接填多一点即可,不过可能填太多了会导致内存占用?
[maxvertexcount(9)]
void geom (triangle MeshData input[3],inout TriangleStream triStream)
{
//原样转换过去
VOutData output[3];
for(int i=0;i
依赖文件CommonCgInc.cginc:
#ifndef CommonCgInc
#define CommonCgInc
float3 FromScript_LocalPositionWS;
float3 FromScript_LocalRotationWS;
float3 FromScript_LocalScaleWS;
//输入结构
struct MeshData
{
float4 vertex : POSITION;
float2 uv1 : TEXCOORD0;
float2 uv2 : TEXCOORD1;
float4 tangentOS :TANGENT;
float3 normalOS : NORMAL;
float4 vertexColor : COLOR;
};
//传递结构
struct VOutData
{
float4 pos : SV_POSITION; // 必须命名为pos ,因为 TRANSFER_VERTEX_TO_FRAGMENT 是这么命名的,为了正确地获取到Shadow
float2 uv1 : TEXCOORD0;
float3 tangentWS : TEXCOORD1;
float3 bitangentWS : TEXCOORD2;
float3 normalWS : TEXCOORD3;
float3 posWS : TEXCOORD4;
float3 posOS : TEXCOORD5;
float3 normalOS : TEXCOORD6;
float4 vertexColor : TEXCOORD7;
float2 uv2 : TEXCOORD8;
};
//传递结构赋值
VOutData FillBaseV2FData(MeshData input)
{
VOutData output;
output.pos = UnityObjectToClipPos(input.vertex);
output.uv1 = input.uv1;
output.uv2 = input.uv2;
output.normalWS = normalize(UnityObjectToWorldNormal(input.normalOS));
output.posWS = mul(unity_ObjectToWorld, input.vertex);
output.posOS = input.vertex.xyz;
output.tangentWS = normalize(UnityObjectToWorldDir(input.tangentOS));
output.bitangentWS = cross(output.normalWS, output.tangentWS) * input.tangentOS.w; //乘上input.tangentOS.w 是unity引擎的bug,有的模型是 1 有的模型是 -1,必须这么写
output.normalOS = input.normalOS;
output.vertexColor = input.vertexColor;
return output;
}
// Hue, Saturation, Value
// Ranges:
// Hue [0.0, 1.0]
// Sat [0.0, 1.0]
// Lum [0.0, HALF_MAX]
// //HSV色彩模型转为RGB色彩模型 FROM::color.hlsl
float3 HSV2RGB( float3 hsv ){
const float4 K = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
float3 p = abs(frac(hsv.xxx + K.xyz) * 6.0 - K.www);
return hsv.z * lerp(K.xxx, saturate(p - K.xxx), hsv.y);
}
//RGB色彩模型转为HSV色彩模型 FROM::color.hlsl
float3 RGB2HSV(float3 rgb)
{
float4 K = float4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
float4 p = lerp(float4(rgb.bg, K.wz), float4(rgb.gb, K.xy), step(rgb.b, rgb.g));
float4 q = lerp(float4(p.xyw, rgb.r), float4(rgb.r, p.yzx), step(p.x, rgb.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return float3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
//Gooch光照模型 ⻛格化的着⾊模型 强调冷暖色调 FROM::render4th P146
float3 GoochModel(float3 baseColor, float3 highLightColor, float3 normalWs, float3 lightDirWs, float3 viewDirWs){
normalWs = normalize(normalWs);
lightDirWs = normalize(lightDirWs);
float3 coolColor = float3(0,0,0.55) + 0.25*baseColor;
float3 warmColor = float3(0.3,0.3,0) + 0.25*baseColor;
float size = clamp(100*(dot(reflect(lightDirWs, normalWs), viewDirWs) - 97), 0, 1);
float4 halfLambert = dot(normalWs, lightDirWs) * 0.5 + 0.5;
return size*highLightColor + (1-size)*(halfLambert*warmColor + (1 - halfLambert)* coolColor);
}
//添加虚拟点光源 _VirtualLightFade越大,衰减越快
float3 C服务器托管网alculatePointVirtualLight(float3 _VirtualLightPos, float3 positionOS, float _VirtualLightFade, float3 _VirtualLightColor)
{
float virtualLightDis = distance(_VirtualLightPos,positionOS);
float3 virtualLight = exp(-_VirtualLightFade*virtualLightDis)*_VirtualLightColor;
//TODO:也许补充方向衰减
return virtualLight;
}
//切线空间计算视差uv 根据视角方向以深度反追命中点uv
float2 CalculateRealUVAfterDepth(float2 originUV, float3 viewDirTS, float depth)
{
//计算视角方向和深度方向的cos值
float cosTheta = dot(normalize(viewDirTS), float3(0,0,-1)); // 一般来讲unity是左手坐标系,但在切线和观察空间较为特殊是右手坐标系,不过这并不影响z轴方向的判断
//根据深度差算出两点间距离
float dis = depth / cosTheta;
//算出应用深度差后对应点位
float3 originUVPoint = float3(originUV, 0);
float3 afrerDepthUVPoint = originUVPoint + normalize(viewDirTS) * dis;
//返回应用深度差后对应UV
return afrerDepthUVPoint.xy;
}
#endif
依赖文件Cu服务器托管网stomTessellation.cginc:
// Tessellation programs based on this article by Catlike Coding:
// https://catlikecoding.com/unity/tutorials/advanced-rendering/tessellation/
// 关于参数的详细说明
// https://zhuanlan.zhihu.com/p/479792793
#include "Assets/TA/ShaderLib/CgincInclude//CommonCgInc.cginc"
//细分切割函数对于每个面片调用一次,对于每一个面片,比如三角形:每条边需要一个切割因子,内部需要一个切割因子
struct TessellationFactors {
float edge[3] : SV_TessFactor; // 对应三条边的切割因子
float inside : SV_InsideTessFactor; // 对应内部的切割因子
};
float4 _TessellationUniform; // 细分因子,当值为1时不发生细分切割。因子可以分别设置,比如边因子是1内部因子是5,那就不会在边上生成顶点,会在中心生成五个顶点
//自定义的细分切割方法,每个面片调用一次
TessellationFactors patchConstantFunction (InputPatch patch)
{
TessellationFactors f;
f.edge[0] = _TessellationUniform.x;
f.edge[1] = _TessellationUniform.y;
f.edge[2] = _TessellationUniform.z;
f.inside = _TessellationUniform.w;
return f;
}
其中,通过
#pragma hull hull #pragma domain domain
定义hull和domain着色器,其中hull负责切割,domain用于根据权重处理细分后的顶点数据。
hull部分:
//细分切割函数对于每个面片调用一次,对于每一个面片,比如三角形:每条边需要一个切割因子,内部需要一个切割因子
struct TessellationFactors {
float edge[3] : SV_TessFactor; // 对应三条边的切割因子
float inside : SV_InsideTessFactor; // 对应内部的切割因子
};
float4 _TessellationUniform; // 细分因子,当值为1时不发生细分切割。因子可以分别设置,比如边因子是1内部因子是5,那就不会在边上生成顶点,会在中心生成五个顶点
//自定义的细分切割方法,每个面片调用一次
TessellationFactors patchConstantFunction (InputPatch patch)
{
TessellationFactors f;
f.edge[0] = _TessellationUniform.x;
f.edge[1] = _TessellationUniform.y;
f.edge[2] = _TessellationUniform.z;
f.inside = _TessellationUniform.w;
return f;
}
//如果不正确配置会报错
[UNITY_domain("tri")] // 正在处理三角形 "tri", "quad", or "isoline"
[UNITY_outputcontrolpoints(3)] // 每个面片输出的顶点为3个
[UNITY_outputtopology("triangle_cw")] // 当创建三角形时应是顺时针还是逆时针,这里应是顺时针 "point", "line", "triangle_cw", or "triangle_ccw"
[UNITY_partitioning("integer")] // 如何细分切割面片 "integer", "pow2", "fractional_even", or "fractional_odd"
[UNITY_patchconstantfunc("patchConstantFunction")] // 细分切割部分还必须提供函数处理,每个面片调用一次
MeshData hull (InputPatch patch, uint id : SV_OutputControlPointID) // 每个顶点调用一次,如果是处理三角形就是调用三次
{
// 如果_TessellationUniform输入值为1,不产生任何变化,但当输入值为2时,具体发生了什么
// 对于一个三角形(因为我们配置的是处理三角形),根据三条边的二等分位置添加额外的一个顶点,如果是3就是三等分位置添加两个顶点
// 对于一个三角形(因为我们配置的是处理三角形),根据三条个顶点添加一个中心顶点
// 根据patchConstantFunction赋值分配生成顶点的插值权重
return patch[id];
}
这里做的操作比较少,只是传入每个面片的切割因子,一个面片(三角形)的每条边需要一个切割因子,内部需要一个切割因子,一共四个切割因子。
domain部分:
// barycentricCoordinates分别代表各个点的插值权重,每个面片调用一次,对细分后的三角顶点形进行处理(也就是说原顶点不经过此处理?)
[UNITY_domain("tri")] // 正在处理三角形
MeshData domain(TessellationFactors factors, OutputPatch patch, float3 barycentricCoordinates : SV_DomainLocation)
{
MeshData 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(normalOS)
MY_DOMAIN_PROGRAM_INTERPOLATE(tangentOS)
MY_DOMAIN_PROGRAM_INTERPOLATE(uv1)
MY_DOMAIN_PROGRAM_INTERPOLATE(uv2)
MY_DOMAIN_PROGRAM_INTERPOLATE(vertexColor)
return data;
}
这里则是根据传入的边和内部的切割因子的权重,对于新增加的顶点,使用这个方法中的规则填充顶点的对应信息,以完成切割。
MY_DOMAIN_PROGRAM_INTERPOLATE
是定义了一个宏用于重复处理所有的属性,还是要根据实际情况对应处理方法。
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
Web前端开发框架的选择对于开发效率、项目维护以及用户体验等方面都有着重要的影响。以下是一些截至目前常见且广泛使用的Web前端开发框架,它们各自具有不同的特点和优势: React: 开发者:由Facebook维护。 特点:采用组件化的方式开发,拥有强大的社区支…