光照基础

Learn Opengl


颜色

我们在现实生活中看到某一物体的颜色并不是这个物体的真实颜色,而是它所反射(Reflected)的颜色。换句话说,那些不能被物体吸收(Absorb)的颜色(被反射的颜色)就是我们能够感知到的物体的颜色。例如,太阳光被认为是由许多不同的颜色组合成的白色光(如下图所示)。如果我们将白光照在一个蓝色的玩具上,这个蓝色的玩具会吸收白光中除了蓝色以外的所有颜色,不被吸收的蓝色光被反射到我们的眼中,使我们看到了一个蓝色的玩具。下图显示的是一个珊瑚红的玩具,它以不同强度的方式反射了几种不同的颜色。

这些颜色反射的规律被直接地运用在图形领域。我们在OpenGL中创建一个光源时都会为它定义一个颜色。当我们把光源的颜色与物体的颜色相乘,所得到的就是这个物体所反射该光源的颜色(也就是我们感知到的颜色)。我们通过检索结果颜色的每一个分量来看一下光源色和物体颜色的反射运算:

Vector3 lightColor(1.0f, 1.0f, 1.0f);
Vector3 toyColor(1.0f, 0.5f, 0.31f);
Vector3 result = lightColor * toyColor; // = (1.0f, 0.5f, 0.31f);

创建一个光照场景

现在我们将在场景中创建一个看得到的物体来代表光源,并且在场景中至少添加一个物体来模拟光照。这2个物体都将使用同一个立方体模型。首先需要一个顶点着色器来绘制箱子:

//color.vsh, 物体的vertex shader
attribute vec3 a_position;

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

void main(){
    gl_Position = projection * view * model * vec4(a_position, 1);
}

物体的片段着色器:

//color.fsh, 物体的fragment shader

uniform vec3 objectColor;
uniform vec3 lightColor;
void main()
{
    gl_FragColor = vec4(lightColor * objectColor, 1);
}

灯光源的顶点着色器和箱子的一样,片段着色器 定义一个常量的白色即可:

//light.fsh, 灯光的fragment shader

void main()
{
    gl_FragColor = vec4(1, 1, 1, 1);
}

表示灯(光源)的立方体需要为之创建一个特殊的VAO,因为 接下来的教程中我们会频繁地对顶点数据做一些改变并且需要改变属性对应指针设置,我们并不想因此影响到灯(我们只在乎灯的位置),因此我们有必要为灯创建一个新的VAO。

//灯光的vao, vbo 跟上边一样,灯光和物体使用同一个顶点数组
    glGenVertexArrays(1, &lightvao);
    glBindVertexArray(lightvao);
    
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    
    //把顶点数据传给 light.vsh vertex shader 的第一个属性(索引为0)
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GL_FLOAT), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    
    glBindVertexArray(0);

全部代码如下:

//
//  OpenGLColor.cpp
//  shaderTest
//
//  Created by MacSBL on 2017/1/5.
//
//

#include "OpenGLColor.h"

bool OpenGLColor::init()
{
    if (!Layer::init()) {
        return false;
    }
    origin = Director::getInstance()->getVisibleOrigin();
    vsize = Director::getInstance()->getVisibleSize();
    GLfloat vertices[] = {
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
        0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
        
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        
        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
        0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
        0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
    };
    
    //物体的vao
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    
    GLuint vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    
    //把顶点数据传给 color.vsh vertex shader 的第一个属性(索引为0)
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GL_FLOAT), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    
    glBindVertexArray(0);
    
    //灯光的vao, vbo 跟上边一样,灯光和物体使用同一个顶点数组
    glGenVertexArrays(1, &lightvao);
    glBindVertexArray(lightvao);
    
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    
    //把顶点数据传给 light.vsh vertex shader 的第一个属性(索引为0)
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GL_FLOAT), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    
    glBindVertexArray(0);
    //——————————————————————————————————————————————————————————————————————————————————————————————
    //设置 glprogram
    auto program = new GLProgram();
    program->initWithFilenames("color.vsh", "color.fsh");
    program->link();
    this->setGLProgram(program);
    
    lamProgram = new GLProgram();
    lamProgram->initWithFilenames("light.vsh", "light.fsh");
    lamProgram->link();
    
    //——————————————————————————————————————————————————————————————————————————————————————————————
    //设置转换矩阵
    model = new Mat4();
    model->rotate(Vec3(1, 1, 0), CC_DEGREES_TO_RADIANS(60));
    
    view = new Mat4();
    view->translate(0, 0, -3);
    
    projection = new Mat4();
    Mat4::createPerspective(45, vsize.width / vsize.height, 0.1, 1000, projection);
    
    
    
    return true;
}

void OpenGLColor::visit(cocos2d::Renderer *render, const cocos2d::Mat4 &parentTransform, uint32_t parentflag)
{
    Layer::visit(render, parentTransform, parentflag);
    _command.init(_globalZOrder);
    _command.func = CC_CALLBACK_0(OpenGLColor::onDraw, this);
    Director::getInstance()->getRenderer()->addCommand(&_command);
}

void OpenGLColor::onDraw()
{
    auto program = this->getGLProgram();
    program->use();
    
    //物体的 model、view、project 矩阵
    model->scale(1);
    GLuint modeloc = glGetUniformLocation(program->getProgram(), "model");
    glUniformMatrix4fv(modeloc, 1, GL_FALSE, model->m);
    
    GLuint viewloc = glGetUniformLocation(program->getProgram(), "view");
    glUniformMatrix4fv(viewloc, 1, GL_FALSE, view->m);
    
    GLuint proloc = glGetUniformLocation(program->getProgram(), "projection");
    glUniformMatrix4fv(proloc, 1, GL_FALSE, projection->m);
    
    GLuint objcolorloc = glGetUniformLocation(program->getProgram(), "objectColor");
    glUniform3f(objcolorloc, 1, 0.5, 0.3);
    GLuint lightcolorloc = glGetUniformLocation(program->getProgram(), "lightColor");
    glUniform3f(lightcolorloc, 1, 0.5, 1);
    
    glBindVertexArray(vao);
    glDrawArrays(GL_TRIANGLES, 0, 36);
    glBindVertexArray(0);
    
    //灯光的model、view、project 矩阵
    lamProgram->use();
    modeloc = glGetUniformLocation(lamProgram->getProgram(), "model");
    auto lightmodel = new Mat4();
    lightmodel->translate(1.1, 0, 0);
    lightmodel->rotate(Vec3(0, 1, 1), CC_DEGREES_TO_RADIANS(30));
    lightmodel->scale(0.2);
    glUniformMatrix4fv(modeloc, 1, GL_FALSE, lightmodel->m);
    
    viewloc = glGetUniformLocation(lamProgram->getProgram(), "view");
    glUniformMatrix4fv(viewloc, 1, GL_FALSE, view->m);
    
    proloc = glGetUniformLocation(lamProgram->getProgram(), "projection");
    glUniformMatrix4fv(proloc, 1, GL_FALSE, projection->m);
    
    glBindVertexArray(lightvao);
    glDrawArrays(GL_TRIANGLES, 0, 36);
    glBindVertexArray(0);
}


光照基础

OpenGL的光照仅仅使用了简化的模型并基于对现实的估计来进行模拟,其中一个模型被称为冯氏光照模型(Phong Lighting Model)。冯氏光照模型的主要结构由3个元素组成:环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照。这些光照元素看起来像下面这样:


环境光照(Ambient Lighting)

即使在黑暗的情况下,世界上也仍然有一些光亮(月亮、一个来自远处的光),所以物体永远不会是完全黑暗的。我们使用环境光照来模拟这种情况,也就是无论如何永远都给物体一些颜色。光通常都不是来自于同一光源,而是来自散落于我们周围的很多光源,即使它们可能并不是那么显而易见。光的一个属性是,它可以向很多方向发散和反弹,所以光最后到达的地点可能并不是它所临近的直射方向;光能够像这样反射(Reflect)到其他表面,一个物体的光照可能受到来自一个非直射的光源影响。

环境光照(Ambient Lighting)是一种简化的全局照明模型。我们使用一个(数值)很小的常量(光)颜色添加进物体片段(Fragment,指当前讨论的光线在物体上的照射点)的最终颜色里,这看起来就像即使没有直射光源也始终存在着一些发散的光。

把环境光照添加到场景里非常简单。我们用光的颜色乘以一个(数值)很小常量环境因子,再乘以物体的颜色,然后使用它作为片段的颜色:

//object.fsh, 物体的fragment shader

uniform vec3 objectColor;
uniform vec3 lightColor;
void main()
{
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * lightColor;
    vec3 result = ambient * objectColor;
    gl_FragColor = vec4(result, 1);
}

漫反射光照(Diffuse Lighting)

环境光本身不提供最明显的光照效果,但是漫反射光照(Diffuse Lighting)会对物体产生显著的视觉影响。漫反射光使物体上与光线排布越近的片段越能从光源处获得更多的亮度。面向光源的一面比其他面会更亮。为了更好的理解漫反射光照,请看下图:

变换那一节教程里,我们知道两个单位向量的角度越小,它们点乘的结果越倾向于1。当两个向量的角度是90度的时候,点乘会变为0。这同样适用于θ,θ越大,光对片段颜色的影响越小。     

计算漫反射光照需要:

  • 法向量:一个垂直于顶点表面的向量。
  • 定向的光线:作为光的位置和片段的位置之间的向量差的方向向量。为了计算这个光线,我们需要光的位置向量和片段的位置向量。

法向量

法向量(Normal Vector)是垂直于顶点表面的(单位)向量。由于顶点自身并没有表面(它只是空间中一个独立的点),我们利用顶点周围的顶点计算出这个顶点的表面。我们能够使用叉乘这个技巧为立方体所有的顶点计算出法线,但是由于3D立方体不是一个复杂的形状,所以我们可以简单的把法线数据手工添加到顶点数据中。更新的顶点数据数组:

GLfloat vertices[] = {
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        
        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,
        0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,
        
        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
        
        0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
        0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
        0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
        0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
        0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
        0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
        
        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
        0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
        0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
        0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
        
        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
        0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f
    };

更新物体的顶点着色器,向每个顶点添加一个法向量:

//object.vsh, 物体的vertex shader
attribute vec3 a_position;
attribute vec3 a_normal; 

varying vec3 v_normal; //把法向量传给 fragment shader
varying vec3 v_fragpos; //片段的位置

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

void main(){
    gl_Position = projection * view * model * vec4(a_position, 1.0);
    v_fragpos = vec3(model * vec4(a_position, 1.0));
    v_normal = a_normal;

}

向顶点着色器的a_normal属性传值:

//把法向量传给 object.vsh 的a_normal属性,注意:这里不能想当然的认为vertex shader中第2个attribute就是索引为1,必须通过program实事求是的取出它的location
    GLuint normloc = glGetAttribLocation(program->getProgram(), "a_normal");
    glEnableVertexAttribArray(normloc);
    glVertexAttribPointer(normloc, 3, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), (GLvoid*)(3*sizeof(GLfloat)));


法向量的转换

首先,法向量只是一个方向向量,不能表达空间中的特定位置。同时,法向量没有齐次坐标(顶点位置中的w分量)。这意味着,平移不应该影响到法向量。因此,如果我们打算把法向量乘以一个模型矩阵,我们就要把模型矩阵左上角的3×3矩阵从模型矩阵中移除(译注:所谓移除就是设置为0),它是模型矩阵的平移部分(注意,我们也可以把法向量的w分量设置为0,再乘以4×4矩阵;同样可以移除平移)。对于法向量,我们只能对它应用缩放(Scale)和旋转(Rotation)变换。

其次,如果模型矩阵执行了不等比缩放,法向量就不再垂直于表面了,顶点就会以这种方式被改变了。因此,我们不能用这样的模型矩阵去乘以法向量。下面的图展示了应用了不等比缩放的矩阵对法向量的影响:

无论何时当我们提交一个不等比缩放(注意:等比缩放不会破坏法线,因为法线的方向没被改变,而法线的长度很容易通过标准化进行修复),法向量就不会再垂直于它们的表面了,这样光照会被扭曲。

修复这个行为的诀窍是使用正规矩阵(Normal Matrix)。

正规矩阵被定义为“模型矩阵左上角的逆矩阵的转置矩阵”。注意,定义正规矩阵的大多资源就像应用到模型观察矩阵(Model-view Matrix)上的操作一样,但是由于我们只在世界空间工作(而不是在观察空间),我们只使用模型矩阵。

在顶点着色器中,我们可以使用inversetranspose函数自己生成正规矩阵,inversetranspose函数对所有类型矩阵都有效。注意,我们也要把这个被处理过的矩阵强制转换为3×3矩阵,这是为了保证它失去了平移属性,之后它才能乘以法向量。

Normal = mat3(transpose(inverse(model))) * normal;

对于着色器来说,逆矩阵也是一种开销比较大的操作,因此,无论何时,在着色器中只要可能就应该尽量避免逆操作,因为它们必须为你场景中的每个顶点进行这样的处理。以学习的目的这样做很好,但是对于一个对于效率有要求的应用来说,在绘制之前,你最好用CPU计算出正规矩阵,然后通过uniform把值传递给着色器(和模型矩阵一样)。

opengl es 2.0 中 没有实现transpose 和 inverse,所以对 model矩阵的 求逆 和转置都放到 cpu中执行,通过unifom传给 vertex shader。

//把model 矩阵 求逆 、转置后传给vertex shader
    Mat4* traninversemodel = new Mat4(*model);
    traninversemodel->inverse();
    traninversemodel->transpose();
    glUniformMatrix4fv(glGetUniformLocation(program->getProgram(), "tranInverseModel"), 1, GL_FALSE, traninversemodel->m);


计算漫反射光照

每个顶点现在都有了法向量,但是我们仍然需要光的位置向量和片段的位置向量。由于光的位置是一个静态变量,我们可以简单的在片段着色器中把它声明为uniform。
修改片段着色器:

//object.fsh, 物体的fragment shader

varying vec3 v_normal; //需要把法向量由顶点着色器传递到片段着色器
varying vec3 v_fragpos; //片段的位置,用以计算每个片段的法向量

uniform vec3 objectColor;
uniform vec3 lightColor;
uniform vec3 lightpos; //传入灯光的位置
uniform vec3 viewPos; //摄像机位置, 观察者的世界空间坐标,我们简单地使用摄像机对象的位置坐标代替(它就是观察者)

void main()
{
    //环境光
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * lightColor;
    
    //漫反射
    vec3 norm = normalize(v_normal);
    vec3 lightdir = normalize(lightpos - v_fragpos);
    float diff = max(dot(norm, lightdir), 0.0);
    vec3 diffuse = diff * lightColor;
    
    //镜面反射
    float specularStrenth = 0.5; //镜面强度
    vec3 viewDir = normalize(viewPos - v_fragpos);
    vec3 reflectDir = reflect(-lightdir, norm);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
    vec3 specular = specularStrenth * spec * lightColor;
    
    vec3 result = (ambient + diffuse + specular) * objectColor;
    gl_FragColor = vec4(result, 1.0);
}

我们需要做的第一件事是计算光源和片段位置之间的方向向量。前面提到,光的方向向量是光的位置向量与片段的位置向量之间的向量差。你可能记得,在变换教程中,我们简单的通过两个向量相减的方式计算向量差。我们同样希望确保所有相关向量最后都转换为单位向量,所以我们把法线和方向向量这个结果都进行标准化。
下一步,我们对normlightDir向量进行点乘,来计算光对当前片段的实际的散射影响。结果值再乘以光的颜色,得到散射因子。两个向量之间的角度越大,散射因子就会越小。
如果两个向量之间的角度大于90度,点乘的结果就会变成负数,这样会导致散射因子变为负数。为此,我们使用max函数返回两个参数之间较大的参数,从而保证散射因子不会变成负数。负数的颜色是没有实际定义的,所以最好避免它,除非你是那种古怪的艺术家。

既然我们有了一个环境光照颜色和一个散射光颜色,我们把它们相加,然后把结果乘以物体的颜色,来获得片段最后的输出颜色。

镜面光照(Specular Lighting)

镜面光照(Specular Lighting)同样依据光的方向向量和物体的法向量,但是这次它也会依据观察方向,例如玩家是从什么方向看着这个片段的。镜面光照根据光的反射特性。如果我们想象物体表面像一面镜子一样,那么,无论我们从哪里去看那个表面所反射的光,镜面光照都会达到最大化。你可以从下面的图片看到效果:

我们通过反射法向量周围光的方向计算反射向量。然后我们计算反射向量和视线方向的角度,如果之间的角度越小,那么镜面光的作用就会越大。它的作用效果就是,当我们去看光被物体所反射的那个方向的时候,我们会看到一个高光。

观察向量是镜面光照的一个附加变量,我们可以使用观察者世界空间位置(Viewer’s World Space Position)和片段的位置来计算。之后,我们计算镜面光亮度,用它乘以光的颜色,在用它加上作为之前计算的光照颜色。

首先,我们定义一个镜面强度(Specular Intensity)变量 specularStrength ,给镜面高光一个中等亮度颜色,这样就不会产生过度的影响了。
float specularStrength = 0.5f;

下一步,我们计算视线方向坐标,和沿法线轴的对应的反射坐标:
    vec3 viewDir = normalize(viewPos - v_fragpos);
    vec3 reflectDir = reflect(-lightdir, norm);

需要注意的是我们使用了 lightDir 向量的相反数。 reflect 函数要求的第一个是从光源指向片段位置的向量,但是 lightDir 当前是从片段指向光源的向量 。为了保证我们得到正确的 reflect 坐标,我们通过 lightDir 向量的相反数获得它的方向的反向。第二个参数要求是一个法向量,所以我们提供的是已标准化的 norm 向量。

剩下要做的是计算镜面亮度分量。

    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
    vec3 specular = specularStrenth * spec * lightColor;

这个32是高光的 发光值(Shininess) 。一个物体的发光值越高,反射光的能力越强,散射得越少,高光点越小。

全部代码:
//
//  OpenGLLighting.cpp
//  shaderTest
//  
//  Created by MacSBL on 2017/1/5.
//
//

#include "OpenGLLighting.h"

bool OpenGLLighting::init()
{
    if (!Layer::init()) {
        return false;
    }
    origin = Director::getInstance()->getVisibleOrigin();
    vsize = Director::getInstance()->getVisibleSize();
    
    GLfloat vertices[] = {
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        
        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,
        0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,
        
        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
        
        0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
        0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
        0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
        0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
        0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
        0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
        
        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
        0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
        0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
        0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
        
        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
        0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f
    };
    
    //——————————————————————————————————————————————————————————————————————————————————————————————
    //设置 glprogram
    auto program = new GLProgram();
    program->initWithFilenames("object.vsh", "object.fsh");
    program->link();
    this->setGLProgram(program);
    //——————————————————————————————————————————————————————————————————————————————————————————————
    
    //物体的vao
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    
    GLuint vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    
    //把顶点数据传给 object.vsh vertex shader 的第一个属性(索引为0)
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    
    //把法向量传给 object.vsh 的a_normal属性,注意:这里不能想当然的认为vertex shader中第2个attribute就是索引为1,必须通过program实事求是的取出它的location
    GLuint normloc = glGetAttribLocation(program->getProgram(), "a_normal");
    glEnableVertexAttribArray(normloc);
    glVertexAttribPointer(normloc, 3, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), (GLvoid*)(3*sizeof(GLfloat)));

    
    glBindVertexArray(0);
    
    //灯光的vao, vbo 跟上边一样,灯光和物体使用同一个顶点数组
    glGenVertexArrays(1, &lightvao);
    glBindVertexArray(lightvao);
    
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    
    //把顶点数据传给 light.vsh vertex shader 的第一个属性(索引为0)
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    
    glBindVertexArray(0);
    //——————————————————————————————————————————————————————————————————————————————————————————————
    
    lamProgram = new GLProgram();
    lamProgram->initWithFilenames("lamp.vsh", "lamp.fsh");
    lamProgram->link();
    
    //——————————————————————————————————————————————————————————————————————————————————————————————
    //设置转换矩阵
    model = new Mat4();
//    model->rotate(Vec3(1, 1, 0), CC_DEGREES_TO_RADIANS(60));
    
//    view = new Mat4();
//    view->translate(0, 0, -3);
    //camera
    cam = new MyCamera(Vec3(0, 0, 3));
    
    view = cam->GetViewMatrix();
    
    projection = new Mat4();
    Mat4::createPerspective(cam->Zoom, vsize.width / vsize.height, 0.1, 1000, projection);
    
    lightpos = Vec3(0, 0.5, 0.7);
    
    Director::getInstance()->setDepthTest(true);
    
    
    
    //touch事件
    auto elistener = EventListenerTouchOneByOne::create();
    elistener->onTouchBegan = CC_CALLBACK_2(OpenGLLighting::onTouchBegan, this);
    elistener->onTouchMoved = CC_CALLBACK_2(OpenGLLighting::onTouchMoved, this);
    elistener->onTouchEnded = CC_CALLBACK_2(OpenGLLighting::onTouchEnded, this);
    _eventDispatcher->addEventListenerWithSceneGraphPriority(elistener, this);
    
    return true;
}

void OpenGLLighting::visit(cocos2d::Renderer *render, const cocos2d::Mat4 &parentTransform, uint32_t parentflag)
{
    Layer::visit(render, parentTransform, parentflag);
    _command.init(_globalZOrder);
    _command.func = CC_CALLBACK_0(OpenGLLighting::onDraw, this);
    Director::getInstance()->getRenderer()->addCommand(&_command);
}

void OpenGLLighting::onDraw()
{
    auto program = this->getGLProgram();
    program->use();
    
   
    
    //物体的 model、view、project 矩阵
    model->scale(1);
    view = cam->GetViewMatrix();
    Mat4::createPerspective(cam->Zoom, vsize.width / vsize.height, 0.1, 1000, projection);
    
    GLuint modeloc = glGetUniformLocation(program->getProgram(), "model");
    glUniformMatrix4fv(modeloc, 1, GL_FALSE, model->m);
    
    GLuint viewloc = glGetUniformLocation(program->getProgram(), "view");
    glUniformMatrix4fv(viewloc, 1, GL_FALSE, view->m);
    
    GLuint proloc = glGetUniformLocation(program->getProgram(), "projection");
    glUniformMatrix4fv(proloc, 1, GL_FALSE, projection->m);
    
    GLuint objcolorloc = glGetUniformLocation(program->getProgram(), "objectColor");
    glUniform3f(objcolorloc, 1.0, 0.5, 0.31);
    GLuint lightcolorloc = glGetUniformLocation(program->getProgram(), "lightColor");
    glUniform3f(lightcolorloc, 1.0, 1.0, 1.0);
    GLuint lightposLoc = glGetUniformLocation(program->getProgram(), "lightpos");
    glUniform3f(lightposLoc, lightpos.x, lightpos.y, lightpos.z);
    
    GLuint viewposloc = glGetUniformLocation(program->getProgram(), "viewPos");
    glUniform3f(viewposloc, cam->Position.x, cam->Position.y, cam->Position.z);
    
    
    glBindVertexArray(vao);
    
    glDrawArrays(GL_TRIANGLES, 0, 36);
    glBindVertexArray(0);
    
    //灯光的model、view、project 矩阵
    lamProgram->use();
    modeloc = glGetUniformLocation(lamProgram->getProgram(), "model");
    auto lightmodel = new Mat4();
    lightmodel->translate(lightpos);
    lightmodel->rotate(Vec3(0, 1, 1), CC_DEGREES_TO_RADIANS(30));
    lightmodel->scale(0.2);
    glUniformMatrix4fv(modeloc, 1, GL_FALSE, lightmodel->m);
    
    viewloc = glGetUniformLocation(lamProgram->getProgram(), "view");
    glUniformMatrix4fv(viewloc, 1, GL_FALSE, view->m);
    
    proloc = glGetUniformLocation(lamProgram->getProgram(), "projection");
    glUniformMatrix4fv(proloc, 1, GL_FALSE, projection->m);
    
    glBindVertexArray(lightvao);
    glDrawArrays(GL_TRIANGLES, 0, 36);
    glBindVertexArray(0);
}
#pragma mark touch

bool OpenGLLighting::onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *evt)
{
    return true;
}

void OpenGLLighting::onTouchMoved(cocos2d::Touch *touch, cocos2d::Event *evt)
{
    Vec2 curpos = touch->getLocationInView();
    Vec2 prepos = touch->getPreviousLocationInView();
    GLfloat dx = curpos.x - prepos.x;
    GLfloat dy = curpos.y - prepos.y;
    
    //移动摄像机
    GLfloat camspeed = 0.05f;
        if (curpos.y - prepos.y > 0) { //w
            cam->ProcessKeyboard(Cam_Move::FORWARD, camspeed);
        }else if (curpos.y - prepos.y < 0){ //s
            cam->ProcessKeyboard(Cam_Move::BACKWARD, camspeed);
        }
         if (curpos.x - prepos.x < 0){ //a
             cam->ProcessKeyboard(Cam_Move::LEFT, camspeed);
        }else if (curpos.x - prepos.x > 0){ //d
            cam->ProcessKeyboard(Cam_Move::RIGHT, camspeed);
        }
    //(3)旋转摄像机
    
//    cam->ProcessMouseMovement(dx, dy);
           //(4)缩放
//    if(fov >= 1 && fov <= 45){
//        fov -= dx * camspeed;
//    }
//    if(fov <= 1){
//        fov = 1;
//    }
//    if(fov >= 45){
//        fov = 45;
//    }
}

void OpenGLLighting::onTouchEnded(cocos2d::Touch *touch, cocos2d::Event *evt)
{
    
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值