在前面我们已经在NDK层搭建好了EGL环境,也介绍了一些着色器相关的理论知识,那么这次我们就使用已经搭配的EGL绘制一个三角形吧。

在Opengl ES的世界中,无论多复杂的形状都是由点、线或三角形组成的。因此三角形的绘制在Opengl ES中相当重要,犹比武林高手的内功心法...

坐标系

在Opengl ES中有很多坐标系,今天我们首先了解一些标准化的设备坐标。

标准化设备坐标(Normalized Device Coordinates, NDC),一旦你的顶点坐标已经在顶点着色器中处理过,它们就是标准化设备坐标了,

标准化设备坐标是一个x、y和z的值都在-1.0到1.0的之间,任何落在-1和1范围外的坐标都会被丢弃/裁剪,不会显示在你的屏幕上。

如下图,在在标准化设备坐标中,假设有一个正方形的屏幕,那么屏幕中心就是坐标原点,左上角就是坐标(-1,1),右下角则是坐标(1,-1)。

上代码

这里需要说明亮点:

  1. 在后续的实战例子中,经常会复用到前面介绍的demo的代码,因此如果是复用之前的代码逻辑,为了节省篇幅,笔者就不重复贴了。
  2. 在demo中为了简洁,并没有开启子线程作为GL线程,很明显这是不对,实际开发中都应该开启子线程对Opengl进行操作。

首先为了后续方便使用,我们在Java层和C++分别创建一个BaseOpengl的基类:

BaseOpengl.java

public class BaseOpengl {

    // 三角形
public static final int DRAW_TYPE_TRIANGLE = 0; public long glNativePtr;
protected EGLHelper eglHelper;
protected int drawType; public BaseOpengl(int drawType) {
this.drawType = drawType;
this.eglHelper = new EGLHelper();
} public void surfaceCreated(Surface surface) {
eglHelper.surfaceCreated(surface);
} public void surfaceChanged(int width, int height) {
eglHelper.surfaceChanged(width,height);
} public void surfaceDestroyed() {
eglHelper.surfaceDestroyed();
} public void release(){
if(glNativePtr != 0){
n_free(glNativePtr,drawType);
glNativePtr = 0;
}
} public void onGlDraw(){
if(glNativePtr == 0){
glNativePtr = n_gl_nativeInit(eglHelper.nativePtr,drawType);
}
if(glNativePtr != 0){
n_onGlDraw(glNativePtr,drawType);
}
} // 绘制
private native void n_onGlDraw(long ptr,int drawType);
protected native long n_gl_nativeInit(long eglPtr,int drawType);
private native void n_free(long ptr,int drawType); }

下面是C++的BaseOpengl:

BaseOpengl.h

#ifndef NDK_OPENGLES_LEARN_BASEOPENGL_H
#define NDK_OPENGLES_LEARN_BASEOPENGL_H
#include "../eglhelper/EglHelper.h"
#include "GLES3/gl3.h"
#include <string> class BaseOpengl {
public:
EglHelper *eglHelper;
GLint program{0}; public:
BaseOpengl();
// 析构函数必须是虚函数
virtual ~BaseOpengl();
// 加载着色器并链接成程序
void initGlProgram(std::string ver,std::string fragment);
// 绘制
virtual void onDraw() = 0;
}; #endif //NDK_OPENGLES_LEARN_BASEOPENGL_H

注意基类的析构函数一定要是虚函数,为什么?如果不是虚函数的话则会导致无法完全析构,具体原因请大家面向搜索引擎编程。

BaseOpengl.cpp

#include "BaseOpengl.h"
#include "../utils/ShaderUtils.h" BaseOpengl::BaseOpengl() { } void BaseOpengl::initGlProgram(std::string ver, std::string fragment) {
program = createProgram(ver.c_str(),fragment.c_str());
} BaseOpengl::~BaseOpengl(){
eglHelper = nullptr;
if(program != 0){
glDeleteProgram(program);
}
}

然后使用BaseOpengl自定义一个SurfaceView,为MyGLSurfaceView:

public class MyGLSurfaceView extends SurfaceView implements SurfaceHolder.Callback {

    public BaseOpengl baseOpengl;
private OnDrawListener onDrawListener; public MyGLSurfaceView(Context context) {
this(context,null);
} public MyGLSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
getHolder().addCallback(this);
} public void setBaseOpengl(BaseOpengl baseOpengl) {
this.baseOpengl = baseOpengl;
} public void setOnDrawListener(OnDrawListener onDrawListener) {
this.onDrawListener = onDrawListener;
} @Override
public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
if(null != baseOpengl){
baseOpengl.surfaceCreated(surfaceHolder.getSurface());
}
} @Override
public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int w, int h) {
if(null != baseOpengl){
baseOpengl.surfaceChanged(w,h);
}
if(null != onDrawListener){
onDrawListener.onDrawFrame();
}
} @Override
public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
if(null != baseOpengl){
baseOpengl.surfaceDestroyed();
}
} public interface OnDrawListener{
void onDrawFrame();
}
}

有了以上基类,既然我们的目标是绘制一个三角形,那么我们在Java层和C++层再新建一个TriangleOpengl的类吧,他们都继承TriangleOpengl:

TriangleOpengl.java

public class TriangleOpengl extends BaseOpengl{

    public TriangleOpengl() {
super(BaseOpengl.DRAW_TYPE_TRIANGLE);
} }

C++ TriangleOpengl类,TriangleOpengl.h:


#ifndef NDK_OPENGLES_LEARN_TRIANGLEOPENGL_H
#define NDK_OPENGLES_LEARN_TRIANGLEOPENGL_H
#include "BaseOpengl.h" class TriangleOpengl: public BaseOpengl{
public:
TriangleOpengl();
virtual ~TriangleOpengl();
virtual void onDraw(); private:
GLint positionHandle{-1};
GLint colorHandle{-1};
}; #endif //NDK_OPENGLES_LEARN_TRIANGLEOPENGL_H

TriangleOpengl.cpp:


#include "TriangleOpengl.h"
#include "../utils/Log.h" // 定点着色器
static const char *ver = "#version 300 es\n"
"in vec4 aColor;\n"
"in vec4 aPosition;\n"
"out vec4 vColor;\n"
"void main() {\n"
" vColor = aColor;\n"
" gl_Position = aPosition;\n"
"}"; // 片元着色器
static const char *fragment = "#version 300 es\n"
"precision mediump float;\n"
"in vec4 vColor;\n"
"out vec4 fragColor;\n"
"void main() {\n"
" fragColor = vColor;\n"
"}"; // 三角形三个顶点
const static GLfloat VERTICES[] = {
0.0f,0.5f,
-0.5f,-0.5f,
0.5f,-0.5f
}; // rgba
const static GLfloat COLOR_ICES[] = {
0.0f,0.0f,1.0f,1.0f
}; TriangleOpengl::TriangleOpengl():BaseOpengl() {
initGlProgram(ver,fragment);
positionHandle = glGetAttribLocation(program,"aPosition");
colorHandle = glGetAttribLocation(program,"aColor");
LOGD("program:%d",program);
LOGD("positionHandle:%d",positionHandle);
LOGD("colorHandle:%d",colorHandle);
} TriangleOpengl::~TriangleOpengl() noexcept { } void TriangleOpengl::onDraw() {
LOGD("TriangleOpengl onDraw");
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(program);
/**
* size 几个数字表示一个点,显示是两个数字表示一个点
* normalized 是否需要归一化,不用,这里已经归一化了
* stride 步长,连续顶点之间的间隔,如果顶点直接是连续的,也可填0
*/
glVertexAttribPointer(positionHandle,2,GL_FLOAT,GL_FALSE,0,VERTICES);
// 启用顶点数据
glEnableVertexAttribArray(positionHandle); // 这个不需要glEnableVertexAttribArray
glVertexAttrib4fv(colorHandle, COLOR_ICES); glDrawArrays(GL_TRIANGLES,0,3); glUseProgram(0); // 禁用顶点
glDisableVertexAttribArray(positionHandle);
if(nullptr != eglHelper){
eglHelper->swapBuffers();
}
LOGD("TriangleOpengl onDraw--end");
}

在前面的章节中我们介绍了着色器的创建、编译、链接等,但是缺少了具体使用方式,这里我们补充说明一下。

着色器的使用只要搞懂如何传递数据给着色器中变量。首先我们需要获取到着色器程序中的变量,然后赋值。

我们看上面的TriangleOpengl.cpp的构造函数:

TriangleOpengl::TriangleOpengl():BaseOpengl() {
initGlProgram(ver,fragment);
// 获取aPosition变量
positionHandle = glGetAttribLocation(program,"aPosition");
// 获取aColor
colorHandle = glGetAttribLocation(program,"aColor");
LOGD("program:%d",program);
LOGD("positionHandle:%d",positionHandle);
LOGD("colorHandle:%d",colorHandle);
}

由上,我们通过函数glGetAttribLocation获取了变量aPosition和aColor的句柄,这里我们定义的aPosition和aColor是向量变量,如果我们定义的是uniform统一变量的话,则需要使用函数glGetUniformLocation获取统一变量句柄。

有了这些变量句柄,我们就可以通过这些变量句柄传递函数给着色器程序了,具体可参考TriangleOpengl.cpp的onDraw函数。

此外如果变量是一个统一变量(uniform)的话,则通过一系列的 glUniform...函数传递参数。

这里说明一下函数glVertexAttribPointer的stride参数,一般情况下不会用到,传递0即可,但是如果需要提高性能,例如将顶点坐标和纹理/颜色坐标等放在同一个数组中传递,则需要使用到这个stride参数了,目前顶点坐标数组和其他数组是分离的,暂时可以不管。

在Activity中调用一下测试结果:

public class DrawTriangleActivity extends AppCompatActivity {

    private TriangleOpengl mTriangleOpengl;

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_draw_triangle);
MyGLSurfaceView glSurfaceView = findViewById(R.id.my_gl_surface_view);
mTriangleOpengl = new TriangleOpengl();
glSurfaceView.setBaseOpengl(mTriangleOpengl);
glSurfaceView.setOnDrawListener(new MyGLSurfaceView.OnDrawListener() {
@Override
public void onDrawFrame() {
mTriangleOpengl.onGlDraw();
}
});
} @Override
protected void onDestroy() {
if(null != mTriangleOpengl){
mTriangleOpengl.release();
}
super.onDestroy();
}
}

如果运行起来,看到一个蓝色的三角形,则说明三角形绘制成功啦!

源码

想来还是不贴源码链接了,纸上得来终觉浅,绝知此事要躬行。很多时候就是这样,你看着觉得很简单,实际如何还得动手敲,只有在敲的过程中出了问题,然后你解决了,只是才算是你的。

在这个系列完毕后再贴出整个项目demo的代码吧。。。

往期笔记

OpenglEs之EGL环境搭建

OpenglEs之着色器

关注我,一起进步,人生不止coding!!!

OpenglEs之三角形绘制的更多相关文章

  1. 【CImg】三角形绘制算法实现

    这周的CV基础练习是简单的图形绘制:比如说矩形.三角形和圆心什么的.会发现其实矩形和圆形的实现思路都很直白,矩形只需要确认两个对角坐标就可以了,圆心只需要确认圆心和半径,接着就是简单的遍历各个像素点判 ...

  2. CSS 利用border三角形绘制方法

    CSS 三角形绘制方法,这里面的transparent比较重要,有和没有影响很大: 原理:这个div是由4个三角形组成,每个三角对应一个border,隐藏其它3个border,就可以得到一个三角形. ...

  3. OpenGL ES 3.0 点,线,三角形绘制形式总结

    OpenGL ES 3.0 顶点     -1,  1, 0, -0.5f,  0, 0,     0, -1, 0,    -1,  0, 0, 0.5f,   0, 0,     1, -1,   ...

  4. css三角形绘制

    三角形演变: 1.将一个块元素的宽.高都设置为0,再设置边框样式,得如下效果图(绿色部分): 样式: {;;border: 35px solid #7de87d;} 通过此样式得到的是一个正方形. 2 ...

  5. 3D引擎为什么使用三角形绘制曲面

    这个问题是我第一次接触3D开发就有的疑问,最近在看<游戏引擎架构>(Game Engine Architecture),在书中找到了答案. 三角网格(Triangle Mesh),游戏开发 ...

  6. CSS 三角形绘制方法

    #triangle-up {    width: 0;    height: 0;    border-left: 50px solid transparent;    border-right: 5 ...

  7. [CSS3] 各种角度的三角形绘制

    #triangle-up { width:; height:; border-left: 50px solid transparent; border-right: 50px solid transp ...

  8. Opengl ES之四边形绘制

    四边形的绘制在Opengl ES是很重要的一项技巧,比如做视频播放器时视频的渲染就需要使用到Opengl ES绘制四边形的相关知识.然而在Opengl ES却没有直接提供 绘制四边形的相关函数,那么如 ...

  9. Android OpenGL 入门示例----绘制三角形和正方形

    Android上对OpenGl的支持是无缝的,所以才有众多3D效果如此逼真的游戏,在Camera的一些流程中也有用到GLSurfaceView的情况.本文记录OpenGL在Android上的入门级示例 ...

随机推荐

  1. ShardingSphere数据库读写分离

    码农在囧途 最近这段时间来经历了太多东西,无论是个人的压力还是个人和团队失误所带来的损失,都太多,被骂了很多,也被检讨,甚至一些不方便说的东西都经历了,不过还好,一切都得到了解决,无论好坏,这对于个人 ...

  2. CF455ABoredom

    题目大意: 给你一个由 \(n\) 个整数构成的序列 \(a\),玩家可以进行几个步骤,每一步他可以选择序列中的一个元素(我们把它的值定义为 \(a_k\))并删除它,此时值等于 \(a_{k + 1 ...

  3. raspberry pi使用笔记

    1.raspberry pi系统vim问题 习惯了Linux的vim的使用,但是在raspberry pi中,用apt-get install vim后,vim变得很难用,进入编辑模式,输入字母时会莫 ...

  4. DQL条件查询模糊查询和约束概述

    模糊查询 -- 查询姓马的有哪些? like SELECT * FROM student where name LIKE '马%'; -- 查询姓名第二个字是化的人 SELECT * FROM stu ...

  5. 手把手带你实现基于 Vite+Vue3 的在线Excel表格系统

    今天,葡萄带你了解如何基于Vite+Vue3实现一套纯前端在线表格系统. 在正式开始项目介绍之前,首先咱们首先来介绍一下Vite和Vue3. Vue3 2020年09月18日Vue.js 3.0发布, ...

  6. Arm32进行远程调试

    Arm 32bit Goland 远程调试 32位支持issue Goland配置Go remote支持文档 https://mojotv.cn/go/golang-remote_debug Delv ...

  7. Apache DolphinScheduler 使用文档(6/8):任务节点类型与任务参数设置

    本文章经授权转载,原文链接: https://blog.csdn.net/MiaoSO/article/details/104770720 目录 6. 任务节点类型和参数设置 6.1 Shell节点 ...

  8. 微软Azure配置中心 App Configuration (三):配置的动态更新

    写在前面 我在前文: <微软Azure配置中心 App Configuration (一):轻松集成到Asp.Net Core>已经介绍了Asp.net Core怎么轻易的接入azure ...

  9. 【小白必看】Redis手把手教你从零开始下载到安装,再到配置允许图形化工具远程连接(一)

    一.Redis安装 本文暂时仅介绍Windows环境下Redis的安装. 由于Windows环境下没有.exe安装文件,这里我们使用"曲线救国"的.msi安装包帮助我们一站式解决安 ...

  10. Python自学笔记11-函数的定义和调用

    函数是组织代码的非常有效的方式,有了函数,我们就可以编写大规模的项目.可以说,函数是组织代码的最小单元. Python函数的定义 函数是代码封装的一种手段,函数中包含一段可以重复执行的代码,在需要用到 ...