[OpenGL] OpenGL makes three-dimensional character rain screensaver

OpenGL makes cool character rain screensaver

  • foreword
  • OpenGL3.3+ making screen saver program
  • Apply the created program to the screen saver
  • Show results

foreword

I have long wanted to make a digital rain screensaver similar to "The Matrix". This time, I took a little time to make a digital rain with a three-dimensional effect, which is a digital rain effect with a sense of distance, not just a two-dimensional effect. This screensaver is a must-have device for home travel, →_→cool character flow, creating a sense of mystery.
The scr screensaver and exe program of this project and the released version can be downloaded from github:
https://github.com/ZeusYang/Breakout/tree/master/MatrixSaver
write picture description here

OpenGL3.3+ making screen saver program

tool

OpenGL3.3+ graphics library, glm math matrix library, glfw to create windows.

character texture

The characters in the digital rain can be implemented by freetype, or a texture image. This texture image combines all the characters to be displayed into one, and then changes the characters by changing the texture coordinates. I use the latter method. If I use freetype to implement it, I need to constantly switch textures, which is very inefficient. If I use texture images, the jaggedness caused by magnification due to the resolution of the texture is its disadvantage. The textures I used are as follows:
write picture description here

window

I use glfw to create a full-screen window, of course, you can also use win32. The code for glfw to create a full-screen window is as follows, see the source code for more details:

    //全屏显示
    GLFWmonitor *pMonitor = glfwGetPrimaryMonitor();
    const GLFWvidmode * mode = glfwGetVideoMode(pMonitor);
    SCREEN_WIDTH = mode->width;
    SCREEN_HEIGHT = mode->height;

    GLFWwindow* window = glfwCreateWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Matrix", pMonitor, nullptr);
    if (window == NULL) {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }

digital character chain

Each character chain in the digital rain is implemented by a linked list. During the falling process of the character rain, new characters are continuously generated at the head, while the tail is continuously deleted and disappeared, and the total length remains unchanged. The attributes of a single character have three attributes: position, color, and character type. Among them, we specify each character through texture coordinates, and different characters can be indexed by changing the texture coordinates.

#pragma once
#include <list>
#include <glm/glm.hpp>

struct Symbol {//单个字符
    glm::vec4 Pos;//位置、大小
    glm::vec4 Color;//颜色
    glm::vec4 Coord;//纹理坐标的移动
};

class CharList{
public:
    CharList(float zn, float zf, float ap, float fy);

    ~CharList() = default;

    void Move(float dt);//移动

    bool IsOufScreen();//判断是否飘出屏幕外

    std::list<Symbol> necklace;

    glm::vec3 pos;//列尾位置
    glm::vec3 vel;//速度方向

    int num;//字符数量

private:
    //单个字符大小、近平面、远平面、宽高比、视锥范围夹角
    float size, znear, zfar, aspect, fovy;

    void RndPos();//随机位置

    inline float Rand0_1() {//0-1的随机数
        return (double)rand() / (double)RAND_MAX;
    }
};
#include "CharList.h"

CharList::CharList(float zn, float zf, float ap, float fy)
    :size(1.0f), znear(zn), zfar(zf), aspect(ap), fovy(fy){
    //字符链的字符数量
    num = 15 + rand() % 10;
    RndPos();//随机位置
    glm::vec3 tp = pos;
    float alpha = 1.0f;
    for (auto x = 0; x < num; ++x) {//生成num个字符
        Symbol tmp;
        tmp.Color = glm::vec4(0.0, 1.0, 0.5, alpha);
        tmp.Pos = glm::vec4(tp.x, tp.y, tp.z, size);
        //随机纹理
        tmp.Coord.x = rand() % 11;
        tmp.Coord.y = rand() % 6;
        necklace.push_back(tmp);
        tp.y += size;
        alpha -= 1.0f / num;
    }
    //头部的颜色为白色
    necklace.front().Color = glm::vec4(0.8, 0.8, 0.8, 1.0);
    //速度
    vel = glm::vec3(0.0, -0.1, 0.022);
}

void CharList::Move(float dt) {
    //头部插入,删除尾部
    Symbol tmp;
    tmp.Color = glm::vec4(0.8, 0.8, 0.8, 1.0);
    tmp.Pos = glm::vec4(necklace.front().Pos.x, necklace.front().Pos.y, necklace.front().Pos.z, size);
    tmp.Pos.y -= size;
    tmp.Coord.x = rand() % 11;
    tmp.Coord.y = rand() % 6;
    necklace.front().Color = glm::vec4(0.0, 1.0, 0.5, 1.0f);
    necklace.push_front(tmp);
    necklace.pop_back();
    //修改alpha值
    float alpha = 1.0f;
    for (auto &e : necklace) {
        e.Color.w = alpha;
        alpha -= 1.0f / num;
        e.Pos.z += vel.z;
    }
    pos = necklace.front().Pos;
}

bool CharList::IsOufScreen() {
    //判断是否飘出屏幕
    double range = tan(glm::radians(fovy))*pos.z;
    float length = num * size;
    if (pos.z >= -znear || pos.z <= -this->zfar || pos.y < (range - length)){
        return true;
    }
    return false;
}

void CharList::RndPos() {
    //随机位置生成
    double range;
    pos.z = -(Rand0_1()*(zfar - znear) + znear);
    range = tan(glm::radians(fovy))*pos.z;
    pos.y = -range;
    pos.x = Rand0_1()*range * 2 * aspect - range*aspect;
}

render sprite

A character is a picture, simply writing a rendering loop to draw one by one is a method, but this method is extremely inefficient, and the number of drawcalls is very large, especially when the number is very large. For this, I use the instantiation feature of OpenGL3.3+.

class SpriteRenderer{
public:
    SpriteRenderer(Shader &shader, float untiX, float unitY);

    ~SpriteRenderer();

    //实例化
    void SetInstance(const std::vector<CharList*> &target);

     //绘制精灵,除了纹理,其余参数都没用到
    void DrawSprite(Texture2D &texture, glm::vec2 position = glm::vec2(0, 0),
        glm::vec2 size = glm::vec2(10, 10), GLfloat rotate = 0.0f,
        glm::vec3 color = glm::vec3(1.0f));

private:
    int amount;//实例化的个数
    Shader shader;
    GLuint quadVAO;
    GLuint instanceVBO;
    void initRenderData(float untiX, float unitY);
};
#include "SpriteRenderer.h"
#include <iostream>

SpriteRenderer::SpriteRenderer(Shader &shader, float unitX, float unitY)
    :shader(shader), amount(0)
{
    this->initRenderData(unitX, unitY);
}

SpriteRenderer::~SpriteRenderer() {
    glDeleteVertexArrays(1, &this->quadVAO);
    glDeleteBuffers(1, &this->instanceVBO);
}

void SpriteRenderer::initRenderData(float unitX,float unitY)
{
    // 配置 VAO/VBO
    GLuint VBO;
    GLfloat vertices[] = {
        // 位置               // 纹理
        -0.5f, 0.5f,0.0f, 0.000f,  unitX,
         0.5f,-0.5f,0.0f,  unitY, 0.000f,
        -0.5f,-0.5f,0.0f, 0.000f, 0.000f,

        -0.5f, 0.5f,0.0f, 0.000f,  unitX,
         0.5f, 0.5f,0.0f,  unitY,  unitX,
         0.5f,-0.5f,0.0f,  unitY, 0.000f
    };

    glGenVertexArrays(1, &this->quadVAO);
    glGenBuffers(1, &instanceVBO);
    glGenBuffers(1, &VBO);
    glBindVertexArray(this->quadVAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

void SpriteRenderer::SetInstance(const std::vector<CharList*> &target) {
    //获取实例矩阵
    std::vector<glm::mat4> instance;
    amount = target.size()*target[0]->necklace.size();
    instance.reserve(amount);
    for (auto it = target.begin(); it != target.end(); ++it) {
        for (auto th = (*it)->necklace.begin(); th != (*it)->necklace.end(); ++th) {
            glm::mat4 tmp(1.0f);
            tmp[0] = th->Pos;
            tmp[1] = th->Color;
            tmp[2] = th->Coord;
            tmp[3] = glm::vec4(1.0f);
            instance.push_back(tmp);
        }
    }

    GLsizei vec4Size = sizeof(glm::vec4);

    glBindVertexArray(quadVAO);
    glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
    glBufferData(GL_ARRAY_BUFFER, instance.size() * sizeof(glm::mat4), &instance[0], GL_DYNAMIC_DRAW);
    glEnableVertexAttribArray(2);
    glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)0);
    glEnableVertexAttribArray(3);
    glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(vec4Size));
    glEnableVertexAttribArray(4);
    glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(2 * vec4Size));
    glEnableVertexAttribArray(5);
    glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(3 * vec4Size));

    glVertexAttribDivisor(2, 1);
    glVertexAttribDivisor(3, 1);
    glVertexAttribDivisor(4, 1);
    glVertexAttribDivisor(5, 1);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

void SpriteRenderer::DrawSprite(Texture2D &texture, glm::vec2 position,
    glm::vec2 size, GLfloat rotate, glm::vec3 color){
    this->shader.Use();
    //绑定纹理
    glActiveTexture(GL_TEXTURE0);
    texture.Bind();
    glBindVertexArray(this->quadVAO);
    glDrawArraysInstanced(GL_TRIANGLES, 0, 6, amount);
    glBindVertexArray(0);
}

render loop

initialization:

//初始化
void Saver::Init() {
    // 加载着色器
    std::string vShader = 
        "#version 330 core\n"
        "layout(location = 0) in vec3 vertex;\n"
        "layout(location = 1) in vec2 texcoord;\n"
        "layout(location = 2) in mat4 instanceMatrix;\n"
        "out vec2 TexCoords;\n"
        "out vec4 Color;\n"
        "uniform mat4 projection;\n"
        "void main()\n"
        "{\n"
        "   //位置\n"
        "   vec3 pos = vertex + instanceMatrix[0].xyz;\n"
        "   pos *= instanceMatrix[0].w;\n"
        "   gl_Position = projection * vec4(pos, 1.0f);\n"
        "   //颜色\n"
        "   Color = instanceMatrix[1];\n"
        "   TexCoords.x = texcoord.x + 0.091f*instanceMatrix[2].x;\n"
        "   TexCoords.y = texcoord.y + 0.167f*instanceMatrix[2].y;\n"
        "}\n";

    std::string fShader =
        "#version 330 core\n"
        "in vec2 TexCoords;\n"
        "in vec4 Color;\n"
        "out vec4 color;\n"
        "uniform sampler2D image;\n"
        "void main() {\n"
        "   vec4 tex = texture(image, TexCoords);\n"
        "   color.xyz = Color.xyz;\n"
        "   color.w = (1.0 - tex.x)*Color.w;\n"
        "}\n";

    ResourceManager::LoadShaderFromString(vShader,
        fShader,  "sprite");
    /*ResourceManager::LoadShader("Sprite.vert",
        "Sprite.frag", nullptr, "sprite");*/
    // 加载纹理
    ResourceManager::LoadTexture("C:/Windows/number.png", GL_TRUE, "number");
    // 配置着色器
    glm::mat4 projection = glm::perspective(glm::radians(fovy), aspect, znear, zfar);
    ResourceManager::GetShader("sprite").Use().SetInteger("image", 0);
    ResourceManager::GetShader("sprite").SetMatrix4("projection", projection);
    //50个字符链
    for (auto x = 0; x < 200; ++x) {
        matrix.push_back(new CharList(znear, zfar, aspect, fovy));
    }
    //渲染精灵,采用实例化渲染
    sprite = std::make_shared<SpriteRenderer>(ResourceManager::GetShader("sprite"), 0.167f, 0.091f);
}

renew:

//更新
void Saver::Update(GLfloat dt) {
    timeCounter += dt;
    //移动
    if (timeCounter >= 0.10f) {
        for (auto it = matrix.begin(); it != matrix.end(); ++it) {
            (*it)->Move(dt);
        }
        timeCounter = 0;
        //检查是否飘出屏幕外
        for (auto it = matrix.begin(); it != matrix.end(); ++it) {
            if ((*it)->IsOufScreen()) {
                CharList* tmp = *it;
                *it = new CharList(znear, zfar, aspect, fovy);
                delete tmp;
            }
        }
    }
    //传入sprite实例化
    sprite->SetInstance(matrix);
}

Render:

//渲染
void Saver::Render() {
    sprite->DrawSprite(ResourceManager::GetTexture("number"));
}

Apply the created program to the screen saver

The executable program we compile and release is an exe file, and then we modify the suffix exe of the exe program to become scr, and the windows operating system automatically recognizes the scr program as a screen saver.
For the screen saver program I compiled, follow the steps below:
1. Put MatrixSaver.scr and number.png in the C:/Windows directory
2. Select MatrixSaver as the screen saver program in the corresponding screen saver selection options
Example : in win8 .1, right-click the blank part of the desktop, select "Personalization" -> Screen Saver -> select MatrixSaver in the drop-down box of the dialog box, and then "OK".

Show results

Enjoy the visual feast! (Good middle school, what a shame)
write picture description here
The scr screensaver and exe program of this project and the released version can be downloaded on github:
https://github.com/ZeusYang/Breakout/tree/master/MatrixSaver

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326292018&siteId=291194637