您现在的位置是:首页 >其他 >【Unity URP】Rendering Debugger和可视化MipMap方案网站首页其他

【Unity URP】Rendering Debugger和可视化MipMap方案

九九345 2023-06-21 08:00:03
简介【Unity URP】Rendering Debugger和可视化MipMap方案

写在前面

最近开始学习Unity性能优化,是结合了《Unity游戏优化》这本书和教程《Unity性能优化》第叁节——静态资源优化(3)——纹理的基础概念一起学习。在学习纹理优化部分时候遇到了问题,固定管线下Unity的Scene窗口有一个可视化Mipmap的渲染模式:

而这批Miscellaneous模式的选项在URP下相同位置没了:

又比较需要这个便捷的查看方法!于是搜一下想看看有没有出一些插件之类的,突然搜到了大概20年也有小伙伴提出这个问题:

Missing Scene view Draw Modes (Mipmaps, Overdraw, etc.) - Unity Forum

底下Unity技术人员回复了,大概意思就是会加入Rendering Debugger来实现之前固定管线下的D

rawMode的一些功能: 

于是就发现了Rnedering Debugger功能,What's new in URP 12 (Unity 2021.2) | Universal RP | 12.0.0

之前URP下做东西的时候完全没有注意和使用过!一起来简单看看:

Rendering Debugger

非常方便了!比如Material的一些查看,可以单独看albedo的效果:

比如OverDraw:

曾经固定管线下的级联阴影、Alpha通道等的一键预览都有了!但是还是没有Mipmap。

其他版本的URP该怎么办?

小插曲,在Rendering Debugger没出来前,这位大佬Scene View Debug Modes in the Unity URP — John Austin甚至自己做了个插件:

虽然后续URP已经提供了rendering debugger,但是这个把shader添加进debug里的整个框架我们是可以参考过来的。 

讨论Build-in下Scene视图的Mipmaps

其实Mipmap可视化已经有人总结过了:Re0-TA成长笔记 01关于Mipmap问题

而且asset store有免费的类似插件:Colored Mipmap Texture - Visualized Checker

还有其他的方法,要么方法我看不太懂,,要么颜色太多我感觉也不需要那么多颜色,只是想把Mipmap视图作为优化纹理的小工具。我希望在URP下也能实现固定管线那种简单的蓝/红色的Mipmap显示效果,实现方法尽量的简单一点,固定管线下就这样简单:

场景很简陋(之前学习入门精要的build-in项目~),也算体现了大概吧,当纹理大小刚刚好的时候展示的就是纹理的样子,纹理太小了精度不够就会是蓝色,纹理太大精度过大就会是红色!

因为比较好奇,build-in下的Scene视图的Mipmap到底是怎么运行的?物体shader里的贴图一定不只有一张主纹理,他的法线纹理、金属度等纹理都会参与Mipmap,这个大小是综合所有贴图判断的吗?还是只关注主要的albedo贴图?

我们测试一下,拿之前学习法线映射的shader为例,shader有两张纹理,albedo和normal,大小均为2048,显然都过大了,所以Mipmap视图下整体是红色:

现在把albedo贴图改为32,变成蓝色了!:

如果我们只改normal的大小为32,albedo那张不变呢?没有变化!还是红色:

这就初步说明,固定管线下这个Mipmap level判断依据,是根据shader中的MainTex(主纹理)来判断的,而且物体shader的SubShader的RenderType一定要有,才能参与这个Scene视图下Mipmap的红蓝判断:

还需要确定一点,就是这个Mipmap渲染的shader到底怎么获取纹理的?我们再测试一下,如果shader里如果主纹理命名不是默认的_MainTex,而是_BaseTex的话:

场景中之前的那个物体又变蓝了!

好了,到这里我们可以初步推断:

Build-in管线下,Scene View里的Mipmaps可视化,只针对shader主纹理命名为_MainTex的物体有效,且只关注_MainTex一张贴图的大小是否合适,其余的法线纹理等其他贴图不受关注

探讨URP下Mipmap可视化方案

初步猜测:如果我只想要简单的红蓝检测效果,且实践方法尽量简单,我必须要在URP下能够访问所有项目的主纹理,然后单独做一个ViewDebug_Mipmaps的shader,挂在View视图窗口。

紧接着,找资料突然就看到了11年的时候就有人提出了一种可视化Mipmap方案:

A way to visualize mip levels · Aras' website (aras-p.info)

有意思的是在查看Build-in的Debug.hlsl文件时,发现Unity的可视化方案也是参考了之前的那个文章(Unity的Debug.hlsl文件):

上面那个人的主要shader如下:

struct v2f {
    float4 pos : SV_POSITION;
    float2 uv : TEXCOORD0;
    float2 mipuv : TEXCOORD1;
};
float2 mainTextureSize;
v2f vert (float4 vertex : POSITION, float2 uv : TEXCOORD0)
{
    v2f o;
    o.pos = mul (matrix_mvp, vertex);
    o.uv = uv;
    o.mipuv = uv * mainTextureSize / 8.0;
    return o;
}
half4 frag (v2f i) : COLOR0
{
    half4 col = tex2D (mainTexture, i.uv);
    half4 mip = tex2D (mipColorsTexture, i.mipuv);
    half4 res;
    res.rgb = lerp (col.rgb, mip.rgb, mip.a);
    res.a = col.a;
    return res;    
}

OHHHH,他的实现思路差不多就是我的意思!shader里的mainTextureSize,实际上就是URP下shader里我们能默认获取得到的BaseMap_TextureSize,其余的操作只是为了能根据距离动态判断纹理大小是否合适,根据判断结果给上红蓝色。

那我们就开始吧!首先是确定,怎么获取纹理?想起来了,可以从URP下自带的Rendering Debugger入手:

看看Rendering Debugger的逻辑

简单一点,我们打开URP的文件夹,搜索Debug

成功定位到Debugging3d.hlsl:

Debugging3D.hlsl

具体内容就不一句一句来了,单独挑一个,Material的:

你会发现!原来Debugger的逻辑并不是重新自己有一套shader,而是直接拿SurfaceData的东西用,输出想要的项就行了。

SurfaceData

之前扒URP的PBR Shader的时候就已经见过他了,SurfaceData是在SurfaceData.hlsl定义的结构体:

然后在LitInput.hlsl中定义了一个InitializeStandardLitSurfaceData()函数,去初始化SurfaceData:

尝试输出Surface.albedo

刚好我作为联系我有想要优化的项目,那就直接在项目中写个shader看看能不能成功提取SurfaceData.albedo吧!写个shader:

Shader "Debug/Debug Albedo"
{
    SubShader
    {
        Tags{"RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline"}

        Pass
        {
            HLSLPROGRAM
            
            #pragma vertex LitPassVertex
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceData.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/LitForwardPass.hlsl"

            float4 frag(Varyings input) : SV_TARGET {

                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);

                #if defined(_PARALLAXMAP)
                #if defined(REQUIRES_TANGENT_SPACE_VIEW_DIR_INTERPOLATOR)
                    half3 viewDirTS = input.viewDirTS;
                #else
                    half3 viewDirTS = GetViewDirectionTangentSpace(input.tangentWS, input.normalWS, input.viewDirWS);
                #endif
                    ApplyPerPixelDisplacement(viewDirTS, input.uv);
                #endif

                SurfaceData surfaceData;
                InitializeStandardLitSurfaceData(input.uv, surfaceData);
                
                return float4(surfaceData.albedo, 1);
                
            }
          
            ENDHLSL
        }
    }

}

参考上面自己Debug的框架,给他做成开关放在Scene下的DrawMode里:

结果,,,Scene是全黑的,并没有单独展示Albedo:

哪里出错了?想起来,不会是因为BaseMap命名和采样的问题吧。为了测试我的想法是否正确,场景中拖个正方体,给他一个简单的shader,shader严格按照BaseMap来:

同样的DrawMode选择Albedo,居然出现了!

果然,验证了我的猜想。检查一下项目里的shader,发现shader都是拿ASE实现的,,,转化的命名什么的都有点混乱,,,

再拿个之前搭的模型为例吧:

同样的Shader:

成功了!调出了albedo,啊,可视化的途径我们算是找到了,接下来就是实现了!

URP下实现Mipmap可视化

给texture每个mip层赋值

这其实是最关键的一步——我们需要有图可采样成颜色。

Tutorial: Creating and modifying custom mipmaps - Texture Tools - NVIDIA Developer Forums

我尝试过Unity创建纹理,也尝试过PS、GIMP生成dds图,但是这俩好像只能打开dds却不能编辑每一个Mip层的颜色!!!

于是另寻他路:dds的目的是给每个mipLevel给一个颜色值,那我们直接获取mipmap的level,根据这个level值输出对应的颜色不就好了,也就是shader里实现这行采样代码:

half4 mip = tex2D (mipColorsTexture, i.mipuv);

这里注意一下,tex2D的本质是什么?这是Unity提供的一个采样器,输入需要采样的纹理及对应的uv值,会进行采样,并根据纹理的设置生成mipmap。这里要关注另一个采样函数:tex2Dlod,这个是可以根据传入uv的.w分量信息指定mipmap层的。随便搜一下tex2Dlod的原理,就能明白了,比如这篇文章:MipMap的LOD实现原理 - 知乎里写的:

float mipmapLevel(float2 uv, float2 textureSize) 
{    
 float dx = ddx(uv * textureSize.x);    
 float dy = ddy(uv * textureSize.y);   
 float d = max(dot(dx, dx), dot(dy, dy));
 return 0.5 * log2(d);//0.5是技巧,本来是d的平方。
} 

我直接在fragment shader里计算:

// 直接自己计算lod,参考tex2DLod
                float2 mipUV = o.uv * o.mainTextureSize/ 8; // 参考文章的方案
                float dx = ddx(mipUV); // 
                float dy = ddy(mipUV);
                float px = 32 * dx;
                float py = 32 * dy;
                float lod = 0.5 * log2(max(dot(px, px), dot(py, py)));

再写个函数,根据传入的lod值lerp我的颜色,颜色取值来自上面的那篇文章:

// 根据当前纹理的Mip层值返回给定颜色
            float4 GetCurMipColorByManualColor(float mipLevel)
            {

                if(mipLevel==0) // 纹理压根没有开启mipmap
                {
                    return real4(0.0,0.0,1.0,1); // 给.a为0,即baseMap
                }
                else
                {
                    if(mipLevel < 1 ) // 代表着纹理太小了,给个蓝色
                    {
                        return lerp(real4(0.0,0.0,1.0,1),real4(0.0,0.0,1.0,0.8),mipLevel);
                    }
                    else if (mipLevel <2)
                    {
                        return lerp(real4(0.0,0.0,1.0,0.8),real4(1,1,1,0), mipLevel-1);
                    }
                    else if(mipLevel <3) // mip的正正好,于是.a值给0,意味着纹理此时恰到好处
                    {
                        return lerp(real4(1,1,1,0),real4(1.0,0.7,0.0,0.2),mipLevel-2);
                    }
                    else if(mipLevel <4)
                    {
                        return lerp(real4(1.0,0.7,0.0,0.2),real4(1.0,0.3,0.0,0.6),mipLevel-3);
                    }
                    else if(mipLevel <5)  // mip太多了吧,意味着纹理太大了!因此给个更红的颜色
                    {
                        return lerp(real4(1.0,0.3,0.0,0.6),real4(1.0,0.0,0.0,0.8),mipLevel-4);
                    }
                    else
                    {
                        return real4(1.0,0.0,0.0,0.8); // mip了超大,直接给正红色,.a直接点满,完全为debug的颜色
                    }
                }
            }

接着fragment shader里:

float3 baseColor = surfaceData.albedo;
                float4 debugColor = GetCurMipColorByManualColor(lod);
                float4 res;
                
                res.rgb =lerp(baseColor.rgb, debugColor.rgb, debugColor.a); //由debugColor.a控制插值
                res.a = 1;
                return res;

就是说,到这里就已经实现了!具体怎么把他搬进Scene视图下,前面列举了一个URP下别人实现Albedo可视化的框架,我是直接拿过来用了,他给了源码,所以脚本这里就不过多的介绍。

效果展示

放上一个跟固定管线里的相同模型、贴图的场景的mipmap可视化对比,左边是固定管线下,右边是我实现的:

可以看出在显示上是有一定偏差的,因为Unity内部到底是怎么做mipmap可视化,颜色如何规定的?颜色和mip层的关系是怎样的?没在源码中找到太多的依据。

但基本上算是实现了!虽然shader用了很多个if,但是只是一个debug环节,不用太考虑效率问题。总的来说这个方案学习过程中自己使用起来还是足够的!

文章写的很潦草,涉及到的内容太扩散了,其实也是我实现过程中的心路历程,希望看到这里的你能看懂!!!!

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。