第一步:确定绘制爱心的路径
网上找的绘制爱心的公式:
在笛卡儿坐标系中,心脏线的参数方程为:
x(t)=a(2cost-cos2t)。
y(t)=a(2sint-sin2t)。
这个公式只能确定二维平面上的点,不过没关系,我们可以随机确定z轴的值,这样让爱心看起来层次更丰富。
第二步:确定绘制一个完整的爱心要用多少个点
一个圆周360度,每一度都绘制一个点,这里用360个点来绘制一个完整的爱心
第三步:将点数代入公式,确定每个点的xyz坐标
第四步:绘制爱心
此时绘制的爱心是这样的:
太整齐了,不够漂亮,我们把点发散开。
第五步:将点的坐标发散
在计算每个点的坐标时,随机对每个点的x、y、z进行调整。调整后绘制的样子是这样的:
第六步:循环绘制多个爱心,使得多个爱心组成一个三维空间中有层次的大爱心
已经有雏形了,但是层次还是不够丰富,如果在边缘能加上密度更低的爱心会更漂亮
第七步:分批次确定爱心的坐标,使得每一批坐标,xyz随机偏移的程度不同
第八步:让爱心动起来
获取时间值,求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