zoukankan      html  css  js  c++  java
  • Directx11教程(54) 简单的基于GS的billboard实现

        本章我们用一个billboard的实现来学习D3D11中的GS。

        在VS shader中,我们输入的是顶点位置及顶点属性,输出的也是顶点位置及顶点属性。在GS shader中,我们输入的是体元(primitive,可以是点,线,三角形等等,凡是D3D11中允许的体元都可以使用), 输出顶点、顶点属性,以及体元信息。

        如下面两个图,左边对一个三角形做vs操作,则会对三个顶点v1,v2,v3分别执行顶点操作,右图对三角形做gs操作,则整个三角形做为输入,输出则为一个四面体(增加了3个三角形)。

    imageimage

            现在我们开始实现一个GS的billboard,原理很简单,用一个点表示billboard面的中心点,用1个参数表示billboard面的宽和高(宽=高),在gs输入中,我们以点做为primitive输入,宽,高做为顶点属性参数输入,最终生成2个完整的三角形表示billboard,同时也会在gs中生成纹理坐标,以便贴上树的纹理。

    image

           我先看看完整的shader 代码,其实只要看懂了shader执行过程,基本上就可以完成编程了,程序中其它代码只是调用一些函数而已:

         gstree.vs, vs代码非常简单,输入顶点,输出顶点,不做任何其他操作,好像一个bypass的过程,但是在D3D11中,vs和ps是两个必须有的shader过程。

    GeometryInputType treeVertexShader(VertexInputType input)
    {
        GeometryInputType output;

        // 直接把数据传到gs阶段
        output.centerW  = input.centerW;
        output.sizeW    = input.sizeW;

        return output;
    }

        gstree.gs中,我们会根据输入的点体元坐标,生成四个点及其纹理坐标,最后生成2个三角形,同时也会求出使得billboard始终面向摄像机的转化矩阵。注意:我们会把点体元的primitive id做为参数传递到ps中去,以便在贴纹理的时候,根据不同的id选择不同的纹理。

        这里,maxvertexcount表示一次gs调用最多产生的顶点数。每次gs调用产生顶点数可以不同,但不能超过这个设置的最大值

        GS shader至少包括两个参数,一个是体元输入参数,它可以是point, triangle, line,lineadj,triangleadj等体元类型,它的数据就是一个顶点数组,如果数组中1个顶点,体元是点,2个顶点,体元是线,3个顶点,体元是三角形,4个顶点就是邻接线,6个顶点就是邻接三角形,如下面函数中的point GeometryInputType gIn[1]。

         另一个是体元输出参数,它的修饰符是inout,它总是流类型,由一系列的顶点组成,流类型格式可以是TriangleStream,PointStream,LineStream,对于点和线来说,输出的体元总是strip,如果要生成list,则要通过RestartStrip()函数来模拟

         uint primID : SV_PrimitiveID是可选参数,表示输入体元的id。默认情况下,比如有n个三角形,则体元id就是0,…,n-1。

    [maxvertexcount(4)]
    void treeGeometryShader(point GeometryInputType gIn[1],    uint primID : SV_PrimitiveID,  inout TriangleStream<PixelInputType> triStream)
    {   

        //转化中心点到世界坐标系
        gIn[0].centerW =  mul(gIn[0].centerW, (float3x3)worldMatrix);

        //得到billboard的四个顶点
        float halfWidth  = 0.5f*gIn[0].sizeW.x;
        float halfHeight = 0.5f*gIn[0].sizeW.y;
       
        float4 v[4];
        v[0] = float4(-halfWidth, -halfHeight, 0.0f, 1.0f);
        v[1] = float4(+halfWidth, -halfHeight, 0.0f, 1.0f);
        v[2] = float4(-halfWidth, +halfHeight, 0.0f, 1.0f);
        v[3] = float4(+halfWidth, +halfHeight, 0.0f, 1.0f);
        //
        // 计算四个顶点的纹理坐标.
        //
        float2 texC[4];
        texC[0] = float2(0.0f, 1.0f);
        texC[1] = float2(1.0f, 1.0f);
        texC[2] = float2(0.0f, 0.0f);
        texC[3] = float2(1.0f, 0.0f);
        //
        // 计算使得billboad面向摄像机的世界矩阵
        //
        float3 up = float3(0.0f, 1.0f, 0.0f);
        float3 look = cameraPosition.xyz - gIn[0].centerW;
        look.y = 0.0f;
        look = normalize(look);
        float3 right = cross(up, look);
       
        matrix W;
        matrix gViewProj;
        gViewProj = mul(viewMatrix, projectionMatrix);
       
        W[0] = float4(right,          0.0f);
        W[1] = float4(up,             0.0f);
        W[2] = float4(look,           0.0f);
        W[3] = float4(gIn[0].centerW, 1.0f);
        //float4x4
        matrix WVP = mul(W,gViewProj);
       
        //
        // 转化顶点到世界坐标系,输出三角形带
        //
        PixelInputType gOut;
        [unroll]
        for(int i = 0; i < 4; ++i)
        {
            gOut.posH     = mul(v[i], WVP);
            gOut.posW     = mul(v[i], W);
            gOut.normalW  = look;
            gOut.texC     = texC[i];
            gOut.primID   = primID; //体元id
           
            triStream.Append(gOut);
        }
    }

    gstree.ps代码:

    float4 treePixelShader(PixelInputType pIn) : SV_Target
    {
        float4 diffuse = shaderTexture.Sample(SampleType, pIn.texC);

        // alpha值小于0.25,放弃该像素
        clip(diffuse.a - 0.25f);
       
        // 输出纹理颜色
       
        return diffuse;
    }

        其它的代码就是增加一个TreeMeshClass,表示树Mesh类,然后就是GSShaderClass,在这个类中,我们会装入gstree.vs,gstree.gs, gstree.ps,并传输const buffer以及相关设置。最后就是在GraphicsClass中,调用GSShaderClass渲染树。

    需要注意的是,在其它ShaderClass中,我们要在RenderShader函数中加入下面的代码:

    deviceContext->GSSetShader(NULL, NULL, 0);

    否则的话,用这些shader类渲染的物体,会使用GSShaderClass指定的gs,从而使得渲染结果出错。

    程序执行的结果如下:

    image

    完整的代码请参考:

    工程文件myTutorialD3D11_48

    代码下载:

    https://files.cnblogs.com/mikewolf2002/d3d1139-49.zip

         在myTutorialD3D11_49中,我们增加很多树,并使用纹理数组做为ps的输入,从而根据体元id号调用不同纹理贴图。

    gtree.ps的代码稍微有点不同:

    Texture2D shaderTexture[4];
    SamplerState SampleType;

    struct PixelInputType
    {
        float4 posH    : SV_POSITION;
        float3 posW    : POSITION;
        float3 normalW : NORMAL;
        float2 texC    : TEXCOORD;
        uint primID    : SV_PrimitiveID;
    };

    float4 treePixelShader(PixelInputType pIn) : SV_Target
    {
        // 从纹理行列式中得到
        //float3 uvw = float3(pIn.texC, pIn.primID%4);
        //float4 diffuse = gDiffuseMapArray.Sample(SampleType, uvw );
        int i = pIn.primID%4;
        float4 diffuse;
        if(i==0)
           diffuse = shaderTexture[0].Sample(SampleType, pIn.texC);
        else if(i==1)
           diffuse = shaderTexture[1].Sample(SampleType, pIn.texC);
        else if(i==2)
           diffuse = shaderTexture[2].Sample(SampleType, pIn.texC);
        else
           diffuse = shaderTexture[3].Sample(SampleType, pIn.texC);

        // alpha值小于0.25,放弃该像素
        clip(diffuse.a - 0.25f);
       
        // 输出纹理颜色
       
        return diffuse;
    }

    程序执行见面如下:

    image

    完整的代码请参考:

    工程文件myTutorialD3D11_49

    代码下载:

    https://files.cnblogs.com/mikewolf2002/d3d1139-49.zip

    在myTutorialD3D11_47中,我们修改之前的基于雾,流水、山的代码,再在山上加上树,程序执行后的最终界面如下:

    image

    完整的代码请参考:

    工程文件myTutorialD3D11_47

    代码下载:

    https://files.cnblogs.com/mikewolf2002/d3d1139-49.zip

     

  • 相关阅读:
    day 12 元组的魔法
    day 12 列表的魔法,及灰魔法
    day 11 Python课上练习解释与基础知识练习题试题一
    day 11 rang的用法和练习
    day 10 字符串的魔法
    day1 Python可变与不可变类型
    day1 数据类型
    Math对象
    Calendar对象
    Date对象
  • 原文地址:https://www.cnblogs.com/mikewolf2002/p/2518863.html
Copyright © 2011-2022 走看看