基于Python的OpenGL 06 之摄像机
1. 引言
本文基于Python语言,描述OpenGL的摄像机
前置知识可参考:
笔者这里不过多描述每个名词、函数和细节,更详细的文档可以参考:
2. 概述
OpenGL的坐标变换流程图如下:

有图可知:
- 摄像机的参数(如,位置、视点、方向)决定视图
根据变化的相对性,控制摄像机的参数可以看成物体的变化(如,摄像机后移相当于物体后移)
观察矩阵可由摄像机的位置、视点和方向计算,如下图:

计算公式:
\]
其中R是右向量,U是上向量,D是方向向量,P是摄像机位置向量;
位置向量是相反的,因为我们最终希望把世界平移到与我们自身移动的相反方向
3. 编码
控制摄像机的参数实质就是控制观察矩阵(view)
生成一个观察矩阵需要位置、视点和方向向量,GLM的lookAt()函数可用于生成观察矩阵:
view = glm.lookAt(glm.vec3(0.0, 0.0, -3.0),
glm.vec3(0.0, 0.0, 0.0),
glm.vec3(0.0, 1.0, 0.0))
可选项,让摄像机的位置绕圆转动,会形成物体转动的感觉
radius = 10.0
camX = np.sin(glfw.get_time())*radius
camZ = np.cos(glfw.get_time())*radius
view = glm.lookAt(glm.vec3(camX, 0.0, camZ),
glm.vec3(0.0, 0.0, 0.0),
glm.vec3(0.0, 1.0, 0.0))
运行一下,结果图如下:

4. 完整代码
主要文件test.py:
import glfw as glfw
from OpenGL.GL import *
import numpy as np
from PIL.Image import open
import glm as glm
import shader as shader
glfw.init()
window = glfw.create_window(800, 600, "camera", None, None)
glfw.make_context_current(window)
VAO = glGenVertexArrays(1)
glBindVertexArray(VAO)
vertices = np.array([
-0.5, -0.5, -0.5, 0.0, 0.0,
0.5, -0.5, -0.5, 1.0, 0.0,
0.5, 0.5, -0.5, 1.0, 1.0,
0.5, 0.5, -0.5, 1.0, 1.0,
-0.5, 0.5, -0.5, 0.0, 1.0,
-0.5, -0.5, -0.5, 0.0, 0.0,
-0.5, -0.5, 0.5, 0.0, 0.0,
0.5, -0.5, 0.5, 1.0, 0.0,
0.5, 0.5, 0.5, 1.0, 1.0,
0.5, 0.5, 0.5, 1.0, 1.0,
-0.5, 0.5, 0.5, 0.0, 1.0,
-0.5, -0.5, 0.5, 0.0, 0.0,
-0.5, 0.5, 0.5, 1.0, 0.0,
-0.5, 0.5, -0.5, 1.0, 1.0,
-0.5, -0.5, -0.5, 0.0, 1.0,
-0.5, -0.5, -0.5, 0.0, 1.0,
-0.5, -0.5, 0.5, 0.0, 0.0,
-0.5, 0.5, 0.5, 1.0, 0.0,
0.5, 0.5, 0.5, 1.0, 0.0,
0.5, 0.5, -0.5, 1.0, 1.0,
0.5, -0.5, -0.5, 0.0, 1.0,
0.5, -0.5, -0.5, 0.0, 1.0,
0.5, -0.5, 0.5, 0.0, 0.0,
0.5, 0.5, 0.5, 1.0, 0.0,
-0.5, -0.5, -0.5, 0.0, 1.0,
0.5, -0.5, -0.5, 1.0, 1.0,
0.5, -0.5, 0.5, 1.0, 0.0,
0.5, -0.5, 0.5, 1.0, 0.0,
-0.5, -0.5, 0.5, 0.0, 0.0,
-0.5, -0.5, -0.5, 0.0, 1.0,
-0.5, 0.5, -0.5, 0.0, 1.0,
0.5, 0.5, -0.5, 1.0, 1.0,
0.5, 0.5, 0.5, 1.0, 0.0,
0.5, 0.5, 0.5, 1.0, 0.0,
-0.5, 0.5, 0.5, 0.0, 0.0,
-0.5, 0.5, -0.5, 0.0, 1.0
])
VBO = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferData(GL_ARRAY_BUFFER, 8 * vertices.size, vertices, GL_STATIC_DRAW)
glVertexAttribPointer(0, 3, GL_DOUBLE, GL_FALSE, int(8 * 5), None)
glEnableVertexArrayAttrib(VAO, 0)
glVertexAttribPointer(1, 2, GL_DOUBLE, GL_FALSE, int(8 * 5), ctypes.c_void_p(8 * 3))
glEnableVertexAttribArray(1)
image = open('./textures/container.jpg')
texture = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, texture)
# 为当前绑定的纹理对象设置环绕、过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.size[0], image.size[1], 0, GL_RGB, GL_UNSIGNED_BYTE, image.tobytes())
glGenerateMipmap(GL_TEXTURE_2D)
image2 = open('./textures/awesomeface.png')
texture2 = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, texture2)
# 为当前绑定的纹理对象设置环绕、过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image2.size[0], image2.size[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, image2.tobytes())
glGenerateMipmap(GL_TEXTURE_2D)
shader = shader.Shader("./glsl/test.vs.glsl", "./glsl/test.fs.glsl")
# 配置项
glEnable(GL_DEPTH_TEST)
shader.use()
glUniform1i(glGetUniformLocation(shader.shaderProgram, "texture1"), 0)
glUniform1i(glGetUniformLocation(shader.shaderProgram, "texture2"), 1)
while not glfw.window_should_close(window):
glClearColor(0.2, 0.3, 0.3, 1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
model = glm.rotate(glm.radians(-55.0)*glfw.get_time(), glm.vec3(1.0, 1.0, 0.0))
radius = 10.0
camX = np.sin(glfw.get_time())*radius
camZ = np.cos(glfw.get_time())*radius
view = glm.lookAt(glm.vec3(camX, 0.0, camZ),
glm.vec3(0.0, 0.0, 0.0),
glm.vec3(0.0, 1.0, 0.0))
projection = glm.perspective(glm.radians(45.0), 800 / 600, 0.1, 100.0)
shader.use()
glUniformMatrix4fv(glGetUniformLocation(shader.shaderProgram, 'view'), 1, GL_FALSE, glm.value_ptr(view))
glUniformMatrix4fv(glGetUniformLocation(shader.shaderProgram, 'projection'), 1, GL_FALSE, glm.value_ptr(projection))
glBindVertexArray(VAO)
glActiveTexture(GL_TEXTURE0) # 在绑定纹理之前先激活纹理单元
glBindTexture(GL_TEXTURE_2D, texture)
glActiveTexture(GL_TEXTURE1) # 在绑定纹理之前先激活纹理单元
glBindTexture(GL_TEXTURE_2D, texture2)
cubePositions = [
glm.vec3(0.0, 0.0, 0.0),
glm.vec3(2.0, 5.0, -15.0),
glm.vec3(-1.5, -2.2, -2.5),
glm.vec3(-3.8, -2.0, -12.3),
glm.vec3(2.4, -0.4, -3.5),
glm.vec3(-1.7, 3.0, -7.5),
glm.vec3(1.3, -2.0, -2.5),
glm.vec3(1.5, 2.0, -2.5),
glm.vec3(1.5, 0.2, -1.5),
glm.vec3(-1.3, 1.0, -1.5)
]
for cube in cubePositions:
model = glm.translate(cube)
model = glm.rotate(model, glfw.get_time(), glm.vec3(1.0, 0.3, 0.5))
glUniformMatrix4fv(glGetUniformLocation(shader.shaderProgram, 'model'), 1, GL_FALSE, glm.value_ptr(model))
glDrawArrays(GL_TRIANGLES, 0, 36)
glfw.swap_buffers(window)
glfw.poll_events()
shader.delete()
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 TexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
// 注意乘法要从右向左读
gl_Position = projection * view * model * vec4(aPos, 1.0);
TexCoord = aTexCoord;
}
片段着色器test.fs.glsl:
#version 330 core
out vec4 FragColor;
in vec2 TexCoord;
uniform sampler2D texture1;
uniform sampler2D texture2;
void main()
{
FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}
5. 参考资料
[1]摄像机 - LearnOpenGL CN (learnopengl-cn.github.io)
[2]OpenGL学习笔记(八)摄像机 - 知乎 (zhihu.com)
[3]LearnOpenGL-Python/camera_circle.py at master · Zuzu-Typ/LearnOpenGL-Python (github.com)
基于Python的OpenGL 06 之摄像机的更多相关文章
- 基于python的opcode优化和模块按需加载机制研究(学习与个人思路)(原创)
基于python的opcode优化和模块按需加载机制研究(学习与思考) 姓名:XXX 学校信息:XXX 主用编程语言:python3.5 个人技术博客:http://www.cnblogs.com/M ...
- 基于Python的机器学习实战:KNN
1.KNN原理: 存在一个样本数据集合,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一个数据与所属分类的对应关系.输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应 ...
- 看我如何基于Python&Facepp打造智能监控系统
由于种种原因,最近想亲自做一个基于python&facepp打造的智能监控系统. 0×00:萌芽 1:暑假在家很无聊 想出去玩,找不到人.玩个lol(已卸载),老是坑人.实在是无聊至极,不过, ...
- Python并发编程06 /阻塞、异步调用/同步调用、异步回调函数、线程queue、事件event、协程
Python并发编程06 /阻塞.异步调用/同步调用.异步回调函数.线程queue.事件event.协程 目录 Python并发编程06 /阻塞.异步调用/同步调用.异步回调函数.线程queue.事件 ...
- 基于Python实现的系统SLA可用性统计
基于Python实现的系统SLA可用性统计 1. 介绍 SLA是Service Level Agreement的英文缩写,也叫服务质量协议.根据SRE Google运维解密一书中的定义: SLA是服务 ...
- 【Machine Learning】决策树案例:基于python的商品购买能力预测系统
决策树在商品购买能力预测案例中的算法实现 作者:白宁超 2016年12月24日22:05:42 摘要:随着机器学习和深度学习的热潮,各种图书层出不穷.然而多数是基础理论知识介绍,缺乏实现的深入理解.本 ...
- 基于Python+Django的Kubernetes集群管理平台
➠更多技术干货请戳:听云博客 时至今日,接触kubernetes也有一段时间了,而我们的大部分业务也已经稳定地运行在不同规模的kubernetes集群上,不得不说,无论是从应用部署.迭代,还是从资源调 ...
- 关于《selenium2自动测试实战--基于Python语言》
关于本书的类型: 首先在我看来技术书分为两类,一类是“思想”,一类是“操作手册”. 对于思想类的书,一般作者有很多年经验积累,这类书需要细读与品位.高手读了会深有体会,豁然开朗.新手读了不止所云,甚至 ...
- psutil一个基于python的跨平台系统信息跟踪模块
受益于这个模块的帮助,在这里我推荐一手. https://pythonhosted.org/psutil/#processes psutil是一个基于python的跨平台系统信息监视模块.在pytho ...
- 一次完整的自动化登录测试-基于python+selenium进行cnblog的自动化登录测试
Web登录测试是很常见的测试!手动测试大家再熟悉不过了,那如何进行自动化登录测试呢!本文作者就用python+selenium结合unittest单元测试框架来进行一次简单但比较完整的cnblog自动 ...
随机推荐
- 多进程TCP服务端并发- 进程join方法 - IPC机制
目录 同步与异步 阻塞与非阻塞 综合使用 创建进程的多种方式 进程间数据隔离 进程join方法 IPC机制 生产者消费者模型 进程对象的多种方法 僵尸进程与孤儿进程 守护进程 多线程实现TCP服务端并 ...
- vscode下无法使用fresh的问题
使用了go get github.com/pilu/fresh ,需要 go install github.com/pilu/fresh. 在带用go.mod的版本中,go get只是下载代码和帮助修 ...
- 靶机练习 - 温故知新 - Toppo(sudo 提权)
重新做了一下以前做过的第一个靶机(https://www.cnblogs.com/sallyzhang/p/12792042.html),这个靶机主要是练习sudo提权,当时不会也没理解. 开启靶机, ...
- SOFAJRaft模块启动过程
本篇文章旨在分析SOFAJRaft中jraft-example模块的启动过程,由于SOFAJRaft在持续开源的过程中,所以无法保证示例代码永远是最新的,要是源代码有较大的变动,亦或出现纰漏.错误的地 ...
- Anaconda和pip常用命令汇总:简单,通俗易懂
前言 在学习 anaconda 的常用命令之前要先安装好 Anaconda for Windows,Anaconda for Ubuntu,这里我总结了 Windows 环境下和 Ubuntu 环境下 ...
- Stream流的特点_只能用一次-Stream流中的常用方法_map
Stream流的特点_只能用一次 Stream流属于管道流,只能被消费(使用)一次 第一个stream流调用完毕方法,数据就会流转到下一个stream上 而这时第一个stream流已经使用完毕,就会关 ...
- C#移除字符串中的不可见Unicode字符
背景 最近发现某个数据采集的系统拿下来的数据,有些字段的JSON被莫名截断了,导致后续数据分析的时候解析JSON失败. 类似这样 {"title": "你好 或者这样,多 ...
- 如何修剪git reflog历史
背景: vscode插件git-graph可以方便查看git-commit-graph,效果很好,关键是交互性很好.点选任意commit即可预览提交内容,实在是太方便了,比我之前用命令行上git lo ...
- 程序员必备的数据库知识 2:Join 算法
前言 连接(Join)是关系数据库重要特性,它和事务常被作为数据库与文件系统的两个重要区别项.程序员江湖一直流传着某某 baba 的神秘开发宝典,其中数据库部分有重要一条避免过多表的 Join,奈何 ...
- 如何在WSL下交叉编译openwrt
首先我们需要准备一个大小写敏感的文件夹. 然后拉取代码,并且执行代码的先决条件脚本. 注意,如果过程中某项条件不符,你可能要临时修复. 假如下图的组件判定失败(即出现ok=>fail) 那么你就 ...