现在的位置: 首页 > 综合 > 正文

DX11_基于GPU_GeometryShader的3D精确拾取

2019年04月15日 ⁄ 综合 ⁄ 共 7107字 ⁄ 字号 评论关闭

 

    拾取是图形学里一个很常用的应用,在3D世界里选中我们想要的东西,枪战游戏中判断子弹是否射中敌人。

    这里我们讲下如何实现一个精确的拾取碰撞,不仅检测是否射中了这个物体,更进一步的返回射中了这个物体的上哪个三角形。在《COD》等比较优秀的枪战游戏中,我们都可以看到子弹是射中敌人身体的不同部位,敌人的身体会做出不同的反应。

     然后我们看看如何实现一个基于GPU的拾取,把拾取的所有计算都映射到GPU上去。这里我们分别用GeometryShaderComputeShader来做一个实现。这一篇先用GeometryShader来实现。

   程序主要是如果点击鼠标,选中了哪个三角形,就绘制它,其它三角形就以线框的方式绘制。程序截图如下:

  

    文章下面先讲下拾取的算法,然后再来讲如何在GeometryShader上实现这个算法。

    精确拾取算法说白了就判断一个射线是否与一个三角形相交。我们设T(u,v,w)=uA+vB+wC定义了三角形ABC平面上的点,其中(u,v,w)为该点的质心坐标且有u+v+w=1,当且仅当该点的质心坐标满足0<=u,v,w<=1时,点T位于三角形ACB里面。设u=1-v-w,则T(v,w)=A+v(B-A)+u(C-A)

    令两端点P,Q构成的有向线段定位为R(t)=P+t(Q-P),0<=t<=1.

    如果拾取到了,或者说我们射出去的射线PQ选中了三角形,那么有

    T(v,w)=R(t)

    则

    (P-Q)t+(B-A)v+(C-A)w=P-A变成矩阵形式:

   
[(P-Q) (B-A) (C-A)] [t v w]'=[P-A] (住 表示转置)

   
用克莱姆法则对t,v,w进行求解:

   
t=det[(P-A) (B-A ) (C-A)]/det[(P-Q) (B-A) (C-A)] (det表示行列式)

   
v=det[(P-Q) (P-A ) (C-A)]/det[(P-Q) (B-A) (C-A)] (det表示行列式

  w=det[(P-Q) (B-A ) (P-A)]/det[(P-Q) (B-A) (C-A)] (det表示行列式)

 

   
具体的负责判断拾取的Shader代码:

bool IntersectTriangle(float3 origin,float3 dir,float3 v0,float3 v1,float3 v2,out float t,out float u,out float v)

{

float3 edge1=v1-v0;

float3 edge2=v2-v0;

float3 qvec=cross(dir,edge2);

float det=dot(edge1,qvec);

if(det<0.001f && det>-0.001f)return false;

float invdet=1.0f/det;

//求u

float3 tvec=origin-v0;

u=dot(tvec,qvec);

u=u*invdet;

if(u<0.0f || u>1.0f)return false;

float3 pvec=cross(tvec,edge1);

//求v

 v=dot(dir,pvec);

v=v*invdet;

if(v<0.0f || ( u+v>1.0f ))return false;

float3 nvec=cross(edge1,edge2);

//求t

t=dot(tvec,nvec);

t=t*invdet;

return true;

}

    第二个问题是如何得到那条拾取射线。拾取射线的计算说白了从屏幕坐标->NDC坐标->视觉坐标->世界坐标->物体局部坐标。具体可以参考《Introduction to 3D Game Programming with DirectX 10》Chapter
15 - Picking
。因为推导比较琐碎,下面只是大概的带过。

    1NDC坐标转换到屏幕坐标的矩阵M

     反过来可以求Xndc,Yndc

    2再转换到视角坐标

    3使其zv=1

    4然后再乘以视觉坐标V的逆矩阵V(-1)把转换到世界坐标。

    5乘以相应物体的世界坐标W的逆矩阵W(-1)转换到局部坐标

    

    讲完了基本算法,我们来看下如何在GeometryShade中实现它。主要是通过GeometryShaderStreamOutput,来把选中的三角形输出到StreamOutput Buffer中,下面来看具体的负责拾取算法的technique。

technique11 IntersectTech

{

pass p0

{

  SetVertexShader(CompileShader(vs_4_0,VS_STREAMOUT()));

  SetGeometryShader(ConstructGSWithSO(CompileShader(gs_4_0,GS_STREAMOUT()),"POSITION.xyz;NORMAL.xyz;TEXCOORD.xy") );

  SetPixelShader(NULL);

  SetDepthStencilState(DisableDepth,0);

}

}

TriPoint VS_STREAMOUT(TriPoint vIn)

{

  return vIn;

}

//下面是本程序的重点之一,计算拾取射线,判断是否选中了该三角形,如果选中把它放到一个StreamOutput Buffer中。

[maxvertexcount(3)]

void GS_STREAMOUT(triangle TriPoint gIn[3],inout TriangleStream<TriPoint> triStream)

{

float3 pickOrigin=float3(0.0f,0.0f,0.0f);

float3 pickDirection;

pickDirection.x=(2.0f*winPos.x/backBufferDesc.x-1.0f)/projMtx[0][0];

pickDirection.y=(-2.0f*winPos.y/backBufferDesc.y+1.0f)/projMtx[1][1];

pickDirection.z=1.0f;

    //乘以视角逆矩阵

pickOrigin=mul(float4(pickOrigin,1.0f),invViewMtx).xyz;

pickDirection=mul(float4(pickDirection,0.0f),invViewMtx).xyz;

     //乘以世界逆矩阵

pickOrigin=mul(float4(pickOrigin,1.0f),invWorldMtx).xyz;

pickDirection=mul(float4(pickDirection,0.0f),invWorldMtx).xyz;

float u=0;

float v=0;

float t=0;

if(IntersectTriangle(pickOrigin,pickDirection,gIn[0].posW,gIn[1].posW,gIn[2].posW,t,u,v))

{

for(int i=0;i<3;i++)

{

triStream.Append(gIn[i]);

}

}

return ;

}

    下面来看下在Direct中的拾取绘制代码,主要就是3个technique,第一个是调用拾取的technique,来把选中的三角形放到StreamOutput Buffer中,第二个是绘制上面刚选中的三角形,第三个是以网格形式来绘制本来的模型,方便观察结果:

void Mesh::DrawPickMesh_GSBUF(CModelViewerCamera *pCamera,POINT cursor,POINT backDesc)

{

    //先设置相关变量

//calculate the matrix

D3DXMATRIX invWorld;

D3DXMatrixInverse(&invWorld,0,&m_World);

D3DXMATRIX viewMtx=*pCamera->GetViewMatrix();

D3DXMATRIX invViewMtx;

D3DXMatrixInverse(&invViewMtx,0,&viewMtx);

const D3DXMATRIX projMtx=*pCamera->GetProjMatrix();

//calculate the cursor and backbuffer

MFloat2 mcur;

MFloat2 mback;

mcur.x=(float)cursor.x;

mcur.y=(float)cursor.y;

mback.x=(float)backDesc.x;

mback.y=(float)backDesc.y;

//set the effect variable

m_pfxInterViewMtx->SetMatrix((float*)&viewMtx);

m_pfxInterInvViewMtx->SetMatrix((float*)&invViewMtx);

m_pfxInterProjMtx->SetMatrix((float*)&projMtx);

m_pfxInterWorldMtx->SetMatrix((float*)(&m_World));

m_pfxInterInvWorldMtx->SetMatrix((float*)&invWorld);

m_pfxInterWinPos->SetRawValue((void*)&mcur,0,sizeof(MFloat2));

m_pfxInterBackbufferDesc->SetRawValue((void*)&mback,0,sizeof(MFloat2));

//set the inputlayout,vertexbuffer,sobuffer,index buffer and so on.

m_pContext->IASetInputLayout(m_pInputLayout);

UINT offset=0;

m_pContext->SOSetTargets(1,&m_pSOBuffer,&offset);

UINT stride=m_pMesh11->GetVertexStride(0,0);

ID3D11Buffer *pVertexBuffer[1];

pVertexBuffer[0]=m_pMesh11->GetVB11(0,0);

m_pContext->IASetVertexBuffers(0,1,pVertexBuffer,&stride,&offset);

m_pContext->IASetIndexBuffer(m_pMesh11->GetIB11(0),m_pMesh11->GetIBFormat11(0),0);

D3DX11_TECHNIQUE_DESC techDesc;

D3D11_PRIMITIVE_TOPOLOGY primType;

SDKMESH_SUBSET *pSubSet;

//1调用一开始说的拾取算法的shader,把拾取到的三角形都放入m_pSOBuffer中。

m_pfxInterTech->GetDesc(&techDesc);

for(int i=0;i<techDesc.Passes;i++)

{

for(int subset=0;subset<m_pMesh11->GetNumSubsets(0);subset++)

{

pSubSet=m_pMesh11->GetSubset(0,subset);

m_pfxInterTech->GetPassByIndex(0)->Apply(0,m_pContext);

//get primitiveType

primType=CDXUTSDKMesh::GetPrimitiveType11((SDKMESH_PRIMITIVE_TYPE)pSubSet->PrimitiveType );

m_pContext->IASetPrimitiveTopology(primType);

m_pContext->DrawIndexed((UINT)pSubSet->IndexCount,0,(UINT)pSubSet->VertexStart);

}

}

//clear streamout buffer

ID3D11Buffer *ClearBuffer[1]={0};

m_pContext->SOSetTargets(1,ClearBuffer,&offset);

//2绘制上面选中的三角形

m_pfxWorldMtx->SetMatrix((float*)&m_World);

m_pfxViewMtx->SetMatrix((float*)pCamera->GetViewMatrix());

m_pfxProjMtx->SetMatrix((float*)pCamera->GetProjMatrix());

m_pfxMeshTech->GetDesc(&techDesc);

m_pContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

stride=sizeof(MeshVertex);

m_pContext->IASetVertexBuffers(0,1,&m_pSOBuffer,&stride,&offset);

for(int i=0;i<techDesc.Passes;i++)

{

m_pfxMeshTech->GetPassByIndex(0)->Apply(0,m_pContext);

m_pContext->DrawAuto();

}

//3 以网格的方式绘制原来的模型,只是作为显示用。

DrawFrameMesh(pCamera);

return;

}

 

 

//以网格的方式绘制原来的模型,只是作为显示用。

void Mesh::DrawFrameMesh(CModelViewerCamera* pCamera)

{

m_pfxWorldMtx->SetMatrix((float*)&m_World);

m_pfxViewMtx->SetMatrix((float*)pCamera->GetViewMatrix());

m_pfxProjMtx->SetMatrix((float*)pCamera->GetProjMatrix());

D3DX11_TECHNIQUE_DESC techDesc;

ZeroMemory(&techDesc,sizeof(D3DX11_TECHNIQUE_DESC));

UINT stride=m_pMesh11->GetVertexStride(0,0);

UINT offset=0;

ID3D11Buffer *pVertexBuffer[1];

pVertexBuffer[0]=m_pMesh11->GetVB11(0,0);

m_pContext->IASetVertexBuffers(0,1,pVertexBuffer,&stride,&offset);

m_pContext->IASetIndexBuffer(m_pMesh11->GetIB11(0),m_pMesh11->GetIBFormat11(0),0);

m_pContext->IASetInputLayout(m_pInputLayout);

m_pfxWireFrameTech->GetDesc(&techDesc);

SDKMESH_SUBSET *pSubSet=NULL;

for(int i=0;i<techDesc.Passes;i++)

{

for(int subset=0;subset<m_pMesh11->GetNumSubsets(0);subset++)

{

pSubSet=m_pMesh11->GetSubset(0,subset);

m_pfxWireFrameTech->GetPassByIndex(0)->Apply(0,m_pContext);

//get primitiveType

D3D11_PRIMITIVE_TOPOLOGY primType;

primType=CDXUTSDKMesh::GetPrimitiveType11((SDKMESH_PRIMITIVE_TYPE)pSubSet->PrimitiveType );

m_pContext->IASetPrimitiveTopology(primType);

//get material

ID3D11ShaderResourceView *pSV=NULL;

pSV=m_pMesh11->GetMaterial(pSubSet->MaterialID)->pDiffuseRV11;

m_pfxDiffTex->SetResource(pSV);

//draw the wireframe mesh

m_pContext->DrawIndexed((UINT)pSubSet->IndexCount,0,(UINT)pSubSet->VertexStart);

}

}

}

抱歉!评论已关闭.