浅谈RenderTexture的shadowmap格式

发表于2018-04-25
评论0 8.6k浏览
最近开始研究Unity中的Shader,在研究一个案例《BlackSmith》时发现其使用render texture时所选择的格式为shadowmap,于是便打算研究一下shadowmap这种存储格式。

首先,这里是在Unity官方文档中对shadowmap的介绍:这是一种自带的shadowmap格式

这种格式的数据可以GPU自动完成对这种数据格式的shadowmap比较计算。当设备支持时,Unity在内部使用这种数据格式进行阴影计算。需要注意的是,在某些平台或GPU不支持shadowmap格式的情况下,阴影将会使用RenderTextureFormat.Depth格式进行计算。

由于并不是所有显卡都支持shadowmap,使用SystemInfo.SupportsRenderTextureFormat方法去检查设备是否支持此格式。

从官方的描述中我们可以看出,shadowmap的数据格式应该和Depth格式相似,因此猜测shadowmap中也是存储的Depth值,且存储于R通道中,距离camera越近Depth值越小,距离Camera越远Depth值越大,camera近裁面Depth值为0,远裁面Depth值为1。

为了验证此猜测,我们在Unity中简单创建一个场景来进行测试。

创建Cube和Sphere作为场景内容,创建camera获取场景画面,创建Rendertexture作为camera的target texture。将RenderTexture的Color Format格式设置为Depth模式,如下:

此时我们在下方可以看到RenderTexture的画面:

当我们将Color Format改为shadowmap时,画面则变为如下:

两者数据大小都是1M,可以进一步验证推测,但是为何shadowmap格式却无法显示任何画面内容呢?我们又该如何使用shadowmap中的数据呢?

为此我们需要使用shader,新建一个shader用来显示RenderTexture中的数据内容。新建一个材质应用此shader,并将材质赋予新建的Plane上,用MainCamera观察此Plane。

当RenderTexture格式为Depth时,我们只需一个简单的Shader,就能获取RenderTexture中的数据,并显示出来。主要代码如下:
fixed4 frag (v2f i) : SV_Target
{
float depth = tex2D(_MainTex, i.uv).r;
fixed4 col = fixed4(0,0,0,1);
if(depth != 1.f)
col = fixed4(depth,depth, depth,1.f);
return col;
}

画面和将RenderTexture作为材质直接赋予Plane一样,显示画面如下:

但是当我们将RenderTexture格式改为shadowmap时,正如我们所想画面中完全变为红色,没有显示任何内容,可见shadowmap中的数据无法使用这种方式获取。

此时,我们需要使用Texture2D.Sample方法来获取shadowmap中的数据,此方式是笔者研究的shader案例中所使用的方法。

为此,我们需要添加如下变量:
Texture2D _ShadowTexture; 
Texture2D fakePoint;
SamplerState samplerfakePoint;

_ShadowTexture需要在Properties中进行声明,用来获取RenderTexture。fakePoint为使用采样器创建的Texture2D纹理,其中是否含有数据,以及数据内容都无关紧要;samplerfakePoint是基于fakePoint进行初始化的采样器,具体采样器命名和初始化方法见这里。

主要代码如下:
float depth = fakePoint.Sample(samplerfakePoint,float2(0,0)).a;
for (int j = 0; j < 5; j++) {
depth += _ShadowTexture.Sample(samplerfakePoint, i.uv).r;//对该点进行反复采样/
}
depth -= fakePoint.Sample(samplerfakePoint, float2(0, 0)).a;//去除多余量/
depth = depth / 5.f;//对采样总和取平均值/

由此,我们可以得到和Depth格式下相同的画面。如果对GBA通道进行采样,我们都无法得到数据值,因此,我们可以确定shadowmap中数据格式和Depth中数据格式是相同的,将深度信息值存储于R通道中,但是为何Depth格式就可以使用tex2D方法获取,而shadowmap格式却需要使用采样?

笔者通过测试发现,如果如果直接使用_ShadowTexture所初始化的采样器(sampler_ShadowTexture)直接对_ShadowTexture进行采样,如下:
float depth = _ShadowTexture.Sample(sampler_ShadowTexture,i.uv).r;

如果RenderTexture格式为Depth时,可以正常获取数据内容;而当格式为shadowmap时则无法获取数据内容。

由此,笔者推测shadowmap格式在Unity内部进行了数据内容的变换,正常情况下无法直接读取,因此需要通过另外声明一个采样器,按照新的采样器的数据格式对shadowmap数据进行采样,采样的过程中Unity会对shadowmap中的数据进行变换,使其对应新采样器的格式,从而让我们可以向shadowmap中的数据正确的提取出来。

但是,在测试过程中,笔者发现使用Depth格式或shadowmap格式时都会使得物体边缘出现一圈白边。经过测试发现此是由于边缘处depth值突变所导致,推测其原因应该是在camera获取图像转化为深度信息值的过程中,但具体原因为何则仍需进一步学习和探索了。

以下是shader源码:
Shader "Custom/rendertextureformat"  
{  
    Properties  
    {  
        _MainTex ("Texture", 2D) = "white" {}  
    _ShadowTexture("ShadowTexture", 2D) = "white" {}  
    }  
    SubShader  
    {  
        // No culling or depth  
        Cull Off ZWrite Off ZTest Always  
        Pass  
        {  
            CGPROGRAM  
            #pragma vertex vert  
            #pragma fragment frag  
            #include "UnityCG.cginc"  
            struct appdata  
            {  
                float4 vertex : POSITION;  
                float2 uv : TEXCOORD0;  
            };  
            struct v2f  
            {  
                float2 uv : TEXCOORD0;  
                float4 vertex : SV_POSITION;  
            };  
            v2f vert (appdata v)  
            {  
                v2f o;  
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);  
                o.uv = v.uv;  
                return o;  
            }  
            sampler2D _MainTex;  
            uniform Texture2D _ShadowTexture;  
            uniform SamplerState sampler_ShadowTexture;  
            Texture2D fakePoint;  
            SamplerState samplerfakePoint;  
            fixed4 frag (v2f i) : SV_Target  
            {  
#if 1  
            //  float depth = _ShadowTexture.Sample(sampler_ShadowTexture,i.uv).r;  
                float depth = fakePoint.Sample(samplerfakePoint,float2(0,0)).a; //建立采样器/  
                for (int j = 0; j < 5; j++) {  
                    depth += _ShadowTexture.Sample(samplerfakePoint, i.uv).r;   //对该点进行反复采样/  
                }  
                depth -= fakePoint.Sample(samplerfakePoint, float2(0, 0)).a;    //去除多余量/  
                depth = depth / 5.f;                                            //对采样总和取平均值/  
#else  
                float depth = tex2D(_MainTex, i.uv).r;  
#endif  
                fixed4 col = fixed4(0,0,0,1);  
                if(depth != 1.f)  
                    col = fixed4(depth,depth, depth,1.f);  
                return col;  
            }  
            ENDCG  
        }  
    }  
}  
来自:https://blog.csdn.net/herbertd/article/details/51198537

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引