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閱讀 227,702評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內容