NDK OpenGL离屏渲染与工程代码整合

news2024/11/29 1:33:51

NDK​系列之OpenGL离屏渲染与工程代码整合,本节主要是对上一节OpenGL渲染画面效果代码进行封装设计,将各种特效代码进行分离解耦,便于后期增加其他特效。

实现效果:

实现逻辑:

1.封装BaseFilter过滤器基类,实现通用的顶点着色器、片元着色器和着色器程序的初始化,赋值和绘制操作;

2.编写CamreaFilter相机过滤器,实现FBO离屏渲染和变换矩阵等相机相关操作,绘制操作完成后,将相机过滤器实现的效果的纹理ID传递给下一个过滤器,在相机过滤器效果上进行叠加其他效果。

本节主要内容:

1.封装BaseFilter过滤器基类;

2.实现CamreaFilter相机过滤器;

3.实现ScreenFilter屏幕过滤器;

4.自定义渲染器MyGlRenderer;

源码:

NdkOpenGLPlay: NDK OpenGL渲染画面效果

一、封装BaseFilter过滤器基类

1)BaseFilter构造方法,保存顶点着色器和片元着色器代码资源文件id,保存坐标系到数据缓冲区;

public BaseFilter(Context context, int vertexSourceId, int fragmentSourceId) {
	this.mVertexSourceId = vertexSourceId; // 子类传递过来的顶点着色器代码ID
	this.mFragmentSourceId = fragmentSourceId; // 子类传递过来的片元着色器代码ID

	// 顶点相关 坐标系
	float[] VERTEX = {
			-1.0f, -1.0f,
			1.0f, -1.0f,
			-1.0f, 1.0f,
			1.0f, 1.0f,};
	mVertexBuffer = BufferHelper.getFloatBuffer(VERTEX); // 保存到 顶点坐标数据缓冲区

	// 纹理相关 坐标系
	float[] TEXTURE = {
			0.0f, 0.0f,
			1.0f, 0.0f,
			0.0f, 1.0f,
			1.0f, 1.0f,};
	mTextureBuffer = BufferHelper.getFloatBuffer(TEXTURE); // 保存到 纹理坐标数据缓冲区

	init(context);
}

2)加载并编译着色器代码,链接色器ID,输出着色器程序,并获取着色器的索引值;

private void init(Context context) {
	String vertexSource = TextResourceReader.readTextFileFromResource(context, mVertexSourceId); // 顶点着色器代码字符串
	String fragmentSource = TextResourceReader.readTextFileFromResource(context, mFragmentSourceId); // 片元着色器代码字符串

	int vertexShaderId = ShaderHelper.compileVertexShader(vertexSource); // 编译顶点着色器代码字符串
	int fragmentShaderId = ShaderHelper.compileFragmentShader(fragmentSource); // 编译片元着色器代码字符串

	mProgramId = ShaderHelper.linkProgram(vertexShaderId, fragmentShaderId); // 链接 顶点着色器ID,片元着色器ID 最终输出着色器程序

	// 删除 顶点 片元 着色器ID
	glDeleteShader(vertexShaderId);
	glDeleteShader(fragmentShaderId);

	// 顶点着色器里面的如下:
	vPosition = glGetAttribLocation(mProgramId, "vPosition"); // 顶点着色器:的索引值
	vCoord = glGetAttribLocation(mProgramId, "vCoord"); // 顶点着色器:纹理坐标,采样器采样图片的坐标 的索引值
	vMatrix = glGetUniformLocation(mProgramId, "vMatrix"); // 顶点着色器:变换矩阵 的索引值

	// 片元着色器里面的如下:
	vTexture = glGetUniformLocation(mProgramId, "vTexture"); // 片元着色器:采样器
}

3)完成绘制操作,顶点坐标赋值,纹理坐标赋值,采样器赋值,通知opengl绘制,将过滤器实现的效果的纹理ID返回给下一个过滤器;

public int onDrawFrame(int textureId) {
	// 设置视窗大小
	glViewport(0, 0, mWidth, mHeight);
	glUseProgram(mProgramId); // 必须要使用着色器程序一次

	// TODO 画画,绘制 等工作

	// TODO 1.顶点坐标赋值
	mVertexBuffer.position(0);
	// 传值
	glVertexAttribPointer(vPosition, 2, GL_FLOAT, false, 0, mVertexBuffer);
	// 激活
	glEnableVertexAttribArray(vPosition);

	// TODO 2.纹理坐标赋值
	mTextureBuffer.position(0);
	// 传值
	glVertexAttribPointer(vCoord, 2, GL_FLOAT, false, 0, mTextureBuffer);
	// 激活
	glEnableVertexAttribArray(vCoord);

	// 只需要把OpenGL的纹理ID,渲染到屏幕上就可以了,不需要矩阵数据传递给顶点着色器了
	// 变换矩阵 把mtx矩阵数据 传递到 vMatrix
	// glUniformMatrix4fv(vMatrix, 1, false, mtx, 0);

	// TODO 3.片元 vTexture
	glActiveTexture(GL_TEXTURE0); // 激活图层

	// 不需要关心摄像头 和 矩阵
	// 绑定图层,为什么不需要GL_TEXTURE_EXTERNAL_OES?答:目前拿到的textureId已经是纹理ID了,不是摄像头直接采集到的纹理ID
	glBindTexture(GL_TEXTURE_2D ,textureId);

	// 因为CameraFilter已经做过了,我就直接显示,我用OepnGL 2D GL_TEXTURE_2D 显示就行了
	// glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId); // 由于这种方式并不是通用的,所以先去除

	glUniform1i(vTexture, 0); // 传递采样器
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // 通知 opengl 绘制

	return textureId; // 返回纹理ID,可以告诉下一个过滤器
}

二、实现CamreaFilter相机过滤器

1)CameraFilter构造方法,将顶点着色器和片元着色器代码资源文件id传给父类,完成顶点着色器、片元着色器和着色器程序的初始化工作;

public CameraFilter(Context context) {
	super(context, R.raw.camera_vertex, R.raw.camera_fragment);
}

2)准备工作,创建FBO帧缓冲区和FBO的纹理ID,并绑定起来;

public void onReady(int width, int height) {
	super.onReady(width,height);

	// TODO 准备工作
	// TODO 第一步:创建 FBO (看不见的离屏的屏幕)
	mFrameBuffers = new int[1];
	// 参数1:int n, fbo 个数
	// 参数2:int[] framebuffers, 用来保存 fbo id 的数组
	// 参数3:int offset 从数组中第几个id来保存,从零下标开始
	glGenFramebuffers(mFrameBuffers.length, mFrameBuffers, 0); // 实例化创建帧缓冲区,FBO缓冲区

	// TODO 第二步:创建属于 fbo 纹理(第一节课是没有配置的,但是这个是FOB纹理,所以需要配置纹理)
	// 既然上面的 FBO(看不见的离屏的屏幕),下面的目的就是要把画面显示到FBO中
	mFrameBufferTextures = new int[1]; // 记录FBO纹理的ID
	TextureHelper.genTextures(mFrameBufferTextures); // 生成并配置纹理

	// TODO 第三步:上面的 FBO缓冲区 与 FBO纹理 还没有任何关系,现在要让他们绑定起来
	glBindTexture(GL_TEXTURE_2D, mFrameBufferTextures[0]);

	// 生产2D纹理图像
	/*
		int target,         要绑定的纹理目标
		int level,          level一般都是0
		int internalformat, 纹理图像内部处理的格式是什么,rgba
		int width,          宽
		int height,         高
		int border,         边界
		int format,         纹理图像格式是什么,rgba
		int type,           无符号字节的类型
		java.nio.Buffer pixels
	 */
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);

	glBindFramebuffer(GL_FRAMEBUFFER, mFrameBuffers[0]); // 发生关系

	 /*
		int target,     fbo的纹理目标
		int attachment, 附属到哪里
		int textarget,  要绑定的纹理目标
		int texture,    纹理
		int level       level一般都是0
	 */
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mFrameBufferTextures[0], 0);

	// TODO 第四步:解绑操作
	glBindTexture(GL_TEXTURE_2D, 0);
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

3)完成绘制操作,渲染到FBO离线缓存中,顶点坐标赋值,纹理坐标赋值,变换矩阵把mtx矩阵数据传递到vMatrix,相机过滤器处理过后,其他过滤器不需要再变换矩阵,采样器赋值,通知opengl绘制,将FBO的纹理ID返回给下一个过滤器;

public int onDrawFrame(int textureId) {
	// 不能调用super,因为父类做的事情,和子类是很大区别的
	// super.onDrawFrame(textureId);
	glViewport(0, 0, mWidth, mHeight);  // 设置视窗大小

	// TODO 渲染到 FBO离线缓存中

	// 绑定FBO缓存(否则会绘制到屏幕上) 我们最终的效果是 离屏渲染
	glBindFramebuffer(GL_FRAMEBUFFER, mFrameBuffers[0]);

	glUseProgram(mProgramId); // 必须要使用着色器程序一次

	// 画画 绘制操作
	mVertexBuffer.position(0); // 顶点坐标赋值
	glVertexAttribPointer(vPosition, 2, GL_FLOAT, false, 0, mVertexBuffer); // 传值
	glEnableVertexAttribArray(vPosition); // 激活

	mTextureBuffer.position(0); // 纹理坐标赋值
	glVertexAttribPointer(vCoord, 2, GL_FLOAT, false, 0, mTextureBuffer); // 传值
	glEnableVertexAttribArray(vCoord); // 激活

	// TODO 变换矩阵,在我们CameraFilter这里就需要处理了,后面的BaseFilter就不需要了
	glUniformMatrix4fv(vMatrix, 1, false, matrix, 0);

	// 片元 vTexture
	glActiveTexture(GL_TEXTURE0); // 激活图层
	// glBindTexture(GL_TEXTURE_2D ,textureId); // 公用的那个  1
	glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId); // 摄像头打交道采样器:使用额外拓展的,不能使用公用的那个  2
	glUniform1i(vTexture, 0);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // 通知 opengl 绘制

	// 解绑 fbo
	glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	// FBO的纹理ID,返回了
	return mFrameBufferTextures[0]; // 你没有学过C,你就不明白,如果你学过,无需多言
}

4)相机过滤器顶点着色器代码:

attribute vec4 vPosition; // 顶点坐标,相当于:相机的四个点位置排版

attribute vec4 vCoord; // 纹理坐标,用来图形上色的

uniform mat4 vMatrix; // 变换矩阵,4*4的格式的

varying vec2 aCoord; // 把这个最终的计算成果,给片元着色器 【不需要Java传递,他是计算出来的】

void main() {
    gl_Position = vPosition; // 确定好位置排版

    aCoord = (vMatrix * vCoord).xy; // 兼容所有设备
}

5)相机过滤器片元着色器代码:

// 导入 samplerExternalOES 
#extension GL_OES_EGL_image_external : require

// float 数据的精度 (precision lowp = 低精度) (precision mediump = 中精度) (precision highp = 高精度)
precision mediump float;

// 根据上面的数据的精度,写下面的 采样器 相机的数据
// uniform sampler2D vTexture;  由于我们用的是 安卓的相机,就不能用他
uniform samplerExternalOES vTexture; // samplerExternalOES才能采样相机的数据

varying vec2 aCoord; // 把这个最终的计算成果,给片元着色器,拿到最终的成果,我才能上色

void main() {
    // 底片效果
    vec4 rgba = texture2D(vTexture,aCoord);  // rgba
    gl_FragColor = vec4(1.-rgba.r, 1.-rgba.g, 1.-rgba.b, rgba.a);
}

三、实现ScreenFilter屏幕过滤器

1)ScreenFilter构造方法,将顶点着色器和片元着色器代码资源文件id传给父类,完成顶点着色器、片元着色器和着色器程序的初始化工作;

public ScreenFilter(Context context) {
	super(context, R.raw.base_vertex, R.raw.base_fragment); // base_vertex(没有矩阵) base_fragment(没有OES 是sampler2D)
}

2)准备工作和绘制操作复用父类完成;

3)屏幕过滤器顶点着色器代码:

attribute vec4 vPosition; // 顶点坐标,相当于:相机的四个点位置排版

attribute vec2 vCoord; // 纹理坐标,用来图形上色的

varying vec2 aCoord; // 把这个最终的计算成果,给片元着色器 【不需要Java传递,他是计算出来的】

void main() {
    gl_Position = vPosition; // 确定好位置排版   gl_Position OpenGL着色器语言内置的变量

    // 着色器语言基础语法
    // aCoord = vCoord.xy;
    // aCoord是2个分量的    vCoord是四个分量的.xy取出两个分量
    aCoord = vCoord;
}

4)屏幕过滤器片元着色器代码

// float 数据的精度 (precision lowp = 低精度) (precision mediump = 中精度) (precision highp = 高精度)
precision mediump float;

// 根据上面的数据的精度,写下面的 采样器 相机的数据
uniform sampler2D vTexture;// 由于我们用的是 安卓的相机,就不能用他(用OpenGL 2D显示就行了,不需要摄像头了,sampler2D)

varying vec2 aCoord;// 把这个最终的计算成果,给片元着色器,拿到最终的成果,我才能上色

void main() {
    // texture2D (采样器, 坐标)  gl_FragColor OpenGL着色器语言内置的变量
    gl_FragColor = texture2D(vTexture, aCoord);// 直接上色,你直接上色,是上camera_fragment.glsl的底片效果
}

四、自定义渲染器MyGlRenderer

从上一节中我们知道,MyGlRenderer实现GLSurfaceView.Renderer接口,实现接口的onSurfaceCreated()、onSurfaceChanged()和onDrawFrame()方法;

1)当Surface创建时,回调onSurfaceCreated()函数,创建ScreenFilter屏幕过滤器,同样在创建ScreenFilter屏幕过滤器之前,我们要先创建CamreaFilter相机过滤器;

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
	mCameraFilter = new CameraFilter(myGLSurfaceView.getContext()); // 先 FBO
	mScreenFilter = new ScreenFilter(myGLSurfaceView.getContext()); // 后 渲染屏幕
}

2)当Surface改变时,回调onSurfaceChanged()函数,完成过滤器准备工作;

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
	mCameraFilter.onReady(width, height);
	mScreenFilter.onReady(width, height);
}

3)绘制一帧图像时,回调onDrawFrame()函数,获取纹理对象的图像数据,先通过CamreaFilter相机过滤器,实现相关效果,再将其FBO的纹理ID传递给下一个过滤器,这里由于暂无实现其他特效,就传递给了ScreenFilter屏幕过滤器,将最终成果的纹理ID通过OpenGL渲染到屏幕;

@Override
public void onDrawFrame(GL10 gl) {
	// 相机过滤器,绘制一帧图像,不可见
	mCameraFilter.setMatrix(mtx);
	int textureId = mCameraFilter.onDrawFrame(mTextureID[0]); // 摄像头,矩阵,都已经做了
	// 增加其他特效
	/*textureId = 美白.onDrawFrame(textureId);
	textureId = 大眼.onDrawFrame(textureId);
	textureId = xxx.onDrawFrame(textureId);*/
	// 屏幕过滤器,绘制一帧图像,屏幕显示
	mScreenFilter.onDrawFrame(textureId); // textureId == 最终成果的纹理ID
}

注:后续将会在CamreaFilter相机过滤器和ScreenFilter屏幕过滤器中间增加各种效果过滤器,实现其他特效,如:美白,大眼,兔耳朵等。

至此,OpenGL离屏渲染与工程代码整合已完成。

源码:

NdkOpenGLPlay: NDK OpenGL渲染画面效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/512628.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ 多线程编程(四) 原子类型atomic

C 11增加了原子类型atomic类&#xff0c;在一定条件下可以实现无锁编程。 1. 简介 atomic是一个模板类&#xff0c;定义如下&#xff1a; template< class T > struct atomic; atomic可以实现无锁编程&#xff0c;在效率上要比mutex高很多&#xff0c;直接看个直观的…

有道云笔记常用快捷键

F5 同步/刷新 Shift AltD 插入当前时间&#xff1a; CTRL B 加粗 CTRL I 斜体字 CTRL U 下划线 CTRL E 删除线 CTRL D 任务框 CTRL 1 变成标题1 CTRL 2 变成标题2 CTRL 3 变成标题3 CTRL 4 变成标题4 CTRL G 高亮块 CTRL H 加水平线 当前行成无序列表&a…

npm安装依赖实践总结

node下载地址&#xff1a;https://nodejs.org/en/download/releases 。可以看到node版本、npm版本、node_module版本。 【1】npm的全局安装路径 查看默认值&#xff1a; npm get prefix默认是C:\Users\你的用户名\AppData\Roaming\npm 可以通过 npm config prefix 更改全局…

为什么PCB设计完成后需要放置mark点

PCB设计中的Mark点是指一些标记点&#xff0c;通常用于促进PCB制造和组装过程中的准确性和一致性。这些标记点在制造过程中可以帮助操作员进行自动化定位&#xff0c;从而确保所有部件都被正确组装到其正确位置&#xff0c;这对于确保产品的质量和可靠性至关重要。 下面&#…

springboot抵御即跨站脚本(XSS)攻击

抵御即跨站脚本(XSS)攻击 XSS攻击通常指的是通过利用网站系统保存系统的漏洞&#xff0c;通过巧妙的方法把恶意指令注入到网页&#xff0c;用户加载网页的时候会自动执行恶意脚本 比如&#xff1a; <script>alert(xss); </script> 如果客户能在你的浏览器执行j…

C# Setting.settings . 配置用法

1、定义 在Settings.settings文件中定义配置字段。把作用范围定义为&#xff1a;User则运行时可更改(用户范围的字段数据更改存储在用户信息中&#xff0c;不在该程序文件中)&#xff0c;Applicatiion则运行时不可更改。可以使用数据网格视图(VS软件的Properties 下面的Settin…

几何深度学习 - 利用几何先验知识的深度学习

深度学习很难。 虽然通用逼近定理表明足够复杂的神经网络原则上可以逼近“任何东西”&#xff0c;但不能保证我们可以找到好的模型。 尽管如此&#xff0c;通过明智地选择模型架构&#xff0c;深度学习取得了巨大进步。 这些模型架构对归纳偏差进行编码&#xff0c;为模型提供…

makefile 条件判断语句

文章目录 前言一、条件判断语句的语法说明二、ifeq / ifneq三、ifdef / ifndef代码讲解&#xff1a; 四、经典示例总结 前言 一、条件判断语句的语法说明 makefile 中支持条件判断语句。 可以根据条件的值决定 make 的执行。可以 比较 两个不同变量或者变量和常量值。 条件判…

物理环境与网络通信安全

物理环境与网络通信安全 物理和环境安全环境安全设施安全-安全区域与边界防护传输安全 OSI七层模型ISO/OSI七层模型结构OSI模型特点OSI安全体系结构 TCP/IP协议安全TCP/IP协议结构网络接口层网络互联层核心协议-IP协议传输层协议-TCP&#xff08;传输控制协议&#xff09;传输层…

AMB300系列母线槽红外测温解决方案南沙XX养殖项目案例分享

安科瑞 耿敏花 一、 行业背景 随着当今社会的发展和用电量的急剧上升&#xff0c;现代化工程设施和装备的涌现&#xff0c;封闭式母线即母线槽因方便、节能、载流量大、机械强度高 、安装灵活、寿命长等特点&#xff0c;逐渐取代传统电缆&#xff0c;广泛应用于室内变压…

Window下载Android源码

Android 10源码下载 想要研究Android 源码的同学可以用此方法进行下载。源码从清华大学开源软件镜像站&#xff08;https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/&#xff09;下载。 使用Linux的同学直接参照清华镜像站提供的使用帮助(https://mirrors.tuna.tsinghua.edu…

面试题:react、 vue中的key有什么作用? (key的内部原理)

面试题:react、 vue中的key有什么作用? &#xff08;key的内部原理) 1.虚拟DOM中key的作用: key是虚拟DOM对象的标识&#xff0c;当状态中的数据发生变化时&#xff0c;Vue会根据【新数据】生成【新的虚拟DON】,随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较&#xff0…

如何用ChatGPT做内容营销方案和选题计划,同时生产和优化内容?

该场景对应的关键词库&#xff08;31个&#xff09;&#xff1a; 内容营销、目标、主题、类型、选题计划、素材、推广策略、优化方案、渠道、目标受众、竞争对手、行业背景、转化率、品牌知名度、客户参与度、销售、发布频率、选题阶段、生产阶段、推广阶段、预算分配、人群特征…

Python+Yolov5舰船侦测识别

程序示例精选 PythonYolov5舰船侦测识别 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<PythonYolov5舰船侦测识别>>编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c…

电子招投标采购系统源码-适合于招标代理、政府采购、企业采购、工程交易等业务的企业

招投标管理系统-适合于招标代理、政府采购、企业采购、工程交易等业务的企业 招投标管理系统是一个用于内部业务项目管理的应用平台。以项目为主线&#xff0c;从项目立项&#xff0c;资格预审&#xff0c;标书编制审核&#xff0c;招标公告&#xff0c;项目开标&#xff0c;项…

效率提升一倍,MES管理系统打造车间数字化“筋骨”

在企业生产车间管理中&#xff0c; MES系统可以实时了解现场的生产状态、任务完成情况、物料需求、质量情况等&#xff0c;从而提高企业的生产效率&#xff0c;帮助企业快速响应市场变化&#xff0c;提高产品质量&#xff0c;降低成本。 MES系统是车间制造执行系统的简称…

软考A计划-重点考点-专题十(算法分析与设计)

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

Golang每日一练(leetDay0062) BST迭代器、地下城游戏

目录 173. 二叉搜索树迭代器 Binary Search Tree Iterator &#x1f31f;&#x1f31f; 174. 地下城游戏 Dungeon Game &#x1f31f;&#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 …

全景丨0基础学习VR全景制作,平台篇第21章:热点功能-电话

大家好&#xff0c;欢迎观看蛙色VR官方——后台使用系列课程&#xff01; 功能说明 应用场景 热点&#xff0c;指在全景作品中添加各种类型图标的按钮&#xff0c;引导用户通过按钮产生更多的交互&#xff0c;增加用户的多元化体验。 电话热点&#xff0c;即手机端点击热点后可…

sentinel参数配置详细说明

使用的是sentinel-dashboard来配置 流控规则 阈值类型 QPS 当每秒并发数大于配置&#xff0c;则进行限流并发数线程数 当执行的线程数大于配置&#xff0c;则进行限流 单机阈值 配置的具体值 流控模式 直接 默认项&#xff0c;就是在资源名配置的路径进行生效 关联&#…