現代OpenGL+Qt學習筆記之六:繪製帶光照效果的三維物體
主要內容
本文僅考慮最簡單的光照,即漫射光,同時在前面程序的基礎上加入多模型的鼠標控制功能。此外,爲了現實真正的三維渲染效果,本文將繪製的物體是一個如圖所示的三維的圓環體。
漫射光
僅僅考慮漫射光是假設物體表面只會進行漫反射,即對於到達物體表面的入射光,其反射光的強度在各個方向上都是相同的,和觀察者的位置無關。那麼怎樣計算漫射光強(也叫輻射量)呢?
如圖,漫反射光強的計算只和兩個向量有關:光到達曲面的法向
其中
當一定強度的入射光到達物體表面時,物體會對一部分的光進行吸收再反射,其反射的比例和物體本身的顏色有關,如一個純紅色的物僅會反射紅光,吸收入射光中的綠色和藍色成分。可以將該反射的比例設爲
示例程序
下面是一個程序的示例,本章的程序是基於現代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()函數中,首先要對法向和物體位置進行變換,然後計算點到光源的方向,注意
LightIntensity = Ld * Kd * max( dot( s, n ), 0.0 );
這是最終的漫反射光強的計算公式,對於維度相同的兩個向量的相乘,GLSL自動實現了逐元素相乘,這裏的餘弦值的計算用的是max( dot( s, n ), 0.0 ),即取
小結
本文主要介紹了一種最簡單的光照理論,以及其在現代OpenGL中的實現方式。同時爲了從不同角度觀察物體的光照效果,還實現了用鼠標控制物體的旋轉和縮放。後面會介紹更加複雜一點的光照模型,使得渲染結果更加真實,還有逐片元渲染技術,可以令曲面表現更加平滑。
源碼地址:http://download.csdn.net/download/chaojiwudixiaofeixia/9988568