顶点着色器详解 (Vertex Shaders)
顶点着色器详解 (Vertex Shaders)
2014-2-9 20:56| 发布者: 隐龙| 查看: 1631| 评论: 0
学习了顶点处理,你就知道固定功能流水线怎么将顶点从模型空间坐标系统转化到屏幕空间坐标系统。虽然固定功能流水线也可以通过设置渲染状态和参数来改变最终输出的结果,但是它的整体功能还是受限。当我们想实现一个外来的光照模型,外来的Fog或者点大小计算方式,等等,我们可能就放弃使用固定功能流水线,转而使用CPU来实现这些计算。 使用vertex shaders,它用一段小程序替换固定功能处理。这段小程序的输入是模型空间的顶点,输出齐次剪裁空间的顶点,并且还携带一些信息,如:per-vertex diffuse 和 specualr,雾,透明度,纹理坐标和点大小。 这一节我们将先讲述vertex shaders的汇编语言编程模型。 Vertex Shader ArichitectureDirect3D对于不同的图形处理器有不同的vertex shaders架构版本。每个版本都有不同数目和类型的寄存器和不同的指令集。一般情况,高版本一般是低版本的衍生品,提供了更多的指令和更少限制。我们将先看完整的1.1版本,然后讨论各个版本在上面的增量。 DirectX 9.0c 支持的vertex shader版本包括1.1,2.0,2.x和3.0。 这些版本的汇编语言的语法标志是:vs_1_1, vs_2_0, vs_2_x和vs_3_0。在老的SDK和文档里面,你也许会看到vs_2_a和vs_2_b,他们已经融合到了vs_2_x版本里面了。当你安装SDK的时候,vertex shader的特殊版本也将会安装,如vs_2_sw和vs_3_sw,这两个版本只用于软件处理,专门用于做模拟和调试之用。shader的软件版本实现了2.0和3.0架构所有的功能,并且大部分的shader验证将被放开。
所有的架构都共享一个公共的执行模型。执行程序称做shader,它在每个顶点上执行一次。shader包含一个或多个指令,每个指令由一个操作码与0个或多个操作数组成的。shader可以访问五组不同的寄存器:顶点数据的input寄存器,渲染参数的const寄存器,用于查询const寄存器的地址寄存器,存储临时数据的临时寄存器,采样纹理的采样寄存器,shader输出结果的ouput寄存器。不同类型寄存器的数目如下表。
每个临时寄存器都存储一个四维的向量值,大多数指令都是在四维向量上进行操作。每个值都是一个浮点值,一般有6个小数数字。指令一般是通用算术运算,如加,乘和一般的向量计算(点积,向量矩阵乘法)。跟一般的CPU不一样的是,低版本的shader一般不支持流控制,以便于shader更加简单和容易硬件加速。
顶点组件通过合适的顶点声明映射到对应的semantics上。semantics使用dcl_usage指令与shader的输入寄存器关联。输入寄存器是只读的,只能用作顶点shader指令的数据源。虽然不同的操作数能应用到不同的修饰符,每个指令只能引用一个input regesiter。
不随着每个顶点变化的参数可以存放在const 寄存器。所有的shader版本都支持浮点const,整数const , bool const只能用在2.0以上的shader版本。每个指令一次只能访问一个const 寄存器,但是不同的源操作数可以访问带有修饰符的同一个const寄存器。 const寄存器值在shader 里面一般是通过def, defb和defi指令定义的,它也能来自于设备,通过方法:SetVertexShaderConstantF, SetVertexShaderConstantB 和 SetVertexShaderConstantI。你可以认为通过shader指令定义的值为local const,而通过设备方法定义的const为global const。 地址寄存器是一个带符号的整数,记录了距离base const寄存器的位置偏移量。const寄存器是只读的,地址寄存器是可写的。当地址寄存器越界,它的值将是(0,0,0,0)。在使用地址寄存器之前,必须先初始化它。 shader 1.1 只能使用地址寄存器的x组件来作为索引。并且地址寄存器只能被设置成mov指令的目的地,当使用它的时候它将进行四舍五入成整数。 shader 2.0以上的版本提供了更加通用的一种使用方式。寄存器的四个组件都可以用来作为索引,能够同时索引const寄存器的不同的部分。mova指令用于设置地址寄存器的值。
output寄存器用于存储shader计算的结果。output寄存器是可写的。它用来存储顶点的同次剪裁空间的坐标以及每个顶点相关的数据,如颜色,纹理坐标信息。3.0之前的shader版本,output寄存器将会分别命名。位置寄存器oPos, 颜色寄存器oD0和oD1,fog 寄存器oFog,点大小寄存器oPts和纹理坐标寄存器oT0到oT7。 每个顶点shader都得写oPos的四个组件。fog系数和点大小的缩放值将分别取oFog和oPts寄存器的x组件。oFog和oPts将被缩放到【0,1】区间。在shader 3.0版本,output寄存器将会使用dcl_usage指令定义。
顶点shader里面通常会有大量的工作。shader 通常会将数据从输入寄存器移动到临时寄存器,然后在临时寄存器上执行计算,最后把结果写入到输出寄存器。其他类型的寄存器在一个指令可能只使用一次,但是临时寄存却有可能使用多次。在一个指令里面有可能有3个临时寄存器被读,一个被写。任何读取一个没有写入数据的临时寄存器都会产生错误。
shader 2.0或者更高版本使用loop和endloop指令来控制流,循环计数寄存器al包含计数器的当前值。在循环体外部,这个值是未定义的。在循环体内部它的值将是固定数组的偏移量。在shader 3.0中,循环计数寄存器将用于索引输出寄存器和const数组。
shader 2.x 或者更高版本将提供了条件寄存器,它包含一个boo值的四维向量。bool值将用于执行条件控制流。setp_comp是唯一的赋值条件寄存器的指令。条件寄存器bool值用来控制if ,callnz,breakp指令。
shader3.0 采用采样寄存器来访问纹理。采样寄存器本身使用texldl指令来采样纹理。采样寄存器在使用前必须使用dcl_usage声明。使用采样寄存器,顶点shader能够执行纹理查询。
每个指令默认情况下操作在源操作数和目的操作数的四维向量值上。为了提高顶点shader的灵活性,并且使指令数减少,每个操作数可以包含一个修饰符来提取某几个维度的值。对于顶点shader指令,共有四种修饰符:目的操作数写掩码,源操作数multiplex,源操作数negation和绝对值操作数。修饰符的语法如下: 目的寄存器写掩码: r.xyzw 源寄存器multiplex: r.[xyzw][xyzw][xyzw][xyzw] 源寄存器negation: -r 绝对值: r_abs 逻辑negation: !r multiplex修改符允许一个四维向量从一个源寄存器的四个组件构造得到。一个组件可能被组合到一个向量的多个组件。 一个操作数也能使用多个修饰符,多个修饰符也能应用到一个指令里面。 Vertex Shader 1.1 架构shader 1.1架构是最简单的架构,没流控制也没有条件分支。最少有96个顶点shader const 寄存器。D3DCAPS9::MaxVertexShaderConst定义最大数目的const寄存器。constant寄存器在被地址寄存器的x组件索引。 指令用于声明,基本运算,矩阵计算,简单比较以及基本光照计算。更高版本的shader能完全支持1.1的指令,只是在某些指令上有些微小的变动。 Vertex Shader 2.0 架构顶点Shader 2.x 架构顶点2.x引入了版本2.0架构的扩展。在版本2.0的基础上增加了条件,静态流控制的深度嵌套和动态流控制指令。D3DCaps9的VS20Caps(它是一个D3DVSSHADERCAPS2_0结构)描述了可选的支持情况。2.x可选的支持包括predicate寄存器,动态流控制,大于12个临时寄存器和静态流控制的深度嵌套。 typedef struct _D3DVSHADERCAPS2_0 { DWORD Caps; INT DynamicFlowControlDepth; INT NumTemps; INT StaticFlowControlDepth; } D3DVSHADERCAPS2_0; 如果Caps的D3DVS20CAPS_PREDICATION为被设置,设备将支持predicate寄存器p0和它相关的指令setp_comp,if, callnz和breakp。predicate寄存器是一个四维的bool向量,只能通过setp_cmp赋值。 NumpTemps制定了能支持的临时寄存器rn的数目,一般至少是12个,它的实际值将在【12,32】之间,D3DVS20_MIN_NUMTEMP和D3DVS20_MAX_NUMTEMPS指定了最大值和最小值。 动态流控制的指令包括if_comp和break_comp。 如果dynamicFlowControlDepth不是0,它将能支持。 顶点Shader 3.0架构sn采样寄存器与dcl_texture关联。声明之后,就可以使用texldl指令从对应的纹理采样。 Shader指令语法在内部Direct3D使用一个DWORD数组来encode一个shader程序。这个encoding可以被认为是一个shader程序的机器语言。因为很难直接创建一个DWORD数组程序指令,SDK提供了工具把一个shader程序文本编译成机器语言。 shader指令的语法也跟大多数CPU汇编语言类似,首先是操作码,然后是操作数。shader 程序文本首先被解析成一串可解析的符号。空格和注释将会被忽略。跟其它汇编语言不同的是,它不必一行只能允许一条指令。一行可以写多条指令。 每个shader指令是由一个操作码和多个操作数组成,并且他们都是大小写敏感的。通常const寄存器操作数一般是c0....。但是,可以通过地址寄存器a0来索引const寄存器,c[16+a0.x] 或者c16[a0.x]。 执行模型顶点软件处理使用软件或者混合处理创建的设备可以在CPU上运行顶点shader。顶点软件处理能够执行所有的顶点shader版本。 顶点shader 1.1 指令顶点shader指令分成两组,一组是简单指令,一组是复杂指令。简单指令只在一个slot里面执行,复杂指令需要在多个slot里面执行。1.1支持的指令如下:
在详细讨论每个指令之前,我们先看看一个简单的shader程序。这个shader 程序把输入顶点数据直接写入到对应的output寄存器。 def d,v0,v1,v2,v3 -------------> d<---------(v0,v1,v2,v3) 为了将顶点的input寄存器映射到顶点对应的组件,dcl_usage指令被使用。 mov指令用来拷贝数据从源操作数到目的操作数。基本的运算执行只使用add,sub,mul 和mad指令。向量的加减使用add和sub指令。 add d,s0,s1 d<---------(s0x+s1x,s0y+s1y,s0z+s1z,s0w+s1w) sub d, s0,s1 d<---------(s0x-s1x,s0y-s1y,s0z-s1z,s0w-s1w) mul d,s0,s1 d<---------(s0x s1x,s0y s1y,s0z s1z,s0w s1w) mad d,s0,s1,s2 d<---------(s0x s1x + s2x, s0y s1y + s2y, s0z s1z + s2z, s0w s1w + s2w) 如果sw = 1, d = (1,1,1,1); 如果sw = 0, d = (无穷大,无穷大,无穷大,无穷大);否则,d= (1/sw,1/sw,1/sw,1/sw)。 d = (f,f,f,f) f = s0x s1x + s0y s1y + s0z s1z d= (f,f,f,f) f = s0x s1x + s0y s1y + s0z s1z + s0w s1w d = (min(s0x,s1x),min(s0y,s1y),min(s0z,s1z),min(s0w,s1w)) d = (max(s0x,s1x),max(s0y,s1y),max(s0z,s1z),max(s0w,s1w)) 如果|sw| = 0 ,d = (负无穷大,负无穷大,负无穷大,负无穷大);否则,d= (f,f,f,f) f= log2(|sw|) d = (s0x >= s1x, s0y>=s1y,s0z >= s1z, s0w >= s1w)。 True的时候组件是1.0, False的时候组件值是0.0。 d= (s0x < s1x, s0y < s1y, s0z<s1z,s0w<s1w)。< p="" style="word-wrap: break-word;"> d = (1,k,k2,1/k) s0=(*,k的平方,k平方,*) s1= (*,1/k,*,1/k) 如果sx >0 , sy >0 , d = (1,sx,sy为底指数为sw的幂,1); 如果sx>0, sy<=0 , d = (1,sx,0,1) 顶点Shader2.0 指令defb和def 定义bool和整数constant寄存器。 defb d, v d = v defi d, i0,i1,i2,i3 d= (i0,i1,i2,i3) mova d, s d= (round(sx),round(sy),round(sz),round(sw)) abs d,s d= (|sx|,|sy|,|sz|,|sw|) sgn d,s0,s1,s2 d= (f(s0x),f(s0y),f(s0z),f(s0w)) f(x) =-1(x<0); 0 (x=0); 1;(x>0) crs d,s0,s1 d为s0和s1的叉积 nrm d, s normalize向量s。
最简单的循环是一个repeat block,如下: rep i //使用一个整数const寄存器作为操作数,它的x组件将是循环的次数,范围在[0,255]。 count <---ix loop <----pc+1 //pc指向程序计数器,pc+1就是下个程序指令的地址 if count = 0 then pc <-------endloop endrep endloop <----- pc+1 count <---------count -1 if count >0 then pc <------- loop loop循环重复执行loop和endloop之间的代码block,一个loop block通过aL寄存器控制循环,al寄存器将作为目的操作数。 loop aL,i aL <--- iy count <----ix loop <-------pc=1 if count = 0 then pc<--------endloop endloop endloop <----- pc+1 count <---------count -1 aL <----------aL + iz if count >0 then pc <------- loop 一个subroutine block将包含在lable和ret指令块。 为了使用一个subroutine,你必须在shader之前使用ret结束 main routine。 label l l <------------pc+1 ret pc <----------pop(pc) call l push(pc,pc+1) pc <------------l callnz l,b if(b = true) then push(pc,pc+1) pc<-----l endif
GPU有限的的资源使shader里面的流控制增加了一些限制。每种流控制指令(循环,分支,subroutine)都有对应的的嵌套限制。在一个指令block里面嵌入另一个指令block,这就是嵌套block。嵌套限制如下表:
除了对嵌套的限制外,对控制流指令的数目也有限制。控制流指令的总数目称作静态流计数(static flow count)。if ,else,rep,loop,call和callnz都会增加static flow count。 在2.0里面,静态流条件指令只能出现在一个routine的top层。 call 和callnz也只能有一层调用,你不能在call里面在调用另外一个call。Loop和rep也只能有一层嵌套,rep 可以放在if block里面,但是它不能放在loop block。 顶点Shader 2.x指令它在2.0的基础上增加了predicate寄存器以及动态流控制。prediation指令一般是使用一个指令的修饰符来实现的。 新指令如下表:
//p0= (true,false,false,true) (p0.x) add r3,r1,r2 //r3 = r1+r2 (p0) add r4,r1,r2 //r4.x = r1.x + r2.x // r4.w = r1.w + r2.w (!p0.x) add r5, r1,r2 // r5 unchangned setp_comp指令是唯一能写入predicate寄存器的指令。 setp_eq d,s0,s1 d = (s0x= s1x,s0y= s1y,s0z = s1z,s0w = s1w) setp_ne , setp_ge,setp_gt,setp_le,setp_lt 这些指令都用于predicate寄存器赋值。 if 指令可以将predicate寄存器的某个组件结合起来使用, break_eq s0.c,s1.c if s0c= s1c then pc<--- endloop break_ne,break_ge,break_gt,break_le,break_lt 也break_eq类似。 if_eq s0.c,s1.c if(s0c = s1c) then ... if_ne, if_ge,if_gt,if_le,if_lt 与if_eq类似。 顶点Shader 3.0指令在3.0架构里面,所有的ouput寄存器都必须声明。声明语法类似input的声明语法,关联一个semantic usage和索引。地址寄存器除了索引const 寄存器外,还可以索引input和output寄存器。3.0新增指令如下表:
3.0 架构在顶点处理阶段引入了采样功能。源纹理的拓扑结构使用dcl_2d,dcl_cube,dcl_volume指令声明的。每个指令都携带单个操作数,它将采样寄存器sn与一个纹理关联起来。如: dcl_2d s, dcl_cube s , dcl_volume s。 一旦采样寄存器被声明,texldl指令用于将一个纹理采样到一个临时寄存器。下面的代码描述了它的基本原理。 texldl d, s0,s1 //s0是纹理的纹理坐标,s1是采样寄存器,指示哪个纹理将会采样。 L = s0w + SSLODBias //s0w用于选择mipmap level,如果这个值是负数,它将选择纹理的the most detailed miplevel。它的小数部分将用于两个miplevel之前插值。 if(L<=0) then L = max(SSMaxMipLevel,0) L = max(SSMaxMipLevel,0) filter = SSMagFilter q = lookup(s0,s1,L,filter) //对纹理进行采样 else L = Max(SSMaxMipLevel,L) filter = SSMinFilter q = Lookup(s0,s1,floor(L),filter) if (SSMipFilter = Linear) then r = lookup(s0,s1,ceil(L),filter) f = s0w - floor(s0w) q = (1-f)q + fr endif endif d = q Manipulating Shaders我们可以使用CreateVertexShader, SetVertexShader和GetVertexShader方法来管理顶点shader。应用程序可以使用D3DX把顶点shader源代码编译DWORD指令数组,提供给CreateVertexShader使用。如果你想要构建运行时动态shaders,最简单的方法就是从字符串构建shader函数,然后把字符串编译成DWORD指令数组。汇编shader比high level shader 语言要快,因为它不必重新汇编shader。虽然high leve shader语言更消耗CPU,但是动态创建的high-level shader也可以按照这样的方式执行。 设备的顶点shader constant 文件属性可以直接通过GetVertexShaderConstant和SetVertexShaderConstant管理。每个寄存器文件都它自己的设备方法,如: HRESULT GetVertexShaderConstantB(DWORD start, BOOL* value, DWORD count); HRESULT GetVertexShaderConstantF(DWORD start, float* value, DWORD count); HRESULT GetVertexShaderConstantI(DWORD start, int* value, DWORD count); HRESULT SetVertexShaderConstantB(DWORD start, const BOOL *value, DWORD count); HRESULT SetVertexShaderConstantF(DWORD start, const float* value, DWORD count) HRESULT SetVertexShaderConstantI(DWORD start, const int * value, DWORD count); start 参数指示第一个寄存器的序号,count指示四维向量值的数目,value指向一个值的数组。下面的例子将在寄存器c15里面存放一个值: const float data[4] = {1.f,0.f,0.f,0.f}; device->SetVertexShaderConstantF(15,&data[0],1); 顶点shader constant随着顶点shader 声明隐式的变化。constant寄存器的内容一直维持到设备rest。如果几个顶点shader使用同样的constant寄存器layout,constant寄存器可以只load一次,应用程序能够在几个顶点shader里面前后前换,而不必重新load constant寄存器。 设备能支持的最大的constant寄存器的数目定义在D3DCAPS9::MaxVertexShaderConst。版本1.1至少是96个。 如果D3DCAPS9::VertexShaderVersion非0,设备将支持顶点shader。 Drawing Multiple InstancesHRESULT SetStreamSourceFreq(UINT stream, UINT frequency); 最简单的例子如,使用相同的几何体,不同的每实例数据绘制n个几何体实例。 几何体数据将重复n次,实例数据将只重复一次。
Common Computation
固定功能处理
|
顶点着色器详解 (Vertex Shaders)相关推荐
- OpenGL ES _ 着色器_片断着色器详解
OpenGL ES _ 入门_01 OpenGL ES _ 入门_02 OpenGL ES _ 入门_03 OpenGL ES _ 入门_04 OpenGL ES _ 入门_05 OpenGL ES ...
- unity曲面细分着色器详解
前言:本文翻译自catlikecoding上一篇十分详细的英文blog并修改了几处错误,逐行解释了如何在自己的shader中添加曲面细分支持,并给出了多种计算细分因子的方案以及它们的优缺点. 原文链 ...
- OpenGL学习之着色器详解
OpenGL着色器语言(GLSL)看上去很像C语言,它由OpenGL实现进行编译和连接,并且(经常是)完全在图形硬件中运行. 我们有顶点着色器.片段着色器和几何着色器,前两种是必需的,后一种是可选的. ...
- cesium获取模型实时坐标_Cesium 顶点着色器中解算模型坐标
1. 由世界坐标转模型坐标 顶点着色器: attribute vec3 position3DHigh; attribute vec3 position3DLow; attribute vec3 nor ...
- Three.js-着色器加工材质及材质着色器详解
在Three中,我们可以使用着色器对材质进行加工,例如在对物体材质进行设置时,我们可以通过对顶点着色器的更改,从而实现物体的运动或变化.使用着色器加工材质,主要依赖于Material材质基类中的onB ...
- 着色器_片断着色器详解
本节学习目标 输入值和输出值 如何渲染多个输出缓冲区 输入值和输出值 片段着色器内置变量 输入值:片段着色器接受顶点管线最终输出的迭代值,这些值包括片段的位置,已解析的主颜色和辅助颜色,一系列的纹理坐 ...
- Opengl ES系列学习--顶点着色器
本节我们继续来看一下<OPENGL ES 3.0编程指南 原书第2版(中文版)>书中第8章的内容,PDF下载地址:OPENGL ES 3.0编程指南 原书第2版(中文版),代码下载地址:O ...
- Unity Shader:细分着色器(Tessellation Shader)在Unity顶点着色器中的写法以及各参数变量解释
图1:在Unity内将sphere细分后 图2:在Unity内将sphere细分后 Unity官网关于细分着色器的资料比较少,只有在Surface Shader中使用的例子.我看了下Surface S ...
- Learn OpenGL(三)——顶点着色器(Vertext Shader)
顶点着色器是几个着色器中的一个, 它是可编程的. 现代OpenGL需要我们至少设置一个顶点着色器和一个片段着色器, 如果我们打算做渲染的话. 我们会简要介绍一下着色器以及配置两个非常简单的着色器来绘制 ...
最新文章
- Java 匿名类也能使用构造函数
- 通过rsync+inotify实现数据的实时备份
- 在NVIDIA Jetson Xavier NX上安装llvmlite报错:No such file or directory: ‘llvm-config‘: ‘llvm-config‘
- BZOJ1563:[NOI2009]诗人小G(决策单调性DP)
- 20101029总结
- 7.12固定信息认证
- django关闭浏览器,怎样清除 cookies 和 session
- C++ STL 容器的一些总结 --- set(multiset)和map(multimap)
- lesson6 复数及复指数
- HTML5 — 知识篇总结《II》【HTML5大力支持的语义化思想与规范】
- 与时间有关的10个短语
- Asp.Net如何实现发送邮件 -详解
- Codeforces Round #569 (Div. 2)A. Alex and a Rhombus
- [附源码]Java计算机毕业设计SSM大学生志愿者管理系统
- Java -- 每日一问:谈谈常用的分布式ID的设计方案?Snowflake是否受冬令时切换影响?
- PHP 51tracking物流单个查询接口调用
- 这个商业模式、盈利模式、谈判技巧值得借鉴!
- es6转es5的在线工具
- 数据库完整性之参照完整性
- 对称加密之流密码RC4