GPU Shader 编程基础

转载自:http://www.cnblogs.com/youthlion/archive/2012/12/07/2807919.html

几个基本概念:

Vertex buffer:存储顶点的数组。当构成模型的所有顶点都放进vertex buffer后,就可以把vertex buffer送进GPU,然后GPU就可以渲染模型了。

Index buffer:这个buffer的作用是索引。记录每个顶点在vertex buffer中的位置。DX SDK里说使用index buffer可以增加顶点数据被缓存在显存里的概率,所以就效率而言应该使用index buffer。

Vertex Shader:vertex shader是一类小程序,主要用来把vertex buffer里的顶点变换到3D空间中去。也可以用vertex shader干点别的,比如算顶点的法向量。对于每个要处理的顶点,GPU都会调用vertex shader。比如5000个三角形的网格模型,每一帧就得调用vertex shader 15000次,每秒60帧的话,vertex shader还真得写得靠谱点。

Pixel Shader:是一般用来处理多边形颜色的小程序。对于场景里每个要画到屏幕上的可见像素,GPU都会调用Pixel Shader来处理。像着色、光照,还有大多数用在多边形上的其他效果,都要靠Pixel Shader来搞定。没错,这个东西也得写的靠谱点。效率,效率啊。要不可能GPU还没有CPU算得快。

HLSL:这就是用来写各种shader程序的语言了。HLSL程序包括全局变量、类型定义、vertex shaders,pixel shaders还有geometry shaders。

程序的整体结构:

overallstructure

这一次接着上一篇笔记的程序继续扩展。在这篇笔记中,我们会用shader绘制一个绿色的三角形。这里三角形是要绘制的对象,实际上是一个简单的多边形模型,也就是数据,所以把它封装到一个模型类(ModelClass)中,并集成到文档类里去。视图类负责显示,而显示的功能是由shader完成的。正如前面说的,shader是一段小程序,上面的图中集成到视图类中的ColorShaderClass负责调用shader,也就是让这段shader小程序运行起来。这就是这篇笔记中程序的最宏观结构。

第一个shader程序:

在工程中添加一个名为color.fx的源文件,看来shader程序源文件的扩展名是.fx了。这个shader的目的是绘制一个绿色的三角形。

这个shader首先声明了三个全局矩阵变量,便于其他类从外部访问,再传回shader。

/
// GLOBALS //
/
matrix worldMatrix;
matrix viewMatrix;
matrix projectionMatrix;

下面几行代码里,使用HLSL中的float4类型创建了一个位置向量,包括x、y、z、w,以及一个颜色向量,包括red、green、blue、alpha分量。其中POSITION、COLOR和SV_POSITION是传递给GPU的语义信息,让GPU知道这些变量是干嘛用的。下面的两个类型貌似作用是一样的,但是必须分别创建。因为对于vertex shader和pixel shader,需要不同的语义。POSITION是对应vertex shader,SV_POSITION适用于pixel shader,COLOR则是两者通用。如果需要同一类型的多个成员,就得在类型后面加上个数值后缀,像COLOR0、COLOR1这种。

//
// TYPEDEFS //
//
struct VertexInputType
{
    float4 position : POSITION;
    float4 color : COLOR;
};
 
struct PixelInputType
{
    float4 position : SV_POSITION;
    float4 color : COLOR;
};

 

当vertex buffer中的数据被送进GPU进行处理的时候,GPU会调用vertex shader。下面定义了一个名为ColorVertexShader的函数,该函数在处理vertex buffer中每个顶点的时候都会被调用。vertex shader的输入必须与vertex buffer缓冲区中的数据以及shader源文件中的类型定义相匹配。这里就是VertexInputType。vertex shader的输出会被送进pixel shader,这里输出类型为上面定义的PixelInputType。

下面代码中的vertex shader流程是这样的:他先创建一个输出变量,类型为PixelInputType,然后拿到输入顶点的坐标,把世界矩阵、视点矩阵、投影矩阵挨个乘上去,对顶点进行变换,最后顶点会被变换到我们视点所观察的3D空间中的正确位置。然后拿到输入的颜色值,放进输出变量里,把输出变量返回,返回的输出变量接下来会被送进pixel shader。

// Vertex Shader
PixelInputType ColorVertexShader(VertexInputType input)
{
    PixelInputType output;
    
    
    // Change the position vector to be 4 units for proper matrix calculations.
    input.position.w = 1.0f;
 
    // Calculate the position of the vertex against the world, view, and projection matrices.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);
    
    // Store the input color for the pixel shader to use.
    output.color = input.color;
    
    return output;
}
接下来就是pixel shader接班,由pixel shader把多边形上那些要渲染到屏幕上的像素绘制出来。下面的这个pixel shader以PixelInputType作为输入,返回一个float4类型,这个float4就是最后的像素颜色值。下面这个pixel shader仅仅是把像素着色为输入的颜色值。重申,vertex shader的输出是pixel shader的输入。
// Pixel Shader
float4 ColorPixelShader(PixelInputType input) : SV_Target
{
    return input.color;
}

下面几行代码里的technique才是真正意义的shader。这个东西是用来渲染多边形、调用vertex shader和pixel shader的,可以把它看做是HLSL的main()函数。在technique里面可以设定多个pass,调用各种vertex shader和pixel shader来组合出想要的效果。这个例子里只使用了一个pass,也只调用了上面写好的vertex和pixel shader。geometry shader暂时不用,这里也没有调用。

还有个值得注意的事儿,代码里用vs_4_0指定vertex shader的版本为4.0,这是SetVertexShader函数的第一个参数。这样我们才可以使用DX10 HLSL中vertex shader4.0相应的功能。pixel shader也是类似。

// Technique
technique10 ColorTechnique
{
    pass pass0
    {
        SetVertexShader(CompileShader(vs_4_0, ColorVertexShader()));
        SetPixelShader(CompileShader(ps_4_0, ColorPixelShader()));
        SetGeometryShader(NULL);
    }
}

以上是这个例子的shader部分。也就是负责实际渲染工作的模块。那么shader渲染的是神马?恩,模型。

所以要在工程里再添加个模型类:

这个例子里,我们的模型仅仅是个三角形,暂时用原教程给的一个模型类ModelClass,后面如果需要,争取把这个模型类用CGAL的Polyhedron替换掉。下面先看一下ModelClass的头文件:

首先在ModelClass中添加顶点类型的定义。这也是vertex buffer的类型。

struct VertexType
{
    D3DXVECTOR3 position;
    D3DXVECTOR4 color;
};

构造和析构函数:

ModelClass();
ModelClass(const ModelClass&);
~ModelClass();

下面的几个函数负责初始化和释放模型的vertex和index buffer。Render函数负责把模型的几何属性送到显卡上,准备让shader绘制。

bool Initialize(ID3D10Device*);
void Shutdown();
void Render(ID3D10Device*);
 
int GetIndexCount();

上面的几个公有函数的功能通过调用下面的几个私有函数实现:

private:
    bool InitializeBuffers(ID3D10Device*);
    void ShutdownBuffers();
    void RenderBuffers(ID3D10Device*);

添加几个私有变量,分别作为vertex buffer和index buffer的指针,另外还有两个整型,用来记录两块buffer的大小。注意DX10里buffer一般用通用的ID3D10Buffer类型,这种类型的变量在创建的时候可以用buffer description进行描述。

private:
    ID3D10Buffer *m_vertexBuffer, *m_indexBuffer;
    int m_vertexCount, m_indexCount;

ModelClass类的实现部分,先是构造和析构函数:

ModelClass::ModelClass()
{
    m_vertexBuffer = NULL;
    m_indexBuffer = NULL;
}
 
 
ModelClass::ModelClass(const ModelClass& other)
{
}
 
 
ModelClass::~ModelClass()
{
}

初始化函数:

bool ModelClass::Initialize(ID3D10Device* device)
{
    bool result;
 
 
    // Initialize the vertex and index buffer that hold the geometry for the triangle.
    result = InitializeBuffers(device);
    if(!result)
    {
        return false;
    }
 
    return true;
}

释放buffer:

void ModelClass::Shutdown()
{
    // Release the vertex and index buffers.
    ShutdownBuffers();
 
    return;
}

Render函数实际是在框架的绘制模块里调用的,也就是我第二篇笔记中的视图类,再具体点,应该就是在视图类的OnPaint方法里。

void ModelClass::Render(ID3D10Device* device)
{
    // Put the vertex and index buffers on the graphics pipeline to prepare them for drawing.
    RenderBuffers(device);
 
    return;
}

GetIndexCount函数返回index的数量:

int ModelClass::GetIndexCount()
{
    return m_indexCount;
}

接下来是Initialize、ShutDown和Render对应的几个私有方法的具体实现,首先是InitializeBuffers,这个函数负责创建vertex buffer和index buffer。在实际的应用里,一般是从数据文件里把模型读进来(.off,.obj,.ply等等)然后创建buffer,在这个例子里,因为模型只是一个三角形,所以直接在vertex buffer和index buffer里人工设置了三个点。

bool ModelClass::InitializeBuffers(ID3D10Device* device)
{
    VertexType* vertices;
    unsigned long* indices;
    D3D10_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
    D3D10_SUBRESOURCE_DATA vertexData, indexData;
    HRESULT result;

首先创建两个数组,用来存储顶点和索引数据。后面会用这两个数组去填充最终的buffer。

// Set the number of vertices in the vertex array.
    m_vertexCount = 3;
 
    // Set the number of indices in the index array.
    m_indexCount = 3;
 
    // Create the vertex array.
    vertices = new VertexType[m_vertexCount];
    if(!vertices)
    {
        return false;
    }
 
    // Create the index array.
    indices = new unsigned long[m_indexCount];
    if(!indices)
    {
        return false;
    }

然后分别对顶点属性和顶点索引赋值。留心,下面的代码是按照顺时针的顺序创建顶点的。如果逆时针创建的话,程序会认为这个三角形是屁股朝着屏幕。如果恰好又设置了背面剔除的话,程序就不再绘制这个三角形了。所以说,被送进GPU的顶点顺序是有讲究的。

// Load the vertex array with data.
    vertices[0].position = D3DXVECTOR3(-1.0f, -1.0f, 0.0f);  // Bottom left.
    vertices[0].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f);
 
    vertices[1].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f);  // Top middle.
    vertices[1].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f);
 
    vertices[2].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f);  // Bottom right.
    vertices[2].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f);
 
    // Load the index array with data.
    indices[0] = 0;  // Bottom left.
    indices[1] = 1;  // Top middle.
    indices[2] = 2;  // Bottom right.

vetex数组和index数组搞定后,可以用它们来创建vertex buffer和index buffer。两种buffer的创建方式是一样的:首先填好buffer的description。在这个description里面ByteWidth(buffer的大小)和BindFlags(buffer类型)必须得填对。填好description后,还要分别填一个subresource指针,这量个指针分别指向前面创建的vertex数组和index数组。description和subresource都填好之后,就可以用D3D device调用CreateBuffer,这个函数会返回指向新创建buffer的指针。

// Set up the description of the vertex buffer.
    vertexBufferDesc.Usage = D3D10_USAGE_DEFAULT;
    vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
    vertexBufferDesc.BindFlags = D3D10_BIND_VERTEX_BUFFER;
    vertexBufferDesc.CPUAccessFlags = 0;
    vertexBufferDesc.MiscFlags = 0;
 
    // Give the subresource structure a pointer to the vertex data.
    vertexData.pSysMem = vertices;
 
    // Now finally create the vertex buffer.
    result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
    if(FAILED(result))
    {
        return false;
    }
 
    // Set up the description of the index buffer.
    indexBufferDesc.Usage = D3D10_USAGE_DEFAULT;
    indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount;
    indexBufferDesc.BindFlags = D3D10_BIND_INDEX_BUFFER;
    indexBufferDesc.CPUAccessFlags = 0;
    indexBufferDesc.MiscFlags = 0;
 
    // Give the subresource structure a pointer to the index data.
    indexData.pSysMem = indices;
 
    // Create the index buffer.
    result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer);
    if(FAILED(result))
    {
        return false;
    }

vertex buffer和index buffer创建后,就可以卸磨杀驴,干掉vertex数组和index数组了:

// Release the arrays now that the vertex and index buffers have been created and loaded.
    delete [] vertices;
    vertices = 0;
 
    delete [] indices;
    indices = 0;
 
    return true;
}

接下来是负责释放vertex buffer和index buffer的ShutdownBuffers函数:

void ModelClass::ShutdownBuffers()
{
    // Release the index buffer.
    if(m_indexBuffer)
    {
        m_indexBuffer->Release();
        m_indexBuffer = 0;
    }
 
    // Release the vertex buffer.
    if(m_vertexBuffer)
    {
        m_vertexBuffer->Release();
        m_vertexBuffer = 0;
    }
 
    return;
}

下面是对应Render函数的私有函数RenderBuffers。这个函数的作用是把GPU中input assembler上的vertex buffer和index buffer设置为激活状态。一旦有了一块激活的vertex buffer,GPU就可以用我们写的HLSL shader去渲染这块buffer。RenderBuffers函数还规定了这些buffer的绘制方式,比如绘制三角形、绘制直线神马的。这一篇笔记里,我们在input assembler上激活index buffer和vertex buffer,并通过DX10的IASetPrimitiveTopology函数告诉GPU,这块buffer要以三角形的方式绘制。

void ModelClass::RenderBuffers(ID3D10Device* device)
{
    unsigned int stride;
    unsigned int offset;
 
 
    // Set vertex buffer stride and offset.
    stride = sizeof(VertexType); 
    offset = 0;
    
    // Set the vertex buffer to active in the input assembler so it can be rendered.
    device->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);
 
    // Set the index buffer to active in the input assembler so it can be rendered.
    device->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);
 
    // Set the type of primitive that should be rendered from this vertex buffer, in this case triangles.
    device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
 
    return;
}

Model类搞定。整理一下宏观的思路:现在我们有了模型,也有了shader,可以用shader去渲染模型了。

问题在于,shader是怎样开始运行的呢?

我们用下面这个ColorShaderClass类来调用shader。

这个类的Initialize和Shutdown两个成员函数完成对shader的初始化和关闭,Render成员函数负责设置shader的参数,然后用shader去绘制模型。

ColorShaderClass类包含的头文件及类声明如下:

#include <d3d10.h>
#include <d3dx10math.h>
#include <fstream>
using namespace std;
 
class ColorShaderClass
{
public:
    ColorShaderClass();
    ColorShaderClass(const ColorShaderClass&);
    ~ColorShaderClass();
 
    bool Initialize(ID3D10Device*, HWND);
    void Shutdown();
    void Render(ID3D10Device*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX);
 
private:
    bool InitializeShader(ID3D10Device*, HWND, WCHAR*);
    void ShutdownShader();
    void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);
 
    void SetShaderParameters(D3DXMATRIX, D3DXMATRIX, D3DXMATRIX);
    void RenderShader(ID3D10Device*, int);
 
private:
    ID3D10Effect* m_effect;
    ID3D10EffectTechnique* m_technique;
    ID3D10InputLayout* m_layout;
 
    ID3D10EffectMatrixVariable* m_worldMatrixPtr;
    ID3D10EffectMatrixVariable* m_viewMatrixPtr;
    ID3D10EffectMatrixVariable* m_projectionMatrixPtr;
};
这个类和模型类的结构类似。在Initialize函数里,真正负责shader初始化的是InitializeShader,我们要传给这个函数三个参数:device、窗口句柄和shader的文件名。
bool ColorShaderClass::Initialize(ID3D10Device* device, HWND hwnd)
{
    bool result;
 
 
    // Initialize the shader that will be used to draw the triangle.
    result = InitializeShader(device, hwnd, L"../02_01/color.fx");
    if(!result)
    {
        return false;
    }
 
    return true;
}

Shutdown调用ShutdownShader关闭shader:

void ColorShaderClass::Shutdown()
{
    // Shutdown the shader effect.
    ShutdownShader();
 
    return;
}

Render函数里做两件事:1、设置shader参数,通过SetShaderParameters完成;2、用shader绘制绿三角,调用RenderShader完成:

void ColorShaderClass::Render(ID3D10Device* device, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix)
{
    // Set the shader parameters that it will use for rendering.
    SetShaderParameters(worldMatrix, viewMatrix, projectionMatrix);
 
    // Now render the prepared buffers with the shader.
    RenderShader(device, indexCount);
 
    return;
}

在下面的InitializeShader函数中我们可以看到,shader实际上是在这里加载的。在这个函数里,我们还需要设置一个layout,这个layout需要与模型类及color.fx类中定义的顶点类相匹配:

bool ColorShaderClass::InitializeShader(ID3D10Device* device, HWND hwnd, WCHAR* filename)
{
    HRESULT result;
    ID3D10Blob* errorMessage;
    D3D10_INPUT_ELEMENT_DESC polygonLayout[2];
    unsigned int numElements;
    D3D10_PASS_DESC passDesc;
 
 
    // Initialize the error message.
    errorMessage = 0;

在D3DX10CreateEffectFromFile函数中,shader程序被编译为一个effect。这个函数的几个重要参数包括shader文件名、shader版本(DX10是4.0)、还要制定要把shader编译到哪个effect里去(对应ColorShaderClass类的m_effect成员)。如果在编译shader的过程中失败的话,D3DX10CreateEffectFromFile会把一条错误消息放到errorMessage里,我们会把这个字符串塞给另一个函数OutputShaderErrorMessage去输出错误信息。要是编译失败了,却还没有错误信息的话,可能是找不到shader文件,对这种情况我们会弹出一个对话框作为提示。

// Load the shader in from the file.
    result = D3DX10CreateEffectFromFile(filename, NULL, NULL, "fx_4_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, 
                        device, NULL, NULL, &m_effect, &errorMessage, NULL);
    if(FAILED(result))
    {
        // If the shader failed to compile it should have writen something to the error message.
        if(errorMessage)
        {
            OutputShaderErrorMessage(errorMessage, hwnd, filename);
        }
        // If there was  nothing in the error message then it simply could not find the shader file itself.
        else
        {
            MessageBox(hwnd, filename, L"Missing Shader File", MB_OK);
        }
 
        return false;
    }

当shader文件成功编译为effect后,就可以用这个effect找到shader里的那个technique。我们后面用这个technique进行绘制:

// Get a pointer to the technique inside the shader.
    m_technique = m_effect->GetTechniqueByName("ColorTechnique");
    if(!m_technique)
    {
        return false;
    }

下一步,shader所处理的顶点,还需要创建并设置一个layout。在这一篇笔记里,shader使用了一个位置向量和一个颜色向量,所以我们在layout中也要创建对应的元素,用来指明位置和颜色信息的内存占用情况。首先要填充的是语义信息,这样shader才能知道这个layout元素的用途。对于位置信息,我们使用POSITION,颜色信息用COLOR。另一个重要信息是格式,位置信息我们用DXGI_FORMAT_R32G32B32_FLOAT,颜色信息用DXGI_FORMAT_R32G32B32A32_FLOAT。最后要注意的是AlignedByteOffset,这个字段指定了buffer中数据存储的起点。对于本例来说,前12个字节是位置,随后的16个字节是颜色。这个字段可以用D3D10_APPEND_ALIGNED_ELEMENT代替,表示DX10会自动计算。layout的其他字段暂时不会用到,这里使用默认设置:

// Now setup the layout of the data that goes into the shader.
    // This setup needs to match the VertexType stucture in the ModelClass and in the shader.
    polygonLayout[0].SemanticName = "POSITION";
    polygonLayout[0].SemanticIndex = 0;
    polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
    polygonLayout[0].InputSlot = 0;
    polygonLayout[0].AlignedByteOffset = 0;
    polygonLayout[0].InputSlotClass = D3D10_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 = D3D10_APPEND_ALIGNED_ELEMENT;
    polygonLayout[1].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA;
    polygonLayout[1].InstanceDataStepRate = 0;

layout数组设置好之后,我们计算一下它包含的元素个数,然后用device创建input layout。

// Get a count of the elements in the layout.
    numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);
 
    // Get the description of the first pass described in the shader technique.
    m_technique->GetPassByIndex(0)->GetDesc(&passDesc);
 
    // Create the input layout.
    result = device->CreateInputLayout(polygonLayout, numElements, passDesc.pIAInputSignature, passDesc.IAInputSignatureSize, 
                       &m_layout);
    if(FAILED(result))
    {
        return false;
    }

下面要做的是获取shader里面那三个矩阵的指针,这样以后就能用这三个指针设置矩阵的值:

// Get pointers to the three matrices inside the shader so we can update them from this class.
    m_worldMatrixPtr = m_effect->GetVariableByName("worldMatrix")->AsMatrix();
    m_viewMatrixPtr = m_effect->GetVariableByName("viewMatrix")->AsMatrix();
    m_projectionMatrixPtr = m_effect->GetVariableByName("projectionMatrix")->AsMatrix();
 
    return true;
}

ShutdownShader函数负责释放资源:

void ColorShaderClass::ShutdownShader()
{
    // Release the pointers to the matrices inside the shader.
    m_worldMatrixPtr = 0;
    m_viewMatrixPtr = 0;
    m_projectionMatrixPtr = 0;
 
    // Release the pointer to the shader layout.
    if(m_layout)
    {
        m_layout->Release();
        m_layout = 0;
    }
 
    // Release the pointer to the shader technique.
    m_technique = 0;
 
    // Release the pointer to the shader.
    if(m_effect)
    {
        m_effect->Release();
        m_effect = 0;
    }
 
    return;
}

在编译vertex shader或pixelshader时若发生问题,错误信息由OutputShaderErrorMessage函数输出:

void ColorShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
{
    char* compileErrors;
    unsigned long bufferSize, i;
    ofstream fout;
 
 
    // Get a pointer to the error message text buffer.
    compileErrors = (char*)(errorMessage->GetBufferPointer());
 
    // Get the length of the message.
    bufferSize = errorMessage->GetBufferSize();
 
    // Open a file to write the error message to.
    fout.open("shader-error.txt");
 
    // Write out the error message.
    for(i=0; i<bufferSize; i++)
    {
        fout << compileErrors[i];
    }
 
    // Close the file.
    fout.close();
 
    // Release the error message.
    errorMessage->Release();
    errorMessage = 0;
 
    // Pop a message up on the screen to notify the user to check the text file for compile errors.
    MessageBox(hwnd, L"Error compiling shader.  Check shader-error.txt for message.", shaderFilename, MB_OK);
 
    return;
}
SetShaderParameters函数便于我们设置shader中的全局变量。这个函数中的三个矩阵是在上一篇笔记中的视图类里创建的,矩阵被创建之后,负责绘图的代码会调用这个函数,把这三个矩阵送进shader。
void ColorShaderClass::SetShaderParameters(D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix)
{
    // Set the world matrix variable inside the shader.
    m_worldMatrixPtr->SetMatrix((float*)&worldMatrix);
 
    // Set the view matrix variable inside the shader.
    m_viewMatrixPtr->SetMatrix((float*)&viewMatrix);
 
    // Set the projection matrix variable inside the shader.
    m_projectionMatrixPtr->SetMatrix((float*)&projectionMatrix);
 
    return;
}

SetShaderParameters函数执行后,各种参数(这里实际就是那仨矩阵)设置完成,ColorShaderClass类随后调用RenderShader,RenderShader通过technique指针调用color.fx文件中的shader程序。

RenderShader函数上来先把input layout激活,这样GPU才能知道vertex buffer里数据的格式。接下来要从shader中获取technique的描述,这个technique告诉GPU调用哪个vertex shader或pixel shader来绘制vertex buffer里的数据。本例中我们获取的是color.fx中ColorTechnique的描述,然后通过device调用DrawIndexed函数,循环调用technique中的各个pass来渲染三角形。目前的例子里,shader只有一个pass(pass0)。

void ColorShaderClass::RenderShader(ID3D10Device* device, int indexCount)
{
    D3D10_TECHNIQUE_DESC techniqueDesc;
    unsigned int i;
    
 
    // Set the input layout.
    device->IASetInputLayout(m_layout);
 
    // Get the description structure of the technique from inside the shader so it can be used for rendering.
    m_technique->GetDesc(&techniqueDesc);
 
    // Go through each pass in the technique (should be just one currently) and render the triangles.
    for(i=0; i<techniqueDesc.Passes; ++i)
    {
        m_technique->GetPassByIndex(i)->Apply(0);
        device->DrawIndexed(indexCount, 0, 0);
    }
 
    return;
}

到这里,我们搞定了一个HLSL shader,设置了vertex buffer和index buffer,并了解了如何调用shader绘制两种buffer中的数据。除此之外,还有一些辅助性的工作要做。第一个问题是,我们绘制的那些内容,是相对于哪个视点的?

好吧,所以我们还需要来个镜头类:

镜头类告诉DX10,镜头是从哪里、以及怎样去观察场景的。镜头类会始终跟踪镜头的位置及其旋转,使用位置和旋转信息生成一个视点矩阵,这个视点矩阵会传进shader,用于渲染。

镜头类声明如下:

#include <d3dx10math.h>
class CameraClass
{
public:
    CameraClass();
    CameraClass(const CameraClass&);
    ~CameraClass();
 
    void SetPosition(float, float, float);
    void SetRotation(float, float, float);
 
    D3DXVECTOR3 GetPosition();
    D3DXVECTOR3 GetRotation();
 
    void Render();
    void GetViewMatrix(D3DXMATRIX&);
 
private:
    float m_positionX, m_positionY, m_positionZ;
    float m_rotationX, m_rotationY, m_rotationZ;
    D3DXMATRIX m_viewMatrix;
};

其中,SetPosition和SetRotation函数用来设置镜头对象的位置和旋转。Render函数基于位置和旋转信息创建视点矩阵。GetViewMatrix用来访问视点矩阵。

构造函数把位置和旋转设置为场景的原点:

CameraClass::CameraClass()
{
    m_positionX = 0.0f;
    m_positionY = 0.0f;
    m_positionZ = 0.0f;
 
    m_rotationX = 0.0f;
    m_rotationY = 0.0f;
    m_rotationZ = 0.0f;
}
 
 
CameraClass::CameraClass(const CameraClass& other)
{
}
 
 
CameraClass::~CameraClass()
{
}

两个set函数:

void CameraClass::SetPosition(float x, float y, float z)
{
    m_positionX = x;
    m_positionY = y;
    m_positionZ = z;
    return;
}
 
 
void CameraClass::SetRotation(float x, float y, float z)
{
    m_rotationX = x;
    m_rotationY = y;
    m_rotationZ = z;
    return;
}

两个get函数:

D3DXVECTOR3 CameraClass::GetPosition()
{
    return D3DXVECTOR3(m_positionX, m_positionY, m_positionZ);
}
 
 
D3DXVECTOR3 CameraClass::GetRotation()
{
    return D3DXVECTOR3(m_rotationX, m_rotationY, m_rotationZ);
}

Render函数用位置和旋转信息构造和更新视点矩阵。这里除了位置和旋转,还需要指定一个“上”方向和镜头朝向。接下来,首先在原点处根据x,y,z的值旋转镜头,旋转之后再把镜头移动到三维空间中的指定位置上。当位置、旋转、方向“上”和所观察的位置都确定下来后,就可以用DX10中的D3DXMatrixLookAtLH函数创建视点矩阵了:

void CameraClass::Render()
{
    D3DXVECTOR3 up, position, lookAt;
    float yaw, pitch, roll;
    D3DXMATRIX rotationMatrix;
 
 
    // Setup the vector that points upwards.
    up.x = 0.0f;
    up.y = 1.0f;
    up.z = 0.0f;
 
    // Setup the position of the camera in the world.
    position.x = m_positionX;
    position.y = m_positionY;
    position.z = m_positionZ;
 
    // Setup where the camera is looking by default.
    lookAt.x = 0.0f;
    lookAt.y = 0.0f;
    lookAt.z = 1.0f;
 
    // Set the yaw (Y axis), pitch (X axis), and roll (Z axis) rotations in radians.
    pitch = m_rotationX * 0.0174532925f;
    yaw   = m_rotationY * 0.0174532925f;
    roll  = m_rotationZ * 0.0174532925f;
 
    // Create the rotation matrix from the yaw, pitch, and roll values.
    D3DXMatrixRotationYawPitchRoll(&rotationMatrix, yaw, pitch, roll);
 
    // Transform the lookAt and up vector by the rotation matrix so the view is correctly rotated at the origin.
    D3DXVec3TransformCoord(&lookAt, &lookAt, &rotationMatrix);
    D3DXVec3TransformCoord(&up, &up, &rotationMatrix);
 
    // Translate the rotated camera position to the location of the viewer.
    lookAt = position + lookAt;
 
    // Finally create the view matrix from the three updated vectors.
    D3DXMatrixLookAtLH(&m_viewMatrix, &position, &lookAt, &up);
 
    return;
}
GetViewMatrix:
void CameraClass::GetViewMatrix(D3DXMATRIX& viewMatrix)
{
    viewMatrix = m_viewMatrix;
    return;
}

至此,我们搞定了负责渲染的shader、负责调用shader的ColorShaderClass、用来存储模型的ModelClass,以及负责管理视点信息的CamaraClass。

下面的故事是,在MFC的MDI框架中,应该怎样用这些类?

从功能上看,文档和视图分别对应数据和显示,在这个例子里,模型是数据(ModelClass),ColorShaderClass实现显示(实际上是shader,color.fx),所以模型嵌入到文档类,而ColorShaderClass集成进视图类。

在文档类中添加ModelClass类指针,这里为了访问方便,直接设置为public:

#include "ModelClass.h"
class CMy02_01Doc : public CDocument
{
    ...
public:
    ModelClass * m_pMesh;
    ...
};

目前文档类要改的有三处:

构造函数:

CMy02_01Doc::CMy02_01Doc()
{
    // TODO: add one-time construction code here
    m_pMesh = NULL;
}

在新建文档时创建模型:

BOOL CMy02_01Doc::OnNewDocument()
{
    if (!CDocument::OnNewDocument())
        return FALSE;
 
    // TODO: add reinitialization code here
    // (SDI documents will reuse this document)
    m_pMesh = new ModelClass();
    return TRUE;
}

关闭文档时释放内存:

CMy02_01Doc::~CMy02_01Doc()
{
    if(m_pMesh)
    {
        m_pMesh->Shutdown();
        delete m_pMesh;
        m_pMesh = NULL;
    }
}

接下来是显示相关的内容。镜头类和ColorShaderClass类都集成到视图类中。

#include <CameraClass.h>
#include <Colorshaderclass.h>
 
class CMy02_01View : public CView
{
    ...
    CameraClass * m_Camara;
    ColorShaderClass * m_ColorShader;
    ...
};

构造函数:

CMy02_01View::CMy02_01View()
{
    // TODO: add construction code here
    ...
 
    m_Camara = NULL;
    m_ColorShader = NULL;
}

给视图类添加一个建立camera、shader对象并进行初始化的函数ShaderInitialize,由于模型是在文档类里创建和销毁的,所以这里只要弄一个临时指针指向模型对象就行了,不需要操心资源管理的事儿:

bool CMy02_01View::ShaderInitialize()
{
    bool result;
 
    HWND hwnd = GetSafeHwnd();
 
    // Create the camera object.
    m_Camera = new CameraClass();
    if(!m_Camera)
    {
        return false;
    }
 
    // Set the initial position of the camera.
    m_Camera->SetPosition(0.0f, 0.0f, -10.0f);
 
    // Create the model object.
    ModelClass * pmesh = ((CMy02_01Doc *)GetDocument())->m_pMesh;
    if(!pmesh)
    {
        MessageBox(L"NULL Model!!");
        return false;
    }
    result = pmesh->Initialize(m_device);
    if(!result)
    {
        MessageBox(L"Could not initialize the model object.");
        return false;
    }
 
    // Create the color shader object.
    m_ColorShader = new ColorShaderClass();
    if(!m_ColorShader)
    {
        return false;
    }
 
    // Initialize the color shader object.
    result = m_ColorShader->Initialize(m_device, hwnd);
    if(!result)
    {
        MessageBox(L"Could not initialize the color shader object.");
        return false;
    }
 
    return true;
}

这个初始化函数在OnInitialUpdate中DX环境初始化后调用:

void CMy02_01View::OnInitialUpdate()
{
    CView::OnInitialUpdate();
 
    // TODO: Add your specialized code here and/or call the base class
    InitDX();
    ShaderInitialize();
}

再添加一个相应的资源释放函数,该函数在视图类的析构函数中调用:

void CMy02_01View::ShutDownShader()
{
    // Release the color shader object.
    if(m_ColorShader)
    {
        m_ColorShader->Shutdown();
        delete m_ColorShader;
        m_ColorShader = NULL;
    }
 
    // Release the camera object.
    if(m_Camera)
    {
        delete m_Camera;
        m_Camera = 0;
    }
}

最后在OnPaint中完成绘制功能:

void CMy02_01View::OnPaint()
{
    D3DXMATRIX viewMatrix, projectionMatrix, worldMatrix;
 
    CPaintDC dc(this); // device context for painting
    // TODO: Add your message handler code here
    // Do not call CView::OnPaint() for painting messages
    float color[4];
 
 
    // Setup the color to clear the buffer to.
    color[0] = 1.0f;
    color[1] = 0.0f;
    color[2] = 0.0f;
    color[3] = 1.0f;
 
    // Clear the back buffer.
    m_device->ClearRenderTargetView(m_renderTargetView, color);
 
    // Clear the depth buffer.
    m_device->ClearDepthStencilView(m_depthStencilView, D3D10_CLEAR_DEPTH, 1.0f, 0);
 
    // Generate the view matrix based on the camera's position.
    m_Camera->Render();
 
    // Get the world, view, and projection matrices from the camera and d3d objects.
    m_Camera->GetViewMatrix(viewMatrix);
    GetWorldMatrix(worldMatrix);
    GetProjectionMatrix(projectionMatrix);
 
    ModelClass * pmesh = ((CMy02_01Doc *)GetDocument())->m_pMesh;
    pmesh->Render(m_device);
 
    // Render the model using the color shader.
    m_ColorShader->Render(m_device, pmesh->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix);
 
    if(m_vsync_enabled)
    {
        // Lock to screen refresh rate.
        m_swapChain->Present(1, 0);
    }
    else
    {
        // Present as fast as possible.
        m_swapChain->Present(0, 0);
    }
}

运行效果,弱爆了T_T

rundemo

下面理一理头绪,回忆一下,上面的shader、ColorShaderClass、ModelClass、CameraClass都是咋回事儿来着?

shader:

(1)它是一个扩展名为.fx的文件

(2)它的入口是technique,这玩意好比main

(3)shader里面还为vertex shader和pixel shader分别定义了顶点类型

(4)分别实现了vertex shader和pixel shader函数

(5)vertex buffer里的数据送进GPU后,会先让vertex shader处理,然后再送进pixel shader

(6)别忘了类型匹配那些事儿

ModelClass:

(1)这是一个模型类,虽然现在这个模型很简单

(2)模型类创建了vertex buffer(注意顶点顺序)和index buffer,并且设置了具体的值(也就是把三角形的各个顶点坐标和颜色值都写进了buffer里面)

(3)模型类激活了vertex buffer和index buffer,让GPU知道,这块数据可以进行绘制了。

(4)模型类告诉GPU,用三角形的方式绘制buffer里的内容

ColorShaderClass:

(1)ColorShaderClass是用来调用shader的

(2)ColorShaderClass要创建并设置与shader中定义的顶点类型匹配的layout,让GPU知道vertex buffer中数据的格式

(3)ColorShaderClass会获取shader中那三个全局矩阵的指针,并设置这三个矩阵的值

(4)ColorShaderClass会获取technique的描述,让GPU知道调用哪些shader函数去绘制,然后循环调用technique中的各个pass进行绘制

CameraClass:

(1)它会设置镜头位置和旋转角度

(2)它会根据镜头位置和旋转角度生成视点变换矩阵

最后,还有一幅恶心的大图,描述了程序的整个流程和关键数据的传输途径:

framework

PS:英文原文教程地址http://www.rastertek.com/dx10tut04.html,根据自己的需要进行了小小改动。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/313404.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Azure pipeline 配置根据条件执行脚本

Azure pipeline 配置根据条件执行脚本Intro我的应用通过 azure pipeline 来做持续集成&#xff0c;之前已经介绍了根据不同分支去打包不同的package&#xff0c;具体的就不再这里详细介绍了&#xff0c;可以参考 Solution来看一下修改之后的 azure-pipelines.yaml 示例配置吧&a…

C# 8 新特性 - 可空引用类型

Nullable Reference Type.在写C#代码的时候&#xff0c;你可能经常会遇到这个错误&#xff1a; 但如果想避免NullReferenceException的发生&#xff0c;确实需要做很多麻烦的工作。 可空引用类型 Null Reference Type 所以&#xff0c;C# 8的可空引用类型就出现了。 C# 8可以让…

统计学习笔记(1) 监督学习概论(1)

原作品&#xff1a;The Elements of Statistical Learning Data Mining, Inference, and Prediction, Second Edition, by Trevor Hastie, Robert Tibshirani and Jerome Friedman An Introduction to Statistical Learning. by Gareth JamesDaniela WittenTrevor Hastie andR…

.NET Core 3.0之深入源码理解ObjectPool(一)

写在前面对象池是一种比较常用的提高系统性能的软件设计模式&#xff0c;它维护了一系列相关对象列表的容器对象&#xff0c;这些对象可以随时重复使用&#xff0c;对象池节省了频繁创建对象的开销。它使用取用/归还的操作模式&#xff0c;并重复执行这些操作。如下图所示&…

Deep Boltzmann Machines

转载自&#xff1a;http://blog.csdn.net/win_in_action/article/details/25333671 http://blog.csdn.net/zouxy09/article/details/8775518 深度神经网络&#xff08;Deep neural network&#xff09; 深度学习的概念源于人工神经网络的研究。含多隐层的多层感知器就是一种…

.NET斗鱼直播弹幕客户端(下)

前言在上篇文章中&#xff0c;我们提到了如何使用 .NET连接斗鱼TV直播弹幕的基本操作。然而想要做得好&#xff0c;做得容易扩展&#xff0c;就需要做进一步的代码整理。本文将涉及以下内容&#xff1a;介绍如何使用 ReactiveExtensions&#xff08; Rx&#xff09;&#xff0c…

【 .NET Core 3.0 】框架之十 || AOP 切面思想

本文有配套视频&#xff1a;https://www.bilibili.com/video/av58096866/?p6前言上回《【 .NET Core3.0 】框架之九 || 依赖注入IoC学习 AOP界面编程初探》咱们说到了依赖注入Autofac的使用&#xff0c;不知道大家对IoC的使用是怎样的感觉&#xff0c;我个人表示还是比较可行…

[ASP.NET Core 3框架揭秘] 跨平台开发体验: Docker

对于一个 .NET Core开发人员&#xff0c;你可能没有使用过Docker&#xff0c;但是你不可能没有听说过Docker。Docker是Github上最受欢迎的开源项目之一&#xff0c;它号称要成为所有云应用的基石&#xff0c;并把互联网升级到下一代。Docker是dotCloud公司开源的一款产品&#…

统计学习笔记(4) 线性回归(1)

Basic Introduction In this chapter, we review some of the key ideas underlying the linear regression model, as well as the least squares approach that is most commonly used to fit this model. Basic form: “≈” means “is approximately modeled as”, to …

敏捷这么久,你知道如何开敏捷发布火车吗?

译者&#xff1a;单冰从事项目管理十几年&#xff0c;先后管理传统型项目团队及敏捷创新型团队。负责京东AI事业部敏捷创新、团队工程效率改进及敏捷教练工作。曾经负责手机端京东App项目管理工作5年&#xff0c;带领千人团队实施敏捷转型工作&#xff0c;版本发布从2个月提升为…

Newton Method in Maching Learning

牛顿方法&#xff1a;转自http://blog.csdn.net/andrewseu/article/details/46771947 本讲大纲&#xff1a; 1.牛顿方法(Newton’s method) 2.指数族(Exponential family) 3.广义线性模型(Generalized linear models) 1.牛顿方法 假设有函数&#xff1a;&#xff0c;我们希…

一键分享博客或新闻到Teams好友或频道

在最近的开发者工具更新中&#xff0c;Teams提供了一个Share to Teams的能力&#xff0c;就是在你的网页上面&#xff0c;放置一个按钮&#xff0c;用户点击后&#xff0c;就可以很方便地将当前网页或者你指定的其他网页&#xff0c;分享到Teams好友或频道中。这个开发文档在这…

C#刷遍Leetcode面试题系列连载(3): No.728 - 自除数

点击蓝字“dotNET匠人”关注我哟加个“星标★”&#xff0c;每日 7:15&#xff0c;好文必达&#xff01;前言前文传送门&#xff1a;上篇文章中我们分析了一个递归描述的字符串问题&#xff0c;今天我们来分析一个数学问题&#xff0c;一道除法相关的面试题。今天要给大家分析的…

【.NET Core 3.0】框架之十二 || 跨域 与 Proxy

本文有配套视频&#xff1a;https://www.bilibili.com/video/av58096866/?p8一、为什么会出现跨域的问题跨域问题由来已久&#xff0c;主要是来源于浏览器的”同源策略”。何为同源&#xff1f;只有当协议、端口、和域名都相同的页面&#xff0c;则两个页面具有相同的源。只要…

.NET 时间轴:从出生到巨人

点击上方蓝字关注“汪宇杰博客”“ 自1995年互联网战略日以来最雄心勃勃的事业—— 微软.NET战略, 2000年6月30日”2002-02-13.NET Framework 1.0CLR 1.0Visual Studio .NET关键词&#xff1a;跨语言、托管代码2003-04-24.NET Framework 1.1CLR 1.1Visual Studio 2003关键词&am…

Boltzmann Machine 入门(2)

发现RBM 中的能量函数概念需要从Hopfield网络的角度理解&#xff0c;于是找到 http://blog.csdn.net/roger__wong/article/details/43374343 和关于BM的最经典论文 http://www.cs.toronto.edu/~hinton/papers.html#1983-1976 一、限制玻尔兹曼机的感性认识 要回答这个问题大…

针对深度学习的GPU芯片选择

转自&#xff1a;http://timdettmers.com/2014/08/14/which-gpu-for-deep-learning/ It is again and again amazing to see how much speedup you get when you use GPUs for deep learning: Compared to CPUs 10x speedups are typical, but on larger problems one can achi…

C# 8 - Range 和 Index(范围和索引)

C# 7 的 Span C# 7 里面出现了Span这个数据类型&#xff0c;它可以表示另一个数据结构里连续相邻的一串数据&#xff0c;并且它是内存安全的。 例子&#xff1a; 这个图的输出是3&#xff0c;4&#xff0c;5&#xff0c;6。 C# 8 的Range类型 而C# 8里面我们可以从一个序列里面…

DCT变换学习

http://blog.csdn.net/timebomb/article/details/5960624 timebomb的博客 DCT变换的基本思路是将图像分解为88的子块或1616的子块&#xff0c;并对每一个子块进行单独的DCT变换&#xff0c;然后对变换结果进行量化、编码。随着子块尺寸的增加&#xff0c;算法的复杂度急剧上升…

敏捷回顾会议的套路与实践分享

01—关于敏捷回顾会议实践过敏捷的人都知道&#xff0c;在敏捷中会有很多的会议要开&#xff0c;比如计划会议&#xff08;Planning&#xff09;、站立会议&#xff08;Daily Scrum&#xff09;、评审会议&#xff08;Review&#xff09;以及回顾会议&#xff08;Retrospective…