unity曲面细分着色器(上):原理和基本实现

unity曲面细分着色器(上):原理和基本实现

目录

前言

最近在学习百人计划的曲面细分着色器的时候,看着大佬提供的源码一头雾水,课上的老师讲得也比较快,感觉无法理解,就去看了英文教程

真的好长啊这个文章,一边自己翻译一边做了两天,都是我自己翻译的,看完之后感觉英语水平又提高了好多

参考

Flat and Wireframe Shading Derivatives and Geometry:https://catlikecoding.com/unity/tutorials/advanced-rendering/flat-and-wireframe-shading/

Tessellation Subdividing Triangles:https://catlikecoding.com/unity/tutorials/advanced-rendering/tessellation/

3.3 曲面细分与几何着色器—大规模草渲染:https://www.yuque.com/sugelameiyoudi-jadcc/okgm7e/xyx5h5#eI5Sb

【技术美术百人计划】图形 3.3 曲面细分与几何着色器 大规模草渲染https://www.bilibili.com/video/BV1XX4y1A7Ns/?spm_id_from=333.1387.collection.video_card.click&vd_source=75cfca612334135de761351e88587faa

原理

着色器顺序

在这里插入图片描述

  • 整体顺序:顶点 → 曲面细分 → 几何 → 片元
    • 曲面细分又分为:Hull shader 、Tessellation Primitive Generator 、 Domain shader
      • Hull shader主要作用:定义一些细分的参数(如:每条边上如何细分,内部三角形如何细分)
      • Tessellation Primitive Generator,不可编程的
      • Domain shader:经过曲面细分着色器细分后的点是位于重心空间的,这部分的作用就是把它转化到我们要用的空间。
  • 在D3D11 和 OpenGL中,名字/叫法有差异,问题不大

输入和输出

在这里插入图片描述

流程

Hull shader → Tessellation Primitive Generator → Domain shader

Hull shader

  • 定义细分的参数
    • 设定Tessellation factor以及Inside Tessellation factor
  • (如果需要的话)可以对输入的Patch参数进行改变

Tessellation Primitive Generator

  • 这部分是不可编程、无法控制的
  • 进行细分操作

Domain shader

  • 对细分后的点进行处理,从重心空间(Barycentric coordinate system)转换到屏幕空间

曲面细分hull shader各参数解析

在这里插入图片描述

Tessellation Factor

  • 定义把一条边分为几个部分
  • 切分的方法有三种
  • equal_Spacing
    • 把一条边等分(二、三分等等…)

  • fractional_even_spacing
    • 向上取最近的偶数

    • 最小值是2

    • 会把周长分为n-2的等长部分、以及两端不等长的部分(两端部分和小数有关,具体看gif)

  • fractional_odd_spacing
    • 向上取最近的奇数

    • 最小值是1

    • 会把周长分为n-2的等长部分、以及两端不等长的部分

    • 目的:让细分更加平滑

inner tesselation factor:

  • 定义内部的三角形/矩形是怎么画出来的
  • 三角形情况

假设我们这个参数设置为三,不管之前的这个factor它是怎么切分这个边的,我们就按当前这个三来把这个三角形切分成三个等份

我们从它一个端点开始,分别找它最近的这两个切分的点,做它们的延长线,他们的交点就是新的内部三角形的一个点,以此类推做出这内部的这个三角形

如果多一个点,就在内部这个三角形再多一个点,然后再用这两个点延长线,然后做交点

在这里插入图片描述
在这里插入图片描述

  • 例如上图三等分的情况:
    • 将三条边三等分,然后从一个端点开始,取邻近的两个切分点做延长线,两者的交点就是新三角形的一个端点。以此类推就是左图的效果。
  • 上图四等分、甚至更多点的情况:
    • 上述三等分步骤之后,内部三角形的每个边的等分点做延长线

矩形类似
在这里插入图片描述
在这里插入图片描述

  • 同样的,做延长线,交点,直到没有交点或者交于重心一个点

hull program

为了看得更清楚,这个曲面细分着色器是基于平面线框着色器的

详情可以看我的上一节,最后有全部源码:unity模型平直着色、线框效果https://blog.youkuaiyun.com/nidayeaaaaaaaaa/article/details/146956340?spm=1001.2014.3001.5501

复制这个shader然后重命名一下,在这基础上实现曲面细分

创建自己的cginc,把核心代码写到这里,这样其他shader想要用这个功能直接引用这个cginc文件就行

注意cginc命名,如果直接命名为Tessellation,他会覆盖unity自带的cginc

在这里插入图片描述

注意target level最低4.6
在这里插入图片描述

曲面细分着色器步骤
在这里插入图片描述

声明hull program

和几何着色器一样,曲面细分着色器也很灵活,可以对三角形、四边形、等值线进行操作,但是我们必须在hull shader中告知他要操作的表面的类型,并且输入必要的参数

添加hull program,hull program是对一个面片起作用,所以需要传入InputPatch参数,他同样需要模版来规定类型

所谓的patch就是一组mesh顶点的集合

这里我们新建一个结构体用于输入模版参数

因为一个三角形有3个顶点,所以模版的第二个参数写3,意味着传入的一个patch有3个顶点,每个顶点都携带着VertexData结构体类型的信息
在这里插入图片描述

hull program的作用是传递顶点信息给曲面细分阶段,尽管他被传入了一整个patch,但是他一次只能输出一个顶点

对于patch中的每个顶点,hullprogram都会被调用一次,所以每次调用都需要一个参数指示他是对哪个控制点生效,这个参数是一个uint类型,语义为SV_OutputControlPointID
在这里插入图片描述

返回指定的patch
在这里插入图片描述

在shader中声明hull program,现在hull program还不完全,所以会报错
在这里插入图片描述

hull program所需的属性

和几何着色器一样,他也需要具体指定对triangles生效,使用UNITY_domain属性:[UNITY_domain("tri")]

而且我们还要具体告知他,他每次输出3个控制点,分别对应三角形的三个点:[UNITY_outputcontrolpoints(3)]

gpu在创建新的三角形的时候,还需要知道这个三角形的三个点的定义顺序是顺时针还是逆时针,unity中默认是顺时针定义(clockwise):[UNITY_outputtopology("triangle_cw")]

gpu还要知道要怎么对这个patch切分,使用UNITY_partitioning属性,有很多partitioning方法,之后会做介绍,现在先用integer方法:[UNITY_patitioning("integer")]

其次,gpu还要知道一个patch被切割为多少份,这个份数可能每个patch都不一样,所以我们需要写一个方法来计算它,这个就是patch constant function,目前我们还没定义,先写着:[UNITY_patchconstantfunc("MyPatchConstantFuncition")]

最后,hullProgram会返回VertexData
在这里插入图片描述

patch constant function

patch的属性之一就是他会被如何分割,patch constant function只会被每个patch调用一次,而不是每个控制点都调用一次

所以他才会被叫做Constant function,因为他在整个patch中都是const的

事实上,patch constant function是和hull program并行运行的子阶段
在这里插入图片描述

对于三角形,gpu使用4个参数来对他进行划分,其中3个分别控制三角形的三个边,使用SV_TessFactor语义,剩下一个控制三角形内部,控制内部的使用**SV_InsideTessFactor**语义
在这里插入图片描述

创建patch constant function,他以patch作为输入,输出上面的这个TessellationFactors结构体

先让他每个边都设置为1,这样他就不会对patch做切分
在这里插入图片描述

Domain shader

创建domain Program

hull和domain都是对三角形作用的,所以再次声明作用域为triangle

domain Program传入TessellationFactors和一个OutputPatch模版
在这里插入图片描述

虽然Tessellation阶段会分割patch,但是他实际上不生成任何新的顶点

他为这些顶点创建重心坐标,然后交由domain Program,domain Program会根据这些重心坐标来派生顶点
在这里插入图片描述

每个顶点都会调用一次domain Program,还需要传入重心坐标,语义为**SV_DomainLocation**
在这里插入图片描述

在function 内部,创建一个VertexData,然后通过重心坐标在原始三角形中进行插值,重心坐标的xyz分别决定了第123个控制点的权重
在这里插入图片描述

后面还要对vertex data里面的每个值做这个操作,这段代码比较难写,所以直接把他封装成一个宏定义函数
在这里插入图片描述

返回值,调用顶点着色器

最后domain shader是要返回数据,要么送到几何着色器,要么送到插值器,这就需要他输出VertexOutput,也就是顶点着色器输出的v2f

要让他输出这个,就让他调用顶点着色器,这时候使用cginc的弊端就出现了,他引用不到shader中的顶点着色器

所以再创建一个cginc,把和处理顶点相关的代码都放进去,其他的再引用这个cginc

vertexProcess.cginc

#if !defined(VERTEXPROCESS_INCLUDED)
#define VERTEXPROCESS_INCLUDED

struct VertexInput
{
    float2 uv : TEXCOORD0;
    float4 vertex : POSITION;
    float3 normal : NORMAL;
    float4 tangent:TANGENT;
};

struct VertexOutput
{
    float2 uv : TEXCOORD0;
    float4 pos : SV_POSITION;
    float4 vertexWS : TEXCOORD1;
    float3 normalWS : TEXCOORD2;
    UNITY_SHADOW_COORDS(3)
    float3 vertexColor : TEXCOORD4;
    float3 tangentWS:TEXCOORD5;
    float2 barycentric : TEXCOORD9;
};

float4 _MainTex_ST;

VertexOutput vert(VertexInput v)
{
    VertexOutput o;
    
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    o.vertexWS = mul(UNITY_MATRIX_M, v.vertex);
    o.pos = mul(UNITY_MATRIX_VP, o.vertexWS);
    o.normalWS = UnityObjectToWorldNormal(v.normal);
    o.tangentWS=UnityObjectToWorldDir(v.tangent);
    TRANSFER_SHADOW(o)
    return o;
}

#endif

原本的Tessellation.cginc中的VertexData结构体其实没必要,因为他本质就是VertexInput结构体,所以删去替换

然后调用顶点着色器,返回VertexOutput
在这里插入图片描述
MyTessellation.cginc

#if !defined(MYTESSELLATION_INCLUDED)
#define MYTESSELLATION_INCLUDED
#include "vertexProcess.cginc"
struct TessellationFactors
{
    float edge[3]:SV_TessFactor;
    float inside:SV_InsideTessFactor;
};

TessellationFactors MyPatchConstantFuncition(
    InputPatch<VertexInput,3> patch
){
    TessellationFactors f;
    f.edge[0]=1;
    f.edge[1]=1;
    f.edge[2]=1;
    f.inside=1;
    return f;
}

[UNITY_domain("tri")]
[UNITY_outputcontrolpoints(3)]
[UNITY_outputtopology("triangle_cw")]
[UNITY_partitioning("integer")]
[UNITY_patchconstantfunc("MyPatchConstantFuncition")]
VertexInput MyHullProgram(InputPatch<VertexInput,3> patch,
    uint id: SV_OutputControlPointID)
{
    return patch[id];
}

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

[UNITY_domain("tri")]
VertexOutput MyDomainProgram(
    TessellationFactors factors,
    OutputPatch<VertexInput,3> patch,
    float3 barycentricCoordinates:SV_DomainLocation
)
{
    VertexInput data;
    MY_DOMAIN_POGRAM_INTERPOLATE(vertex)
    MY_DOMAIN_POGRAM_INTERPOLATE(normal)
    MY_DOMAIN_POGRAM_INTERPOLATE(tangent)
    MY_DOMAIN_POGRAM_INTERPOLATE(uv)

    return vert(data);
}
#endif

然后在shader中声明domain
在这里插入图片描述

现在依然有bug,这么做了之后,物体直接消失不见了
在这里插入图片描述

控制点

顶点着色器vert只会被调用一次,刚才已经被domain shader调用了,所以失效了

我们还要再定义一个顶点着色器,但是他实际上不用做任何事情,只需要传递参数信息就行了,这里还是把他写在vertexProcess.cginc
在这里插入图片描述

同时在shader中修改定义
在这里插入图片描述

这又会造成报错,所以再创造一个结构体TessellationControlPoint,里面的vertex的语义为INTERNALTESSPOS,其余的和vertexOutput保持一致,这个结构体的顶点信息用于为曲面细分提供控制点
在这里插入图片描述

hull Program和 MyPatchConstantFuncition和MyDomainProgram中也做修改
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

现在他可以正常显示了但是实际上并没有做曲面细分
在这里插入图片描述

代码

vertexProcess.cginc

#if !defined(VERTEXPROCESS_INCLUDED)
#define VERTEXPROCESS_INCLUDED

struct VertexInput
{
    float2 uv : TEXCOORD0;
    float4 vertex : POSITION;
    float3 normal : NORMAL;
    float4 tangent:TANGENT;
};

struct VertexOutput
{
    float2 uv : TEXCOORD0;
    float4 pos : SV_POSITION;
    float4 vertexWS : TEXCOORD1;
    float3 normalWS : TEXCOORD2;
    UNITY_SHADOW_COORDS(3)
    float3 vertexColor : TEXCOORD4;
    float3 tangentWS:TEXCOORD5;
    float2 barycentric : TEXCOORD9;
};

float4 _MainTex_ST;

VertexOutput vert(VertexInput v)
{
    VertexOutput o;
    
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    o.vertexWS = mul(UNITY_MATRIX_M, v.vertex);
    o.pos = mul(UNITY_MATRIX_VP, o.vertexWS);
    o.normalWS = UnityObjectToWorldNormal(v.normal);
    o.tangentWS=UnityObjectToWorldDir(v.tangent);
    TRANSFER_SHADOW(o)
    return o;
}
struct TessellationControlPoint {
  float4 vertex : INTERNALTESSPOS;
  float3 normal : NORMAL;
  float4 tangent : TANGENT;
  float2 uv : TEXCOORD0;
};
TessellationControlPoint MyTessellationVertexProgram(VertexInput v)
{
    TessellationControlPoint o;
    o.vertex=v.vertex;
    o.normal=v.normal;
    o.uv=v.uv;
    o.tangent=v.tangent;
    return o;
}

#endif

MyTessellation.cginc

#if !defined(MYTESSELLATION_INCLUDED)
#define MYTESSELLATION_INCLUDED
#include "vertexProcess.cginc"
struct TessellationFactors
{
    float edge[3]:SV_TessFactor;
    float inside:SV_InsideTessFactor;
};

TessellationFactors MyPatchConstantFuncition(
    InputPatch<TessellationControlPoint,3> patch
){
    TessellationFactors f;
    f.edge[0]=1;
    f.edge[1]=1;
    f.edge[2]=1;
    f.inside=1;
    return f;
}

[UNITY_domain("tri")]
[UNITY_outputcontrolpoints(3)]
[UNITY_outputtopology("triangle_cw")]
[UNITY_partitioning("integer")]
[UNITY_patchconstantfunc("MyPatchConstantFuncition")]
TessellationControlPoint MyHullProgram(InputPatch<TessellationControlPoint,3> patch,
    uint id: SV_OutputControlPointID)
{
    return patch[id];
}

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

[UNITY_domain("tri")]
VertexOutput MyDomainProgram(
    TessellationFactors factors,
    OutputPatch<TessellationControlPoint,3> patch,
    float3 barycentricCoordinates:SV_DomainLocation
)
{
    VertexInput data;
    MY_DOMAIN_POGRAM_INTERPOLATE(vertex)
    MY_DOMAIN_POGRAM_INTERPOLATE(normal)
    MY_DOMAIN_POGRAM_INTERPOLATE(tangent)
    MY_DOMAIN_POGRAM_INTERPOLATE(uv)

    return vert(data);
}
#endif

切分三角形

曲面细分规律

一个三角形patch是怎么切分的取决于他的**TessellationFactors**,如图,我们把它改成2,三角形的所有边都被切成了2份,一个三角形就会多出3个顶点
在这里插入图片描述
在这里插入图片描述

同时在三角形的中心也会增加一个顶点,然后这个顶点会和其余顶点相连,这样一来,原本一个三角形就被切分成了6个,而一个quad有2个三角形,就被切成12个
在这里插入图片描述

如果是切分3条子边,那么三角形内部还会多出3个点,构成一个内部的小三角形,然后连接
在这里插入图片描述

在这里插入图片描述

如果让他inside为7,其余的都为1

    f.edge[0] = 1;
    f.edge[1] = 1;
    f.edge[2] = 1;
    f.inside = 7;

在这里插入图片描述

简而言之,在MyPatchConstantFuncition里面edge[n]分别指示了三角形的每条边被分成几份,而inside则指示了三角形内部会多出多少个顶点
在这里插入图片描述

控制曲面细分参数

添加一个参数用于控制曲面细分的个数,注意范围,最低为1
在这里插入图片描述
在这里插入图片描述
请添加图片描述

分数因数Fractional Factors

眼下我们做的细分都是等价细分,这是因为我们的partitioning mode设置为integer,他在演示的时候比较好看,但是在细分级别切换的时候不够流畅

fractional_odd

修改为*fractional_odd,结果和integer差不多,但是额外的边缘细分将被分裂和增长,或收缩和合并 *,这样每条边就不再是每次分成等长的段,优点就在于细分级别之间切换流畅
请添加图片描述
在这里插入图片描述
请添加图片描述

fractional_even

在这里插入图片描述
请添加图片描述

对比

这两种Fractional Factors哪个好?

如图,可以看到even一开始就是经过细分了的,尽管TessellationUniform=1,但是他的最低级别强制为2

所以其实odd更加常用

具体代码

TessellationShader

Shader "Unlit/TessellationShader"
{
    Properties {
        _MainTex("MainTex",2D)="white"{}
        _DiffuseIntensity("DiffuseIntensity",float)=1
        _SpecularIntensity("SpecularIntensity",float)=1
        _Shineness("Shineness",float)=0.5

        [Header(WireFrame)]
        [Toggle]_WireFrameEnable("WireFrameEnable",float)=1
        [Toggle]_FlatShading("FlatShading",float)=1
        _WireFrameColor("WireFrameColor",Color)=(0,0,0,1)
        _WireframeSmoothing ("Wireframe Smoothing", Range(0, 10)) = 1
    _WireframeThickness ("Wireframe Thickness", Range(0, 10)) = 1

        [Header(Tessellation)]
        _TessellationUniform("TessellationUniform",Range(1,64))=1
     }
    SubShader
    {
        pass
        {
            Tags{"LightMode"="ForwardBase"}
            CGPROGRAM
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"
            #include "FlatWireFrame.cginc"
            #include "MyTessellation.cginc"
            #include "vertexProcess.cginc"
            #pragma shader_feature _WIREFRAMEENABLE_ON
            #pragma vertex MyTessellationVertexProgram
            #pragma fragment frag
            #pragma hull MyHullProgram
            #pragma domain MyDomainProgram
            #pragma geometry MyGeometryProgram
            #pragma multi_compile_fwdbase
            #pragma target 4.6

            void InitializeFragmentNormal(inout VertexOutput i)
            {
                float3 dpdx = ddx(i.vertexWS);
                float3 dpdy = ddy(i.vertexWS);
                i.normalWS = normalize(cross(dpdy, dpdx));
            }

            sampler2D _MainTex;
            float _DiffuseIntensity,_SpecularIntensity,_Shineness;
            float4 _WireFrameColor;
            float _WireframeThickness,_WireframeSmoothing,_WireFrameEnable;
            

            float4 frag(VertexOutput i) : SV_TARGET {
                
                // InitializeFragmentNormal(i);
                // 阴影
                UNITY_LIGHT_ATTENUATION(atten,i,i.vertexWS);

                float4 ambient=tex2D(_MainTex,i.uv)*atten*unity_AmbientSky;
                
                // Lambert:ambient+kd*dot(N,L)
                float3 lightDir=_WorldSpaceLightPos0;
                float3 normalDir=normalize(i.normalWS);
                float4 Lambert=ambient+_DiffuseIntensity*max(0,dot(normalDir,lightDir))*_LightColor0;

                // Blinn-Phong:ks*dot(N,H)
                float3 viewDir=normalize(_WorldSpaceCameraPos-i.vertexWS);
                float3 H=normalize(lightDir+viewDir);
                float4 BlPhong=_SpecularIntensity*pow(max(0,dot(normalDir,H)),_Shineness);

                float4 result=Lambert+BlPhong;
                #if _WIREFRAMEENABLE_ON
                    float3 barys=getBarysWithWireframe(i);
                    float3 deltas = fwidth(barys);
                    float3 smoothing = deltas * _WireframeSmoothing;
                    float3 thickness = deltas * _WireframeThickness;
                    barys = smoothstep(thickness, thickness + smoothing, barys);
                    float minBary = min(barys.x, min(barys.y, barys.z));
                    
                    float4 finalBary=lerp(_WireFrameColor, (Lambert+BlPhong), minBary);
                    result=finalBary;
                #endif
                
                return result;
            }

            ENDCG
        }
        pass
        {
            Tags{"LightMode"="ShadowCaster"}
            CGPROGRAM
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_shadowcaster
            struct VertexInput
            {
               float4 vertex:POSITION;
               float3 normal:NORMAL;
            };

            struct v2f
            {
                V2F_SHADOW_CASTER;
            };

            v2f vert(VertexInput v)
            {
                v2f o;
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
                return o;
            }

            fixed4 frag(v2f i):SV_TARGET
            {
                SHADOW_CASTER_FRAGMENT(i)
            }
            ENDCG
        }
    }
}


MyTessellation.cginc

#if !defined(MYTESSELLATION_INCLUDED)
#define MYTESSELLATION_INCLUDED
#include "vertexProcess.cginc"
struct TessellationFactors
{
    float edge[3]:SV_TessFactor;
    float inside:SV_InsideTessFactor;
};

float _TessellationUniform;
TessellationFactors MyPatchConstantFuncition(
    InputPatch<TessellationControlPoint,3> patch
){
    TessellationFactors f;
    f.edge[0]=_TessellationUniform;
    f.edge[1]=_TessellationUniform;
    f.edge[2]=_TessellationUniform;
    f.inside=_TessellationUniform;
    return f;
}

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

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

[UNITY_domain("tri")]
VertexOutput MyDomainProgram(
    TessellationFactors factors,
    OutputPatch<TessellationControlPoint,3> patch,
    float3 barycentricCoordinates:SV_DomainLocation
)
{
    VertexInput data;
    MY_DOMAIN_POGRAM_INTERPOLATE(vertex)
    MY_DOMAIN_POGRAM_INTERPOLATE(normal)
    MY_DOMAIN_POGRAM_INTERPOLATE(tangent)
    MY_DOMAIN_POGRAM_INTERPOLATE(uv)

    return vert(data);
}
#endif

vertexProcess.cginc

#if !defined(VERTEXPROCESS_INCLUDED)
#define VERTEXPROCESS_INCLUDED

struct VertexInput
{
    float2 uv : TEXCOORD0;
    float4 vertex : POSITION;
    float3 normal : NORMAL;
    float4 tangent:TANGENT;
};

struct VertexOutput
{
    float2 uv : TEXCOORD0;
    float4 pos : SV_POSITION;
    float4 vertexWS : TEXCOORD1;
    float3 normalWS : TEXCOORD2;
    UNITY_SHADOW_COORDS(3)
    float3 vertexColor : TEXCOORD4;
    float3 tangentWS:TEXCOORD5;
    float2 barycentric : TEXCOORD9;
};

float4 _MainTex_ST;

VertexOutput vert(VertexInput v)
{
    VertexOutput o;
    
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    o.vertexWS = mul(UNITY_MATRIX_M, v.vertex);
    o.pos = mul(UNITY_MATRIX_VP, o.vertexWS);
    o.normalWS = UnityObjectToWorldNormal(v.normal);
    o.tangentWS=UnityObjectToWorldDir(v.tangent);
    TRANSFER_SHADOW(o)
    return o;
}
struct TessellationControlPoint {
  float4 vertex : INTERNALTESSPOS;
  float3 normal : NORMAL;
  float4 tangent : TANGENT;
  float2 uv : TEXCOORD0;
};
TessellationControlPoint MyTessellationVertexProgram(VertexInput v)
{
    TessellationControlPoint o;
    o.vertex=v.vertex;
    o.normal=v.normal;
    o.uv=v.uv;
    o.tangent=v.tangent;
    return o;
}

#endif

注意

unity不同时支持gpu实例化和曲面细分着色器,所以把gpu实例化有关的代码删除否则报错

毕竟gpu实例化和曲面细分在性能上的影响是完全相反的

如果实在要用很多要用到曲面细分的实例化物体,那就使用LOD级别吧:LOD0级使用一个无实例化的曲面细分材质,其他的LOD级别使用实例化的、没有曲面细分的材质

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值