Android音视频开发-OpenGL ES正交投影实现方法

news2024/9/20 17:19:21

本文实例为大家分享了OpenGL ES正交投影展示的具体代码,供大家参考,具体内容如下

绘制正方形

在最开始绘制的六边形里面好像看起来挺容易的,也没有出现什么问题,接下来不妨忘记前面绘制六边形的代码,让我们按照自己的理解来绘制一个简单的正方形。

按照我的理解,要想在屏幕中间显示一个正方形,效果如下图所示

image.png

应该创建的数据如下图所示

image.png

即传给渲染管线的顶点数据如下图:

float[] vertexArray = new float[] {
   (float) -0.5, (float) -0.5, 0,
   (float) 0.5, (float) -0.5, 0,
   (float) -0.5, (float) 0.5, 0,
   (float) 0.5, (float) 0.5, 0
  };

于是代码大概是这样子的,这里省略掉与主题无关的代码,颜色用纯色填充,因此在片元着色器中指定颜色,也省略掉一系列矩阵变换。顶点着色器中直接将顶点传给渲染管线,片元着色器中给片元设置固定颜色红色。

Rectangle.java

public class Rectangle {
 private FloatBuffer mVertexBuffer;
 private int mProgram;
 private int mPositionHandle;

 public Rectangle(float r) {
  initVetexData(r);
 }

 public void initVetexData(float r) {
  // 初始化顶点坐标
  float[] vertexArray = new float[] {
   (float) -0.5, (float) -0.5, 0,
   (float) 0.5, (float) -0.5, 0,
   (float) -0.5, (float) 0.5, 0,
   (float) 0.5, (float) 0.5, 0
  };

  ByteBuffer buffer = ByteBuffer.allocateDirect(vertexArray.length * 4);
  buffer.order(ByteOrder.nativeOrder());
  mVertexBuffer = buffer.asFloatBuffer();
  mVertexBuffer.put(vertexArray);
  mVertexBuffer.position(0);

  int vertexShader = loaderShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
  int fragmentShader = loaderShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);

  mProgram = GLES20.glCreateProgram();
  GLES20.glAttachShader(mProgram, vertexShader);
  GLES20.glAttachShader(mProgram, fragmentShader);
  GLES20.glLinkProgram(mProgram);

  mPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
 }

 public void draw() {
  GLES20.glUseProgram(mProgram);
  // 将顶点数据传递到管线,顶点着色器
  GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false, 0, mVertexBuffer);
  GLES20.glEnableVertexAttribArray(mPositionHandle);
  // 绘制图元
  GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
 }

 private int loaderShader(int type, String shaderCode) {
  int shader = GLES20.glCreateShader(type);
  GLES20.glShaderSource(shader, shaderCode);
  GLES20.glCompileShader(shader);
  return shader;
 }

 private String vertexShaderCode = "attribute vec3 aPosition;"
   + "void main(){"
   + "gl_Position = vec4(aPosition,1);"
   + "}";

 private String fragmentShaderCode = "precision mediump float;"
   + "void main(){"
   + "gl_FragColor = vec4(1,0,0,0);"
   + "}";

}

RectangleView.java

public class RectangleView extends GLSurfaceView{

 public RectangleView(Context context) {
  super(context);
  setEGLContextClientVersion(2);
  setRenderer(new MyRender());
 }

 class MyRender implements GLSurfaceView.Renderer {
  private Rectangle rectangle;

  @Override
  public void onSurfaceCreated(GL10 gl, EGLConfig config) {
   GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1);
   rectangle = new Rectangle(0.5f);
   GLES20.glEnable(GLES20.GL_DEPTH_TEST);
  }

  @Override
  public void onSurfaceChanged(GL10 gl, int width, int height) {
   GLES20.glViewport(0, 0, width, height);
  }

  @Override
  public void onDrawFrame(GL10 gl) {
   GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
   rectangle.draw();
  }
 }

}

然后出来的效果是这样子的,实际上屏幕上的坐标并不是这样子的,后面可以知道上面画的这个样子其实只是一个归一化的设备坐标。归一化设备坐标可以通过公式映射到实际的手机屏幕,后面会学到。

image.png

咦,实际效果好像和想象中的不太一样呀。我的本意是显示一个正方形,但实际上现实的却是一个矩形了,y轴上被拉伸了,并且横屏状态下也是类似的情况。但比较巧的是,如果以屏幕中心做一个坐标轴,就会发现,这个矩形的四个顶点在这个坐标轴x、y范围为[-1,1]的中间。

实际上,要显示的所有物体映射到手机屏幕上,都是要映射到x、y、z轴上的[-1,1]范围内,这个范围内的坐标称为归一化设备坐标,独立于屏幕的实际尺寸和形状。

因此按照这样的规定,我们要创建一个正方形就非常困难了,因为要创建正方形就必须考虑手机的宽高比,传入数据的时候就比较复杂了:不能仅仅站在要绘制物体的自身角度来看了。也就是说,上面的例子中要绘制一个正方形,传入的顶点数据的y坐标要按照比例进行一点转换,比如对16:9的屏幕,将上面传入的顶点数据的y坐标都乘以9/16即可。但同时会发现当处于横屏时,又要处理传入的x坐标的值,显然这不是一个好的方案。

引入投影

实际上,对于一个物体来说它有它自身的坐标,这个空间称为物体空间,也就是设计物体的时候采用的一个坐标空间,物体的几何中心在坐标原点上,归一化后坐标范围在[-1,1]之间,x和y轴分度是一致的。

将在这个空间的物体直接往手机屏幕的归一化坐标绘制时,由于屏幕的宽高比的问题,就会出现和预料结果不一样。所以只需要对物体空间的坐标做一个映射即可。

正交投影就是为了解决这个问题的,

public static void orthoM(float[] m, int mOffset,
  float left, float right, float bottom, float top,
  float near, float far)

正交投影背后的数学

orthoM函数产生的矩阵会把所有的左右之间、上下之间,远近之间的点映射到归一化设备坐标中。

各参数的含义如图所示

image.png

正交投影是一种平行投影,投影线是平行的,其视景体是一个长方体,坐标位于视景体中的物体才有效,视景体里面的物体投影到近平面上的部分最终会显示到屏幕的视口中,关于视口后面会降到。

会产生下面的矩阵,z轴的负值会反转z坐标,这是因为归一化设备坐标是左手系统,而OpenGL ES中的坐标系统都是右手系统,这里还涉及到顶点坐标的w分量,目前暂时用不到。

image.png

利用矩阵的就可以将物体空间[-1,1]之间的坐标映射到屏幕归一化设备坐标的[-1,1]之间。归一化屏幕坐标是右手坐标系统,原点在屏幕正中心,向右为x轴正方向,向上为y轴正方向,z轴垂直屏幕向外。以竖屏为例,比如设置left=-1,right=1,bottom=-hight/width,top=hight/width,比如我的手机分辨率为1920*1080 =1.8 对上面的正方形点(0.5,0.5)坐标而言经过变化就成了(0.5,0.3)

image.png

在屏幕的归一化设备坐标中来看就是一个正方形了,因为y轴范围显然比x轴大,0.3对应的实际长度和x轴的0.5长度是一样的。

上面的代码需要做如下修改,在onSurfaceChanged里面增加如下代码

@Override
  public void onSurfaceChanged(GL10 gl, int width, int height) {
   GLES20.glViewport(0, 0, width, height);
   // 根据屏幕方向设置投影矩阵
   float ratio= width   height ? (float)width / height : (float)height / width;
   if (width   height) {
    // 横屏 
    Matrix.orthoM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 0, 5);
   } else {
    Matrix.orthoM(mProjectionMatrix, 0, -1, 1, -ratio, ratio, 0, 5);
   }
  }

接着在顶点着色器中对顶点乘以投影矩阵

private String vertexShaderCode = "uniform mat4 uProjectionMatrix;" // 增加这一行
   + "attribute vec3 aPosition;"
   + "void main(){"
   + "gl_Position = uProjectionMatrix * vec4(aPosition,1);" // 不是直接赋值而是乘以投影矩阵
   + "}";

最后增加获取着色器中uProjectionMatrix以及传入值的代码部分即可。最终的效果不论横屏还是竖屏,显示的都是我们期望的正方形。

摄像机设置

需要补充的是,上面的参数near、far的含义指的是和视点的距离,视点貌似到目前还未接触到,它指的是摄像机的位置,和实际生活中用相机看物体一样,从不同的角度和位置拍摄同一个物体获得的照片肯定是不一样的,摄像机位置用setLookAtM函数指定。

 public static void setLookAtM(float[] rm, // 生成的摄像机矩阵
        int rmOffset,
   float eyeX, float eyeY, float eyeZ, // 摄像机的位置
   float centerX, float centerY, float centerZ, // 观察目标点的位置
               // 摄像机位置和观察目标点的位置确定了观察方向
   float upX, float upY,float upZ // up向量在x、y、z轴上的分量,我觉得一般应该是和观察方向垂直的
        )

前面提到的确定的视景体就和上面函数指定的摄像机位置和观察方向有关。摄像机默认位置在(0,0,0)处,在上面的设置下,如果将改正方形沿z轴正方向平移1个单位,屏幕上就显示不了,因为已经跑到了设置的视景体外面了。

关于摄像机的参数和投影near和far参数的设置需要注意,肯定不是胡乱设置的!摄像机的位置、方向和投影矩阵定义的视景体最终确定了视景体的位置,如果设置不当就会导致物体没有显示在屏幕上,因为物体的坐标可能位于视景体外面。

视口

前面说过在视景体中的物体最终会投影到近平面上,最终显示到视口上,正如前面在onSurfaceChanged设置的那样。

public static native void glViewport(
  int x,
  int y,
  int width,
  int height
 );

视口中各参数的含义

image.png

视口用的屏幕坐标系原点并不在屏幕左上角而是在左下角,x轴向右,y轴向上。其实还不是很准确,准确的说,视口的坐标原点位于该View的左下角,因为GLSurfaceView并不总是占据整个屏幕的。

以上就是本文的全部内容,希望对大家的学习有所帮助。

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

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

相关文章

设计模式——组件协作模式之模板方法模式

文章目录 前言一、“组件协作” 模式二、模板方法模式1、动机2、源码分析讲解①、结构化软件设计②、面向对象软件设计 三、模板方法模式定义四、结构要点总结 前言 一、“组件协作” 模式 现代软件专业分工之后的第一个结果是 “框架与应用程序的划分”,“组件协作…

部署LVS-NAT群集实验

一、 实验准备 负载调度器:内网关 ens33:192.168.109.12,外网关 ens37:12.0.0.1外网 Web节点服务器1:192.168.109.13 Web节点服务器2:192.168.109.14 NFS服务器:192.168.109.11 客户端&#xf…

C#基础学习--其他主题

目录 概述 字符串 使用StringBuilder类 把字符串解析为数据值 关于可空类型的更多内容 为可空类型赋值 使用空接合运算符 Main方法 文档注释 嵌套类型 析构函数和dispose模式 概述 再本章中会讲解一些重要的杂项知识 字符串 字符串是Unicode字符串数组 字符串是不可…

ISO-27145故障诊断说明

ISO-27145故障诊断说明 2.1 27145目录说明 ISO27145-1: 这里边介绍的是一般信息和用例定义; ISO27145-2: 这里边介绍的是与排放相关的通用数据规则,用于查询; ISO27145-3: 这里边主要介绍了支持的服务 12服务 14服务 19服务 22服务 31服务&…

【移动端网页布局】移动端网页布局基础概念 ⑧ ( 移动端页面布局方案 | 单独制作的移动端页面 - 主流 | 响应式页面兼容移动端 - 开发难度较大 )

文章目录 一、移动端页面布局方案1、单独制作的移动端页面2、响应式页面兼容移动端 一、移动端页面布局方案 移动端页面方案 : 单独制作的移动端页面 : 主流开发方案 , PC 端 与 移动端 访问的是不同的页面 , 目前的 京东 / 淘宝 等电商网站移动端页面采取的该方案 ;响应式页面…

高效编程----VSCode+ChatGPT插件

VSCode中使用ChatGPT插件 首先在VSCode中打开扩展面板,搜索ChatGPT,安装蓝色圈出插件,如图所示: 安装完成后,需要重启VSCode 注册账号,如图所示: 然后在ChatGPT对话框中输入信息即可使用&#…

[oeasy]python0137_相加运算_python之禅_import_this_显式转化

变量类型 相加运算 回忆上次内容 上次讲了是从键盘输入变量input 函数 可以有提示字符串需要有具体的变量接收输入的字符串 输入单个变量没有问题 但是输入两个变量之后一相加就非常离谱 怎么办呢?🤔 基本实验 回到 游乐场 做个 实验 两个整数 相加…

C++STL——map与set的模拟实现

map与set的模拟实现 map与set的部分源码参考改造红黑树红黑树的迭代器补全set与map的实现完整代码 map与set的部分源码参考 map和set的底层都是由红黑树实现的。 所以这里将上次实现的红黑树插入拿来用。 首先想一想,搜索二叉树不能修改值,因为会破坏整…

第十章 装饰者模式

文章目录 前言一、装饰者模式定义装饰者模式 UML图 二、装饰者模式解决星巴克咖啡订单完整代码Drink 抽象 饮料类Coffee 咖啡类继承 Drink 做一个缓冲层Espresso 意大利咖啡 继承 CoffeeLongBlack 咖啡ShortBlack 咖啡装饰者,调料牛奶巧克力豆浆咖啡店测试程序添加 …

01-yolo算法

要点: 归纳 YOLOv5 github 1 YOLO v1 1) 将一幅图像分成SxS个网格(grid cell),如果某个object的中心 落在这个网格中,则这个网格就负责预测这个object。 2)每个网格要预测B个bounding box,每个bounding box 除了要预测位置之…

TortoiseSVN使用-合并分支代码

文章目录 3.4.12 合并分支代码TortoiseSVN有2种合并方式演示场景1:(合并一个版本范围 Merge a range of revisions)演示场景2:(合并两个不同的树 Merge two different trees),不设置主分支版本演…

初窥Edubuntu 23.04:装有教育软件的Ubuntu桌面

导读4月20日,Edubuntu将作为Ubuntu官方口味卷土重来,作为即将发布的Ubuntu 23.04(Lunar Lobster)的一部分,所以我认为让你们先看看这个重制版中包含的内容是个好主意。 Edubuntu以前被称为Ubuntu教育版,最…

Anaconda安装及tensorflow安装

1.下载Anaconda安装包,并安装好 官网下载网址:https://www.anaconda.com/download/ 清华映像站: https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ 注意:注意安装anaconda时一定要把环境变量加入windows环境中。若没有…

C语言---函数介绍详解

生活的全部意义在于无穷地探索尚未知道的东西,在于不断地增加更多的知识——左拉 文章目录 前言函数模块化程序设计方法函数的定义函数的分类函数定义角度库函数自定义函数 函数形式角度无参函数有参函数 函数兼有其他语言中的函数和过程两种功能的角度有返回值函数无返回值函…

ENVI 国产高分2号(GF-2)卫星数据辐射定标 大气校正 影像融合

1.数据 高分2号卫星数据,包含: MSS-1\2多光谱数据,4m分辨率; Pan-1\2全色波段数据,0.8m分辨率。 2.处理软件 ENVI5.3 国产插件下载地址:ENVI App Store (geoscene.cn) 首先下载插件文件; …

Spring的事务传播行为

事务传播行为 多个声明的事务的方法在相互调用的时候,这个是事务应该如何去传递。 比如说methodA()调用methodB(), 那么这两个方法都显示了开启了事务,那么methodB()是开启一个新的事务还是继续在methodA()这个事务里面去执行就取决于所谓的事务传播的…

多兴趣推荐召回模型:ComiRec

前言 多兴趣向量召回系列: 通过Youtube DNN推荐模型来理解推荐流程 多兴趣召回模型:MIND 推荐系统可以表达为序列推荐问题的形式,序列推荐任务是通过用户的历史行为来预测用户下一个感兴趣的item,这也与真实场景的推荐场景是符…

Linux 通过Chrony实现NTP

Linux实现NTP服务器时间同步,可以通过ntp服务实现,也可以通过chrony服务实现 两者区别主要有 Chrony运行于UDP的323端口,NTP运行于UDP的123端口 Chrony相比于NTP可以更快同步,能够最大同步的减少时间和频率的误差 Chrony能够更好…

linux安装harbor ,搭建镜像私服

linux安装harbor ,搭建镜像私服 前提 环境中要有 docker 和 docker-compose 2、下载 harbor 的 .tgz 安装包 官网地址: Releases goharbor/harbor GitHub 第一个是离线安装包,第二个是在线安装包;带asc后缀的文件就是校验…

ROS学习第三十二节——xacro构建激光雷达小车

https://download.csdn.net/download/qq_45685327/87718396 在前面小车底盘基础之上&#xff0c;添加摄像头和雷达传感器。 0.底盘实现 deamo02_base.xacro <!--使用 xacro 优化 URDF 版的小车底盘实现&#xff1a;实现思路:1.将一些常量、变量封装为 xacro:property比如…