• DX11_基于GPU_GeometryShader的3D精确拾取


     

        拾取是图形学里一个很常用的应用,在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);

    }

    }

    }

  • 相关阅读:
    Callback2.0
    设计模式之Composite
    设计模式之Proxy
    React Native DEMO for Android
    React Native 与 夜神模拟器的绑定
    Skipping 'Android SDK Tools, revision 24.0.2'; it depends on 'Android SDK Platform-tools, revision 20' which was not installed.
    .ui/qrc文件自动生成.py文件
    简单排序算法
    Big O
    设计模式之Adapter
  • 原文地址:https://www.cnblogs.com/bester/p/3255786.html
Copyright © 2020-2023  润新知