变形动画算法——你中有我 我中有你

题目:牛郎织女互变(渐变动画),可以是图形方式,也可以是图像方式


效果

image.png

一、方法

根据变形动画三种:直接变形、间接变形、渐变技术,显然我们需要实现渐变技术
渐变又划分成三种实现方式:坐标网格法、特征值、3d轴向切片,很明显,此类图像渐变采取坐标网格法。
根据课程实例:

渐变

坐标网格法:


坐标网格法
实例

我们最终绘制图像时,所用的方法依然是线性插值。这时候考虑三种插值方法,有两种可以使用:

  1. 基于形状插值
  2. 基于颜色插值
1.基于形状

先说说基于形状插值实现的渐变。最终效果是一个实色的形状变成另一个形状
实现步骤:

  1. 对牛郎、织女分别生成变形工具
    -计算人物(非透明区域)最大x、y
    -计算一个包括人物的矩形
    -均匀划分x、y,形成网格
  2. 每个网格单元记录包含的顶点:网格内,若像素pixel有颜色则为true,透明则为false
  3. 每个网格单元对应一个二位数组:所有有颜色的顶点
    当前拥有2个图像imag[2],分别对应数个grids_A[],且网格单元数量一致,每个网格单元对应数个array[][],二维数组的数量也一致。
  4. 最后对每组二维数组进行插值运算

此法重点在于第2步,找出所有顶点的位置,我们可以做完之后再贴纹理
可能会用到GLSL的内置函数,也没了解这样的函数,所以没采用此法。

此外又不得不提的是,上述坐标网格法提到了五张图片I1、I2、Is、It和If,而这里只用到了三张图片。是因为网格法为了让人物五官对准,渐变的效果更好,而这里并没设计人物的五官,所以用两张图片和生成的中间帧就可以。

2.基于颜色

我们同样可以使用颜色插值的方法,这样最终将是淡入淡出的效果。

而且我们之前已经了解到了在fragment shader中的mix函数便是对纹理进行插值的,所以采用此法!

将整张图片看成一个网格单元,这样不用做繁琐的变形工具约束,也不用记录顶点(记录顶点是为了做插值,我们有mix函数做插值)

  1. 前期需要接着上次实验,再做一张牛郎和织女的png,并且改变方向,调至合理处(为了使用颜色插值)。
  2. 插值计算

二、代码

    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、三角函数、矩阵和算法逻辑等知识。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容