百人计划(图形部分)曲面细分与几何着色器

https://space.bilibili.com/7398208?spm_id_from=333.788.b_765f7570696e666f.1

 曲面细分着色器与几何着色器都是在顶点着色器之后,裁剪之前,其中曲面细分着色器在几何着色器之前,两个都是可选的着色器,并不是必须要写的,且曲面细分着色器目前在手机上基本上没应用,而几何着色器最常用的则是用来渲染草地;在这里插入图片描述

曲面细分与几何着色器概述

曲面着色器的应用

使用曲面细分是为了增加模型的细节,但是我们通常没必要在用户关注不到地方增加不必要的细节,因此我们会采取一些策略来得到动态的曲面细分因子:

  • 距离相机的远近:距离相机越远,需要越少的模型细节,而离相机越近,曲面细分的程度可以越高。
  • 屏幕空间的覆盖范围::我们可以估算出一个模型覆盖到的屏幕区域大小,覆盖区域越小,说明模型在屏幕上占比越小,此时可以使用低多边形模型,相反,覆盖区域越大,曲面细分的程度需要越高。
  • 三角面的朝向:三角面的朝向和观察方向的夹角越大,说明越靠近模型边缘(视觉上的轮廓边缘),而此时需要更高的模型精度,因此曲面细分程度要越高。
  • 表面粗糙度:粗糙表面相比平滑表面,需要更多的模型细节。例如视觉上粗糙不平的地面相比光滑的镜面,需要更多的模型细节来进行表现。

 和置换贴图结合使用

  • 单独使用置换贴图的问题在于,如果模型的面数不够多,边缘就会非常锐利,效果不好;使用曲面细分着色器使模型面数变多之后,细节的表达会更精确、更平滑
  • 使用曲面细分着色器可以根据一定的规则(如距离)来动态地调整模型的复杂度,比起直接使用高模能带来更好的性能

 几何着色器的应用

 几何动画

 草地等效果(与曲面细分结合)

 自定义草的画法,再和曲面细分着色器结合,就可以得到一个可以动态调整草密度的一个草地效果。

从管线顺序理解

整体顺序:顶点 → 曲面细分 → 几何 → 片元

曲面细分又分为:Hull shader 、Tessellation Primitive Generator 、 Domain shader

  • Hull shader主要作用:定义一些细分的参数(如:每条边上如何细分,内部三角形如何细分)
  • Tessellation Primitive Generator,不可编程的,不需要我们做啥
  • Domain shader:经过曲面细分着色器细分后的点是位于重心空间的,这部分的作用就是把它转化到我们要用的空间。

在D3D11 和 OpenGL中,名字/叫法有差异

曲面细分着色器-Tessellation shader(TESS)

TESS的输入和输出

 TESS的流程

Hull shader参数详解

Tessellation Factor

定义把一条边分为几个部分

在这里插入图片描述

equa_Spacing:将一条边等分(二、三分等等..),Subdivide参数是几就是几等分;
fractional_even_Spacing:最小值为2,Subdivide参数向上取最近的偶数,将周长分为n-2的等长的部分,以及两端不等长的部分,目的是让细分更平滑;
fractional_odd_Spacing :最小值为1,Subdivide参数向上取最近的奇数,将周长分为n-2的等长的部分,以及两端不等长的部分,目的是让细分更平滑;

Inner Tessellation Factor

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

 内部细分因素,当该参数为3时,无论上面的Tessellation Factor怎样去进行切分的,我们把三角形切分为三等分,然后分别找最近的两个切分的点,做其延长线,其焦点便是在新内部三角形的一个点;
(概括下就是取边上点的垂线的延长线做交点,直至最后无交点或者交于中心一点)

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

曲面细分Demo部分

曲面细分算法展示

请添加图片描述

//曲面细分Demo1
Shader "Unlit/TessShader"
{
    Properties
    {
        _TessellationUniform("TessellationUniform",Range(1,64)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
        Pass
        {
            CGPROGRAM
            //定义2个函数 hull domain
            #pragma hull hullProgram
            #pragma domain ds
           
            #pragma vertex tessvert
            #pragma fragment frag

            #include "UnityCG.cginc"
            //引入曲面细分的头文件
            #include "Tessellation.cginc" 

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

            struct VertexOutput
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
            };

            VertexOutput vert (VertexInput v)
            //这个函数应用在domain函数中,用来空间转换的函数
            {
                VertexOutput o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                o.tangent = v.tangent;
                o.normal = v.normal;
                return o;
            }

            //有些硬件不支持曲面细分着色器,定义了该宏就能够在不支持的硬件上不会变粉,也不会报错
            #ifdef UNITY_CAN_COMPILE_TESSELLATION
                //顶点着色器结构的定义
                struct TessVertex{
                    float4 vertex : INTERNALTESSPOS;
                    float3 normal : NORMAL;
                    float4 tangent : TANGENT;
                    float2 uv : TEXCOORD0;
                };

                struct OutputPatchConstant { 
                    //不同的图元,该结构会有所不同
                    //该部分用于Hull Shader里面
                    //定义了patch的属性
                    //Tessellation Factor和Inner Tessellation Factor
                    float edge[3] : SV_TESSFACTOR;
                    float inside  : SV_INSIDETESSFACTOR;
                };

                TessVertex tessvert (VertexInput v){
                    //顶点着色器函数
                    TessVertex o;
                    o.vertex  = v.vertex;
                    o.normal  = v.normal;
                    o.tangent = v.tangent;
                    o.uv      = v.uv;
                    return o;
                }

                float _TessellationUniform;
                OutputPatchConstant hsconst (InputPatch<TessVertex,3> patch){
                    //定义曲面细分的参数
                    OutputPatchConstant o;
                    o.edge[0] = _TessellationUniform;
                    o.edge[1] = _TessellationUniform;
                    o.edge[2] = _TessellationUniform;
                    o.inside  = _TessellationUniform;
                    return o;
                }

                [UNITY_domain("tri")]//确定图元,quad,triangle等
                [UNITY_partitioning("fractional_odd")]//拆分edge的规则,equal_spacing,fractional_odd,fractional_even
                [UNITY_outputtopology("triangle_cw")]
                [UNITY_patchconstantfunc("hsconst")]//一个patch一共有三个点,但是这三个点都共用这个函数
                [UNITY_outputcontrolpoints(3)]      //不同的图元会对应不同的控制点
              
                TessVertex hullProgram (InputPatch<TessVertex,3> patch,uint id : SV_OutputControlPointID){
                    //定义hullshaderV函数
                    return patch[id];
                }

                [UNITY_domain("tri")]//同样需要定义图元
                VertexOutput ds (OutputPatchConstant tessFactors, const OutputPatch<TessVertex,3>patch,float3 bary :SV_DOMAINLOCATION)
                //bary:重心坐标
                {
                    VertexInput v;
                    v.vertex = patch[0].vertex*bary.x + patch[1].vertex*bary.y + patch[2].vertex*bary.z;
			        v.tangent = patch[0].tangent*bary.x + patch[1].tangent*bary.y + patch[2].tangent*bary.z;
			        v.normal = patch[0].normal*bary.x + patch[1].normal*bary.y + patch[2].normal*bary.z;
			        v.uv = patch[0].uv*bary.x + patch[1].uv*bary.y + patch[2].uv*bary.z;

                    VertexOutput o = vert (v);
                    return o;
                }
            #endif

            float4 frag (VertexOutput i) : SV_Target
            {

                return float4(1.0,1.0,1.0,1.0);
            }
            ENDCG
        }
    }
    Fallback "Diffuse"
}

和置换贴图结合

image.png

 通过置换贴图的深度,来把顶点沿着它的法线方向进行移动,以此来对mash进行形变。

 代码部分和上个Demo的区别也就是在顶点shader部分对顶点进行了位移、和一些计算法线的参数。因为顶点位移后没有对应的法线贴图,所以需要自己计算一下

//曲面细分Demo2:与置换贴图结合使用
Shader "Unlit/Tess_Diss_Shader"
{
    Properties
    {
        _MainTex("MainTex",2D) = "white"{}
        _DisplacementMap("_DisplacementMap",2D)="gray"{}
        _DisplacementStrength("DisplacementStrength",Range(0,1)) = 0
        _Smoothness("Smoothness",Range(0,5))=0.5
        _TessellationUniform("TessellationUniform",Range(1,64)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" 
               "LightMode"="ForwardBase"}
        LOD 100
        Pass
        {
            CGPROGRAM
            //定义2个函数 hull domain
            #pragma hull hullProgram
            #pragma domain ds
           
            #pragma vertex tessvert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            //引入曲面细分的头文件
            #include "Tessellation.cginc" 

            #pragma target 5.0
            float _TessellationUniform;
            sampler2D _MainTex;
            float4 _MainTex_ST;

            sampler2D _DisplacementMap;
            float4 _DisplacementMap_ST;
            float _DisplacementStrength;
            float _Smoothness;

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

            struct VertexOutput
            {
                float2 uv : TEXCOORD0;
                float4 pos : SV_POSITION;
                float4 worldPos:TEXCOORD1;
                half3 tspace0 :TEXCOORD2;
                half3 tspace1 :TEXCOORD3;
                half3 tspace2 :TEXCOORD4;
            };

            VertexOutput vert (VertexInput v)
            //这个函数应用在domain函数中,用来空间转换的函数
            {
                VertexOutput o;
                o.uv = TRANSFORM_TEX(v.uv,_MainTex);
                //Displacement
                //由于并不是在Fragnent shader中读取图片,GPU无法获取mipmap信息,因此需要使用tex2Dlod来读取图片,使用第四坐标作为mipmap的level,这里取了0
                float Displacement = tex2Dlod(_DisplacementMap,float4(o.uv.xy,0.0,0.0)).g;
                Displacement = (Displacement-0.5)*_DisplacementStrength;
                v.normal = normalize(v.normal);
                v.vertex.xyz += v.normal * Displacement;

                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);

                //计算切线空间转换矩阵
                half3 vNormal = UnityObjectToWorldNormal(v.normal);
                half3 vTangent = UnityObjectToWorldDir(v.tangent.xyz);
                //compute bitangent from cross product of normal and tangent
                half tangentSign = v.tangent.w * unity_WorldTransformParams.w;
                half3 vBitangent = cross(vNormal,vTangent)*tangentSign;
                //output the tangent space matrix
                o.tspace0 = half3(vTangent.x,vBitangent.x,vNormal.x);
                o.tspace1 = half3(vTangent.y,vBitangent.y,vNormal.y);
                o.tspace2 = half3(vTangent.z,vBitangent.z,vNormal.z);
                return o;
            }

            //有些硬件不支持曲面细分着色器,定义了该宏就能够在不支持的硬件上不会变粉,也不会报错
            #ifdef UNITY_CAN_COMPILE_TESSELLATION
                //顶点着色器结构的定义
                struct TessVertex{
                    float4 vertex : INTERNALTESSPOS;
                    float3 normal : NORMAL;
                    float4 tangent : TANGENT;
                    float2 uv : TEXCOORD0;
                };

                struct OutputPatchConstant { 
                    //不同的图元,该结构会有所不同
                    //该部分用于Hull Shader里面
                    //定义了patch的属性
                    //Tessellation Factor和Inner Tessellation Factor
                    float edge[3] : SV_TESSFACTOR;
                    float inside  : SV_INSIDETESSFACTOR;
                };

                TessVertex tessvert (VertexInput v){
                    //顶点着色器函数
                    TessVertex o;
                    o.vertex  = v.vertex;
                    o.normal  = v.normal;
                    o.tangent = v.tangent;
                    o.uv      = v.uv;
                    return o;
                }

                //float _TessellationUniform;
                OutputPatchConstant hsconst (InputPatch<TessVertex,3> patch){
                    //定义曲面细分的参数
                    OutputPatchConstant o;
                    o.edge[0] = _TessellationUniform;
                    o.edge[1] = _TessellationUniform;
                    o.edge[2] = _TessellationUniform;
                    o.inside  = _TessellationUniform;
                    return o;
                }

                [UNITY_domain("tri")]//确定图元,quad,triangle等
                [UNITY_partitioning("fractional_odd")]//拆分edge的规则,equal_spacing,fractional_odd,fractional_even
                [UNITY_outputtopology("triangle_cw")]
                [UNITY_patchconstantfunc("hsconst")]//一个patch一共有三个点,但是这三个点都共用这个函数
                [UNITY_outputcontrolpoints(3)]      //不同的图元会对应不同的控制点
              
                TessVertex hullProgram (InputPatch<TessVertex,3> patch,uint id : SV_OutputControlPointID){
                    //定义hullshaderV函数
                    return patch[id];
                }

                [UNITY_domain("tri")]//同样需要定义图元
                VertexOutput ds (OutputPatchConstant tessFactors, const OutputPatch<TessVertex,3>patch,float3 bary :SV_DOMAINLOCATION)
                //bary:重心坐标
                {
                    VertexInput v;
                    v.vertex = patch[0].vertex*bary.x + patch[1].vertex*bary.y + patch[2].vertex*bary.z;
			        v.tangent = patch[0].tangent*bary.x + patch[1].tangent*bary.y + patch[2].tangent*bary.z;
			        v.normal = patch[0].normal*bary.x + patch[1].normal*bary.y + patch[2].normal*bary.z;
			        v.uv = patch[0].uv*bary.x + patch[1].uv*bary.y + patch[2].uv*bary.z;

                    VertexOutput o = vert (v);
                    return o;
                }
            #endif

            float4 frag (VertexOutput i) : SV_Target
            {
                float3 lightDir =_WorldSpaceLightPos0.xyz;
                float3 tnormal = UnpackNormal (tex2D (_DisplacementMap, i.uv));
                half3 worldNormal;
                worldNormal.x=dot(i.tspace0,tnormal);
                worldNormal.y= dot (i.tspace1, tnormal);
                worldNormal.z=dot (i.tspace2, tnormal);
                float3 albedo=tex2D (_MainTex, i.uv). rgb;
                float3 lightColor = _LightColor0.rgb;
                float3 diffuse = albedo * lightColor * DotClamped(lightDir,worldNormal);
                float3 viewDir = normalize (_WorldSpaceCameraPos. xyz-i. worldPos. xyz);
                float3 halfVector = normalize(lightDir + viewDir);
                float3 specular = albedo * pow (DotClamped (halfVector, worldNormal), _Smoothness * 100);
                float3 result = specular + diffuse;
                return float4(result, 1.0);

                return float4(result,1.0);
            }
            ENDCG
        }
    }
    Fallback "Diffuse"
}

几何着色器-Geometry shader (GS)

输入与输出

 几何着色器Demo部分

Demo1:基于三角形构建一个草地

在这里插入图片描述

 在这里插入图片描述

Shader "TA100/GS/Grass"
{
    Properties
    {
        [Header(shading)]
        _TopColor ("Top Color", Color) = (1,1,1,1)
        _BottomColor ("Bottom Color", Color) = (1,1,1,1)
        _BladeWidth ("Blade Width",float) = 0.05
        _BladeWidthRandom ("Blade Width Random",float) = 0.02
        _BladeHeight ("Blade Height",float) = 0.5
        _BladeHeightRandom ("Blade Height Random",float) = 0.3
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            //定义几何着色器
            #pragma geometry geo
            #pragma fragment frag
            #pragma target 4.6

            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "Autolight.cginc"

            float4 _TopColor;
            float4 _BottomColor;
            
            // 生成随机数
            float rand(float3 co)
            {
                return frac(sin(dot(co.xyz,float3(12.9898,78.233,53.539))) * 43758.5453);
            }

            float3x3 AngleAxis3x3(float angle, float3 axis)//旋转矩阵
            {
                float c,s;

                sincos(angle,s,c);

                float t = 1 - c;
                float x = axis.x;
                float y = axis.y;
                float z = axis.z;

                return float3x3(
                t * x * x + c, t * x * y - s * z, t * x * z + s * y,
                t * x * y + s * z, t * y * y + c, t * y * z - s * x,
                t * x * z - s * y, t * y * z + s * x, t * z * z + c
                );
            }

            float _BladeHeight;
            float _BladeHeightRandom;
            float _BladeWidth;
            float _BladeWidthRandom;

            struct vertexInput
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
            };

            struct vertexOutput
            {
                float3 normal : NORMAL;
                float4 vertex : SV_POSITION;
                float4 tangent : TANGENT;
            };


            vertexOutput vert (vertexInput v)
            {
                vertexOutput o;
                o.vertex = v.vertex;
                o.normal = v.normal;
                o.tangent = v.tangent;
                return o;
            }

            struct geometryOutput
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            geometryOutput CreateGeoOutput(float3 pos,float2 uv)// 用于空间转换的函数
            {
                geometryOutput o;
                o.pos = UnityObjectToClipPos(pos);
                o.uv = uv;
                return o;
            }

            [maxvertexcount(3)]//定义最大输出点,此处输出三角形所以是3
            //输出使用了TriangleStream,每个顶点都用到了结构体geometryOutput
            void geo(triangle vertexOutput IN[3] : SV_POSITION, inout TriangleStream<geometryOutput> triStream)
            {
                //抛弃顶点本身位置信息的影响,所以采用切线空间,类比法线
                float3 pos = IN[0].vertex;
                float3 vNormal = IN[0].normal;
                float4 vTangent = IN[0].tangent;
                float3 vBitangent = cross(vNormal,vTangent) * vTangent.w;

                float height = (rand(pos.zyx) * 2 - 1) * _BladeHeightRandom + _BladeHeight;
                float width = (rand(pos.xzy) * 2 - 1) * _BladeWidthRandom + _BladeWidth;

                //构建矩阵
                float3x3 facingRotationMatrix = AngleAxis3x3(rand(pos) * UNITY_TWO_PI,float3(0,0,1));
                float3x3 tangentToLocal = float3x3(
                vTangent.x,vBitangent.x,vNormal.x,
                vTangent.y,vBitangent.y,vNormal.y,
                vTangent.z,vBitangent.z,vNormal.z
                );
                float3x3 transformationMat = mul(tangentToLocal,facingRotationMatrix);

                //输出图元用到了TriangleStream,相当于一个用来装配三角形的工具
                //定义了三角形的宽度和高度
                geometryOutput o;
                triStream.Append(CreateGeoOutput(pos + mul(transformationMat,float3(width,0,0)),float2(0,0)));
                triStream.Append(CreateGeoOutput(pos + mul(transformationMat,float3(-width,0,0)),float2(1,0)));
                triStream.Append(CreateGeoOutput(pos + mul(transformationMat,float3(0,0,height)),float2(0.5,1)));
            }

            fixed4 frag (geometryOutput i,fixed facing : VFACE) : SV_Target
            {
                return lerp(_BottomColor,_TopColor,i.uv.y);
            }
            ENDCG
        }
    }
}

Demo2:基于简单的草地,结合曲面细分着色器

几何着色器部分代码:

首先是宽度和高度进行了一个随机,forward是前向的弯曲程度

加入了风

  //占位符

加入了弯曲

//占位符

//SEGMENTS参数解释占位符

曲面细分着色器部分是include进来的

注意:这里的空间转换最后放到几何着色器中进行了,所以这里Domain函数中没有进一步进行空间转换

曲面细分着色器中重要的区别:

distancefactor是用Unity中的一个基于距离的函数来设置的

之前是手动设置factor,这样不管哪里密度都一样,效果不好

改用这里之后,密度会基于距离进行调整

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值