实验题目来自2021年春季学期山东大学软件学院计算机动画基础课程
本人比较菜,代码有很多bug以及莫名其妙的地方,发在这记录一下写代码的艰辛😭,仅供参考思路哦!
现在代码已经找不到了,请不要找我要文件啦!(当然,欢迎指正)
- 使用glfw,glad库,C++编写,参考LearnOpenGL
实验题目
给出咳嗽或打喷嚏时,应该纸巾掩口鼻的渐变动画
可以是图形方式,也可以是图像方式
思路
使用坐标网格法实现图像的渐变:
定义两幅图像,初始(没打喷嚏)和目标(打喷嚏),分别定义网格并将网格点与图像的特征位置相对应(眼睛、鼻子、嘴等位置)。
在渲染循环中,不断对初始网格和目标网格进行插值,并将其交叉融合形成中间网格。
利用中间网格作为图像的采样点,使初始图象和目标图像发生扭曲,将其融合(插值),得到中间图像。
实现效果
步骤
-
使用maya等建模工具定义网格,并移动特征点到相应位置。(没错,思路就是这么神奇)
-
从maya导出obj格式文件,编写函数读取其各顶点位置,分别保存为初始和目标网格信息,注意需要将其大小转换为OpenGL标准坐标。
void readObj(char* path, float * r) { int i = 0; ifstream ifs(path, ios::in); if (!ifs) { cout << "open file error!" << endl; exit(1); } float t = 0; char l; for (i = 0; i < 121*3; i+=3) { ifs >> l; ifs >> t; r[i] = t / 4; ifs >> t; r[i+1] = t / 4; ifs >> t; r[i+2] = t / 4; } ifs.close(); } //1.get the points of the source and destination image readObj((char*)"pic111.txt", sourc); readObj((char*)"pic222.txt", destn);
-
从网格读取到的顶点即为采样点,需要用其数据绘制三角形面片,因此需要设置索引数组来确定三角形的绘制顺序。从maya导出的文件顶点是从左下角开始,一行一行向上排列的,因此绘制过程中需要特殊注意从左下角开始计算。(非常奇怪的算法。。 别骂了)
int indc[20 * 10 * 3]; for (size_t l = 0; l < 10; l++)//each line { for (size_t i = 0; i < 10; i++) { indc[l * 20 * 3 + 6 * i] = l * 11 + i; indc[l * 20 * 3 + 6 * i + 1] = (l + 1) * 11 + i; indc[l * 20 * 3 + 6 * i + 2] = l * 11 + i + 1; indc[l * 20 * 3 + 6 * i + 3] = (l + 1) * 11 + i; indc[l * 20 * 3 + 6 * i + 4] = l * 11 + i + 1; indc[l * 20 * 3 + 6 * i + 5] = (l + 1) * 11 + i + 1; } }
-
设置vao,vbo以及ebo。
-
导入两个纹理图片
-
在渲染循环中,不断插值。
//change the value of t(interpolation value) t += delt; Sleep(15); if (t > 1.0 || t < 0) { //draw back and forth. delt = -delt; } if (t >= 0.9 || t <= 0.1)Sleep(15); // set the value ourShader.use(); ourShader.setFloat("t", t);
-
在顶点着色器中,对网格顶点的位置进行插值,并将其作为纹理坐标传递给片段着色器(需要稍加修改,因为纹理坐标的范围是0~1),在片段着色器中,将两个纹理使用OpenGL内建的mix函数交叉融合,最终实现渐变效果。
#version 330 core layout (location = 1) in vec3 aPos; layout (location = 0) in vec3 bPos; out vec2 aTex; out vec2 bTex; out float tf; uniform float t; void main(){ float nx=(1-t)*aPos.x+t*bPos.x; float ny=(1-t)*aPos.y+t*bPos.y; gl_Position = vec4(aPos.x, aPos.y, 0.0f, 1.0f); aTex=vec2(nx*0.5+0.5, ny*0.5+0.5); bTex=vec2(nx*0.5+0.5, ny*0.5+0.5); tf=t; } #version 330 core in vec2 aTex; in vec2 bTex; in float tf; // texture samplers uniform sampler2D texture1; uniform sampler2D texture2; out vec4 FragColor; void main(){ FragColor = mix(texture(texture1, aTex), texture(texture2, bTex), tf); }
结论分析与体会
-
实现渐变动画有两种方法,坐标网格法和特征法,想要手动实现都是比较复杂的。以坐标网格法为例,书中给出的方法需要:建立坐标网格,按照一定的方向逐像素扫描,通过网格坐标与像素坐标的对应关系确定目标网格中每一个像素在源图像中对应的像素,这里可能是多个像素,还需要使用一定的插值算法将其颜色值融合。经过尝试,这种方法的计算需要花费大量时间,可能需要一定的算法支持才能渲染出实时的动画效果。
-
OpenGL提供的mix函数可以将两个纹理交叉融合,可以实现没有任何特征对应的直接两幅图片的全部像素的插值,可以实现效果比较差的渐变效果。
-
可以将两种方法综合考虑,因为OpenGL的纹理坐标,即采样点的位置改变可以使其附近区域的颜色值随其改变到的位置发生变化,因此若在源图像中较为密集的设置采样点,并移动采样点的位置,即改变纹理坐标的位置,可以较为自然的形成扭曲的效果。若将源图像和目标图像都扭曲到同样的网格(纹理坐标),并将其交叉融合,再插值改变网格的形状,即可实现效果稍微好一点的,有特征对应的渐变效果。
然而,可能因为采样点不够密集,以及网格的选取可能不够准确,或者由于要变形的两个图像是动漫人物,图形简单边缘过于清晰,实现出的效果仍然比较一般,变形的效果稍微有点奇怪,后续可能还需要继续改进。 -
Obj文件:不同三维软件导出的obj文件格式并不完全相同,maya的obj文件格式如下:
顶点数据:
v开头的行是顶点数据,
v+’ '的行表示是顶点数据之空间坐标(position),
v+‘t’的行表示是顶点数据之纹理坐标(texture coordinate),
v+‘n’的行表示是顶点数据之法线向量(normal)。
索引数据:
比如说,对于上述的立方体obj文件,
v+’ ‘的行从v+’ '开始的第一行直到结束的第八行分别对应于索引1-8,
v+'t’的行从v+'t’开始的第一行直到结束的第二十四行分别对应于索引1-24,
v+'n’的行从v+‘n’开始的第一行直到结束的第二十四行分别对应于索引1-24。
面数据:
f开头的行,表示面(face),以3个“v+’ '索引/v+'t’索引/v+'n’索引”描述的顶点组成。
一点不重要的
- 再次感谢某位同学一直以来的帮助!