本文正在参与「金石计划」

前篇回顾*

前面一篇文章咱们介绍了关于如安在OpenGL中运用纹路,以及纹路坐标,纹路映射等内容,信任你们已经都学会了。那么今日咱们来做个稍微难点的东西:运用OpenGL画一个立方体贴图。便是下面这种作用。

【安卓音视频开发OpenGLES】 开发入门(三):绘制一个3D立方体

废话不多说,咱们直接进入正题。

纹路坐标设置

同样的,咱们需求先界说好立方体的极点坐标:一个立方体有6个面,一个面需求2个三角形图元组成,也便是需求6个极点(当然你能够运用EBO索引缓冲目标来让两个三角形共用4个极点),这儿为了便利咱们理解,我就直接运用一个面6个极点来处理了,6个面便是36个极点坐标,别离界说如下:

//0.初始化极点数据
  float 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
   };

注意:每一行的前三位为一个极点坐标方位,后两位为纹路坐标方位,因为咱们还需求为每个面都设置一个纹路贴图。

设置好纹路坐标,下一步便是关于的极点着色器以及片段着色器的编写了。

极点着色器

#version 300 es
layout(location = 0) in vec4 vPosition;
layout(location = 1) in vec2 texCords;
out vec2 TexCoord;
void main()
{
  gl_Position = vPosition;
  TexCoord = texCords;
}

片段着色器:

#version 300 es
precision mediump float;
in vec2 TexCoord;
out vec4 fragColor;
uniform sampler2D textureColor;
void main()
{
  vec3 picColor = vec3 (texture(textureColor,TexCoord));
  fragColor = vec4 (picColor, 1.0 );
}

能够看到着色器代码部分很简单,和前面一篇文章的设置正方形的极点着色器根本相同。所以不要觉得GLSL是什么很玄乎的东西,你只要把他看做是运行在GPU上的一段处理输入并输出的代码就能够

in和out关键字的运用,一个是输入,一个是输出。

编译着色器

programObj = GLUtils::CreateProgram(vShaderStr,fShaderStr);

编译着色器一般步骤:

  • 1.创立极点着色器目标,给极点着色器目标绑定着色器代码,编译极点着色器。
  • 2.用同样的办法,创立并编译片段着色器、
  • 3.创立着色器程序目标,并将1,2中创立的着色器目标依附到着色器程序目标中,终究链接着色器。

这儿我将编译着色器封装在了一个工具库中,详见github。

设置极点特点:

//2.生成VAO,VBO目标,并绑定极点特点
GLuint VBO;
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(GLfloat),(GLvoid*)0);
glEnableVertexAttribArray(0);
//极点色彩特点
glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,5*sizeof(GLfloat),(GLvoid*)(3*sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glBindVertexArray(GL_NONE);

由于这儿咱们没有运用EBO索引缓冲目标,所以不需求EBO来设置极点索引。

极点特点的规范流程:

  • 1.创立极点数组目标VAO以及极点缓冲目标VBO
  • 2.绑定极点数组目标VAO
  • 3.绑定VBO,并为VAO绑定对应的极点数据
  • 4.设置极点坐标特点/纹路坐标特点等
  • 5.解绑VAO目标。

记住,这是规范流程,不同形状需求,无非便是改动极点数据结构,其实都能够套用这个步骤。

加载纹路贴图:

//生成纹路
glGenTextures(1, &textureID);
ImageUtil::_stbi_set_flip_vertically_on_load(true);
int width, height, nrComponents;
unsigned char *data = ImageUtil::_stb_image_load("/sdcard/mmpic.png", &width, &height, &nrComponents, 0);
if (data)
{
  GLenum format;
  if (nrComponents == 1)
    format = GL_RED;
  else if (nrComponents == 3)
    format = GL_RGB;
  else if (nrComponents == 4)
    format = GL_RGBA;
​
  glBindTexture(GL_TEXTURE_2D, textureID);
  glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
  glGenerateMipmap(GL_TEXTURE_2D);
​
  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_LINEAR_MIPMAP_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
​
  ImageUtil::_stb_image_free(data);
}
else
{
  LOGCATE("Texture failed to load at path: %s",data);
  ImageUtil::_stb_image_free(data);
}

纹路加载进程也是规范化进程:

  • 1.调用glBindTexture绑定纹路,由于这儿运用的是2D纹路,所以运用的是GL_TEXTURE_2D
  • 2.调用glTexImage2D为绑定的2D纹路绑定添加纹路数据。
  • 3.调用glGenerateMipmap,自动生成纹路mipmap。

这儿加载纹路贴图运用到了stb_image.h这个类库,强烈推荐咱们能够去github上看看,这个库中还有很多其他好用的东西。

stb_image.h:github.com/nothings/st…

设置好这些,咱们运用下面办法来烘托:

glDrawArrays(GL_TRIANGLES, 0, 36);

假如一切顺利你或许看到如下作用:

【安卓音视频开发OpenGLES】 开发入门(三):绘制一个3D立方体

额,不是说好的立方体么,这不只是个长方形么。。

先别急,还没竣工,为了能够看到立方体的作用,咱们还需求来了解下OpenGL中的坐标体系。

坐标体系

首要要知道OpenGL的一切坐标转化都是为了将local坐标转化为屏幕上的坐标

这个转化进程需求经过下面几个坐标:

  • 部分空间(Local Space,或许称为物体空间(Object Space))
  • 世界空间(World Space)
  • 调查空间(View Space,或许称为视觉空间(Eye Space))
  • 裁剪空间(Clip Space)
  • 屏幕空间( Screen Space)

而这些坐标在转化进程中,又需求运用到几个转化矩阵,其间最重要的几个别离是模型(Model)视图(View)投影(Projection) 三个矩阵。

首要,极点坐标开端于部分空间(Local Space) ,称为部分坐标(Local Coordinate) ,然后经过世界坐标(World Coordinate)调查坐标(View Coordinate)裁剪坐标(Clip Coordinate) ,并终究以屏幕坐标(Screen Coordinate) 完毕。下面的图示显示了整个流程及各个转化进程做了什么。

【安卓音视频开发OpenGLES】 开发入门(三):绘制一个3D立方体

  1. 部分坐标是目标相关于部分原点的坐标;也是目标开端的坐标。
  2. 将部分坐标转化为世界坐标,世界坐标是作为一个更大空间范围的坐标体系。这些坐标是相关于世界的原点的。
  3. 接下来咱们将世界坐标转化为调查坐标,调查坐标是指以摄像机或调查者的视点调查的坐标。
  4. 在将坐标处理到调查空间之后,咱们需求将其投影到裁剪坐标。裁剪坐标是处理-1.0到1.0范围内并判别哪些极点将会出现在屏幕上。
  5. 终究,咱们需求将裁剪坐标转化为屏幕坐标,咱们将这一进程成为视口改换(Viewport Transform) 。视口改换将坐落-1.0到1.0范围的坐标转化到由glViewport函数所界说的坐标范围内。终究转化的坐标将会送到光栅器,由光栅器将其转化为片段。

上面这段是引证的官网话语。要去看的话需求花很多时间而且并不一定能看懂,下面小余就用大白话讲下:

  • 1.烘托的物体进行不是有或许进行平移,旋转,缩放等动作么,那么就用模型(Model)矩阵来处理。
  • 2.物体是烘托好了,可是咱们从哪个视点调查呢?便是咱们眼睛看物体哪个地方,那个方向呢,那就用视图(View)矩阵来处理。
  • 3.在咱们实在世界中,看到的物体是不是越远就越小,越近就越大呢?完成这种作用就用到投影(Projection)矩阵来处理。

这样是不是很好理解了呢?

依据上面的解说,咱们先来设置这几个矩阵,让咱们看到立方体作用。

首要来改造下极点着色器代码:

#version 300 es
layout(location = 0) in vec4 vPosition;
layout(location = 1) in vec2 texCords;
out vec2 TexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
  gl_Position = projection*view*model*vPosition;
  TexCoord = texCords;
}

咱们运用uniform关键字,传递三个全局的mat4矩阵变量,别离表明model矩阵,view矩阵,以及projection矩阵。

在C++代码中对极点着色器中的全局变量进行设置:

glm::mat4 model,view,projection;
 glUniformMatrix4fv(glGetUniformLocation(programObj,"model"),1,GL_FALSE,glm::value_ptr(model));
view = glm::lookAt(glm::vec3(0.0f,0.0f,2.5f),glm::vec3(0.0f,0.0f,0.0f),glm::vec3(0.0f,1.0f,0.0f));   glUniformMatrix4fv(glGetUniformLocation(programObj,"view"),1,GL_FALSE,glm::value_ptr(view));
projection = glm::perspective(45.0f,(GLfloat)mScreenWidth/(GLfloat)mScreenHeight,0.1f,100.0f);
    glUniformMatrix4fv(glGetUniformLocation(programObj,"projection"),1,GL_FALSE,glm::value_ptr(projection));

对Model矩阵,咱们没有做任何处理,只是设置了一个初始值。

对View矩阵,咱们运用了glm::lookAt办法进行设置,下面来看lookAt办法参数:

  • 参数1:代表从哪看,便是调查者眼睛地点的方位,比方咱们要在烘托的3D物体的左边看,那么就能够设置为vec3(-1.0f,0.0f,0.0f),就好像咱们在手机的侧边-1.0f间隔处看这个物体,这个间隔能够调节,越大,眼睛方位离物体就越远,物体看起来就越小。
  • 参数2:代表眼睛看的方位,这儿咱们指定为vec3(0.0f,0.0f,0.0f),表明眼睛凝视物体的中心方位,可调节。
  • 参数3:这是一个笔直咱们眼睛的一个分量,这个向量是用来干嘛的呢,便是标识咱们是头是斜着看呢仍是正对着,仍是倒立看呢?能理解不。

经过上面的三个参数,咱们就建立了一个调查者,调查者的不同,咱们看到的图画也不同,就好像:

一个女人漂亮不漂亮,光看背影是不行的。。

对projection矩阵,有两种,一种是正射投影,一种是透视投影,正射投影用的不多,咱们来说下透视投影。

透视投影

透视投影是为了处理实在世界中的,离咱们越远的物体,看起来就会越小。用火车路来看吧:

【安卓音视频开发OpenGLES】 开发入门(三):绘制一个3D立方体

透视投影很好的处理了这个问题,物体远近体现在咱们的屏幕中便是物体的深度,Z轴

透视投影运用glm::perspective来设置。

  • 参数1:界说了fov的值,它表明的是视野(Field of View) ,而且设置了调查空间的巨细。关于一个实在的调查作用,它的值经常设置为45.0,当然咱们也能够自界说巨细来看作用。
  • 参数2:设置了宽高比,由视口的高除以宽.
  • 参数3和4:设置了平截头体的近和远平面。咱们经常设置近间隔为0.1远间隔设为100.0一切在近平面和远平面的极点且处于平截头体内的极点都会被烘托

glm::perspective所做的其实便是再次创立了一个界说了可视空间的大的平截头体,任安在这个平截头体的目标终究都不会出现在裁剪空间体积内,而且将会受到裁剪。一个透视平截头体能够被可视化为一个不均匀形状的盒子,在这个盒子内部的每个坐标都会被映射到裁剪空间的点。一张透视平截头体的照片如下所示:

【安卓音视频开发OpenGLES】 开发入门(三):绘制一个3D立方体

好了,设置好上面三个矩阵之后,咱们来看下作用:

【安卓音视频开发OpenGLES】 开发入门(三):绘制一个3D立方体

嗯,图画变为了正方形了,可是仍是没看到立方体呀?

假如你调查细心,前面咱们把View矩阵的第一个参数也便是眼睛的方位设置为了vec3(0.0f,0.0f,2.5f),x和y为0,z值为一个正数,z值代表深度,阐明咱们眼睛方位是正对屏幕的。所以你看到的仍是一个正方形,咱们把眼睛方位改为vec3(2.5f,1.5f,2.5f),看下作用:

【安卓音视频开发OpenGLES】 开发入门(三):绘制一个3D立方体

作用已经出来了,下面咱们让这个正方体跟着屏幕滑动起来。

float lastX = 0,lastY = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
  int action = event.getAction();
  float dx = 0,dy = 0;
  float curX = event.getX();
  float curY = event.getY();
  switch (action){
    case MotionEvent.ACTION_DOWN:
      lastX = curX;
      lastY = curY;
       break;
​
    case MotionEvent.ACTION_MOVE:
      dx = curX-lastX;
      dy = curY-lastY;
      mRenderer.move(dx,dy);
      lastX = curX;
      lastY = curY;
       break;
    case MotionEvent.ACTION_UP:
      mRenderer.move(0.0f,0.0f);
       break;
   }
  Log.d("MyGLSurfaceView","curX:"+curX+" curY:"+curY);
  Log.d("MyGLSurfaceView","dx:"+dx+" dy:"+dy);
  return true;
​
}

这儿咱们界说一个native接口,将每次屏幕滑动触发的x和y偏移量传递给native层。

在native层咱们运用model矩阵来处理滑动作用:

 /**
 * 计算旋转轴
 * 运用两个向量的叉乘能够获取一个笔直这两个向量的新向量
 * 向量V为移动的方向向量vec3(moveX,moveY,0.0f);
 * 向量K为笔直屏幕的向量vec3(0.0f,0.0f,1.0f); 能够运用单位向量替代
 * */
if(moveX!=0||moveY!=0){
  float radius = 360.0f;
  float moveL = sqrt(moveX*moveX+moveY*moveY);
  LOGCATD("move moveL:%f",moveL);
  glm::vec3 _v = glm::vec3(moveX,moveY,0.0f);
  glm::vec3 _k = glm::vec3(0.0f,0.0f,1.0f);
  glm::vec3 _u = glm::cross(_k,_v);
  float angle = sin(moveL)*radius;
  LOGCATD("move angle:%f %f %f",_u.x,_u.y,_u.z);
  model = glm::rotate(model,angle,_u);
}else {
  model = glm::rotate(model,0.0f,glm::vec3(0.0f,1.0f,0.0f));
}

来看终究作用:

【安卓音视频开发OpenGLES】 开发入门(三):绘制一个3D立方体

好了,本文就解说到这儿了,本文首要经过一个立方体的烘托进程,来解说了OpenGL中的坐标体系的运用。

下篇文章,将会解说OpenGL中的光照运用。我是小余,重视我咱们下期见。

参考:

OpenGL中文教程