6

OpenglEs之三角形绘制 - 思想觉悟

 1 year ago
source link: https://www.cnblogs.com/goFlyer/p/16733596.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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 <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!!!

微信扫码关注

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK