Shader 基础

时间秒杀一切 提交于 2020-01-17 01:05:59

顶点和片元着色器

Vertex shader program(顶点着色程序)和 Fragment shader program(片断着色程序)分别被 Programmable Vertex Processor(可编程顶点处理器)和Programmable Fragment Processo(可编程片断处理器)所执行。

顶点着色程序从 GPU 前端模块(寄存器)中提取图元信息(顶点位置、法向量、纹理坐标等),并完成顶点坐标空间转换、法向量空间转换、光照计算等操作,最后将计算好的数据传送到指定寄存器中;然后片断着色程序从中获取需要的数据,通常为“纹理坐标、光照信息等”,并根据这些信息以及从应用程序传递的纹理信息(如果有的话)进行每个片断的颜色计算,最后将处理后的数据送光栅操作模块。

由于 GPU 对数据进行并行处理,所以每个数据都会执行一次 shader 程序程序。 即, 每个顶点数据都会执行一次顶点程序; 每个片段都会执行一次片段程序。

什么是片断?片断和像素有什么不一样?所谓片断就是所有的三维顶点 在光栅化之后的数据集合,这些数据还没有经过深度值比较,而屏幕显示的像素都是经过深度比较的

开始 Cg 之旅

Cg 支持 7 种基本的数据类型:

  1. float, 32 位浮点数据,一个符号位。浮点数据类型被所有的 profile 支持(但是 DirectX8 pixel profiles 在一些操作中降低了浮点数的精度和范围);
  2. half, 16 为浮点数据;
  3. int, 32 位整形数据,有些 profile 会将 int 类型作为 float 类型使用;
  4. fixed, 12 位定点数,被所有的 fragment profiles 所支持;
  5. bool,布尔数据,通常用于 if 和条件操作符( ?:),布尔数据类型被所有的
    profiles 支持;
  6. sampler*,纹理对象的句柄( the handle to a texture object),分为 6 类:sampler, sampler1D, ampler2D, sampler3D, samplerCUBE,和 samplerRECT。DirectX profiles 不支持 samplerRECT 类型, 除此之外这些类型被所有的 pixelprofiles 和 NV40 vertex program profile 所支持( CgUsersManual 30 页)。由此可见,在不远的未来,顶点程序也将广泛支持纹理操作;
  7. string,字符类型,该类型不被当前存在的 profile 所支持,实际上也没有
    必要在 Cg 程序中用到字符类型,但是你可以通过 Cg runtime API 声明该类
    型变量,并赋值;因此,该类型变量可以保存 Cg 文件的信息。

注意: 向量最长不能超过 4 元, 即在 Cg 程序中可以声明 float1、 float2、 float3、float4 类型的数组变量,但是不能声明超过 4 元的向量,例如: float5 array;//编译报错 向量初始化方式一般为:
float4 array = float4(1.0, 2.0, 3.0, 4.0);

数组

数组数据类型在 Cg 程序中的作用是:作为函数的形参,用于大量数据的转递。Cg 中声明数组变量的方式和 C 语言类似:例如:

float a[10];//声明了一个数组,包含 10 个 float 类型数据
float4 b[10];//声明了一个数组,包含 10 个 float4 类型向量数据

对数组进行初始化的方式为:

float a[4] = {1.0, 2.0, 3.0, 4.0}; //初始化一个数组`在这里插入代码片`

要获取数组长度,可以调用“ .length”,例如:

float a[10]; //声明一个数组
int length = a.length;//获取数组长度

结构

struct appdata
    {
         float4 vertex : POSITION;
         float2 uv : TEXCOORD0;
     };

结构体定义与普通的 C 结构定义不同,除了定义结构体成员的数据类型外,还定义了该成员的绑定语义类型( Binding Semantics)所谓绑定语义类型是为了与宿主环境进行数据交换的时候识别不同数据类型的。 目前
Cg 支持的绑定语义类型包括 POSTION 位置), COLOR(颜色), NORMAL(法向量), Texcoord(纹理坐标)等类型。

表达式与控制语句

有一点需要注意: Cg中的逻辑与( &&)和逻辑或( ||)不存在C中的短路现象( short-circuiting,即只用计算一个操作数的bool值即可),而是参与运算的操作数据都进行bool分析

需要注意的是:求余操作符%。只能在int类型数据间进行,否则编译器会提示错误信息: error C1021: operands to “
%” must be integral

Swizzle 操作符

举例如下:

float4(a, b, c, d).xyz 等价于 float3(a, b, c)
float4(a, b, c, d).xyy 等价于 float3(a, b, b)
float4(a, b, c, d).wzyx 等价于 float4(d, c, b, a)
float4(a, b, c, d).w 等价于 float d

同样, return只能作为最后一条语句出现。函数的递归调用( recursion)在Cg语言中是被禁止的。 Switch 、
case和default在Cg中作为保留关键字存在,但是它们目前不被任何profile所支持

语义

Uniform
使用 Uniform 修辞的变量, 除了数据来源不同外, 与其他变量是完全一样的。需要注意的一点是: uniform 修辞的变量的值是从外部传入的,所以在 Cg 程序(顶点程序和片段程序)中通常使用 uniform 参数修辞函数形参,不容许声明一个用 uniform 修辞的局部变量!

const
被 const 所修辞的变量在初始化之后不能再去改变它的值。下面的例子程序中有一个声明为 const 的变量被赋值修改:

const float a = 1.0;
a = 2.0; //错误
float b = a++; //错误

参数传递
Cg 语言中参数传递方式同样分为“值传递”和“引用传递”,但指针机制并不被 GPU 硬件所支持,所以 Cg 语言采用不同的语法修辞符来区别“值传递”和“引用传递”。这些修辞符分别为:

  1. in: 修辞一个形参只是用于输入,进入函数体时被初始化,且该形参值的改变不会影响实参值,这是典型的值传递方式。
  2. out: 修辞一个形参只是用于输出的,进入函数体时并没有被初始化,这种类型的形参一般是一个函数的运行结果;
  3. inout: 修辞一个形参既用于输入也用于输出,这是典型的引用传递。

语义概念

语义概念的提出和图形流水线工作机制大有关系。从前面所讲的 GPU 处理流程中可以看出,一个阶段处理数据,然后传输给下一个阶段,那么每个阶段之间的接口是如何确定的呢?例如: 顶点处理器的输入数据是处于模型空间的顶点数据(位置、法向量),输出的是投影坐标和光照颜色;片段处理器要将光照颜色做为输入,问题是“片段处理器怎么知道光照颜色值的存放位置”?在高级语言中( C/C++),数据从接口的一端流向另一端,是因为提供了数据存放的内存位置(通常是指针信息);由于 Cg 语言并不支持指针机制,且图形硬件处理过程中,数据通常暂存在寄存器中,故而在 Cg 语言中,通过引入语义绑定( binding emantics)机制,指定数据存放的位置,实际上就是将输入\输出数据和寄存器做一个映射关系(在 OpenGL Cg profiles 中是这样的,但在DirectX-based Cg profiles 中则并没有这种映射关系)。根据输入语义,图形处理器从某个寄存器取数据;然后再将处理好的数据,根据输出语义,放到指定的寄存器。记住这一点:语义,是两个处理阶段(顶点程序、片段程序)之间的输入\输出数据和寄存器之间的桥梁,同时语义通常也表示数据的含义,如 POSITION一般表示参数种存放的数据是顶点位置
语义只有在入口函数中才有效,在内部函数(一个阶段的内部处理函数,和下一个阶段没有数据传递关系)无效,被忽略

顶点着色程序的输入语义

POSITION BLENDWEIGHT
NORMAL TANGENT
BINORMAL PSIZE
BLENDINDICES TEXCOORD0---TEXCOORD7

顶点位置坐标传入顶点着色程序中转化为四元向量,最后一元数据为 1,而顶点法向量传入顶点着色程序中转化为四元向量,最后一元数据为 0

顶点着色程序的输出语义

顶点程序的输出数据被传入到片断程序中,所以顶点着色程序的输出语义词,通常也是片段程序的输入语义词,不过语义词POSITION除外。下面这些语义词适用于所有的Cg vertex profiles作为输出语义和Cg fragment profiles的输入语义: POSITION, PSIZE, FOG,COLOR0-COLOR1,
TEXCOORD0-TEXCOORD7

如果需要从顶点着色程序向片段程序传递数据,例如顶点投影坐标、光照信息等,则可以声明另外的参数,绑定到TEXCOORD系列的语义词进行数据传递,实际上TEXCOORD系列的语义词通常都被用于从顶点程序向片段程序之间传递数据

片段着色程序的输出语义

片段着色程序的输出语义词较少,通常是COLOR。这是因为片段着色程序运行完毕后,就基本到了GPU流水线的末端了。 片段程序必须声明一个out向量(三元或四元),绑定语义词COLOR,这个值将被用作该片断的最终颜色值。
例如:

void main_f(out float4 color : COLOR)
{
    color.xyz = float3(1.0,1.0,1.0);
    color.w = 1.0;
}

函数

数组形参

在 C\C++中,当一个数组作为函数的形参时,实际上传入的只是指向首元素的指针, 并且数组边界被忽略
而在 Cg 语言中不存在指针机制(图形硬件不支持),数组作为函数形参,传递的是数组的完整拷贝

入口函数

通过观察程序的输入输出语义绑定,就可以区分入口函数对应到顶点程序还是片段程序。而内部函数则忽略任何应用到形参上的语义, 通常也没有人会在内部函数使用语义词,

Cg 标准函数库主要分为五个部分

  1. 数学函数( Mathematical Functions) ;
  2. 几何函数(Geometric Functions);
  3. 纹理映射函数(Texture Map Functions); (作为单独的一章进行讲解)
  4. 偏导数函数(Derivative Functions);84
  5. 调试函数(Debugging Function);

Cg 语言标准函数库中有3 个几何函数会经常被使用到, 分别是: normalize 函数, 对向量进行归一化; reflect
函数,计算反射光方向向量; refract 函数,计算折射光方向向量。

注意

  1. 着色程序中的向量最好进行归一化之后再使用,否则会出现难以预料的错误;
  2. reflect 函数和 refract 函数都存在以“入射光方向向量”作为输入参数,注意这两个函数中使用的入射光方向向量,是从外指向几何顶点的;平时我们在着色程序中或者在课本上都是将入射光方向向量作为从顶点出发。

偏导函数
3. 函数 ddx 和 ddy 用于求取相邻像素间某属性的差值;
4. 函数 ddx 和 ddy 的输入参数通常是纹理坐标;
5. 函数 ddx 和 ddy 返回相邻像素键的属性差值;

偏导数的物理含义是: 在某一个方向上的变化快慢。所以 ddx 求的是 X 方向上,相邻两个像素的某属性值的变化量; ddy 球的是 Y方向上,相邻两个像素的某属性值的变化量。

光源

环境光(Ambient Light):从物体表面所产生的反射光的统一照明,称为环境光或背景光
通常我们认为理想的环境光具有如下特性:没有空间或方向性;在所有方向上和所有物体表面上投射的环境光强度是统一的恒定值

为什么在进行第一步渲染时要关闭光源?原因在于:之前渲染场景完全是为了获得场景像素的深度值,而开启光源只会花更多时间进行光照渲染,但这是不必要的。这也说明了一点:计算阴影时,要两次渲染场景。

Z Fail 算法

1: 开启深度写,关闭光源,渲染整个场景,获取深度值;
2: 关闭深度写,渲染阴影体背面,深度测试失败(场景像素深度值小于阴影体背面深度值),则 stencil 加 1
3:渲染阴影体正面,深度测试失败(场景像素深度值小于阴影体正面深度值场景像素),则 stencil 减一;
4: 最后 stencil 值不为零的像素点处于阴影体中。开启阴影写,开启光源,重新渲染场景,并查看每个像素点的 stencil 值是否为零;如果不为零,则对像素颜色值赋予暗色调。

体素( Voxel)

体素,是组成体数据的最小单元,一个体素表示体数据中三维空间某部分的值。 体素相当于二维空间中像素的概念

体纹理( Volume Texture)

纹理上的2, 3维之分本质上是根据其所描述的数据维数而定的,所谓2d texture指的是纹理只描述了空间的面数据, 而3d texture则是描述了空间中的三维数据。 3d texture另一个较为学术化的名称是: volume texture

三维纹理,即体纹理,是传统2D纹理在逻辑上的扩展。二维纹理是一张简单的位图图片,用于为三维模型提供表面点的颜色值;而一个三维纹理,可以被认为由很多张2D纹理组成的,用于描述三维空间数据的图片。
三维纹理通过三维纹理坐标进行访问

齐次函数

均匀性,也称为齐次性,输入函数扩大a倍,而其响应函数相应的也扩大a倍。
若 f(x)→y(x) 则 af(x)→ay(x)
一般地,在数学里面,如果一个函数的自变量乘以一个系数,那么这个函数将乘以这个系数的k次方,我们称

这个函数为k次齐次函数,也就是:
如果函数 f(v)满足
f(ax)=a^k f(x)

其中,x是输入变量,k是整数,a是非零的实数,则称f(x)是k次齐次函数。齐次性在数学中描述的是函数的一个倍数的性质。

对于一个向量 v 以及 基 oabc, 很容易找到一组坐标 v1,v2,v3, 使得
v=v1a+v2b+v3c …(1)
对于一个点 p 而言,我们同样能找到一组坐标 p1,p2,p3, 使得
p−o=p1a+p2b+p3c…(2)

从上面对向量和点的表达,我们可以看出为了在坐标系中表示一个点(如p),我们把点的位置看作是对这个基的原点 o 所进行的一个位移,即一个向量: p−o,我们在表达这个向量的同时用等价的方式表达出了点 p=o+p1a+p2b+p3c …(3)
但表达一个点比一个向量需要额外的信息。

把 (1) 写成矩阵的形式: v=[a,b,c,o][v1,v2,v3,0]T
把 (2) 写成矩阵的形式: p=[a,b,c,o][p1,p2,p3,1]T

这里的 [a,b,c,o]是坐标基矩阵,右边的列向量分别是向量 v 和 点 p 在基下的坐标。向量和点在这个基上就有了不同的表达: 3D向量的第4个代数分量是0,而3D 点的第4个代数分量是1。 像这种用 4个代数分量表示 3D 几何概念的方式是一种齐次坐标表示。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!