Python之OpenGL笔记(31):扭动的软糖

一、目的

1、顶点着色器的妙用,画一个扭动的软糖;

二、程序运行结果

三、基本原理

   吴亚峰《OpenGL ES 3.x游戏开发》(下卷)内容
   从图 2-4 中可以看出,软糖模型实际上是由很多层小矩形叠加而成。在同一帧中,随着 y 坐标的不断升高,此层的顶点绕中心轴扭曲的角度越大。因此,实现扭动软糖的效果只要将代表软糖的长方体中各层顶点的 x、z 坐标按照一定的规则根据顶点的 y 坐标以及当前帧的控制参数进行变换即可,具体的计算思路如图 2-5、图 2-6 与图 2-7 所示。
   具体的计算步骤如下:
   1、首先如图 2-5 所示,需要计算出当前顶点 y 坐标与最下层顶点 y 坐标的差值。


   2、接着根据 y 坐标的差值、角度换算比例以及本帧的总扭曲角度换算出当前顶点的扭曲角度,计算公式为: currAngle =(currY-yStart)/ySpan×angleSpan。
   3、最后根据当前顶点的 x、 z 坐标以及扭曲的角度计算出变换后顶点的 x、 z 坐标,此步的计算思路如图 2-6 与图 2-7 所示。


   4、从图 2-6 与图 2-7 中可以看出,将顶点绕中心轴扭曲(旋转)实际上可以看成是将从中心点出发到变换前顶点的向量旋转指定的角度。旋转后得到的新向量的终点位置即所求的变换后顶点的位置,因此具体的计算公式如下。
   x'=xcosα-zsinα
   z'=xsinα+zcosα
   上述公式中的α为需要旋转(扭曲)的角度,实际计算时采用前面步骤计算出来的变量 currAngle 的值即可。

四、源代码

"""
程序名称:GL_textures05.py
编程: dalong10
功能: 扭动的软糖的实现
参考资料: 《OpenGL ES 3.x游戏开发》(下卷)吴亚峰
"""
import myGL_Funcs    #Common OpenGL utilities,see myGL_Funcs.py
import glfw
from OpenGL.GL import *
from OpenGL.GL.shaders import compileProgram, compileShader
import numpy 
import numpy as np
import pyrr
from PIL import Image

SphereVS = """
#version 330 core
layout(location = 0) in vec3 aPosition;
layout(location = 1) in vec2 inTexCoord;
uniform mat4 uMVPMatrix; //总变换矩阵
uniform float angleSpan;//本帧扭曲总角度
uniform float yStart;//Y坐标起始点
uniform float ySpan;//Y坐标总跨度

out vec2 passTexCoord;
void main(){
   float tempAS= angleSpan*(aPosition.y-yStart)/ySpan;//计算当前顶点扭动(绕中心点选择)的角度
   vec3 tPosition=aPosition;
  
   if(aPosition.y>yStart)
   {//若不是最下面一层的顶点则计算扭动后的X、Z坐标
     tPosition.x=(cos(tempAS)*aPosition.x-sin(tempAS)*aPosition.z);
     tPosition.z=(sin(tempAS)*aPosition.x+cos(tempAS)*aPosition.z);
   }
   gl_Position = uMVPMatrix * vec4(tPosition,1); //根据总变换矩阵计算此次绘制此顶点的位置
   passTexCoord = inTexCoord; //将接收的纹理坐标传递给片元着色器
    }
"""

SphereFS = """
#version 330 core
precision mediump float;
uniform sampler2D tex; //纹理内容数据
in vec2 passTexCoord; //接收从顶点着色器过来的参数
out vec4 outColor;    //输出到的片元颜色
void main(){
   //给此片元从纹理中采样出颜色值            
   outColor = texture(tex, passTexCoord);
        }
"""

class CuboMode:
    def make_Mode(self):
        if ( self.rows < 1 or self.cols < 1):
            raise Exception("BoardMode can not be initialized!")        
        self.vertexs   = np.array([], np.float32)  # 位置FloatArray(numPoint * 3)
        self.texcoords = np.array([], np.float32)    # 纹理FloatArray(numPoint * 2)
        self.indices =  np.array([], np.int32)       # 索引 ShortArray(numPoint * 6)        
        # 把矩形平铺在一个平面上
        PI = np.pi
        Y_MAX=1.5*0.5     #  
        Y_MIN=-1.5 *0.5    #   
        FD=6 
        hw=0.5 *0.5
        self.vCount=FD*4*6            # 每个格子两个三角形,每个三角形3个顶点
        yStart=Y_MIN
        ySpan=(Y_MAX-Y_MIN)/FD
        count=0
        tCount=0
        for i in range(FD):
            x1=-hw
            y1=yStart
            z1=hw
            x2=hw
            y2=yStart
            z2=hw
            x3=hw
            y3=yStart
            z3=-hw
            x4=-hw
            y4=yStart
            z4=-hw
             
            x5=-hw
            y5=yStart+ySpan
            z5=hw
            x6=hw
            y6=yStart+ySpan
            z6=hw
            x7=hw
            y7=yStart+ySpan
            z7=-hw
            x8=-hw
            y8=yStart+ySpan
            z8=-hw
            self.vertexs=np.hstack((self.vertexs, np.array([x5,y5,z5], np.float32) ))   #每个顶点xyz三个坐标,6个顶点//512
            self.vertexs=np.hstack((self.vertexs, np.array([x1,y1,z1], np.float32) ))    
            self.vertexs=np.hstack((self.vertexs, np.array([x2,y2,z2], np.float32) ))    
            
            self.vertexs=np.hstack((self.vertexs, np.array([x5,y5,z5], np.float32) ))   #每个顶点xyz三个坐标,6个顶点//526
            self.vertexs=np.hstack((self.vertexs, np.array([x2,y2,z2], np.float32) ))    
            self.vertexs=np.hstack((self.vertexs, np.array([x6,y6,z6], np.float32) ))    
        
            self.vertexs=np.hstack((self.vertexs, np.array([x6,y6,z6], np.float32) ))   #每个顶点xyz三个坐标,6个顶点//623
            self.vertexs=np.hstack((self.vertexs, np.array([x2,y2,z2], np.float32) ))    
            self.vertexs=np.hstack((self.vertexs, np.array([x3,y3,z3], np.float32) ))    

            self.vertexs=np.hstack((self.vertexs, np.array([x6,y6,z6], np.float32) ))   #每个顶点xyz三个坐标,6个顶点//637
            self.vertexs=np.hstack((self.vertexs, np.array([x3,y3,z3], np.float32) ))    
            self.vertexs=np.hstack((self.vertexs, np.array([x7,y7,z7], np.float32) ))    

            self.vertexs=np.hstack((self.vertexs, np.array([x7,y7,z7], np.float32) ))    
            self.vertexs=np.hstack((self.vertexs, np.array([x3,y3,z3], np.float32) ))    
            self.vertexs=np.hstack((self.vertexs, np.array([x4,y4,z4], np.float32) ))    

            self.vertexs=np.hstack((self.vertexs, np.array([x7,y7,z7], np.float32) ))    
            self.vertexs=np.hstack((self.vertexs, np.array([x4,y4,z4], np.float32) ))    
            self.vertexs=np.hstack((self.vertexs, np.array([x8,y8,z8], np.float32) ))    

            self.vertexs=np.hstack((self.vertexs, np.array([x8,y8,z8], np.float32) ))    
            self.vertexs=np.hstack((self.vertexs, np.array([x4,y4,z4], np.float32) ))    
            self.vertexs=np.hstack((self.vertexs, np.array([x1,y1,z1], np.float32) ))    

            self.vertexs=np.hstack((self.vertexs, np.array([x8,y8,z8], np.float32) ))    
            self.vertexs=np.hstack((self.vertexs, np.array([x1,y1,z1], np.float32) ))    
            self.vertexs=np.hstack((self.vertexs, np.array([x5,y5,z5], np.float32) ))    

            yStart=yStart+ySpan
            self.texcoords=np.hstack((self.texcoords, numpy.array([0,0,0,1,1,1,0,0,1,1,1,0], numpy.float32) ))                           
            self.texcoords=np.hstack((self.texcoords, numpy.array([0,0,0,1,1,1,0,0,1,1,1,0], numpy.float32) ))                           
            self.texcoords=np.hstack((self.texcoords, numpy.array([0,0,0,1,1,1,0,0,1,1,1,0], numpy.float32) ))                           
            self.texcoords=np.hstack((self.texcoords, numpy.array([0,0,0,1,1,1,0,0,1,1,1,0], numpy.float32) ))                           
        #-------------生成软糖数据库完毕-----------

    def Image_Open(self):       
        img = Image.open(self.imgfilename) 
        imgData = np.array(list(img.getdata()), np.int8)
        texture = glGenTextures(1)
        glPixelStorei(GL_UNPACK_ALIGNMENT,1)
        glBindTexture(GL_TEXTURE_2D, texture)
        glPixelStorei(GL_UNPACK_ALIGNMENT,1)
        glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_BORDER_COLOR, 1,0,0)    # 如果用GL_CLAMP_TO_BORDER,还需要指定一个边缘颜色,超出的坐标为用户指定的边缘颜色。
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img.size[0], img.size[1], 
                 0, GL_RGBA, GL_UNSIGNED_BYTE, imgData)
        glBindTexture(GL_TEXTURE_2D, 0)
        self.texid1 = texture   


    def __init__(self,rows,cols,imgfilename):
        self.rows=rows
        self.cols=cols
        self.imgfilename = imgfilename  
 
        self.make_Mode()
        # load shaders
        self.program = myGL_Funcs.loadShaders(SphereVS, SphereFS)
        #print('ok1')
        glUseProgram(self.program)
        self.vertIndex = glGetAttribLocation(self.program, b"aPosition")
                    # set up vertex array object (VAO)
        self.vao = glGenVertexArrays(1)
        glBindVertexArray(self.vao)            
                    # set up VBOs
        vertexData = numpy.array(self.vertexs, numpy.float32)
        self.vertexBuffer = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer)
        glBufferData(GL_ARRAY_BUFFER, 4*len(vertexData), vertexData, GL_STATIC_DRAW)                 
                    # enable arrays
        glEnableVertexAttribArray(self.vertIndex)
         # Position attribute
        glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer)
        glVertexAttribPointer(self.vertIndex, 3, GL_FLOAT, GL_FALSE, 0,None)               
        # 顶点纹理属性
        texData = numpy.array(self.texcoords, numpy.float32)
        self.texBuffer = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, self.texBuffer)
        glBufferData(GL_ARRAY_BUFFER, 4*len(texData), texData, GL_STATIC_DRAW)           
        glEnableVertexAttribArray(1)
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE,  0,None)
        self.Image_Open()
        # unbind VAO
        glBindVertexArray(0)
        glBindBuffer(GL_ARRAY_BUFFER, 0)    
        
        
    def render(self, pMatrix,angleSpan,yStart,ySpan):  
        # use shader
        glUseProgram(self.program)

        # set modelview matrix
        glUniformMatrix4fv(glGetUniformLocation(self.program, 'uMVPMatrix'), 
                          1, GL_FALSE, mvMatrix)
        glUniform1f(glGetUniformLocation(self.program, "angleSpan"), angleSpan)
        glUniform1f(glGetUniformLocation(self.program, "yStart"), yStart)
        glUniform1f(glGetUniformLocation(self.program, "ySpan"), ySpan)

        #// 启用多个纹理单元 绑定纹理对象        
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, self.texid1);
        glUniform1i(glGetUniformLocation(self.program, "tex"), 0); #// 设置纹理单元为0号
        
        # bind VAO
        glBindVertexArray(self.vao)
        # draw
        glDrawArrays(GL_TRIANGLES, 0,self.rows*self.cols*6 )
        # unbind VAO
        glBindVertexArray(0)
        

# glfw callback functions
def window_resize(window, width, height):
    glViewport(0, 0, width, height)


if __name__ == '__main__':
    import sys
    import glfw
    import OpenGL.GL as gl
    cameraPos=np.array([0.0, 0.0, 4])  # 眼睛的位置(默认z轴的正方向)
    cameraFront=np.array([0.0, 0.0, 0.0])  # 瞄准方向的参考点(默认在坐标原点)
    cameraUp=np.array([0.0, 1.0, 0.0])  # 定义对观察者而言的上方(默认y轴的正方向)

    # Initialize the library
    if not glfw.init():
        sys.exit()
    # Create a windowed mode window and its OpenGL context
    window = glfw.create_window(320, 240, "My OpenGL window", None, None)
    if not window:
        glfw.terminate()
        sys.exit()
    # set window's position
    glfw.set_window_pos(window, 100, 100)
    # set the callback function for window resize
    glfw.set_window_size_callback(window, window_resize)
    # make the context current
    glfw.make_context_current(window)

    glClearColor(0, 0.1, 0.1, 1)
    glEnable(GL_DEPTH_TEST)
    glEnable(GL_BLEND)
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

    mvMatrix=np.matrix(np.identity(4))
    theta1=0
    WIDTH_SPAN=6
    board1=CuboMode(40,40,"wall.png",) 
    mvMatrix = pyrr.Matrix44.from_x_rotation(0.5)   

    # the main application loop
    while not glfw.window_should_close(window):
        width, height = glfw.get_framebuffer_size(window)
        ratio = width / float(height)
        currentFrame =  1.0*glfw.get_time()
        glfw.poll_events()           
        gl.glViewport(0, 0, width, height)       
        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
        gl.glMatrixMode(gl.GL_PROJECTION)
        gl.glLoadIdentity()
        gl.glOrtho(-ratio, ratio, -1, 1, 1, -1)
        gl.glMatrixMode(gl.GL_MODELVIEW)
        gl.glLoadIdentity()
        gl.glClearColor(0.0,0.0,4.0,0.0)  
  
        theta1=currentFrame
        board1.render( mvMatrix, theta1,-0.6, WIDTH_SPAN)  #从y=-0.6以上开始扭

        glfw.swap_buffers(window)

    # terminate glfw, free up allocated resources
    glfw.terminate()

五、参考资料

1、大龙10的简书://www.greatytc.com/p/49dec482a291
2、吴亚峰《OpenGL ES 3.x游戏开发》(下卷)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容