本文正在参与「金石计划」
前篇回顾*
前面一篇文章咱们介绍了关于如安在OpenGL中运用纹路,以及纹路坐标,纹路映射等内容,信任你们已经都学会了。那么今日咱们来做个稍微难点的东西:运用OpenGL画一个立方体贴图。便是下面这种作用。
废话不多说,咱们直接进入正题。
纹路坐标设置
同样的,咱们需求先界说好立方体的极点坐标:一个立方体有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);
假如一切顺利你或许看到如下作用:
额,不是说好的立方体么,这不只是个长方形么。。
先别急,还没竣工,为了能够看到立方体的作用,咱们还需求来了解下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) 完毕。下面的图示显示了整个流程及各个转化进程做了什么。
- 部分坐标是目标相关于部分原点的坐标;也是目标开端的坐标。
- 将部分坐标转化为世界坐标,世界坐标是作为一个更大空间范围的坐标体系。这些坐标是相关于世界的原点的。
- 接下来咱们将世界坐标转化为调查坐标,调查坐标是指以摄像机或调查者的视点调查的坐标。
- 在将坐标处理到调查空间之后,咱们需求将其投影到裁剪坐标。裁剪坐标是处理-1.0到1.0范围内并判别哪些极点将会出现在屏幕上。
- 终究,咱们需求将裁剪坐标转化为屏幕坐标,咱们将这一进程成为视口改换(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矩阵,有两种,一种是正射投影,一种是透视投影,正射投影用的不多,咱们来说下透视投影。
透视投影
透视投影是为了处理实在世界中的,离咱们越远的物体,看起来就会越小。用火车路来看吧:
透视投影很好的处理了这个问题,物体远近体现在咱们的屏幕中便是物体的深度,Z轴。
透视投影运用glm::perspective来设置。
- 参数1:界说了fov的值,它表明的是视野(Field of View) ,而且设置了调查空间的巨细。关于一个实在的调查作用,它的值经常设置为45.0,当然咱们也能够自界说巨细来看作用。
- 参数2:设置了宽高比,由视口的高除以宽.
- 参数3和4:设置了平截头体的近和远平面。咱们经常设置近间隔为0.1而远间隔设为100.0。一切在近平面和远平面的极点且处于平截头体内的极点都会被烘托。
glm::perspective
所做的其实便是再次创立了一个界说了可视空间的大的平截头体,任安在这个平截头体的目标终究都不会出现在裁剪空间体积内,而且将会受到裁剪。一个透视平截头体能够被可视化为一个不均匀形状的盒子,在这个盒子内部的每个坐标都会被映射到裁剪空间的点。一张透视平截头体的照片如下所示:
好了,设置好上面三个矩阵之后,咱们来看下作用:
嗯,图画变为了正方形了,可是仍是没看到立方体呀?
假如你调查细心,前面咱们把View矩阵的第一个参数也便是眼睛的方位设置为了vec3(0.0f,0.0f,2.5f),x和y为0,z值为一个正数,z值代表深度,阐明咱们眼睛方位是正对屏幕的。所以你看到的仍是一个正方形,咱们把眼睛方位改为vec3(2.5f,1.5f,2.5f),看下作用:
作用已经出来了,下面咱们让这个正方体跟着屏幕滑动起来。
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));
}
来看终究作用:
好了,本文就解说到这儿了,本文首要经过一个立方体的烘托进程,来解说了OpenGL中的坐标体系的运用。
下篇文章,将会解说OpenGL中的光照运用。我是小余,重视我咱们下期见。
参考:
OpenGL中文教程