题目:牛郎织女互变(渐变动画),可以是图形方式,也可以是图像方式
效果
一、方法
根据变形动画三种:直接变形、间接变形、渐变技术,显然我们需要实现渐变技术。
渐变又划分成三种实现方式:坐标网格法、特征值、3d轴向切片,很明显,此类图像渐变采取坐标网格法。
根据课程实例:
坐标网格法:
坐标网格法
我们最终绘制图像时,所用的方法依然是线性插值。这时候考虑三种插值方法,有两种可以使用:
- 基于形状插值
- 基于颜色插值
1.基于形状
先说说基于形状插值实现的渐变。最终效果是一个实色的形状变成另一个形状。
实现步骤:
- 对牛郎、织女分别生成变形工具
-计算人物(非透明区域)最大x、y
-计算一个包括人物的矩形
-均匀划分x、y,形成网格- 每个网格单元记录包含的顶点:网格内,若像素pixel有颜色则为true,透明则为false
- 每个网格单元对应一个二位数组:所有有颜色的顶点
当前拥有2个图像imag[2],分别对应数个grids_A[],且网格单元数量一致,每个网格单元对应数个array[][],二维数组的数量也一致。- 最后对每组二维数组进行插值运算
此法重点在于第2步,找出所有顶点的位置,我们可以做完之后再贴纹理
可能会用到GLSL的内置函数,也没了解这样的函数,所以没采用此法。
此外又不得不提的是,上述坐标网格法提到了五张图片I1、I2、Is、It和If,而这里只用到了三张图片。是因为网格法为了让人物五官对准,渐变的效果更好,而这里并没设计人物的五官,所以用两张图片和生成的中间帧就可以。
2.基于颜色
我们同样可以使用颜色插值的方法,这样最终将是淡入淡出的效果。
而且我们之前已经了解到了在fragment shader中的mix函数便是对纹理进行插值的,所以采用此法!
将整张图片看成一个网格单元,这样不用做繁琐的变形工具约束,也不用记录顶点(记录顶点是为了做插值,我们有mix函数做插值)
- 前期需要接着上次实验,再做一张牛郎和织女的png,并且改变方向,调至合理处(为了使用颜色插值)。
- 插值计算
二、代码
glUseProgram(myShaderBack->ID);
glUniform1i(glGetUniformLocation(myShaderBack->ID, "texture0"), 0);
glUniform1i(glGetUniformLocation(myShaderBack->ID, "texture1"), 1);
float max = 0;
float min = 1;
bool boo = true;
float i = 0;
glm::mat4 tran1,tran2;
//********************************************************************************
//渲染循环
for (;!glfwWindowShouldClose(window); i = i + 0.0015)
{
//输入
processInput(window);
//渲染指令
//清除颜色缓冲
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(myShaderBack->ID);
glBindVertexArray(VAO[0]);
//背景
glUniform1i(glGetUniformLocation(myShaderBack->ID, "flag"), 0);
glUniform1f(glGetUniformLocation(myShaderBack->ID, "t"), 0.0f);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, TexBuffer[2]);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
if (boo==true)
{
float temp = max * max * 0.64;//0.64
float x = max * max * 0.14;//0.14
glUniform1i(glGetUniformLocation(myShaderBack->ID, "flag"), 1);
//牛郎
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, TexBuffer[0]);
glm::mat4 trans;
trans = glm::translate(trans, glm::vec3(temp, x, 0.0f));
glUniformMatrix4fv(glGetUniformLocation(myShaderBack->ID, "transform"), 1, GL_FALSE, glm::value_ptr(trans));
glUniform1f(glGetUniformLocation(myShaderBack->ID, "t"), 0.0f);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
if (max > sin(i)) { tran1=trans; }
//织女
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, TexBuffer[1]);
trans = glm::mat4(1.0f);
trans = glm::translate(trans, glm::vec3(-temp, x, 0.0f));
glUniformMatrix4fv(glGetUniformLocation(myShaderBack->ID, "transform"), 1, GL_FALSE, glm::value_ptr(trans));
glUniform1f(glGetUniformLocation(myShaderBack->ID, "t"), 0.0f);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
if (max <= sin(i)) { max = sin(i); }
else { boo = false; tran2 = trans; }
}
if (boo == false)
{
glUniform1i(glGetUniformLocation(myShaderBack->ID, "flag"), 1);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, TexBuffer[0]);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, TexBuffer[4]);
glUniformMatrix4fv(glGetUniformLocation(myShaderBack->ID, "transform"), 1, GL_FALSE, glm::value_ptr(tran1));
glUniform1f(glGetUniformLocation(myShaderBack->ID, "t"), -min*min+1);//这里是二次函数,三角函数也行
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, TexBuffer[1]);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, TexBuffer[5]);
glUniformMatrix4fv(glGetUniformLocation(myShaderBack->ID, "transform"), 1, GL_FALSE, glm::value_ptr(tran2));
glUniform1f(glGetUniformLocation(myShaderBack->ID, "t"), -min * min + 1);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
if (min >= sin(i)) { min = sin(i); }
else { boo = true; min = 1; max = -1; }
}
//检查并调用事件,交换缓冲
glfwSwapBuffers(window);
glfwPollEvents();
}
三、位移->变形->反变形->反位移
我们仍需做一些工作来实现更好的动画效果。
1.每次绘制仅需两张图片,激活两个纹理单元即可。
2.发现glfwgettime()的局限性,第一次绘制时间大约0.9s,不在初始位置,故换成自设值max、min
3.为实现位移和变形的转变,设置布尔值首先判定进行哪种操作。当一种活动完毕,调成另一个模式进行。用max、min和sin函数比较来确立,利用了三角函数的周期性。
4.为实现位移内部和变形内部的往复效果,插值t和位移都使用了二次函数,利用了二次函数的对称性。
二次函数和三角函数都具有对称性。若增加一个判定值,当二次函数到某一点回到其对称位置,则二次函数具有了周期性。
此次实验综合运用了PS、三角函数、矩阵和算法逻辑等知识。