Lesson 10: 着色器

原文地址:
http://blog.csdn.net/sinat_24229853/article/details/46941927

Direct3D要求使用着色器来渲染几何图形,为了解着色器的由来,我们首先要了解下图形流水线的历史。

图形流水线是什么?
图形流水线是GPU(图形处理器,显卡的心脏)工作的通用模型。它以某种形式表示的三维场景或物体为输入,输出二维的光栅化图像到显示器。

光栅化,就是把顶点数据转换为片元的过程。片元中的每一个元素对应于帧缓冲区中的一个像素。见下图
这里写图片描述

固定图形流水线有哪些流程?
1.图形流水线的起点是一个三维模型,三维模型中的顶点列表即是流水线的输入数据,从起点进入流水线。
2.顶点可以用来形成多边形,从而拟出近似的表面。由顶点形成多边形最常用的一种方法就是三角化,即每相邻的三个点组成一个三角形。接下来每个顶点要经过一系列的逐顶点操作,比如计算每个顶点的光照,每个顶点的坐标变换等。
3.由于显示输出的需要,用户会定义一个视口,即观察模型的位置和角度,然后模型被投影到与视口观察方向垂直的平面上。这个投影变换也是硬件加速的。根据视域的大小,投影的结果有可能被裁剪(clipping)。
4.接受模型投影的平面是一个帧缓存,它是一个由像素定义的光栅化平面。光栅化(rasterization)的过程,实际上就是决定帧缓存上的哪些像素该取怎样的值。通过采样和插值,光栅化器会决定一幅最接近原投影图像的位图。
5.这些像素或者由像素连成的片段还须经历一些逐片段操作,也就是说,它们的颜色也可以根据算法改变。另外,纹理映射在这一阶段也会覆盖某些像素的值。另外,对于投影和光栅化的结果,还要判断片段的可见性,也就是遮挡探测(occlusion detection)。
6.最后帧缓存里的结果被刷新到显示器上。该过程以较高的帧频率重复,用户就能在显示器上看到连续的图形变换。

这个过程可以简要地表示为图1-5所示的流水线。从顶点列表到最终显示,三维模型主要经历了逐顶点操作、投影变换、裁剪、光栅变换和逐片段操作,最后输出显示。随着日益复杂的图形处理要求和不断完善的硬件加速性能,有越来越多的功能被添加到图形流水线中,下图只概括了流水线的基本功能。
这里写图片描述

可编程图形流水线是什么?
下图表示一个可编程图形流水线,其中已经略去了与经典GPGPU方法无关的模块。流水线的功能模块用箭头串起来,表示工序流动的方向。灰色的模块为可编程模块。当然,除了图上所给出的模块之外,它还有其他的模块。这里,除了我们已经熟悉的这条流水线,图下图还添加了另一个重要的模块──纹理缓存(Texture Buffer)。
这里写图片描述
图中的两个着色器模块被涂成了灰色,这是为了引入一个重要的概念:可编程图形流水线。从结构上来说,上图的流水线模型从应用程序到帧缓存的部分同固定图形流水线中的功能一一对应,只是OpenGL使用了不同的术语来指代这些流水线组件和硬件之间的对应关系。支持可编程图形流水线的GPU就是可编程图形处理器。2001年之前,GPU都是功能固定的,或者是可设置的(configurable)。可编程GPU与它们最大的区别是,用户可以用自定义的算法来实现着色器的功能。在可编程图形流水线中,有两个模块是可以让用户加载自定义算法的,它们是顶点着色器和片段着色器。

着色器是什么?
着色器(shader),又叫着色单元,实际上就是GPU的处理器。一般情况下,一个GPU会有多个处理器(几十个甚至几百个),它们同时工作,体现了GPU大规模并行处理的能力。进行几何计算的处理器叫顶点着色器,它负责对顶点进行坐标变换、投影变换等;进行片段的颜色处理的叫做片段着色器(Direct3D中称其为像素着色器)。应用程序输入GPU的是三维的点云数据。从流水线输入端直到顶点着色器,流水线计算的对象都是三维几何模型;从光栅化器开始,所有的操作都是针对二维的像素了。

加载着色器需要以下步骤:
1.编译着色器:
我们首先需要编译两个着色器,顶点着色器以及像素着色器,这是渲染所需要用到的两个着色器。

我们通过调用D3DX11CompileFromFile()来完成编译,
函数的原型如下:

HRESULT D3DX11CompileFromFile(
    LPCTSTR pSrcFile,                // file containing the code
    D3D10_SHADER_MACRO *pDefines,    // advanced
    LPD3D10INCLUDE pInclude,         // advanced
    LPCSTR pFunctionName,            // name of the shader's starting function
    LPCSTR pProfile,                 // profile of the shader
    UINT Flags1,                     // advanced
    UINT Flags2,                     // advanced
    ID3DX11ThreadPump *pPump,        // advanced
    ID3D10Blob **ppShader,           // blob containing the compiled shader
    ID3D10Blob **ppErrorMsgs,        // advanced
    HRESULT *pHResult);              // advanced

第一个参数,HLSL着色器代码的载入和编译路径。
第二个参数,在着色器代码中的全局宏,该宏在HLSL 着色器中的工作方式与 C/C++中的宏一样。在应用程序内定义 HLSL 宏,使用类型D3D_SHADER_MACRO。
第三个参数,一个可选参数,用于用于处理 HLSL 着色器文件中的#include 语句。这个接口的主要作用就是在包含它的着色器源代码中打开和关闭文件。
第四个参数,你所编译的着色器入口函数名。一个文件可以包含多个着色器类型(例如,顶点,像素,几何着色器等)和用于多种目的的多个函数。
第五个参数,具体的着色器模型,对于我们的目的可以使用 vs_4_0或者vs_5_0来代表使用着色器 4.0 或者 5.0。为了支持着色器模型 5.0,必须需要支持 DirectX 11 的图形硬件单元。
第六个参数,着色编译标识。
第七个参数,特效文件标识,如果我们编译的着色器使用了特效文件就需要设置它。
第八个参数,指向一个线程 pump 的指针。若是设置为 NULL,则当编译完成时函数将返回。此参数处理多线程,在游戏开发中是一个很热门和高级的主题。使用线程允许我们异步加载着色器,调用此函数未返回时,可以继续后面的代码执行。
第九个参数,存放着色器编译完成后字节码的内存地址,它还包括所编译的着色器的任何调试和符号表信息。
第十个参数,存放编译过程中产生的错误和警告信息的内存地址。如果编译过程没有错误发生则该参数对象为 NULL,如果我们使用它来报告错误我们能够准备它。
第十一个参数,线程 pump 的返回值,如果线程 pump(第八个参数)不为 NULL,则该参数在异步执行完成前必须是一个有效地址。

使用例子:

ID3D10Blob *VS, *PS;
D3DX11CompileFromFile(L"shaders.shader", 0, 0, "VShader", "vs_4_0", 0, 0, 0, &VS, 0, 0);
D3DX11CompileFromFile(L"shaders.shader", 0, 0, "PShader", "ps_4_0", 0, 0, 0, &PS, 0, 0);

ID3D10Blob:blob是任意长度的内存块,以字节计,常用于诸如vertex buffer、adjacency以及shader code等数据。

我们将这段代码放在一个新的函数中:

void InitPipeline()
{
    // load and compile the two shaders
    ID3D10Blob *VS, *PS;
    D3DX11CompileFromFile(L"shaders.shader", 0, 0, "VShader", "vs_4_0", 0, 0, 0, &VS, 0, 0);
    D3DX11CompileFromFile(L"shaders.shader", 0, 0, "PShader", "ps_4_0", 0, 0, 0, &PS, 0, 0);
}

2.将两个着色器封装到shader objects中:
每个着色器都被储存在了各自的COM接口对象中,这系列COM接口为ID3D11_____Shader:

// global
ID3D11VertexShader *pVS;    // the vertex shader
ID3D11PixelShader *pPS;     // the pixel shader

当我们有了这些接口之后,我们就可以像这样创建对象了:

void InitPipeline()
{
    // load and compile the two shaders
    ID3D10Blob *VS, *PS;
    D3DX11CompileFromFile(L"shaders.shader", 0, 0, "VShader", "vs_4_0", 0, 0, 0, &VS, 0, 0);
    D3DX11CompileFromFile(L"shaders.shader", 0, 0, "PShader", "ps_4_0", 0, 0, 0, &PS, 0, 0);

    // encapsulate both shaders into shader objects
    dev->CreateVertexShader(VS->GetBufferPointer(), VS->GetBufferSize(), NULL, &pVS);
    dev->CreatePixelShader(PS->GetBufferPointer(), PS->GetBufferSize(), NULL, &pPS);
}

3.绑定着色器:
通过调用Direct3D设备环境的VSSetShader函数来设置顶点着色器,调用PSSetShader来设置像素着色器。这两个函数都采用着色器作为参数,一个类型实例数组指针,和该数组的大小。

就像这样:

void InitPipeline()
{
    // load and compile the two shaders
    ID3D10Blob *VS, *PS;
    D3DX11CompileFromFile(L"shaders.shader", 0, 0, "VShader", "vs_4_0", 0, 0, 0, &VS, 0, 0);
    D3DX11CompileFromFile(L"shaders.shader", 0, 0, "PShader", "ps_4_0", 0, 0, 0, &PS, 0, 0);

    // encapsulate both shaders into shader objects
    dev->CreateVertexShader(VS->GetBufferPointer(), VS->GetBufferSize(), NULL, &pVS);
    dev->CreatePixelShader(PS->GetBufferPointer(), PS->GetBufferSize(), NULL, &pPS);

    // set the shader objects
    devcon->VSSetShader(pVS, 0, 0);
    devcon->PSSetShader(pPS, 0, 0);
}

记住pVS和pPS都是对象,所以我们最后必须对其进行释放:

void CleanD3D(void)
{
    swapchain->SetFullscreenState(FALSE, NULL);    // switch to windowed mode

    // close and release all existing COM objects
    pVS->Release();
    pPS->Release();
    swapchain->Release();
    backbuffer->Release();
    dev->Release();
    devcon->Release();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值