Opengl 坐标

          继续 Learn Opengl


在流水线(Pipe line)中,物体的顶点在最终转化为屏幕坐标之前还会被变换到多个坐标系统。下图展示了整个流程:

coordinate_systems

局部空间

局部空间是指物体所在的坐标空间,即对象最开始所在的地方。想象你在一个建模软件中创建了一个立方体。你创建的立方体的原点有可能位于(0, 0, 0),即便它有可能最后在程序中处于完全不同的位置。你的模型的所有顶点都是在局部空间中:它们相对于你的物体来说都是局部的。局部坐标是对象相对于局部原点的坐标,也是物体起始的坐标。


世界空间

世界空间中的坐标是指顶点相对于(游戏)世界的坐标。如果你希望将物体分散在世界上摆放(特别是非常真实的那样),这就是你希望物体变换到的空间。物体的坐标将会从局部变换到世界空间;该变换是由模型矩阵(Model Matrix)实现的。

模型矩阵是一种变换矩阵,它能通过对物体进行位移、缩放、旋转来将它置于它本应该在的位置或朝向。你可以将它想像为变换一个房子,你需要先将它缩小(它在局部空间中太大了),并将其位移至郊区的一个小镇,然后在y轴上往左旋转一点以搭配附近的房子。你也可以把上一节将箱子到处摆放在场景中用的那个矩阵大致看作一个模型矩阵;我们将箱子的局部坐标变换到场景/世界中的不同位置。

观察空间

观察空间经常被人们称之OpenGL的摄像机(Camera)(所以有时也称为摄像机空间(Camera Space)或视觉空间(Eye Space))。观察空间是将世界空间坐标转化为用户视野前方的坐标而产生的结果。因此观察空间就是从摄像机的视角所观察到的空间。而这通常是由一系列的位移和旋转的组合来完成,平移/旋转场景从而使得特定的对象被变换到摄像机的前方。这些组合在一起的变换通常存储在一个观察矩阵(View Matrix)里,它被用来将世界坐标变换到观察空间。

裁剪空间

在一个顶点着色器运行的最后,OpenGL期望所有的坐标都能落在一个特定的范围内,且任何在这个范围之外的点都应该被裁剪掉(Clipped)。被裁剪掉的坐标就会被忽略,所以剩下的坐标就将变为屏幕上可见的片段。这也就是裁剪空间(Clip Space)名字的由来。

因为将所有可见的坐标都指定在-1.0到1.0的范围内不是很直观,所以我们会指定自己的坐标集(Coordinate Set)并将它变换回标准化设备坐标系,就像OpenGL期望的那样。

为了将顶点坐标从观察变换到裁剪空间,我们需要定义一个投影矩阵(Projection Matrix),它指定了一个范围的坐标,比如在每个维度上的-1000到1000。投影矩阵接着会将在这个指定的范围内的坐标变换为标准化设备坐标的范围(-1.0, 1.0)。所有在范围外的坐标不会被映射到在-1.0到1.0的范围之间,所以会被裁剪掉。在上面这个投影矩阵所指定的范围内,坐标(1250, 500, 750)将是不可见的,这是由于它的x坐标超出了范围,它被转化为一个大于1.0的标准化设备坐标,所以被裁剪掉了。

如果只是图元(Primitive),例如三角形,的一部分超出了裁剪体积 (Clipping Volume),则OpenGL会重新构建这个三角形为一个或多个三角形让其能够适合这个裁剪范围。

由投影矩阵创建的观察箱(Viewing Box)被称为平截头体(Frustum),每个出现在平截头体范围内的坐标都会最终出现在用户的屏幕上。将特定范围内的坐标转化到标准化设备坐标系的过程(而且它很容易被映射到2D观察空间坐标)被称之为投影(Projection),因为使用投影矩阵能将3D坐标投影(Project)到很容易映射到2D的标准化设备坐标系中。

一旦所有顶点被变换到裁剪空间,最终的操作——透视除法(Perspective Division)将会执行,在这个过程中我们将位置向量的x,y,z分量分别除以向量的齐次w分量;透视除法是将4D裁剪空间坐标变换为3D标准化设备坐标的过程。这一步会在每一个顶点着色器运行的最后被自动执行。

在这一阶段之后,最终的坐标将会被映射到屏幕空间中(使用glViewport中的设定),并被变换成片段。

将观察坐标变换为裁剪坐标的投影矩阵可以为两种不同的形式,每种形式都定义了不同的平截头体。我们可以选择创建一个正射投影矩阵(Orthographic Projection Matrix)或一个透视投影矩阵(Perspective Projection Matrix)。

正交投影

正射投影矩阵定义了一个类似立方体的平截头箱,它定义了一个裁剪空间,在这空间之外的顶点都会被裁剪掉。创建一个正射投影矩阵需要指定可见平截头体的宽、高和长度。在使用正射投影矩阵变换至裁剪空间之后处于这个平截头体内的所有坐标将不会被裁剪掉。它的平截头体看起来像一个容器:

orthographic projection frustum

上面的平截头体定义了可见的坐标,它由由宽、高、近(Near)平面和远(Far)平面所指定。任何出现在近平面之前或远平面之后的坐标都会被裁剪掉。正射平截头体直接将平截头体内部的所有坐标映射为标准化设备坐标,因为每个向量的w分量都没有进行改变;如果w分量等于1.0,透视除法则不会改变这个坐标。

透视投影

透视的效果在我们看一条无限长的高速公路或铁路时尤其明显,正如下面图片显示的那样:

perspective

正如你看到的那样,由于透视,这两条线在很远的地方看起来会相交。这正是透视投影想要模仿的效果,它是使用透视投影矩阵来完成的。这个投影矩阵将给定的平截头体范围映射到裁剪空间,除此之外还修改了每个顶点坐标的w值,从而使得离观察者越远的顶点坐标w分量越大。被变换到裁剪空间的坐标都会在-w到w的范围之间(任何大于这个范围的坐标都会被裁剪掉)。OpenGL要求所有可见的坐标都落在-1.0到1.0范围内,作为顶点着色器最后的输出,因此,一旦坐标在裁剪空间内之后,透视除法就会被应用到裁剪空间坐标上:


顶点坐标的每个分量都会除以它的w分量,距离观察者越远顶点坐标就会越小。这是也是w分量非常重要的另一个原因,它能够帮助我们进行透视投影。最后的结果坐标就是处于标准化设备空间中的。

cocos2dx中 这样创建一个透视投影矩阵:
    projection = new Mat4();
    Mat4::createPerspective(45, viewsize.width/viewsize.height, 0.1, 200, projection);

下面是一张透视平截头体的图片:

 perspective_frustum

它的第一个参数定义了fov的值,它表示的是视野(Field of View),并且设置了观察空间的大小。如果想要一个真实的观察效果,它的值通常设置为45.0f,但想要一个末日风格的结果你可以将其设置一个更大的值。第二个参数设置了宽高比,由视口的宽除以高所得。第三和第四个参数设置了平截头体的平面。我们通常设置近距离为0.1f,而远距离设为100.0f。所有在近平面和远平面内且处于平截头体内的顶点都会被渲染。

当你把透视矩阵的  near  值设置太大时(如10.0f),OpenGL会将靠近摄像机的坐标(在0.0f和10.0f之间)都裁剪掉,这会导致一个你在游戏中很熟悉的视觉效果:在太过靠近一个物体的时候你的视线会直接穿过去。

当使用正射投影时,每一个顶点坐标都会直接映射到裁剪空间中而不经过任何精细的透视除法(它仍然会进行透视除法,只是w分量没有被改变(它保持为1),因此没有起作用)。因为正射投影没有使用透视,远处的物体不会显得更小,所以产生奇怪的视觉效果。由于这个原因,正射投影主要用于二维渲染以及一些建筑或工程的程序,在这些场景中我们更希望顶点不会被透视所干扰。某些如 Blender 等进行三维建模的软件有时在建模时也会使用正射投影,因为它在各个维度下都更准确地描绘了每个物体。下面你能够看到在Blender里面使用两种投影方式的对比:

perspective_orthographic

你可以看到,使用透视投影的话,远处的顶点看起来比较小,而在正射投影中每个顶点距离观察者的距离都是一样的。

以上都组合在一起(viewport transform)

我们为上述的每一个步骤都创建了一个变换矩阵:模型矩阵、观察矩阵和投影矩阵。一个顶点坐标将会根据以下过程被变换到裁剪坐标:

Vclip=MprojectionMviewMmodelVlocal

注意矩阵运算的顺序是相反的(记住我们需要从右往左阅读矩阵的乘法)。最后的顶点应该被赋值到顶点着色器中的gl_Position,OpenGL将会自动进行透视除法和裁剪。

然后呢?

顶点着色器的输出要求所有的顶点都在裁剪空间内,这正是我们刚才使用变换矩阵所做的。OpenGL然后对裁剪坐标执行透视除法从而将它们变换到标准化设备坐标。OpenGL会使用glViewPort内部的参数来将标准化设备坐标映射到屏幕坐标,每个坐标都关联了一个屏幕上的点(在我们的例子中是一个800x600的屏幕)。这个过程称为视口变换。


进入3D

在开始进行3D绘图时,我们首先创建一个模型矩阵。这个模型矩阵包含了位移、缩放与旋转操作,它们会被应用到所有物体的顶点上,以变换它们到全局的世界空间。让我们变换一下我们的平面,将其绕着x轴旋转,使它看起来像放在地上一样。这个模型矩阵看起来是这样的:
    model = new Mat4();
    model->rotate(Vec3(1, 0, 0), CC_DEGREES_TO_RADIANS(50));
通过将顶点坐标乘以这个模型矩阵,我们将该顶点坐标变换到世界坐标。我们的平面看起来就是在地板上,代表全局世界里的平面。

接下来我们需要创建一个观察矩阵。我们想要在场景里面稍微往后移动,以使得物体变成可见的(当在世界空间时,我们位于原点(0,0,0))。

要想在场景里面移动,先想一想下面这个问题:

  • 将摄像机向后移动,和将整个场景向前移动是一样的。

这正是观察矩阵所做的,我们以相反于摄像机移动的方向移动整个场景。因为我们想要往后移动,并且OpenGL是一个右手坐标系(Right-handed System),所以我们需要沿着z轴的正方向移动。我们会通过将场景沿着z轴负方向平移来实现。它会给我们一种我们在往后移动的感觉。

右手坐标系(Right-handed System)

按照惯例,OpenGL是一个右手坐标系。简单来说,就是正x轴在你的右手边,正y轴朝上,而正z轴是朝向后方的。想象你的屏幕处于三个轴的中心,则正z轴穿过你的屏幕朝向你。坐标系画起来如下:

coordinate_systems_right_handed

为了理解为什么被称为右手坐标系,按如下的步骤做:

  • 沿着正y轴方向伸出你的右臂,手指着上方。
  • 大拇指指向右方。
  • 食指指向上方。
  • 中指向下弯曲90度。 

如果你的动作正确,那么你的大拇指指向正x轴方向,食指指向正y轴方向,中指指向正z轴方向。如果你用左臂来做这些动作,你会发现z轴的方向是相反的。这个叫做左手坐标系,它被DirectX广泛地使用。注意在标准化设备坐标系中OpenGL实际上使用的是左手坐标系(投影矩阵交换了左右手)。

观察矩阵是这样的:
    view = new Mat4();
    view->translate(0, 0, -3);

最后我们需要做的是定义一个投影矩阵。我们希望在场景中使用透视投影,所以像这样声明一个投影矩阵:
    cocos2d::Size viewsize = Director::getInstance()->getVisibleSize();
    projection = new Mat4();
    Mat4::createPerspective(45, viewsize.width/viewsize.height, 0.1, 200, projection);
既然我们已经创建了变换矩阵,我们应该将它们传入着色器。首先,让我们在顶点着色器中声明一个uniform变换矩阵然后将它乘以顶点坐标:
//matrix.vsh
attribute vec3 a_position;
attribute vec3 a_color;
attribute vec2 a_texcoord;

varying vec4 v_vertexColor;
varying vec2 v_textureCoord;


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

void main()
{
    gl_Position = projection * view * model* vec4(a_position, 1);
    v_vertexColor = vec4(a_color, 1);
    v_textureCoord = vec2(a_texcoord.x, 1.0 - a_texcoord.y);
}

我们还应该将矩阵传入着色器(这通常在每次的渲染迭代中进行,因为变换矩阵会经常变动):
   //矩阵变换,给vertex shader传值
    GLuint modelLoc = glGetUniformLocation(glprogram->getProgram(), "model");
    glUniformMatrix4fv(modelLoc, 1, GL_FALSE, model->m);
    GLuint viewLoc = glGetUniformLocation(glprogram->getProgram(), "view");
    glUniformMatrix4fv(viewLoc, 1, GL_FALSE, view->m);
    GLuint projectionLoc = glGetUniformLocation(glprogram->getProgram(), "projection");
    glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, projection->m);

我们的顶点坐标已经使用模型、观察和投影矩阵进行变换了,最终的物体应该会:

  • 稍微向后倾斜至地板方向。
  • 离我们有一些距离。
  • 有透视效果(顶点越远,变得越小)。
全部代码:
//
//  Triangle.cpp
//  shaderTest
//
//  Created by MacSBL on 2016/12/15.
//
//

#include "OpenGLMatrix.h"


bool OpenGLMatrix::init()
{
    if (!Layer::init()) {
        return false;
    }
    
    GLfloat vertices[] = {
        //顶点        //颜色       //纹理坐标
        0.5, 0.5, 0,   1, 0, 0,    1.0, 1.0, //右上
        0.5, -0.5, 0,  0, 1, 0,    1.0, 0, //右下
        -0.5, -0.5, 0, 0, 0, 1,    0, 0, //左下
        -0.5, 0.5, 0,  0, 0, 0,    0, 1,//左上
    };
    GLuint indices[] = {
        0, 1, 3,
        1, 2, 3
    };
    
    //创建并绑定纹理
    //////////////////////////////////////////////////////////
    //方法1
    auto sprite = Sprite::create("awesomeface.png");
    textureId2 = sprite->getTexture()->getName();
    //////////////////////////////////////////////////////////
    //方法2
    glGenTextures(1, &textureId);
    glBindTexture(GL_TEXTURE_2D, textureId);
    
    //纹理环绕方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    //纹理过滤
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
    //加载纹理
    Image* img = new Image();
    img->initWithImageFile("wall.jpg");
    int width = img->getWidth();
    int height = img->getHeight();
    unsigned char* imgdata = img->getData();
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, imgdata);
    glGenerateMipmap(GL_TEXTURE_2D);
    //释放内存,解绑texture
    CC_SAFE_DELETE(img);
    glBindTexture(GL_TEXTURE_2D, 0);
    ///////////////////////////////////////////////////////////////////
    
    //1、绑定vao
    //顶点数组对象(Vertex Array Object)被绑定后,任何随后的顶点属性调用都会储存在这个VAO中。这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的VAO就行了。刚刚设置的所有状态都将存储在VAO中
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    
    // 2. 把顶点数组复制到缓冲(vbo)中供OpenGL使用
    //使用glGenBuffers函数和一个缓冲ID生成一个VBO对象
    GLuint vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    //把之前定义的顶点数据复制到缓冲的内存中
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    
    //3, ebo
    GLuint ebo;
    glGenBuffers(1, &ebo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    
    //解析顶点数据, 应用到第1个顶点属性上
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8* sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    // 颜色,应用到第2个属性上
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(1);
    //纹理坐标,应用到第3个属性上
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
    glEnableVertexAttribArray(2);
    
    //解除绑定vao和vbo
    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    
    //——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
    auto program = new GLProgram;
    program->initWithFilenames("matrix.vsh", "matrix.fsh");
    program->link();
    
    
    //——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
    //矩阵
    //绕某轴旋转x°
    model = new Mat4();
    model->rotate(Vec3(1, 0, 0), CC_DEGREES_TO_RADIANS(50));
    
    //离我们有一些距离
    view = new Mat4();
    view->translate(0, 0, -3);
    
    //有透视效果(顶点越远,变得越小)
    cocos2d::Size viewsize = Director::getInstance()->getVisibleSize();
    projection = new Mat4();
    Mat4::createPerspective(45, viewsize.width/viewsize.height, 0.1, 200, projection);
    
    
    this->setGLProgram(program);
    
    return true;
}

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

void OpenGLMatrix::onDraw()
{
    //获取当前node 的shader
    auto glprogram = getGLProgram();
    //需要在init中指定shader才能在这use
    glprogram->use();
    
    //绑定纹理, 它会自动把纹理赋值给片段着色器texture.fsh的采样器u_myTexture.因为一个纹理的默认纹理单元是0,它是默认的激活纹理单元。
//    GL::bindTexture2D(textureId);
    
    /////////////////////////////////////////////////////////////
    //纹理单元, uniform采样器对应纹理单元
    GL::bindTexture2DN(0, textureId);
    glUniform1i(glGetUniformLocation(glprogram->getProgram(), "u_myTexture1"), 0);
    GL::bindTexture2DN(1, textureId2);
    glUniform1i(glGetUniformLocation(glprogram->getProgram(), "u_myTexture2"), 1);
    /////////////////////////////////////////////////////////////
    //矩阵变换,给vertex shader传值
    GLuint modelLoc = glGetUniformLocation(glprogram->getProgram(), "model");
    glUniformMatrix4fv(modelLoc, 1, GL_FALSE, model->m);
    GLuint viewLoc = glGetUniformLocation(glprogram->getProgram(), "view");
    glUniformMatrix4fv(viewLoc, 1, GL_FALSE, view->m);
    GLuint projectionLoc = glGetUniformLocation(glprogram->getProgram(), "projection");
    glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, projection->m);
    
    
    glBindVertexArray(vao);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
    glBindVertexArray(0);
}


3D立方体

要想渲染一个立方体,我们一共需要36个顶点(6个面 x 每个面有2个三角形组成 x 每个三角形有3个顶点) 。注意,这一次我们省略了颜色值,因为我们只通过纹理来获取最终的颜色值。这36个顶点的位置:
GLfloat vertices[] = {
        //3维顶点,            //纹理坐标
        -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
    };

我们使用glDrawArrays 来绘制立方体,但这一次总共有36个顶点:
glDrawArrays(GL_TRIANGLES, 0, 36);

注意,这里需要开启深度测试。 OpenGL存储深度信息在一个叫做Z缓冲 (Z-buffer)的缓冲中,它允许OpenGL决定何时覆盖一个像素而何时不覆盖。通过使用Z缓冲,我们可以配置OpenGL来进行深度测试。深度值存储在每个片段里面(作为片段的z值),当片段想要输出它的颜色时,OpenGL会将它的深度值和z缓冲进行比较,如果当前的片段在其它片段之后,它将会被丢弃,否则将会覆盖。这个过程称为深度测试(Depth Testing),它是由OpenGL自动完成的。深度测试默认是关闭的。
    //开启深度测试
    Director::getInstance()->setDepthTest(true);

更多的立方体

让我们为每个立方体定义一个位移向量来指定它在世界空间的位置。我们将在一个 glm::vec3 数组中定义n个立方体位置:

    cubpos[0] = {0, 0, 0};
    cubpos[1] = {2, 5, -15};
    cubpos[2] = {-1, -2, -2};
    cubpos[3] = {1, 0.5, -1.6};

在游戏循环中,我们调用glDrawArrays  n次,但这次在我们渲染之前每次传入一个不同的模型矩阵到顶点着色器中。我们将会在游戏循环中创建一个小的循环用不同的模型矩阵渲染我们的物体n次。注意我们也对每个箱子加了一点旋转:

for (int i=0; i<4; i++) { //画n个立方体
        Mat4* posmodel = new Mat4();
        posmodel->translate(cubpos[i]);
        posmodel->rotate(Vec3(0, 1, 0), CC_DEGREES_TO_RADIANS(50*i));
        GLuint modelLoc = glGetUniformLocation(glprogram->getProgram(), "model");
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, posmodel->m);
        glDrawArrays(GL_TRIANGLES, 0, 36);
    }

全部代码:
//
//  Triangle.cpp
//  shaderTest
//
//  Created by MacSBL on 2016/12/15.
//
//

#include "OpenGLCube.h"


bool OpenGLCube::init()
{
    if (!Layer::init()) {
        return false;
    }
    
    GLfloat vertices[] = {
        //3维顶点,            //纹理坐标
        -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
    };
    
    //创建并绑定纹理
    //////////////////////////////////////////////////////////
    //方法1
    auto sprite = Sprite::create("awesomeface.png");
    textureId2 = sprite->getTexture()->getName();
    //////////////////////////////////////////////////////////
    //方法2
    glGenTextures(1, &textureId);
    glBindTexture(GL_TEXTURE_2D, textureId);
    
    //纹理环绕方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    //纹理过滤
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
    //加载纹理
    Image* img = new Image();
    img->initWithImageFile("wall.jpg");
    int width = img->getWidth();
    int height = img->getHeight();
    unsigned char* imgdata = img->getData();
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, imgdata);
    glGenerateMipmap(GL_TEXTURE_2D);
    //释放内存,解绑texture
    CC_SAFE_DELETE(img);
    glBindTexture(GL_TEXTURE_2D, 0);
    ///////////////////////////////////////////////////////////////////
    
    //1、绑定vao
    //顶点数组对象(Vertex Array Object)被绑定后,任何随后的顶点属性调用都会储存在这个VAO中。这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的VAO就行了。刚刚设置的所有状态都将存储在VAO中
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    
    // 2. 把顶点数组复制到缓冲(vbo)中供OpenGL使用
    //使用glGenBuffers函数和一个缓冲ID生成一个VBO对象
    GLuint vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    //把之前定义的顶点数据复制到缓冲的内存中
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    
    
    //解析顶点数据, 应用到第1个顶点属性上
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5* sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    // 纹理坐标,应用到第2个属性上
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(1);
    
    //解除绑定vao和vbo
    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    
    //——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
    auto program = new GLProgram;
    program->initWithFilenames("cube.vsh", "cube.fsh");
    program->link();
    
    
    //——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
    //矩阵
    //绕某轴旋转x°
    model = new Mat4();
    model->rotate(Vec3(1, 0, 0), CC_DEGREES_TO_RADIANS(50));
    
    //离我们有一些距离
    view = new Mat4();
    view->translate(0, 0, -3);
    
    //有透视效果(顶点越远,变得越小)
    cocos2d::Size viewsize = Director::getInstance()->getVisibleSize();
    projection = new Mat4();
    Mat4::createPerspective(45, viewsize.width/viewsize.height, 0.1, 200, projection);
    
    
    this->setGLProgram(program);
    //开启深度测试
    Director::getInstance()->setDepthTest(true);
    
    cubpos[0] = {0, 0, 0};
    cubpos[1] = {2, 5, -15};
    cubpos[2] = {-1, -2, -2};
    cubpos[3] = {1, 0.5, -1.6};
    
    return true;
}

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

void OpenGLCube::onDraw()
{
    //获取当前node 的shader
    auto glprogram = getGLProgram();
    //需要在init中指定shader才能在这use
    glprogram->use();
    
    //绑定纹理, 它会自动把纹理赋值给片段着色器texture.fsh的采样器u_myTexture.因为一个纹理的默认纹理单元是0,它是默认的激活纹理单元。
//    GL::bindTexture2D(textureId);
    
    /////////////////////////////////////////////////////////////
    //纹理单元, uniform采样器对应纹理单元
    GL::bindTexture2DN(0, textureId);
    glUniform1i(glGetUniformLocation(glprogram->getProgram(), "u_myTexture1"), 0);
    GL::bindTexture2DN(1, textureId2);
    glUniform1i(glGetUniformLocation(glprogram->getProgram(), "u_myTexture2"), 1);
    /////////////////////////////////////////////////////////////
    //矩阵变换,给vertex shader传值
//    model->rotate(Vec3(1, 0, 0), CC_DEGREES_TO_RADIANS(2));
//    GLuint modelLoc = glGetUniformLocation(glprogram->getProgram(), "model");
//    glUniformMatrix4fv(modelLoc, 1, GL_FALSE, model->m);
    GLuint viewLoc = glGetUniformLocation(glprogram->getProgram(), "view");
    glUniformMatrix4fv(viewLoc, 1, GL_FALSE, view->m);
    GLuint projectionLoc = glGetUniformLocation(glprogram->getProgram(), "projection");
    glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, projection->m);
    
    
    glBindVertexArray(vao);
    for (int i=0; i<4; i++) { //画n个立方体
        Mat4* posmodel = new Mat4();
        posmodel->translate(cubpos[i]);
        posmodel->rotate(Vec3(0, 1, 0), CC_DEGREES_TO_RADIANS(50*i));
        GLuint modelLoc = glGetUniformLocation(glprogram->getProgram(), "model");
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, posmodel->m);
        glDrawArrays(GL_TRIANGLES, 0, 36);
    }
    glBindVertexArray(0);
}







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值