浅谈RenderTexture的shadowmap格式
发表于2018-04-25
最近开始研究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