姓名:卜凡

学号:2020211066

指导老师:符强

日期:2022-03-15 ~ 17

[#] 作业内容

  1. 使用 PyOpenGL 绘制两个不同颜色的三角形,其中一个三角形位置固定,另一个三角形穿过前一个三角形;
  2. 分别展示开启和未开启 depth test 时的绘制结果(穿越前后分别截图);
  3. 完成报告,需包含代码、结果图、原理介绍。

[1] 非渲染部分的框架

非渲染部分,也就是除了三角形绘制之外大体的代码框架,如下。

from OpenGL.GL import *     # 导入 OpenGL 库
from OpenGL.GLUT import *   # 导入 GLUT (OpenGL Utility Toolkit) 库
import time                 # 导入时间与日期库

### 全局变量
global t_begin, t    # 初始时间戳, 当前时间
t_begin = t = 0

### 重绘方法
def Draw():

    # 重置画面,重置图像缓存和深度缓存
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    
    ### >> LATER: 三角形绘制(见下)
    
    # 强制刷新缓冲,保证绘图命令将被执行
    glFlush()

### 空闲事件方法
def OnIdle():
    global t_begin, t
    t = time.time() - t_begin   # 更新当前时间
    glutPostRedisplay()         # 标记当前窗口需要重新绘制

### 主方法
if __name__ == "__main__":

    ### 1. 初始化
    glutInit()
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA)
    glutInitWindowSize(720, 720)            # 窗口大小
    glutCreateWindow("Homework 1 By July")  # 窗口标题,创建窗口
    # >> TODO: 修改这里开启(glEnable())和关闭(glDisable())深度校验
    glEnable(GL_DEPTH_TEST)                 # 开启深度校验
    glClearColor(1.0, 1.0, 1.0, 1.0)        # 设置 glClear 的背景色为 白色
    glOrtho(-1.2, 1.2, -1.2, 1.2, 2, -2)    # 设置正交投影的范围

    ### 2. 设置函数
    glutDisplayFunc(Draw)           # 重绘方法
    # glutReshapeFunc(OnResize)     # 窗口大小更新事件方法
    # glutKeyboardFunc(OnKey)       # 键盘事件方法
    glutIdleFunc(OnIdle)            # 空闲时间方法

    ### 3. 开始主循环
    t_begin = time.time()           # 记录初始时间戳
    glutMainLoop()

◇ 维护时间戳

这里引入了 Python 自带的 time 日期与时间模块,并创建两个全局变量 t_begint 来分别表示初始时间戳和当前程序执行时间(均为浮点数,单位均为秒),在开始主循环的时候初始化 t_begin ,并在空闲时间方法 OnIdle() 中更新 t ,以便后续能够根据 t 实现物体的运动。

[2] 渲染部分的实现

2.1 绘制固定三角形 $\triangle PQR$

渲染一个在 $z=0$ 平面上固定不动的三角形 $\triangle PQR$ ,其三个顶点的坐标分别位于$P(-1, -1, 0), \ Q(-0.5, 1, 0), \ R(1, -1, 0)$。 它将整体被渲染为红色( RGBA(1, 0, 0, 1) )。

2.2 绘制运动三角形 $\triangle ABC$

另,渲染一个随全局时间 $t$(即 t )沿 $z$ 轴做正弦往复运动的运动三角形 $\triangle ABC$ 。

  1. 坐标:其三顶点坐标为 $A(1,1,m), \ B(1,-1,m), \ C(-0.5, 0, -1+m)$ ,其中 $m = \sin t$ 。易知此三角形可达的 $z$ 范围为 $[-2, 1]$ 。注意 OpenGL 中为左手坐标系,摄像机朝向 $+z$ 轴,越近的物体 $z$ 坐标越小,故又称 $AB$ 为远顶点,$C$ 为近顶点。示意图如下。

Fig 2-1 所绘制图形的立体图示

Fig 2-1 所绘制图形的立体图示

  1. 颜色:此三角形的各顶点将随 $z$ 坐标的远近而取不同颜色,越近越绿,越远越黑。具体说来,对每个顶点而言,其颜色将随该点的 $z$ 坐标在 $[-2, 1]$ 中的取值而取 $[$绿色,黑色$]$ ( [RGBA(0,1,0,1), RGBA(0,0,0,1)] )中线性对应的值,三角形内部则作默认插值。

    为实现上述描述,编写区间线性映射算法如下:给定要映射的原值 $v_0$、原区间 $[l_0, r_0]$、新区间 $[l, r]$,构造区间线性映射 $[l_0, r_0] \to [l, r]$ 并获取原值在新区间尺度下的取值 $v$ 而返回。

    依据线性映射,有等比例公式:

    $$ \frac{v-l}{r-l} = \frac{v_0-l_0}{r_0-l_0} $$

    可得到结果如下(规定当给入原区间为空区间时,给出结果为新区间的左值)。

    $$ v= \begin{cases} \frac{(v_0-l_0)(r-l)}{r_0-l_0} + l ,& r_0 \neq l_0 \\ l,& r_0 = l_0 \end{cases} $$

    代码如下。它支持进行结果的区间限制 (Clamp),也支持反向区间(即左值大右值小的区间)。

    # 区间映射方法
    def IntervalMapF(v0:float, l0:float, r0:float, l:float, r:float, clamp:bool):
    
        if clamp:   # 进行结果的区间限制
            if (r0 >= l0):      # 正向区间或空区间
                if (v0 <= l0):      # 左值外部(更小)
                    return l
                elif (v0 >= r0):    # 右值外部(更大)
                    return r
            else:               # 反向区间
                if (v0 >= l0):      # 左值外部(更大)
                    return l            # 返回新左值
                elif (v0 <= r0):    # 右值外部(更小)
                    return r            # 返回新右值
    
        if (r0 - l0 == 0):          # 空区间
            return l
        else:                       # 非空区间
            return (v0 - l0) * (r - l) / (r0 - l0) + l
    

2.3 渲染部分 — 总结