Shader 简介

Hanrea 发表于 2017-4-14 09:21:14 | 显示全部楼层 |阅读模式 [复制链接]
0 129
顶点着色器和像素着色器可以很容易地解释。在一般情况下,着色器是可以直接在图形处理单元(GPU)上运行的程序。换句话说,着色器类似一个汇编程序,它使您能够发送指令到一个中央处理单元(CPU)。事实上,着色器与汇编语言是非常相似的编程汇编语言。“快速3D演算,”讨论了汇编称为SIMD流指令扩展(SSE)的特殊形式。你可以认为着色器编程时一个特殊的汇编形式,这意味着它是运行在GPU上的。简而言之,你可以通过使用汇编编程的CPU,你也可以使用顶点和像素着色器的GPU编程。
EngineBUS enginebus EngineBUS enginebus都知道,但是一些限制应用,限制了我们控制整个设置在图形适配器上的3D渲染管线的能力。一个顶点着色和像素着色仅用于在3D管道的某些确切点,这意味着其他地方的某些图形渲染过程中仍然无法访问当前图形硬件。但是每一个新的着色器版本会介绍更多的功能着色器,从而进一步取代固定功能管线部分。
EngineBUS enginebus EngineBUS enginebus
EngineBUS enginebus EngineBUS enginebus
EngineBUS enginebus EngineBUS enginebus现在,它是谈论关于三维管道本身的时候了,这意味着你回答了当你把几何图形的顶点和索引列表发送到图形适配器后发生什么的问题。图3.10显示了一个三维管线的图像。在管道的开始,有一些顶点的数据被输入。此输入如果有一个活动着色器,那么这些输入被发送到顶点着色器。如果一个顶点着色器是不活跃,输入被发送到变换和灯光引擎,或短TNL。 TNL是内置的图形适配器转换顶点数据和计算顶点光照的能力。如果硬件不能够做到这一点,Direct3D将我们替我们管理它。
EngineBUS enginebus EngineBUS enginebus若干年前,和标准的软件TnL相比,硬件TnL引擎上的图形适配器提供了一个很大的处理速度,TNL在软件中完成。现在每图形适配器应该能够执行TNL等功能。TNL引擎被认为是固定功能管线的一部分,所谓/的固定功能管线(而不是一个灵活的管道)。从事实上,固定渲染管线的由来是程序员可以设置变量到硬件中去,然后仅仅影响其中一部分计算结果。这种限制代表了不能免费的访问图形硬件(例如写一个新的函数)这样的一种约束。相比之下,着色器若使用一个灵活的渲染管道,那么你直接访问GPU的指令,并适用于任何函数处理数据,你可以用一个数学公式表达一个着色器。
EngineBUS enginebus EngineBUS enginebus好吧,现在让我们回到运动的数据。下一步3D渲染管线中的处理是视口变换和视口大小的裁剪。正如第6章中提到,“引擎的渲染设备,”视口坐标从-1.0F到1.0F运行,预计所有更大或更小的在视域体之外的坐标的投影顶点,因此将被截去。
EngineBUS enginebus EngineBUS enginebus下一步还要区分固定功能渲染管线和具有所谓的多纹理单元与使用像素着色的灵活渲染管道。这一步计算像素现有的颜色,纹理色彩和像素着色器中每个像素光照强度都会被考虑进来。在这之后,如果雾化效果被激活,那么这个3D渲染管线会将雾的颜色应用于当前颜色。在此之后我们只是对像素进行了一些测试(例如,Alpha测试,模板测试和深度测试)。如果像素管理器打算将所有测试整到一块,像素会被混合帧缓冲区。简言之,固定功能渲染管线由两部分组成:TNL引擎和多重纹理映射单元。一个灵活的渲染管线用他们各自相对应的部分代替了原来的两组成部分:顶点着色和像素着色器。如图3.20所示,着色器完全取代他们的同行:如果你激活一个顶点着色器,TNL引擎将会被关闭;如果您使用像素着色器,多纹理单元将会被关闭。因此,如果你想使用一个顶点着色器,例如,你不得不要在顶点着色器中自己做所有的转换和所有的灯光。没有办法只能让一个顶点着色做一些工作,然后让TNL也做一些工作。这同样适用于多纹理单元与像素着色。如果您使用像素着色器,你必须自己计算像素的颜色,包括计算从多重纹理到单一像素颜色的那些颜色值。不要被吓坏了,它不是太困难。
EngineBUS enginebus EngineBUS enginebus图3.20显示了更多。最多只有一个顶点着色器可以被激活。(像素着色器也是如此,只能激活一个)没有办法可以结合几个顶点着色器或像素着色器到链表中。这儿也没有直接的方法将一个顶点着色器和一个像素着色器联系在一起。你可以发送一些数据到像素着色器(例如纹理坐标和类似的数据),当然,但只能通过3D渲染管线发送。从着色器发送任何数据到应用程序是不可能的。3D渲染管线对于在背面缓冲区结束的有效像素或裁剪像素就像是一个单行道。
EngineBUS enginebus EngineBUS enginebus作为最后一点,请记住,某些图形适配器没有一个真正的固定渲染管道。一些只使用了一个内置着色器去模拟固定的渲染管线。你可能已经猜到了,这样的着色器必须是能够相当普遍的处理所有的渲染状态设置。在任何情况下,你应该使用你自己定制的着色器,这样的着色器可以的具体的,因此执行会更有效。应用中,当你有一个使用了3D图形编程的重要的应用程序,这个程序也不太复杂,此时你可以想象一下GPU或CPU的限制情况。进一步想象一下,当你运行这个应用程序时它运行的难以置信的慢。许多的原因可能会导致应用程序运行缓慢,但所有这些潜在的原因分别被分为两大类:CPU限制或者GPU限制。
EngineBUS enginebus EngineBUS enginebusCPU的限制是指CPU做了太多诸如计算数据然后把数据传送到数据总线的工作。与此同时GPU却处于闲置状态。你忽略了您的图形适配器的能力; 在这种情况下,确切的说应用程序是CPU限制。在这种情况下,你可以在没有放慢帧速率的情况下从图形适配器的虚拟快速存取存储器(VRAM)中渲染额外的10,000三角形。这将导致在速度上没有影响,因为现在的GPU将有一些工作要做,而CPU仍然处于忙的状态。也许有人会说,他们拥有顶尖的图形适配器,因此就可以让它做所有的琐事。他们不在CPU上执行踢出或裁剪计算,而是使所有数据强制在显存中的计算。在这种情况下,应用程序将无法运行得更快。相反,GPU一直在不停的运转,努力完成任务,而CPU在该忙的时候却被挂起。在这种情况下,甚至渲染少量的几个三角形就会降低帧率,所以你的应用程序属于GPU限制。在GPU限制的情况下,他的瓶颈可能是一个过多重载着色器。执行越多指令和计算,那么它的速度就越慢。每一个顶点着色器要执行每个顶点,当然,如果有大量的顶点这个着色器以及固定渲染管线TNL会成为应用程序的瓶颈。每一个像素着色器会从图元中计算每个像素。记住,在这些计算完成以后深度缓存检测将会被执行。因此,所有那些像素的填充速率也可以成为应用程序的瓶颈。
EngineBUS enginebus EngineBUS enginebus3.4.5 顶点着色器的设计和实现
EngineBUS enginebus EngineBUS enginebus    着色器是游戏渲染引擎中的重要内容,一个完整的引擎必须要有支持着色器编程的功能。这里首先要介绍一下着色器的内容,在以前的显卡中,图像的呈现就像流水线一样,不能够进行编程。不过后来GPU出现了,那样就可以计算很多东东,也就可以进行编程了,着色器就是一个可编程的例子,他通过代码传给GPU进行计算,然后通过屏幕显示出来。就像在前面的例子中,我们创建了一个缓存数据(Vertex Buffer)将三角形的坐标传给GPU。在Directx 11 SDK中,支持三种基础着色器:顶点着色器(Vertex Shader),像素着色器(Pixel Shader),以及几何着色器(Geometry Shader)。顶点着色器通过顶点作为输入数据,只要将每个顶点缓存数据传入GPU就会执行;像素着色器使用一个像素作为输入数据;而几何着色器使用基元(primitive)作为输入,基元可以是一个点,一条线或一个三角形。这三个基础着色器在呈现时都会遇到,在Direct3D 11中,GPU必须包含一个正确的顶点和像素着色器,而几何着色器是可选的,因此这也是为什么我们先了解顶点着色器和像素着色器的原因。当然在DirectX中,还包含了其他着色器,如Hull Shader,Domain Shader(域着色器)用于曲面细分(有些地方叫做镶嵌tessellation),Compute Shader用于计算。
EngineBUS enginebus EngineBUS enginebus3.4.5.1 顶点着色器简介
EngineBUS enginebus EngineBUS enginebus在进行顶点着色器之前我们得明白什么是顶点着色器?首先顶点着色器是一组指令代码,这组指令代码在顶点被渲染时执行。然后同一时间内,只能激活一个顶点着色器。再次每个源顶点着色器最多拥有128条指令(DirextX8.1),而在DirectX9,则可以达到256条。
EngineBUS enginebus EngineBUS enginebus顶点着色器支持编程,可以把顶点着色器看出是C语言中的一个函数,通过顶点作为参数输入这个函数,并且返回编程后的顶点信息。当程序通过顶点着色器传入顶点缓冲数据时,GPU就会迭代顶点缓冲数据,并为每一个顶点执行一次着色器函数。顶点着色器可以做非常多的事情,最主要的是用于进行坐标变换等等,在Direct3D编程中会涉及到很多坐标的变换,如:将世界坐标转换为屏幕坐标等等。
EngineBUS enginebus EngineBUS enginebus顶点着色器可以提高渲染场景速度。 用顶点着色器你可以做布类仿真,高级别动画,实时修改透视效果(比如水底效果),高级光亮(需要像素着色器支持)简单说来,运作方式如下:当渲染一个顶点时,API会执行你在顶点着色器中所写的指令。依靠这种方法,你可以自己控制每个顶点,包括渲染,确定位置,是否显示在屏幕上。用一个文本编辑器就可以了!我建议使用notepad或者vs开发环境来创建和修改着色器。另外,必须拥有一个支持可编程着色器的显卡。写完着色器后,保存他。API就可以调用他了(Direct3D或OpenGL)。API通过一些函数来调用这些代码指令到硬件中。
EngineBUS enginebus EngineBUS enginebus3.4.5.2 顶点着色器设计的步骤
EngineBUS enginebus EngineBUS enginebus(1) 顶点类型自定义:
EngineBUS enginebus EngineBUS enginebus不同类型的顶点着色器需要对应的不同的描述类型的顶点,例如有的顶点需要纹理坐标,有点不需要纹理坐标,有的需要一对纹理坐标,有的要动画网格模型的骨骼索引,有点顶点需要颜色值等。如果我们用D3D中的顶点模式,一般来说都能满足我们的设计要求。但是对于一些特殊顶点结构还是需要自己定义一个顶点结构体。但是为了更好的可扩展性,现在我们在引擎中自己定义一些常用的顶点格式。如果我们需要一些特定的顶点格式,照同样的模式直接添加定义即可。在这个渲染引擎中顶点元素定义和顶点类型枚举示例如下:
EngineBUS enginebus EngineBUS enginebus顶点元素结构体的定义:
EngineBUS enginebus EngineBUS enginebustypedef struct VERTEX_TYPE {
EngineBUS enginebus EngineBUS enginebus   float    x, y, z;
EngineBUS enginebus EngineBUS enginebusfloat  vcN[3];
EngineBUS enginebus EngineBUS enginebus   float  tu, tv;
EngineBUS enginebus EngineBUS enginebus   } VERTEX;//有坐标、法向量、和纹理坐标
EngineBUS enginebus EngineBUS enginebus
EngineBUS enginebus EngineBUS enginebustypedef struct LVERTEX_TYPE {
EngineBUS enginebus EngineBUS enginebus   float    x, y, z;
EngineBUS enginebus EngineBUS enginebus         DWORD  Color;
EngineBUS enginebus EngineBUS enginebus   float  tu, tv;
EngineBUS enginebus EngineBUS enginebus   } LVERTEX;//顶点坐标、颜色和纹理坐标
EngineBUS enginebus EngineBUS enginebus
EngineBUS enginebus EngineBUS enginebus以上顶点类型枚举:
EngineBUS enginebus EngineBUS enginebustypedef enum HSVERTEXID_TYPE {
EngineBUS enginebus EngineBUS enginebus   VID_UU,       //不可移动不可发光顶点
EngineBUS enginebus EngineBUS enginebus   VID_UL,       //不可移动有颜色的顶点
EngineBUS enginebus EngineBUS enginebus   } HSVERTEXID;
EngineBUS enginebus EngineBUS enginebus
EngineBUS enginebus EngineBUS enginebus#define FVF_VERTEX  (D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1)
EngineBUS enginebus EngineBUS enginebus#define FVF_LVERTEX  (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1)
EngineBUS enginebus EngineBUS enginebus
EngineBUS enginebus EngineBUS enginebus(2) 创建顶点着色器的准备:
EngineBUS enginebus EngineBUS enginebus在接口是实现类中,我们定义了D3D顶点和D3D着色器对象,可以支持多个着色器。定义如下:
EngineBUS enginebus EngineBUS enginebus  private:
EngineBUS enginebus EngineBUS enginebus  LPDIRECT3DVDECL9      m_pDeclVertex;       //顶点定义
EngineBUS enginebus EngineBUS enginebus  LPDIRECT3DVDECL9      m_pDeclLVertex;      
EngineBUS enginebus EngineBUS enginebus
EngineBUS enginebus EngineBUS enginebus  LPDIRECT3DVERTEXSHADER9  m_pVShader[MAX_SHADER];//顶点着色器对象数组
EngineBUS enginebus EngineBUS enginebus  LPDIRECT3DPIXELSHADER9  m_pPShader[MAX_SHADER];//像素着色器对象数组
EngineBUS enginebus EngineBUS enginebus  UINT         m_nNumVShaders;//记录顶点着色器的数目
EngineBUS enginebus EngineBUS enginebus  UINT         m_nNumPShaders;
EngineBUS enginebus EngineBUS enginebusvoid    PrepareShaderStuff(void);
EngineBUS enginebus EngineBUS enginebus私有函数PrepareShaderStuff的功能就是为了在创建顶点或者像素着色器之前完成一些必要的准备工作。这些准备工作包括检测硬件是否支持、检测顶点着色器版本号、D3DVARTEXELEMENT9 顶点元素的定义。PrepareShaderStuff函数中代码实现如下:
EngineBUS enginebus EngineBUS enginebus首先获得硬件设备信息保存在结构体d3dCaps中
EngineBUS enginebus EngineBUS enginebusif (FAILED(m_pDevice->GetDeviceCaps(&d3dCaps))) {
EngineBUS enginebus EngineBUS enginebus         m_bCanDoShaders = false;
EngineBUS enginebus EngineBUS enginebus      return;
EngineBUS enginebus EngineBUS enginebus      }
EngineBUS enginebus EngineBUS enginebus检测信息体中的版本号,不能小于1.1,否则不支持顶点着色器
EngineBUS enginebus EngineBUS enginebusif (d3dCaps.VertexShaderVersion < D3DVS_VERSION(1,1) ) {
EngineBUS enginebus EngineBUS enginebus       m_bCanDoShaders = false;
EngineBUS enginebus EngineBUS enginebus      return;
EngineBUS enginebus EngineBUS enginebus      }   
EngineBUS enginebus EngineBUS enginebus定义D3DVERTEXELEMENT9顶点类型,声明顶点着色器的数据。如下代码所示,这个结构体中定义了一个顶点格式中的各个元素的属性,一次为流索引、数据偏移值、数据类型(如D3DDECLTYPE_FLOAT3)、处理方法(如D3DDECLMETHOD_DEFAULT)、语义(如D3DDECLUSAGE_POSITION)、语义索引。每一种类型的顶点都要进行这种顶点声明,这里只列举了两种,其实有很多种,或者自行定义的也可以声明。如下代码所示:
EngineBUS enginebus EngineBUS enginebusD3DVERTEXELEMENT9 declVertex[] =
EngineBUS enginebus EngineBUS enginebus      {
EngineBUS enginebus EngineBUS enginebus         { 0,  0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,  D3DDECLUSAGE_POSITION, 0},
EngineBUS enginebus EngineBUS enginebus         { 0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,  D3DDECLUSAGE_NORMAL,   0},
EngineBUS enginebus EngineBUS enginebus         { 0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT,  D3DDECLUSAGE_TEXCOORD, 0},
EngineBUS enginebus EngineBUS enginebus         D3DDECL_END()
EngineBUS enginebus EngineBUS enginebus      };
EngineBUS enginebus EngineBUS enginebus
EngineBUS enginebus EngineBUS enginebusD3DVERTEXELEMENT9 declLVertex[] =
EngineBUS enginebus EngineBUS enginebus      {
EngineBUS enginebus EngineBUS enginebus         { 0,  0, D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT,  D3DDECLUSAGE_POSITION, 0},
EngineBUS enginebus EngineBUS enginebus         { 0, 12, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT,  D3DDECLUSAGE_COLOR,    0},
EngineBUS enginebus EngineBUS enginebus         { 0, 16, D3DDECLTYPE_FLOAT2,   D3DDECLMETHOD_DEFAULT,  D3DDECLUSAGE_TEXCOORD, 0},
EngineBUS enginebus EngineBUS enginebus         D3DDECL_END()
EngineBUS enginebus EngineBUS enginebus      };
EngineBUS enginebus EngineBUS enginebusdeclLVertex[1]中的声明说明如下:数据来自数据流0,从数据流起始位置起偏移量为0处的位置开始,声明位置数据为三个浮点数(D3DDECLTYPE_FLOAT3),告诉tessellator复制顶点数据(D3DDECLMETHOD_DEFAULT),定义数据的用途为顶点数据(D3DDECLUSAGE_POSITION),并说明用途索引为0。D3DDECLEND()宏用来结束顶点声明。
EngineBUS enginebus EngineBUS enginebus然后准备工作的最后一步就是创建顶点定义到m_pDeclVertex:
EngineBUS enginebus EngineBUS enginebusm_pDevice->CreateVertexDeclaration(declVertex,   &m_pDeclVertex);
EngineBUS enginebus EngineBUS enginebus(3)创建顶点着色器
EngineBUS enginebus EngineBUS enginebus在这个渲染引擎中可以实现多个着色器,所以每个着色器都有唯一的一个索引号ID。所以创建顶点着色器是时候得需要确定这个ID索引值。着色器的创建包括四种形式,有是否从文件中创建和是否已经编译过,这两种选择来判定。在渲染接口中这个函数的定义为:
EngineBUS enginebus EngineBUS enginebusvirtual HRESULT CreateVShader(const void *pData, UINT nSize, bool bLoadFromFile, bool bIsCompiled,  UINT *pID)=0;                           
EngineBUS enginebus EngineBUS enginebuspData是文件名或者是数据流,nSize是数据大小,bLoadFromFile表示是否着色器是从文件中进行编译。bIsCompiled表示是否文件或数据流已经是被编译过的了。所以顶点着色器的创建可以被分为四种情况,而且每个新建是着色器都有唯一的一个索引ID。这个函数的具体实现如下:
EngineBUS enginebus EngineBUS enginebusLPD3DXBUFFER  pCode=NULL; // 包含经过汇编的着色器代码的缓存
EngineBUS enginebus EngineBUS enginebusLPD3DXBUFFER  pDebug=NULL; // 包含错误信息的缓存
EngineBUS enginebus EngineBUS enginebusDWORD        *pVS=NULL;//指向编译过的着色器数据流
EngineBUS enginebus EngineBUS enginebusHANDLE        hFile, hMap;//文件句柄
EngineBUS enginebus EngineBUS enginebus//检测是否有足够的着色器
EngineBUS enginebus EngineBUS enginebus   if (m_nNumVShaders >= (MAX_SHADER-1)) {
EngineBUS enginebus EngineBUS enginebus      Log("error:达到了着色器的最大数目, MAX_SHADER");
EngineBUS enginebus EngineBUS enginebus      return HS_OUTOFMEMORY;
EngineBUS enginebus EngineBUS enginebus      }
EngineBUS enginebus EngineBUS enginebus第一种情况:编译过(从文件)
EngineBUS enginebus EngineBUS enginebushFile = CreateFile((LPCTSTR)pData, GENERIC_READ,
EngineBUS enginebus EngineBUS enginebus                            0, 0, OPEN_EXISTING,
EngineBUS enginebus EngineBUS enginebus                            FILE_ATTRIBUTE_NORMAL, 0);
EngineBUS enginebus EngineBUS enginebus         if (hFile == INVALID_HANDLE_VALUE) {
EngineBUS enginebus EngineBUS enginebus                   return HS_FILENOTFOUND;
EngineBUS enginebus EngineBUS enginebus            }
EngineBUS enginebus EngineBUS enginebus hMap = CreateFileMapping(hFile,0,PAGE_READONLY, 0,0,0);//返回文件映射句柄
EngineBUS enginebus EngineBUS enginebus pVS = (DWORD*)MapViewOfFile(hMap,FILE_MAP_READ,0,0,0);//返回mapView的开始址
EngineBUS enginebus EngineBUS enginebus从文件中导入一个着色器,这个WinAPI函数CreateFile打开一个文件,获得访问这个文件的句柄。CreateFileMapping和MapViewOfFile这两个WinAPI函数在一个二进制文件上建立一个mapped view,通过这个view,我们可以从编译过的数据流中创建着色器对象。
EngineBUS enginebus EngineBUS enginebus第二种情况:编译过(从数据流)
EngineBUS enginebus EngineBUS enginebus这种情况最好办,此时输入参数pData就是我们需要的数据,pVS = (DWORD*)pData;
EngineBUS enginebus EngineBUS enginebus第三种情况:未编译过(从文件)
EngineBUS enginebus EngineBUS enginebus那么要做就是从文件中编译获得数据流:
EngineBUS enginebus EngineBUS enginebus     hrA = D3DXAssembleShaderFromFile((char*)pData,  NULL, NULL, 0,
EngineBUS enginebus EngineBUS enginebus                                         &pCode, &pDebug)
EngineBUS enginebus EngineBUS enginebus         }
EngineBUS enginebus EngineBUS enginebus如果函数成功,那么pCode指向包含汇编过的着色器代码缓存。然后再得到pCode缓存指针。
EngineBUS enginebus EngineBUS enginebus    if (SUCCEEDED(hrA)) {
EngineBUS enginebus EngineBUS enginebus         pVS = (DWORD*)pCode->GetBufferPointer();
EngineBUS enginebus EngineBUS enginebus         }
EngineBUS enginebus EngineBUS enginebus第四种情况:未编译过(从数据流)
EngineBUS enginebus EngineBUS enginebus这种情况只要对输入的数据流进行编译即可,然后获得指向汇编过后的数据缓存指针:
EngineBUS enginebus EngineBUS enginebus hrA = D3DXAssembleShader((char*)pData, nSize-1, NULL, NULL, 0, &pCode, &pDebug);
EngineBUS enginebus EngineBUS enginebus if (SUCCEEDED(hrA)) {
EngineBUS enginebus EngineBUS enginebus         pVS = (DWORD*)pCode->GetBufferPointer();
EngineBUS enginebus EngineBUS enginebus         }
EngineBUS enginebus EngineBUS enginebus经过以上四种情况之一,我们就能得到一个指向编译过的数据流指针pVB,然后用CreateVertexShader函数从数据中创建着色器对象,代码如下:
EngineBUS enginebus EngineBUS enginebus// 创建着色器对象
EngineBUS enginebus EngineBUS enginebus   if (FAILED(hrC=m_pDevice->CreateVertexShader(pVS,
EngineBUS enginebus EngineBUS enginebus                                  &m_pVShader[m_nNumVShaders]))) {                        
EngineBUS enginebus EngineBUS enginebus      if (hrC==D3DERR_INVALIDCALL)
EngineBUS enginebus EngineBUS enginebus         Log("INVALID_CALL");
EngineBUS enginebus EngineBUS enginebus      else if (hrC==D3DERR_OUTOFVIDEOMEMORY)
EngineBUS enginebus EngineBUS enginebus         Log("D3DERR_OUTOFVIDEOMEMORY");
EngineBUS enginebus EngineBUS enginebus      else if (hrC==E_OUTOFMEMORY)
EngineBUS enginebus EngineBUS enginebus         Log("E_OUTOFMEMORY");
EngineBUS enginebus EngineBUS enginebus      return HS_FAIL;
EngineBUS enginebus EngineBUS enginebus      }
EngineBUS enginebus EngineBUS enginebus//保存着色器索引ID
EngineBUS enginebus EngineBUS enginebus   if (pID) (*pID) = m_nNumVShaders;
EngineBUS enginebus EngineBUS enginebusm_nNumVShaders++
EngineBUS enginebus EngineBUS enginebus然后释放资源:
EngineBUS enginebus EngineBUS enginebus  if (bIsCompiled && bLoadFromFile) {
EngineBUS enginebus EngineBUS enginebus      UnmapViewOfFile(pVS);
EngineBUS enginebus EngineBUS enginebus      CloseHandle(hMap);
EngineBUS enginebus EngineBUS enginebus      CloseHandle(hFile);
EngineBUS enginebus EngineBUS enginebus      }
EngineBUS enginebus EngineBUS enginebus
EngineBUS enginebus EngineBUS enginebus(4)激活顶点着色器
EngineBUS enginebus EngineBUS enginebus顶点着色器是渲染引擎的一个可选内容,在设备不支持的情况下或者不想启用着色器功能,那么我们在接口中提供了激活顶点着色器的接口函数,ActivateVShader(UINT nID, HSVERTEXID VertexID),为渲染设备激活先前创建的着色器对象。以下是激活顶点着色器代码:
EngineBUS enginebus EngineBUS enginebus   if (!m_bCanDoShaders) return HS_NOSHADERSUPPORT;
EngineBUS enginebus EngineBUS enginebus   if (nID >= m_nNumVShaders) return HS_INVALIDID;
EngineBUS enginebus EngineBUS enginebus
EngineBUS enginebus EngineBUS enginebus   m_pVertexMan->ForcedFlushAll();
EngineBUS enginebus EngineBUS enginebus   switch (VertexID) {
EngineBUS enginebus EngineBUS enginebus      case VID_UU: {
EngineBUS enginebus EngineBUS enginebus         if (FAILED(m_pDevice->SetVertexDeclaration(m_pDeclVertex)))
EngineBUS enginebus EngineBUS enginebus            return HS_FAIL;
EngineBUS enginebus EngineBUS enginebus         } break;
EngineBUS enginebus EngineBUS enginebus       //其他类型省略
EngineBUS enginebus EngineBUS enginebus      }
EngineBUS enginebus EngineBUS enginebus   if (FAILED(m_pDevice->SetVertexShader(m_pVShader[nID])))
EngineBUS enginebus EngineBUS enginebus      return HS_FAIL;
EngineBUS enginebus EngineBUS enginebus   m_nActiveVShader = nID;
EngineBUS enginebus EngineBUS enginebus   m_bUseShaders = true;
EngineBUS enginebus EngineBUS enginebus   return HS_OK;
EngineBUS enginebus EngineBUS enginebus   }
EngineBUS enginebus EngineBUS enginebus首先是判断着色器ID是否有效,然后清除所有顶点缓存,为新的着色器打扫战场。然后激活顶点着色器我们还需要两步,第一就是调用Idirect3Ddevice9:: SetVertexDeclaration函数激活顶点声明对象,让Direct3D的知道它所应用数据格式和数据源是什么样的。第二就是调用Idirect3Ddevice9::SetVertexShader()函数激活一个nID索引的着色器对象。最后标志顶点着色器状态变量。
EngineBUS enginebus EngineBUS enginebus五 设计着色器功能
EngineBUS enginebus EngineBUS enginebus在我们的实例中都会使用到可编程式文本着色器语言,其中一个比较基础的设计如下:
EngineBUS enginebus EngineBUS enginebusvs.1.1                 
EngineBUS enginebus EngineBUS enginebusdcl_position  v0      // 说明位置数据在寄存器v0中
EngineBUS enginebus EngineBUS enginebus
EngineBUS enginebus EngineBUS enginebusdcl_normal    v3     //声明向量数据在v3寄存器中  
EngineBUS enginebus EngineBUS enginebusdcl_texcoord  v6     //声明纹理数据在v6寄存器中
EngineBUS enginebus EngineBUS enginebusdcl_tangent   v8     //声明切向量数据在v8寄存器中
EngineBUS enginebus EngineBUS enginebus
EngineBUS enginebus EngineBUS enginebus;用视图投影矩阵变换变换顶点位置
EngineBUS enginebus EngineBUS enginebusm4x4 oPos, v0, c0  
EngineBUS enginebus EngineBUS enginebus
EngineBUS enginebus EngineBUS enginebus;环境光做为漫反射光
EngineBUS enginebus EngineBUS enginebus    ;载入固定颜色
EngineBUS enginebus EngineBUS enginebusmov oD0, c4
EngineBUS enginebus EngineBUS enginebus
EngineBUS enginebus EngineBUS enginebus; texture coords for each
EngineBUS enginebus EngineBUS enginebus; stage we use, i.e. 0 and 1
EngineBUS enginebus EngineBUS enginebusmov oT0, v6     //将纹理的颜色赋值给纹理寄存器oT0     
EngineBUS enginebus EngineBUS enginebusmov oT1, v6     //
EngineBUS enginebus EngineBUS enginebus
EngineBUS enginebus EngineBUS enginebus该文件包含了三条数据运算指令和一个寄存器定义。着色器文件的第一条指令必须是着色器版本声明。vs指令用来声明顶点着色器的版本,此处为1_1。dcl_position指令把寄存器v0定义为顶点位置数据的源。dcl_texcoord  指令把寄存器v6定义为顶点位置数据的源。dcl_tangent指令把寄存器v8定义为顶点位置数据的源。m4x4指令用视/投影矩阵对物体的顶点进行变换。矩阵被载入并保存在常量寄存器c0, c1, c2, c3(如下所示)中。Mov指令把寄存器c4中的固定颜色复制到输出漫反射色寄存器oD0中。这导致输出顶点的颜色被改变。Mov指令将纹理寄存器v6中的数据复制到。用之前定义的着色器函数导入、编译就会得到一个简单纹理映射着色器。
EngineBUS enginebus EngineBUS enginebus3.4.6 像素着色器的设计
EngineBUS enginebus EngineBUS enginebus    在现代的显示器中,屏幕是有很多个正方形格子组成的,这些格式很小,我们把它叫做像素,每一个像素都包含着自己的颜色,他们之间不用相互依赖。其实当我们需要在屏幕上绘制一个三角形时,在屏幕上呈现的不确切是一个三角形,它的一些斜线其实是带锯齿的。
EngineBUS enginebus EngineBUS enginebus一个三角形,包;含了三个顶点,通过这三个顶点连在一起,即叫做光栅化。GPU首先需要知道哪些像素需要被呈现,然后将这些需要呈现(三角形内部)像素激活并赋予颜色值,像素着色器就是为了计算些像素需要的颜色。像素着色器通过输入的像素颜色值,然后计算颜色并且将其返回给绘图管线。像素管线参数一般由几何着色器返回,假如没有几何着色器,那么就通过顶点着色器返回。
EngineBUS enginebus EngineBUS enginebus
EngineBUS enginebus EngineBUS enginebus
EngineBUS enginebus EngineBUS enginebus
EngineBUS enginebus EngineBUS enginebus顶点着色器中通过SV_POSITION声明描述创建了一个float4的值用于返回像素的坐标位置,这将作为像素着色器的输入参数,这样就告之了GPU当前坐标,而像素着色器返回的一个颜色值,即也为float4数值,并且使用SV_TARGET声明描述,以表示将用于目标的呈现格式。
EngineBUS enginebus EngineBUS enginebus3.4.6.1 像素着色器简介
EngineBUS enginebus EngineBUS enginebus像素着色器也是一组指令,这组指令在顶点中像素被渲染时执行。在每个执行时间,都会有很多像素被渲染(像素的数目依靠屏幕的分辨率决定)。
EngineBUS enginebus EngineBUS enginebus像素着色器的指令和顶点着色器的指令非常接近。像素着色器不能像顶点着色器那样,单独存在。他们在运行的时候,必须有一个顶点着色器被激活。像素着色器过去是一种高级图形技术,专门用来提高渲染速度。和顶点着色器一样,使用像素着色器,程序员能自定义渲染每个像素。一个像素着色器操作顶点上单独的像素。和顶点着色器一样,像素着色器源代码也是通过一些API加载到硬件的。
EngineBUS enginebus EngineBUS enginebus也和顶点着色器一样,你只需要一个文本编辑器和支持着色器编程的显卡即可。同样,API(Direct3D OpenGL)加载像素着色器代码指令到硬件中。
EngineBUS enginebus EngineBUS enginebusPixel Shader ALU (像素着色器逻辑运算单元):下图显示了一个像素着色器运算逻辑单元 (ALU) 的平行管线结构。左边的是矢量管线,它操作矢量数据。矢量数据也被叫做颜色数据,包含三个数据通道( RGB )。右边的是一个标量管线,它操作单一一个 alpha 值。管线依照它所操作的数据种类而被分别称为颜色管线和 alpha 管线。
EngineBUS enginebus EngineBUS enginebus
EngineBUS enginebus EngineBUS enginebus图3.22像素着色器运算逻辑单元 (ALU) 的平行管线结构
EngineBUS enginebus EngineBUS enginebus解释说明如下:
EngineBUS enginebus EngineBUS enginebusInput Registers – 像素着色器寄存器 为 像素着色器 提供 GRBA 输入数据。
EngineBUS enginebus EngineBUS enginebusComponent Copy – 源寄存器选择器 把数据从一个通道复制到另一个通道,这个操作一般被叫做 交叉混合。
EngineBUS enginebus EngineBUS enginebusModify Data - 在一个指令被执行之前,源寄存器修饰器 修饰从源寄存器读入的数据。
EngineBUS enginebus EngineBUS enginebusExecute Instruction - 指令通常用于在像素着色器上实现算术和纹理地址操作。颜色和 alpha 管线并不一定执行相同的指令或者有相同的源寄存器。对于 PS.1.4 ,目的寄存器可能也可不同。
EngineBUS enginebus EngineBUS enginebusModify Result - 在执行结果被写入输出寄存器之前,指令修饰器修饰执行结果。
EngineBUS enginebus EngineBUS enginebusMasking - 目的寄存器写入掩码 能控制目的寄存器的哪个分量被指令写入。
EngineBUS enginebus EngineBUS enginebusOutput Register - 在着色器的末尾,像素着色器寄存器 r0 包含最后的输出结果(也就是最后的结果要写入到 r0 中)。
EngineBUS enginebus EngineBUS enginebus3.4.6.2 设计的步骤
EngineBUS enginebus EngineBUS enginebus像素着色器的设计实现步骤和顶点着色器几乎是一样的,对于顶点着色器的设计我们会稍微简略一些。
EngineBUS enginebus EngineBUS enginebus第一步: 顶点类型自定义和顶点着色器是一样的。
EngineBUS enginebus EngineBUS enginebus第二步: 创建顶点着色器的准备和顶点着色器一样,都是在PrepareShaderStuff函数中进行。
EngineBUS enginebus EngineBUS enginebus第三步:像素着色器的创建:
EngineBUS enginebus EngineBUS enginebus在这个渲染引擎中可以实现多个像素着色器,所以每个着色器都有唯一的一个索引号ID。所以创建顶点着色器是时候得需要确定这个ID索引值。着色器的创建包括四种形式,有是否从文件中创建和是否已经编译过,这两种选择来判定。在渲染接口中这个函数的定义为:
EngineBUS enginebus EngineBUS enginebusHRESULT HSD3D::CreatePShader(const void *pData, UINT nSize,
EngineBUS enginebus EngineBUS enginebus                              bool bLoadFromFile,
EngineBUS enginebus EngineBUS enginebus                              bool bIsCompiled, UINT *pID) {
EngineBUS enginebus EngineBUS enginebus可以看到函数定义几乎和顶点着色器一样。实际上他们创建的方法是一样的,也是分为四种情况编译和导入像素着色器数据,唯一不同的是,如果创建的是像素着色器,那么必须用IDirect3D9Device9::CreatePixelShader去建立最后的着色器对象。
EngineBUS enginebus EngineBUS enginebus第四步:激活像素着色器
EngineBUS enginebus EngineBUS enginebus和顶点着色器一样,我们为渲染接口提供了一个激活函数。和顶点着色器差不多,但是调用IDirect3D9Device9::SetPixelShader()函数激活一个已经创建的像素着色器。实现如下:
EngineBUS enginebus EngineBUS enginebusHRESULT HSD3D::ActivatePShader(UINT nID) {
EngineBUS enginebus EngineBUS enginebus   if (!m_bCanDoShaders) return HS_NOSHADERSUPPORT;
EngineBUS enginebus EngineBUS enginebus   if (nID >= m_nNumPShaders) return HS_INVALIDID;
EngineBUS enginebus EngineBUS enginebus   //清除之前所有缓存  
EngineBUS enginebus EngineBUS enginebus   m_pVertexMan->ForcedFlushAll();
EngineBUS enginebus EngineBUS enginebus
EngineBUS enginebus EngineBUS enginebus   if(FAILED(m_pDevice->SetPixelShader(m_pPShader[nID])))
EngineBUS enginebus EngineBUS enginebus      return HS_FAIL;
EngineBUS enginebus EngineBUS enginebus
EngineBUS enginebus EngineBUS enginebus   m_nActivePShader = nID;
EngineBUS enginebus EngineBUS enginebus   m_bUseShaders = true;
EngineBUS enginebus EngineBUS enginebus   return HS_OK;
EngineBUS enginebus EngineBUS enginebus   }
EngineBUS enginebus EngineBUS enginebus
EngineBUS enginebus EngineBUS enginebus
您需要登录后才可以回帖 登录 | Sign Up

本版积分规则

推荐阅读

QQ| Archiver|手机版|小黑屋| 引擎巴士 EngineBUS  

Powered by Discuz! X3.2© 2001-2013 Comsenz Inc.  

返回顶部 返回列表