常见的一段代码
在以前的实现中,我们需要自己组织Vertex,对于2D Quad场景来说,一般组织这样的一组数据:
const ScreenVertex svDefault[4] = {
// x y z w u v
{ { -1.0f, 1.0f, 0.5f, 1.0f },{ 0.0f, 0.0f } }, // 0
{ { 1.0f, 1.0f, 0.5f, 1.0f },{ 1.0f, 0.0f } }, // 1
{ { -1.0f, -1.0f, 0.5f, 1.0f },{ 0.0f, 1.0f } }, // 2
{ { 1.0f, -1.0f, 0.5f, 1.0f },{ 1.0f, 1.0f } } // 3
};
// Vertex Buffer Layout
D3D11_BUFFER_DESC vbdesc;
ZeroMemory(&vbdesc, sizeof(vbdesc));
vbdesc.Usage = D3D11_USAGE_DYNAMIC;
vbdesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbdesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
vbdesc.MiscFlags = 0;
vbdesc.ByteWidth = sizeof(svDefault);
D3D11_SUBRESOURCE_DATA InitData;
InitData.pSysMem = svDefault;
InitData.SysMemPitch = 0;
InitData.SysMemSlicePitch = 0;
CHECK_HR(hr = m_pD3D11Device->CreateBuffer(&vbdesc, &InitData, &m_pDefaultVertex));
现在比较流行的写法是省略掉这部分代码,通过系统的顶点ID(SV_VertexID)和下面这一段HLSL Vertex Shader代码中来自动计算这些2D Quad顶点坐标系,包括TexCoord和NDC坐标。
//--------------------------------------------------------------------------------------
// ScreenVS.hlsl
//--------------------------------------------------------------------------------------
struct PS_INPUT
{
float4 Pos : SV_POSITION;
float2 Tex : TEXCOORD;
};
//--------------------------------------------------------------------------------------
// Vertex Shader
//--------------------------------------------------------------------------------------
PS_INPUT VS(uint vI : SV_VERTEXID)
{
PS_INPUT output = (PS_INPUT)0;
float2 texcoord = float2(vI & 1, vI >> 1);
output.Pos = float4((texcoord.x - 0.5f) * 2.0f, -(texcoord.y - 0.5f) * 2.0f, 0.0f, 1.0f);
output.Tex = texcoord;
return output;
}
SV_VERTEXID就是顶点序号,从0开始,比如对于2D的一个屏幕来讲,是一个矩形,有四个顶点,构成两个顺势针方向的三角形填充区域(
△
#
0
#
1
#
2
\triangle_{\#0\#1\#2}
△#0#1#2,
△
#
2
#
1
#
3
\triangle_{\#2\#1\#3}
△#2#1#3)。
上面的代码计算结果是:
SV_VERTEXID | texcoord/output.Tex | output.Pos: SV_POSITION |
---|---|---|
0 | (0, 0) | (-1, 1) |
1 | (1, 0) | (1, 1) |
2 | (0, 1) | (-1, -1) |
3 | (1, 1) | (1, -1) |
可以看到正好通过vertex id可以准确计算出Texel Coordinate System和NDC,的确省了很多代码。
texcoord是用来在原来的texture上选择那块三角形来填充, output.Pos SV_POSITION则是用来控制屏幕上哪些位置需要填充texture的素材。
一些简单的扩展
下面用具体的图来演示这两个坐标系的作用,假设这是原图:
这是素材(Texture)填充坐标系:
素材有两个可以用来填充的区域,分别是ABC和CBD,如果想要用同一个素材ABC填CBD的区域,只要稍微改一下text coord就行了。
SV_VERTEXID | texcoord/output.Tex | output.Pos: SV_POSITION |
---|---|---|
0 | (0, 0) | (-1, 1) |
1 | (1, 0) | (1, 1) |
2 | (0, 1) | (-1, -1) |
3 | (1, 1) → \to →(0,0) | (1, -1) |
对应的Vertex Shader可以简单直接的改为:
PS_INPUT VS(uint vI : SV_VERTEXID)
{
PS_INPUT output = (PS_INPUT)0;
float2 texcoord = float2(vI & 1, vI >> 1);
//output.Pos = float4((texcoord.x - 0.5f) * 2.0f, -(texcoord.y - 0.5f) * 2.0f, 0.0f, 1.0f);
//output.Tex = texcoord;
switch (vI)
{
case 0:
output.Tex = float2(0, 0);
output.Pos = float4(-1.0f, 1.0f, 0.0f, 1.0f);
break;
case 1:
output.Tex = float2(1, 0);
output.Pos = float4(1.0f, 1.0f, 0.0f, 1.0f);
break;
case 2:
output.Tex = float2(0, 1);
output.Pos = float4(-1.0f, -1.0f, 0.0f, 1.0f);
break;
case 3:
output.Tex = float2(0, 0);
output.Pos = float4(1.0f, -1.0f, 0.0f, 1.0f);
break;
}
return output;
}
这样可以看到如下显示效果:
如果是一个正方形的图片,可以看到没有变形的以对角线为对称轴的镜像映射图片,实际上D3D engine会以ABC填充对角线的左上部分,然后再以ABC再次填充对角线下部分。
这是屏幕渲染坐标系(SV_POSITION):
通过控制这个坐标系,可以做平移,变形,比如通过如下调整,可以把图像缩小到中心:
SV_VERTEXID | texcoord/output.Tex | output.Pos: SV_POSITION |
---|---|---|
0 | (0, 0) | (-1, 1) → \to → (-0.5, 0.5) |
1 | (1, 0) | (1, 1) → \to → (0.5, 0.5) |
2 | (0, 1) | (-1, -1) → \to → (-0.5, -0.5) |
3 | (1, 1) | (1, -1) → \to → (0.5, -0.5) |
代码就不贴了,可以参考上面的实现
再来个变形
SV_VERTEXID | texcoord/output.Tex | output.Pos: SV_POSITION |
---|---|---|
0 | (0, 0) | A (-1, 1) → \to → A’ (-0.5, 0.5) |
1 | (1, 0) | (1, 1) |
2 | (0, 1) | (-1, -1) |
3 | (1, 1) | D(1, -1) → \to → D’(-0.3, -0.75) |
输出的图像大概是:
结论
通过上面的介绍,应该能对用于2D场景中各个坐标系有进一步的了解,在后面的文章中还会进一步穿插讲解这三个坐标如何用在一些实际场景中。
原创不易,如果对你有帮助,望点赞和关注。