• OpenglEs之三角形绘制


    在前面我们已经在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 
    
    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!!!
    微信扫码关注

  • 相关阅读:
    网安须知|什么是护网行动?什么是红蓝对抗?
    Python——format格式输出
    记录一次在欧拉(openEuler22.03LTS-SP4)系统下安装(踩坑)Freeswitch1.10.11的全过程
    SpringCloud微服务实战——搭建企业级开发框架(四十四):【微服务监控告警实现方式一】使用Actuator + Spring Boot Admin实现简单的微服务监控告警系统
    torch.argmax()函数用法
    GO语言篇之反射
    CSP 202112-1 序列查询
    计算机毕业设计微信小程序开发项目源代码ssm社区疫情防控+后台管理系统|前后分离VUE[包运行成功]
    [ML从入门到入门] 支持向量机:从SVM的推导过程到SMO的收敛性讨论
    uni-app:引用文件的方法
  • 原文地址:https://www.cnblogs.com/goFlyer/p/16733596.html