图形渲染(3)图形管线与着色器

欢迎关注公众号:sumsmile /专注图像处理的移动开发老兵~~

这篇文章主要讲着色器的实现,顺带提一下图形管线
代码参考:
github.com/Quanwei1992…

实现效果

基于一个小奶牛模型实现各种贴图。

模型:

模型

纹理贴图:

纹理贴图

冯氏光照:

冯氏光照

法线贴图:

法线贴图

凹凸贴图:

凹凸贴图

唯一贴图:

位移贴图

图形管线

OpenGL中,将固定的流程封装好,对开发者透明,只暴露顶点着色器、片元着色器供定制。这是非常好的设计。以冯氏光照为例,在片元着色器中实现”环境光”、”漫反射”、”镜面反射”,渲染出来的很像塑料制品的效果。

图形管线不是难点,重点分析着色器的实现

着色器

简单的场景,顶点着色器一般不做复杂的计算,仅作为参数的入口,逻辑主要在片元着色器中。更复杂的场景还可能用到几何着色器。

顶点着色器的代码非常简单,返回模型的position。

Eigen::Vector3f vertex_shader(const vertex_shader_payload& payload)
{
    return payload.position;
}
复制代码

顶点着色器的计算量一般远小于片元着色器。因为组成三角形的顶点相对有限,而片元需要基于顶点组成的三角形进行插值。

比如,一个100 * 100大小的矩形平面,有四个顶点,计算四次,但是片元着色器需要计算10000次。

所以,能在顶点着色器里计算的工作,就不要放在片元着色器中。

此处我没有做科学的测试,因为GPU的高并发特点,实际性能也不应该按次数来对比。

纹理贴图

纹理贴图比较简单,模型中对每个顶点已经生成好了对应的纹理坐标,直接获取对应的color即可

纹理模型一般是用工具按照一定的规则设计好的
小奶牛纹理模型

冯氏光照

冯氏光照比较简单,网上有大量的资料。
冯氏光照

法线贴图

也比较简单,模型自带了法线。注意,法线取值范围[-1, 1],需要映射到[0,1],再转换到[0, 255]

Eigen::Vector3f normal_fragment_shader(const fragment_shader_payload& payload)
{
    Eigen::Vector3f return_color = (payload.normal.head<3>().normalized() + Eigen::Vector3f(1.0f, 1.0f, 1.0f)) / 2.f;
    Eigen::Vector3f result;
    result << return_color.x() * 255, return_color.y() * 255, return_color.z() * 255;
    return result;
}
复制代码

凹凸贴图

到这里就略复杂了。在冯氏光照的基础上,加上凹凸纹理贴图,用凹凸纹理来替代奶牛模型表面的法线,来模拟真实物体表面凹凸不平的效果。

凹凸纹理

凹凸纹理中法线用RGB来存储,大多数法线垂直表面朝上,则B分量较大,所以整体成蓝色。

凹凸贴图原理

凹凸贴图原理

  1. 基于凹凸纹理,计算出具体点扰动后的法向量。需用到微积分的偏导。
  • dp/du = c1 * [h(u+1) – h(u)]
  • dp/dv = c2 * [h(v+1) – h(v)]
  • n = (-dp/du, -dp/dv, 1).normalized()
  1. 计算TBN(切线矩阵)

切线矩阵可以将法线贴图对齐到模型表面上,则一张法线贴图可以复用。

TBN的理解完全理解有一定难度,学习的过程中,我参考了不少资料

此处不再赘述。

实际代码中可以用更巧妙的方式来计算TBN

  • 将法线n投影到xz平面、y轴,得到两个向量A(xz)和B(y)
  • 将A(xz)和B(y)同时旋转,使得A(xz)对齐y轴,B(y)对齐xz平面,新得到的两个向量组成了T向量,即切向量
  • T和法向量n叉乘,得到复切向量

我也是从这篇帖子得到启发 TBN快速算法的解释

注意,代码里的实现是错误的,t的各分量正负号不对,得到的TBN并不是两垂直。

 Vector3f t(x*y/sqrt(x*x+z*z),sqrt(x*x+z*z),z*y/sqrt(x*x+z*z));
    Vector3f b = normal.cross(t);
复制代码

求出TBN,对法线做变换,得到view空间的法线

 Vector3f n = (TBN * ln).normalized();
复制代码

此外,法向量随着模型一起,需要经过mv矩阵计算,但是直接这么算会导致法线并不能垂直模型表面,比如平移会改变法线的方向。实际法线的变换矩阵是逆矩阵的转置,原理参考

OpenGL Normal Vector Transformation

位移贴图

凹凸贴图vs位移贴图

位移贴图是在凹凸贴图的基础上进一步优化, 对比上图,凹凸贴图是通过法向量的方向来模拟凹凸不平,实际上是影响了光线的反射,位移贴图是真的将顶点做了位移。

凹凸贴图的外轮廓还是光滑的,位移贴图外轮廓也呈现凹凸不平,更真实。

    // 改变原始点的位置,按照法向量的方向做变化
    Vector3f ln(-dU,-dV,1);
    Vector3f n = (TBN * ln).normalized();
    Vector3f p = point +  n * huv * kn;
    // 然后冯氏光照渲染
    ...
    
复制代码

此处的位移贴图比较简单,可以参考
learnopengl-cn.github.io/
上对位移贴图的讲解,非常详细。

补充,判断点在三角形内

实际代码中,光栅化逻辑中,判断点在三角形内的算法做了优化,初次理解还挺费劲。

常见的“判断点在三角形内的”算法都很好理解,基于向量叉乘或点乘。

判断点是否在三角形内

本篇文章代码中的实现:

static bool insideTriangle(int x, int y, const Vector4f* _v){
    Vector3f v[3];
    for(int i=0;i<3;i++)
        v[i] = {_v[i].x(),_v[i].y(), 1.0};
    Vector3f f0,f1,f2;
    f0 = v[1].cross(v[0]);
    f1 = v[2].cross(v[1]);
    f2 = v[0].cross(v[2]);
    Vector3f p(x,y,1.);
    if((p.dot(f0)*f0.dot(v[2])>0) && (p.dot(f1)*f1.dot(v[0])>0) && (p.dot(f2)*f2.dot(v[1])>0))
        return true;
    return false;
}
复制代码

参考下图及说明:

可以动的图地址:
www.geogebra.org/3d/qf2fcj9t

理解:

  • 三角形ABC分别看作起点在原点处的向量
  • 叉乘计算OA、OC的法向量,为f0
  • 如此,OB和f0的夹角 > 90°,如果点F和B在AC的同一侧,那么OB.dot(f0) 与 OF.dot(f0)符号相同,同为正或者负

原理都是利用叉乘来判断,这个算法少了三次减法,即求向量AB BC CA。确实比上一篇文章里的insideTriangle算法实现更优,就是理解不太直观,此处本人磕了好几个小时才搞明白。

欢迎关注公众号:sumsmile /专注图像处理的移动开发老兵~~

参考

[1]
Gouraud着色法: zh.wikipedia.org/wiki/Gourau…

[2]
冯氏光照: learnopengl-cn.github.io/02%20Lighti…

[3]
切线空间与法向量变换: juejin.cn/post/696790…

[4]
Tangent Space: www.opengl.org/archives/re…

[5]
Derivation of the Tangent Space Matrix: www.blacksmith-studios.dk/projects/do…

[6]
TBN问题: www.zhihu.com/question/43…

[7]
法线贴图、TBN: learnopengl-cn.github.io/05%20Advanc…

[8]
opengl-tutorial 法线贴图: www.opengl-tutorial.org/cn/intermed…

[9]
TBN快速算法的解释: games-cn.org/forums/topi…

[10]
OpenGL Normal Vector Transformation: www.songho.ca/opengl/gl_n…

[11]
判断点是否在三角形内: www.cnblogs.com/graphics/ar…

[12]
Cross product: en.wikipedia.org/wiki/Cross_…

[13]
伴随矩阵: zh.wikipedia.org/wiki/伴随矩阵

[14]
逆矩阵的推导过程: mp.weixin.qq.com/s/NWmLZnIDA…

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享