颠倒的世界

为了开发一个新项目,Cube Art Project 采用了测试驱动开发方法。在这种方法中,首先实现对应用程序的特定功能的测试,然后再实现该特定功能。我认为这种方法的一大优点是最终接口的实现,在功能开发开始之前尽可能不涉及实现细节。通过这种方法,当接口是特定实现的契约时,测试指示进一步的实现,添加契约编程的所有好处。
立方体艺术项目–一种 3D 编辑器,用户可以在其中从立方体构建图形;不久前,这种类型非常流行。由于这是一个图形应用程序,我决定添加带有屏幕截图验证的测试。
要验证屏幕截图,您需要从 OpenGL 上下文中获取它们,这是使用 glReadPixels 函数完成的。函数参数的描述很简单–起始位置、宽度、高度、格式(RGB/RGBA/等)、指向输出缓冲区的指针;任何使用过 SDL 或具有 C 数据缓冲区经验的人都可以简单地替换必要的参数。然而,我认为有必要描述一下 glReadPixels 输出缓冲区的一个有趣的功能;像素从下到上存储在其中,而在 SDL_Surface 中,所有基本操作都是从上到下发生的。
也就是说,从 png 文件加载参考屏幕截图后,我无法直接比较两个缓冲区,因为其中一个缓冲区是颠倒的。
要从 OpenGL 翻转输出缓冲区,您需要通过减去 Y 坐标的屏幕截图高度来填充它。但是,值得考虑的是,如果在填充时不减去它,则有可能超出缓冲区限制。导致内存损坏。
由于我总是尝试使用“通过接口编程”的 OOP 范例,而不是通过指针直接进行类似 C 的内存访问,因此当我尝试在缓冲区外部写入数据时,由于方法中的边界验证,对象会通知我这一点.
自上而下的截图方法最终代码:

    auto width = params->width;
    auto height = params->height;

    auto colorComponentsCount = 3;
    GLubyte *bytes = (GLubyte *)malloc(colorComponentsCount * width * height);
    glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, bytes);

    auto screenshot = make_shared(width, height);

    for (auto y = 0; y < height; y++) {
        for (auto x = 0; x < width; x++) {
            auto byteX = x * colorComponentsCount;
            auto byteIndex = byteX + (y * (width * colorComponentsCount));
            auto redColorByte = bytes[byteIndex];
            auto greenColorByte = bytes[byteIndex + 1];
            auto blueColorByte = bytes[byteIndex + 2];
            auto color = make_shared(redColorByte, greenColorByte, blueColorByte, 255);
            screenshot->setColorAtXY(color, x, height - y - 1);
        }
    }

    free(bytes);

来源

https://community.khronos.org/ t/glreadpixels-fliped-image/26561
https://stackoverflow.com/questions/8346115/why-are-bmps-stored-upside-down

源代码

https://gitlab.com/demensdeum/cube-艺术项目引导

WebGL + SDL + Emscripten

我最终使用 SDL 1 和 Emscripten 将 Mika 移植到 WebGL。

接下来我将描述需要在代码中更改哪些内容才能成功完成 JavaScript 中的构建。

  1. 使用 SDL 1 而不是 SDL 2。目前 emscripten 有一个 SDL 2 端口,但我发现使用 emscripten 内置的 SDL 1 更合适。上下文不是在窗口中初始化的,而是使用 SDL_SetVideoMode 和 SDL_OPENGL 标志初始化的。使用 SDL_GL_SwapBuffers() 命令绘制缓冲区
  2. 由于 JavaScript 循环的方式–渲染被放置在一个单独的函数中,并使用 emscripten_set_main_loop
  3. 函数定期调用

  4. 还必须使用密钥“-s FULL_ES2=1”进行组装
  5. 我不得不放弃 assimp 库,从文件系统加载模型,并从磁盘加载纹理。所有必需的缓冲区都已加载到桌面版本上,并插入到 c 头文件中,以便使用 emscripten 进行组装。

代码:
https://github.com/demensdeum/OpenGLES3-Experiments/tree/master/9-sdl-gles-obj-textured-assimp-miku-webgl/mikuWebGL

文章:
http://blog.scottlogic.com/2014/03/12/native-code-emscripten-webgl-simmer-gently.html
https://kripken.github.io/emscripten-site/docs/porting/multimedia_and_graphics/OpenGL-support.html

型号:
https://sketchfab.com/models/7310aaeb8370428e966bdcff414273e7

只有美九一个人

使用 OpenGL ES 和代码处理 FSGL 库的结果:

接下来我将描述它是如何编程的,以及如何解决各种有趣问题。

首先,我们将初始化 OpenGL ES 上下文,正如我在上一篇文章中所写的那样。此外,我们将仅考虑渲染和代码的简要描述。

黑客帝国正在注视着你

视频中 Miku 的这个形象由三角形组成。要在 OpenGL 中绘制三角形,需要指定坐标为 x、y、z 的三个点。在 OpenGL 上下文的 2D 坐标中。
由于我们需要绘制包含 3D 坐标的图形,因此需要使用投影矩阵。我们还需要旋转、放大或任何我们想要对模型执行的操作——为此,使用了模型矩阵。 OpenGL 中没有相机的概念;事实上,对象围绕静态相机旋转。为此,使用了视图矩阵

简化 OpenGL ES 的实现它不包含矩阵数据。您可以使用添加缺失功能的库,例如 GLM

着色器

为了允许开发人员以任何方式绘制任何内容,OpenGL ES 必须实现顶点和片段着色器。顶点着色器必须接收渲染坐标作为输入,使用矩阵执行转换,并将坐标传递给 gl_Position。片段或像素着色器 –已经绘制颜色/纹理、应用叠加等。

我用 GLSL 编写了着色器。在我当前的实现中,着色器作为 C 字符串直接内置到主应用程序代码中。

缓冲区

顶点缓冲区包含顶点(vertices)的坐标;该缓冲区还包含纹理的坐标以及着色器所需的其他数据。生成顶点缓冲区后,需要将指针绑定到顶点着色器的数据。这是通过 glVertexAttribPointer 命令完成的,您需要在其中指定元素数量、指向数据开头的指针以及用于遍历缓冲区的步长。在我的实现中,完成了像素着色器的顶点坐标和纹理坐标的绑定。不过,值得一提的是,数据(纹理坐标)到片段着色器的传输是通过顶点着色器进行的。为了实现这一点,使用variing声明坐标。

这样OpenGL就知道以什么顺序绘制三角形的点–您将需要一个索引缓冲区(索引)。索引缓冲区包含数组中的顶点编号;使用三个这样的索引,可以获得一个三角形。

纹理

首先您需要加载/生成 OpenGL 纹理。为此,我使用了 SDL_LoadBMP,纹理是从 bmp 文件加载的。不过,值得注意的是,只有 24 位 BMP 适合,并且其中的颜色不是按通常的 RGB 顺序存储,而是以 BGR 存储。即加载后需要将红色通道替换为蓝色通道
纹理坐标以以下格式指定: UV,即只需要传递两个坐标即可。纹理输出在片段着色器中完成。为此,您需要将纹理绑定到片段着色器中。

没什么多余的

因为,根据我们的指示,OpenGL 通过 2D 绘制 3D ——然后实现深度,并选择不可见的三角形–您需要使用剔除和深度缓冲区(Z-Buffer)。在我的实现中,我设法避免使用两个命令手动生成深度缓冲区:glEnable(GL_DEPTH_TEST);和选择 glEnable(GL_CULL_FACE);
另外一定要检查投影矩阵的近平面是否大于零,因为使用近平面的空值检查深度将不起作用。

渲染

要使用有意识的东西填充顶点缓冲区、索引缓冲区,例如 Miku 模型,您需要加载此模型。为此,我使用了 assimp 库。 Miku被放置在Wavefront OBJ格式文件中,使用assimp加载,并实现了assimp到顶点和索引缓冲区的数据转换。

渲染分几个阶段进行:

  1. 使用模型矩阵旋转来旋转 Miku
  2. 清除屏幕和深度缓冲区
  3. 使用 glDrawElements 命令绘制三角形。

下一阶段–使用 Emscripten 在 WebGL 中实现渲染。

源代码:
https://github.com/demensdeum/OpenGLES3-Experiments/tree/master/8-sdl-gles-obj-textured-assimp-miku
型号:
https://sketchfab.com/models/7310aaeb8370428e966bdcff414273e7

 

投影它

在 3D 中绘制了一个红色茶壶后,我认为我有责任简要描述它是如何完成的。

现代OpenGL不绘制3D,它只在2D屏幕坐标中绘制三角形、点等。
要使用 OpenGL 至少输出一些内容,您需要提供一个顶点缓冲区,编写一个顶点着色器,将所有必要的矩阵(投影、模型、视图)添加到顶点着色器,将所有输入数据与着色器,调用OpenGL中的渲染方法。是不是看起来很简单?


好的,什么是顶点缓冲区?要绘制的坐标列表(x,y,z)
顶点着色器告诉 GPU 要绘制什么坐标。
像素着色器告诉绘制什么(颜色、纹理、混合等)
矩阵将 3D 坐标转换为可以渲染的 2D OpenGL 坐标

在接下来的文章中,我将提供代码示例和结果。

SDL2 – OpenGL ES

I love Panda3D game engine. But right now this engine is very hard to compile and debug on Microsoft Windows operation system. So as I said some time ago, I begin to develop my own graphics library. Right now it’s based on OpenGL ES and SDL2.
In this article I am going to tell how to initialize OpenGL ES context and how SDL2 helps in this task. We are going to show nothing.

King Nothing

First of all you need to install OpenGL ES3 – GLES 3 libraries. This operation is platform dependant, for Ubuntu Linux you can just type sudo apt-get install libgles2-mesa-dev. To work with OpenGL you need to initialize OpenGL context. There is many ways to do that, by using one of libraries – SDL2, GLFW, GLFM etc. Actually there is no one right way to initialize OpenGL context, but I chose SDL2 because it’s cross-platform solution, code will look same for Windows/*nix/HTML5/iOS/Android/etc.

To install sdl2 on Ubuntu use this command sudo apt-get install libsdl2-dev

So here is OpenGL context initialization code with SDL2:

    SDL_Window *window = SDL_CreateWindow(
            "SDL2 - OGLES",
            SDL_WINDOWPOS_UNDEFINED,
            SDL_WINDOWPOS_UNDEFINED,
            640,
            480,
            SDL_WINDOW_OPENGL
            );
	    

    SDL_GLContext glContext = SDL_GL_CreateContext(window);

After that, you can use any OpenGL calls in that context.

Here is example code for this article:
https://github.com/demensdeum/OpenGLES3-Experiments/tree/master/3sdl-gles
https://github.com/demensdeum/OpenGLES3-Experiments/blob/master/3sdl-gles/sdlgles.cpp

You can build and test it with command cmake . && make && ./SDLGles