一、目的
1、复习VBO、VAO、VEO,画一个旋转的立方体;
二、程序运行结果
三、 关于坐标的问题
1. 标准化设备坐标:
输入的顶点数据就应该在标准化设备坐标范围里面即:x,y,z的值都在(-1-1)之间。在这个区间之外的坐标都会被丢弃。
1.1一旦顶点数据传入顶点着色器中,那它们一定全都是标准化设备坐标了。
1.2标准化设备坐标符合右手定则,即原点在屏幕中心。
2. 屏幕空间坐标:
标准化设备坐标接着会变换为屏幕空间坐标(Screen-space Coordinates),这是使用glViewport函数,进行视口变换(Viewport Transform)完成的。
2.1屏幕空间坐标就是中心在左下角。
3. 屏幕空间坐标又会被变换为片段传入片段着色器中。
四、 关于VBO
1. 定义好顶点数据以后,我们会把它作为输入发送给图形渲染管线的第一个处理阶段:顶点着色器。它会在GPU上创建内存用于储存我们的顶点数据,还要配置OpenGL如何解释这些内存,并且指定其如何发送给显卡。顶点着色器接着会处理我们在内存中指定数量的顶点。
2. VBO则用来管理这些内存:它会在GPU内存(通常被称为显存)中储存大量顶点。使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。
3. 调用glBindBuffer(GL_ARRAY_BUFFER, VBO)。 从这一刻起,我们使用的任何(在GL_ARRAY_BUFFER目标上的)缓冲调用都会用来配置当前绑定的缓冲(VBO)。然后我们可以调用glBufferData函数,它会把之前定义的顶点数据复制到缓冲的内存中。
4. 现在我们已经把顶点数据储存在显卡的内存中,用VBO这个顶点缓冲对象管理。下面就可以创建一个顶点和片段着色器来真正处理这些数据。
5. 使用glVertexAttribPointer函数告诉OpenGL该如何解析顶点数据(应用到逐个顶点属性上):第一个参数指定我们要配置的顶点属性。还记得我们在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置值(Location)吗?它可以把顶点属性的位置值设置为0。因为我们希望把数据传递到这一个顶点属性中,所以这里我们传入0。
五、 关于VAO
1. 顶点数组对象(Vertex Array Object, VAO)可以像顶点缓冲对象那样被绑定,任何随后的顶点属性调用都会储存在这个VAO中。
2. 一个顶点有多种属性:位置,一种或多种颜色,一个或多个纹理坐标等。
3. 这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的VAO就行了。刚刚设置的所有状态都将存储在VAO中。
4. 要想使用VAO,要做的只是使用glBindVertexArray绑定VAO。
5. 从绑定之后起,我们应该绑定和配置对应的VBO和属性指针,之后解绑VAO供之后使用。当我们打算绘制一个物体的时候,我们只要在绘制物体前简单地把VAO绑定到希望使用的设定上就行了。(这里很重要,也就是说在渲染循环里面只需要绑定VAO就行了不需要绑定VBO)。
6. 但是在渲染循环中,如果我们只有一个VAO,也不必每次都绑定。
六、 关于VEO
1. 索引缓冲对象(Element Buffer Object,EBO,也叫Index Buffer Object,IBO)。
2. 和顶点缓冲对象一样,EBO也是一个缓冲,它专门储存索引,OpenGL调用这些顶点的索引来决定该绘制哪个顶点。所谓的索引绘制(Indexed Drawing)正是我们问题的解决方案。首先,我们先要定义(不重复的)顶点,和绘制出矩形所需的索引。
3. 与VBO类似,我们先绑定EBO然后用glBufferData把索引复制到缓冲里。同样,和VBO类似,我们会把这些函数调用放在绑定和解绑函数调用之间,只不过这次我们把缓冲的类型定义为GL_ELEMENT_ARRAY_BUFFER。
4. 最后一件要做的事是用glDrawElements来替换glDrawArrays函数,来指明我们从索引缓冲渲染。使用glDrawElements时,我们会使用当前绑定的索引缓冲对象中的索引进行绘制。
5. glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);第一个参数指定了我们绘制的模式,这个和glDrawArrays的一样。第二个参数是我们打算绘制顶点的个数,这里填6,也就是说我们一共需要绘制6个顶点。第三个参数是索引的类型,这里是GL_UNSIGNED_INT。最后一个参数里我们可以指定EBO中的偏移量
6. glDrawElements函数从当前绑定到GL_ELEMENT_ARRAY_BUFFER目标的EBO中获取索引。这意味着我们必须在每次要用索引渲染一个物体时绑定相应的EBO,这还是有点麻烦。
7. 不过顶点数组对象同样可以保存索引缓冲对象的绑定状态。VAO绑定时正在绑定的索引缓冲对象会被保存为VAO的元素缓冲对象。绑定VAO的同时也会自动绑定EBO。(这里很重要)
8. 当目标是GL_ELEMENT_ARRAY_BUFFER的时候,VAO会储存glBindBuffer的函数调用。这也意味着它也会储存解绑调用,所以确保你没有在解绑VAO之前解绑索引数组缓冲,否则它就没有这个EBO配置了。
七、实用函数
"""
程序名称:myGL_Funcs.py
编程: dalong10
功能: 一些OpenGL函数库
参考资料:Mahesh Venkitachalam OpenGL utilities.
"""
import OpenGL
from OpenGL.GL import *
from OpenGL.GL.shaders import *
import numpy, math
import numpy as np
from PIL import Image
def loadTexture(filename):
"""load OpenGL 2D texture from given image file"""
img = Image.open(filename)
imgData = numpy.array(list(img.getdata()), np.int8)
texture = glGenTextures(1)
glPixelStorei(GL_UNPACK_ALIGNMENT,1)
glBindTexture(GL_TEXTURE_2D, texture)
glPixelStorei(GL_UNPACK_ALIGNMENT,1)
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_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)
return texture
def perspective(zLeft,zRight,zTop,zBottom, zNear, zFar):
"""returns matrix equivalent for gluPerspective"""
return numpy.array([2*zNear/float(zRight-zLeft), 0.0, (zRight+zLeft) /float(zRight-zLeft), 0.0,
0.0, 2*zNear/float(zTop-zBottom), (zTop+zBottom) /float(zTop-zBottom), 0.0,
0.0, 0.0, (zFar+zNear)/float(zNear-zFar), 2.0*zFar*zNear/float(zNear-zFar),
0.0, 0.0, -1.0, 0.0],
numpy.float32)
def ortho(l, r, b, t, n, f):
"""returns matrix equivalent of glOrtho"""
return numpy.array([2.0/float(r-l), 0.0, 0.0, -(r+l)/float(r-l),
0.0, 2.0/float(t-b), 0.0, -(t+b)/float(t-b),
0.0, 0.0, -2.0/float(f-n), -(f+n)/float(f-n) ,
0.0, 0.0,0.0, 1.0],
numpy.float32)
def perspectivefov(fov, aspect, zNear, zFar):
"""returns matrix equivalent for gluPerspective"""
fovR = math.radians(fov)
f = 1.0/math.tan(fovR/2.0)
return numpy.array([f/float(aspect), 0.0, 0.0, 0.0,
0.0, f, 0.0, 0.0,
0.0, 0.0, (zFar+zNear)/float(zNear-zFar), -1.0,
0.0, 0.0, 2.0*zFar*zNear/float(zNear-zFar), 0.0],
numpy.float32)
def lookAt(eye, center, up):
"""returns matrix equivalent of gluLookAt - based on MESA implementation"""
# create an identity matrix
m = np.identity(4, np.float32)
# glm::vec3 f = glm::normalize(target - eye); // forward vector
# glm::vec3 s = glm::normalize(glm::cross(f, viewUp)); // side vector
# glm::vec3 u = glm::normalize(glm::cross(s, f)); // up vector
# glm::mat4 lookAtMat(
# glm::vec4(s.x, u.x, -f.x, 0.0), // 第一列
# glm::vec4(s.y, u.y, -f.y, 0.0), // 第二列
# glm::vec4(s.z, u.z, -f.z, 0.0), // 第三列
# glm::vec4(-glm::dot(s, eye),
# -glm::dot(u, eye), glm::dot(f, eye), 1.0) // 第四列
# );
# return lookAtMat;
f = np.array(center) - np.array(eye)
norm = np.linalg.norm(f)
f /= norm
# side vector
s = np.cross(f , up)
norm = np.linalg.norm(s)
s /= norm
# up vector
u = np.cross(s, f)
m[0][0] = s[0]
m[1][0] = s[1]
m[2][0] = s[2]
m[0][1] = u[0]
m[1][1] = u[1]
m[2][1] = u[2]
m[0][2] = -f[0]
m[1][2] = -f[1]
m[2][2] = -f[2]
m[3][0] = np.dot(s, eye)
m[3][1] = np.dot(u, eye)
m[3][2] = np.dot(f, eye)
return m
def translate(tx, ty, tz):
"""creates the matrix equivalent of glTranslate"""
return np.array([1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
tx, ty, tz, 1.0], np.float32)
def loadShaders(strVS, strFS):
"""load vertex and fragment shaders from strings"""
# compile vertex shader
shaderV = compileShader([strVS], GL_VERTEX_SHADER)
# compiler fragment shader
shaderF = compileShader([strFS], GL_FRAGMENT_SHADER)
# create the program object
program = glCreateProgram()
if not program:
raise RunTimeError('glCreateProgram faled!')
# attach shaders
glAttachShader(program, shaderV)
glAttachShader(program, shaderF)
# Link the program
glLinkProgram(program)
# Check the link status
linked = glGetProgramiv(program, GL_LINK_STATUS)
if not linked:
infoLen = glGetProgramiv(program, GL_INFO_LOG_LENGTH)
infoLog = ""
if infoLen > 1:
infoLog = glGetProgramInfoLog(program, infoLen, None);
glDeleteProgram(program)
raise RunTimeError("Error linking program:\n%s\n", infoLog);
return program
def compileShader2(source, shaderType):
"""Compile shader source of given type
source -- GLSL source-code for the shader
shaderType -- GLenum GL_VERTEX_SHADER, GL_FRAGMENT_SHADER, etc,
returns GLuint compiled shader reference
raises RuntimeError when a compilation failure occurs
"""
if isinstance(source, str):
print('string shader')
source = [source]
elif isinstance(source, bytes):
print('bytes shader')
source = [source.decode('utf-8')]
shader = glCreateShader(shaderType)
glShaderSource(shader, source)
glCompileShader(shader)
result = glGetShaderiv(shader, GL_COMPILE_STATUS)
if not(result):
# TODO: this will be wrong if the user has
# disabled traditional unpacking array support.
raise RuntimeError(
"""Shader compile failure (%s): %s"""%(
result,
glGetShaderInfoLog( shader ),
),
source,
shaderType,
)
return shader
八、主程序
"""
程序名称:GL_DrawCube.py
编程: dalong10
功能: 画一个立方体,绕轴(0,0.7071,0.7071)旋转
///
/// Y
/// |
/// 5___________1
/// /| /|
/// / | / |
/// 4--+--------0 |
/// | 7_ _ _ _ |_ 3____ X
/// | / | /
/// | / | /
/// |/__________|/
/// 6 2
/// /
/// Z
///
"""
import myGL_Funcs #通用 OpenGL 程序,见 myGL_Funcs.py
import sys, random, math
import OpenGL
from OpenGL.GL import *
from OpenGL.GL.shaders import *
import numpy
import numpy as np
import glfw
strVS = """
#version 330 core
layout(location = 0) in vec3 position;
uniform float theta;
void main(){
mat4 rot=mat4( vec4(0.5+0.5*cos(theta), 0.5-0.5*cos(theta), -0.707106781*sin(theta), 0),
vec4(0.5-0.5*cos(theta),0.5+0.5*cos(theta), 0.707106781*sin(theta),0),
vec4(0.707106781*sin(theta), -0.707106781*sin(theta),cos(theta), 0.0),
vec4(0.0, 0.0,0.0, 1.0));
gl_Position=rot *vec4(position.x, position.y, position.z, 1.0);
}
"""
strFS = """
#version 330 core
out vec3 color;
void main(){
color = vec3(1,0,0);
}
"""
class CubeModel:
def __init__(self, cube_vertices ,indices):
self.cube_vertices = cube_vertices
self.indices = indices
# load shaders
self.program = myGL_Funcs.loadShaders(strVS, strFS)
glUseProgram(self.program)
self.vertIndex = glGetAttribLocation(self.program, b"position")
# set up vertex array object (VAO)
self.vao = glGenVertexArrays(1)
glBindVertexArray(self.vao)
# set up VBOs
vertexData = numpy.array(cube_vertices, numpy.float32)
self.vertexBuffer = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer)
glBufferData(GL_ARRAY_BUFFER, 4*len(vertexData), vertexData, GL_STATIC_DRAW)
# set up EBOs
indiceData = numpy.array(indices, numpy.int32)
self.eboID = glGenBuffers(1)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,self.eboID)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 4 *len(indiceData), indiceData, 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)
# unbind VAO
glBindVertexArray(0)
glBindBuffer(GL_ARRAY_BUFFER, 0)
def render(self):
# use shader
glUseProgram(self.program)
theta = i*PI/180.0
glUniform1f(glGetUniformLocation(self.program, "theta"), theta)
# bind VAO
glBindVertexArray(self.vao)
# draw
glDrawElements(GL_TRIANGLES, self.indices.size, GL_UNSIGNED_INT, None)
# unbind VAO
glBindVertexArray(0)
if __name__ == '__main__':
import sys
import glfw
import OpenGL.GL as gl
def on_key(window, key, scancode, action, mods):
if key == glfw.KEY_ESCAPE and action == glfw.PRESS:
glfw.set_window_should_close(window,1)
# Initialize the library
if not glfw.init():
sys.exit()
# Create a windowed mode window and its OpenGL context
window = glfw.create_window(300, 300, "draw Cube ", None, None)
if not window:
glfw.terminate()
sys.exit()
# Make the window's context current
glfw.make_context_current(window)
# Install a key handler
glfw.set_key_callback(window, on_key)
PI = 3.14159265358979323846264
s = 0.5
positions = [
s, s, s, # 0
s, s, -s, # 1
s, -s, s, # 2
s, -s, -s, # 3
-s, s, s, # 4
-s, s, -s, # 5
-s, -s, s, # 6
-s, -s, -s # 7
]
indexes = [
0, 2, 1, 1, 2, 3, #// +X faces.
0, 1, 5, 0, 5, 4, #// +Y faces.
0, 4, 2, 2, 4, 6, #// +Z faces.
7, 6, 4, 7, 4, 5, #// -X faces.
7, 5, 3, 3, 5, 1, #// -Z faces.
7, 3, 2, 7, 2, 6 #// -Y faces.
]
ebo=numpy.array(indexes, numpy.int)
# Loop until the user closes the window
firstCube0 = CubeModel(positions,ebo)
a=0
while not glfw.window_should_close(window):
# Render here
width, height = glfw.get_framebuffer_size(window)
ratio = width / float(height)
gl.glViewport(0, 0, width, height)
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
gl.glClearColor(0.0,0.0,4.0,0.0)
i=a
firstCube0.render()
a=a+1
if a>360:
a=0
# Swap front and back buffers
glfw.swap_buffers(window)
# Poll for and process events
glfw.poll_events()
glfw.terminate()
九、参考资料
Garrett_Wale的博客 https://www.cnblogs.com/GarrettWale/p/11335044.html