文章内容源自《GPU编程与CG语言之阳春白雪下里巴人》,因笔者读书易中途放弃,遂每读一章节,将其移至简书平台,以此作为对自己读书的勉励。笔者用粗体、斜体 标注了关键词句,望感兴趣的读者们一起学习共勉。猛戳这里查看更多!
和 C 的标准函数库类似,Cg 提供了一系列内建的标准函数。这些函数用于执行数学上的通用计算或通用算法(纹理映射等),例如,需要求取入射光线的反射光线方向向量可以使用标准函数库中的 reflect 函数,求取折射光线方向向量可以使用 refract 函数,做矩阵乘法运算时可以使用 mul 函数。
有些函数直接和 GPU 指令相对应,所以执行效率非常高。绝大部分标准函数都被重载过,用于支持不同长度的数组和向量作为输入参数。
Cg 标准函数会随着未来 GPU 硬件的发展而不断优化,所以基于标准函数库写的程序是可以用在以后的 GPU 硬件上的。
Cg 标准函数库主要分为五个部分:
表 4中列举了 Cg 标准函数库中所有的数学函数,这些数学函数用于执行数学上常用计算,包括:三角函数、幂函数、园函数、向量和矩阵的操作函数。这些函数都被重载,以支持标量数据和不同长度的向量作为输入参数。
函数 | 功能 |
---|---|
abs(x) | 返回输入参数的绝对值 |
acos(x) | 反余切函数,输入参数范围为[-1,1],返回[ 0, π ] 区间的角度值 |
all(x) | 如果输入参数均不为 0,则返回 ture;否则返回 flase。&&运算 |
any(x) | 输入参数只要有其中一个不为 0,则返回 true。||运算 |
asin(x) | 反正弦函数,输入参数取值区间为[ 1,1− ] ,返回角度值范围为 [ - π/2, π/2] |
atan(x) | 反正切函数,返回角度值范围为 [ - π/2, π/2] |
atan2(y,x) | 计算 y/x 的反正切值。实际上和 atan(x)函数 功能完全一样,至少输入参数不同。atan(x) = atan2(x, float(1))。 |
ceil(x) | 对输入参数向上取整。例如:ceil(float(1.3)) ,其返回值为 2.0 |
clamp(x,a,b) | 如果 x 值小于 a,则返回 a;如果 x 值大于 b,返回 b;否则,返回 x。 |
cos(x) | 返回弧度 x 的余弦值。返回值范围为 [-1, 1] |
cosh(x) | 双曲余弦(hyperbolic cosine)函数,计算 x 的双曲余弦值。 |
cross(A,B) | 返回两个三元向量的叉积(cross product)。注意,输入参数必须是三元向量! |
degrees(x) | 输入参数为弧度值(radians),函数将其转换为角度值(degrees) |
determinant(m) | 计算矩阵的行列式因子。 |
dot(A,B) | 返回 A 和 B 的点积(dot product)。参数 A 和 B 可以是标量,也可以是向量(输入参数方面, 点积和叉积函数有很大不同)。 |
exp(x) | 计算 x e 的值,e= 2.71828182845904523536 |
exp2(x) | 计算2x 的值 |
floor(x) | 对输入参数向下取整。例如 floor(float(1.3)) 返回的值为 1.0;但是 floor(float(-1.3))返回的值为-2.0。该函数与 ceil(x)函数相对应。 |
fmod(x,y) | 返回 x/y 的余数。如果 y 为 0,结果不可预料。 |
frac(x) | Returns the fractional portion of a scalar or each vector component |
frexp(x, out exp) | 将浮点数 x 分解为尾数和指数,即 x = m* 2^exp,返回 m,并将指数存入 exp 中;如果 x 为 0,则尾数和指数都返回 0 |
isfinite(x) | 判断标量或者向量中的每个数据是否是有限数,如果是返回 true;否则返回 false;无限的或者非数据(not-a-number NaN) |
isinf(x) | 判断标量或者向量中的每个数据是否是无限,如果是返回 true;否则返回 false |
isnan(x) | 判断标量或者向量中的每个数据是否是非数据(not-a-number NaN),如果是返回 true;否则返回 false |
ldexp(x, n) | 计算 2n x∗ 的值 |
lerp(a, b, f) | 计算(1 - f) * a + b * f 或者 a + f * (b - a) 的 值。即在下限 a 和上限 b 之间进行插值,f 表示权值。注意,如果 a 和 b 是向量,则权值 f 必须是标量或者等长的向量。 |
lit(NdotL, NdotH, m) | N 表示法向量;L 表示入射光向量;H 表示半角向量;m 表示高光系数。 函数计算环境光、散射光、镜面光的贡献,返回的 4 元向量:X 位表示环境光的贡献,总是 1.0。 Y 位代表散射光的贡献,如果 N • L < 0 ,则为 0;否则为 N • L。Z 位代表镜面光的贡献,如果 N • L < 0 或者 N • H < 0,则为 0;否则为 (N • H) ^ m。 W 位始终为 1.0。 |
log(x) | 计算 ln(x) 的值,x 必须大于 0 |
log2(x) | 计算 log2(x) 的值,x 必须大于 0 |
log10(x) | 计算 log10(x) 的值,x 必须大于 0 |
max(a, b) | 比较两个标量或等长向量元素,返回大值。 |
min(a,b) | 比较两个标量或等长向量元素,返回小值。 |
modf(x, out ip) | 在 Cg Reference Manual 中没有查到 mul(M, N) 计算两个矩阵相乘,如果 M 为 AxB 阶矩阵, N 为 BxC 阶矩阵,则返回 AxC 阶矩阵。下面两个函数为其重载函数。 |
mul(M, v) | 计算矩阵和向量相乘 mul(v, M) 计算向量和矩阵相乘 |
noise( x) | 噪声函数,返回值始终在 0,1 之间;对于同样的输入,始终返回相同的值(也就是说, 并不是真正意义上的随机噪声)。 |
pow(x, y) | x^y |
radians(x) | 函数将角度值转换为弧度值 |
round(x) | Round-to-nearest,或 closest integer to x 即四舍五入。 |
rsqrt(x) | X 的反平方根,x 必须大于 0 |
saturate(x) | 如果 x 小于 0,返回 0;如果 x 大于 1,返回 1;否则,返回 x |
sign(x) | 如果 x 大于 0,返回 1;如果 x 小于 0,返回 01;否则返回 0。 |
sin(x) | 输入参数为弧度,计算正弦值,返回值范围 为[-1, 1] |
sincos(float x, out s, out c) | 该函数是同时计算 x 的 sin 值和 cos 值,其中 s=sin(x),c=cos(x)。该函数用于“同时需要计算 sin 值和 cos 值的情况”,比分别运算要快很多! |
sinh(x) | 计算双曲正弦(hyperbolic sine)值。 |
smoothstep(min, max, x) | 值 x 位于 min、max 区间中。如果 x=min,返 回 0;如果 x=max,返回 1;如果 x 在两者之间,按照下列公式返回数据: |
step(a, x) | 如果 x<a,返回 0;否则,返回 1。 |
sqrt(x) | 求 x 的平方根,x 必须大于 0。 |
tan(x) | 输入参数为弧度,计算正切值 |
tanh(x) | 计算双曲正切值 |
transpose(M) | M 为矩阵,计算其转置矩阵 |
几何函数,如表 5 所示,用于执行和解析几何相关的计算,例如根据入射光向量和顶点法向量,求取反射光和折射光方向向量。Cg 语言标准函数库中有 3 个几何函数会经常被使用到,分别是:normalize 函数,对向量进行归一化;reflect 函数,计算反射光方向向量;refract 函数,计算折射光方向向量。大声呐喊,并要求强烈注意:
函数 | 功能 |
---|---|
distance( pt1, pt2) | 两点之间的欧几里德距离(Euclidean distance) |
faceforward(N,I,Ng) | 如果 Ng • I < 0,返回 N;否则返回 -N。 |
length(v) | 返回一个向量的模,即 sqrt(dot(v,v)) |
normalize( v) | 归一化向量 |
reflect(I, N) | 根据入射光方向向量 I,和顶点法向量 N,计算反射光方向向量。其中 I 和 N 必须被归一化,需要非常注意的是,这个 I 是指向顶点的;函数只对三元向量有效。 |
refract(I,N,eta) | 计算折射向量,I 为入射光线,N 为法向量,eta 为折射系数;其中 I 和 N 必须被归一化,如果 I 和 N 之间的夹角太大,则返回(0,0,0),也就是没有折射光线;I 是指向顶点的;函数只对三元向量有效。 |
下表提供 Cg 标准函数库中的纹理映射函数。这些函数被 ps_2_0、ps_2_x、 arbfp1、fp30 和 fp40 等 profiles 完全支持(fully supported)。所有的这些函数返回四元向量值。
函数 |
---|
tex1D(sampler1D tex, float s) 一维纹理查询 |
tex1D(sampler1D tex, float s, float dsdx, float dsdy) 使用导数值(derivatives)查询一维纹理 |
Tex1D(sampler1D tex, float2 sz) 一维纹理查询,并进行深度值比较 |
Tex1D(sampler1D tex, float2 sz, float dsdx,float dsdy) 使用导数值(derivatives)查询一维纹理,并进行深度值比较 |
Tex1Dproj(sampler1D tex, float2 sq) 一维投影纹理查询 |
Tex1Dproj(sampler1D tex, float3 szq) 一维投影纹理查询,并比较深度值 |
Tex2D(sampler2D tex, float2 s) 二维纹理查询 |
Tex2D(sampler2D tex, float2 s, float2 dsdx, float2 dsdy) 使用导数值(derivatives)查询二维纹理 |
Tex2D(sampler2D tex, float3 sz) 二维纹理查询,并进行深度值比较 |
Tex2D(sampler2D tex, float3 sz, float2 dsdx,float2 dsdy) 使用导数值(derivatives)查询二维纹理,并进行深度值比较 |
Tex2Dproj(sampler2D tex, float3 sq) 二维投影纹理查询 |
Tex2Dproj(sampler2D tex, float4 szq) 二维投影纹理查询,并进行深度值比较 |
texRECT(samplerRECT tex, float2 s) |
texRECT (samplerRECT tex, float2 s, float2 dsdx, float2 dsdy) |
texRECT (samplerRECT tex, float3 sz) texRECT (samplerRECT tex, float3 sz, float2 dsdx,float2 dsdy) |
texRECT proj(samplerRECT tex, float3 sq) |
texRECT proj(samplerRECT tex, float3 szq) |
Tex3D(sampler3D tex, float s) 三维纹理查询 |
Tex3D(sampler3D tex, float3 s, float3 dsdx, float3 dsdy) 结合导数值(derivatives)查询三维纹理 |
Tex3Dproj(sampler3D tex, float4 szq) 查询三维投影纹理,并进行深度值比较 |
texCUBE(samplerCUBE tex, float3 s) 查询立方体纹理 |
texCUBE (samplerCUBE tex, float3 s, float3 dsdx, float3 dsdy) 结合导数值(derivatives)查询立方体纹理 |
texCUBEproj (samplerCUBE tex, float4 sq) 查询投影立方体纹理 |
s 象征一元、二元、三元纹理坐标;z 代表使用“深度比较(depth comparison) ” 的值;q 表示一个透视值(perspective value,其实就是透视投影后所得到的齐次坐标的后一位),这个值被用来除以纹理坐标(S),得到新的纹理坐标(已归一 化到 0 和 1 之间)然后用于纹理查询。
纹理函数非常多,总的来说,按照纹理维数进行分类,即:1D 纹理函数, 2D 纹理函数,3D 纹理函数,以及立方体纹理。需要注意,TexREC 函数查询的纹理实际上也是二维纹理。
3D 纹理,另一个比较学术化的名称是“体纹理(Volume Texture)”,体纹理通常用于体绘制,体纹理用于记录空间中的体细节数据。通常的二维纹理如图 14 左边所示,记录每个三角面皮上的颜色信息,但并不会记录体内的信息;体纹理记录三个方向,从里到外的信息,如图右边所示。在第 15 章和 16 章将会详细阐述体绘制算法和体纹理知识。
图 14 二维纹理与三维纹理比较
还有一类较为特殊的纹理查询函数以 proj
结尾,主要是针对投影纹理进行查询。所谓投影纹理是指:将纹理当做一张幻灯片投影到场景中,使用投影纹理技术需要计算投影纹理坐标,然后使用投影纹理坐标进行查询。使用投影纹理坐标进行查询的函数就是投影纹理查询函数。
本质来说,投影纹理查询函数和普通的纹理查询函数没有什么不同,唯一的区别在于“投影纹理查询函数使用计算得到的投影纹理坐标,并在使用之前会将该投影纹理坐标除以透视值 ”。举例而言,计算得到的投影纹理坐标为 float4 uvproj,使用二维投影纹理查询函数:
tex2Dproj(texture,uvproj);
等价于按如下方法使用普通二维纹理查询函数:
float4 uvproj = uvproj/uvproj.q;
tex2D(texture,uvproj);
函数 | 功能 |
---|---|
ddx(a) | 参数 a 对应一个像素位置,返回该像素值在 X 轴上的偏导数 |
ddy(a) | 参数 a 对应一个像素位置,返回该像素 值在 X 轴上的偏导数 |
偏导函数的用法很容易让人困惑,因为找不到非常信息的解释说明,当我查找这两个函数的中文资料时,给出的解释通常都是“ddx:返回关于屏幕坐标 x 轴的偏导数;ddy:返回关于屏幕坐标 y 轴的偏导数”。这个简单且单纯的解释完全可以让人陷入长时间的迷惑中,并进而怀疑自己的智商。于是我查找Cg Refernece Manual 中关于 ddx 函数的定义(ddy 函数的定义与之类似),原文是“returns approximate partial derivative with respect to window-space X
”。看来是典型的上梁不正下梁歪。学习一个函数,重要的是明白其输入 \ 输出参数的意义,以及有何作用。下面我们带着问题来学习这两个函数:
下面这段话引自某个论坛上的技术讨论贴,作者不详:
If you evaluate ddx (myVar), the GPU will give you the difference between the value of myVar at the current pixel and its value at the pixel next door. It's a straight linear difference, made efficient by the nature of GPU SIMD architectures (neighboring pixels will be calculated simultaneously).The derivative of any uniform value will always be zero.Because these derivatives are always linear, the second derivatives—for example, ddx(ddx(myVar))—will always be zero.
上面这句话的意思是,如果函数 ddx 的参数为 myVar,该参数对应的像素点记为 p(i, j),则 ddx(myVar)的值为“像素点 p(i + 1, j) 的值减去 myVar”。同理, ddy(myVar)的值为“像素点 p(i, j + 1) 的值减去 myVar”。如果函数 ddx 和 ddy 的输入参数为常数,则函数返回值永远为 0。
这里面存在像素点所对应数据的类型问题。从前面的知识可知,传入片段程 序的顶点属性一般有:屏幕坐标空间的顶点齐次坐标、纹理坐标、法向量、光照颜色等。假设传递给 ddx \ ddy 函数的参数 myVar 是纹理坐标,则,ddx(myVar) 的值为,纹理上像素点 p(i + 1, j) 的纹理颜色值减去 myVar 对应的纹理颜色值。
现在我们可以回答上面的三个问题:
下面的这段文字来自于 GPU Gems: Programming Techniques, Tips and Tricks for Real-Time Graphics,24.2. Understanding GPU Derivatives,by Randima Fernando.
Complex filtering depends on knowing just how much of the texture (or shading) we need to filter. Modern GPUs such as the GeForce FX provide partial derivative functions to help us. For any value used in shading, we can ask the GPU: "How much does this value change from pixel to pixel, in either the screen-x or the screen-y direction?" These functions are ddx() and ddy(). Although they are little used, they can be very helpful for filtering and antialiasing operations.
中文含义是:现代的 GPU 中提供了计算 partial derivative 的指令(通常被称 为梯度指令或者偏导数指令,DDX 或 DDY),Cg 中所对应的函数为 ddx()和 ddy()(注:glsl 和 hlsl 中也有该函数)。按照屏幕相关空间 x 或 y 计算偏导数,对纹理滤镜以及抗锯齿等非常有用(注:也可以用于 TXD 纹理查找的参数计算)。
学过高等数学应该知道,偏导数的物理含义是:在某一个方向上的变化快慢。 所以 ddx 求的是 X 方向上,相邻两个像素的某属性值的变化量;ddy 球的是 Y 方向上,相邻两个像素的某属性值的变化量。
正是由于 ddx 和 ddy 指令是作用于像素级的,所以 ddx 和 ddy 函数只被片段程序所支持。
当在纹理查询函数中使用 ddx 和 ddy 是可以进行图形过滤。所谓图像过滤,一个比较简单的定义是:对于给定的输入图像 A,要创建新的图像 B,把源图像 A 变换到目标图像 B 的操作就是图像滤波。一般的变换是调整图像大小、锐化、变化颜色,以及模糊图像等。复杂的过滤有赖于知道究竟需要过滤多少纹理。 像 GeForce 那样的现代 GPU 提供的偏导函数可以帮助我们。
图 15展示了对二维纹理正常采样时的效果:
图 15 二维纹理正常采样效果图
图展示了tex2D(sampler2D tex , float3 sz ,float2 dsdx , float2 dsdy )函数进行纹理采样时,分别取x,y方向的偏导数为(0.01,0.01)、(0.02,0.02)、(0.0, 0.015)时的效果。
图 16 使用偏导函数进行采样的效果图
函数 | 功能 |
---|---|
void debug(float4 x) | 如果在编译时设置了 DEBUG,片段着 色程序中调用该函数可以将值 x 作为 COLOR 语义的终输出;否则该函数什么也不做。 |
首先顶点 profiles 不支持调试函数。总的来说,目前所有的 shader language 中的调试功能都非常有限,表 8中列出的 debug 函数实际上十分鸡肋,能起到的作用非常有限的,随着而来的影响是“难以查找程序逻辑错误”。目前对 Cg 程序无法像 C++程序一样进行运行调试,步步跟踪。