现代OpenGL+Qt学习笔记之六:绘制可旋转、带光照效果的三维物体

版权声明:转载请标明出处。 https://blog.csdn.net/chaojiwudixiaofeixia/article/details/78043051

现代OpenGL+Qt学习笔记之六:绘制带光照效果的三维物体

主要内容

  本文仅考虑最简单的光照,即漫射光,同时在前面程序的基础上加入多模型的鼠标控制功能。此外,为了现实真正的三维渲染效果,本文将绘制的物体是一个如图所示的三维的圆环体。


圆环体

漫射光

  仅仅考虑漫射光是假设物体表面只会进行漫反射,即对于到达物体表面的入射光,其反射光的强度在各个方向上都是相同的,和观察者的位置无关。那么怎样计算漫射光强(也叫辐射量)呢?

漫反射

  如图,漫反射光强的计算只和两个向量有关:光到达曲面的法向n和入射点到光源的方向s。一个常识问题是,当入射光是沿着法向方向入射到物体表面上时,其其入射光是最强的;而当入射光是垂直于法向方向入射到物体表面时,其入射光强是0。实际上,当入射光的方向介于这两个极端方向之间时,入射光强是和ns之间的余弦值成正比的。而余弦值又和两个向量之间的点乘成正比,且当两个向量都是单位向量时,比例为1,即两者相等。在计算入射光强时,我们令ns都是单位向量,假设光源的光强为Ld则入射光强可以表示为:
Ldsn

  其中Ld是一个vec3类型的向量(r,g,b),分别表示光的红绿蓝3个分量。3个量的取值都在0.0到1.0之间。

  当一定强度的入射光到达物体表面时,物体会对一部分的光进行吸收再反射,其反射的比例和物体本身的颜色有关,如一个纯红色的物仅会反射红光,吸收入射光中的绿色和蓝色成分。可以将该反射的比例设为Kd,也是一个vec3类型的向量,分别表示对光的3个成分的反射比例,通常叫做漫反射率,这是物体本身的材料属性,所以通常也会叫做材料反射系数。那么一定强度的光到达物体表面后,经过吸收反射的光强计算公式为:

L=KdLdsn

示例程序

  下面是一个程序的示例,本章的程序是基于现代OpenGL+Qt学习笔记之四:使用Uniform变量实现对模型的旋转的,但是因为修改幅度比较大,就不做详细介绍。这里仅仅介绍几个关键点。

圆环体

  首先在我们的程序中因为要用到光照,再绘制前面的平面三角形就无法显示真正的光照效果了,因此从本文开始,以后绘制的三维物体要稍微复杂一点,如圆环体。

  这里主要定义了一个VBOTorus类,创建该类的对象,再在OpenGL部件类的渲染函数paintGL()中调用该类的render()函数即可。比较方便,还可以将模型数据和场景数据分开。有关该类的详细实现可以查看文后的源码。

#ifndef VBOTORUS_H
#define VBOTORUS_H
#include <QOpenGLFunctions>
#include <QOpenGLVertexArrayObject>

class QOpenGLVertexArrayObject;
class VBOTorus : protected QOpenGLFunctions
{
public:
    VBOTorus(float outerRadius, float innerRadius, int nrings, int nsides);
    ~VBOTorus();
    void render();
    int getVertexArrayHandle();
protected:
    QOpenGLVertexArrayObject vao;
private:
    int faces, rings, sides;
    void generateVerts(float * verts, float * norms, float * tex,
                       unsigned int * el,
                       float outerRadius, float innerRadius);
};

#endif // VBOTORUS_H

鼠标事件

  为了从多个角度观察物体的光照效果,程序还将实现对模型的控制,包括旋转和缩放模型。实现该功能,主要是要再OpenGLWidget类中添加下列3个事件处理函数:

    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
    void wheelEvent(QWheelEvent *event);

其中,在mousePressEvent()中我们要记录鼠标按下时的位置,而在mouseMoveEvent()中,我们要实现在鼠标左键按下且鼠标移动时,控制模型旋转,最后在wheelEvent()是在鼠标滚轮滚动时,实现物体的缩放(其实就是沿着z轴的平移,远小近大)。再添加和旋转、缩放相关的私有数据成员如下:

QMatrix4x4 model;
    QMatrix4x4 view;
    QMatrix4x4 projection;

    GLfloat xtrans, ytrans, ztrans; // translation on x,y,z-axis
    QVector2D mousePos;
    QQuaternion rotation;

  其中model, view, projection对应现代OpenGL+Qt学习笔记之五:OpenGL矩阵变换中介绍的模型矩阵,视图矩阵和投影矩阵。有关函数详细的实现和变量使用,还是要结合代码阅读了。这里只介绍一个比较重要的类QQuaternion。这是Qt提供的一个4元祖类,专门用来实现常用到的旋转操作。为什么是4元组呢?因为三维物体的旋转的确定包含旋转角度和旋转轴两个属性,而旋转轴具有3个分量,这就是为什么和旋转相关的类是4元组的原因。本示例代码中主要用到了该类的fromAxisAndAngle()函数,该函数是通过传递一个QVector3D类型的旋转轴和scalar类型的旋转角度计算得到和该旋转对应的4元组。

  有关该控制过程的实现,可以参考文后源码。

光照计算

  在OpenGL主程序提供了几何体的几何数据、光源光强和材料反射系数数据后,光照计算在顶点着色器或片元着色器中实现。

  先看片元着色器:

#version 430

in vec3 LightIntensity;

layout( location = 0 ) out vec4 FragColor;

void main() {
    FragColor = vec4(LightIntensity, 1.0);
}

很简单,就是通过顶点着色器输入一个反射光强(其实就是颜色了),再在最后添加alpha分量后输出即可。那么具体的模型旋转、缩放和光照计算是怎样的呢?

#version 430

layout (location = 0) in vec3 VertexPosition;
layout (location = 1) in vec3 VertexNormal;

out vec3 LightIntensity;

uniform vec4 LightPosition; // Light position in eye coords.
uniform vec3 Kd;            // Diffuse reflectivity
uniform vec3 Ld;            // Diffuse light intensity

uniform mat4 ModelViewMatrix;
uniform mat3 NormalMatrix;
uniform mat4 ProjectionMatrix;
uniform mat4 MVP;

void main()
{
    vec3 tnorm = normalize( NormalMatrix * VertexNormal);
    vec4 eyeCoords = ModelViewMatrix * vec4(VertexPosition,1.0);
    vec3 s = normalize(vec3(LightPosition - eyeCoords));

    LightIntensity = Ld * Kd * max( dot( s, tnorm ), 0.0 );

    gl_Position = MVP * vec4(VertexPosition,1.0);
}

  
 可以看到,输入信息必须包含顶点的位置和法向,其输出就是计算得到的漫发射光强。有关光源信息包含光源位置LightPosition、材料反射系数Kd和光源光强Ld。和旋转缩放变换相关的矩阵主要有模型视图矩阵ModelViewMatrix,法向矩阵NormalMatrix和投影矩阵ProjectionMatrix,有关这些矩阵的更详细信息参考现代OpenGL+Qt学习笔记之五:OpenGL矩阵变换。矩阵MVP是在客户端计算的projection*view*model得到的,这样做事为了减少重复计算,提高效率。

  在main()函数中,首先要对法向和物体位置进行变换,然后计算点到光源的方向,注意ns在计算漫反射光强之前都进行了单位化。

    LightIntensity = Ld * Kd * max( dot( s, n ), 0.0 );

  这是最终的漫反射光强的计算公式,对于维度相同的两个向量的相乘,GLSL自动实现了逐元素相乘,这里的余弦值的计算用的是max( dot( s, n ), 0.0 ),即取sn和0.0中的较大者,因为当sn为负时,说明该点在背光处,是没有漫反射发生的。

  这里写图片描述

小结

  本文主要介绍了一种最简单的光照理论,以及其在现代OpenGL中的实现方式。同时为了从不同角度观察物体的光照效果,还实现了用鼠标控制物体的旋转和缩放。后面会介绍更加复杂一点的光照模型,使得渲染结果更加真实,还有逐片元渲染技术,可以令曲面表现更加平滑。

源码地址:http://download.csdn.net/download/chaojiwudixiaofeixia/9988568

展开阅读全文

没有更多推荐了,返回首页