Android OpenGL ES 2.0 手把手教学(6)- 纹理

你们好,下面和大学一块儿学习纹理,在个人github上有一个项目OpenGLES2.0SamplesForAndroid,我会不断地编写学习样例,文章和代码同步更新,欢迎关注,连接:github.com/kenneycode/…git

在前面的例子中,咱们渲染的都是一些比较简单的颜色,若是咱们要渲染一张图片,该怎么作呢?这就须要用到纹理,咱们须要建立一个纹理并把图片加载到纹理中,而后在fragment shader中对纹理进行采样,从而将纹理渲染出来。github

咱们先经过glGenTextures建立一个纹理,而后设置一些参数,这里获得的纹理只是一个idbash

// 建立图片纹理
// Create texture
val textures = IntArray(1)
GLES20.glGenTextures(textures.size, textures, 0)
val imageTexture = textures[0]

GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, imageTexture)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE)
复制代码

建立好纹理以后,它仍是空的,咱们要给这个纹理填充内容,咱们先将一张图片解码成bitmap,并将像素数据copy到一个ByteBuffer中,由于将bitmp加载到纹理中的方法接受的是一个ByteBufferpost

val bitmap = Util.decodeBitmapFromAssets("image_0.jpg")
val b = ByteBuffer.allocate(bitmap.width * bitmap.height * 4)
bitmap.copyPixelsToBuffer(b)
b.position(0)
复制代码

下面经过glTexImage2D方法将上面获得的ByteBuffer加载到纹理中:学习

GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 
                    0, 
                    GLES20.GL_RGBA, 
                    bitmap.width, 
                    bitmap.height, 
                    0, 
                    GLES20.GL_RGBA, 
                    GLES20.GL_UNSIGNED_BYTE, 
                    b)
复制代码

这时纹理中才真正有了内容,接下来须要将纹理传递给fragment shader进行采样,从而渲染出来,咱们先来看看如何在fragment shader中使用纹理:ui

// vertex shader
precision mediump float;
attribute vec4 a_position;
attribute vec2 a_textureCoordinate;
varying vec2 v_textureCoordinate;
void main() {
    v_textureCoordinate = a_textureCoordinate;
    gl_Position = a_position;
}

// fragment shader
precision mediump float;
varying vec2 v_textureCoordinate;
uniform sampler2D u_texture;
void main() {
    gl_FragColor = texture2D(u_texture, v_textureCoordinate);
}
复制代码

关键点是uniform sampler2D u_texture;这句,它声明了一个2D的采样器用于采样纹理。spa

varying vec2 v_textureCoordinate;则是从vertex shader中传递过来的一个通过插值的纹理坐标值,关于varying变量,在以前的一个篇文章《Android OpenGL ES 2.0 手把手教学(4)- 片断着色器 fragment shader》中有涉及到。3d

gl_FragColor = texture2D(u_texture, v_textureCoordinate);就是从纹理中采样出v_textureCoordinate坐标所对应的颜色做为fragment shader的输出,咱们能够看到,fragment shader的输出实际上就是一个颜色,在以前的文章中,是咱们本身去控制这个颜色,而当这个颜色若是是来自纹理的采样,那最终渲染出来就是纹理的样子。code

如今,纹理和shader都准备好了,若是把它们联系越来呢?首先须要像以前同样先获取shader中纹理变量的locationorm

val uTextureLocation = GLES20.glGetAttribLocation(programId, "u_texture")
复制代码

而后给这个location指定对应哪一个纹理单元,这里咱们使用0号纹理单元:

GLES20.glUniform1i(uTextureLocation, 0)
复制代码

看到这里,可能有些朋友有点懵,纹理单元又是个什么东西?是这样的,纹理单元能够想像成是一种相似寄存器的东西,在OpenGL使用纹理前,咱们先要把纹理放到某个纹理单元上,以后的操做OpenGL就会去咱们指定的纹理单元上去取对应的纹理。

咱们刚才让location对应0号纹理单元,可是咱们好像没并无哪里说咱们把纹理放在了0号纹理单元,这是怎么回事呢?由于默认状况下,OpenGL是使用0号纹理单元的,咱们由于没有更改过使用的纹理单元,所以默认就是0号了,咱们若是想使用其它纹理单元,能够经过glActiveTexture来指定,例如:

GLES20.glActiveTexture(GLES20.GL_TEXTURE1)
复制代码

就会指定使用1号纹理单元,若是咱们的例子改成使用1号纹理单元,那么uTextureLocation就要相应地改成让它对应1号纹理单元:

GLES20.glUniform1i(uTextureLocation, 1)
复制代码

下面我看来看看顶点坐标和纹理坐标:

private val vertexData = floatArrayOf(-1f, -1f, -1f, 1f, 1f, 1f, -1f, -1f, 1f, 1f, 1f, -1f)
private val textureCoordinateData = floatArrayOf(0f, 1f, 0f, 0f, 1f, 0f, 0f, 1f, 1f, 0f, 1f, 1f)
复制代码

如何给shader传递顶点坐标,方法你们如今应该比较熟悉了,其实纹理坐标的传递也几乎是同样的,只是值不同而已。

以前提到过,顶点坐标的做用是告诉OpenGL要渲染到什么区域,顶点坐标的坐标系是每一个轴的范围都是-1~1,其实也能够超出-11,只不过超出就不在渲染的范围内了,就看不见了,并非就算错误,顶点坐标系的原点在中间。

纹理坐标的坐标原点在左下角,每一个轴的范围是0~1,一样的也能够超出01,超出以后的表现会根据设置的纹理参数有所不一样。

在这个例子中,咱们使用GL_TRIANGLES的绘制模式进行渲染,关于渲染模式,能够参考个人上一篇文章《Android OpenGL ES 2.0 手把手教学(5)- 绘制模式》,在这种绘制模式下,每三个点构成一个独立三形,所以纹理坐标构成的两个三角形会对应渲染到顶点坐标指定的两个三角形中,咱们来看一下效果:

细心的朋友可能会发现,咱们的顶点坐标和纹理坐标上下是颠倒的,好比顶点坐标(-1,-1)对应纹理坐标(0,1),也就是渲染区域的左下角对应纹理的左上角,这样同样,渲染出来的图像不是应该倒过来吗?但咱们看到的效果倒是正确的。

这是由于咱们的纹理来自于bitmap,而bitmap的坐标原点是左上角,也就是它和OpenGL中的纹理坐标系是上下颠倒的,因此咱们把纹理坐标再颠倒一次,就是正的了。

咱们刚才建立纹理时给纹理设置了几个参数,咱们来看一下:

GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE)
复制代码

其中GL_TEXTURE_MIN_FILTERGL_TEXTURE_MAG_FILTER是纹理过滤参数,指定当咱们渲染出来的纹理比原来的纹理小或者大时要如何处理,这里咱们使用了GL_LINEAR的方式,这是一种线性插值的方式,获得的结果会更平滑,除此以外,还有其它不少选项,还有另外一个比较经常使用的是GL_NEAREST,它会选择和它最近的像素,获得的结果锯齿感比GL_LINEAR要大,咱们将顶点坐标扩大5倍,即变成-5~5,来获得放大的效果,能够来看看放大时的这两种效果:

GL_TEXTURE_WRAP_SGL_TEXTURE_WRAP_T则是指定纹理坐标超出了纹理范围以后,该如何填充,比较经常使用的有GL_CLAMP_TO_EDGEGL_REPEAT,它们的效果分别是填充边缘像素和重复这个纹理,咱们将纹理坐标改成0~3,看看效果:

以上就是关于纹理的一些基础知识,代码在我github的OpenGLES2.0SamplesForAndroid项目中,本文对应的是SampleTexture,项目连接:github.com/kenneycode/…

感谢阅读!

相关文章
相关标签/搜索