D3D10与Geometry Shader学习笔记(2)
华南理工大学,张嘉华 [email protected] QQ:188318005,欢迎和我多多交流,共同学习进步
贝赛尔曲线在图形绘制中经常用到,在这一节里面讲介绍如何在Geometry Shader下实现贝赛尔曲线的绘制.首先来看最简单的贝赛尔曲线,就是由四个点控制.
考虑到贝赛尔曲线输入是四个点,而我们这里是为二维图形绘制实时的贝赛尔曲线,因此可以用两个四维的顶点表示这四个二维的点,因为对于Geometry Shader输入的类型中point必须是一个点,line是两个点,triangle是三个点,也就是input数组的大小,而不存在四个元素的输入数组类型.在GS_Bezier()首先把两个四维的点转换为四个二维的点.接着根据贝赛尔曲线的函数,通过100次线段细分来逼近曲线,因此t=1/100,计算t1,t2为处于整段曲线的比例,接着利用贝赛尔曲线函数计算出每个线段的两端的两个点,接着Append到输出流,每输出一段线段都需要RestartStrip()一下,以确认以之前append的数据构成图元输出了.输出流这里定义为LineStream表示线段流.下面是HLSL文件,重点是Geometry Shader的代码:
struct GSIn
{
float4 pos : POSITION;
};
GSIn VS_Bezier(int i : TEXCOORD0)
{
GSIn Output;
Output.pos = pos[i];
return Output;
}
[maxvertexcount(100)]
void GS_Bezier(line GSIn input[2], inout LineStream<PS_INPUT> LineOutputStream)
{
float2 positions[4];
positions[0]=input[0].pos.xy;
positions[1]=input[0].pos.zw;
positions[2]=input[1].pos.xy;
positions[3]=input[1].pos.zw;
for(int i=0;i<=99;i++)
{
float t1=i*0.01;
float x1=positions[0].x*(1-t1)*(1-t1)*(1-t1)+positions[1].x*(1-t1)*(1-t1)*3*t1+positions[2].x*(1-t1)*3*t1*t1+positions[3].x*t1*t1*t1;
float y1=positions[0].y*(1-t1)*(1-t1)*(1-t1)+positions[1].y*(1-t1)*(1-t1)*3*t1+positions[2].y*(1-t1)*3*t1*t1+positions[3].y*t1*t1*t1;
float t2=(i+1)*0.01;
float x2=positions[0].x*(1-t2)*(1-t2)*(1-t2)+positions[1].x*(1-t2)*(1-t2)*3*t2+positions[2].x*(1-t2)*3*t2*t2+positions[3].x*t2*t2*t2;
float y2=positions[0].y*(1-t2)*(1-t2)*(1-t2)+positions[1].y*(1-t2)*(1-t2)*3*t2+positions[2].y*(1-t2)*3*t2*t2+positions[3].y*t2*t2*t2;
PS_INPUT output1;
output1.Pos=float4(x1,y1,0.0f,1.0f);
output1.color=float4(1,1,1,1);
output1.uv=float2(0,0);
LineOutputStream.Append(output1);
PS_INPUT output2;
output2.Pos=float4(x2,y2,0.0f,1.0f);
output2.color=float4(1,1,1,1);
output2.uv=float2(0,0);
LineOutputStream.Append(output2);
LineOutputStream.RestartStrip();
}
}
float4 PS_Bezier(PS_INPUT input) : SV_Target
{
return input.color;
}
technique10 Bezier
{
pass P0
{
SetVertexShader( CompileShader( vs_4_0, VS_Bezier() ) );
SetGeometryShader( CompileShader( gs_4_0, GS_Bezier() ) );
SetPixelShader( CompileShader( ps_4_0, PS_Bezier() ) );
SetBlendState( AdditiveBlending, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF );
SetDepthStencilState( DisableDepth, 0 );
}
}
下面是CPP文件,需要在其中调用IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_LINELIST )设置输入IA为线段列类型:
HRESULT G2DGraphics::DrawBezier(float x1,float y1,float x2,float y2,float x3,float y3,float x4,float y4,DWORD color)
{
HRESULT hr=S_OK;
ID3D10Device* pd3dDevice=GGraphicsDevice::GetDevice();
//透明则略过
if (color>>24==0)
return S_OK;
float fdevvicewidth=(float)(GGraphicsDevice::GetDeviceWidth());
float fdeviceheight=(float)(GGraphicsDevice::GetDeviceHeight());
float4 positions[4];
float4 colors[4];
float4 uvs[4];
positions[0].x=((float)(x1)/fdevvicewidth-0.5f)*2.0f;
positions[0].y=((float)(y1)/fdeviceheight-0.5f)*-2.0f;
positions[0].z=((float)(x2)/fdevvicewidth-0.5f)*2.0f;
positions[0].w=((float)(y2)/fdeviceheight-0.5f)*-2.0f;
colors[0]=ConvertColorFromDwordToFloat4(color);
positions[1].x=((float)(x3)/fdevvicewidth-0.5f)*2.0f;
positions[1].y=((float)(y3)/fdeviceheight-0.5f)*-2.0f;
positions[1].z=((float)(x4)/fdevvicewidth-0.5f)*2.0f;
positions[1].w=((float)(x4)/fdevvicewidth-0.5f)*2.0f;
colors[1]=ConvertColorFromDwordToFloat4(color);
g_VertexPositions->SetFloatVectorArray((float*)&positions,0,4);
g_VertexColors->SetFloatVectorArray((float*)&colors,0,4);
// Set the input layout
pd3dDevice->IASetInputLayout( g_pVertexLayout );
// Set vertex buffer
UINT stride = sizeof( UINT );
UINT offset = 0;
pd3dDevice->IASetVertexBuffers( 0, 1, &g_pVertexBuffer, &stride, &offset );
pd3dDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_LINELIST );
D3D10_TECHNIQUE_DESC techDesc;
g_pBezierTech->GetDesc( &techDesc );
for( UINT p = 0; p < techDesc.Passes; ++p )
{
g_pBezierTech->GetPassByIndex( p )->Apply(0);
pd3dDevice->Draw( 2, 0 );
}
return S_OK;
}
圆形在图形绘制中也经常用到,由半径和中心坐标控制,下面是实现的HLSL代码,同样也是通过把圆形细分为特定数量线段进行逼近,根据角度计算圆形上线段的两端顶点.
[maxvertexcount(100)]
void GS_Circle(point GSIn input[1], inout LineStream<PS_INPUT> LineOutputStream)
{
int number=40;
float2 position=input[0].pos.xy;
float radius=input[0].pos.z;
float dangle=3.1415926f*2.0f/number;
for(int i=0;i<number;i++)
{
float t1=i*dangle;
float x1=radius*cos(t1)+position.x;
float y1=radius*sin(t1)+position.y;
float t2=(i+1)*dangle;
float x2=radius*cos(t2)+position.x;
float y2=radius*sin(t2)+position.y;
PS_INPUT output1;
output1.Pos=float4(x1,y1,0.0f,1.0f);
output1.color=float4(1,1,1,1);
output1.uv=float2(0,0);
LineOutputStream.Append(output1);
PS_INPUT output2;
output2.Pos=float4(x2,y2,0.0f,1.0f);
output2.color=float4(1,1,1,1);
output2.uv=float2(0,0);
LineOutputStream.Append(output2);
LineOutputStream.RestartStrip();
}
}
下面是CPP文件,输入类型为D3D10_PRIMITIVE_TOPOLOGY_POINTLIST,因为只需要圆心坐标(x,y)和半径,用一个四维顶点就可以存储了.
HRESULT G2DGraphics::DrawCircle(float x,float y,float r,DWORD color)
{
HRESULT hr=S_OK;
ID3D10Device* pd3dDevice=GGraphicsDevice::GetDevice();
//透明则略过
if (color>>24==0)
return S_OK;
float fdevvicewidth=(float)(GGraphicsDevice::GetDeviceWidth());
float fdeviceheight=(float)(GGraphicsDevice::GetDeviceHeight());
float4 positions[4];
float4 colors[4];
float4 uvs[4];
positions[0].x=((float)(x)/fdevvicewidth-0.5f)*2.0f;
positions[0].y=((float)(y)/fdeviceheight-0.5f)*-2.0f;
positions[0].z=((float)(r)/fdevvicewidth)*2.0f;
colors[0]=ConvertColorFromDwordToFloat4(color);
g_VertexPositions->SetFloatVectorArray((float*)&positions,0,4);
g_VertexColors->SetFloatVectorArray((float*)&colors,0,4);
// Set the input layout
pd3dDevice->IASetInputLayout( g_pVertexLayout );
// Set vertex buffer
UINT stride = sizeof( UINT );
UINT offset = 0;
pd3dDevice->IASetVertexBuffers( 0, 1, &g_pVertexBuffer, &stride, &offset );
pd3dDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_POINTLIST );
D3D10_TECHNIQUE_DESC techDesc;
g_pCircleTech->GetDesc( &techDesc );
for( UINT p = 0; p < techDesc.Passes; ++p )
{
g_pCircleTech->GetPassByIndex( p )->Apply(0);
pd3dDevice->Draw( 1, 0 );
}
return S_OK;
}
介绍了两种简单的二维图形在Geometry Shader的绘制后,在学习笔记3里将介绍较为复杂的Geometry Shader下的LOD应用.