尝试利用OpenGL绘制一些可爱的表情包。
一二布布是较为流行的一套萌系表情包。我选择绘制一二布布系列的一张表情包。这张表情包描绘了趴在窗户上的一只小棕熊布布。
表情包印在手机壳的样例如下:
头的绘制。布布的头并不是一个简单的椭圆形。它上窄下宽,并不关于x轴对称。并且它在和
方向比椭圆更加突出,本质上更加贴近圆角矩形。因此,无法用椭圆拼凑出布布的头。
利用desmos函数工具,寻找对应的函数。利用公式绘制出布布的头。
此数学函数并不在预设中,因此另外编写函数绘制蚶线函数图。
椭圆形手臂也用类似的方法计算。求任意方向椭圆顶点位置的代码为:
for (int i = 0; i < ELLIPSE_NUM_POINTS; i++) {
double sita = i * 2 * M_PI / ELLIPSE_NUM_POINTS;//寮у害
double r = 1.0/(1-a*cos(sita-b));//妯¢暱
double x;
if(type==0)
x = -0.3* scale * r * cos(sita);
else if(type==1)
x = 0.3*scale * r * cos(sita);
double y = 0.3*scale* r * sin(sita);
conic_vertices[i] = glm::vec2(x, y)+center;
conic_colors[i] = color;
}
使用圆锥曲线的极坐标方程计算顶点位置。
本图像由35个椭圆、蚶线图像组合而成。生成这么多图形,我们不可能每个图形都复制一遍生成点、链接、绘制的代码。因此我们把生成椭圆和生成蚶线的代码分别提取出来,抽离成一个函数。这样做使得代码更加简洁且易于维护。在生成椭圆时也更加简单,无需一处处修改代码,只需要在调用函数时修改位置、大小、颜色、离心率等数值即可。
布布趴在玻璃上,额头、下巴、手和脸颊都压扁在玻璃上,产生白色的印记。额头和下巴印记的图像也不是标准的椭圆。因此我们修改函数,给生成椭圆的函数增加一个参数type。如果type=0,则生成正常椭圆,type=1,则生成变体椭圆。变体椭圆对y坐标进行进一步处理。将所有顶点y坐标乘以一个值,离y轴越近,除的越多,离y轴越远除的越少,以此达到把椭圆进一步拍扁,形成近似圆角矩形的效果。
图像的叠放次序是十分重要的。本图像中35个图形的叠放形成了最终结果。
边框的绘制方法是:先绘制一个深色图形,再绘制一个稍小的浅色图形遮盖深色图形的大部分面积。深色图形未被遮盖的部分即为深色边框。
耳朵的绘制方法是:绘制一个深色椭圆,原位绘制一个浅色椭圆,然后在靠近头的部分绘制一个深色椭圆。每个耳朵由三个椭圆拼接而成,三个椭圆绘制完毕后,绘制头部的蚶线图像,把这些椭圆的一部分遮掉,露出的部分就是耳朵。
嘴巴的绘制方法是:绘制两个深色椭圆,然后在上方绘制数个浅色椭圆遮掉部分深色。露出的部分形成微笑的嘴部表情。
眼角挤扁细节的绘制方法是:绘制一个深色椭圆,然后在外侧绘制一个浅色椭圆把深色椭圆的大部分遮盖掉,露出部分剩余一段弧形。注意,需要先绘制眼角纹理再绘制脸颊挤在玻璃上的腮红。否则腮红会被棕色椭圆遮盖掉。
手和身体的绘制方法:先绘制脸和身体的轮廓,然后画上腮部色块,然后画上脸。然后画上双手轮廓和双手棕色部分,这样子形成手的椭圆就会遮盖掉形成脸的蚶线图像,形成手抱住脸的形态。绘制完双手后再绘制身体的棕色部分,这个棕色椭圆形可以遮掉脸部的下侧棕色边框和双手的内侧棕色边框。合理的叠放次序造就了可爱的布布。
最终的绘制结果:
全部代码:
#include "Angel.h"
#include <string>
const glm::vec3 WHITE(1.0, 1.0, 1.0);
const glm::vec3 BLACK(0.0, 0.0, 0.0);
const glm::vec3 RED(1.0, 0.0, 0.0);
const glm::vec3 GREEN(0.0, 1.0, 0.0);
const glm::vec3 BLUE(0.0, 0.0, 1.0);
const int CIRCLE_NUM_POINTS = 100;
const int ELLIPSE_NUM_POINTS = 100;
const int TRIANGLE_NUM_POINTS = 3;
const int SQUARE_NUM = 6;
const int SQUARE_NUM_POINTS = 4 * SQUARE_NUM;
const int FUNCTION_1_NUM_POINTS = 2000;
// 每当窗口改变大小,GLFW会调用这个函数并填充相应的参数\\
] ;ODT
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
// 根据角度生成颜色
float generateAngleColor(double angle)
{
return 1.0 / (2 * M_PI) * angle;
}
// 计算椭圆/圆上的点
glm::vec2 getEllipseVertex(glm::vec2 center, double scale, double verticalScale, double angle,int type)//type==1生成变体
{
glm::vec2 vertex(sin(angle), cos(angle));
vertex *= scale;
vertex.y /= verticalScale;
if(type==1)
vertex.y /= (1.25 - abs(vertex.x));
vertex += center;
return vertex;
}
//计算部分椭圆的点
// 获得椭圆/圆的每个顶点
void generateEllipsePoints(glm::vec2 vertices[], glm::vec3 colors[], int startVertexIndex, int numPoints,
glm::vec2 center, double scale, double verticalScale,glm::vec3 color,int type)//开始和结束的弧度
{
double angleIncrement = (2 * M_PI) / numPoints;
double currentAngle = M_PI/2;
for (int i = startVertexIndex; i < startVertexIndex + numPoints; ++i) {
vertices[i] = getEllipseVertex(center, scale, verticalScale, currentAngle,type);
if (verticalScale == 1.0) {//圆
colors[i] = glm::vec3(generateAngleColor(currentAngle), 0.0, 0.0);
} else {
colors[i] = color;
}
currentAngle += angleIncrement;
}
}
//获得部分椭圆的点(没什么用)
void generatePartEllipsePoints(glm::vec2 vertices[], glm::vec3 colors[], int startVertexIndex, int numPoints,
glm::vec2 center, double scale, double verticalScale, glm::vec3 color,double startr,double endr)
{
double angleIncrement = (2 * M_PI) / numPoints;
double currentAngle = M_PI / 2;
for (int i = startVertexIndex; i < startVertexIndex + numPoints; ++i) {
//if (currentAngle>startr&¤tAngle<endr) {
vertices[i] = getEllipseVertex(center, scale, verticalScale, currentAngle, 0);
if (verticalScale == 1.0) {//圆
colors[i] = glm::vec3(generateAngleColor(currentAngle), 0.0, 0.0);
}
else {
colors[i] = color;
}
//}
currentAngle += angleIncrement;
}
}
GLuint vao[50], program;//vao 0:三角形 1:正方形 2:椭圆 3:圆
// 创建顶点缓存对象,vbo[2]是因为我们将要使用两个缓存对象
GLuint vbo[2];
void createellipse(glm::vec2 ellipsecenter, glm::vec3 color,int i, double scale, double verticalScale,int type) {//创建椭圆 i为在vao中的下标 type=1为生成变体
// 定义椭圆的点
glm::vec2 ellipse_vertices[ELLIPSE_NUM_POINTS];
glm::vec3 ellipse_colors[ELLIPSE_NUM_POINTS];
generateEllipsePoints(ellipse_vertices, ellipse_colors, 0, ELLIPSE_NUM_POINTS, ellipsecenter, scale, verticalScale, color,type);//椭圆
glGenVertexArrays(1, &vao[i]);
glBindVertexArray(vao[i]);
glGenBuffers(1, &vbo[0]);
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(ellipse_vertices), ellipse_vertices, GL_STATIC_DRAW);
GLuint location = glGetAttribLocation(program, "vPosition");
glEnableVertexAttribArray(location);
glVertexAttribPointer(
location,
2,
GL_FLOAT,
GL_FALSE,
sizeof(glm::vec2),
BUFFER_OFFSET(0));
glGenBuffers(1, &vbo[1]);
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(ellipse_colors), ellipse_colors, GL_STATIC_DRAW);
GLuint cLocation = glGetAttribLocation(program, "vColor");
glEnableVertexAttribArray(cLocation);
glVertexAttribPointer(
cLocation,
3,
GL_FLOAT,
GL_FALSE,
sizeof(glm::vec3),
BUFFER_OFFSET(0));
// 绘制椭圆
glBindVertexArray(vao[i]);
glDrawArrays(GL_TRIANGLE_FAN, 0, ELLIPSE_NUM_POINTS);
}
void createpartellipse(glm::vec2 ellipsecenter, glm::vec3 color, int i, double scale, double verticalScale, double startr, double endr) {//创建椭圆 i为在vao中的下标 type=1为生成变体
//绘制出弧线(没什么用)
glm::vec2 ellipse_vertices[ELLIPSE_NUM_POINTS];
glm::vec3 ellipse_colors[ELLIPSE_NUM_POINTS];
generatePartEllipsePoints(ellipse_vertices, ellipse_colors, 0, ELLIPSE_NUM_POINTS, ellipsecenter, scale, verticalScale, color,startr,endr);//椭圆
glGenVertexArrays(1, &vao[i]);
glBindVertexArray(vao[i]);
glGenBuffers(1, &vbo[0]);
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(ellipse_vertices), ellipse_vertices, GL_STATIC_DRAW);
GLuint location = glGetAttribLocation(program, "vPosition");
glEnableVertexAttribArray(location);
glVertexAttribPointer(
location,
2,
GL_FLOAT,
GL_FALSE,
sizeof(glm::vec2),
BUFFER_OFFSET(0));
glGenBuffers(1, &vbo[1]);
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(ellipse_colors), ellipse_colors, GL_STATIC_DRAW);
GLuint cLocation = glGetAttribLocation(program, "vColor");
glEnableVertexAttribArray(cLocation);
glVertexAttribPointer(
cLocation,
3,
GL_FLOAT,
GL_FALSE,
sizeof(glm::vec3),
BUFFER_OFFSET(0));
// 绘制部分椭圆
glBindVertexArray(vao[i]);
glDrawArrays(GL_TRIANGLE_FAN, 0, ELLIPSE_NUM_POINTS);
}
void showface(double a, double b,int i,glm::vec3 color,int type) {//蚶线的参数
//定义蚶线的点
glm::vec2 limacon_vertices[ELLIPSE_NUM_POINTS];
glm::vec3 limacon_colors[ELLIPSE_NUM_POINTS];
for (int i = 0; i < ELLIPSE_NUM_POINTS; i++) {
double sita = i * 2*M_PI / ELLIPSE_NUM_POINTS;//弧度
double r = a + b * sin(sita)+ 0.06 * type;//模长
double x = 0.3*r * cos(sita);
double y = 0.3*r * sin(sita)-0.6;
limacon_vertices[i]=glm::vec2(x, y);
limacon_colors[i] = color;
}
glGenVertexArrays(1, &vao[i]);
glBindVertexArray(vao[i]);
glGenBuffers(1, &vbo[0]);
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(limacon_vertices), limacon_vertices, GL_STATIC_DRAW);
GLuint location = glGetAttribLocation(program, "vPosition");
glEnableVertexAttribArray(location);
glVertexAttribPointer(
location,
2,
GL_FLOAT,
GL_FALSE,
sizeof(glm::vec2),
BUFFER_OFFSET(0));
glGenBuffers(1, &vbo[1]);
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(limacon_colors), limacon_colors, GL_STATIC_DRAW);
GLuint cLocation = glGetAttribLocation(program, "vColor");
glEnableVertexAttribArray(cLocation);
glVertexAttribPointer(
cLocation,
3,
GL_FLOAT,
GL_FALSE,
sizeof(glm::vec3),
BUFFER_OFFSET(0));
// 绘制蚶线
glBindVertexArray(vao[i]);
glDrawArrays(GL_TRIANGLE_FAN, 0, ELLIPSE_NUM_POINTS);
}
void showhand(glm::vec2 center,double a, double b, int i, glm::vec3 color, double scale,int type) {//圆锥曲线的参数 type=0左 type=1右
//定义圆锥曲线的点
glm::vec2 conic_vertices[ELLIPSE_NUM_POINTS];//conic section圆锥曲线
glm::vec3 conic_colors[ELLIPSE_NUM_POINTS];
for (int i = 0; i < ELLIPSE_NUM_POINTS; i++) {
double sita = i * 2 * M_PI / ELLIPSE_NUM_POINTS;//弧度
double r = 1.0/(1-a*cos(sita-b));//模长
double x;
if(type==0)
x = -0.3* scale * r * cos(sita);
else if(type==1)
x = 0.3*scale * r * cos(sita);
double y = 0.3*scale* r * sin(sita);
conic_vertices[i] = glm::vec2(x, y)+center;
conic_colors[i] = color;
}
glGenVertexArrays(1, &vao[i]);
glBindVertexArray(vao[i]);
glGenBuffers(1, &vbo[0]);
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(conic_vertices), conic_vertices, GL_STATIC_DRAW);
GLuint location = glGetAttribLocation(program, "vPosition");
glEnableVertexAttribArray(location);
glVertexAttribPointer(
location,
2,
GL_FLOAT,
GL_FALSE,
sizeof(glm::vec2),
BUFFER_OFFSET(0));
glGenBuffers(1, &vbo[1]);
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(conic_colors), conic_colors, GL_STATIC_DRAW);
GLuint cLocation = glGetAttribLocation(program, "vColor");
glEnableVertexAttribArray(cLocation);
glVertexAttribPointer(
cLocation,
3,
GL_FLOAT,
GL_FALSE,
sizeof(glm::vec3),
BUFFER_OFFSET(0));
// 绘制圆锥曲线
glBindVertexArray(vao[i]);
if (type == 1)
glDrawArrays(GL_TRIANGLE_FAN, 0, ELLIPSE_NUM_POINTS);
else if (type == 0)
glDrawArrays(GL_LINE_LOOP, 0, ELLIPSE_NUM_POINTS);
}
void init()
{
// 读取着色器并使用
std::string vshader, fshader;
vshader = "shaders/vshader.glsl";
fshader = "shaders/fshader.glsl";
program = InitShader(vshader.c_str(), fshader.c_str());
glUseProgram(program);
/* 布布配色 */
/*
glm::vec3 color1(0.957, 0.643, 0.376);//244,164,96沙棕色 皮肤
glm::vec3 color2(0.98, 0.922, 0.843);//250,235,215古代白 贴住玻璃的部分
glm::vec3 color3(1.0, 0.871, 0.678);//255,222,173纳瓦霍白 贴住的腮黄
glm::vec3 color4(0.561, 0.306, 0.208);//143,78,53 轮廓
*/
/* 一二配色 */
glm::vec3 color1(1,1,1);//255,255,255白色 皮肤
glm::vec3 color2(1,0.941,0.961);//255,240,245脸红的淡紫色 贴住玻璃的部分
glm::vec3 color3(1,0.714,0.757);//255,182,193浅粉红 贴住的腮红
glm::vec3 color4(0.502,0,0);//128,0,0栗色 轮廓
glm::vec2 ellipsecenter9(-0.48,0.16);
createellipse(ellipsecenter9,color4,0,0.12,1.0001,0);
glm::vec2 ellipsecenter10(-0.48,0.16);
createellipse(ellipsecenter10,color1,1,0.1,1.0001,0);
glm::vec2 ellipsecenter11(-0.47,0.15);
createellipse(ellipsecenter11,color4,2,0.085,1.0001,0);//左耳朵
glm::vec2 ellipsecenter12(0.48,0.16);
createellipse(ellipsecenter12,color4,3,0.12,1.0001,0);
glm::vec2 ellipsecenter13(0.48,0.16);
createellipse(ellipsecenter13,color1,4,0.1,1.0001,0);
glm::vec2 ellipsecenter14(0.47,0.15);
createellipse(ellipsecenter14,color4,5,0.085,1.0001,0);//右耳朵
showface(2.0,1,6,color4,1);//脸的轮廓
glm::vec2 ellipsecenter20(0,-0.8);
createellipse(ellipsecenter20,color4,7,0.505,0.8,0);//身体的轮廓
glm::vec2 ellipsecenter32(-0.55,-0.5);
createellipse(ellipsecenter32,color4,8,0.13,0.95,0);
glm::vec2 ellipsecenter33(-0.55,-0.5);
createellipse(ellipsecenter33,color1,9,0.115,0.95,0);
glm::vec2 ellipsecenter34(0.55,-0.5);
createellipse(ellipsecenter34,color4,10,0.13,0.95,0);
glm::vec2 ellipsecenter35(0.55,-0.5);
createellipse(ellipsecenter35,color1,11,0.115,0.95,0);//腮部
showface(2.0,1,12,color1,0);//脸
glm::vec2 ellipsecenter22(-0.458,-0.795);
showhand(ellipsecenter22,0.8,0.7,13,color4,0.21,0);//左手的轮廓
glm::vec2 ellipsecenter23(-0.472,-0.785);
showhand(ellipsecenter23,0.8,0.7,14,color1,0.186,0);//左手
glm::vec2 ellipsecenter24(0.458,-0.795);
showhand(ellipsecenter24,0.8,0.7,15,color4,0.21,1);//右手的轮廓
glm::vec2 ellipsecenter25(0.472,-0.785);
showhand(ellipsecenter25,0.8,0.7,16,color1,0.186,1);//右手
glm::vec2 ellipsecenter21(0,-0.8);
createellipse(ellipsecenter21,color1,17,0.49,0.8,0);//身体
glm::vec2 ellipsecenter28(0.385,-0.405);
createellipse(ellipsecenter28,color4,18,0.1,1.05,0);
glm::vec2 ellipsecenter29(0.43,-0.45);
createellipse(ellipsecenter29,color1,19,0.145,1.01,0);
glm::vec2 ellipsecenter30(-0.385,-0.405);
createellipse(ellipsecenter30,color4,20,0.1,1.05,0);
glm::vec2 ellipsecenter31(-0.43,-0.45);
createellipse(ellipsecenter31,color1,21,0.145,1.01,0);//眼角细节
glm::vec2 ellipsecenter1(0.45,-0.50);
createellipse(ellipsecenter1,color2,22,0.14,1.1,0);
glm::vec2 ellipsecenter2(-0.45,-0.50);
createellipse(ellipsecenter2,color2,23,0.14,1.1,0);
glm::vec2 ellipsecenter3(0.46,-0.50);
createellipse(ellipsecenter3,color3,24,0.103,1.02,0);
glm::vec2 ellipsecenter4(-0.46,-0.50);
createellipse(ellipsecenter4,color3,25,0.103,1.02,0);//两边的腮黄
glm::vec2 ellipsecenter5(0.27,-0.32);
createellipse(ellipsecenter5,color4,26,0.06,1.001,0);
glm::vec2 ellipsecenter6(-0.27,-0.32);
createellipse(ellipsecenter6,color4,27,0.06,1.001,0);//眼睛
glm::vec2 ellipsecenter7(0,0.1);
createellipse(ellipsecenter7,color2,28,0.19,1.06,1);//额头
glm::vec2 ellipsecenter8(0,-0.58);
createellipse(ellipsecenter8,color2,29,0.1,1.15,1);//下巴
glm::vec2 ellipsecenter15(-0.023,-0.34);
createpartellipse(ellipsecenter15,color4,30,0.045,1.15,11.0/12*M_PI,3.0/2*M_PI);
glm::vec2 ellipsecenter16(0.023,-0.34);
createpartellipse(ellipsecenter16,color4,31,0.045,1.15,3.0/2*M_PI,2*M_PI);//唇瓣
glm::vec2 ellipsecenter17(-0.02,-0.32);
createellipse(ellipsecenter17,color1,32,0.035,1.2,0);
glm::vec2 ellipsecenter18(0.02,-0.32);
createellipse(ellipsecenter18,color1,33,0.035,1.2,0);//贴掉多余形状
glm::vec2 ellipsecenter19(0,-0.32);
createellipse(ellipsecenter19,color1,34,0.081,5,0);//贴掉多余形状
glm::vec2 ellipsecenter26(-0.62,-0.65);
createellipse(ellipsecenter26,color2,35,0.05,1.01,0);
glm::vec2 ellipsecenter27(0.62,-0.65);
createellipse(ellipsecenter27,color2,36,0.05,1.01,0);//手上的白色
glClearColor(0.882,1,1,0.9);//0.529,0.808,0.980 135,206,250淡蓝色 //225,255,255淡青色
}
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(program);
for (int i = 0; i < 40; i++) {//绘制各种图形
glBindVertexArray(vao[i]);
glDrawArrays(GL_TRIANGLE_FAN, 0, ELLIPSE_NUM_POINTS);
}
glFlush();
}
int main(int argc, char **argv)
{
// 初始化GLFW库
glfwInit();
// 配置GLFW
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
// 配置窗口属性
GLFWwindow* window = glfwCreateWindow(512, 512, "2020152030_赵宇轩_实验一", NULL, NULL);//最后使用的代码
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// 调用任何OpenGL的函数之前初始化GLAD
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
init();
std::cout << "OpenGL Vendor: " << glGetString(GL_VENDOR) << std::endl;
std::cout << "OpenGL Renderer: " << glGetString(GL_RENDERER) << std::endl;
std::cout << "OpenGL Version: " << glGetString(GL_VERSION) << std::endl;
std::cout << "Supported GLSL version is: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << std::endl;
while (!glfwWindowShouldClose(window))
{
display();
// 交换颜色缓冲 以及 检查有没有触发什么事件(比如键盘输入、鼠标移动等)
glfwSwapBuffers(window);
glfwPollEvents();
}
return 0;
}