Qt OpenGL(10)光照模型基础

文章目录

  • 物体的光照模型
    • 立方体坐标
    • 构建立方体的6个面
  • 代码框架
    • widget.cpp
    • 顶点着色器
    • 片元着色器
  • Ambient 环境光
  • Diffuse 漫反色
    • 法向量
    • 计算漫反射分量
  • Specular Highlight镜面高光
    • 计算镜面反射分量
    • 补充:半程向量的使用

物体的光照模型

出于性能的原因,一般使用冯氏光照模型(Phong Lighting Model)。

冯氏光照模型的主要结构由3个分量组成:环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照。下面这张图展示了这些光照分量看起来的样子:
Qt OpenGL(10)光照模型基础_第1张图片

  • 环境光照(Ambient Lighting) :让物体从各个角度都能看到一些微弱的但很均匀的色彩
  • 漫反射光照(Diffuse Lighting):让物体产生明暗对比效果的一个重要的分量,产生了表面的粗糙度的感觉
  • 镜面光照(Specular Lighting) :让物体表面出现光泽,产生了表面的光滑度的感觉

基础光照 - LearnOpenGL CN

立方体坐标

直观上这几个点很容易就能获取,但注意这8个点不是单位球面上的点。如果想细分立方体获取球面,简单的方式是做归一化处理。

// 中心位于坐标原点,边与坐标轴平行的单位立方体的顶点
QVector3D v[] = {
    { -0.5, -0.5,  0.5 },
    { -0.5,  0.5,  0.5 },
    {  0.5,  0.5,  0.5 },
    {  0.5, -0.5,  0.5 },
    { -0.5, -0.5, -0.5 },
    { -0.5,  0.5, -0.5 },
    {  0.5,  0.5, -0.5 },
    {  0.5, -0.5, -0.5 },
};

// 在细分前归一化处理8个顶点
    for (int i = 0; i < 8; ++i) {
        v[i].normalize ();
        qout << v[i];
    }

    recursion(0);

构建立方体的6个面

Qt OpenGL(10)光照模型基础_第2张图片

// 组成6个面,12个三角形,36个顶点(每个顶点赋一种颜色),按照右手法则,保证正面朝外
int tindices[6][4] = {
   {1,0,3,2},	// 103  和 132 两个三角形,右手法则
   {2,3,7,6}, // 237  和 276,下同里
   {3,0,4,7},
   {6,5,1,2},
   {4,5,6,7},
   {5,4,0,1}
};

// 6个面,每面两个三角形,可进一步细分,如果不想细分,传入n=0即可
void recursion( int n ){
    for (int i = 0; i < 6; ++i) {
        divide_triangle (v[ tindices[i][0] ],v[tindices[i][1]],v[tindices[i][2]], n);
        divide_triangle (v[ tindices[i][0] ],v[tindices[i][2]],v[tindices[i][3]], n);
    }
}

Qt OpenGL(10)光照模型基础_第3张图片 Qt OpenGL(10)光照模型基础_第4张图片
   recursion(0);           recursion(3);

代码框架

widget.cpp

#include "Widget.h"
#include "qmatrix4x4.h"
#include "qvector3d.h"
#include "qvector4d.h"
#include 
#include 
#include 
#include 
#include 

#define qRandom   QRandomGenerator::global ()
#define qout if( 1 ) qDebug() << __FILE__ << __LINE__ << ": "

bool rotateFlag = false;


// 中心位于坐标原点,边与坐标轴平行的单位立方体的顶点
QVector3D v[] = {
    { -0.5, -0.5,  0.5 },
    { -0.5,  0.5,  0.5 },
    {  0.5,  0.5,  0.5 },
    {  0.5, -0.5,  0.5 },
    { -0.5, -0.5, -0.5 },
    { -0.5,  0.5, -0.5 },
    {  0.5,  0.5, -0.5 },
    {  0.5, -0.5, -0.5 },
};


int tindices[6][4] = {
   {1,0,3,2},
   {2,3,7,6},
   {3,0,4,7},
   {6,5,1,2},
   {4,5,6,7},
   {5,4,0,1}
};

std::vector<QVector3D> vdata;
std::vector<QVector3D> normals;

void triangle(QVector3D a, QVector3D b, QVector3D c){
    vdata.push_back (a);
    vdata.push_back (b);
    vdata.push_back (c);

    auto normal = QVector3D::crossProduct (a-b,b-c).normalized();

    normals.push_back (normal);
    normals.push_back (normal);
    normals.push_back (normal);
}

void divide_triangle(QVector3D a, QVector3D b, QVector3D c, int n ){
    QVector3D ab, bc, ca;
    if( n > 0 ) {
        // 如果使用归一化,顶点和的结果是否除以 2 都不影响结果
        ab = ( a + b )/2;
        ca = ( a + c )/2;
        bc = ( b + c )/2;

        ab.normalize ();
        ca.normalize ();
        bc.normalize ();

        divide_triangle(a, ab, ca, n-1);
        divide_triangle(b, bc, ab, n-1);
        divide_triangle(c, ca, bc, n-1);
        divide_triangle(ab,bc, ca, n-1);
    }
    else
        triangle (a,b,c);
}


void recursion( int n ){
    for (int i = 0; i < 6; ++i) {
        divide_triangle (v[tindices[i][0]],v[tindices[i][1]],v[tindices[i][2]], n);
        divide_triangle (v[tindices[i][0]],v[tindices[i][2]],v[tindices[i][3]], n);
    }
}



// 相对于每个坐标轴的旋转角度(°)
enum {Xaxis, Yaxis, Zaxis, NumAxes};
int         Axis  = Zaxis;
//float   Theta[NumAxes] = { 0.0, 0.0, 0.0 };
//QVector3D   Theta = { 20.0, -10.0, 0.0 };
QVector3D   Theta = { 70.0, 0.0, 0.0 };
//QVector3D   Theta;


Widget::Widget(QWidget *parent)
    : QOpenGLWidget(parent)
{
    setWindowTitle ("12_basic_lighting");
    resize (100,100);

    for (int i = 0; i < 8; ++i) {
        v[i].normalize ();
        qout << v[i];
    }

    recursion(0);

}

Widget::~Widget()
{
    makeCurrent ();
    glDeleteBuffers (1,&VBO);
    glDeleteVertexArrays (1,&VAO);
    doneCurrent ();
}


void Widget::initializeGL()
{
    initializeOpenGLFunctions ();


    const char *version =(const char *) glGetString (GL_VERSION);
    qout << QString(version);

    // ---------------------------------
    // 创建一个顶点数组对象
    glGenVertexArrays (1,&VAO);
    glBindVertexArray(VAO);

    // ---------------------------------

    // 创建并初始化一个缓冲区对象
    glGenBuffers (1,&VBO);
    glBindBuffer (GL_ARRAY_BUFFER,VBO);
    glBufferData (GL_ARRAY_BUFFER,
                  sizeof(QVector3D)*vdata.size ()  + sizeof(QVector3D)*normals.size () ,
                  nullptr,
                  GL_STATIC_DRAW);

    glBufferSubData (GL_ARRAY_BUFFER, 0 , sizeof(QVector3D)*vdata.size () , vdata.data ());
    glBufferSubData (GL_ARRAY_BUFFER, sizeof(QVector3D)*vdata.size ()  ,sizeof(QVector3D)*normals.size ()  , normals.data ());

    glVertexAttribPointer(0,
                          3,        // 变量中元素的个数,
                          GL_FLOAT, // 类型
                          GL_FALSE, // 标准化,是否在 [-1,1] 之间
                          sizeof(QVector3D),  // 步长
                          (void*)0 );   // 变量的偏移量,在多个变量混合时指定变量的偏移
    glEnableVertexAttribArray(0); // 使用 location = 0 的索引

    glVertexAttribPointer(1,
                          3,        // 变量中元素的个数,
                          GL_FLOAT, // 类型
                          GL_FALSE, // 标准化,是否在 [-1,1] 之间
                          sizeof(QVector3D),  // 步长
                          (void*)( sizeof(QVector3D) * vdata.size ()  ) );   // 变量的偏移量,在多个变量混合时指定变量的偏移
    glEnableVertexAttribArray(1); // 使用 location = 0 的索引



    // ---------------------------------
    QString filename = ":/shader";
    shaderProgram.addShaderFromSourceFile (QOpenGLShader::Vertex,   filename+".vert");
    shaderProgram.addShaderFromSourceFile (QOpenGLShader::Fragment, filename+".frag");
    shaderProgram.link ();

    glBindBuffer (GL_ARRAY_BUFFER,0);

    // 控制多边形的正面和背面的绘图模式
//    glPolygonMode (GL_FRONT_AND_BACK,GL_LINE);

    // 深度测试
    glEnable(GL_DEPTH_TEST);

}



void Widget::paintGL()
{
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);   // 设置背景色
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glBindVertexArray(VAO);
    shaderProgram.bind ();

    // uniform 变量
    QVector3D lightColor (  1.0f, 1.0f,  1.0f );
    QVector3D objectColor(  1.0f, 0.5f,  0.31f);
    QVector3D objectPos  ( -1.5f, 0.0f, -5.0f );
    QVector3D lightSourcePos   ( 0, 1.0f, 1.0f );
    float ambientStrength = 0.2f;
    float specularStrength = 0.5;

    // ---------------------------------
    // // 绘制 物体
    QMatrix4x4 translateModel;
//    translateModel.translate (objectPos);

    QMatrix4x4 rx,ry,rz;
    rx.rotate (Theta[Xaxis],1.0,0.0,0.0);
    ry.rotate (Theta[Yaxis],0.0,1.0,0.0);
    rz.rotate (Theta[Zaxis],0.0,0.0,1.0);


    QMatrix4x4 scaleModel;
    scaleModel.scale (0.5);


    QMatrix4x4 model = translateModel * rx * ry * rz * scaleModel;

    QMatrix4x4 view;
    view.setColumn (3,QVector4D(0,0,-3,1));


    QMatrix4x4 projection;
    projection.perspective (45.0f, (float)width ()/height (), 0.1f, 100.0f);


    shaderProgram.setUniformValue ("model", model);
    shaderProgram.setUniformValue ("view", view);
    shaderProgram.setUniformValue ("projection", projection);
    shaderProgram.setUniformValue ("lightColor",  lightColor);
    shaderProgram.setUniformValue ("objectColor", objectColor  );
    shaderProgram.setUniformValue ("lightPos", lightSourcePos  );
    shaderProgram.setUniformValue ("ambientStrength",ambientStrength  );
    shaderProgram.setUniformValue ("specularStrength",specularStrength  );
    shaderProgram.setUniformValue ("lighting", true  );
    shaderProgram.setUniformValue ("viewPos", QVector3D(0,0,3) );

//    qout << lightColor * objectColor;

    glDrawArrays (GL_TRIANGLES, 0, (int)vdata.size ());


    // 绘制 灯
    QMatrix4x4 lightModel;
    lightModel.translate (5,5,-15);  // 灯是随意摆放,对光照计算没啥用处。只是为了简单示意
//    lightModel.scale (0.2f);
//    lightColor /= ambientStrength;
    shaderProgram.setUniformValue ("model", lightModel);
    shaderProgram.setUniformValue ("objectColor", 0.2f, 0.2f, 0.2f);
    shaderProgram.setUniformValue ("lightColor",  lightColor / ambientStrength);
    shaderProgram.setUniformValue ("lighting",  false);
    glDrawArrays (GL_TRIANGLES, 0, (int)vdata.size () );


    if( rotateFlag ){
        QThread::currentThread ()->msleep (50);
        Theta[Axis] += 2;
        if( Theta[Axis] > 360.0 )
            Theta[Axis] = - 360.0;
        update ();
    }
}

void Widget::mousePressEvent(QMouseEvent *e)
{
    rotateFlag = !rotateFlag;
    if(rotateFlag == false) {
        qout << Theta;
        return;
    }

    auto b = e->button ();
    switch (b) {
        case Qt::LeftButton:
            Axis = Xaxis;
            break;
        case Qt::MiddleButton:
            Axis = Yaxis;
            break;
        case Qt::RightButton:
            Axis = Zaxis;
            break;
        default:
            break;
    }
    update ();
}

顶点着色器

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

out vec3 FragPos;
out vec3 Normal;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    FragPos = vec3(model * vec4(aPos, 1.0));
    Normal = mat3(transpose(inverse(model))) * aNormal;

    gl_Position = projection * view * vec4(FragPos, 1.0);
}

片元着色器

#version 330 core
in vec3 Normal;
in vec3 FragPos;

uniform vec3 lightPos;
uniform vec3 viewPos;
uniform vec3 lightColor;
uniform vec3 objectColor;

void main()
{
    // ambient
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * lightColor;

    // diffuse
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = diff * lightColor;

    // specular
    float specularStrength = 0.5;
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
    vec3 specular = specularStrength * spec * lightColor;

    vec3 result = (ambient + diffuse + specular) * objectColor;
    gl_FragColor = vec4(result, 1.0);
}

Ambient 环境光

在顶点着色器中实现,简单来水就是用一个很小的环境光强度乘上光 再乘上物体的颜色就行。

效果:均匀着色

#version 330 core
layout (location = 0) in vec3 aPos;

out vec3 result;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

uniform vec3 lightColor;
uniform vec3 objectColor;

uniform float ambientStrength;

void main()
{

    gl_Position = projection * view * model * vec4(aPos, 1.0);

    vec3 ambient = ambientStrength * lightColor;

    result = ( ambient   ) * objectColor;
}

Qt OpenGL(10)光照模型基础_第5张图片 Qt OpenGL(10)光照模型基础_第6张图片 Qt OpenGL(10)光照模型基础_第7张图片 Qt OpenGL(10)光照模型基础_第8张图片
  strength : 0.1      strength : 0.2       strength: 0.3       strength: 0.4

Diffuse 漫反色

漫反射开始对物体产生显著的明暗效果。

从反射角度可以看到明亮的表面,偏离反射角度观察,物体表面开始暗淡下去。

Qt OpenGL(10)光照模型基础_第9张图片

所以,计算漫反射光照需要什么?

  • 法向量:一个垂直于顶点表面的向量。
  • 定向的光线:作为光源的位置与片段的位置之间向量差的方向向量。

法向量

法向量是一个垂直于顶点表面的(单位)向量。

由于顶点本身并没有表面(它只是空间中一个独立的点),我们利用它周围的顶点来计算出这个顶点的表面。

我们对构成立方体的每一个三角形的3个顶点计算法向量,可以简单地把法线数据手工添加到顶点数据中。

void triangle(QVector3D a, QVector3D b, QVector3D c){
    vdata.push_back (a);
    vdata.push_back (b);
    vdata.push_back (c);

    auto normal = QVector3D::crossProduct (a-b,b-c).normalized();
    normals.push_back (normal);
    normals.push_back (normal);
    normals.push_back (normal);
}

计算漫反射分量

  1. 计算光源和片段位置之间的光线向量

    vec3 lightDir = normalize(lightPos - FragPos);
    
  2. 计算光线向量和法向量的点积

    float diff = max(dot(norm, lightDir), 0.0);
    
  3. 得到漫反射分量

    vec3 diffuse = diff * lightColor;
    

Qt OpenGL(10)光照模型基础_第10张图片

Specular Highlight镜面高光

和漫反射光照一样,镜面光照也决定于光的方向向量和物体的法向量,但是它也决定于观察方向,例如玩家是从什么方向看向这个片段的。

镜面光照决定于表面的反射特性。如果我们把物体表面设想为一面镜子,那么镜面光照最强的地方就是我们看到表面上反射光的地方。你可以在下图中看到效果:

Qt OpenGL(10)光照模型基础_第11张图片

计算镜面反射分量

  1. 定义一个镜面强度

    float specularStrength = 0.5;
    
  2. 计算视线和片段位置之间的向量

    vec3 viewDir = normalize(viewPos - FragPos);
    
  3. 计算光线的镜面反射向量

    vec3 reflectDir = reflect(-lightDir, norm);
    

    reflect函数是个内置函数,要求第一个向量是光源指向片段位置的向量,但是lightDir当前正好相反,是从片段指向光源(由先前我们计算lightDir向量时,减法的顺序决定)。为了保证我们得到正确的reflect向量,我们通过对lightDir向量取反来获得相反的方向。第二个参数要求是一个法向量,所以我们提供的是已标准化的norm向量。

  4. 计算视线向量和镜面反射向量的点积

    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
    

Qt OpenGL(10)光照模型基础_第12张图片

计算得到点乘(并确保不是负值),然后取它的32次幂。这个32是高光的反光度(Shininess)。一个物体的反光度越高,反射光的能力越强,散射得越少,高光点就会越小。从上面的图片里,你会看到不同反光度的视觉效果影响。

  1. 计算镜面高光分量

    vec3 specular = specularStrength * spec * lightColor;
    

另外:这些光照计算可以在 顶点着色器中实现。在顶点着色器中实现的冯氏光照模型叫做Gouraud着色(Gouraud Shading),而不是冯氏着色(Phong Shading)。

补充:半程向量的使用

参考 Blinn-Phong光照模型解析及其实现_晴夏。的博客-CSDN博客_blinn phong

相关的讲解在 GAMES101-现代计算机图形学入门-闫令琪 视频中学习

在镜面反射中,我们通过计算出射方向和视点的夹角来确定反射光的强度,在计算机中计算反射向量R的计算量比较大。因此我们可以用其他计算方法近似替代。

Qt OpenGL(10)光照模型基础_第13张图片

float specularStrength = 0.50;
vec3  viewDir = normalize(viewPos - FragPos);
vec3  H_norm = normalize(lightDir + viewDir);  // 使用半程向量,只是简单的加减就可以获取到
float spec = pow(max(dot(H_norm, norm), 0.0), 256);
vec3  specular = specularStrength * spec * lightColor;

你可能感兴趣的:(#,Qt,OpenGL,c++,开发语言)