这几天在细读Virtual texture代码,对里面如何将一张贴图贴到RT的指定区域比较感兴趣。
看了下主要是使用Graphic.blit这个API来实现:如下
private void DrawTexture(Texture source, RenderTexture target, RectInt position)
{
if (source == null || target == null || m_DrawTextureShader == null)
return;
// 初始化绘制材质
if (m_DrawTextureMateral == null)
m_DrawTextureMateral = new Material(m_DrawTextureShader);
// 构建变换矩阵
float l = position.x * 2.0f / target.width - 1;
float r = (position.x + position.width) * 2.0f / target.width - 1;
float b = position.y * 2.0f / target.height - 1;
float t = (position.y + position.height) * 2.0f / target.height - 1;
var mat = new Matrix4x4();
mat.m00 = r - l;
mat.m03 = l;
mat.m11 = t - b;
mat.m13 = b;
mat.m23 = -1;
mat.m33 = 1;
// 绘制贴图
m_DrawTextureMateral.SetMatrix(Shader.PropertyToID("_ImageMVP"), GL.GetGPUProjectionMatrix(mat, true));
target.DiscardContents();
Graphics.Blit(source, target, m_DrawTextureMateral);
}
可以看到是通过修改blit时候使用的shader中的MVP矩阵来达到想要的效果。
这个API一开始让我认为是绘制的(-1,1),(1,-1),(-1,-1),(1,1)这四个点,然后MVP矩阵直接使用的identity,这样的话我只要将目标RT的缩放到source一样的大小,并且平移到source的位置就是想要的MVP矩阵。
但事实并非如此,且得到的矩阵与上图中代码所写的并不一致。
于是为了搞懂原因,对Graphic.Blit做了测试,故意vert中针对顶点转换到clip空间的操作屏蔽,直接返回v.vertex:
v2f_img vert (appdata_img v)
{
v2f_img o;
// o.pos = mul(_ImageMVP, v.vertex);
o.uv = v.texcoord;
o.pos = v.vertex;
return o;
}
会发现,Blit会将source绘制到右上角的1/4。结合源码可以确定blit所绘制的mesh的四个点为(0,0),(0,1),(1,0),(1,1).
而正常使用时却能覆盖整个RT,因此可以推导出Blit的内置MVP矩阵所作的为*2-1的操作,也即MVP矩阵为:
知道了Blit的MVP后,再结合上面所说红色部分的就可以得到需要传入的修改后的MVP矩阵:
(注:假设source的宽和高为(w0,h0). RT的宽和高为(w,h).
的理由相对简单,只是将RT的大小缩放到source一样大。
offsetX和offsetY其实就是将缩放后的Rect平移到指定位置:如下图将黄色的矩阵平移到绿色的。
实际上就是求绿色的中心点所在坐标。
X很显然为(position.x+position.width/2)/target.width. 得到的范围为(0,1),对应到clip空间下需要*2-1. 因此X = (2*position.x + position.width)/target,width - 1.
Y类似。
代入,最后
得到的即为代码中的矩阵(这里以3X3矩阵来做说明,因为不涉及到投影变换)。