版权声明:本文为博主原创文章,转载请注明出处 https://blog.csdn.net/u013746357/article/details/85302564
记录使用unity 轻量管线中遇到的一些坑
在LWRP中使用表面细分功能和传统buildin里使用方式基本一致(使用hull domain shader的方式,而不是surface shader ,LWRP不支持surface shader)。唯一问题,这篇文章的发出时的LWRP版本 4.6之前,如果开启了MSAA,会导致表面细分功能出现问题,考虑可能是unity bug,期待后续修复。
尽管这篇文章是讲在LWRP下实现tessellation的方法,同样适用于传统渲染管线和HDRP高清渲染光线,只需做些许调整。
Shader "LWRP/Custom/TessellationSimple"
{
Properties
{
// 这里填入需要在inspector面板显示的属性
// 例如tessellation factor就可以设置成可调节的,而不是直接在代码里定义
}
SubShader
{
// 轻量管线需要声明 RenderPipeline 为 LightweightPipeline,否则不会显示
Tags { "RenderType"="Opaque" "RenderPipeline"="LightweightPipeline" }
//LOD 100
Pass
{
// 声明当前pass的为轻量管线中的LightweightForward,这是轻量管线中的着色pass
// 还有其他pass,可以直接使用 Fallback 里标准内置材质的定义
// 如果有顶点位移动画,可能需要重写DepthOnly pass 和shadowcaster pass。
Tags {"LightMode"="LightweightForward"}
HLSLPROGRAM
// tessellatio 需要GPU支持shader target 4.6 也就是 OpenGLES 3.2 ,其他平台metal, D3D,vulkan 可以参看unity文档
#pragma target 4.6
// LWRP 4.x版本 修改了引用package里hlsl文件的搜索路径,需要填完整路径"Package/xxxx/yyyy/zzzz.hlsl"
#include "LWRP/ShaderLibrary/Core.hlsl"
// 顶点着色器vert
#pragma vertex vert
// 片元着色器frag
#pragma fragment frag
// hull shader,Hull 这里计算表面细分因子
#pragma hull Hull
// domain shader,Domain 这里执行表面细分逻辑
#pragma domain Domain
// 定义宏,用于后边设置最大tessellation 因子,可以设置最大值64,但是在主机平台上最大只能为15,建议移动端也设置为15
#define MAX_TESSELLATION_FACTORS 15.0
// 顶点shader的输入结构
struct CustomVertexInput {
float4 vertex :POSITION; // 模型空间下顶点坐标
float3 normal :NORMAL; // 模型空间下法线
float4 tangent :TANGENT; // 模型空间下切线
float2 texcoord :TEXCOORD0; // 顶点的UV坐标
};
// 顶点shader 的输出结构,以及 tessellation 的输入结构
struct CustomVaryingsMeshToDS{
float3 posWS :INTERNALTESSPOS; // 世界空间下顶点坐标
float3 normal:NORMAL; // 法线,这里为模型空间下法线
float4 tangent:TANGENT; // 切线,这里为模型空间下切线
float2 uv:TEXCOORD0; // uv
};
// tessellation domain shader的输出结构和片元shader的输入结构
struct CustomVertexOutput {
float4 clipPos :SV_POSITION; // 齐次裁剪空间下坐标
float4 uv :TEXCOORD0; // UV 坐标 ,xy主纹理UV,zw,法线纹理UV。
float3 posWS :TEXCOORD2; // 世界空间下坐标
float4 normal :TEXCOORD3; // 法线
float4 tangent :TEXCOORD4; // 切线
};
// 表面细分因子的结构,通用结构,和unity buildIn shader 里定义的UnityTessellationFactor结构一样
struct TessellationFactors{
float edge[3]:SV_TessFactor; //三角面的三个边的细分因子
float inside:SV_InsideTessFactor; // 三角形内部的细分因子
};
TessellationFactors HullConstant(InputPatch<CustomVaryingsMeshToDS,3> input){
// 这里细分因子需要根据不同需求计算,这里直接写裸数,可以参照Buildin shader 里的表面细分的几种方式。
float4 tf=float4(2,2,2,1);// GetTessellationFactors(p0,p1,p2,n0,n1,n2);
TessellationFactors output;
output.edge[0]=min(tf.x,MAX_TESSELLATION_FACTORS);
output.edge[1]=min(tf.y,MAX_TESSELLATION_FACTORS);
output.edge[2]=min(tf.z,MAX_TESSELLATION_FACTORS);
output.inside=min(tf.w,MAX_TESSELLATION_FACTORS);
return output;
}
// 顶点着色器
CustomVaryingsMeshToDS vert (CustomVertexInput v)
{
CustomVaryingsMeshToDS o=(CustomVaryingsMeshToDS)0;
o.uv.xy = v.texcoord;
// 这里需要注意的是,只计算世界空间下顶点坐标即可,齐次空间下的计算会在Domain shader里处理
float3 posWS = mul(UNITY_MATRIX_M, float4(v.vertex.xyz,1.0)).xyz;
o.normal.xyz =v.normal;
o.tangent =v.tangent;
o.posWS = posWS;
return o;
}
//[maxtessfactor(MAX_TESSELLATION_FACTORS)]
[domain("tri")] // 处理三角面
[partitioning("integer")] // 细分的因子的参数类型,这里用integer表示整数,可以为浮点数 "fractional_odd"
[outputtopology("triangle_cw")] // 顺时针顶点排列作为三角面的正面
[patchconstantfunc("HullConstant")] // 计算三角面细分的因子的函数,这里并不是常量,不同三角面可以有不同的值,常量可以理解为对一个三角面内部的三个顶点来讲是统一的值。
[outputcontrolpoints(3)] // 明确指出每个patch处理三个顶点数据
CustomVaryingsMeshToDS Hull(InputPatch<CustomVaryingsMeshToDS,3> input ,uint id:SV_OutputControlPointID){
return input[id]; // 这里直接输出,计算细分因子是在指定的HullConstant函数里
// 这里可以进行其他计算,例如顶点位移等等
}
// 把细分的结果数据转入光栅化阶段这里只把顶点的世界坐标转为了齐次裁剪空间的坐标,这里往往会有其他计算
CustomVertexOutput VertTesselation(CustomVaryingsMeshToDS input)
{
CustomVertexOutput output=(CustomVertexOutput)0;
output.clipPos=mul(UNITY_MATRIX_VP,float4(input.posWS,1.0)); //TransformWorldToHClip(input.posWS);
output.posWS=input.posWS;
output.uv.xy=input.uv;
output.normal=float4(input.normal,1);
output.tangent=input.tangent;
return output;
}
[domain("tri")] // 指定处理的是三角面 triangle
CustomVertexOutput Domain(TessellationFactors tessFactors,const OutputPatch<CustomVaryingsMeshToDS,3> input ,float3 baryCoords:SV_DomainLocation){
CustomVaryingsMeshToDS data;
// 这里使用了一个宏定义来减少重复代码的书写
#define MY_DOMAIN_PROGRAM_INTERPOLATE(fieldName) data.fieldName = \
input[0].fieldName * baryCoords.x + \
input[1].fieldName * baryCoords.y + \
input[2].fieldName * baryCoords.z;
MY_DOMAIN_PROGRAM_INTERPOLATE(posWS) // 插值计算顶点坐标
MY_DOMAIN_PROGRAM_INTERPOLATE(normal) // 插值计算法线
MY_DOMAIN_PROGRAM_INTERPOLATE(tangent) // 插值计算切线
MY_DOMAIN_PROGRAM_INTERPOLATE(uv) // 插值计算UV
return VertTesselation(data); // 处理插值结果,准备光栅化阶段需要的数据
}
// 片元着色器
half4 frag (CustomVertexOutput IN) : SV_Target
{
// 简单的输出白色
float4 col = float4(1,1,1,1);
return col;
}
ENDHLSL
}
}
// 使用标准内置shader里的其他pass
// 需要注意的是,LWRP4.x版本修改了标准内置shader的名称,这里需要对应的修改
FallBack "LightweightPipeline/Standard (Physically Based)"
}