Unity3D Tessellation曲面细分

简述

曲面细分是渲染管线的一个可配置的阶段,在opengl和direct3d里都有实现,Unity的表面着色器对曲面细分有一定的支持,不过必须运行在Shader Model 4.6以上。
曲面细分说白了就是可以实时生成更多的顶点,我们知道在顶点着色器里是不能生成新的顶点的,如果没有曲面细分阶段的话想实时生成新的顶点只能在cpu里生成然后再传输到gpu里,因此为了提升实时生成顶点的速度就多了曲面细分这样一个渲染阶段。
曲面细分阶段是配置式的,不能像顶点着色器和片元着色器一样自己写shader来完全的控制,但是可以配置生成新顶点的方式,数量等等。
曲面细分阶段主要是为了提升渲染的质量和性能,可以对重要的模型生成更多的顶点,对远处或者不重要的模型生成更少的顶点,而更多的顶点就代表更好的渲染质量。虽然有很多类似法线贴图视差贴图之类优化在低模上的渲染效果的方法,但是这些方法的效果都比不过直接提升顶点数量来的直接,这也是曲面细分的强大之处。
我们以曲面细分配合位移贴图(displacement map)来描述一下unity中曲面细分的配置方法,曲面细分配合位移贴图也是一种常见的用法。

位移贴图

位移贴图是一种和法线贴图,视差贴图同类型的技术,都是为了提升低模的显示效果。
如果说法线贴图是将高模的法线覆盖到低模上,视差贴图是为了解决法线贴图部分顶点不会互相遮挡的问题。
那么位移贴图就是更近一步,直接用一张贴图来对顶点进行位移,彻底解决了法线贴图和视差贴图解决不了的问题。
说起来还是挺简单的,位移贴图就是一张灰度图,然后用顶点对图片进行采样,根据采样得到的灰度将该顶点朝法线方向进行移动相应的距离,这个灰度图一般由高模生成。
有的人可能会觉得,那直接用高模不就行了吗?先用低模然后又用一张贴图生成更多的顶点然后又进行位移,最后不是又和高模一样了吗?
其实是不一样的,这主要是因为曲面细分是可以配置的,虽然我们理论上可以生成超过高模的顶点数,但是一般是根据性能指标来进行配置的,想要效果更好就生成更多的顶点,依次类推。而且还可以根据模型的重要性来对曲面细分进行不同的配置,或者是根据距离来确定曲面细分的配置,因此使用曲面细分从低模生成更多的顶点比起直接使用高模还是要更加灵活的。

先写一个简单的表面着色器来使用位移贴图,这个着色器就是简单采样了主纹理,然后再用顶点采样位移贴图来位移顶点。

Shader "LX/tessellation"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _DispTex ("DispTex", 2D) = "white" {}
        _Displacement ("Displacement", float) = 1
        _Tess ("Tess", float) =1
        _Power ("Power", range(1,8)) =1
        _Phong ("Phong Strengh", Range(0,1)) = 0.5
        _EdgeLength ("Edge length", Range(2,50)) = 5
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Opaque"
        }
        LOD 200

        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows vertex:disp
        #pragma target 3.0
        #include "Tessellation.cginc"

        sampler2D _MainTex;
        sampler2D _DispTex;

        float _Phong;

        struct Input
        {
            float2 uv_MainTex;
            float3 worldNormal;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        float _Displacement;


        float _Tess;
        float _Power;
        float _EdgeLength;


        void disp(inout appdata_base v)
        {
            float d = pow(Luminance(tex2Dlod(_DispTex, float4(v.texcoord.xy, 0, 0))), _Power) * _Displacement;
            v.vertex.xyz += v.normal * d;
        }

        void surf(Input IN, inout SurfaceOutputStandard o)
        {
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

至于位移贴图我们这边简单使用了一个中间凸出一块渐变白色圆形的贴图来做示范,理想情况下这个贴图可以把该平面中间凸出一个比较完美的半球。
请添加图片描述

创建一个quad来使用这个着色器,发现根本没有任何反应,因为我们上面的代码还没有加入曲面细分配置,而quad只有四个顶点,想象一下就知道根本无从采样,对于采样位移贴图来说实在太少了。位移贴图是比较要有足够多的顶点才可以看出效果的,顶点越多效果就越平滑。
请添加图片描述
因为曲面细分是直接配置的一个阶段,所以我们并不用写太多代码, 在表面着色器的第一行配置后面追加tessellate:tessFixed 来使用固定值的曲面细分

#pragma surface surf Standard fullforwardshadows vertex:disp tessellate:tessFixed

再在CGPROGRAM里加入对应名字的函数,然后返回一个曲面细分值,这个值越大曲面细分的程度越大。

            float _Tess;

            float4 tessFixed()
            {
                return _Tess;
            }

设置一下参数之后可以看到
请添加图片描述

切换到scene 的渲染模式到wireframe之后可以看到曲面细分生成了新的顶点
tess=1(原始状态,只有四个顶点)
请添加图片描述
tess=20,生成了非常多的新顶点,而且我们也可以看到,和法线贴图那种通过控制明暗程度来表现’虚假’的凸出效果不同,曲面细分是实实在在的生成了新的顶点。
请添加图片描述

固定值的曲面细分就是这么配置,那么如果想要使用基于距离的曲面细分,unity也有写好的内置函数
同样先在上面配置曲面细分函数的名称

#pragma surface surf Standard fullforwardshadows vertex:disp tessellate:tessFixed

然后使用unity内置的UnityDistanceBasedTess函数来实现基于距离的曲面细分,tessDistance 的三个参数分别是三角形的三个顶点,这个不用管直接依次传入UnityDistanceBasedTess函数,然后minDist和maxDist就是距离的起始值和结束值,_Tess就是曲面细分程度。
这个函数的话需要包含Tessellation.cginc文件

            float _Tess;

            float4 tessDistance (appdata v0, appdata v1, appdata v2) {
                float minDist = 10.0;
                float maxDist = 25.0;
                return UnityDistanceBasedTess(v0.vertex, v1.vertex, v2.vertex, minDist, maxDist, _Tess);
            }

unity还支持基于线段长度的曲面细分,可以防止一些很小的三角形的曲面细分程度过高

            float _EdgeLength;

            float4 tessEdge (appdata v0, appdata v1, appdata v2)
            {
                return UnityEdgeLengthBasedTess (v0.vertex, v1.vertex, v2.vertex, _EdgeLength);
            }

还有一个tessphong配置可以和tessellate配置一起用,tessphong可以使生成出来的顶点位置更加平滑,_Phong的话就是一个参数,越大越平滑。

 #pragma surface surf Standard fullforwardshadows vertex:disp tessellate:tessEdge tessphong:_Phong

有兴趣的话也可以研究一下这几个函数的内部实现,其实最后还是根据距离之类的条件返回一个曲面细分值。

最后的参数面板

请添加图片描述

完整代码

Shader "LX/tessellation"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _DispTex ("DispTex", 2D) = "white" {}
        _Displacement ("Displacement", float) = 1
        _Tess ("Tess", float) =1
        _Power ("Power", range(1,8)) =1
        _Phong ("Phong Strengh", Range(0,1)) = 0.5
        _EdgeLength ("Edge length", Range(2,50)) = 5
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Opaque"
        }
        LOD 200

        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows vertex:disp tessellate:tessEdge tessphong:_Phong
        #pragma target 3.0
        #include "Tessellation.cginc"

        sampler2D _MainTex;
        sampler2D _DispTex;

        float _Phong;

        struct Input
        {
            float2 uv_MainTex;
            float3 worldNormal;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        float _Displacement;


        float _Tess;
        float _Power;
        float _EdgeLength;

        // float4 tessFixed()
        // {
        //     return _Tess;
        // }

        float4 tessEdge(appdata_base v0, appdata_base v1, appdata_base v2)
        {
            return UnityEdgeLengthBasedTess(v0.vertex, v1.vertex, v2.vertex, _EdgeLength);
        }

        void disp(inout appdata_base v)
        {
            float d = pow(Luminance(tex2Dlod(_DispTex, float4(v.texcoord.xy, 0, 0))), _Power) * _Displacement;
            v.vertex.xyz += v.normal * d;
        }

        void surf(Input IN, inout SurfaceOutputStandard o)
        {
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

另外代码也传到github仓库里了,大家也可以关注一下哦~
我的github

### 构建任务失败解决方案 当遇到 `Execution failed for task ':app:shrinkReleaseRes'` 错误时,这通常意味着资源压缩过程中出现了问题。此错误可能由多种原因引起,包括但不限于配置不正确、依赖冲突或特定于项目的其他因素。 #### 可能的原因分析 1. **ProGuard 或 R8 配置不当** ProGuard 和 R8 是用于优化和混淆代码以及减少 APK 大小的工具。如果这些工具的配置存在问题,可能会导致资源无法正常处理[^1]。 2. **重复资源** 如果项目中有多个模块定义了相同的资源名称,可能导致冲突并引发该错误。检查是否存在重名的 drawable、string 等资源文件[^2]。 3. **第三方库兼容性** 某些第三方库可能与当前使用的 Gradle 插件版本或其他库存在兼容性问题,从而影响到资源打包过程中的行为[^3]。 4. **Gradle 缓存问题** 有时旧缓存数据会干扰新编译的结果,尝试清理本地仓库和重新同步项目可以帮助排除此类潜在障碍[^4]。 #### 推荐的操作方法 为了有效解决问题,建议按照以下步骤逐一排查: ```bash # 清理项目构建目录 ./gradlew clean # 删除 .gradle 文件夹下的所有内容以清除缓存 rm -rf ~/.gradle/caches/ ``` 调整 `build.gradle` 中的相关设置也是一个重要环节: ```groovy android { ... buildTypes { release { minifyEnabled true // 是否启用代码缩减 shrinkResources true // 是否开启资源压缩 proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' // 尝试禁用 shrinkResources 来测试是否为资源压缩引起的错误 // shrinkResources false } } } ``` 此外,在 `proguard-rules.pro` 文件内添加必要的保留规则,防止关键类被意外移除: ```text -keep class com.example.yourpackage.** { *; } # 替换为你自己的包路径 -dontwarn androidx.**,com.google.** # 忽略警告信息 ``` 最后,确保所使用的 Android Studio 版本是最新的稳定版,并且已经应用了所有的补丁更新。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值