【笔记】【百人计划】图形2.3 常用函数介绍(HLSL)

微软文档HLSL内部函数:

https://docs.microsoft.com/zh-cn/windows/win32/direct3dhlsl/dx-graphics-hlsl-intrinsic-functions

一、基本数学运算

1
2
3
4
5
6
7
8
9
10
max(a,b)//最大值
min(a,b)//最小值
mul(a,b)//变量相乘,矩阵、向量运算(a是向量则看作行向量,b是向量则看作列向量)
abs(a)//绝对值
round(x)//最接近的整数
sqrt(x)//平方根
rsqrt(x)//平方根倒数
degrees(x)//弧度转角度
redians(x)//角度转弧度
noise(x)//噪声函数
img img

二、幂指对函数

1
2
3
4
5
6
7
8
pow(x,y);//x^y
exp(x);//e^x
exp2(x);//2^x
ldexp(x, exp);//x*2^exp
log(x);//lnx
log10(x);//log_10 x
log2(x);//log_2 x
frexp(x, out exp);//把浮点数x分解为尾数mantissa和指数 x = ret * 2^exp,返回值是位数,exp参数返回的值是指数(如果x参数为0,则尾数和指数均返回0)
img

三、三角函数与双曲函数

1
2
3
4
5
6
7
8
9
10
sin(x);cos(x);tan(x);//x均为弧度
sincos(x, out s, out c);//返回x的正弦值和余弦值
tan(y,x);//返回y/x的正切值
asin(x);//反正弦
acos(x);//反余弦
atan(x);//反正切
atan2(y,x);//输出y/x的反正切
sinh(x);//双曲正弦,(e^x-e^(-x))/2
cosh(x);//双曲余弦,(e^x+e^(-x))/2
tanh(x);//双曲正切,(e^x-e^(-x))/(e^x+e^(-x))
img img

四、数据范围类

1
2
3
4
5
6
7
8
9
10
ceil(x);//向上取整
floor(x);//向下取整
step(x,y);//x<=y为1,否则为0
saturate(x);//返回将x钳制到0和1之间的值
clamp(x,min,max);//把x限制在[min,max]范围内,
fmord(x,y);//返回x对y取余的余数
frac(x);//返回x的小数部分
modf(x, out ip);//将x分为小数和整数部分(符号与x相同),ip返回整数部分,整体返回小数部分
lerp(x,y,s);//按照s在x到y之间插值(x*(1-s)+y*s)
smoothstep(min,max,x);//如果x在min和max范围内,则返回01之间的平滑Hermite插值,使用smoothstep在两个值之间创建平滑过渡。例如平滑混合两种颜色
img img

五、类型判断类

1
2
3
4
5
6
all(x);//确定x所有分量是否均为非零,均非零则返回true,否则false(处理浮点型、整形、布尔型数据定义的标量、向量或矩阵)
clip(x);//如果x小于零,则丢弃当前像素,常用于判定范围(不仅针对0,返回值为void)。常用于测试alpha,如果每个分量代表到平面距离,还可用来模拟剪切平面
sign(x);//返回x正负性,小于0为-1,大于0为1,0则返回0
isinf(x);//x为+INF或-INF则为true,否则false
isfinite(x);//判断x是有限的,与isinf相反
isnan(x);//如果x为NAN(非数字),返回true,否则false

六、向量与矩阵类

1
2
3
4
5
6
7
length(v);//向量模长
normalize(v);//x/length(x)归一化
distance(a,b);//向量之间的距离(表示的点的距离)
dot(a,b);//点乘
cross(a,b);//叉乘
determinant(m);//矩阵m的行列式
transpose(m);//m的转置矩阵

七、光线运算

1
2
3
4
5
6
7
8
9
//注意所有的入射方向都表示入射方向的相反方向
reflect(i,n);//计算入射方向i对于法线n的反射方向
fefract(i,n,ri);//入射方向i,法线n,ri折射率,计算折射方向
lit(n_dot_l,n_dot_h,m);//返回照明系数向量(ambient,diffuse,specular,1)
//ambient=1
//diffuse = ndotl<0 ? 0 : ndotl
//specular = ndotl<0 || ndoth < 0 ? 0: ndoth^m
faceforward(n,i,ng);//输出法线n,输入视线方向i,表面法线方向ng,反转表面法线(如有必要)以面向和i相反的方向,在n中返回结果
//返回-n*sign(dot(i,ng))

八、1D纹理查找(几乎不用)

函数ddx,ddy用于求取相邻像素间某属性的差值;输入参数通常是纹理坐标,返回相邻像素间的属性差值;

1
2
3
4
5
6
tex1D(s,t);//一维纹理查找,返回纹理采样器s在标量t位置的color4
tex1D(s,t,ddx,ddy);//微分查询一维纹理ddx,ddy均为向量
tex1Dlod(s,t);//使用LOD查找纹理s在t.w位置的color4
tex1Dbias(s,t);//t.w决定的某个mip层偏置后的一维纹理查找
tex1Dgrad(s,t,ddx,ddy);//使用微分并指定mip层的一维纹理查找
tex1Dproj(s,t);//把纹理当作一张幻灯片投影到场景中,计算出投影纹理坐标t(t.w除以透视值),然后使用投影纹理坐标查询

九、2D纹理查找

1
2
3
4
5
6
tex2D(s,t);//二维纹理查找,返回纹理采样器s在t(x,y)位置的color4
tex2D(s,t,ddx,ddy);//微分查询二维纹理t,ddx,ddy均为向量
tex2Dlod(s,t);//使用LOD查找纹理s在t.w位置的color4
tex2Dbias(s,t);//t.w决定的某个mip层偏置后的二维纹理查找
tex2Dgrad(s,t,ddx,ddy);//使用微分并指定mip层的二维纹理查找
tex2Dproj(s,t);//把纹理当作一张幻灯片投影到场景中,计算出投影纹理坐标t(t.w除以透视值),然后使用投影纹理坐标查询

十、3D纹理查找

3D纹理资源(体积纹理),包含3D体积的纹理像素

1
2
3
4
5
6
tex3D(s,t);//三维纹理查找,返回纹理采样器s在vec t位置的color4
tex3D(s,t,ddx,ddy);//微分查询三维纹理t,ddx,ddy均为向量
tex3Dlod(s,t);//使用LOD查找纹理s在t.w位置的color4
tex3Dbias(s,t);//t.w决定的某个mip层偏置后的立方体纹理查找
tex3Dgrad(s,t,ddx,ddy);//使用微分并指定mip层的立方体纹理查找
tex3Dproj(s,t);//使用投影方式的立方体文里查找

十一、立体纹理查找

指CubeMap

1
2
3
4
5
6
texCUBE(s,t);//返回纹理采样器s在vec t位置的color4
texCUBE(s,t,ddx,ddy);//微分查询立方体纹理t,ddx,ddy均为向量
texCUBElod(s,t);//使用LOD查找纹理s在t.w位置的color4
texCUBEbias(s,t);//t.w决定的某个mip层偏置后的三维纹理查找
texCUBEgrad(s,t,ddx,ddy);//使用微分并指定mip层的三维纹理查找
texCUBEproj(s,t);//把纹理当作一张幻灯片投影到场景中,计算出投影纹理坐标t(t.w除以透视值),然后使用投影纹理坐标查询

作业

  • 写出最常用的5个函数
    • dot,mul,normalize,tex2d,pow
  • 这是我当前感觉最常用的,dot和mul不必说,向量矩阵运算必备;而由于光照计算中使用方向,常常需要对向量归一化;有贴图就有纹理采样;最后这个pow方法,看看各种光照模型的公式里,就有很多地方出现幂运算了。
  • ddx ddy的实际使用测试

ddx/ddy与法线贴图

ddx/ddy计算法线

首先ddx与ddy是两个函数,完整的表达是ddx(m),ddy(m)。

在glsl中,它相当于dFdx(m),dFdy(m)。

这两个函数只能作用于片元着色器中,根据他们的作用也很好理解这一点。

我们需要提供一个属性,然后这个函数会计算当前片元在屏幕空间中,沿x或y方向关于这个属性的偏导,因为片元是离散的,当然是以差分形式。

img

如果是考虑世界空间位置这一属性,我的理解是,对于当前着色点,计算的是这两个方向的切线方向。

于是有一个通常的法线计算方法

1
normal = normalize(cross(ddx(pos),ddy(pos)));

image-20220728230748527

左边是ddx,ddy计算出的法线,右边是顶点法线在fs中插值的法线。

很明显,这两者就好像flat shading和phong shading的关系。仔细想想确实如此。不考虑着色,只考虑几何信息,这个球体本来就是三角网格

image-20220728230953252

对于三角形上的一个点的法线,使用切线叉乘计算的法线当然是原原本本的网格法线。而右边的法线是顶点法线插值。

在unity中还原flat shading或是low poly风格的渲染,确实会采用ddx和ddy的方法。

ddx/ddy与法线贴图

learnopengl中这一段代码给我留下了深刻印象。当时用的时候完全不知道dFdx是做什么的,只能硬着头皮用了。现在我们已经了解了ddx函数,再回过头来看一下它对发现贴图的处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Easy trick to get tangent-normals to world-space to keep PBR code simplified.
// Don't worry if you don't get what's going on; you generally want to do normal
// mapping the usual way for performance anways; I do plan make a note of this
// technique somewhere later in the normal mapping tutorial.
vec3 getNormalFromMap()
{
vec3 tangentNormal = texture(normalMap, TexCoords).xyz * 2.0 - 1.0;

vec3 Q1 = dFdx(WorldPos);
vec3 Q2 = dFdy(WorldPos);
vec2 st1 = dFdx(TexCoords);
vec2 st2 = dFdy(TexCoords);

vec3 N = normalize(Normal);
vec3 T = normalize(Q1*st2.t - Q2*st1.t);
vec3 B = -normalize(cross(N, T));
mat3 TBN = mat3(T, B, N);

return normalize(TBN * tangentNormal);
}

核心就是求TBN矩阵嘛。N向量和B向量都很好理解,一个直接就是模型法线,一个就是由叉乘计算的副切线。

问题就在于切线的计算。

1
vec3 T  = normalize(Q1*st2.t - Q2*st1.t);

img

(这里把E1看作Q1,E2看作Q2)

$E_1=ΔU_1T+ΔV_1B$

$E_2=ΔU_2T+ΔV_2B$

这样再结合cpu计算TBN的方法,就很好理解了。

似乎后面的课程马上就会到法线贴图的部分,到时候再详述好了。

参考资料

[1]https://www.bilibili.com/video/BV1q64y1m7Ev 【技术美术百人计划】图形 2.3 常用函数介绍
[2]https://www.jianshu.com/p/7fc6a2fef29d
[3]https://learnopengl.com/code_viewer_gh.php?code=src/6.pbr/1.2.lighting_textured/1.2.pbr.fs
[4]https://learnopengl-cn.github.io/05%20Advanced%20Lighting/04%20Normal%20Mapping/
[5]https://zhuanlan.zhihu.com/p/484182779