本文正在参加「金石方案」
前篇回顾
前面一篇文章咱们学习了OpenGLES的一些基础知识,包含:图像烘托管线,极点着色器,片段着色器,着色器程序目标,极点数组目标(VAO),极点缓冲目标(VBO),极点数据结构等,并用这些基础知识画了一个三角形。
今天咱们来学习画一个正方体,并给正方体附上纹路。在绘图之前,咱们需求先来学习一些前置知识。
什么是纹路?
前面咱们制作三角形的时候,是对每个极点做了色彩的分配,这种情况适用于纯色制作,如果要制作一些杂乱图像(如图片)到三角形上,那么就需求足够多的极点数据以及色彩数据,这对开发者来说是很大的挑战,且很粗笨,那么这个时候纹路就派上用场了。
在OpenGL中,纹路更像是一张贴纸,当咱们画好形状之后,再将纹路贴纸贴到对应的形状上,这样就能够实现将图片映射到物体上的酷炫且杂乱的场景。
纹路运用场景很好了解,可是内部是怎样实现的呢?
图元:
你或许还记得在图像烘托管线中,在极点着色器后的阶段便是图元安装:
那么什么是图元安装呢?
概念很简略:便是将极点衔接成不同的形状或许独自作为一个原点运用,这些衔接后的形状或许原点便是一个个图元。
要留意了很多同学不清楚的会经常将片段和图元搞混淆,片段仅仅某个图元上的一个像素,这个像素是需求在片段着色器中进行上色的。
OpenGL中一共有7种图元,依据图元的不同,能够将OpenGL中构成图形的三种根本图形扩展成其他的图形。
图元名称 | 图元描述 |
---|---|
GL_POINTS | 每个极点在屏幕上都是独自的点,没有衔接 |
GL_LINES | 每一对极点(每两个极点)构成一条线段 |
GL_LINE_STRIP | 从第一个极点开端,依次(依极点次序)衔接后一个极点构成n-1条线段,n为极点数量 |
GL_LINE_LOOP | 与GL_LINE_STRIP相同的衔接次序,不同点是最终一个极点和第一个极点最终需求衔接起来构成闭环 |
GL_TRIANGLES | 按照极点次序,每三个极点构成一个三角形 |
GL_TRIANGLE_STRIP | 共用一个条带上的极点组成新的三角形,然后构成三角形带 |
GL_TARIANGLE_FAN | 以一个极点为中心,呈扇形摆放,共用相邻的两个极点的三角形扇 |
7种图元对应的图形如下:
比较常见运用的便是GL_POINTS,GL_LINES,GL_TRIANGLES这三种。
从上面的图形也能够看出,其他图元都能够运用三角形进行变换组成,差异便是三角形的极点数组不一样罢了,其实都是三角形的变种。所以在咱们开发中,运用GL_TRIANGLES就能够了。
知道图元咱们再来讲解下纹路映射。
什么是纹路映射?
纹路映射也叫做纹路贴图,经过给图元的极点坐标设置纹路坐标,经过纹路坐标在纹路图中选定特定的纹路区域,最终经过纹路坐标与极点的映射联系,将选定的纹路区域映射到指定图元上。
简略了解便是将纹路坐标系中指定的区域和极点坐标系指定的区域进行区域映射。
纹路坐标系
:
极点坐标系
:
4个纹路坐标:
T0(0,0),T1(1,0),T2(1,1),T3(0,1)
4个极点坐标:
V0(-1,-1),V1(0.5,-0.5),V2(0.5,0.5),V3(-0.5,0.5)
假定咱们需求烘托上面的图片,则需求先制作一个正方形,然后对正方形贴上纹路即可。
咱们知道正方形能够分割为两个三角形,那就只需求制作两个三角形图元就能够了。
这儿运用:V0V1V2和V2V3V0
制作正方形
有了上面的分析,咱们的极点数据就能够设计为:
//0.初始化极点数据
GLfloat vertices[] = {
//极点 //纹路坐标
-0.5f, -0.5f, 0.0f,0.0f, 0.0f,//V0
0.5f,-0.5f, 0.0f,1.0f,0.0f,//V1
0.5f, 0.5f, 0.0f, 1.0f, 1.0f,//V2
-0.5f, 0.5f, 0.0f,0.0f, 1.0f//V3
};
每一行的前三位为极点数据,后两位为纹路坐标数据。
这儿为了切开为两个三角形,需求引入EBO索引缓冲目标:
EBO首要用来告知OpenGL,咱们需求指定哪几个极点为一组三角形。
//确认三角形索引
GLint indices[] = {
0,1,2,
0,2,3
};
创立EBO的方式和VAO是一样的,仅仅type不同:
glGenBuffers(1,&EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(indices),indices,GL_STATIC_DRAW);
指定为EBO后,咱们还需求设置极点纹路坐标特点:
//极点坐标特点
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);
制作纹路贴图
最终,咱们需求把咱们的纹路附加到着色器中:
//生成纹路
glGenTextures(1, &textureID);
int width, height, nrComponents;
//解析sdcard中的纹路图片数据
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);
}
留意:
这儿我运用了github上的一个开源库来解析图片:其实便是一个头文件,现已放在了demo源码中了。
做了这些,当然在制作之前还需求去激活纹路Textture:
glUniform1i(glGetUniformLocation(programObj, "textureColor"), 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureID);
最终调用下面代码进行制作:
glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_INT,0);
看下效果图
:
额。。咋是倒过来的呢?
好吧,这儿用OpenGL官网的解说:
You probably noticed that the texture is flipped upside-down! This happens because OpenGL expects the 0.0 coordinate on the y-axis to be on the bottom side of the image, but images usually have 0.0 at the top of the y-axis.
大约意思便是OpenGL希望纹路坐标原点是以图片左下方为开端位置,可是图片读取的话一般都是以右上方为原点开端读取,所以咱们拿到的纹路数据,开端是转了180度的,想象下。。
那怎样解决这个问题:官网给出了建议
Luckily for us, stb_image.h can flip the y-axis during image loading by adding the following statement before loading any image:
咱们能够运用stb_image.h中的:
stbi_set_flip_vertically_on_load(true);
让咱们读取的数据翻转回来。
设置后效果
:
完整代码如下:
/**
* 制作前操作
* 0.初始化极点数据
* 1.创立着色器程序目标
* 2.生成VAO,VBO目标
* */
void MyGLRenderContext::beforeDraw() {
if(programObj!= 0){
return;
}
//0.初始化极点数据
GLfloat vertices[] = {
//极点 //纹路坐标
-0.5f, -0.5f, 0.0f,0.0f, 0.0f,//V0
0.5f,-0.5f, 0.0f,1.0f,0.0f,//V1
0.5f, 0.5f, 0.0f, 1.0f, 1.0f,//V2
-0.5f, 0.5f, 0.0f,0.0f, 1.0f//V3
};
//确认三角形索引
GLint indices[] = {
0,1,2,
0,2,3
};
//1.创立着色器程序,此处将着色器程序创立封装到一个东西类中
char vShaderStr[] =
"#version 300 es \n"
"layout(location = 0) in vec4 vPosition; \n"
"layout(location = 1) in vec2 texCords; \n"
"out vec2 color; \n"
"void main() \n"
"{ \n"
" gl_Position = vPosition; \n"
" color = texCords; \n"
"} \n";
char fShaderStr[] =
"#version 300 es \n"
"precision mediump float; \n"
"in vec2 color; \n"
"out vec4 fragColor; \n"
"uniform sampler2D textureColor; \n"
"void main() \n"
"{ \n"
" vec3 picColor = vec3 (texture(textureColor,color)); \n"
" fragColor = vec4 (picColor, 1.0 ); \n"
"} \n";
programObj = GLUtils::CreateProgram(vShaderStr,fShaderStr);
//2.生成VAO,VBO目标,并绑定极点特点
GLuint VBO,EBO;
glGenVertexArrays(1,&VAO);
glGenBuffers(1,&VBO);
glGenBuffers(1,&EBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER,VBO);
glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(indices),indices,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);
//生成纹路
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.清除buffer
* 2.运用程序着色器目标
* 3.开端制作
* 4.解绑
* */
void MyGLRenderContext::OnDrawFrame() {
beforeDraw();
if(programObj == 0){
return;
}
//清除buffer
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(0.3f,0.5f,0.4f,1.0f);
glUniform1i(glGetUniformLocation(programObj, "textureColor"), 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureID);
//运用程序着色器目标
glUseProgram(programObj);
//绑定VAO
glBindVertexArray(VAO);
//开端制作
//glDrawArrays(GL_TRIANGLES,0,6);
glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_INT,0);
//解绑VAO
glBindVertexArray(GL_NONE);
//解绑程序着色器目标
glUseProgram(GL_NONE);
}
总结
本文首要经过一个简略的demo来演示了怎样给一个图像装上贴图纹路。 触及的内容有:图元,纹路,纹路图片加载,EBO,纹路坐标系,极点坐标系等内容,内容不多,可是了解起来还是有点难,多着手吧,别仅限于看懂。
好了,本篇文章讲到这儿了,我是小余,重视我,免费获取最新面试资料,咱们下期见。