范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

一看就懂的OpenGLES教程临摹画手的浪漫之纹理映射(实践篇)

  通过阅读本文,你将获得以下收获 :
  1.如何将Bitmap传到Native层处理
  2.如何使用代码实现纹理映射
  3.通过纹理映射实现一些有趣的效果上篇回顾
  上一篇一看就懂的OpenGL ES教程——临摹画手的浪漫之纹理映射(理论篇)已经详细叙述了 纹理的概念 以及 纹理映射到图元上的原理 ,都是纯理论,略显枯燥。
  今天就将理论付诸实践,一起来看看  具体代码如何实现纹理映射  。
  最后再利用纹理映射来实现一些有意思的效果,绝对不能错过~
  代码实战如何将图片传入Native层
  上一篇一看就懂的OpenGL ES教程——临摹画手的浪漫之纹理映射(理论篇)主页有已经说过, 纹理就是携带图片信息的容器  ,所以这里首先要获取到图片的信息(不然还纹理映射个毛线),在android的Java层,获取位图的方式可谓妇孺皆知: Bitmap bitmap = ((BitmapDrawable) getResources().getDrawable(R.drawable.liyingai)).getBitmap();
  因为我们OpenGL代码是在Native层的,那么怎么将Bitmap传给Native层处理呢?其实直接传入即可,在C++层用jobject接收(如果对于ndk还不太熟悉可以看下我之前写的入门文章:初探ndk的世界(一) ),然后ndk已经提供了对应的 jnigraphics  库来处理Bitmap相关操作,它可以直接操作Bitmap的像素。
  使用之前,先要 在CmakeList中链接jnigraphics库  : target_link_libraries( # Specifies the target library.                        native-lib                        GLESv3                        EGL                        android                        jnigraphics # 操作Bitmap的库                        # Links the target library to the log library                        # included in the NDK.                        ${log-lib} )
  Java层创建绘制纹理的Native方法: public native void drawTexture(Bitmap bitmap,  Object surface);
  在Native层对应的方法如下: Java_com_example_openglstudydemo_YuvPlayer_drawTexture(JNIEnv *env, jobject thiz, jobject bitmap,                                                         jobject surface)
  注意到在这里Bitmap对象已经是 jobject  类型。
  首先用jnigraphics库的 AndroidBitmap_getInfo  方法获取Bitmap对象的相关信息: /**  * Given a java bitmap object, fill out the {@link AndroidBitmapInfo} struct for it.  * If the call fails, the info parameter will be ignored.  */ int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap,                           AndroidBitmapInfo* info);
  第一个参数就是 JNIEnv  指针,第二个参数为 Bimtap  对象,第三个为结构体 AndroidBitmapInfo  的指针。
  AndroidBitmapInfo为何物呢?其实,它就类似一个水桶,在函数执行完就将数据舀出来,也就是获取到的信息会存放在 AndroidBitmapInfo的结构体  中,对于图片来说,最常见的信息莫过于宽高、像素格式等: /** Bitmap info, see AndroidBitmap_getInfo(). */ typedef struct {     /** The bitmap width in pixels. */     uint32_t    width;     /** The bitmap height in pixels. */     uint32_t    height;     /** The number of byte per row. */     uint32_t    stride;     /** The bitmap pixel format. See {@link AndroidBitmapFormat} */     int32_t     format;     /** Bitfield containing information about the bitmap.      *      * 

Two bits are used to encode alpha. Use {@link ANDROID_BITMAP_FLAGS_ALPHA_MASK} * and {@link ANDROID_BITMAP_FLAGS_ALPHA_SHIFT} to retrieve them. * *

One bit is used to encode whether the Bitmap uses the HARDWARE Config. Use * {@link ANDROID_BITMAP_FLAGS_IS_HARDWARE} to know. * *

These flags were introduced in API level 30. */ uint32_t flags; } AndroidBitmapInfo;   执行 AndroidBitmap_getInfo方法的返回值 会是以下几种情况,成功返回为0。 /** AndroidBitmap functions result code. */ enum { /** Operation was successful. */ ANDROID_BITMAP_RESULT_SUCCESS = 0, /** Bad parameter. */ ANDROID_BITMAP_RESULT_BAD_PARAMETER = -1, /** JNI exception occured. */ ANDROID_BITMAP_RESULT_JNI_EXCEPTION = -2, /** Allocation failed. */ ANDROID_BITMAP_RESULT_ALLOCATION_FAILED = -3, };   一旦返回为0,那么恭喜你,已经成功拿到了Bitmap基本信息。   C++音视频学习资料免费获取方法:关注音视频开发T哥 ,点击「链接」即可免费获取2023年最新 C++音视频开发进阶独家免费学习大礼包!   但是光拿到Bitmap的基本信息还是不够的,还记得上一篇一看就懂的OpenGL ES教程——临摹画手的浪漫之纹理映射(理论篇)提到纹理映射原理的时候说过:   遍历图形中所有的片段,依次通过片段所在的位置坐标定位到其对应在纹理中的纹素,再获取到对应的颜色 。   我们知道一张2D图片,其实就是一个 二维数组 ,按照一定的格式, 每若干个数组元素其实就是代表一个纹素 ,所以要 拿到对应的纹素 ,首先要拿到图片的 像素二维数组 。   所幸的事,jnigraphics库的 AndroidBitmap_lockPixels 已经帮我们做好这件事了: /** * Given a java bitmap object, attempt to lock the pixel address. * Locking will ensure that the memory for the pixels will not move * until the unlockPixels call, and ensure that, if the pixels had been * previously purged, they will have been restored. * * If this call succeeds, it must be balanced by a call to * AndroidBitmap_unlockPixels, after which time the address of the pixels should * no longer be used. * * If this succeeds, *addrPtr will be set to the pixel address. If the call * fails, addrPtr will be ignored. */ int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr);   前两个参数不言而喻,最后一个参数就是指向Bitmap像素 二维数组的二级指针 (如果对于二级指针不太理解,可以看下我之前的博文: 漫谈C语言指针(三) ),简单来说, 该方法的作用就是通过一个二级指针指向传过来的Bitmap的像素数组 。   注意这个方法的名字带有lock,即它会锁一些东西。锁什么呢?通过方法的注释可知,会 锁住像素数据的内存 ,直到 AndroidBitmap_unlockPixels 方法调用才解锁。   关于如何处理Bitmap纹素就先看到这,至于拿到的像素数据二级指针要怎么用等会再解答,我们再看看其他的纹理映射逻辑先。   添加纹理坐标   上一篇一看就懂的OpenGL ES教程——临摹画手的浪漫之纹理映射(理论篇)已经提及过纹理坐标的概念:   所以这里我们需要指定纹理坐标,这里 指定坐标的意义是指定需要进行纹理映射的那一部分纹理的顶点的坐标点 ,比如还是下面这张图,就是指定了左边需要映射的三角形的三个顶点在整个纹理中的坐标:   为了简单,我们这里先映射整张图吧: float vertices[] = { // 图元顶点坐标 // 纹理坐标 0.5f, 0.5f, 0.0f, 1.0f, 1.0f, // top right 0.5f, -0.5f,0.0f, 1.0f, 0.0f, // bottom right -0.5f, -0.5f,0.0f, 0.0f, 0.0f, // bottom left -0.5f, 0.5f, 0.0f, 0.0f, 1.0f // top left };   这里的纹理坐标就是指定了整张图片四个顶点。(当然,也可以指定只采样图片的一部分,后面会演示)   然后依然像一看就懂的OpenGL ES教程——缓冲对象优化程序(二) 一样使用VBO, VAO, EBO优化程序: unsigned int indices[] = { 0, 1, 3, // first triangle 1, 2, 3 // second triangle }; unsigned int VBO, VAO, 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); 复制代码着色器逻辑   既然添加了纹理坐标了,根据经验和直觉,着色器是不是就要添加一个变量去接收纹理坐标了呢?   没错,这是毋庸置疑的~   顶点着色器: #version 300 es layout (location = 0) in vec4 aPosition; //新增的接收纹理坐标的变量 layout (location = 1) in vec2 aTexCoord; //纹理坐标输出给片段着色器使用 out vec2 TexCoord; void main() { //直接把传入的坐标值作为传入渲染管线。gl_Position是OpenGL内置的 gl_Position = aPosition; //纹理坐标传给片段着色器 TexCoord = aTexCoord; };   这里要 新增一个接收纹理坐标的变量aTexCoord ,不过,因为 采样 这个任务还是交给了 片段着色器 来完成,毕竟 着色 还是片段着色器要干的活,所以最终还是提供给片段着色器使用,所以又用输出变量 TexCoord "送"了出去。   片段着色器: #version 300 es precision mediump float; //新增的接收纹理坐标的变量 in vec2 TexCoord; out vec4 FragColor; //传入的纹理 uniform sampler2D ourTexture; void main() { //texture方法执行具体的采样 FragColor = texture(ourTexture, TexCoord); };   这里用同名的TexCoord去接收顶点着色器传过来的纹理坐标。   这里开始出现了一个陌生的新变量类型: sampler2D ,看下官网的定义:   A sampler is a set of GLSL variable types. Variables of one of the sampler types must be uniforms or as function parameters. Each sampler in a program represents a single texture of a particular texture type. The type of the sampler corresponds to the type of the texture that can be used by that sampler.   可见它就是代表一个 纹理对象 ,这里 sampler2D 中的"2D"代表的就是 2D纹理 。   但是说它代表一个纹理对象其实是 不准确的 ,更准确的是代表一个 纹理单元 ,通过 纹理单元去绑定一个纹理对象 ,从而 间接绑定纹理对象 。   它只能被 uniform 修饰或者作为方法参数,这里被 uniform 修饰也就代表了一帧图像内,这个纹理单元是不会变的,即对应的纹理的图片是不变的。   再看看main函数里面唯一的"宠儿": FragColor = texture(ourTexture, TexCoord);   它就是传说中重中之重的 采样函数 了,具体来说就是 获取到传入的具体纹理坐标值TexCoord在ourTexture对应的纹理上的纹素的颜色 (当然由于不同的过滤模式会导致具体采样颜色的细节不同)。   你可能会问,这里的TexCoord具体的坐标值是多少呢?如果这样问,那你可能8成没看过我之前讲过光栅化插值这个"骚操作"的博文:一看就懂的OpenGL ES教程——这或许是你遇过最难画的三角形(五),如果看过就知道,在这里,你传 入顶点着色器的纹理坐标的那几个值,已经经过光栅化等的处理,把它通过几何关系转化为对应的一个坐标值了 。   这么一想,是不是整个流程都非常通畅了呢?   纹理对象配置   所以这里我们先通过 AndroidBitmap_getInfo 方法获取Bitmap基本信息: //存储Bitmap基本信息的结构体 AndroidBitmapInfo bmpInfo; if (AndroidBitmap_getInfo(env, bitmap, &bmpInfo) < 0) { LOGD("AndroidBitmap_getInfo() failed ! "); return; }   然后获取Bitmap像素数组的指针: void *bmpPixels; AndroidBitmap_lockPixels(env, bitmap, &bmpPixels);   此时(Bitmap像素数组的指针bmpPixels)枪在手跟我走~   接下来就是 配置纹理对象了 。   配置什么呢?还记得上一篇博文 讲的 纹理环绕 和 纹理过滤 么?不记得的话直接回去看看这篇博文先吧。   前面讲过纹理对象就是一个OpenGL Object,所以它的用法和其他的OpenGL Object是非常相似的,以下是纹理对象的结构图:   Diagram of the contents of a texture object   可以看出,纹理对象由 纹理数据存储区+采样参数+纹理参数构成 。   根据之前博文 一看就懂的OpenGL ES教程——缓冲对象优化程序(一) 写的,使用一个OpenGL Object的几部曲:   创建对象——绑定对象——处理相关操作逻辑——解绑对象——销毁对象   纹理对象也是如此。 //纹理id unsigned int texture1; //创建纹理 glGenTextures(1, &texture1); //绑定纹理 glBindTexture(GL_TEXTURE_2D, texture1);   绑定纹理,开始具体的采样参数配置(当然不配置也有默认配置,一般最好配置一下为好): //纹理环绕配置 //横坐标环绕配置 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) //纵坐标环绕配置 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); //纹理过滤配置 // set texture filtering parameters(配置纹理过滤) //纹理分辨率大于图元分辨率,即纹理需要被缩小的过滤配置 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); //纹理分辨率小于图元分辨率,即纹理需要被放大的过滤配置 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);   纹理对象的配置是通过glTexParameteri函数实现的:   void glTexParameteri(   GLenum target ,   GLenum pname ,   GLint param )`;   target 是指定绑定的纹理目标,必须为 GL_TEXTURE_1D , GL_TEXTURE_1D_ARRAY , GL_TEXTURE_2D , GL_TEXTURE_2D_ARRAY , GL_TEXTURE_2D_MULTISAMPLE , GL_TEXTURE_2D_MULTISAMPLE_ARRAY , GL_TEXTURE_3D , GL_TEXTURE_CUBE_MAP , GL_TEXTURE_CUBE_MAP_ARRAY , o, GL_TEXTURE_RECTANGLE 中的一种。我们映射的是普通的2D纹理,所以使用 GL_TEXTURE_2D 。   pname 为需要配置具体配置种类。   param 为具体的配置的值。   首先是 纹理环绕配置 ,这里通过 首先是纹理环绕配置,这里指定的配置种类为 GL_TEXTURE_WRAP_S 和 GL_TEXTURE_WRAP_T 分别表示在s和t轴方向的采样环绕配置。 GL_REPEAT 表示超过范围重复出现。   s和t轴是什么?看下上篇文章这个熟悉的表示纹理坐标图估计你就懂了~   然后是 纹理过滤配置 : //纹理分辨率大于图元分辨率,即纹理需要被缩小的过滤配置 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); //纹理分辨率小于图元分辨率,即纹理需要被放大的过滤配置 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);   GL_TEXTURE_MIN_FILTER 和 GL_TEXTURE_MAG_FILTER 分别代表纹理被缩小和放大的场景。上一篇文章一看就懂的OpenGL ES教程——临摹画手的浪漫之纹理映射(理论篇)已经提到过,当进行采样的时候,纹理的纹素和图元的片段往往不是一样多的(简单理解就是图元面积和纹理图片的面积不一样大),这也就导致了,当纹理映射的时候,我们要做类似将纹理的几个顶点"拉伸"或者"收缩"到和图元顶点贴合在一起的时候,纹理会被放大或者缩小,于是就需要在纹理被放大和缩小2种情况下分别进行采样过滤的配置。   接下来,也就是最重要的一步,那就是将前一步获取到的 图片数据传给纹理对象 : glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bmpInfo.width, bmpInfo.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bmpPixels);   看起来有点眼熟的 bmpPixels 正是前一步获取到的图片像素数组的指针。   glTexImage2D 方法的声明为:   void glTexImage2D( GLenum target , GLint level , GLint internalformat , GLsizei width , GLsizei height , GLint border , GLenum format , GLenum type , const void * data );   target :依然代表纹理目标。   level :这里指的是mipmap的层级,mipmap还没讲到,这里我们暂时只传0。   internalformat :表示纹理 存储在GPU 中的 颜色格式 。包括`Base Internal Formats、Sized Internal Formats、Compressed Internal Formats。   最常见的 Base Internal Formats 有以下格式:   Base Internal Format   RGBA, Depth and Stencil Values   Internal Components   GL_DEPTH_COMPONENT   Depth   D   GL_DEPTH_STENCIL   Depth, Stencil   D, S   GL_RED   Red   R   GL_RG   Red, Green   R, G   GL_RGB   Red, Green, Blue   R, G, B   GL_RGBA   Red, Green, Blue, Alpha   R, G, B, A   width和height :分别表示纹理图片的宽度和高度,一般要求至少有1024个纹素。   border :这个据说是历史遗留的一个参数,现在固定传0就好。   format :表示传入的纹理 像素数据的颜色格式 (注意和internalformat的区别)。比如: GL_RED 、 GL_RG 、 GL_RGB , GL_BGR 、 GL_RGBA , GL_BGRA 。   type :表示传入的 纹理像素数据数组的元素的数据类型 ,比如 GL_UNSIGNED_BYTE , GL_BYTE , GL_UNSIGNED_SHORT , GL_SHORT , GL_UNSIGNED_INT , GL_INT , GL_HALF_FLOAT , GL_FLOAT 等等。   data :这就是传入的 纹理像素数据的指针 了。   还是那句话,OpenGL为了强大的功能性,牺牲了使用的方便性,导致它就像一个憨憨,需要把传入的数据的细枝末节非常唠叨地告诉它,它才知道怎么去解析传入的数据。   这里我们按如下参数来传: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bmpInfo.width, bmpInfo.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bmpPixels);   首先映射的是2D纹理,所以target传GL_TEXTURE_2D。然后通过AndroidBitmap_lockPixels方法得到的像素数据格式为RGBA,所以internalformat和format都传GL_RGBA。接下来尺寸数据传从bmpInfo获取的宽高数据,这里像素数据的每个通道由8位组成,即范围为0-255,所以对应的格式为GL_UNSIGNED_BYTE。   然后又是熟悉的解析顶点属性数组数据, 分别传入顶点和纹理坐标数据 (如果还不清楚具体是怎么解析的,请看系列的前面几篇博文): //顶点坐标 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) 0); glEnableVertexAttribArray(0); //纹理坐标 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) (3 * sizeof(float))); glEnableVertexAttribArray(1);   为了增强大家的学习效果,这次纹理映射的图片就依旧用经典的女神图。   运行看下效果:   额,图片怎么上下颠倒了…   这是Android平台的OpenGL es一个扎根多年的历史大坑,不要问我出现的原因,我只知道,在 Android平台的OpenGL es,纹理坐标的原点是在左上角点(即一般一般情况下(0.0,1.0)点),而不是常见的左下角点 ,导致我们 直接使用传入的纹理坐标会发生上下沿y轴=0.5的直线发生镜面翻转 。   在Android平台的OpenGL es,真正的纹理坐标如下图红色文字所示:   所以,这里顶点着色器传给片段着色器的纹理坐标我们需要做一点调整: #version 300 es layout (location = 0) in vec4 aPosition; layout (location = 1) in vec2 aTexCoord; out vec2 TexCoord; void main() { gl_Position = aPosition; //纹理坐标要经过上下翻转再传给片段着色器 TexCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y);; };   再运行一下:   完美!   聪明的你可能又觉察到一丝不对劲了……   片段着色器中表示纹理(纹理单元)的变量 ourTexture 我们都没传,咋就能够采样了呢?   原因很简单: OpenGL内部帮我们传了 。   如果当前的渲染只需要一个纹理单元的情况下,OpenGL会默认我们使用的是第一个纹理单元,即 GL_TEXTURE0 。所以 片段着色器声明的sampler2D对象就会默认赋值为0,0则代表和GL_TEXTURE0的纹理关联 。   而在客户端程序中,我们也并没有制定创建的纹理是属于哪个纹理单元的,所以 默认也为第一个纹理单元,即GL_TEXTURE0 ,所以对该纹理对象的所有操作,都 默认为针对即GL_TEXTURE0对应的纹理单元 ,所以我们的数据其实是 默认和片段着色器的ourTexture变量关联上的 。 实现多图层混叠   刚才实现的是单个纹理单元的渲染,接下来,我要做一件有趣的事情,就是将石原美里和李英爱的图片混合在一起:   惊不惊喜~~   首先新增一个纹理对象并配置参数和纹理数据: glGenTextures(1, &texture2); glBindTexture(GL_TEXTURE_2D, texture2); // set the texture wrapping parameters glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // set texture filtering parameters glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bmpInfo1.width, bmpInfo1.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bmpPixels1); AndroidBitmap_unlockPixels(env, bitmap1);   上面刚说的我想就不用在这里重复了吧。   这里要增加的步骤是,因为现在是需要2个纹理单元了,所以我们需要手动对纹理单元进行赋值: //对着色器中的纹理单元变量进行赋值 glUniform1i(glGetUniformLocation(program, "ourTexture"), 0); glUniform1i(glGetUniformLocation(program, "ourTexture1"), 1);   关于Uniform变量的设置在一看就懂的OpenGL ES教程——这或许是你遇过最难画的三角形(五)已经提及,这里就不赘述了。   分别对片段着色器中的 ourTexture 和 ourTexture1 变量赋值 0和1 ,分别表示 GL_TEXTURE0 和 GL_TEXTURE1 纹理单元一一对应。   然后将纹理单元和纹理对象进行绑定: //将纹理单元和纹理对象进行绑定 //激活纹理单元,下面的绑定就会和当前激活的纹理单元关联上 glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture1); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, texture2);   先使用glActiveTexture方法激活纹理单元,然后根据OpenGL的规则, 接下来执行的glBindTexture对应的纹理对象就会和这个激活的纹理单元关联上 。   这样子,便完成了 纹理对象texture1和着色器中的变量ourTexture、纹理对象texture2和着色器中的变量ourTexture1的绑定 。(不得不说这个绑定真绕= =)   片段着色器变成: #version 300 es precision mediump float; in vec2 TexCoord; out vec4 FragColor; //传入的纹理 uniform sampler2D ourTexture; //新增纹理单元 uniform sampler2D ourTexture1; void main() { //对2个纹理进行混合 FragColor = mix(texture(ourTexture, TexCoord), texture(ourTexture1, TexCoord), 0.5); };   mix 为OpenGL内置的函数,表示对2个数进行按比例混合叠加,这里就是对 当前片段从2纹理采样得到的颜色值进行按照0.5的比例混合 。   运行看下:   是不是有点电影转场内味了?是不是妙不可言~   总结   本文主要从代码实践角度详细( 盲猜可能网上没有比这个更详细的嘻嘻 )讲解为如何进行纹理映射,最后通过将2个纹理进行混合,实现了一个挺有意思的效果。当然不仅仅是混合,这里就可以充分发挥想象力,去干一些灰常有趣的事情,这就是下一篇文章的内容,即 开始玩一些奇技淫巧了 。   项目代码   opengl-es-study-demo (不断更新中) 参考   纹理 Texture Sampler (GLSL) 作者:半岛铁盒里的猫 链接:https://juejin.cn/post/7155040552353234951   来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。   在开发的路上你不是一个人,欢迎加入 C++音视频开发交流群「链接」大家庭讨论交流!


攻防两端齐发力,深圳队主场力克北京首钢3月14日晚,万众期待的焦点大战深圳烈豹队主场迎战北京首钢终于到来。本赛季,两队此前一次交战中,深圳队拔得头筹。赛前,郑永刚指导表示,北京队是一个强大的对手,深圳队已经做好了全面部朱婷11分女排欧联杯半决赛首回合斯坎迪奇3比0完胜土耳其航空北京时间今天(15日)凌晨的比赛,2023女排欧联杯半决赛首回合,斯坎迪奇在主场以30(2725,2512,2518)完胜土耳其航空,在主客场金局制的系列赛中占得先机。中国运动员朱波尔图主帅球队展现出团结和韧劲,没能晋级八强还是有些遗憾直播吧3月15日讯在今天凌晨结束的欧冠18决赛次回合比赛中,波尔图主场00与国米战平,最终以01的总比分无缘八强。赛后波尔图主帅孔塞桑接受了葡媒纪录报的采访,对球队表现进行总结。孔官宣!FIFA两项重大改革,七大细节曝光,国足和中国有望成为赢家FIFA官方宣布关于世界杯和世俱杯的最新改革方案,两项重大改革,也将推动足球走向新的高度,而在改革浪潮下,我们国家和我们的中国男足,都有望成为大赢家。FIFA官宣两项重大改革FIFWTT新加坡大满贯第三比赛日,国乒拒绝意外六场全胜WTT新加坡大满贯,今天进入第三比赛日。在昨天国乒女队三人出局后,今天国乒没有让意外再次发生。六场比赛全胜。具体战绩如下马龙30林诗栋王曼昱30木原美悠王楚钦孙颖莎31林仲勋申裕斌指南力荐拯救高龄卵巢功能减退者生育力,8种疗法效果获认可!头条创作挑战赛卵巢功能减退真的是一个令人绝望的问题,轻则生育力下降,重则终身可能难以抱娃,很多家庭因为这个原因出现裂痕。卵巢功能减退的治疗宜早不宜迟,如果早发现早治疗,不但抱娃不成万人参赛!京沪半马同时锁定4月16日,选手要注意什么?中新经纬3月14日电据北京半程马拉松微信号14日消息,2023北京半程马拉松将于4月16日起跑。据介绍,本次比赛项目为半程马拉松(21。0975公里),参赛规模20000人,比赛时超过100人参会要收费了?腾讯会议再调会员规则以后,企业领导们可能无法随便简单讲两句了,因为腾讯会议收费规则又变了。3月14日,腾讯会议发布调整说明4月4日起逐步取消免费用户300人不限时会议使用权限,单场会议最高人数和时长调土库曼斯坦五个上榜山花,不是有故事就是有功效土库曼斯坦的春天,是山花烂漫的季节。自2月开始,各种花朵在春天的田野里竞相绽放,并且花期会持续到春天的最后一个月。在这个万物复苏的季节里,就连沙漠中也覆盖着一年生或多年生的植被。在文心一言效果怎么样?AI能用来做配音吗?随着信息技术的飞速发展,人工智能技术也越来越成熟。AI技术不仅可以辅助人类进行重复性劳动,而且还可以应用于语音合成图像识别等领域。那么,AI技术能否用来做配音呢?首先,我们要了解一拜登也没有想到,中国反制效果这么好,这回轮到美企被卡脖子文文学科技社科学技术是第一生产力,这句话放在哪个时代似乎都不过时,随着中国经济与科技的迅速崛起,中美之间的各项差距正在逐步缩小,其中,在某些领域,比如5G,中国甚至已将反超美国,成
扒一扒各大品牌私藏的生发专利,哪种秃还能拯救一下?染发是爱豆的逆袭密码吧?自从叶舒华染了金发后,一整个大哇塞,让一直对她无感的大人狠狠get到了她的颜。对比黑发时期的寡淡小白花,金发把她趁得更有大明星那味了。不过这个漂发复仇法可不每天认识一支手表浪琴八针月相腕表官方售价26700元二级市场售价12000元浪琴表的优雅姿态是其品牌的代名词,这家拥有180多年古老历史的手表品牌,推出了多款月相腕表。今天为大家带来一款浪琴复杂功能腕表,该表款拥初春就要温柔好看,这几件单品,轻松打造优雅时髦范儿嗨,我是柒作作。我们期待已久的春天就要到了,这个春天一定要变得更温柔一些,因为温柔的女子最好命啊!初春穿搭想要打造出温柔时髦的造型,我们其实并不需要花大价钱买太多的衣服,衣橱里有这商务部2023年支持自贸试验区深入开展差别化探索王舒嫄中国证券报中证网中证网讯(记者王舒嫄)商务部副部长郭婷婷2月2日在国新办新闻发布会上表示,2023年,商务部将按照党中央国务院决策部署,切实履行好国务院自由贸易试验区工作部际胡鑫宇案的警示孩子快乐成长比什么都重要胡鑫宇案终于真相大白,遗憾的是孩子已经离开了这个世界。录音笔的内容还没完全公布,但是我们可以想象在他的内心里,积累了多少压力,以至于他觉得离开这个世界,才会终止这一切。现在的孩子,逝者已矣生者如斯该怎样避免发生在胡鑫宇身上的悲剧再次发生?胡鑫宇曾多次尝试自杀他长期处在自责愧疚的情绪中有关这起事件的发布会上公布了他在录音笔上录制的内容,感觉活着没有意义从以上他的这些表现来看,是重度抑郁症的症状。善良可怜的胡鑫宇,希望蔡英文参加大陆台商春节活动称愿与大陆展开对话中国台湾网2月2日讯据台湾联合报报道,台湾海基会2日在台北举办2023大陆台商春节活动,是新冠疫情爆发三年来该活动首次恢复举办,蔡英文台经济事务主管部门负责人王美花台湾陆委会主委邱日本泡温泉需守3个害羞规定,女游客没隐私了被称之为温泉王国的日本,人们在空闲时,选择到日本去泡温泉,不过日本的泡汤规则中有三点是让女人很尴尬的。(此处已添加小程序,请到今日头条客户端查看)有些中国旅游客直接说丝毫没有任何的家门口就业收入增3000!张家口万全正北沟村这样做房子租给村里改造民宿,不但能收到租金,自己也能回来养老,真是享了民宿旅游的福了!万全区膳房堡乡正北沟村村民王永生介绍,受益于村里发展民宿旅游,他每年能收1万元租金。现在,正北沟村越树莓派4B新手入门3基础设置当烧录完镜像后大部分开发者都需要通过SSH进行远程登陆,首先需要进行联网操作。新手需要借助屏幕进行wifi连接或者直接连接网线,不过在没有屏幕的情况下较为麻烦。这里介绍几种常用的方瞭望山区县如何实现专精特新全覆盖以比较优势招引潜力股立足传统产业打深井用政策环境留住归乡雁浙江山区县专精特新企业呈现出不同的成长轨迹我们这个行业没有捷径,工艺提升比的就是谁更细心谁更专注。作为专精特新企业生产制造