【译】【PyOpenGL教程-着色器简介】 第一步(基本几何)

原文地址:http://pyopengl.sourceforge.net/context/tutorials/shader_1.html

第一步(基本几何)

在本教程中我们将会学到:
- 顶点着色器在GLSL中“必须”做什么
- 片元着色器“必须”做什么
- 什么是VBO对象
- 如何激活和停用着色器和VBO
- 如何渲染简单的几何体

image

首先我们进行imports,OpenGLContext testingcontext允许PyGame、wxPython者GLUT GUI使用相同的代码。这些imports将为机器检索合适的上下文环境,如果你没有安装任何额外的依赖包,比如说Pygame或者wxPython,将会为你的机器提供一个GLUT上下文。

from OpenGlContext import testingcontext
BaseContext = testingcontext.getInteractive()

现在导入我们将要使用的PyOpenGL功能。
OpenGL.GL包含标准OpenGL函数,更详细的信息可以阅读PyOpenGL的手册页。

from OpenGL.GL import *

OpenGL.arrays.vbo.VBO类是一个方便的包装器,它使得从PyOpenGL中使用顶点缓冲区对象变得更容易。它负责确定要使用哪个实现,创建偏移对象,甚至是基于基本切片的VBO中内容的更新。

from OpenGL.arrays import VBO

OpenGLContext.arrays只是一个抽象点,它可以导入Numpy(首选)或具有一些兼容性函数的旧数字库,以使Numeric看起来像新的Numpy模块。

from OpenGLContext.arrays import*

OpenGL.GL.shaders是一个用于访问着色器功能的便利库。

from OpenGL.GL import shaders

OpenGLContext contexts是Context的所有子类的合集,包含各种混合插件为不同的窗口类型,不同的交互机制等提供支持。这里的BaseContext是我们上面导入的TestingContext。

class TestContext(BaseContext):
    """Creates a simple vertex shader"""

OnInit方法是在已经成功创建了一个有效的OpenGL渲染上下文之后调用的。在OpenGL context被创建之前,你必须非常小心,不要调用(大多数)OpenGL接口(如果不能遵守这条规则通常会导致segfault或其他极端行为)。

    def OnInit(self):

OpenGL.GL.shaders.compileProgram函数是一个方便的函数,它执行许多基本操作,用于抽象出很多着色器设置的复杂性。GLSL Shaders开始作为OpenGL的扩展,后来成为Core OpenGL的一部分,但有些驱动程序不支持“核心”版本的着色器API。这种扩展机制是扩展OpenGL的“正常”方式,但它会造成杂乱的API。
每个“着色器程序”由许多简单的组件“着色器”组成,它们被链接在一起。目前有两种常见的着色器类型,即顶点着色器和片段着色器。较新的硬件可能包含其他着色器类型,例如几何着色器。

顶点着色器

我们的第一个着色器是VERTEX_SHADER,它必须计算每个要生成的顶点的顶点位置。通常,这是我们传递给GL的每个顶点的一个顶点,但是使用几何着色器等可以创建更多的顶点。
顶点着色器只需要做一件事,即生成一个gl_Position值,该值必须是vec4()类型。使用传统的OpenGL(我们在这里使用的),通常使用内置变量“gl_Vertex”来计算gl_Position,该变量是一个vec4(),代表固定功能渲染管线生成的顶点。
大多数OpenGL程序倾向于使用透视投影矩阵将笛卡尔模型的模型空间坐标转换为屏幕的“视图坐标”空间。传统的OpenGL包括通过“平移”,“旋转”,“缩放”等操作这些矩阵的函数。现代的OpenGL程序员需要自己计算矩阵(或者有一个库可以帮助他们)。
在这里,我们将使用OpenGLContext的内置矩阵计算,它将为我们设置“模型 - 视图矩阵”作为简单的透视变换。
视图坐标中的最终顶点位置是使用模型视图矩阵和要转换的顶点的简单点积来计算的。main()函数使用类C的GLSL语法定义,不返回任何内容(void)。这里使用的每个变量都是(内置的)全局变量,所以我们不必声明它们的数据类型。
OpenGL.GL.shaders.compileShader函数编译着色器并检查是否有编译错误。(使用glCreateShader,glShaderSource和glshaders.compileShader)。

        VERTEX_SHADER = shaders.compileShader("""#version 120
        void main() {
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
        }""", GL_VERTEX_SHADER)

顶点着色器处理顶点后,它会经过许多固定功能过程,包括“裁剪”过程,该过程可能会将单个顶点转换为多个顶点,以便仅将几何图形渲染到近剪裁的“前方”平面。
因此,如果三角形“映入眼帘”,则GL将生成两个顶点,这些顶点位于三角形与近剪裁平面相交的点上,并从最初的一个三角形创建三个三角形(所有剪裁计划也是如此为平截头体)。

片段着色器

固定功能操作将生成“片段”,这些片段可以被粗略地认为是“可能的像素”。它们可能代表一个子采样插值,或者最终会隐藏在另一个像素后面的值(透支)。我们的渲染器会得到一个(大量的)碎片,每个碎片都会根据我们的顶点着色器生成的三角形顶点的面积(gl_Position值)计算出一个位置。
片段着色器只需*做一件事,即生成一个gl_FragColor值,即确定片段应该是什么颜色。着色器还可以决定为不同的颜色缓冲区写入不同的颜色,甚至改变片段的位置,但其主要工作仅仅是确定像素的颜色(vec4()值)。
在我们的代码中,我们为每个像素创建一个新的颜色,这是一个纯绿色不透明(最后1个)值。我们将该值赋给(全局内置的)gl_FragColor值,我们完成了。

FRAGMENT_SHADER = shaders.compileShader("""#version 120
        void main() {
            gl_FragColor = vec4( 0, 1, 0, 1 );
        }""", GL_FRAGMENT_SHADER)

现在我们已经定义了着色器,我们需要将它们编译成可应用于几何图形的视频卡上的程序。shaders.compileProgram便捷功能执行以下操作:
- 创建着色器“程序”(glCreateProgram)
- 为每个提供的着色器*着色器附加到程序
- 链接程序(glLinkProgram)
- 验证程序(glValidateProgram,glGetProgramiv)
- 清理并返回着色器程序
着色器程序是一个不透明的GLuint,用于在与GL交谈时引用着色器。有了这些,我们已经创建好了一个OpenGL着色器,现在我们只需要给它一些东西去渲染。

        shaders.glUseProgram(self.shader)

顶点缓冲数据对象Vertex Buffer Data Object(VBOs)

现代OpenGL想要你将数据尽可能的加载到显卡上。对于几何信息,往往通过顶点缓冲对象(VBO)将信息加载到显卡上。VBO是存储在显卡上灵活的数据存储区域,具有数据流输入输出多样策略。
对我们来讲,我们可以将VBO当作是是显卡上的一片用于存储拷贝来的顶点描述信息的空间。我们将使用Numpy数组来定义这些数据,这是处理大量数值型数组数据的合适的格式。
现代的显卡在处理格式为单个的顶点数据都被紧凑的打包进VBO的数据时表现最佳。因此,数组中的每条记录需要渲染一个单个的顶点。由于我们的着色器只需要顶点坐标信息,我们将使用3 float点数据(注:不用python中的double,而是3个机器floating点数据)
现代OpenGL只支持三角和点类型的几何,因此最简单的绘制方法(尽管不是最快的)是指定一系列有序的三角形的顶点。本例中我们创建一个三角形和一个用户看起来是方形(实际上是两个共享顶点的三角形)
vbo.VBO类简单的获取一个数组类型的数据,并将其存入显卡中。它也有各种标志位去控制更高级的特性,这些我们将在之后讨论。

self.vbo = vbo.VBO(
            array([
                [ 0, 1, 0 ],
                [ -1,-1, 0 ], 
                [ 1,-1, 0 ], 
                [ 2,-1, 0 ], 
                [ 4,-1, 0 ], 
                [ 4, 1, 0 ], 
                [ 2,-1, 0 ], 
                [ 4, 1, 0 ],
                [ 2, 1, 0 ],
            ],'f') 
        )

至此,我们完成了所有的应用初始化工作,我们编译了shader,VBO也已经准备好用于绘制了。现在我们需要告诉OpenGL如何渲染我们的场景。context的Render()方法在所有OpenGL样板设置完成之后被调用,且场景需要在模型空间中被渲染。OpenGLContext已经创建了一个默认的模型视图矩阵,相机距离原点10个偏移的透视场景。它将场景清空为纯白,并准备好接受渲染命令。

    def Render(self, mode):
        """Render the geometry for the scene"""

渲染(Rendering)

告诉OpenGL使用我们已经编译好的着色器。在我们启用自定义的shader之前,GL使用固定管线着色器(legacy)。

        shaders.glUseProgram(self.shader)

现在我们通知OpenGL,我们要启用VBO作为几何数据源。有两种VBO类型可以使用,一是几何数据缓冲,一是索引数据缓存,这里使用的是几何数据缓冲。

        try: 
            self.vbo.bind() 
            try:

现在我们通知OpenGL去处理从顶点指针(指向VBO的地址)找到顶点(位置)数据。VBO就像是常规的数组数据一样,存储在外存而不是主存中。VBO对象实际上传递一个void指针给数组指针,该指针指向有效的VBO数组的0地址。

                glEnableClientState(GL_VERTEX_ARRAY); 
                glVertexPointerf( self.vbo

)

最终,我们告知OpenGL去绘制一些几何体。这里我们
通知OpenGL使用偏移从0到9的这些顶点去绘制一些三角形(因此,得到三个三角形)。glDrawArray函数总是从顶点缓存(vertex array)中“依次”绘制。我们之后将尝试使用索引绘制函数。

glDrawArrays(GL_TRIANGLES, 0, 9)

在完成了几何体的渲染之后,我们要清空OpenGL环境。我们要解绑VBO以便不使用VBO的代码可以执行,此外还要解绑shader,以便使用固定管线着色器(legacy)的几何体的渲染行为可以正常执行。

            finally:
                self.vbo.unbind() 
                glDisableClientState(GL_VERTEX_ARRAY); 
        finally:
            shaders.glUseProgram( 0 )

我们需要将该代码作为最顶层的脚本调用(不是被其他脚本import之后调用)。之前导入的TestingContext同样也提供了合适的主循环函数供给我们调用。

if __name__ == "__main__":
    TestContext.ContextMainLoop()

运行程序,我们应该看到一个绿色的三角形和一个长方形显示在黑色背景上,如图所示:
这里写图片描述
术语:
- frustum(截锥)

视野范围,也就是"相机"所能看到的那一部分世界空间,包含一个近裁平面和一个远裁平面,以及左边、右边、顶部和底部的剪裁平面。

这里写图片描述

  • GLSL
    OpenGL中定义的着色器语言有两个级别,早期的着器语言是低级装配语言,在这之后出现的GLSL是一个更高级一些的类C语言,本教程将使用GLSL语言。此外还有第三方语言比如CG,也可以编译同样的代码给DirectX和OpenGL渲染器。
  • legacy(遗留)
    OpenGL是一个古老的标准,大量的传统的API已经被OpenGL标准委员会弃用了。尽管大多数供应商仍支持旧的标准,但官方已经不支持使用旧的标准。这些遗留的API使用大量的全局状态变量确定的单一的渲染模型。新的API更加灵活,但需要使用起来也更加复杂。

译者附:

完整程序

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

class TestContext( BaseContext ):   

    def OnInit( self ):
        VERTEX_SHADER = shaders.compileShader("""#version 120 
            void main() {
             gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; 
             }""", GL_VERTEX_SHADER)
        FRAGMENT_SHADER = shaders.compileShader("""#version 120 
            void main() { 
            gl_FragColor = vec4( 0, 1, 0, 1 ); 
            }""", GL_FRAGMENT_SHADER)
        self.shader = shaders.compileProgram(VERTEX_SHADER,FRAGMENT_SHADER)
        self.vbo = vbo.VBO(
            array([
                [ 0, 1, 0 ],
                [ -1,-1, 0 ], 
                [ 1,-1, 0 ], 
                [ 2,-1, 0 ], 
                [ 4,-1, 0 ], 
                [ 4, 1, 0 ], 
                [ 2,-1, 0 ], 
                [ 4, 1, 0 ],
                [ 2, 1, 0 ],
            ],'f') 
        )

    def Render( self, mode): 
        """Render the geometry for the scene.""" 
        shaders.glUseProgram(self.shader) 
        try: 
            self.vbo.bind() 
            try:
                glEnableClientState(GL_VERTEX_ARRAY); 
                glVertexPointerf( self.vbo )
                glDrawArrays(GL_TRIANGLES, 0, 9)
            finally:
                self.vbo.unbind() 
                glDisableClientState(GL_VERTEX_ARRAY); 
        finally:
            shaders.glUseProgram( 0 ) 
if __name__ == "__main__":
    TestContext.ContextMainLoop()

猜你喜欢

转载自blog.csdn.net/v_xchen_v/article/details/80337352