原文地址:http://pyopengl.sourceforge.net/context/tutorials/shader_4.html
本教程构建在往期教程的基础之上新增了:
前面我们已经提到过很多次,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()
由于图片经过压缩,展示效果有些失真: