OpenGL 用点绘制跳动的爱心(复刻天才程序员)

第一步:确定绘制爱心的路径

网上找的绘制爱心的公式:

在笛卡儿坐标系中,心脏线的参数方程为:

x(t)=a(2cost-cos2t)。

y(t)=a(2sint-sin2t)。

这个公式只能确定二维平面上的点,不过没关系,我们可以随机确定z轴的值,这样让爱心看起来层次更丰富。

第二步:确定绘制一个完整的爱心要用多少个点

一个圆周360度,每一度都绘制一个点,这里用360个点来绘制一个完整的爱心

第三步:将点数代入公式,确定每个点的xyz坐标

第四步:绘制爱心

此时绘制的爱心是这样的:


image.png

太整齐了,不够漂亮,我们把点发散开。

第五步:将点的坐标发散

在计算每个点的坐标时,随机对每个点的x、y、z进行调整。调整后绘制的样子是这样的:


image.png

第六步:循环绘制多个爱心,使得多个爱心组成一个三维空间中有层次的大爱心

image.png

已经有雏形了,但是层次还是不够丰富,如果在边缘能加上密度更低的爱心会更漂亮

第七步:分批次确定爱心的坐标,使得每一批坐标,xyz随机偏移的程度不同

image.png

第八步:让爱心动起来

获取时间值,求sin值(这里将结果定义为positionOffset),作用在z轴上,在每次循环中通过model变换去改变定点的位置。并且每批的爱心,z轴改变的幅度不一样。此外,为了让心脏跳动看起来更真实,当positionOffset大于0时,做以下操作:

positionOffset -= positionOffset * positionOffset * 2;

最终效果:
https://www.bilibili.com/video/BV1D14y1E7Bi/?vd_source=41d5c97c05ab408a02962803ccc39182

完整代码:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#ifdef __cplusplus
extern "C" {
#endif
#include <glad/glad.h>
#ifdef __cplusplus
}
#endif
#include <GLFW/glfw3.h>
#include <iostream>
#include <glm/gtc/type_ptr.hpp>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "../../common/Camera.h"
#include "../../common/stb_image.h"

#ifdef Q_OS_MAC
#include <objc/objc.h>
#include <objc/message.h>
void setupDockClickHandler();
bool dockClickHandler(id self,SEL _cmd,...);

#endif


#ifdef DEBUG

#else
#pragma comment( linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" )
#endif // DEBUG

void processGLFWWindowSizeChanged(GLFWwindow* window, int width, int height);
void processGLFWInput(GLFWwindow *window);
void processGLFWWindowClose(GLFWwindow *window);
void processGLFWWindowFocus(GLFWwindow* window, int focused);
void processGLFWWndowIconify(GLFWwindow* window, int iconified);
void processGLFWWndowMaximize(GLFWwindow* window, int maximized);

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 800;

//"   gl_Position = pTransform * vTransform * mTransform * vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"

const char *vertexShaderSource = "#version 330 core\n"
                                 "layout (location = 0) in vec3 aPos;\n"
                                 "uniform mat4 mTransform;\n"
                                 "uniform mat4 vTransform;\n"
                                 "uniform mat4 pTransform;\n"
                                 "void main()\n"
                                 "{\n"
                                 "   gl_Position = pTransform * vTransform * mTransform * vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
                                 "}\0";

const char *fragmentShaderSource = "#version 330 core\n"
                                   "out vec4 FragColor;\n"
                                   "uniform vec4 color;\n"
                                   "void main()\n"
                                   "{\n"
                                   "   FragColor = color;\n"
                                   "}\0";


const int cubeCount = 360;
void generData(float *vertices,int loopCount,int divisor) {
    float a = 0.18f;
    for (int j = 0; j < loopCount; j++) {
        for (int i = 0; i < cubeCount; i++) {
            int randBaseX = rand() % divisor;
            int randBaseY = rand() % (int)(divisor * 1);
            int symbolX = rand() % 2 == 0 ? 1 : -1;
            int symbolY = rand() % 2 == 0 ? 1 : -1;
            float offsetX = randBaseX * 0.01f * symbolX;
            float offsetY = randBaseY * 0.01f * symbolY;

            int firstIndex = i * 3 + j * cubeCount * 3;
            float y = a * (2 * cos(i) - cos(2 * i)) + offsetY;
            y += 0.1;
            float x = a * (2 * sin(i) - sin(2 * i)) + offsetX;
            float z = offsetX * offsetY;
            //z *= z;

            //if (y > 0) {
            float fix = 0.05;
            if (abs(x) < fix) {
                float yFix = fix - abs(x);
                /*if (yFix > 0.05) {
                        yFix = 0.05;
                    }*/
                y -= yFix;
            }
            //}

            vertices[firstIndex] = x;
            vertices[firstIndex + 1] = y;
            vertices[firstIndex + 2] = z;
        }
    }
}

int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
    QGuiApplication app(argc, argv);

    //    QGuiApplication::setQuitOnLastWindowClosed(false);

    //    QQmlApplicationEngine engine;
    //    const QUrl url(QStringLiteral("qrc:/main.qml"));
    //    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
    //                     &app, [url](QObject *obj, const QUrl &objUrl) {
    //        if (!obj && url == objUrl)
    //            QCoreApplication::exit(-1);
    //    }, Qt::QueuedConnection);
    //    engine.load(url);
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif



//    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);   //不可改变大小
//    glfwWindowHint(GLFW_DECORATED, GL_FALSE);   //没有边框和标题栏
    glfwWindowHint(GLFW_AUTO_ICONIFY, GL_TRUE);   //


    // glfw window creation
    // --------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Heart", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }

    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, processGLFWWindowSizeChanged);
    glfwSetWindowCloseCallback(window,processGLFWWindowClose);
    glfwSetWindowFocusCallback(window,processGLFWWindowFocus);
    glfwSetWindowIconifyCallback(window,processGLFWWndowIconify);
    glfwSetWindowMaximizeCallback(window,processGLFWWndowMaximize);

#ifdef Q_OS_MAC
    setupDockClickHandler();
#endif


    //    glfw

    int width, height, nrChannels;
    unsigned char *data = stbi_load("/Users/xxb/git/coding/MJMediaDevelopStudy/HeartApp/logo.png", &width, &height, &nrChannels, 0);
    if (data)
    {

    }
    else
    {
        std::cout << "Failed to load texture" << std::endl;
    }
    //    stbi_image_free(data);
    GLFWimage image;
    image.width = width;
    image.height = height;
    image.pixels = data;
    glfwSetWindowIcon(window, 1, &image);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }


    const int loopCountMain = 10;
    const int verticesCountMain = cubeCount * 3 * loopCountMain;
    float verticesMain[verticesCountMain];
    generData(verticesMain, loopCountMain,7);

    const int loopCountTwo = 15;
    const int verticesCountTwo = cubeCount * 3 * loopCountTwo;
    float verticesTwo[verticesCountTwo];
    generData(verticesTwo, loopCountTwo, 12);

    const int loopCountThree = 20;
    const int verticesCountThree = cubeCount * 3 * loopCountThree;
    float verticesThree[verticesCountThree];
    generData(verticesThree, loopCountThree, 16);


    // build and compile our shader program
    // ------------------------------------
    // vertex shader
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    // check for shader compile errors
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // fragment shader
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    // check for shader compile errors
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // link shaders
    unsigned int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    // check for linking errors
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);


    unsigned int VBO[3];
    glGenBuffers(3, VBO);

    //¥¥Ω®∂•µ„ ˝◊È∂‘œÛ
    unsigned int VAO[3];
    glGenVertexArrays(3, VAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(verticesMain), verticesMain, GL_STATIC_DRAW);
    glBindVertexArray(VAO[0]);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GL_FLOAT), (void *)0);
    glEnableVertexAttribArray(0);


    glBindBuffer(GL_ARRAY_BUFFER, VBO[1]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(verticesTwo), verticesTwo, GL_STATIC_DRAW);
    glBindVertexArray(VAO[1]);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GL_FLOAT), (void *)0);
    glEnableVertexAttribArray(0);

    glBindBuffer(GL_ARRAY_BUFFER, VBO[2]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(verticesThree), verticesThree, GL_STATIC_DRAW);
    glBindVertexArray(VAO[2]);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GL_FLOAT), (void *)0);
    glEnableVertexAttribArray(0);

    //Ω‚∞Û
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    // uncomment this call to draw in wireframe polygons.
    //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    Camera camera(glm::vec3(0.0f, 0.0f, 2.0f));

    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        // input
        // -----
        processGLFWInput(window);

        // render
        // ------
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        float positionOffset = sin(glfwGetTime() * 4) * 0.4;

        GLint location = glGetUniformLocation(shaderProgram, "color");
        glUniform4f(location, 1.0f, 0.3f + positionOffset * positionOffset * positionOffset, 0.3f + positionOffset * positionOffset, 1.0f);

        if (positionOffset > 0) {
            positionOffset -= positionOffset * positionOffset * 2;
        }


        glm::mat4 mTransform = glm::mat4(1.0f);
        mTransform = glm::translate(mTransform,glm::vec3(0.0f,0.0f,-positionOffset * positionOffset * positionOffset * 2));
        location = glGetUniformLocation(shaderProgram, "mTransform");
        glUniformMatrix4fv(location, 1, false, glm::value_ptr(mTransform));

        location = glGetUniformLocation(shaderProgram,"vTransform");
        glUniformMatrix4fv(location, 1, false,glm::value_ptr(camera.GetViewMatrix()));
        //        glUniformMatrix4fv(location, 1, false,glm::value_ptr(glm::mat4(1.0f)));

        glm::mat4 pTransMatrix = glm::mat4(1.0f);
        pTransMatrix = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
        location = glGetUniformLocation(shaderProgram, "pTransform");
        glUniformMatrix4fv(location, 1, false, glm::value_ptr(pTransMatrix));

        glPointSize(2.f);
        // draw our first triangle
        glUseProgram(shaderProgram);

        glBindVertexArray(VAO[0]);
        glDrawArrays(GL_POINTS,0,verticesCountMain);


        glm::mat4 mTransformTwo = glm::mat4(1.0f);
        mTransformTwo = glm::translate(mTransformTwo, glm::vec3(0.0f, 0.0f, -positionOffset * positionOffset * positionOffset * 1.5));
        location = glGetUniformLocation(shaderProgram, "mTransform");
        glUniformMatrix4fv(location, 1, false, glm::value_ptr(mTransformTwo));
        glBindVertexArray(VAO[1]);
        glDrawArrays(GL_POINTS, 0, verticesCountTwo);

        glm::mat4 mTransformThree = glm::mat4(1.0f);
        mTransformThree = glm::translate(mTransformThree, glm::vec3(0.0f, 0.0f, -positionOffset * positionOffset * positionOffset));
        location = glGetUniformLocation(shaderProgram, "mTransform");
        glUniformMatrix4fv(location, 1, false, glm::value_ptr(mTransformThree));
        glBindVertexArray(VAO[2]);
        glDrawArrays(GL_POINTS, 0, verticesCountThree);


        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // optional: de-allocate all resources once they've outlived their purpose:
    // ------------------------------------------------------------------------
    glDeleteVertexArrays(3, VAO);
    glDeleteBuffers(3, VBO);
    glDeleteProgram(shaderProgram);

    glfwDestroyWindow(window);
    // glfw: terminate, clearing all previously allocated GLFW resources.
    // ------------------------------------------------------------------
    glfwTerminate();

    return app.exec();
}


void closeQTApp() {
    exit(0);
}

void processGLFWWindowClose(GLFWwindow* window) {
    closeQTApp();
}

void processGLFWInput(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, true);
        closeQTApp();
    }
}

void processGLFWWindowSizeChanged(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}

void processGLFWWindowFocus(GLFWwindow* window, int focused) {
    std::cout << "processGLFWWindowFocus\n" << std::endl;
}

//窗口最小化和非最小化的回调
void processGLFWWndowIconify(GLFWwindow* window, int iconified)
{
    std::cout << "processGLFWWndowIconify\n" << std::endl;
    if (iconified)
    {
        // The window was iconified
    }
    else
    {
        // The window was restored
    }
}

//窗口最大化和非最大化的回调
void processGLFWWndowMaximize(GLFWwindow* window, int maximized)
{
    std::cout << "processGLFWWndowMaximize\n" << std::endl;
    if (maximized)
    {
        // The window was maximized
    }
    else
    {
        // The window was restored
    }
}


#ifdef Q_OS_MAC

void setupDockClickHandler() {
    Class cls = objc_getClass("NSApplication");
    objc_object *appInst = objc_msgSend((objc_object*)cls, sel_registerName("sharedApplication"));

    if(appInst != NULL) {
        objc_object* delegate = objc_msgSend(appInst, sel_registerName("delegate"));
        Class delClass = (Class)objc_msgSend(delegate,  sel_registerName("class"));
        SEL shouldHandle = sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:");
        if (class_getInstanceMethod(delClass, shouldHandle)) {
            if (class_replaceMethod(delClass, shouldHandle, (IMP)dockClickHandler, "B@:"))
                qDebug() << "Registered dock click handler (replaced original method)";
            else
                qWarning() << "Failed to replace method for dock click handler";
        }
        else {
            if (class_addMethod(delClass, shouldHandle, (IMP)dockClickHandler,"B@:"))
                qDebug() << "Registered dock click handler";
            else
                qWarning() << "Failed to register dock click handler";
        }
    }
}

bool dockClickHandler(id self,SEL _cmd,...) {
    Q_UNUSED(self);
    Q_UNUSED(_cmd);


    qDebug() << "Registered dock click handler";
    //显示窗口
//    QWidget w;
//    w.show();

    //

    // Return NO (false) to suppress the default OS X actions
    return false;
}


#endif


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,706评论 4 366
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,002评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,462评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,375评论 0 216
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,763评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,849评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,033评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,768评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,490评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,734评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,204评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,566评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,227评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,137评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,934评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,926评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,774评论 2 274

推荐阅读更多精彩内容