六、OpenGL ES纹理的使用

一、关于纹理

前面我们已经使用简单的图形和颜色就完成了很多工作了,但是还是很丑陋,我们需要画的更加紧致一些,这就需要用到纹理了,所谓纹理就是一个图片,他可以被加进OpenGL中。一旦使用了纹理,我们就需要使用多个着色器的程序了。当把纹理按照特定的方式映射到物体表面上的时候,能使物体看上去更加真实。当前流行的图形系统中,纹理绘制已经成为一种必不可少的渲染方法。在理解纹理映射时,可以将纹理看做应用在物体表面的像素颜色。在真实世界中,纹理表示一个对象的颜色、图案以及触觉特征。纹理只表示对象表面的彩色图案,它不能改变对象的几何形式。
每个二维的纹理都是由许多小的纹理元素组成,它们是小块的数据,类似片段和像素,最常用的方式是直接从一个图像文件中加载数据。

每个二维的纹理都有其自己的坐标空间,如果想把一幅纹理映射到相应的几何图元,就必须告诉GPU如何进行纹理映射,也就是为图元的顶点指定恰当的纹理坐标。其范围是从一个拐角的(0,0)到另一个拐角的(1,1),按照惯例,一个维度叫做S(横轴),而另一个称为T(y轴),当我们想要把一个纹理应用于一个三角形或者一组三角形的时候,我们需要为每个顶点指定一组ST纹理坐标以便OpenGL知道需要用哪个纹理的哪个部分画到每个三角形上,这些纹理坐标有时也会被称为UV纹理坐标。使用纹理坐标获取纹理颜色叫做采样(Sampling)。纹理坐标起始于(0, 0),也就是纹理图片的左下角,终始于(1, 1),即纹理图片的右上角。


对于一个OpenGL纹理来说,它没有内在的方向性,因此我们可以使用不同的坐标把它定向到任何我们喜欢的方向上。
然而,大多数计算机图像都有一个默认的方向,他们通常被规定为y轴向下,这点和纹理的T坐标刚好相反,因此我们需要额外处理下。


在标准的OpenGL es2.0中,纹理不必是正方形,但是每个维度都应该是2的幂次方(POT),这样规定的原因在于非POT纹理可以被使用的场合非常有限,而POT纹理适合各种情况。
纹理的尺寸也有一个最大值,他根据不同的实现而变化,但是通常都比较大,比如2048*2048

二、纹理过滤

当纹理大小要被扩大或者缩小的时候,我们需要使用纹理过滤明确说明会发生什么,当我们在渲染表面上绘制一个纹理时,那个纹理的纹理元素可能无法精确地映射到OpenGL生成的片段上,有2种情况:缩小或者放大。
当我们尽力把几个纹理元素挤进一个片段时,缩小就会发生了,当把一个纹理元素扩展到许多片段时,放大就发生了。
针对每一种情况,我们都可以配置OpenGL使用一个纹理过滤器,我会使用下面的图像阐述每一种过滤模式:

2.1 GL_NEAREST(也叫邻近过滤,Nearest Neighbor Filtering)是OpenGL默认的纹理过滤方式。当设置为GL_NEAREST的时候,OpenGL会选择中心点最接近纹理坐标的那个像素。下图中你可以看到四个像素,加号代表纹理坐标。左上角那个纹理像素的中心距离纹理坐标最近,所以它会被选择为样本颜色:

这种方式为每个片段选择最近的纹理元素,当放大纹理时它的锯齿效果看起来相当明显,每个纹理单元都清楚地显示为一个小方块。

当我们缩小纹理时,因为没有足够的片段来绘制所有的纹理单元,许多细节将会丢失。

2.2 GL_LINEAR(也叫线性过滤,(Bi)linear Filtering)它会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。一个纹理像素的中心距离纹理坐标越近,那么这个纹理像素的颜色对最终的样本颜色的贡献越大。下图中你可以看到返回的颜色是邻近像素的混合色:

线性过滤使用双线插值平滑像素之间的过渡,而不是每个片段使用最近的纹理元素,OpenGL会使用四个邻接的纹理元素,并在他们之间用一个线性差值算法做差值,这个算法与前面介绍平滑着色的算法一样,之所以叫它双线性,是因为它是沿两个维度差值的,它会比近邻过滤要平滑很多,但还是会有一些锯齿显示出来,因为我们把这个纹理扩展得太多了,但是锯齿没有最近邻过滤那么明显。

2.3 MIP贴图

尽管线性过滤很适合处理放大,但是缩小到超过一定的大小时,他就不好用了,一个纹理在渲染表面所占大小减少得越多,就会越多的纹理元素拥挤到一个片段上,因为OpenGL的线性过滤只会给每个片段使用四个纹理元素,我们将会失去很多细节。因为每一帧都要选择不同的纹理元素, 这还会引起噪音以及移动中的物体闪烁。为了克服这些缺陷,可以使用MIP贴图技术,他可以用来生成一组优化过的不同大小的纹理,当生成这组纹理的时候,OpenGL会使用所有的纹理元素生成每个级别的纹理,当过滤纹理的时候,还要确保所有的纹理元素都能被使用。在渲染时,OpenGL会根据每个片段的纹理元素数据为每个片段选择最合适的级别。使用MIP贴图会占用更多的内存,但是渲染也会更快,这是因为较小级别的纹理在GPU的纹理缓存中占用较小的空间。随着MIP贴图的使用OpenGL将选择最合适的纹理级别,然后用优化过的纹理做线性差值,每个级别的纹理都是用自所有纹理元素的信息构建的。

2.3 三线性过滤

如果OpenGL在不同的MIP贴图级别之间来回切换,当使用线性过滤来使用MIP贴图时,在其渲染的场景中,在不同级别的MIP贴图切换时,会看到明显的跳跃或者线条。我们可以切换到三线性过滤,告诉OpenGL在两个最邻近的MIP贴图级别之间也要差值,这样每个片段总共要使用8个纹理元素差值。这有消除每个MIP贴图级别之间的过滤,并且得到一个更平滑的图像。

更多的纹理过滤模式和使用场景如下表所示:

三、把纹理加载进OpenGL中

我们需要使用Android的api读入图像数据,OpenGL不能直接读取PNG或者JPEG文件的数据,因为这些文件被编码为特定的压缩格式,OpenGL需要使用非压缩形式的原始数据,新建一个帮助类TextureHelper,代码如下:

object TextureHelper {
    private const val TAG = "TextureHelper"

    /**
     * 加载纹理
     * @param resId 图片id
     */
    fun loadTexture(context: Context, resId: Int): Int {
        val textureObjectIds = IntArray(1)
        // 创建纹理对象,并把id存储到textureObjectIds数组中
        glGenTextures(1, textureObjectIds, 0)
        if (textureObjectIds[0] == 0) {
            if (LoggerConfig.ON) {
                Log.w(TAG, "Could not generate a new OpenGL texture object.")
            }
            return 0
        }
        // 加载位图数据并与纹理绑定
        // OpenGL不能直接读取PNG或者JPEG文件的数据,因为这些文件被编码为特定的压缩格式,OpenGL需要使用非压缩形式的原始数据
        val options = BitmapFactory.Options()
        options.inScaled = false //表示需要的是原始数据
        val bitmap = BitmapFactory.decodeResource(context.resources, resId, options)
        if (bitmap == null) {
            if (LoggerConfig.ON) {
                Log.w(TAG, "Resource ID :$resId could not be decoded.")
            }
            // 解析bitmap失败后,删除掉前面创建的纹理对象
            glDeleteTextures(1, textureObjectIds, 0)
            return 0
        }
        // 绑定纹理到OpenGL中,参数1表示使用的是二维纹理,参数2表示要绑定到那个纹理对象中
        glBindTexture(GL_TEXTURE_2D, textureObjectIds[0])

        /**
         * 设置纹理过滤参数
         * GL_TEXTURE_MIN_FILTER是指缩小的情况,对于缩小的情况采用GL_LINEAR_MIPMAP_LINEAR来告诉OpenGL使用三线性过滤
         * GL_TEXTURE_MAG_FILTER是指放大的情况,对于发大的情况使用GL_LINES来告诉OpenGL使用双线性过滤
         */
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
        // 加载纹理, 加载bitmap数据并把它复制到当前绑定的纹理对象.
        texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);

        // 生成mip贴图
        glGenerateMipmap(GL_TEXTURE_2D);
        // 释放bitmap对象
        bitmap.recycle()

        // 纹理解除绑定,参数2传0即可
        glBindTexture(GL_TEXTURE_2D, 0);
        // 返回纹理对象id
        return textureObjectIds[0];
    }
}

然后在drawable-nodpi目录下放入如下图片,nodpi是与屏幕密度无关的目录,放在这里的图片不会被缩放。

上面的代码将BitmapFactory.Options()的inScaled设置为false,表示要使用的是原始图像数据,而不是这个图像的缩放版本。为了告诉OpenGL后面的纹理调用应该用于这个纹理对象,我们还需要调用glBindTexture,参数1表示二维纹理,参数2表示要绑定到刀哥纹理对象的id。

四、创建新的顶点着色器

在把纹理绘制到屏幕之前,我们不得不创建一套新的着色器,他可以接收纹理,并把它应用在要绘制的片段上。这些新的着色器与我们目前为止使用过的着色器相似,只是为了支持纹理而做了一些轻微的改动。

在res/raw/目录创建一个文件texture_vertex_shader.glsl,加入如下内容:

uniform mat4 u_Matrix;

attribute vec4 a_Position;  
attribute vec2 a_TextureCoordinates;

varying vec2 v_TextureCoordinates;

void main()                    
{                            
    v_TextureCoordinates = a_TextureCoordinates;	  	  
    gl_Position = u_Matrix * a_Position;    
}          

这个着色器的大多数代码看上去应该都比较熟悉了,我们已经为矩阵定义了一个uniform并且也为位置定义了一个属性,我们使用这些去设置最后的gl_Position。而对于这些新的东西,我同样加了一个新的属性a_TextureCoordinates,因为它有2个分量:S坐标和T坐标,所以被定义为vec2,
我把这些坐标传递给顶点着色器差值的varying,称为v_TextureCoordinates。

五、创建新的片段着色器

在res/raw/目录创建一个文件texture_fragment_shader.glsl,加入如下内容:

precision mediump float; 
      	 				
uniform sampler2D u_TextureUnit;      	 								
varying vec2 v_TextureCoordinates;      	   								
  
void main()                    		
{                              	
    gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);                           		
}

为了把纹理绘制到一个物体上,OpenGL会为每个片段都调用片段着色器,并且每个调用都接收v_TextureCoordinates的纹理坐标,
片段着色器也通过uniform(u_TextureUnit)来接收实际的纹理数据,u_TextureUnit被定义为一个sampler2D,这个变量类型指的是一个二维纹理数据的数组。被插值的纹理坐标和纹理数据被传递给着色器函数texture2D,它会读入纹理中那个特定坐标处的颜色值,接着通过把结果复制给gl_FragColor。

六、为顶点数据创建新的类结构

首先,我们将把顶点数据分离到不同的类中,每个类代表一个物理对象的类型,我们将为矩形创建一个table类,两个点创建一个Mallet类。并且每个类都会有一个VertexArray类的实例,它用来封装存储顶点矩阵的FloatBuffer。

先来看看VertexArray类的定义:

/**
 * @Author: mChenys
 * @Date: 2020/12/28
 * @Description: 顶点数据封装类
 */
class VertexArray(vertexData: FloatArray) {

    // 在本地代码中存储顶点数据的buffer
    private var floatBuffer: FloatBuffer =
        ByteBuffer.allocateDirect(vertexData.size * Constants.BYTES_PER_FLOAT)
            .order(ByteOrder.nativeOrder())
            .asFloatBuffer()
            .put(vertexData)

    // 把着色器的属性与数据进行关联
    fun setVertexAttributePointer(
        dataOffset: Int,
        attributeLocation: Int,
        componentCount: Int,
        stride: Int
    ) {
        floatBuffer.position(dataOffset)
        glVertexAttribPointer(
            attributeLocation,
            componentCount,
            GL_FLOAT,
            false,
            stride,
            floatBuffer
        )
        glEnableVertexAttribArray(attributeLocation)
        floatBuffer.position(0)
    }
}

table类定义如下:

class Table {
    private val POSITION_COMPONENT_COUNT = 2
    private val TEXTURE_COORDINATES_COMPONENT_COUNT = 2
    private val STRIDE: Int = (POSITION_COMPONENT_COUNT + TEXTURE_COORDINATES_COMPONENT_COUNT) * BYTES_PER_FLOAT


    private val VERTEX_DATA = floatArrayOf(
        // Order of coordinates: X, Y, S, T
        // T分量刚好和y分量的方向相反,因为图像的纹理坐标的朝向是右边向上的.
        0f, 0f, 0.5f, 0.5f,
        -0.5f, -0.8f, 0f, 0.9f,
        0.5f, -0.8f, 1f, 0.9f,
        0.5f, 0.8f, 1f, 0.1f,
        -0.5f, 0.8f, 0f, 0.1f,
        -0.5f, -0.8f, 0f, 0.9f
        // 使用0.1f和0.9f作为T坐标,为什么? 这个桌子是1个单位宽,1.6的单位高,而纹理图像是512*1024
        // 因此它的宽度对应1个单位时,它的高度实际就是2个单位了,为了避免把纹理压扁,我们使用范围0.1到0.9裁剪它的边缘,
        // 而不是使用0.0到1.0,并且只画它中间的部分,即使不使用裁剪,我们还可以使用0.0到1.0的纹理坐标,把纹理预拉伸,
        // 这样被压扁到桌子之后,它看上去就是正确的了.采用这种方法,哪些无法显示的纹理部分就不会占用任何内存了.
    )

    // 数据封装类
    private var vertexArray: VertexArray

    init {
        vertexArray = VertexArray(VERTEX_DATA)
    }

    // 绑定顶点数组到纹理着色器上
    fun bindData(textureProgram: TextureShaderProgram) {
        //着色器属性和数据进行关联
        vertexArray.setVertexAttributePointer(
            0,
            textureProgram.getPositionAttributeLocation(), // 把位置数据绑定到引用的着色器属性上
            POSITION_COMPONENT_COUNT,
            STRIDE
        )
        vertexArray.setVertexAttributePointer(
            POSITION_COMPONENT_COUNT,
            textureProgram.getTextureCoordinatesAttributeLocation(),// 把纹理坐标数据绑定到被引用的着色器属性上
            TEXTURE_COORDINATES_COMPONENT_COUNT,
            STRIDE
        )
    }

    fun draw() {
        glDrawArrays(GL_TRIANGLE_FAN, 0, 6)
    }
}

Mallet类定义如下:

class Mallet {
    private val POSITION_COMPONENT_COUNT = 2
    private val COLOR_COMPONENT_COUNT = 3
    private val STRIDE: Int = ((POSITION_COMPONENT_COUNT + COLOR_COMPONENT_COUNT) * BYTES_PER_FLOAT)

    private val VERTEX_DATA = floatArrayOf(
        // Order of coordinates: X, Y, R, G, B
        0f, -0.4f, 0f, 0f, 1f,
        0f, 0.4f, 1f, 0f, 0f
    )

    // 数据
    private var vertexArray: VertexArray

    init {
        vertexArray = VertexArray(VERTEX_DATA)
    }

    // 绑定
    fun bindData(colorProgram: ColorShaderProgram) {
        vertexArray.setVertexAttributePointer(
            0,
            colorProgram.getPositionAttributeLocation(),
            POSITION_COMPONENT_COUNT,
            STRIDE
        )
        vertexArray.setVertexAttributePointer(
            POSITION_COMPONENT_COUNT,
            colorProgram.getColorAttributeLocation(),
            COLOR_COMPONENT_COUNT,
            STRIDE
        )
    }

    fun draw() {
        glDrawArrays(GL_POINTS, 0, 2)
    }
}

因为上面几个类都需要用到BYTES_PER_FLOAT常量,因此还需要创建一个常量类Constants

object Constants {
    val BYTES_PER_FLOAT: Int = 4
}

七、为着色器程序添加类

我们还需要为纹理着色器创建一个类,并为颜色着色器程序创建另一个类;我们会用纹理着色器绘制table,用颜色着色器绘制Mallet,我还需要创建一个基类管理一些公共字段和方法,这个基类就命名为ShaderProgram,代码如下:

open class ShaderProgram(private val context: Context, private val vertexShaderResourceId: Int
                    , private val fragmentShaderResourceId: Int) {
    // Uniform constants
    protected val U_MATRIX = "u_Matrix"
    protected val U_TEXTURE_UNIT = "u_TextureUnit"

    // Attribute constants
    protected val A_POSITION = "a_Position"
    protected val A_COLOR = "a_Color"
    protected val A_TEXTURE_COORDINATES = "a_TextureCoordinates"

    // Shader program
    protected var program = 0

    init {
        // Compile the shaders and link the program.
        program = ShaderHelper.buildProgram(
            readTextFileFromResource(
                context, vertexShaderResourceId
            ),
            readTextFileFromResource(
                context, fragmentShaderResourceId
            )
        )
    }

    fun useProgram() {
        // Set the current OpenGL shader program to this program.
        glUseProgram(program)
    }
}

绘制table的纹理着色器程序代码如下:

class TextureShaderProgram(context: Context) : ShaderProgram(
    context, R.raw.texture_vertex_shader,
    R.raw.texture_fragment_shader
) {
    // Uniform locations
    private var uMatrixLocation = 0
    private var uTextureUnitLocation = 0

    // Attribute locations
    private var aPositionLocation = 0
    private var aTextureCoordinatesLocation = 0

    init {
        // Retrieve uniform locations for the shader program.
        uMatrixLocation = glGetUniformLocation(program, U_MATRIX);
        uTextureUnitLocation = glGetUniformLocation(program, U_TEXTURE_UNIT);

        // Retrieve attribute locations for the shader program.
        aPositionLocation = glGetAttribLocation(program, A_POSITION);
        aTextureCoordinatesLocation =
            glGetAttribLocation(program, A_TEXTURE_COORDINATES);
    }

    // 设置uniform并返回属性位置
    fun setUniforms(matrix: FloatArray?, textureId: Int) {
        // 传递matrix矩阵给uniform
        glUniformMatrix4fv(uMatrixLocation, 1, false, matrix, 0)

        /**
         * 当使用OpenGL进行纹理绘制时,我们不需要直接给着色器传递纹理
         * 而是使用纹理单元来保存那个纹理,这是因为GPU只能同时绘制数量有限的纹理.使用纹理单元表示当前正在被绘制的活动的纹理.
         * 通过glActiveTexture把活动的纹理单元设为单元0作为开始
         */
        glActiveTexture(GL_TEXTURE0)

        // 把纹理绑定到纹理单元中
        glBindTexture(GL_TEXTURE_2D, textureId)

        // 把选中的纹理单元传递给片段着色器u_TextureUnit
        glUniform1i(uTextureUnitLocation, 0)
    }

    // 获取顶点位置属性
    fun getPositionAttributeLocation(): Int {
        return aPositionLocation
    }

    // 获取纹理位置属性
    fun getTextureCoordinatesAttributeLocation(): Int {
        return aTextureCoordinatesLocation
    }

}

绘制木槌的颜色着色器程序代码如下:

class ColorShaderProgram(context: Context) : ShaderProgram(
    context,
    R.raw.simple_vertex_shader3,
    R.raw.simple_fragment_shader2
) {
    // Uniform locations
    private var uMatrixLocation = 0

    // Attribute locations
    private var aPositionLocation = 0
    private var aColorLocation = 0

    init {
        // Retrieve uniform locations for the shader program.
        uMatrixLocation = glGetUniformLocation(program, U_MATRIX);
        // Retrieve attribute locations for the shader program.
        aPositionLocation = glGetAttribLocation(program, A_POSITION);
        aColorLocation = glGetAttribLocation(program, A_COLOR);
    }

    fun setUniforms(matrix: FloatArray?) {
        // Pass the matrix into the shader program.
        glUniformMatrix4fv(uMatrixLocation, 1, false, matrix, 0)
    }

    fun getPositionAttributeLocation(): Int {
        return aPositionLocation
    }

    fun getColorAttributeLocation(): Int {
        return aColorLocation
    }

}

八、绘制纹理

既然我们已经把顶点数据和着色器程序分别放于不同的类中了,现在就可以更新渲染类,使用纹理进行绘制。代码修改如下:


class AirHockeyRenderer(private val context: Context) : GLSurfaceView.Renderer {

    // 定义投影矩阵
    private val projectionMatrix = FloatArray(16)

    // 定义模型矩阵用来移动物体的
    private var modelMatrix = FloatArray(16)

    // 绘制的对象
    private lateinit var table: Table
    private lateinit var mallet: Mallet
    // 纹理着色器程序
    private lateinit var textureProgram: TextureShaderProgram
    // 颜色着色器程序
    private lateinit var colorProgram: ColorShaderProgram
    // 当前加载的纹理
    private var texture = 0

   
    override fun onSurfaceCreated(glUnused: GL10, config: EGLConfig) {
        glClearColor(0.0f, 0.0f, 0.0f, 0.0f)

        table = Table()
        mallet = Mallet()

        textureProgram = TextureShaderProgram(context)
        colorProgram = ColorShaderProgram(context)

        texture = TextureHelper.loadTexture(context, R.drawable.air_hockey_surface)
    }


    override fun onSurfaceChanged(glUnused: GL10, width: Int, height: Int) {
        // Set the OpenGL viewport to fill the entire surface.
        glViewport(0, 0, width, height)
        // 调用投影矩阵帮助类创建透视投影矩阵,设置投影的z值取值范围为[-1,-10]
        MatrixHelper.perspectiveM(
            projectionMatrix,
            45f, // 这里使用45°视野创建一个透视投影
            width.toFloat() / height.toFloat(),
            1f, // 视点离近平面的距离,相当于z的起始坐标是-1
            10f // 视点离远平面的距离,相当于z的终止坐标是-10
        )
        // 此时运行会看不到任何东西,因为没有指定z值的位置,默认情况它处于z为0的位置,因为这个视椎体是从z值为-1的位置开始
        // 除非把它移动到那个距离内,否则无法看到,这里我们不通过硬编码的形式修改z值,而是使用投影矩阵之前先使用平移矩阵移出来
        // 在类的顶部创建模型矩阵
        setIdentityM(modelMatrix, 0)// 先将modelMatrix设置为单位矩阵
        translateM(modelMatrix, 0, 0f, 0f, -2.5f)// 然后基于单位矩阵修改z的值,沿z轴平移-2
        // 当把图形的坐标与这个modelMatrix矩阵相乘的时候,那些坐标最终就会沿z轴方向移动2个单位


        //加入旋转
        rotateM(modelMatrix, 0, -60f, 1f, 0f, 0f);

        val temp = FloatArray(16)
        // 将投影矩阵和模型矩阵相乘的结果赋给临时数组temp中
        multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0)
        //然后再拷贝到projectionMatrix中,此时的projectionMatrix就包含了投影矩阵和模型矩阵的组合效果了
        System.arraycopy(temp, 0, projectionMatrix, 0, temp.size)

    }


    override fun onDrawFrame(glUnused: GL10) {
        // Clear the rendering surface.
        glClear(GL_COLOR_BUFFER_BIT)

        // Draw the table.
        textureProgram.useProgram();
        textureProgram.setUniforms(projectionMatrix, texture);
        table.bindData(textureProgram);
        table.draw();

        // Draw the mallets.
        colorProgram.useProgram();
        colorProgram.setUniforms(projectionMatrix);
        mallet.bindData(colorProgram);
        mallet.draw();
    }
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
OpenGL ES中,纹理数组是一组纹理的集合,可以一次性加载多个纹理并进行处理。使用纹理数组可以提高渲染效率,减少内存占用。 纹理数组的创建和使用主要有以下几个步骤: 1. 创建纹理数组对象:使用glGenTextures函数创建纹理数组对象,并使用glBindTexture函数绑定到OpenGL ES上下文中。 2. 加载纹理数据:使用glTexImage2D函数将纹理数据加载到纹理数组中。 3. 设置纹理参数:使用glTexParameter函数设置纹理参数,如过滤模式、纹理环绕模式等。 4. 绑定纹理数组:使用glBindTexture函数将纹理数组绑定到OpenGL ES上下文中。 5. 使用纹理数组:在渲染时,使用glActiveTexture函数激活纹理单元,并使用glBindTexture函数将纹理数组绑定到激活的纹理单元上,然后在着色器中使用sampler2DArray类型的变量进行采样。 需要注意的是,在使用纹理数组时,每个纹理的大小和格式必须相同。 下面是一个使用纹理数组的示例代码: ``` GLuint texArray; glGenTextures(1, &texArray); glBindTexture(GL_TEXTURE_2D_ARRAY, texArray); glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, width, height, numTextures, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D_ARRAY, texArray); glUniform1i(textureLoc, 0); ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值