【PyOpenGL教程-介绍着色器】attribute变量(补间动画)
发表于2018-07-11
属性值(实现补间动画)
原文地址:http://pyopengl.sourceforge.net/context/tutorials/shader_4.html
本教程构建在往期教程的基础之上新增了:
- 在着色器中定义attribute变量
- 定义给attribute变量赋值的数组
- 基本不再使用legacy代码(转为使用attribute变量给顶点着色器传入属性值)
- 实现一个简单的“补间”几何动画
前面我们已经提到过很多次,glVertexPointer和glColorPointer是“legacy”(遗留的、早期的)OpenGL API。实际上在绘制基于shader(着色器)的几何体时,并不是总要限制每个顶点只对应单一的位置、颜色或纹理。
就像我们定义任意uniform变量传值给shader一样,我们也可以定义任意“Vertex Attribute”(顶点属性),它可以像使用glVertexPointer/glColorPointer一样从VBO中获取数据。
本教程将为每个顶点定义两个不同的位置,最终实际的位置将由这两个位置根据传入shader的作为系数的uniform变量混合而成。用这种“补间”的方法实现平滑的网格模型动画。
from OpenGLContext import testingcontext BaseContext = testingcontext.getInteractive() from OpenGL.GL import * from OpenGL.arrays import vbo from OpenGLContext.arrays import * from OpenGL.GL import shaders
以下是我们唯一新的import,它从OpenGLContext导入了一个实用的对象——Timer,它可以使用fraction()函数生成用于动画的值。
from OpenGLContext.events.timer import Timer class TestContext( BaseContext ): """Demonstrates use of attribute types in GLSL """ def OnInit( self ): """Initialize the context"""
我们定义了一个uniform变量“tween”,该变量决定了两个位置值当前的混合值。
当我们使用glVertexPointer/glColorPointer接口时,有隐式定义的属性值(gl_Vertex, gl_Color)接受我们的数据。在不使用legacy操作时,我们需要显式定义将要使用的属性值。定义的方式与uniform变量的定义方式非常相似,只有关键字不同。
vertex = shaders.compileShader(""" uniform float tween; attribute vec3 position; attribute vec3 tweened; attribute vec3 color; varying vec4 baseColor; void main() { gl_Position = gl_ModelViewProjectionMatrix * mix( vec4( position,1.0), vec4( tweened,1.0), tween ); baseColor = vec4(color,1.0); }""",GL_VERTEX_SHADER) fragment = shaders.compileShader(""" varying vec4 baseColor; void main() { gl_FragColor = baseColor; }""",GL_FRAGMENT_SHADER) self.shader = shaders.compileProgram(vertex,fragment)
由于我们的VBO现在有两个位置记录和一个颜色记录,所以每个顶点数据都包含3个额外的浮点数。
self.vbo = vbo.VBO( array( [ [ 0, 1, 0, 1,3,0, 0,1,0 ], [ -1,-1, 0, -1,-1,0, 1,1,0 ], [ 1,-1, 0, 1,-1,0, 0,1,1 ], [ 2,-1, 0, 2,-1,0, 1,0,0 ], [ 4,-1, 0, 4,-1,0, 0,1,0 ], [ 4, 1, 0, 4,9,0, 0,0,1 ], [ 2,-1, 0, 2,-1,0, 1,0,0 ], [ 4, 1, 0, 1,3,0, 0,0,1 ], [ 2, 1, 0, 1,-1,0, 0,1,1 ], ],'f') )
与uniform变量一样,我们在调用GL时,必须使用不透明的“location”值来引用我们的属性。
self.position_location = glGetAttribLocation( self.shader, 'position' ) self.tweened_location = glGetAttribLocation( self.shader, 'tweened', ) self.color_location = glGetAttribLocation( self.shader, 'color' ) self.tween_location = glGetUniformLocation( self.shader, 'tween', )
这里设置了OpenGLContext Timer类,以提供一个0.0-1.0的动画事件,并将其传递给给定函数。
self.time = Timer( duration = 2.0, repeating = 1 ) self.time.addEventHandler( "fraction", self.OnTimerFraction ) self.time.register (self) self.time.start () def Render( self, mode = 0): """Render the geometry for the scene.""" BaseContext.Render( self, mode ) glUseProgram(self.shader)
我们传入当前(当前帧)的动画分数值。timer会生成事件在空闲事件更新该值,
glUniform1f( self.tween_location, self.tween_fraction ) try:
每一个attribute(属性)数组,就像使用legacy pointer方法一样,都将绑定到当前(顶点)VBO。因为我们只使用一个VBO,可以只绑定一次。如果我们的位置数组值存储在不同的VBOs中,我们需要配合glVertexAttribPointer的调用,多次绑定和解除VBO的绑定。
self.vbo.bind() try:
与legacy pointer方法一样,我们必须显式地启用检索值,否则,GL将尝试读取所定义的每个属性的值。每个顶点的未启用的属性为默认值。也可以为attribute赋单个值用于每一个顶点(好像attribute是uniform一样)这段不是很理解强行翻译。
glEnableVertexAttribArray( self.position_location ) glEnableVertexAttribArray( self.tweened_location ) glEnableVertexAttribArray( self.color_location )
现在我们的顶点数组的每条记录占36个字节,glVertexAttributePointer的调用和legacy方法的调用非常相似,都是提供了数据数组将要存储的位置(attribute location)信息。
stride = 9*4 glVertexAttribPointer( self.position_location, 3, GL_FLOAT,False, stride, self.vbo ) glVertexAttribPointer( self.tweened_location, 3, GL_FLOAT,False, stride, self.vbo+12 ) glVertexAttribPointer( self.color_location, 3, GL_FLOAT,False, stride, self.vbo+24 ) glDrawArrays(GL_TRIANGLES, 0, 9) finally: self.vbo.unbind()
与legacy pointer操作一样,我们希望清理我们的数组,以便以后的任何调用都不会在试图读取这些阵列的记录(可能超出数组的末尾)时导致seg-fault。
glDisableVertexAttribArray( self.position_location ) glDisableVertexAttribArray( self.tweened_location ) glDisableVertexAttribArray( self.color_location ) finally: glUseProgram( 0 )
我们的普通事件处理函数将时间的fraction值存入我们的tween_fraction变量中。
tween_fraction = 0.0 def OnTimerFraction( self, event ): frac = event.fraction() if frac > .5: frac = 1.0-frac frac *= 2 self.tween_fraction =frac self.triggerRedraw() if __name__ == "__main__": TestContext.ContextMainLoop()
如果让物体的关键帧以顺序存储(相对于被打包成巨大的顶点记录),在gpu上制作补间动画可能会使用一个非常大的数组。因此,可以让动画代码选择两个关键帧,并为这些键帧启用数据指针。代码“能够”将每个动画的每一个帧存储为同一个顶点记录的一部分,但是这可能会导致在渲染时加载比连续情况下更多的数据。
译者附:
完整代码
from OpenGLContext import testingcontext BaseContext = testingcontext.getInteractive() from OpenGL.GL import * from OpenGL.arrays import vbo from OpenGLContext.arrays import * from OpenGL.GL import shaders from OpenGLContext.events.timer import Timer class TestContext( BaseContext ): """Demonstrates use of attribute types in GLSL """ def OnInit( self ): """Initialize the context""" vertex = shaders.compileShader(""" uniform float tween; attribute vec3 position; attribute vec3 tweened; attribute vec3 color; varying vec4 baseColor; void main() { gl_Position = gl_ModelViewProjectionMatrix * mix( vec4( position,1.0), vec4( tweened,1.0), tween ); baseColor = vec4(color,1.0); }""",GL_VERTEX_SHADER) fragment = shaders.compileShader(""" varying vec4 baseColor; void main() { gl_FragColor = baseColor; }""",GL_FRAGMENT_SHADER) self.shader = shaders.compileProgram(vertex,fragment) self.vbo = vbo.VBO( array( [ [ 0, 1, 0, 1,3,0, 0,1,0 ], [ -1,-1, 0, -1,-1,0, 1,1,0 ], [ 1,-1, 0, 1,-1,0, 0,1,1 ], [ 2,-1, 0, 2,-1,0, 1,0,0 ], [ 4,-1, 0, 4,-1,0, 0,1,0 ], [ 4, 1, 0, 4,9,0, 0,0,1 ], [ 2,-1, 0, 2,-1,0, 1,0,0 ], [ 4, 1, 0, 1,3,0, 0,0,1 ], [ 2, 1, 0, 1,-1,0, 0,1,1 ], ],'f') ) self.position_location = glGetAttribLocation( self.shader, 'position' ) self.tweened_location = glGetAttribLocation( self.shader, 'tweened', ) self.color_location = glGetAttribLocation( self.shader, 'color' ) self.tween_location = glGetUniformLocation( self.shader, 'tween', ) self.time = Timer( duration = 2.0, repeating = 1 ) self.time.addEventHandler( "fraction", self.OnTimerFraction ) self.time.register (self) self.time.start () def Render( self, mode = 0): """Render the geometry for the scene.""" BaseContext.Render( self, mode ) glUseProgram(self.shader) glUniform1f( self.tween_location, self.tween_fraction ) try: self.vbo.bind() try: glEnableVertexAttribArray( self.position_location ) glEnableVertexAttribArray( self.tweened_location ) glEnableVertexAttribArray( self.color_location ) stride = 9*4 glVertexAttribPointer( self.position_location, 3, GL_FLOAT,False, stride, self.vbo ) glVertexAttribPointer( self.tweened_location, 3, GL_FLOAT,False, stride, self.vbo+12 ) glVertexAttribPointer( self.color_location, 3, GL_FLOAT,False, stride, self.vbo+24 ) glDrawArrays(GL_TRIANGLES, 0, 9) finally: self.vbo.unbind() glDisableVertexAttribArray( self.position_location ) glDisableVertexAttribArray( self.tweened_location ) glDisableVertexAttribArray( self.color_location ) finally: glUseProgram( 0 ) tween_fraction = 0.0 def OnTimerFraction( self, event ): frac = event.fraction() if frac > .5: frac = 1.0-frac frac *= 2 self.tween_fraction =frac self.triggerRedraw() if __name__ == "__main__": TestContext.ContextMainLoop()
由于图片经过压缩,展示效果有些失真:
来自:https://blog.csdn.net/v_xchen_v/article/details/80338056