首页 > 其他分享 >用纹理增加细节

用纹理增加细节

时间:2024-02-04 23:56:35浏览次数:20  
标签:val component 纹理 细节 0f 增加 GL 着色器

一.理解纹理

  OpenGL中的纹理可以用来表示照片,图像。每个二维的纹理都由许多小的纹理元素组成,他们是小块的数据,类似于我们前面讨论的片段和像素。要使用纹理,最直接的方式是从图像文件加载数据。我们现在要加载下面这副图像作为空气曲棍球桌子的表面纹理:

  

  我们将其存储在drawable文件夹中即可。每个纹理都有坐标空间,其范围是从一个拐角(0,0)到另一个拐角(1,1),我们想要把一个纹理应用到一个或多个三角形时,我们要为每个顶点指定一个纹理坐标,以便让OpenGL知道用纹理的哪个部分画到每个三角形上。按照惯例,一个二维的纹理一个维度称作S,另一个维度称作T。

二.把纹理加载进OpenGL中

  我们的第一个任务是将一副图像文件的数据加载到一个OpenGL的纹理中,我们将创建一个新的类TextureHelper,并在其中完成加载纹理的工作。在进行这个工作之前,我们先来了解一下纹理过滤,当纹理大小被放大或缩小时,我们要使用纹理过滤明确说明会发生什么。当我们在渲染表面绘制一个纹理时,那个纹理的纹理元素可能无法精确的映射到OpenGL生成的片段上,此时会出现两种情况,放大和缩小。当我们将几个纹理元素挤到一个片段时,缩小就发生了;当我们把一个纹理元素扩大到几个片段上时,放大就发生了。针对每种情况,我们都需要配置纹理过滤器。我们会通过glTexParameteri()函数设置纹理过滤模式,下面是OpenGL支持的纹理过滤模式:

   并且放大和缩小两种情况下所允许的纹理过滤模式有所不同,如下所示:

   下面,是加载纹理的代码:

class TextureHelper {
    companion object {
        val TAG="TextureHelper"
        fun loadTexture(context: Context, id:Int):Int{//加载由id指定的图像,并生成纹理对象返回
            //生成纹理对象
            val textureObjectIds= IntArray(1)
            glGenTextures(1,textureObjectIds,0)
            if(textureObjectIds[0]==0){//返回0表示创建纹理对象失败
                Log.i(TAG,"could not generate texture object")
                return 0
            }
            val option= BitmapFactory.Options()
            option.inScaled=false//保留原始图像,取消缩放
            //OpenGL不能直接使用压缩的jpg,png图像,要解码为它能理解的位图数据
            val bitmap=BitmapFactory.decodeResource(context.resources,id,option)
            if(bitmap==null){
                Log.i(TAG,"decode failed.")
                glDeleteTextures(1,textureObjectIds,0)
                return 0
            }
            //告诉OpenGL后面的纹理调用应该应用于这个纹理对象
            glBindTexture(GL_TEXTURE_2D,textureObjectIds[0])
            //设置纹理过滤
            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR)//处理图片缩小的情况
            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR)//处理图片放大的情况
            //加载位图数据到opengl,并复制到当前绑定的纹理对象
            GLUtils.texImage2D(GL_TEXTURE_2D,0,bitmap,0)
            //使用完后,回收位图数据
            bitmap.recycle()
            glGenerateMipmap(GL_TEXTURE_2D)//生成各种级别的贴图
            glBindTexture(GL_TEXTURE_2D,0)//解除绑定当前的纹理对象
            return textureObjectIds[0]//返回纹理对象id
        }
    }

}

三.创建新的着色器集合

  在把纹理绘制到屏幕之前,我们需要创建一套新的着色器,他们可以接收纹理,并且把它们应用到要绘制的片段上。这些新的着色器和我们之前使用的着色器非常类似,只是为了支持纹理做了轻微的改动。

  1.创建新的顶点着色器:texture_vertex_shader.glsl

#version 300 es
layout(location=0) uniform mat4 u_Matrix;
layout(location=0) in vec4 a_Position;
layout(location=1) in vec2 a_TextureCoordinates;
out vec2 v_TextureCoordinates;
void main() {
    v_TextureCoordinates=a_TextureCoordinates;
    gl_Position=u_Matrix*a_Position;
}

  我们用uniform定义了一个向量a_TextureCoordinates,用于接收纹理坐标,由于纹理是二维的,所以这里我们也定义成了二维的,然后将其传递给片段着色器。

  2.创建新的片段着色器:texture_fragment_shader.glsl

#version 300 es
precision mediump float;
layout(location=1) uniform sampler2D u_TextureUnit;
in vec2 v_TextureCoordinates;
out vec4 fragColor;
void main() {
    fragColor=texture(u_TextureUnit,v_TextureCoordinates);
}

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

四.为顶点数据创建新的类结构

  首先,我们要把顶点数组分离到不同的类中,每个类代表一个物理对象的类型。我们为桌子创建一个新类,并为木槌创建另一个类。为了避免重复,我们会创建一个单独的类用于封装实际的顶点数组,新的类结构如下图所示:

  Table用于存储桌子的顶点数据,Mallet用于存储木槌的顶点数据,VertexArray用于存储实际的FloatBuffer数据,并且Table和Mallet都持有一个VertexArray实例。

  我们先从VertexArray开始,新建一个VertexArray类,并加入以下代码:

class VertexArray(vertexData:FloatArray) {
    private var floatBuffer: FloatBuffer
    init {
        floatBuffer= ByteBuffer
            .allocateDirect(vertexData.size*4)//一个浮点数占4个字节
            .order(ByteOrder.nativeOrder())
            .asFloatBuffer()
            .put(vertexData)
    }
    fun setVertexAttribPointer(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 var vertexArray:VertexArray
    companion object{
        val position_component_count=2//记录顶点的位置由两个分量表示
        val texture_coordinates_component_count=2//记录纹理坐标用两个分量表示
        val stride=(position_component_count+ texture_coordinates_component_count)*4//两个点的跨距
        val vertex_data= floatArrayOf(
            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
        )
    }
    init {
        vertexArray= VertexArray(vertex_data)
    }
    fun bindData(){//为位置属性和纹理坐标属性绑定数据
        vertexArray.setVertexAttribPointer(0,0, position_component_count, stride)
        vertexArray.setVertexAttribPointer(position_component_count,1, texture_coordinates_component_count, stride)
    }
    fun draw(){
        glDrawArrays(GL_TRIANGLE_FAN,0,6)
    }
}

  这个vertex_data数组中包含了空气曲棍球桌子的顶点数据,我们定义了x,y的位置以及S和T纹理坐标。我们需要注意的是S轴的方向是向右为正的,范围是从0到1,T轴是向下为正的,范围也是从0到1。我们还使用了0.1和0.9作为T的坐标,为什么?因为桌子是1个单位宽,1.6个单位高,而纹理图像是512x1024,因此如果宽对应一个单位,那么高就对应两个单位,如果我们使用[0,1]范围的T值的话,即整幅图像的高,那么这副图像的高就会被压缩。我们选择纹理图像[0.1,0.9]范围的高,对图像进行了裁剪,取图像的中间部分,这时,宽高比正好是1:1.6,纹理图像就不会被压缩了。

  创建一个Mallet类,用于管理木槌数据。代码如下:

class Mallet() {
    private var vertexArray:VertexArray
    companion object{
        val position_component_count=2//记录顶点的位置由两个分量表示
        val color_component_count=3//记录顶点颜色用三个分量表示
        val stride=(position_component_count+ color_component_count)*4//两个点的跨距
        val vertex_data= floatArrayOf(
            0f,-0.4f,0f,0f,1f,
            0f,0.4f,1f,0f,0f,
        )
    }
    init{
        vertexArray= VertexArray(vertex_data)
    }
    fun bindData(){
        vertexArray.setVertexAttribPointer(0,0, position_component_count,stride)
        vertexArray.setVertexAttribPointer(position_component_count,1, color_component_count, stride)
    }
    fun draw(){
        glDrawArrays(GL_POINTS,0,2)
    }
}

  接下来,我们会为纹理着色器程序创建一个类,为颜色着色器程序创建另一个类,我们会用纹理着色器绘制桌子,并用颜色着色器绘制木槌。我们也会创建一个基类作为他们的公共函数,我们不需要画中间那条线,因为那是纹理的一部分,类的继承结构如下:

   我们先给ShaderHelper类中加入一个函数用于编译着色器并链接成OpenGL程序,代码如下:

fun buildProgram(vertexShaderSource:String,fragmentShaderSource:String):Int{
            var program=0
            val vertexShader=compileVertexShader(vertexShaderSource)
            val fragmentShader=compileFragmentShader(fragmentShaderSource)
            program= linkProgram(vertexShader,fragmentShader)
            return program
        }

  现在我们来创建ShaderProgram类,代码如下:

open class ShaderProgram(context: Context, vertexShaderSourceId:Int, fragmentShaderSourceId:Int) {
    var program=0
    init{
        program=ShaderHelper.buildProgram(
            TextResourceReader.readTextFileFromResource(context,vertexShaderSourceId),
            TextResourceReader.readTextFileFromResource(context,fragmentShaderSourceId)
        )
    }
    fun useProgram(){
        glUseProgram(program)
    }
}

  加入纹理着色器程序TextureShaderProgram类:

class TextureShaderProgram(context: Context):ShaderProgram(context,R.raw.texture_vertex_shader,R.raw.texture_fragment_shader) {
    fun setUniforms(matrix:FloatArray,textureId:Int){//给uniform变量传递数据
        glUniformMatrix4fv(0,1,false,matrix,0)//传递投影矩阵
        //在opengl里使用纹理进行绘制时,不需要直接传递纹理给着色器,我们使用纹理单元texture unit保存那个纹理,然后将纹理单元传递给着色器
        glActiveTexture(GL_TEXTURE0)//激活纹理单元0
        glBindTexture(GL_TEXTURE_2D,textureId)//绑定纹理
        glUniform1i(1,0)
    }
}

  加入颜色着色器程序ColorShaderProgram类:

class ColorShaderProgram(context: Context):ShaderProgram(context,R.raw.simple_vertex_shader,R.raw.simple_fragment_shader) {
    fun setUniforms(matrix:FloatArray){
        glUniformMatrix4fv(0,1,false,matrix,0)
    }
}

  现在,我们已经把顶点数据和着色器程序放在不同的类了,现在就可以更新渲染器类,使用纹理进行绘制了。打开MyRenderer类,删掉所有代码,只保留onSurfaceChanged()函数,修改后的代码如下所示:

class MyRenderer(val context: Context):Renderer {
    private val projectionMatrix:FloatArray=FloatArray(16)//存储投影矩阵
    private val modelMatrix:FloatArray=FloatArray(16)//存储模型矩阵
    private var table:Table?=null
    private var mallet:Mallet?=null
    private var textureShaderProgram:TextureShaderProgram?=null
    private var colorShaderProgram:ColorShaderProgram?=null
    private var texture=0
    override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {
        glClearColor(0.0F,0.0F,0.0F,0.0F)//设置清除所使用的颜色,参数分别代表红绿蓝和透明度
        table= Table()
        mallet= Mallet()
        textureShaderProgram= TextureShaderProgram(context)
        colorShaderProgram= ColorShaderProgram(context)
        texture=TextureHelper.loadTexture(context,R.drawable.air_hockey_surface)
    }

    override fun onSurfaceChanged(p0: GL10?, width: Int, height: Int) {
        glViewport(0,0,width,height)//是一个用于设置视口的函数,视口定义了在屏幕上渲染图形的区域。这个函数通常用于在渲染过程中指定绘图区域的大小和位置,前两个参数x,y表示视口左下角在屏幕的位置
        Matrix.perspectiveM(projectionMatrix,0,45f,width.toFloat()/height.toFloat(),1f,10f)
        //生成模型矩阵
        Matrix.setIdentityM(modelMatrix,0)//设置为单位矩阵
        Matrix.translateM(modelMatrix,0,0f,0f,-3.5f)//将z值平移到可见范围内
        Matrix.rotateM(modelMatrix,0,-60f,1f,0f,0f)//绕x轴旋转-60度
        val temp:FloatArray=FloatArray(16)//存储矩阵相乘的结果
        Matrix.multiplyMM(temp,0,projectionMatrix,0,modelMatrix,0)
        System.arraycopy(temp,0,projectionMatrix,0,temp.size)//将temp复制到projectionMatrix
    }

    override fun onDrawFrame(p0: GL10?) {
        glClear(GL_COLOR_BUFFER_BIT)//清除帧缓冲区内容,和glClearColor一起使用
        //绘制桌子
        textureShaderProgram?.useProgram()
        textureShaderProgram?.setUniforms(projectionMatrix,texture)
        table?.bindData()
        table?.draw()
        //绘制木槌
        colorShaderProgram?.useProgram()
        colorShaderProgram?.setUniforms(projectionMatrix)
        mallet?.bindData()
        mallet?.draw()

    }
}

  最后,运行程序,看看纹理是否绘制在球桌上了。

 

 

  

 

标签:val,component,纹理,细节,0f,增加,GL,着色器
From: https://www.cnblogs.com/luqman/p/18006810

相关文章

  • 工程细节笔记
    SDRAM控制器读写非数据流的情况:在某些情况下最后剩下的那一段数据无法达到单次Brust长度,此时应该仲裁决定是否继续读写。这个问题在DDR3需要通过填0去解决,因为预读量远不是一个级别,填零以后通过截断获得有效数据是好的做法。//WR_req  always@(posedgeSys_clkornege......
  • informer增加过滤标签
    labelOptions:=informers.WithTweakListOptions(func(opts*metav1.ListOptions){ opts.LabelSelector="a=b"})informerFactory:=informers.NewSharedInformerFactoryWithOptions(kubeClient,0,labelOptions)hasSynced:=informerFactory.Core().V1()......
  • electron (electron-forge )细节
    前沿使用cnpmnpm运行不太行还是要使用打包npm在你的electron-forge项目目录下 关于打包新建一个.npmrc文件registry=https://registry.npmmirror.comelectron_mirror=https://npmmirror.com/mirrors/electron/#electron_builder_binaries_mirror=http://npm.tao......
  • 安卓开发五——创建数据库和增加数据
    packagecom.example.myapplication;importandroid.content.Context;importandroid.database.sqlite.SQLiteDatabase;importandroid.database.sqlite.SQLiteOpenHelper;publicclassDBHelperextendsSQLiteOpenHelper{privatestaticintDB_VERSION=1;......
  • 机器视觉-无GPC情况下增加内存的速度测试
    测试场景我的电脑没有GPU,想着升级增加内存多少会提升一下深度学习的速度,实践证明,增加内存并不能提升速度,连一星点效果都没有,原因也简单,瓶颈在CPU上而不是内存.如果手上没有GPU的电脑,还是直接在算力平台上租用靠谱.测试结果如下:内存batch内存使用率5epoc......
  • 增加颜色和着色
    一.平滑着色  我们已经知道,在OpenGL中,我们只能画点,直线和三角形,并且所有物体都是以他们为基础构建的。既然受限于这三个基本图元,那么我们如何用许多不同的颜色和着色表达更复杂的场景呢?我们能使用的一个方法就是使用上百万个小三角形,每个三角形的颜色都不同,这样就可以看到一副......
  • 2、linux增加域名代替ip
    linux增加域名代替ip1、查看当前的域名配置列表cat/etc/hosts2、配置域名vi/etc/hosts/etc/hosts:192.168.0.10为当前服务器ip192.168.0.20为配置其他服务ip127.0.0.1localhostlocalhost.localdomainlocalhost4localhost4.localdomain4::1localhostl......
  • Socket.D v2.3.9 发布(增加 node.js server 实现)
    Socket.D是基于"事件"和"语义消息""流"的网络应用层传输协议。有用户说,“Socket.D之于Socket,尤如Vue之于Js、Mvc之于Http”。支持tcp,udp,ws,kcp传输。协议特点可参考《官网介绍》。pyton已开发完成,再在测试中;go,rust,c++正在开发中。forJava适配框架更新说......
  • 找瓦工贴瓷砖前一定要交代的5个防坑细节
     找瓦工贴瓷砖,我说啥他能不坑我  1,做全屋通铺,不要过门石  这句话告诉师父,不要为了省事,推荐我做过门石  不好看不说,还得多花钱买小空间的压抑  记得排个版,把小条子藏起来  2,卫生间做墙地通缝,这句话告诉师父,  别为了省事,给我贴的乱七八糟 ......
  • [职场] 职场中的沟通细节 八点容易被忽视的沟通细节
    职场中沟通时要注意的细节其实很多,最基本的包括:不要发语音矩阵;不要发呵呵;不要发微笑的表情;复杂的事情可以直接电话沟通;重要的事情请留有书面记录。除此以外,小编还整理了8个我认为很重要的、容易被大家忽视的沟通细节。1.慎用感叹号感叹号一多整个话看起来就像命令。如果是强调语气,......