我正在参加「启航计划」

经过阅览本文,你将获得以下收成:
1.怎样烘托3D纹路
2.怎样烘托一个多纹路的立方体
3.怎样烘托多个立方体而且供给交互操作

上篇回顾

上2篇博文一看就懂的OpenGL ES教程——走进3D的国际之坐标体系(上篇)和一看就懂的OpenGL ES教程——走进3D的国际之坐标体系(下篇) 已经比较具体地将3D物体怎样烘托到2D平面的进程描述一遍,即经过坐标系的转换(模型改换-》视图改换-》投影改换-》视口改换),将原始极点坐标方位,改换到一个模拟出眼睛看物体的作用

温馨提示:假如还没有看过前面2篇博文,那么强烈建议先看下前面2篇博文,不然本文或许彻底看不懂~

前两篇博文是纯理论篇,咱们看的进程中不免感觉枯燥,估计能坚持看到最终的小伙伴都不多。假如说前两篇博文是良药苦口,那么今日的内容就如同炎炎夏日来一杯百事可乐相同爽快~~

烘托3D纹路

信任各位还记得讲烘托纹路的这篇博文吧一看就懂的OpenGL ES教程——临摹画手的浪漫之纹路映射(实践篇),其时是依照2D的办法烘托的,那今日就来看看假如烘托出一个3D作用的纹路。

关于怎样烘托2D纹路这儿就不再赘述了,不清楚的童鞋能够再去看看上面这篇博文。

直接从处理3D部分讲起,3D的中心便是处理那几个改换矩阵。有童鞋或许会问了,前两篇的改换矩阵推导那么杂乱,那么会不会每次需求运用到改换矩阵的时分都要算一遍?

答案明显不是。在软件开发职业开展如此兴旺的年代,这种杂乱的计算进程往往已经有优异的库帮程序员分管的,而这次帮咱们分管工作量的依然是glm

在之前写的博文一看就懂的OpenGL ES教程——仿抖音滤镜的奇技淫巧之改换滤镜(实践篇)中就已经见识过glm的好了,今日咱们能够进一步感受下glm的“温暖”。

        #version 300 es
        layout (location = 0) in vec4 aPosition;//输入的极点坐标,会在程序指定将数据输入到该字段\n"//假如传入的向量是不行4维的,主动将前三个重量设置为0.0,最终一个重量设置为1.0
        layout (location = 1) in vec2 aTextCoord;//输入的纹路坐标,会在程序指定将数据输入到该字段
        //输出的纹路坐标;
        out vec2 TexCoord;
        //模型矩阵
        uniform mat4 model;
        //调查矩阵
        uniform mat4 view;
        //投影改换矩阵
        uniform mat4 projection;
        void main() {
           //这儿其实是将上下翻转过来(因为安卓图片会主动上下翻转,所以转回来)
           TexCoord = vec2(aTextCoord.x, 1.0 - aTextCoord.y);
           //原始极点坐标按顺序左乘模型矩阵、调查矩阵、投影矩阵
           gl_Position = projection * view * model * aPosition;
        };

首要看看极点着色器,从3D到2D的改换处理都在这儿。咱们需求的是模型矩阵、调查矩阵、投影矩阵,在极点着色器中界说为uniform变量。然后让原始极点坐标按顺序左乘模型矩阵、调查矩阵、投影矩阵,得到最终真正在2D平面中显现的极点坐标(当然还有视口改换,不过这个OpenGL有直接供给api)。

片段着色器依然是最简略的纹路映射代码:

          #version 300 es
          precision mediump float;
          in vec2 TexCoord;
          out vec4 FragColor;
          //传入的纹路
          uniform sampler2D ourTexture;
          void main() { 
             FragColor = texture(ourTexture, TexCoord);
           };

在C++代码中,需求做的是经过glm得到对应的模型矩阵、调查矩阵、投影矩阵,并传给极点着色器。

//模型矩阵,将部分坐标转换为国际坐标
glm::mat4 model = glm::mat4(1.0f);
//视图矩阵,确认物体和摄像机的相对方位
glm::mat4 view = glm::mat4(1.0f);
//透视投影矩阵,完成近大远小的作用
glm::mat4 projection = glm::mat4(1.0f);
//沿着x轴旋转
model = glm::rotate(model, glm::radians(-45.0f), glm::vec3(1.0f, 0.0f, 0.0f));
// 留意,咱们将矩阵向咱们要进行移动场景的反方向移动。(右手坐标系,所以z正方从屏幕指向外部)
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
//这儿假定视场角为45度,视口的宽高直接取屏幕宽高,近平面间隔取0.1,远平面间隔取100
projection = glm::perspective(glm::radians(45.0f), (float) screenWidth / (float) screenHeight,
                              0.1f,
                              100.0f);
//将矩阵传递给shader
GLint modelLoc = glGetUniformLocation(program, "model");
GLint viewLoc = glGetUniformLocation(program, "view");
GLint projectionLoc = glGetUniformLocation(program, "projection");
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));

模型改换简略点,就让纹路绕x轴逆时针旋转-45度,glm::radians(-45.0f)为将其转为弧度。

视图改换,也简略点,假定相机就放在纹路的前方3个单位间隔方位,也便是纹路在摄像机的负z方向间隔摄像机3个单位,所以平移向量为(0.0f, 0.0f, -3.0f)。

接下来的透视投影矩阵咱们知道是最为杂乱的,所幸glm已经有直接供给透视投影矩阵的办法了:

template <typename T>
GLM_FUNC_DECL tmat4x4<T, defaultp> perspective(
   T fovy,
   T aspect,
   T near,
   T far);

这是一个泛型函数,返回透视投影需求的改换矩阵。返回值的类型tmat4x4<T, defaultp>是一个类型界说,它是GLM库中的一个模板类,用于表明一个4×4的矩阵。该类型界说包含两个模板参数:

-T:矩阵元素的类型,可所以浮点数、整数等。
-defaultp:矩阵的存储精度,默以为单精度浮点数。

该函数接受四个参数:

-fovy:视场角,以弧度为单位。
-aspect:视口的宽高比。
-zNear:近平面的间隔。
-zFar:远平面的间隔。

一看就懂的OpenGL ES教程-3D渲染实战

这儿假定视场角为45度,视口的宽高直接取屏幕宽高,近平面间隔取0.1,远平面间隔取100。

projection = glm::perspective(glm::radians(45.0f), (float) screenWidth / (float) screenHeight,
                              0.1f,
                              100.0f);

运转代码可得:

一看就懂的OpenGL ES教程-3D渲染实战

能够看到,原来平铺的纹路登时有了立体感,向后倾斜,而且有近大远小的感觉。

烘托立方体

接下来,咱们来画一个很有意思的正方体,染上酷炫的渐变色。

极点着色器和上面那制作3D纹路的简直相同,仅仅多了一个色彩的输入变量。

        #version 300 es
        layout (location = 0) in vec4 aPosition;//输入的极点坐标,会在程序指定将数据输入到该字段//假如传入的向量是不行4维的,主动将前三个重量设置为0.0,最终一个重量设置为1.0
        layout (location = 1) in vec4 aColor;//输入的色彩,会在程序指定将数据输入到该字段
        out vec4 vTextColor;//输出的色彩
        out vec2 TexCoord;//输出的纹路坐标;
        uniform mat4 model;
        uniform mat4 view;
        uniform mat4 projection;
        void main() {
            //直接把传入的坐标值作为传入烘托管线。gl_Position是OpenGL内置的
            gl_Position = projection * view * model * aPosition;
            vTextColor = aColor;
        };

片段着色器很简略,仅仅将输入色彩赋值给FragColor

        #version 300 es
        precision mediump float;
        in vec4 vTextColor;//输入的色彩
        out vec4 FragColor;
        //传入的纹路
        void main() {
             FragColor = vTextColor;
        };

因为是立方体,共有8个点,所以极点数组就比较杂乱了:

float vertices[] = {
        // 极点坐标                 色彩
        -0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,//反面左下角点 0
        0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.0f,//反面右下角点 1
        0.5f, 0.5f, -0.5f, 0.0f, 0.0f, 1.0f,//反面右上角点 2
        -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, 0.0f,//反面左上角点 3
        -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.0f,//前面左下角点 4
        0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 1.0f,//前面右下角点 5
        0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,//前面右上角点 6
        -0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f,//前面左上角点 7
};

咱们用EBO来烘托立方体的6个面,那么立方体的6个面能够如下表明:

unsigned int indices[] = {
        //反面
        0, 3, 1, // first triangle
        3, 2, 1, // second triangle
        //上面
        2, 3, 7, // first triangle
        7, 6, 2,  // second triangle
        //左边
        3, 0, 4, // first triangle
        4, 7, 3, // second triangle
        //右面
        5, 1, 2, // first triangle
        2, 6, 5, // second triangle
        //下面
        4, 0, 1, // first triangle
        1, 5, 4,// second triangle
        //前面
        4, 5, 6, // first triangle
        6, 7, 4, // second triangle
};

其实烘托代码和上面那烘托纹路的根本相同了,不过为了增加乐趣,我让它旋转起来:

//f表明不断改动的旋转视点,每一帧就改动1度
float f = 0.0f;
while (f >= 0) {
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    //铲除深度缓冲和色彩缓冲,开端烘托全新的一帧
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    //模型矩阵,将部分坐标转换为国际坐标
    glm::mat4 model = glm::mat4(1.0f);
    //视图矩阵,确认物体和摄像机的相对方位
    glm::mat4 view = glm::mat4(1.0f);
    //透视投影矩阵,完成近大远小的作用
    glm::mat4 projection = glm::mat4(1.0f);
    //沿着向量(0.5f, 1.0f, 0.0f)旋转
    model = glm::rotate(model, glm::radians(f), glm::vec3(0.5f, 1.0f, 0.0f));
    // 留意,咱们将矩阵向咱们要进行移动场景的反方向移动。(右手坐标系,所以z正方形从屏幕指向外部)
    view = glm::translate(view, glm::vec3(0.0f, 0.0f, -5.0f));
    projection = glm::perspective(glm::radians(45.0f),
                                  (float) screen_width / (float) screen_height, 0.1f,
                                  100.0f);
    GLint modelLoc = glGetUniformLocation(program, "model");
    GLint viewLoc = glGetUniformLocation(program, "view");
    GLint projectionLoc = glGetUniformLocation(program, "projection");
    glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
    glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
    glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
    glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
    //窗口显现,交换双缓冲区
    eglSwapBuffers(display, winSurface);
    //每一帧就改动1度
    f++;
    //间隔0.4秒烘托一帧
    sleep(static_cast<unsigned int>(0.4));
}

这儿变量f表明不断改动的旋转视点,每一帧就改动1度,间隔0.4秒烘托一帧,这样就有动画的作用,运转作用如下:

一看就懂的OpenGL ES教程-3D渲染实战

perfect~酷毙了!

不过,怎样看起来很古怪的姿态?

原本应该被遮挡住的面,反而能够看到,明显不契合实际情况。原因是少开了深度测验,关于深度测验,从前在博文一看就懂的OpenGL ES教程——图形烘托管线的那些事结尾讲过,不知咱们是否有形象,简略来说,便是记载每个片段的深度,关于不透明物体,在烘托的时分只烘托深度最小的,这样就契合咱们看东西前面的物体会挡住后边物体的情景,又能够节省不必要的性能开支。

具体来说,深度缓冲是在片段着色器运转之后在屏幕空间中运转的。深度缓冲就像色彩缓冲(Color Buffer)(贮存所有的片段色彩:视觉输出)相同,在每个片段中贮存了信息,而且(通常)和色彩缓冲有着相同的宽度和高度。深度缓冲是由窗口体系主动创建的,它会以16、24或32位float的方式贮存它的深度值。在大部分的体系中,深度缓冲的精度都是24位的。

当深度测验(Depth Testing)被启用的时分,OpenGL会将一个片段的深度值与深度缓冲的内容进行对比。OpenGL会执行一个深度测验,假如这个测验经过了的话,深度缓冲将会更新为新的深度值。假如深度测验失败了,片段将会被丢弃

敞开深度测验只需求一行代码:

glEnable(GL_DEPTH_TEST);

还有便是要在烘托每帧之前,就像清空色彩缓冲相同在运用glClear办法加上GL_DEPTH_BUFFER_BIT表明清空深度缓冲:

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

敞开深度测验之后运转:

一看就懂的OpenGL ES教程-3D渲染实战

这下就正常了~

烘托多纹路的立方体

再来玩点更有趣的东西。能不能立方体每个面都烘托上不同纹路呢?答案是必定的。

极点着色器直接运用上面烘托一个3D纹路的代码即可,毕竟仅仅将1一个纹路改为烘托6个:

        #version 300 es
        layout (location = 0) in vec4 aPosition;//输入的极点坐标,会在程序指定将数据输入到该字段\n"//假如传入的向量是不行4维的,主动将前三个重量设置为0.0,最终一个重量设置为1.0
        layout (location = 1) in vec2 aTextCoord;//输入的纹路坐标,会在程序指定将数据输入到该字段
        //输出的纹路坐标;
        out vec2 TexCoord;
        //模型矩阵
        uniform mat4 model;
        //调查矩阵
        uniform mat4 view;
        //投影改换矩阵
        uniform mat4 projection;
        void main() {
           //这儿其实是将上下翻转过来(因为安卓图片会主动上下翻转,所以转回来)
           TexCoord = vec2(aTextCoord.x, 1.0 - aTextCoord.y);
           //原始极点坐标按顺序左乘模型矩阵、调查矩阵、投影矩阵
           gl_Position = projection * view * model * aPosition;
        };

片段着色器相同能够运用烘托一个3D纹路的代码:

          #version 300 es
          precision mediump float;
          in vec2 TexCoord;
          out vec4 FragColor;
          //传入的纹路
          uniform sampler2D ourTexture;
          void main() { 
             FragColor = texture(ourTexture, TexCoord);
           };

C++层中,先界说6个面:

float vertices[] = {
        // 极点坐标           纹路坐标
        //反面
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,  //2
        0.5f, -0.5f, -0.5f,  1.0f, 0.0f,  //1
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f, //0
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,  //0
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,  //3
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,  //2
        //前面
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,//4
        0.5f, -0.5f,  0.5f,  1.0f, 0.0f,//5
        0.5f,  0.5f,  0.5f,  1.0f, 1.0f,//6
        0.5f,  0.5f,  0.5f,  1.0f, 1.0f,//6
        -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,//7
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,//4
        //左边
        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,//7
        -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,//3
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,//0
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,//0
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,//4
        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,//7
        //右面
        0.5f, -0.5f, -0.5f,  0.0f, 1.0f,//1
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,//2
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,//6
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,//6
        0.5f, -0.5f,  0.5f,  0.0f, 0.0f,//5
        0.5f, -0.5f, -0.5f,  0.0f, 1.0f,//1
        //底面
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,//0
        0.5f, -0.5f, -0.5f,  1.0f, 1.0f,//1
        0.5f, -0.5f,  0.5f,  1.0f, 0.0f,//5
        0.5f, -0.5f,  0.5f,  1.0f, 0.0f,//5
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,//4
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,//0
        //上面
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,//6
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,//2
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,//3
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,//3
        -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,//7
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,//6
};

能够看到,因为每个面都要烘托一个纹路,所以每个面的点都对应一个纹路坐标

VBO, VAO的装备代码如下:

    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    // 读取极点属性装备
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) 0);
    glEnableVertexAttribArray(1);
    // 读取纹路坐标装备
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float),
                          (void *) (3 * sizeof(float)));
    glEnableVertexAttribArray(1);

然后先把Java层传入的6个Bitmap的图片信息方针保存起来:

//持有图片信息类BitmapInfo的调集,这儿是保存6个面的纹路对应的图片信息
std::vector<BitmapInfo> bitmapVector;
//bitmaps为从Java层传入的6个Bitmap方针
jsize ref_size = env->GetArrayLength(bitmaps);
for (int i = 0; i < ref_size; ++i) {
    jobject bitmap = env->GetObjectArrayElement(bitmaps, i);
    AndroidBitmapInfo bmpInfo;
    BitmapInfo bitmapInfo(env,bitmap,bmpInfo);
    bitmapVector.emplace_back(bitmapInfo);
}

现在6个面的纹路烘托仍是相同的处理,所以只要复用到一个纹路方针即可。以下是纹路方针的装备:

unsigned int texture1;
//-------------------- texture1的装备start ------------------------------
glGenTextures(1, &texture1);
glBindTexture(GL_TEXTURE_2D, texture1);
// set the texture wrapping parameters(装备纹路盘绕)
//横坐标盘绕装备
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
                GL_REPEAT);    // set texture wrapping to GL_REPEAT (default wrapping method)
//纵坐标盘绕装备
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
// set texture filtering parameters(装备纹路过滤)
//纹路分辨率大于图元分辨率,即纹路需求被缩小的过滤装备
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
//纹路分辨率小于图元分辨率,即纹路需求被放大的过滤装备
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// load image, create texture and generate mipmaps
//这儿指定纹路尺度为1280*720,所以运用到的图片尺度也有必要契合这个尺度
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1280, 720, 0, GL_RGBA,
             GL_UNSIGNED_BYTE, NULL);
//-------------------- texture1的装备end ------------------------------
//对着色器中的纹路单元变量进行赋值
glUniform1i(glGetUniformLocation(program, "ourTexture"), 0);

改换矩阵的处理和上面烘托立方体比方是一模相同的,不同的是这儿需求烘托6个面的纹路:

 for (int i = 0; i < 36; i = i + 6) {
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, texture1);
            int index = i/6;
            //取出每个纹路的图片信息
            BitmapInfo bmpInfo = bitmapVector[index];
            void *bmpPixels;
            AndroidBitmap_lockPixels(env, bmpInfo.bitmap, &bmpPixels);
            //替换纹路,比重新运用glTexImage2D性能高
            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bmpInfo.bmpInfo.width, bmpInfo.bmpInfo.height, GL_RGBA,
                            GL_UNSIGNED_BYTE,
                            bmpPixels);
            AndroidBitmap_unlockPixels(env, bmpInfo.bitmap);
            //每6个点烘托一个面
            glDrawArrays(GL_TRIANGLES, i, 6);
        }

便是取出6个面的图片信息,然后经过glTexSubImage2D办法进行纹路的烘托,假如对这个办法不熟悉,能够看下之前写的博文,里边有具体介绍: 一看就懂的OpenGL ES教程——烘托宫崎骏动漫重拾幼年

运转下:

一看就懂的OpenGL ES教程-3D渲染实战

简直帅到没朋友了,我乐意称它为旋转的幼年~

烘托多个立方体

现在咱们要画10个立方体,别离在不同的方位,要怎样做呢。

想一下咱们之前讲过的坐标体系,和单个物体方位坐标相关的便是部分坐标系到国际坐标系的转换了,原本自己的自拍是以自己为中心坐标点的,现在要拍合照,那么每个人都要改换到另一个坐标系。

所以10个立方体只需求别离传不同的模型矩阵即可:

//每个立方体的平移向量
glm::vec3 cubePositions[] = {
        glm::vec3( 2.0f,  5.0f, -15.0f),
        glm::vec3(-1.5f, -2.2f, -2.5f),
        glm::vec3(-3.8f, -2.0f, -12.3f),
        glm::vec3 (2.4f, -0.4f, -3.5f),
        glm::vec3(-1.7f,  3.0f, -7.5f),
        glm::vec3( 1.3f, -2.0f, -2.5f),
        glm::vec3( 1.5f,  2.0f, -2.5f),
        glm::vec3( 1.5f,  0.2f, -1.5f),
        glm::vec3(-1.3f,  1.0f, -1.5f),
        glm::vec3( 0.0f,  0.0f,  0.0f),
        ……
 for (unsigned int i = 0; i < 10; i++) {
            //先每个立方体做平移改换
            glm::mat4 model = glm::mat4(1.0f);
            model = glm::translate(model, cubePositions[i]);
            //每个立方体再做不通的旋转改换
            switch (i % 3) {
                case 0:
                    model = glm::rotate(model, glm::radians(f * 2), glm::vec3(1.0f, 0.3f, 0.5f));
                    break;
                case 1:
                    model = glm::rotate(model, glm::radians(f), glm::vec3(1.0f, 1.0f, 0.5f));
                    break;
                case 2:
                    model = glm::rotate(model, glm::radians(f * 1.5f), glm::vec3(0.5f, 0.0f, 0.5f));
                    break;
            }
            //传给着色器
            glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
            //制作立方体
            glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
    }
};

每个立方体的模型矩阵应用不同的平移和旋转改换,这样10个立方体看起来就彻底不相同的方位和”姿态”了。

运转一下:

愈加酷毙~

交互操作

假如要完成旋转摄像机观看视点调查这些立方体的作用,要怎样处理呢?

经过上面的比方,要旋转立方体处理的是模型矩阵,那么改动摄像机观看视点那便是改动调查矩阵了,这也能体会到将改换拆分为多个过程的好处了吧。

在之前的博文一看就懂的OpenGL ES教程——走进3D的国际之坐标体系(上篇)从前讲过从国际坐标改换到以摄像机为原点的调查空间坐标系,需求知道摄像机的方位以及以摄像机为原点建立起来的坐标系的3个坐标轴在国际坐标中的对应向量。在上面的比方里,仅仅简略地把原点从国际坐标系原点进行平移就得到摄像机坐标系原点方位,即假定摄像机的3个轴是别离和国际坐标系的3个坐标轴平行的,但实际上不是这么简略的,摄像机或许会有各种旋转,所以3个轴不一定和国际坐标系的3个坐标轴平行。博文一看就懂的OpenGL ES教程——走进3D的国际之坐标体系(上篇)中得到的调查矩阵为:

一看就懂的OpenGL ES教程-3D渲染实战

其中R\color{red}R是摄像机的右向量即x轴,U\color{green}U是摄像机的上向量即y轴,D\color{blue}D是摄像机的方向向量z轴,P\color{purple}P是摄像机方位向量。

不过用代码来表明这个调查矩阵仍是太麻烦了,好在交心的glm又有供给直接运用的办法:

template <typename T, precision P>
GLM_FUNC_DECL tmat4x4<T, P> lookAt(
   tvec3<T, P> const & eye,
   tvec3<T, P> const & center,
   tvec3<T, P> const & up);

eye:表明摄像机在国际坐标系中的方位。 center:表明摄像机看向的点。 up:表明摄像机的上方向向量。

什么叫做摄像机看向的点呢,比方下图,摄像机看向的点便是国际坐标系的原点:

一看就懂的OpenGL ES教程-3D渲染实战

要完成出一种摄像机在改动观看视点的作用,其实便是改动摄像机看过去的方针点,也便是摄像机的方向向量。因为要依据触摸而改动,所以将摄像机相关的方位和向量都界说为变量。

假定摄像机的方位、方向向量、上方向向量别离为:

glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 10.0f);
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -10.0f);
glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);

则摄像机看过去的方针点为:cameraPos + cameraFront。

则结构出来的调查矩阵为:

glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);

后续的烘托逻辑也是和前面比方相同,现在关键便是怎样经过触摸屏幕去改动摄像机观看的视点。

咱们知道二维空间中的旋转只需求一个视点即可,那三维空间的旋转呢?

欧拉角(Euler Angle)是能够表明3D空间中任何旋转的3个值,由莱昂哈德欧拉(Leonhard Euler)在18世纪提出。一共有3种欧拉角:俯仰角(Pitch)、偏航角(Yaw)和滚转角(Roll),下面的图片展示了它们的含义:

一看就懂的OpenGL ES教程-3D渲染实战

俯仰角(Pitch)表明垂直于x轴的旋转,偏航角(Yaw)表明垂直于y轴的旋转,滚转角(Roll)表明垂直于z轴的旋转。

现在咱们要求的,便是这三个视点改动关于方向向量的影响

一般关于摄像机体系来说,咱们只关心俯仰角和偏航角,所以咱们不会讨论滚转角(幻想下一个第一人称游戏的视角的滚转角总在变会有多难过)。

即求给定一个俯仰角和偏航角,怎样把它们转换为一个代表新的方向向量的3D向量。

咱们先温习下基础的三角函数知识:

一看就懂的OpenGL ES教程-3D渲染实战

假如咱们把斜边边长界说为1,咱们就能知道邻边的长度是cos x/h=cos x/1=cos x ,它的对边是sin y/h=sin y/1=sin y。

关于俯仰角Pitch来说,咱们能够从xz轴所在平面看向Y轴,假定方向向量为单位向量,则如图:

一看就懂的OpenGL ES教程-3D渲染实战

假定Pitch角为\theta,则由三角函数基础可得关于一个给定俯仰角的y值等于sin⁡\sin\ \theta

所以俯仰角和方向向量的y重量的关系为:

direction.y = sin(glm::radians(pitch)); // 留意咱们先把视点转为弧度

同理咱们能够只看xz平面,看偏航角(Yaw)和方向向量的x、z重量的关系,可得到:

一看就懂的OpenGL ES教程-3D渲染实战

就像俯仰角的三角形相同,咱们能够看到x重量取决于cos(yaw)的值,z值相同取决于(yaw)的值,而此刻斜边是单位长度的方向向量在xz平面的投影,即长度为cos(pitch),所以可得方向向量的x、z重量为:

direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw));
direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));

因为是要经过触摸屏幕改动摄像机的观看方向,所以就需求老生常谈的onTouchEvent办法了,咱们新建一个Native办法去给Java的onTouchEvent办法调用:

extern "C"
JNIEXPORT void JNICALL
Java_com_example_openglstudydemo_YuvPlayer_handleTouchEvent(JNIEnv *env, jobject thiz, jint action,
                                                            jfloat xpos, jfloat ypos) {
    TouchCtlCamera_LOGD("handleTouchEvent action:%d,xpos:%f,ypos:%f:",action, xpos, ypos);
    if (touchCtlCamera == nullptr){
        return;
    }
    switch (action) {
        case TouchActionMode::ACTION_UP:
            touchCtlCamera->lastX = 0.0f;
            touchCtlCamera->lastY = 0.0f;
            break;
        case TouchActionMode::ACTION_CANCEL:
            touchCtlCamera->lastX = 0.0f;
            touchCtlCamera->lastY = 0.0f;
            break;
        case TouchActionMode::ACTION_DOWN:
            //每次手指按下,就用lastX和lastY保存起来
            touchCtlCamera->lastX = xpos;
            touchCtlCamera->lastY = ypos;
            break;
        case TouchActionMode::ACTION_MOVE:
            //算出当前和上一次触摸点移动的间隔,即滑动间隔
            float xoffset = xpos - touchCtlCamera->lastX;
            float yoffset = touchCtlCamera->lastY - ypos;
            touchCtlCamera->lastX = xpos;
            touchCtlCamera->lastY = ypos;
            //摄像机的移动敏感度,即手指滑动对摄像机移动视点的影响
            float sensitivity = 0.02;
            xoffset *= sensitivity;
            yoffset *= sensitivity;
            //依据触摸的偏移量计算出视点的改动
            touchCtlCamera->yaw += xoffset;
            touchCtlCamera->pitch += yoffset;
            if (touchCtlCamera->pitch > 89.0f) {
                touchCtlCamera->pitch = 89.0f;
            }
            if (touchCtlCamera->pitch < -89.0f) {
                touchCtlCamera->pitch = -89.0f;
            }
            //计算出视点改动导致的方向向量的改动
            glm::vec3 front;
            front.x = cos(glm::radians(touchCtlCamera->yaw)) * cos(glm::radians(touchCtlCamera->pitch));
            front.y = sin(glm::radians(touchCtlCamera->pitch));
            front.z = sin(glm::radians(touchCtlCamera->yaw)) * cos(glm::radians(touchCtlCamera->pitch));
            touchCtlCamera->cameraFront = glm::normalize(front);
            break;
    }

依据手指的移动,得到对应的俯仰角(Pitch)、偏航角(Yaw),然后得到对应的摄像机方向向量,因为烘托是不断循环的,所以在手指的移动同时改动了方向向量,所以每次烘托运用的方向向量就发生了偏移,从而就逐步改动了摄像机看过去的方针方向,形成了动效。

一看就懂的OpenGL ES教程-3D渲染实战

wonderful,有点打游戏的感觉了!

总结

今日破纪录写了超越2万字了,不过内容确实挺过瘾的,从简略的3D作用的烘托一张图片,到烘托旋转立方体,再到给立方体每一面烘托不同的纹路,最终到烘托多个立方体而且供给触摸交互操作,也算是体验到3D烘托的快感了,后边章节,就能够开端触摸光照相关的知识了。

项目代码

opengl-es-study-demo 不断更新中,欢迎各位来star~

参阅:

GAMES101-现代计算机图形学入门-闫令琪
改换
Fundamentals of Computer Graphics, Fourth Edition
计算机图形学系列笔记
坐标体系

原创不易,假如觉得本文对自己有协助,别忘了随手点赞和关注,这也是我创作的最大动力~

系列文章目录

体系化学习系列博文,请看音视频体系学习总目录

实践项目: 介绍一个自己刚出炉的安卓音视频播放录制开源项目 欢迎各位来star~

相关专栏:

C/C++基础与进阶之路

音视频理论基础系列专栏

音视频开发实战系列专栏

一看就懂的OpenGL es教程