【PyOpenGL教程-介绍着色器】attribute变量(补间动画)

发表于2018-07-11
评论0 2.8k浏览
属性值(实现补间动画)
原文地址: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

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引