Unity URP 曲面细分

news2024/10/7 12:20:17

Unity URP 曲面细分

我终于变得不像我

文章目录

  • Unity URP 曲面细分
    • 1 曲面细分与镶嵌
      • 1.1 外壳着色器 Hull Shader
      • 1.2 镶嵌器阶段 Tessellator
      • 1.3 域着色器阶段 Domain Shader
    • 2 具体实现
      • 2.2 不同的细分策略
        • 2.2.1 Flat Tessellation
        • 2.2.2 PN Tessellation
        • 2.2.3 Phone Tessellation
      • 2.3 不同的细分因子
        • 2.3.1 基于相机距离
        • 2.3.2 基于屏幕占用范围
        • 2.3.3 其他
    • 3 参考文献

阅读注意:

  • 本文的URP版本为10.8.1
  • 本文会结合Tessellation.hlsl中的内容进行说明

1 曲面细分与镶嵌

曲面细分或细分曲面(Subdivision surface)是指一种通过递归算法将一个粗糙的几何网格细化的技术。镶嵌(Tessellation)则是实现曲面细分的具体手段,它能将场景中的几何物体顶点集划分为合适的渲染结构,例如三角形。在一些情况下,“镶嵌”也会被代称为“曲面细分”。

通过曲面细分,我们可以在内存中维护一个低模,根据需求再动态的增加三角网格,来节省资源。我们还能在GPU层面实现LOD,并能通过一些因素(例如:相机距离等)来调节曲面细分的程度,让近处呈现高模,远处呈现低模。

在Direct3D 11之前,要想达到这种操作,只能在CPU阶段细化网格,再传入GPU中,比较低效。于是,在Direct3D 11中引入曲面细分阶段(Tessllation Stage),将任务交给了GPU:
在这里插入图片描述
曲面细分阶段分为三个阶段:外壳着色器(Hull Shader)、镶嵌器阶段(Tessellator)和域着色器阶段(Domain Shader)。

1.1 外壳着色器 Hull Shader

Hull Shder 实际上是由两个阶段(Phase)组成:常量外壳着色器(Constant Hull Shader)和 控制点外壳着色器(Control point hull shader)。

Constant Hull Shader 会对每一个面片进行处理,其主要任务是输出网格的曲面细分因子(Tessellation Factor),细分因子用于指导面片细分的份数。我们这里以三角面片(triangle patch)为例:

// 三角面片
struct PatchTess {  
    float edgeFactor[3] : SV_TESSFACTOR;
    float insideFactor  : SV_INSIDETESSFACTOR;
};

PatchTess PatchConstant (InputPatch<VertexOut,3> patch, uint patchID : SV_PrimitiveID){ 
    PatchTess o;
    o.edgeFactor[0] = 4;
    o.edgeFactor[1] = 4; 
    o.edgeFactor[2] = 4;
    o.insideFactor  = 4;
    return o;
}

常量外壳着色器会以面片的所有顶点(或控制点)为输入,InputPatch<VertexOut,3>其中VertexOut是顶点着色器输出的结构体,后面的数字3代表一共传入三角面片的三个顶点数据(以此类推,如果你传入是四边形面片,那么这里应该是InputPatch<VertexOut,4>)。

代码中还包含了三个语义:

  • SV_PrimitiveID:提供传入面片的ID值。此处传入的参数patchID可以根据具体需求进行操作。
  • SV_TESSFACTOR:用于标识对应边缘的细分因子
  • SV_INSIDETESSFACTOR:用于标识内部的细分因子

由于我们传入的是三角面片,那么自然会输出3个边缘细分因子和1个内部细分因子。如果传入的是四角面片,那么PatchTess的结构如下:

// 四角面片
struct PatchTess {  
    float edgeFactor[4] : SV_TESSFACTOR; // 分别对应四角面片的四个边
    float insideFactor[2]  : SV_INSIDETESSFACTOR; // 分别对应内部细分的列数与行数
};

此处,我们把细分因子全部固定为了4,但实际操作中可以根据不同的策略灵活地调整因子的大小,以实现LOD的效果,例如后面会提到基于相机位置来调整因子大小。

Control Point Hull Shader 可以用来改变每个输出顶点的位置等信息,例如将一个三角形变为一个3次的贝塞尔三角面片。

[domain("tri")]    
[partitioning("integer")]    
[outputtopology("triangle_cw")]   
[patchconstantfunc("PatchConstant")]   
[outputcontrolpoints(3)]             
[maxtessfactor(64.0f)]        
HullOut ControlPoint (InputPatch<VertexOut,3> patch,uint id : SV_OutputControlPointID){  
    HullOut o;
    o.positionOS = patch[id].positionOS;
    o.texcoord = patch[id].texcoord; 
    return o;
}

不过了为了方便说明,这里函数并没做过多的事情,只是将值传递了一下。我们一步一步来看一下这个复杂的写法:

  • domain:面片类型。参数有三角面皮tri、四角面片quad、等值线isoline

  • partitioning:曲面细分模式。参数有integerfractional_evenfractional_odd

    integer

    指新顶点的添加只取决于细分的整数部分,在Opengl中对应的模式是equal_spacing
    在这里插入图片描述

    这个模式由于只取整数,所以当细分级别改变时,图形会发生明显的突变(pop)。例如:当你靠经时,从正方体突然变为球体等。

    fractional_even

    向上取最近的偶数n,将整段切割为n-2个相等长度的部分,和两端较短的部分。
    在这里插入图片描述

    fractional_odd

    向上取最近的奇数n,将整段切割为n-2个相等长度的部分,和两端较短的部分。
    在这里插入图片描述

  • outputtopology:细分创建的三角面片绕序。参数有顺时针triangle_cw、逆时针triangle_ccw

  • patchconstantfunc:指定常量外壳着色器的函数名。

  • outputcontrolpoints:外壳着色器的执行次数,每执行一次就会生成一个控制点。这个数量不一定要和输入的控制点数保持一致,例如输入4个控制点可以输出16个控制点。

  • maxtessfactor:程序会使用到的最大细分因子。Direct3D 11 支持的最大细分因子为64。

  • SV_OutputControlPointID:该语义标识出当前正在操作的控制点索引ID。

更多的细节请查阅Tessellation Stages。
在这里插入图片描述
常量外壳着色器和控制点外壳着色器两个阶段由硬件并行运行。Constant Hull Shader 对每一个面片运行一次,输出边缘细分因子等信息。Control Point Hull Shader 对每一个控制点运行一次,并输出对应的或衍生的控制点。

1.2 镶嵌器阶段 Tessellator

这一阶段我们无法对其做出任何控制,全程由硬件控制。在这一阶段,硬件根据之前曲面细分因子对面片走出细分操作。

我们以三角面片为例,选择细分模式为integer,增大边缘细分因子,可以看到三角形的每条边都分割为了对应的数量。
在这里插入图片描述

增大内部细分因子,你可以看到内部镶嵌的规则有点不直观。因为内部因子的数量并不直接对应内部三角形的数量。
在这里插入图片描述

内三角环的数目几乎是细分数量的一半,当细分是偶数的话,最内层将会是一个顶点。

关于细分的具体规则可以查阅Opengl官网的解释Tessellation 。

1.3 域着色器阶段 Domain Shader

域着色器相当于是每一个控制的“顶点着色器”。

在这里插入图片描述

就和普通的顶点着色器要做的差不多,我们需要计算每一个控制点的顶点位置等信息。

struct DomainOut
{
    float4  positionCS      : SV_POSITION;
    float3  color           : TEXCOORD0; 
};


[domain("tri")]      
DomainOut DomainShader (PatchTess tessFactors, const OutputPatch<HullOut,3> patch, float3 bary : SV_DOMAINLOCATION)
{
    float3 positionOS = patch[0].positionOS * bary.x + patch[1].positionOS * bary.y + patch[2].positionOS * bary.z; 
    float2 texcoord   = patch[0].texcoord * bary.x + patch[1].texcoord * bary.y + patch[2].texcoord * bary.z;

    DomainOut output;
    output.positionCS = TransformObjectToHClip(positionOS);
    output.texcoord = texcoord;

    return output; 
} 

这里值得注意的是,我们是怎么获取细分后的顶点位置。以三角面片为例,我们传入基础的三个控制点patch信息,然后用语义SV_DOMAINLOCATION细分后的顶点参数坐标,此处一个质心坐标(u, v, w)。(如果是四角面片,这个值会是个二维坐标(u,v))

有了顶点的质心坐标,我们就可以插值获得对应的顶点信息。

经过域着色器的处理,数据会按照流水线传递给几何着色器和片元着色器。

2 具体实现

上面简单介绍了一下,曲面细分阶段的各个部分的负责的内容。接下来,我们将他们实际应用起来!

2.2 不同的细分策略

2.2.1 Flat Tessellation

我们只需要把上面的代码组装一下,就可以获得一个最简单的平面镶嵌形式。

Shader "Tessellation/Flat Tessellation"
{
    Properties
    {
        [NoScaleOffset]_BaseMap ("Base Map", 2D) = "white" {}  
        
        [Header(Tess)][Space]
     
        [KeywordEnum(integer, fractional_even, fractional_odd)]_Partitioning ("Partitioning Mode", Float) = 0
        [KeywordEnum(triangle_cw, triangle_ccw)]_Outputtopology ("Outputtopology Mode", Float) = 0
        _EdgeFactor ("EdgeFactor", Range(1,8)) = 4 
        _InsideFactor ("InsideFactor", Range(1,8)) = 4 
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
         
        Pass
        { 
            HLSLPROGRAM
            #pragma target 4.6 
            #pragma vertex FlatTessVert
            #pragma fragment FlatTessFrag 
            #pragma hull FlatTessControlPoint
            #pragma domain FlatTessDomain
             
            #pragma multi_compile _PARTITIONING_INTEGER _PARTITIONING_FRACTIONAL_EVEN _PARTITIONING_FRACTIONAL_ODD 
            #pragma multi_compile _OUTPUTTOPOLOGY_TRIANGLE_CW _OUTPUTTOPOLOGY_TRIANGLE_CCW 
   
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" 

            CBUFFER_START(UnityPerMaterial) 
            float _EdgeFactor; 
            float _InsideFactor; 
            CBUFFER_END

            TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); 

            struct Attributes
            {
                float3 positionOS   : POSITION; 
                float2 texcoord     : TEXCOORD0;
    
            };

            struct VertexOut{
                float3 positionOS : INTERNALTESSPOS; 
                float2 texcoord : TEXCOORD0;
            };
             
            struct PatchTess {  
                float edgeFactor[3] : SV_TESSFACTOR;
                float insideFactor  : SV_INSIDETESSFACTOR;
            };

            struct HullOut{
                float3 positionOS : INTERNALTESSPOS; 
                float2 texcoord : TEXCOORD0;
            };

            struct DomainOut
            {
                float4  positionCS      : SV_POSITION;
                float2  texcoord        : TEXCOORD0; 
            };


            VertexOut FlatTessVert(Attributes input){ 
                VertexOut o;
                o.positionOS = input.positionOS; 
                o.texcoord   = input.texcoord;
                return o;
            }
   
   
            PatchTess PatchConstant (InputPatch<VertexOut,3> patch, uint patchID : SV_PrimitiveID){ 
                PatchTess o;
                o.edgeFactor[0] = _EdgeFactor;
                o.edgeFactor[1] = _EdgeFactor; 
                o.edgeFactor[2] = _EdgeFactor;
                o.insideFactor  = _InsideFactor;
                return o;
            }
 
            [domain("tri")]   
            #if _PARTITIONING_INTEGER
            [partitioning("integer")] 
            #elif _PARTITIONING_FRACTIONAL_EVEN
            [partitioning("fractional_even")] 
            #elif _PARTITIONING_FRACTIONAL_ODD
            [partitioning("fractional_odd")]    
            #endif 
 
            #if _OUTPUTTOPOLOGY_TRIANGLE_CW
            [outputtopology("triangle_cw")] 
            #elif _OUTPUTTOPOLOGY_TRIANGLE_CCW
            [outputtopology("triangle_ccw")] 
            #endif

            [patchconstantfunc("PatchConstant")] 
            [outputcontrolpoints(3)]                 
            [maxtessfactor(64.0f)]                 
            HullOut FlatTessControlPoint (InputPatch<VertexOut,3> patch,uint id : SV_OutputControlPointID){  
                HullOut o;
                o.positionOS = patch[id].positionOS;
                o.texcoord = patch[id].texcoord; 
                return o;
            }
 
  
            [domain("tri")]      
            DomainOut FlatTessDomain (PatchTess tessFactors, const OutputPatch<HullOut,3> patch, float3 bary : SV_DOMAINLOCATION)
            {  
                float3 positionOS = patch[0].positionOS * bary.x + patch[1].positionOS * bary.y + patch[2].positionOS * bary.z; 
	            float2 texcoord   = patch[0].texcoord * bary.x + patch[1].texcoord * bary.y + patch[2].texcoord * bary.z;
                   
                DomainOut output;
                output.positionCS = TransformObjectToHClip(positionOS);
                output.texcoord = texcoord;
                return output; 
            }
 

            half4 FlatTessFrag(DomainOut input) : SV_Target{   
                half3 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.texcoord).rgb;
                return half4(color, 1.0); 
            }
              
            ENDHLSL
        }
    }
}

代码通过#pragma hull#pragma domain来指定外壳着色器 和 域着色器。并定义了枚举的宏定义,方便我们在这几个模式之前切换。
在这里插入图片描述

平面镶嵌只是线性插值位置信息,细分后的图案只比之前多了一些三角面片,单独使用并不能平滑模型。它通常和置换贴图(Displacement Map)配合使用,来创建一个凹凸不平的平面。

2.2.2 PN Tessellation

在之前的尝试中,我们没有过多地设计外壳着色器的Control Point阶段。这里我们可以尝试一下不同的控制点策略。

在外壳着色器阶段,把一个三角面片(3个控制点)转换为一个3次贝塞尔三角面片(Cubic Bezier Triangle Patch,一种具有10个控制点的面片),这种策略称为 Curved Point-Normal Triangles(PN triangles)。 它不同于Flat Tessellation,即使没有置换贴图,也能实现改变模型形状,平滑轮廓的作用。
在这里插入图片描述

由于控制点的增多,在Hull Shader输出时每个顶点需要多携带两个顶点信息(中心控制点b111可以直接推算出来),例如:b030 可能需要携带b021和b012的顶点信息。

按照这个策略,我们重新设计一下代码。

struct HullOut{
    float3 positionOS : INTERNALTESSPOS;
    float3 normalOS   : NORMAL;
    float2 texcoord   : TEXCOORD0;
    float3 positionOS1 : TEXCOORD1;	// 三角片元每个顶点多携带两个顶点信息
    float3 positionOS2 : TEXCOORD2;
}; 

float3 ComputeCP(float3 pA, float3 pB, float3 nA){
    return (2 * pA + pB - dot((pB - pA), nA) * nA) / 3.0f;
}

[domain("tri")]    
[partitioning("integer")]   
[outputtopology("triangle_cw")]   
[patchconstantfunc("PatchConstant")]     
[outputcontrolpoints(3)]                 
[maxtessfactor(64.0f)] 
HullOut PNTessControlPoint(InputPatch<VertexOut,3> patch,uint id : SV_OutputControlPointID){ 
    HullOut output;
    const uint nextCPID = id < 2 ? id + 1 : 0;

    output.positionOS    = patch[id].positionOS;
    output.normalOS      = patch[id].normalOS;
    output.texcoord      = patch[id].texcoord;

    output.positionOS1 = ComputeCP(patch[id].positionOS, patch[nextCPID].positionOS, patch[id].normalOS);
    output.positionOS2 = ComputeCP(patch[nextCPID].positionOS, patch[id].positionOS, patch[nextCPID].normalOS);

    return output;
}

输出结构体HullOut中用positionOS1 和 positionOS2 两个位置来存放多出来的控制点信息。通过一个简单的推算得到相邻顶点的ID——nextCPID,有了当前顶线和相邻顶点,就能推算出两点之间的多出来的控制点。
在这里插入图片描述

ComputeCP函数的原理其实就是一个简单的几何关系。以上图为例(图片来自:CurvedPNTriangles),那么各个点之前存在以下关系:
在这里插入图片描述

再来看看域着色器阶段:

[domain("tri")]      
DomainOut PNTessDomain (PatchTess tessFactors, const OutputPatch<HullOut,3> patch, float3 bary : SV_DOMAINLOCATION)
{ 
    float u = bary.x;
    float v = bary.y;
    float w = bary.z;

    float uu = u * u;
    float vv = v * v;
    float ww = w * w;
    float uu3 = 3 * uu;
    float vv3 = 3 * vv;
    float ww3 = 3 * ww;

    float3 b300 = patch[0].positionOS;
    float3 b210 = patch[0].positionOS1;
    float3 b120 = patch[0].positionOS2;
    float3 b030 = patch[1].positionOS;
    float3 b021 = patch[1].positionOS1;
    float3 b012 = patch[1].positionOS2;
    float3 b003 = patch[2].positionOS;
    float3 b102 = patch[2].positionOS1;
    float3 b201 = patch[2].positionOS2;  

    float3 E = (b210 + b120 + b021 + b012 + b102 + b201) / 6.0;
    float3 V = (b003 + b030 + b300) / 3.0; 
    float3 b111 = E + (E - V) / 2.0f;  
	// 插值获得细分后的顶点位置
    float3 positionOS = b300 * uu * u + b030 * vv * v + b003 * ww * w 
        + b210 * uu3 * v 
        + b120 * vv3 * u
        + b021 * vv3 * w
        + b012 * ww3 * v
        + b102 * ww3 * u
        + b201 * uu3 * w
        + b111 * 6.0 * w * u * v;
	// 此处简化了法线的计算
    float3 normalOS = patch[0].normalOS * u 
        + patch[1].normalOS * v
        + patch[2].normalOS * w;
    normalOS = normalize(normalOS);

    float2 texcoord = patch[0].texcoord * u
        + patch[1].texcoord * v
        + patch[2].texcoord * w;

    DomainOut output; 
    output.positionCS = TransformObjectToHClip(positionOS);  
    output.normalWS = TransformObjectToWorldNormal(normalOS);
    output.uv = texcoord;
    return output; 
}

控制点增加到10个后,利用质心坐标插值的运算复杂度也自然就上去了。所以,在处理法线时,这里采用了最简单的三个控制点插值获取的方式。

这里贴一下整段代码。

Shader "Tessellation/PN Tri Tessellation + Cubic Bezier Triangle Patch"
{
    Properties
    {
        [NoScaleOffset]_BaseMap ("Base Map", 2D) = "white" {}  
        
        [Header(Tess)][Space]
     
        [KeywordEnum(integer, fractional_even, fractional_odd )]_Partitioning ("Partitioning Mode", Float) = 2
        [KeywordEnum(triangle_cw, triangle_ccw)]_Outputtopology ("Outputtopology Mode", Float) = 0
        _EdgeFactor ("EdgeFactor", Range(1, 8)) = 4 
        _InsideFactor ("InsideFactor", Range(1, 8)) = 4 
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
         
        Pass
        { 
            HLSLPROGRAM
            #pragma target 4.6 
            #pragma vertex PNTessVert
            #pragma fragment PNTessFrag 
            #pragma hull PNTessControlPoint
            #pragma domain PNTessDomain
             
            #pragma multi_compile _PARTITIONING_INTEGER _PARTITIONING_FRACTIONAL_EVEN _PARTITIONING_FRACTIONAL_ODD   
            #pragma multi_compile _OUTPUTTOPOLOGY_TRIANGLE_CW _OUTPUTTOPOLOGY_TRIANGLE_CCW 
             
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" 
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

            CBUFFER_START(UnityPerMaterial) 
            float _EdgeFactor; 
            float _InsideFactor; 
            CBUFFER_END

            TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); 


            struct Attributes
            {
                float3 positionOS   : POSITION; 
                float3 normalOS     : NORMAL;
                float2 texcoord     : TEXCOORD0;
    
            };

            struct VertexOut{
                float3 positionOS : INTERNALTESSPOS;
                float3 normalOS   : NORMAL;
                float2 texcoord   : TEXCOORD0; 
            }; 

            struct PatchTess {  
                float edgeFactor[3] : SV_TESSFACTOR; 
                float insideFactor  : SV_INSIDETESSFACTOR; 
            };

            struct HullOut{
                float3 positionOS : INTERNALTESSPOS;
                float3 normalOS   : NORMAL;
                float2 texcoord   : TEXCOORD0;
                float3 positionOS1 : TEXCOORD1;
                float3 positionOS2 : TEXCOORD2;
            }; 


            struct DomainOut
            {
                float4 positionCS      : SV_POSITION;
                float3 normalWS        : TEXCOORD0; 
                float2 uv              : TEXCOORD1;
            };


            VertexOut PNTessVert(Attributes input){ 
                VertexOut o = (VertexOut)0;
                o.positionOS = input.positionOS; 
                o.normalOS   = input.normalOS;
                o.texcoord   = input.texcoord;
                return o;
            } 
            
            PatchTess PatchConstant (InputPatch<VertexOut,3> patch, uint patchID : SV_PrimitiveID){
 
                PatchTess o; 
     
                o.edgeFactor[0] = _EdgeFactor;
                o.edgeFactor[1] = _EdgeFactor; 
                o.edgeFactor[2] = _EdgeFactor;
                o.insideFactor  = _InsideFactor;
                return o;
            }

 
            float3 ComputeCP(float3 pA, float3 pB, float3 nA){
                return (2 * pA + pB - dot((pB - pA), nA) * nA) / 3.0f;
            }
 
            [domain("tri")]   
            #if _PARTITIONING_INTEGER
            [partitioning("integer")] 
            #elif _PARTITIONING_FRACTIONAL_EVEN
            [partitioning("fractional_even")] 
            #elif _PARTITIONING_FRACTIONAL_ODD
            [partitioning("fractional_odd")]    
            #endif 
 
            #if _OUTPUTTOPOLOGY_TRIANGLE_CW
            [outputtopology("triangle_cw")] 
            #elif _OUTPUTTOPOLOGY_TRIANGLE_CCW
            [outputtopology("triangle_ccw")] 
            #endif

            [patchconstantfunc("PatchConstant")]     
            [outputcontrolpoints(3)]                 
            [maxtessfactor(64.0f)] 

            HullOut PNTessControlPoint (InputPatch<VertexOut,3> patch,uint id : SV_OutputControlPointID){ 
                HullOut output;
                const uint nextCPID = id < 2 ? id + 1 : 0;
    
                output.positionOS    = patch[id].positionOS;
                output.normalOS      = patch[id].normalOS;
                output.texcoord      = patch[id].texcoord;

                output.positionOS1 = ComputeCP(patch[id].positionOS, patch[nextCPID].positionOS, patch[id].normalOS);
                output.positionOS2 = ComputeCP(patch[nextCPID].positionOS, patch[id].positionOS, patch[nextCPID].normalOS);
      
                return output;
            }
   
 
            [domain("tri")]      
            DomainOut PNTessDomain (PatchTess tessFactors, const OutputPatch<HullOut,3> patch, float3 bary : SV_DOMAINLOCATION)
            { 
                float u = bary.x;
                float v = bary.y;
                float w = bary.z;

                float uu = u * u;
                float vv = v * v;
                float ww = w * w;
                float uu3 = 3 * uu;
                float vv3 = 3 * vv;
                float ww3 = 3 * ww;

                float3 b300 = patch[0].positionOS;
                float3 b210 = patch[0].positionOS1;
                float3 b120 = patch[0].positionOS2;
                float3 b030 = patch[1].positionOS;
                float3 b021 = patch[1].positionOS1;
                float3 b012 = patch[1].positionOS2;
                float3 b003 = patch[2].positionOS;
                float3 b102 = patch[2].positionOS1;
                float3 b201 = patch[2].positionOS2;  

                float3 E = (b210 + b120 + b021 + b012 + b102 + b201) / 6.0;
                float3 V = (b003 + b030 + b300) / 3.0; 
                float3 b111 = E + (E - V) / 2.0f;    
  
                float3 positionOS = b300 * uu * u + b030 * vv * v + b003 * ww * w 
                                + b210 * uu3 * v 
                                + b120 * vv3 * u
                                + b021 * vv3 * w
                                + b012 * ww3 * v
                                + b102 * ww3 * u
                                + b201 * uu3 * w
                                + b111 * 6.0 * w * u * v;
   
                float3 normalOS = patch[0].normalOS * u 
                                + patch[1].normalOS * v
                                + patch[2].normalOS * w;
                normalOS = normalize(normalOS);

                float2 texcoord = patch[0].texcoord * u
                                + patch[1].texcoord * v
                                + patch[2].texcoord * w;
       
                DomainOut output; 
                output.positionCS = TransformObjectToHClip(positionOS);  
                output.normalWS = TransformObjectToWorldNormal(normalOS);
                output.uv = texcoord;
                return output; 
            }
 
            half4 PNTessFrag(DomainOut input) : SV_Target{  
     
                Light mainLight = GetMainLight();
                half3 baseColor = SAMPLE_TEXTURE2D(_BaseMap,sampler_BaseMap,input.uv).xyz;   
      
                half NdotL = saturate(dot(input.normalWS, mainLight.direction) * 0.5 + 0.5);
                half3 diffuseColor = mainLight.color * NdotL;

                return half4(diffuseColor * baseColor ,1.0); 
            } 
            ENDHLSL
        }
    }
}
 

我这里随便掰了一下三角面片的法线,Shader运行效果如下:
在这里插入图片描述

然而,目前的做法是有缺陷的,在面对一些相同位置有不同法线的模型时,细分后会造成模型边缘的不连续,形成裂缝(Crack)
在这里插入图片描述

为了解决这一问题,NVIDIA它们采用了一种改进的策略 PN-AEN (Point-Normal Triangles Using Adjacent Edge Normals)。这里摆出相关链接:

  • My Tessellation Has Cracks
  • PN-AEN-Triangles-Whitepaper

这种策略会在数据预处理阶段生成一个携带有邻接信息的索引缓冲区。在细分时,就可以通过邻接顶点信息,来消除裂缝。
在这里插入图片描述

但Unity中似乎不方便生成邻接信息的索引缓冲区,要生成这个数据可能得手动操作一下。我看到Asset Store中有人实现过PN-AEN Crack-Free Tessellation Displacement,有兴趣的可以看看。(或者直接让美术硬凹!)

2.2.3 Phone Tessellation

Phone Tessellation 和 PN Tessellation 类似,也是为了到达平滑模型轮廓的目的。只不过Phone镶嵌可以用更少的计算达到类似的效果。更多内容推荐阅读Phone Tessellation。

其核心思想是,将生成的顶点 P P P投影到三个顶点的切平面上,然后再用质心坐标插值这些投影点,最后得到顶点 P ∗ P^* P
在这里插入图片描述

在代码上,我们需要修改域着色器,下面贴出整段代码:

Shader "Tessellation/Flat Tri Tessellation + Phone Tess"
{
    Properties
    {
        [NoScaleOffset]_BaseMap ("Base Map", 2D) = "white" {}  
        
        [Header(Tess)][Space]
     
        [KeywordEnum(integer, fractional_even, fractional_odd)]_Partitioning ("Partitioning Mode", Float) = 2
        [KeywordEnum(triangle_cw, triangle_ccw)]_Outputtopology ("Outputtopology Mode", Float) = 0
        _EdgeFactor ("EdgeFactor", Range(1,16)) = 4 
        _InsideFactor ("InsideFactor", Range(1,16)) = 4  
        _PhoneShape ("PhoneShape", Range(0, 1)) = 0.5
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
         
        Pass
        { 
            HLSLPROGRAM
            #pragma target 4.6 
            #pragma vertex PhoneTriTessVert
            #pragma fragment PhoneTriTessFrag 
            #pragma hull PhoneTriTessControlPoint
            #pragma domain PhoneTriTessDomain
             
            #pragma multi_compile _PARTITIONING_INTEGER _PARTITIONING_FRACTIONAL_EVEN _PARTITIONING_FRACTIONAL_ODD 
            #pragma multi_compile _OUTPUTTOPOLOGY_TRIANGLE_CW _OUTPUTTOPOLOGY_TRIANGLE_CCW 

 
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/GeometricTools.hlsl"
            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Tessellation.hlsl"

            CBUFFER_START(UnityPerMaterial) 
            float _EdgeFactor;  
            float _InsideFactor; 
            float _PhoneShape;
            CBUFFER_END

            TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); 

            struct Attributes
            {
                float3 positionOS   : POSITION; 
                float3 normalOS     : NORMAL;
                float2 texcoord     : TEXCOORD0;
    
            };

            struct VertexOut{
                float3 positionWS : INTERNALTESSPOS; 
                float2 texcoord : TEXCOORD0;
                float3 normalWS : TEXCOORD1;
            };
             
            struct PatchTess {  
                float edgeFactor[3] : SV_TESSFACTOR;
                float insideFactor  : SV_INSIDETESSFACTOR;
            };

            struct HullOut{
                float3 positionWS : INTERNALTESSPOS; 
                float2 texcoord : TEXCOORD0;
                float3 normalWS : TEXCOORD1;
            };

            struct DomainOut
            {
                float4  positionCS      : SV_POSITION;
                float2  texcoord        : TEXCOORD0; 
            };


            VertexOut PhoneTriTessVert(Attributes input){ 
                VertexOut o;
                o.positionWS = TransformObjectToWorld(input.positionOS);  
                o.normalWS   = TransformObjectToWorldNormal(input.normalOS);
                o.texcoord   = input.texcoord;
                return o;
            }
   
   
            PatchTess PatchConstant (InputPatch<VertexOut,3> patch, uint patchID : SV_PrimitiveID){ 
                PatchTess o;
                o.edgeFactor[0] = _EdgeFactor;
                o.edgeFactor[1] = _EdgeFactor;
                o.edgeFactor[2] = _EdgeFactor;

                o.insideFactor  = _InsideFactor;
                return o;
            }
 
            [domain("tri")]   
            #if _PARTITIONING_INTEGER
            [partitioning("integer")] 
            #elif _PARTITIONING_FRACTIONAL_EVEN
            [partitioning("fractional_even")] 
            #elif _PARTITIONING_FRACTIONAL_ODD
            [partitioning("fractional_odd")]    
            #endif 
 
            #if _OUTPUTTOPOLOGY_TRIANGLE_CW
            [outputtopology("triangle_cw")] 
            #elif _OUTPUTTOPOLOGY_TRIANGLE_CCW
            [outputtopology("triangle_ccw")] 
            #endif

            [patchconstantfunc("PatchConstant")] 
            [outputcontrolpoints(3)]                 
            [maxtessfactor(64.0f)]                 
            HullOut PhoneTriTessControlPoint (InputPatch<VertexOut,3> patch,uint id : SV_OutputControlPointID){  
                HullOut o;
                o.positionWS = patch[id].positionWS;
                o.texcoord = patch[id].texcoord; 
                o.normalWS = patch[id].normalWS;
                return o;
            }
 
  
            [domain("tri")]      
            DomainOut PhoneTriTessDomain (PatchTess tessFactors, const OutputPatch<HullOut,3> patch, float3 bary : SV_DOMAINLOCATION)
            {  
                float3 positionWS = patch[0].positionWS * bary.x + patch[1].positionWS * bary.y + patch[2].positionWS * bary.z; 
                positionWS = PhongTessellation(positionWS, patch[0].positionWS, patch[1].positionWS, patch[2].positionWS, patch[0].normalWS, patch[1].normalWS, patch[2].normalWS, bary, _PhoneShape);

                float2 texcoord   = patch[0].texcoord * bary.x + patch[1].texcoord * bary.y + patch[2].texcoord * bary.z;
    
                DomainOut output;
                output.positionCS = TransformWorldToHClip(positionWS);
                output.texcoord = texcoord;
    
                return output; 
            }
 

            half4 PhoneTriTessFrag(DomainOut input) : SV_Target{ 
                half3 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.texcoord).rgb;
                return half4(color, 1.0); 
            } 


            ENDHLSL
        }
    }
}

PhongTessellation函数的内容如下:

// ===================== GeometricTools.hlsl ======================

float3 ProjectPointOnPlane(float3 position, float3 planePosition, float3 planeNormal)
{
    return position - (dot(position - planePosition, planeNormal) * planeNormal);
}


// ===================== Tessellation.hlsl =========================

// p0, p1, p2 triangle world position
// p0, p1, p2 triangle world vertex normal
real3 PhongTessellation(real3 positionWS, real3 p0, real3 p1, real3 p2, real3 n0, real3 n1, real3 n2, real3 baryCoords, real shape)
{
    // 分别计算三个切平面的投影点
    real3 c0 = ProjectPointOnPlane(positionWS, p0, n0);
    real3 c1 = ProjectPointOnPlane(positionWS, p1, n1);
    real3 c2 = ProjectPointOnPlane(positionWS, p2, n2);
	
    // 利用质心坐标插值得到最终顶点位置
    real3 phongPositionWS = baryCoords.x * c0 + baryCoords.y * c1 + baryCoords.z * c2;
	
    // 通过shape 控制平滑程度
    return lerp(positionWS, phongPositionWS, shape);
}

Shader的运行效果如下:
在这里插入图片描述

同样,Phone Tessellation 和 PN Tessellation 一样都是以来模型的法线来平滑表面,如果一个顶点位置有多个法线方向,那也是会出现裂缝情况的。

2.3 不同的细分因子

到此为止,我们的细分因子都是受Shader面板统一控制。接下来,我们会运用算法灵活调整细分因子。

2.3.1 基于相机距离

为了让距离相机近的位置细分程度高一点,现将常量外壳着色器的代码调整如下:

   
PatchTess PatchConstant (InputPatch<VertexOut,3> patch, uint patchID : SV_PrimitiveID){ 
    PatchTess o;
    float3 cameraPosWS = GetCameraPositionWS();
    real3 triVectexFactors =  GetDistanceBasedTessFactor(patch[0].positionWS, patch[1].positionWS, patch[2].positionWS, cameraPosWS, _TessMinDist, _TessMinDist + _FadeDist);

    float4 tessFactors = _EdgeFactor * CalcTriTessFactorsFromEdgeTessFactors(triVectexFactors);
    o.edgeFactor[0] = max(1.0, tessFactors.x);
    o.edgeFactor[1] = max(1.0, tessFactors.y);
    o.edgeFactor[2] = max(1.0, tessFactors.z);

    o.insideFactor  = max(1.0, tessFactors.w);
    return o;
}

这里获取了相机的位置,并将三个顶点的世界空间坐标传入GetDistanceBasedTessFactor函数中:

// Tessellation.hlsl
real3 GetDistanceBasedTessFactor(real3 p0, real3 p1, real3 p2, real3 cameraPosWS, real tessMinDist, real tessMaxDist)
{
    real3 edgePosition0 = 0.5 * (p1 + p2);
    real3 edgePosition1 = 0.5 * (p0 + p2);
    real3 edgePosition2 = 0.5 * (p0 + p1);

    // In case camera-relative rendering is enabled, 'cameraPosWS' is statically known to be 0,
    // so the compiler will be able to optimize distance() to length().
    real dist0 = distance(edgePosition0, cameraPosWS);
    real dist1 = distance(edgePosition1, cameraPosWS);
    real dist2 = distance(edgePosition2, cameraPosWS);

    // The saturate will handle the produced NaN in case min == max
    real fadeDist = tessMaxDist - tessMinDist;
    real3 tessFactor;
    tessFactor.x = saturate(1.0 - (dist0 - tessMinDist) / fadeDist);
    tessFactor.y = saturate(1.0 - (dist1 - tessMinDist) / fadeDist);
    tessFactor.z = saturate(1.0 - (dist2 - tessMinDist) / fadeDist);

    return tessFactor;
}

函数取每一边中点与相机的距离,从最小细分距离tessMinDist开始,到最远细分距离tessMaxDist,细分因子逐渐衰减到0。

取得边缘细分因子后,内部细分因子就简单地处理为三条边的平均就行了。

// Tessellation.hlsl
real4 CalcTriTessFactorsFromEdgeTessFactors(real3 triVertexFactors)
{
    real4 tess;
    tess.x = triVertexFactors.x;
    tess.y = triVertexFactors.y;
    tess.z = triVertexFactors.z;
    tess.w = (triVertexFactors.x + triVertexFactors.y + triVertexFactors.z) / 3.0;

    return tess;
}

下面贴出完整代码:

Shader "Tessellation/Flat Tri Tessellation + Distance-Based"
{
    Properties
    {
        [NoScaleOffset]_BaseMap ("Base Map", 2D) = "white" {}  
        
        [Header(Tess)][Space]
     
        [KeywordEnum(integer, fractional_even, fractional_odd)]_Partitioning ("Partitioning Mode", Float) = 2
        [KeywordEnum(triangle_cw, triangle_ccw)]_Outputtopology ("Outputtopology Mode", Float) = 0
        [IntRange]_EdgeFactor ("EdgeFactor", Range(1,8)) = 4
        _TessMinDist ("TessMinDist", Range(0,10)) = 10.0
        _FadeDist ("FadeDist", Range(1,20)) = 15.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
         
        Pass
        { 
            HLSLPROGRAM
            #pragma target 4.6 
            #pragma vertex DistanceBasedTessVert
            #pragma fragment DistanceBasedTessFrag 
            #pragma hull DistanceBasedTessControlPoint
            #pragma domain DistanceBasedTessDomain
             
            #pragma multi_compile _PARTITIONING_INTEGER _PARTITIONING_FRACTIONAL_EVEN _PARTITIONING_FRACTIONAL_ODD 
            #pragma multi_compile _OUTPUTTOPOLOGY_TRIANGLE_CW _OUTPUTTOPOLOGY_TRIANGLE_CCW 

 
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/GeometricTools.hlsl"
            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Tessellation.hlsl"

            CBUFFER_START(UnityPerMaterial) 
            float _EdgeFactor;  
            float _TessMinDist;
            float _FadeDist;
            CBUFFER_END

            TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); 

            struct Attributes
            {
                float3 positionOS   : POSITION; 
                float2 texcoord     : TEXCOORD0;
    
            };

            struct VertexOut{
                float3 positionWS : INTERNALTESSPOS; 
                float2 texcoord : TEXCOORD0;
            };
             
            struct PatchTess {  
                float edgeFactor[3] : SV_TESSFACTOR;
                float insideFactor  : SV_INSIDETESSFACTOR;
            };

            struct HullOut{
                float3 positionWS : INTERNALTESSPOS; 
                float2 texcoord : TEXCOORD0;
            };

            struct DomainOut
            {
                float4  positionCS      : SV_POSITION;
                float2  texcoord        : TEXCOORD0; 
            };


            VertexOut DistanceBasedTessVert(Attributes input){ 
                VertexOut o;
                o.positionWS = TransformObjectToWorld(input.positionOS);  
                o.texcoord   = input.texcoord;
                return o;
            }
   
   
            PatchTess PatchConstant (InputPatch<VertexOut,3> patch, uint patchID : SV_PrimitiveID){ 
                PatchTess o;
                float3 cameraPosWS = GetCameraPositionWS();
                real3 triVectexFactors =  GetDistanceBasedTessFactor(patch[0].positionWS, patch[1].positionWS, patch[2].positionWS, cameraPosWS, _TessMinDist, _TessMinDist + _FadeDist);
 
                float4 tessFactors = _EdgeFactor * CalcTriTessFactorsFromEdgeTessFactors(triVectexFactors);
                o.edgeFactor[0] = max(1.0, tessFactors.x);
                o.edgeFactor[1] = max(1.0, tessFactors.y);
                o.edgeFactor[2] = max(1.0, tessFactors.z);

                o.insideFactor  = max(1.0, tessFactors.w);
                return o;
            }
 
            [domain("tri")]   
            #if _PARTITIONING_INTEGER
            [partitioning("integer")] 
            #elif _PARTITIONING_FRACTIONAL_EVEN
            [partitioning("fractional_even")] 
            #elif _PARTITIONING_FRACTIONAL_ODD
            [partitioning("fractional_odd")]    
            #endif 
 
            #if _OUTPUTTOPOLOGY_TRIANGLE_CW
            [outputtopology("triangle_cw")] 
            #elif _OUTPUTTOPOLOGY_TRIANGLE_CCW
            [outputtopology("triangle_ccw")] 
            #endif

            [patchconstantfunc("PatchConstant")] 
            [outputcontrolpoints(3)]                 
            [maxtessfactor(64.0f)]                 
            HullOut DistanceBasedTessControlPoint (InputPatch<VertexOut,3> patch,uint id : SV_OutputControlPointID){  
                HullOut o;
                o.positionWS = patch[id].positionWS;
                o.texcoord = patch[id].texcoord; 
                return o;
            }
 
  
            [domain("tri")]      
            DomainOut DistanceBasedTessDomain (PatchTess tessFactors, const OutputPatch<HullOut,3> patch, float3 bary : SV_DOMAINLOCATION)
            {  
                float3 positionWS = patch[0].positionWS * bary.x + patch[1].positionWS * bary.y + patch[2].positionWS * bary.z; 
	            float2 texcoord   = patch[0].texcoord * bary.x + patch[1].texcoord * bary.y + patch[2].texcoord * bary.z;

                DomainOut output;
                output.positionCS = TransformWorldToHClip(positionWS);
                output.texcoord = texcoord;
    
                return output; 
            }
 

            half4 DistanceBasedTessFrag(DomainOut input) : SV_Target{   
                half3 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.texcoord).rgb;
                return half4(color, 1.0); 
            }


            ENDHLSL
        }
    }
}

Shader应用效果如下:
在这里插入图片描述

2.3.2 基于屏幕占用范围

将常量外壳着色器修改如下:

PatchTess PatchConstant (InputPatch<VertexOut,3> patch, uint patchID : SV_PrimitiveID){ 
    PatchTess o;
    real3 triVectexFactors =  GetScreenSpaceTessFactor(patch[0].positionWS, patch[1].positionWS, patch[2].positionWS, GetWorldToHClipMatrix() , _ScreenParams, _TriangleSize);
    float4 tessFactors = _EdgeFactor * CalcTriTessFactorsFromEdgeTessFactors(triVectexFactors);
    o.edgeFactor[0] = tessFactors.x;
    o.edgeFactor[1] = tessFactors.y;
    o.edgeFactor[2] = tessFactors.z;

    o.insideFactor  = tessFactors.w;
    return o;
}

GetScreenSpaceTessFactor函数如下:

// Tessellation.hlsl
// Reference: http://twvideo01.ubm-us.net/o1/vault/gdc10/slides/Bilodeau_Bill_Direct3D11TutorialTessellation.pdf

// Compute both screen and distance based adaptation - return factor between 0 and 1
real3 GetScreenSpaceTessFactor(real3 p0, real3 p1, real3 p2, real4x4 viewProjectionMatrix, real4 screenSize, real triangleSize)
{
    // Get screen space adaptive scale factor
    real2 edgeScreenPosition0 = ComputeNormalizedDeviceCoordinates(p0, viewProjectionMatrix) * screenSize.xy;
    real2 edgeScreenPosition1 = ComputeNormalizedDeviceCoordinates(p1, viewProjectionMatrix) * screenSize.xy;
    real2 edgeScreenPosition2 = ComputeNormalizedDeviceCoordinates(p2, viewProjectionMatrix) * screenSize.xy;

    real EdgeScale = 1.0 / triangleSize; // Edge size in reality, but name is simpler
    real3 tessFactor;
    tessFactor.x = saturate(distance(edgeScreenPosition1, edgeScreenPosition2) * EdgeScale);
    tessFactor.y = saturate(distance(edgeScreenPosition0, edgeScreenPosition2) * EdgeScale);
    tessFactor.z = saturate(distance(edgeScreenPosition0, edgeScreenPosition1) * EdgeScale);

    return tessFactor;
}

大致思路是,通过ComputeNormalizedDeviceCoordinates函数计算得到屏幕坐标,然后乘以屏幕尺寸screenSize,获得顶点的屏幕位置。如果屏幕上边的长度小于triangleSize,那么其细分因子就会衰减。
在这里插入图片描述

2.3.3 其他

在《DirectX12 3D 游戏开发实战》中还提到了两种计算细分因子的衡量标准:

  • 根据三角形的朝向:比如位于轮廓线(Sihouette edge)周围的三角形应该比其他位置拥有更多的细节。我们可以通过面片法线和观察方向点乘来判断是否位于轮廓线附近。
    在这里插入图片描述

    更多内容可以阅读Real-time linear silhouette enhancement。

  • 根据粗糙程度:粗糙不平的表面比光滑的表面更需要细致的曲面细分处理。可以通过纹理获取粗糙度数据,来决定镶嵌次数。

3 参考文献

  • Tessellation
  • Direct3d-11-advanced-stages-tessellation
  • CurvedPNTriangles
  • My Tessellation Has Cracks
  • PN-AEN-Triangles-Whitepaper
  • Phone Tessellation
  • Real-time linear silhouette enhancement
  • Direct3D 11 Tutorial: Tessellation

水平有限,如有错误,请多包涵 (〃‘▽’〃)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/114568.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis分布式锁的实现方式

目录一、分布式锁是什么1、获取锁2、释放锁二、代码实例上面代码存在锁误删问题&#xff1a;三、基于SETNX实现的分布式锁存在下面几个问题1、不可重入2、不可重试3、超时释放4、主从一致性四、Redisson实现分布式锁1、pom2、配置类3、测试类五、探索tryLock源码1、tryLock源码…

微软发布 Entity Framework EF Core 8 或 EF8

Entity Framework 现已被广泛使用&#xff0c;微软首席软件工程经理 Arthur Vickers 日前在一个在线社区会议上的发言。 Entity Framework Core 8.0&#xff08;也称为 EF Core 8 或 EF8&#xff09;的未来规划。EF Core 8 是 EF Core 7 之后的下一个版本&#xff0c;这将是一个…

链表的实现:无头单向非循环链表的实现

笔者在上篇博客书写了一个名为&#xff1a;链式存储之&#xff1a;链表的引出及其简介原文链接为&#xff1a;https://blog.csdn.net/weixin_64308540/article/details/128374876?spm1001.2014.3001.5501对于此篇博客&#xff0c;在一写出来&#xff0c;便引起了巨大反响&…

Golang 【basic_leaming】函数详解

阅读目录1、函数定义2、函数的调用3、函数参数4、函数返回值5、函数变量作用域全局变量局部变量6、函数类型与变量定义函数类型函数类型变量7、高阶函数函数作为参数函数作为返回值8、匿名函数和闭包匿名函数闭包闭包进阶示例1闭包进阶示例2闭包进阶示例39、defer 语句defer 执…

Windows-试用phpthink发现原来可这样快速搭建mysql、redis等环境、xdebug

一、前言 最近在简单学习 php 国人框架 phpthink&#xff0c;不得不说牛&#xff0c;我在 github 上既然搜不到此项目… 但是发现搭建依赖环境不会&#xff0c;于是百度一下&#xff0c;几乎都是各种集成工具什么宝塔、小皮面板等等。有固然是方便&#xff0c;但为什么其它语言…

DAY5 Recommended system cold startup problem

推荐系统的冷启动问题 推荐系统冷启动概念 ⽤户冷启动&#xff1a;如何为新⽤户做个性化推荐物品冷启动&#xff1a;如何将新物品推荐给⽤户&#xff08;协同过滤&#xff09;系统冷启动&#xff1a;⽤户冷启动物品冷启动本质是推荐系统依赖历史数据&#xff0c;没有历史数据⽆…

html+圣诞树

圣诞节 基督教纪念耶稣诞生的重要节日。亦称耶稣圣诞节、主降生节&#xff0c;天主教亦称耶稣圣诞瞻礼。耶稣诞生的日期&#xff0c;《圣经》并无记载。公元336年罗马教会开始在12月25日过此节。12月25日原是罗马帝国规定的太阳神诞辰。有人认为选择这天庆祝圣诞&#xff0c;是…

【学习打卡07】 可解释机器学习笔记之Shape+Lime代码实战

可解释机器学习笔记之ShapeLime代码实战 文章目录可解释机器学习笔记之ShapeLime代码实战基于Shapley值的可解释性分析使用Pytorch对MNIST分类可解释性分析使用shap的Deep Explainer进行可视化使用Pytorch对预训练ImageNet图像分类可解释性分析指定单个预测类别指定多个预测类别…

Elasticsearch 核心技术(一):Elasticsearch 安装、配置、运行(Windows 版)

❤️ 个人主页&#xff1a;水滴技术 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; &#x1f338; 订阅专栏&#xff1a;大数据核心技术从入门到精通 文章目录一、Elasticsearch 版本的选择二、下载 **Elasticsearch**三、安装 Elasticsear…

Springboot+Netty实现基于天翼物联网平台CTWing(AIOT)终端TCP协议(透传模式)-云服务端(IOT平台)

之前有文章用java实现了设备端和应用订阅端&#xff0c;那么我根据AIOT的协议也可以实现一个demo物联网平台端&#xff0c;这种简易的平台是实现自己搭建物联网平台的基础。 直接用代码 新建Springboot的maven项目&#xff0c;pom.xml文件导入依赖包&#xff08;用到了swagge…

UDP协议在Windows上使用示例

UDP(User Datagram Protocol&#xff0c;用户数据报协议)是无连接的&#xff0c;因此在两个进程通信前没有握手过程。UDP协议提供一种不可靠数据传送服务&#xff0c;也就是说&#xff0c;当进程将一个报文发送进UDP套接字时&#xff0c;UDP协议并不保证该报文将到达接收进程。…

过孔基础常识

过孔&#xff0c;一个绝大多数硬件工程师都听说过&#xff0c;但又并非真正了解的名词。了解的都知道&#xff0c;其在PCB板中其着至关重要的的作用。没有过孔的存在&#xff0c;很难画出一块完美的PCB板。所以呢&#xff0c;小编今日就带大家了解了解什么是过孔。 什么是过孔…

FCN代码及效果展示

1. 代码获取 代码地址: https://github.com/Le0v1n/ml_code/tree/main/Segmentation/FCN 2. 从头开始训练 2.1 测试平台 GPU&#xff1a;NVIDIA RTX 3070CPU: Intel I5-10400FRAM: 16GBOS: Windows 11Dataset: VOC2012Class num: 21(201)Batch size: 4Learning Rate: 0.1Ep…

嘉兴经开区第四届创新创业大赛总决赛成功举办

12月21日至12月22日&#xff0c;嘉兴经济技术开发区第四届创新创业大赛总决赛成功举办&#xff0c;经过激烈角逐最后共有10家企业分别获得大赛初创组和成长组的一二三等奖。 总决赛现场 嘉兴经开区第四届中国创新创业大赛于6月正式启动&#xff0c;陆续在嘉兴、成都、北京、西…

【详细学习SpringBoot源码之内嵌Tomcat启动原理分析编译部署Tomcat源码过程解析-9】

一.知识回顾 【0.SpringBoot专栏的相关文章都在这里哟&#xff0c;后续更多的文章内容可以点击查看】 【1.SpringBoot初识之Spring注解发展流程以及常用的Spring和SpringBoot注解】 【2.SpringBoot自动装配之SPI机制&SPI案例实操学习&SPI机制核心源码学习】 【3.详细学…

12-RabbitMq概述与工作模式深度剖析

MQ概述 MQ全称 Message Queue&#xff08;消息队列&#xff09;&#xff0c;是在消息的传输过程中保存消息的容器。多用于分布式系统之间进行通信。 MQ 的优势 应用解耦&#xff1a;提高系统容错性和可维护性 异步提速&#xff1a;提升用户体验和系统吞吐量 削峰填谷&#xff1…

unity中使用代码接绘制三维模型

一 模型的构成 在三维世界中&#xff0c;绘制一个模型并不是什么很复杂的问题。只要知道了基本原理一切需求便迎刃而解。 如下图所示&#xff0c;任何模型都是由点线面构成的&#xff0c;而面的最小单位是三角形。 任何一个多边形的面&#xff0c;都是由多个三角形构成的。比…

Web前端105天-day64-HTML5_CORE

HTML5CORE04 目录 前言 一、复习 二、WebSocket 三、服务器搭建 四、聊天室 五、defineProperty 5.1.初识defineProperty 5.2.配置多个属性 5.3.可配置 5.4.赋值监听 5.5.练习 5.6.计算属性 总结 前言 HTML5CORE04学习开始 一、复习 SVG: 利用HTML的 DOM 来绘制图…

PCB贴片机如何送料?

1.常见的贴片机供料器四种形式 http://www.sz-bjzn.com/1547.html 2.模块化设计SMT贴片机送料器的操作方法 3.淘宝 https://item.taobao.com/item.htm?spma230r.1.14.98.33e41823OZ1zzn&id579043582781&ns1&abbucket20#detail 不错&#xff1a;https://item.tao…

distinct与group by 去重

distinct与group by 去重distinct 特点&#xff1a;group by 特点&#xff1a;总结&#xff1a;mysql中常用去重复数据的方法是使用 distinct 或者group by &#xff0c;以上2种均能实现&#xff0c;但也有不同的地方。distinct 特点&#xff1a; 1、distinct 只能放在查询字段…