• Directx11教程(5) 画一个简单的三角形(1)


          在本篇教程中,我们将通过D3D11画一个简单的三角形。在D3D11中,GPU的渲染主要通过shader来操作(当然还有一些操作是由GPU固定管线完成,比如光栅化操作),最常用的shader操作是顶点shader(vertex shader)和像素shader(pixel shader)。其实shader就是在GPU中执行的代码,这些代码被driver编译成硬件依赖的机器码,最终被GPU中shader pipe执行,从而完成3D渲染。D3D11中shader是用一种类C的语言HLSL编写的。

         下面我们先来了解几个概念:

          三维物体的模型,通常3D对象都是通过mesh(三角形)来表示的。比如我们看到的一个渲染后的球体,实际上它是有许许多多的小的mesh组成。

         顶点缓冲就是一个buffer,用来存放3D物体的顶点数据。

         索引缓冲,就是对顶点缓冲的索引,可以用来减少渲染物体时候传入显存的顶点数量。比如一个正方体有8个顶点,同时也是由12个三角形组成(每个面2个三角形),我们渲染3D物体,在硬件层次都是通过三角形来渲染的,所以渲染这12个三角形,就需要36个顶点数据,但是通过索引缓冲,我们只需传入8个顶点,通过索引不同顺序的3个顶点,来实现12个三角形的渲染。

    [GPU和系统内存之间,通过PCIE总线连接,传入数据受总线宽度的影响,所以我们要尽可能减少传输数据的数量]

    程序的框架现在如下图所示:

    image 

    我们增加了3个类:

       ModelClass主要用来建立顶点缓冲、索引缓冲,准备渲染数据。

       CamerClass主要用来得到view矩阵,就是得到摄像机在三维空间的位置和方位。

       ColorShaderClass主要用来处理shader相关的代码。

    下面我们将贴出关键的程序代码:

    color.vs的代码如下:

    /////////////
    // GLOBALS //
    //shader中使用的全局变量都在定义在const buffer中
    //这样shader编译后,这些变量放在gpu的const buffer中
    /////////////
    cbuffer MatrixBuffer
    {
        matrix worldMatrix;
        matrix viewMatrix;
        matrix projectionMatrix;
    };

    //////////////
    // TYPEDEFS //
    //注意:POSITION, COLOR等是我们在定义顶点布局时定义的名字。
    //////////////
    struct VertexInputType
    {
        float4 position : POSITION;
        float4 color : COLOR;
    };

    struct PixelInputType
    {
        float4 position : SV_POSITION; //SV表示系统自动定义的格式。
        float4 color : COLOR;
    };

    ////////////////////////////////////////////////////////////////////////////////
    // Vertex Shader
    ////////////////////////////////////////////////////////////////////////////////
    PixelInputType ColorVertexShader(VertexInputType input)
    {
        PixelInputType output;
       

        //顶点坐标扩展成四个分量,并设置为1,以便矩阵运算
        input.position.w = 1.0f;

        // 乘以3个矩阵,得到clip空间的坐标
        output.position = mul(input.position, worldMatrix);
        output.position = mul(output.position, viewMatrix);
        output.position = mul(output.position, projectionMatrix);
       
        //直接输出顶点的颜色(顶点之间的颜色,会在光栅化阶段采用插值的方式计算
        output.color = input.color;
       
        return output;
    }

    color.ps的代码如下,代码非常简单,直接输出像素的颜色:

    //////////////
    // TYPEDEFS //
    //像素输入格式
    //////////////
    struct PixelInputType
    {
        float4 position : SV_POSITION;
        float4 color : COLOR;
    };


    ////////////////////////////////////////////////////////////////////////////////
    // Pixel Shader
    ////////////////////////////////////////////////////////////////////////////////
    float4 ColorPixelShader(PixelInputType input) : SV_TARGET
    {
        return input.color;
    }


    GraphicsClass.h代码如下:

    #pragma once

    #include <windows.h>
    #include "d3dclass.h"
    #include "cameraclass.h"
    #include "modelclass.h"
    #include "colorshaderclass.h"

    /////////////
    // GLOBALS //
    /////////////
    const bool FULL_SCREEN = false; //是否全屏
    const bool VSYNC_ENABLED = true; //是否垂直同步
    const float SCREEN_DEPTH = 1000.0f; //深度,远点
    const float SCREEN_NEAR = 0.1f; //深度,近点

    class GraphicsClass
        {
        public:
            GraphicsClass(void);
            GraphicsClass(const GraphicsClass&);
            ~GraphicsClass(void);
            bool Initialize(int, int, HWND);
            void Shutdown();
            bool Frame();

        private:
            bool Render();

           //定义一个D3DClass类成员变量
            D3DClass* m_D3D;
            CameraClass* m_Camera;
            ModelClass* m_Model;
            ColorShaderClass* m_ColorShader;

        };

    GraphicsClass.cpp主要代码如下:

           首先会计算三个矩阵:世界矩阵(模型坐标空间到世界坐标空间转化矩阵)、视点矩阵(世界坐标空间转化到视点坐标空间、或者说是摄像机坐标空间)、投影矩阵(视点坐标空间进行投影操作,在透视投影情况下,产生一个称作frustum的视锥体,在平行投影的情况下,产生一个长方体)。

          然后在Shader类中执行具体的渲染操作。


    bool GraphicsClass::Render()
        {

        D3DXMATRIX viewMatrix, projectionMatrix, worldMatrix;
        bool result;


        // 设置framebuffer.为浅蓝色
        m_D3D->BeginScene(0.0f, 0.0f, 0.5f, 1.0f);

        // 得到view矩阵.
       m_Camera->Render();

        // 得到3个矩阵.
        m_Camera->GetViewMatrix(viewMatrix);
        m_D3D->GetWorldMatrix(worldMatrix);
        m_D3D->GetProjectionMatrix(projectionMatrix);

        // 把模型顶点和索引缓冲放入管线,准备渲染.
        m_Model->Render(m_D3D->GetDeviceContext());

        // 用shader渲染.
        result = m_ColorShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix);
        if(!result)
            {
            return false;
            }

       
        //把framebuffer中的图像present到屏幕上.
        m_D3D->EndScene();

        return true;
        }

         ModelClass类的关键函数是InitializeBuffers和RenderBuffer,在初始化函数中产生顶点缓冲和索引缓冲,在RenderBuffer函数中,把缓冲绑定到管线,并且指定渲染体元类型。

    bool ModelClass::InitializeBuffers(ID3D11Device* device)
        {
        VertexType* vertices;
        unsigned long* indices;
        D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
        D3D11_SUBRESOURCE_DATA vertexData, indexData;
        HRESULT result;

        //首先,我们创建2个临时缓冲存放顶点和索引数据,以便后面使用。.

        // 设置顶点缓冲大小为3,一个三角形.
        m_vertexCount = 3;

        // 设置索引缓冲大小.
        m_indexCount = 3;

        // 创建顶点临时缓冲.
        vertices = new VertexType[m_vertexCount];
        if(!vertices)
            {
            return false;
            }

        // 创建索引缓冲.
        indices = new unsigned long[m_indexCount];
        if(!indices)
            {
            return false;
            }
        //创建顺时针方向的三角形,左手规则
        // 设置顶点数据.

        vertices[0].position = D3DXVECTOR3(-1.0f, -1.0f, 0.0f);  // 左下
        vertices[0].color = D3DXVECTOR4(1.0f, 1.0f, 0.0f, 1.0f);

        vertices[1].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f);  // 中上.
        vertices[1].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f);

        vertices[2].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f);  // 底右
        vertices[2].color = D3DXVECTOR4(0.0f, 1.0f, 1.0f, 1.0f);

       // 设置索引缓冲数据.
        indices[0] = 0;  // Bottom left.
        indices[1] = 1;  // Top middle.
        indices[2] = 2;  // Bottom right.

        // 设置顶点缓冲描述
        vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
        vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
        vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
        vertexBufferDesc.CPUAccessFlags = 0;
        vertexBufferDesc.MiscFlags = 0;
        vertexBufferDesc.StructureByteStride = 0;

       // 指向保存顶点数据的临时缓冲.
        vertexData.pSysMem = vertices;
        vertexData.SysMemPitch = 0;
        vertexData.SysMemSlicePitch = 0;

        // 创建顶点缓冲.
        result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
        if(FAILED(result))
            {
            return false;
            }

        // 设置索引缓冲描述.
        indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
        indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount;
        indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
        indexBufferDesc.CPUAccessFlags = 0;
        indexBufferDesc.MiscFlags = 0;
        indexBufferDesc.StructureByteStride = 0;

        // 指向存临时索引缓冲.
        indexData.pSysMem = indices;
        indexData.SysMemPitch = 0;
        indexData.SysMemSlicePitch = 0;

       // 创建索引缓冲.
        result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer);
        if(FAILED(result))
            {
            return false;
            }

       // 释放临时缓冲.
        delete [] vertices;
        vertices = 0;

        delete [] indices;
        indices = 0;

        return true;
        }

    void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext)
        {
        unsigned int stride;
        unsigned int offset;


       // 设置顶点缓冲跨度和偏移.
        stride = sizeof(VertexType);
        offset = 0;

        //在input assemberl阶段绑定顶点缓冲,以便能够被渲染
        deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);

        //在input assemberl阶段绑定索引缓冲,以便能够被渲染
        deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);

       // 设置体元语义,渲染三角形列表.
        deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

        return;
        }

    CameraClass类主要是得到视图矩阵,代码就不贴了。

        下面是本章的关键部分,shader代码部分,主要包括打开shader文件,编译shader文件,绑定常量缓冲,定义顶点布局,调用shader执行渲染操作等等。

    ColorShaderClass.cpp主要代码如下:

    bool ColorShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix,
        D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix)
        {
        bool result;

    这几个shader参数是我们在vs中定义在const buffer中的。

        // 设置shader参数.
        result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix);
        if(!result)
            {
            return false;
            }

        // 用shader渲染指定缓冲顶点
        RenderShader(deviceContext, indexCount);

        return true;
        }

    bool ColorShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename)
        {
        HRESULT result;
        ID3D10Blob* errorMessage;
        ID3D10Blob* vertexShaderBuffer;
        ID3D10Blob* pixelShaderBuffer;
        D3D11_INPUT_ELEMENT_DESC polygonLayout[2];
        unsigned int numElements;
        D3D11_BUFFER_DESC matrixBufferDesc;


       // 初始化指针为空.
        errorMessage = 0;
        vertexShaderBuffer = 0;
        pixelShaderBuffer = 0;

       // 编译vs代码.
        result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "ColorVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL,
            &vertexShaderBuffer, &errorMessage, NULL);
        if(FAILED(result))
            {
            // 如果vs编译失败,输出错误消息.
            if(errorMessage)
                {
                OutputShaderErrorMessage(errorMessage, hwnd, vsFilename);
                }
            // 如果没有任何错误消息,可能是shader文件丢失.
            else
                {
                MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK);
                }

            return false;
            }

        // 编译ps.
        result = D3DX11CompileFromFile(psFilename, NULL, NULL, "ColorPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL,
            &pixelShaderBuffer, &errorMessage, NULL);
        if(FAILED(result))
            {
            // 如果ps编译失败,输出错误信息.
            if(errorMessage)
                {
                OutputShaderErrorMessage(errorMessage, hwnd, psFilename);
                }
            // 如果没有任何错误消息,可能是shader文件丢失.
            else
                {
                MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK);
                }

            return false;
            }


       // 从缓冲创建vs shader.
        result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader);
        if(FAILED(result))
            {
            return false;
            }

        // 从缓冲创建ps shader.
        result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader);
        if(FAILED(result))
            {
            return false;
            }

        // 设置数据布局layout,以便在shader中使用.
        // 定义要和ModelClass中的顶点结构一致.
        polygonLayout[0].SemanticName = "POSITION"; //vs中的输入参数
        polygonLayout[0].SemanticIndex = 0;
        polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
        polygonLayout[0].InputSlot = 0;
        polygonLayout[0].AlignedByteOffset = 0;
        polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
        polygonLayout[0].InstanceDataStepRate = 0;

        polygonLayout[1].SemanticName = "COLOR";
        polygonLayout[1].SemanticIndex = 0;
        polygonLayout[1].Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
        polygonLayout[1].InputSlot = 0;
        polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
        polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
        polygonLayout[1].InstanceDataStepRate = 0;

       // 得到layout中的元素数量
        numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

        // 创建顶点输入布局.
        result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(),
            vertexShaderBuffer->GetBufferSize(), &m_layout);
        if(FAILED(result))
            {
            return false;
            }

        //释放顶点和像素缓冲.
        vertexShaderBuffer->Release();
        vertexShaderBuffer = 0;

        pixelShaderBuffer->Release();
        pixelShaderBuffer = 0;

        // 设置动态矩阵描述.
        matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
        matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType);
        matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
        matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
        matrixBufferDesc.MiscFlags = 0;
        matrixBufferDesc.StructureByteStride = 0;

        // 创建const buffer指针,以便访问shader常量.
        result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer);
        if(FAILED(result))
            {
            return false;
            }

        return true;
        }

    bool ColorShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix,
        D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix)
        {
        HRESULT result;
        D3D11_MAPPED_SUBRESOURCE mappedResource;
        MatrixBufferType* dataPtr;
        unsigned int bufferNumber;

        // 传入shader前,确保矩阵转置,这是D3D11的要求.
        D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
        D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
        D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);

        //  锁定常量缓冲,以便能够写入.
        result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
        if(FAILED(result))
            {
            return false;
            }

        // 得到const buffer指针.
        dataPtr = (MatrixBufferType*)mappedResource.pData;

        // 设置world,view以及projection矩阵.
        dataPtr->world = worldMatrix;
        dataPtr->view = viewMatrix;
        dataPtr->projection = projectionMatrix;

        // 解锁常量缓冲.
        deviceContext->Unmap(m_matrixBuffer, 0);

        // 设置常量缓冲位置.
        bufferNumber = 0;

        // 用更新后的值设置常量缓冲.
        deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);

        return true;
        }

    DrawIndexed函数会根据指定的索引缓冲、体元类型、相关shader,向GPU传送一个draw primitive的命令,从而真正开始体元的渲染操作。

    void ColorShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount)
        {
        // 绑定顶点布局.
        deviceContext->IASetInputLayout(m_layout);

        // 设置渲染使用vs和ps.
        deviceContext->VSSetShader(m_vertexShader, NULL, 0);
        deviceContext->PSSetShader(m_pixelShader, NULL, 0);

       // 渲染三角形
        deviceContext->DrawIndexed(indexCount, 0, 0);

        return;
        }

    程序运行后如下图所示:

    image

     

    完整的代码请参考:

    工程文件myTutorialD3D11_4

    代码下载:

    https://files.cnblogs.com/mikewolf2002/myTutorialD3D11.zip

  • 相关阅读:
    Hash索引与B-Tree索引
    Android -- taskAffinity
    Android -- getSystemService
    Android面试,与Service交互方式
    Quartz.NET开源作业调度框架系列(四):Plugin Job-转
    Quartz.NET开源作业调度框架系列(三):IJobExecutionContext 参数传递-转
    Quartz.NET开源作业调度框架系列(一):快速入门step by step-转
    Quartz.NET开源作业调度框架系列(二):CronTrigger-转
    quartz.net 的配置文件资料
    基于.net 的加载自定义配置-误操作
  • 原文地址:https://www.cnblogs.com/mikewolf2002/p/2389808.html
Copyright © 2020-2023  润新知