计算机图形学:实验三 光照与阴影
一、程序功能设计
设置了一个3D渲染场景,支持通过键盘和鼠标控制交互,能够动态调整光源位置、物体材质参数等,具有光照、阴影和材质效果的场景渲染。
OpenGL物体渲染和设置
- 创建3D物体:代码中通过 openGLObject 结构体表示一个3D物体,包含了顶点数据、着色器程序以及与物体的变换矩阵相关的信息。每个物体可以有多个顶点(如位置、颜色、法向量等),以及对应的材质、光源、阴影等属性。
- 绑定物体数据到OpenGL:bindObjectAndData 函数用于将物体的顶点、颜色、法线等数据传输给OpenGL。该函数使用 glGenVertexArrays 和 glGenBuffers 创建 VAO 和 VBO,并设置着色器的顶点属性(如位置、颜色、法向量等)。它还配置了物体的变换矩阵,使得物体可以在3D空间中进行旋转、缩放等操作。
光照和材质管理
- 光源和材质传递到着色器:bindLightAndMaterial 函数将物体的光源信息(如环境光、漫反射光、镜面反射光)和材质信息(如物体的环境光、漫反射、镜面反射强度)传递到着色器中。通过设置光源的位置、颜色等,影响物体的渲染效果。每个物体在渲染时都可以拥有不同的材质参数,例如镜面反射的高光系数。
- 计算相机位置:该函数还将相机的位置传递给着色器,影响光照计算中的视角部分,从而可以实现如镜面反射等效果。
渲染和变换矩阵处理
- 视图矩阵和投影矩阵:在 display 函数中,程序首先计算视图矩阵和投影矩阵,以确定摄像机的位置和视野范围。这些矩阵帮助实现3D物体的透视效果,并确定物体在屏幕上的位置。
- 变换操作:物体的变换(如旋转、缩放、平移)通过变换矩阵传递给着色器。着色器程序会使用这些变换矩阵来渲染物体的位置和方向。
光照和阴影效果
- 动态调整光源:通过键盘或鼠标操作,用户可以动态调整光源的位置,从而影响光照效果。这意味着场景中的物体会随光源的移动而产生不同的阴影和光照效果。
- 阴影计算:在 display 函数中,还计算并渲染了光源的投影效果。通过光源与物体的关系,程序生成阴影效果,创建更加真实的视觉效果。
交互功能
- 键盘交互:通过检测不同的按键和操作(按下、按住等),可以控制物体的颜色、材质、位置等。按键包括功能键(如 ESC、H、Q)和数字键(如 1、2、3 等),用于调整 ambient、diffuse 和 specular 的分量(RGB)以及物体的平移(通过 LEFT、RIGHT、UP、DOWN 控制)。
- 鼠标交互:鼠标左键按下时,可以获取当前鼠标的坐标,并根据这些坐标将光源的位置调整到与鼠标位置对应的屏幕坐标范围内。x 和 y 坐标转换为 -1 到 1 之间的值,并根据这个值设置光源位置。
二、程序代码实现
绘制场景
使用一个灰色平面来对物体的黑色阴影进行呈现。
添加平面呈现阴影
- 添加一个TriMesh*类型的plane:plane用作计算和展示物体阴影的投影平面。光源投射到该平面上的阴影通过投影矩阵计算,并渲染在平面上,同时plane通常位于场景的底部(如y=0平面)以便显示物体的阴影效果。
- 添加一个openGLObject类型的plane_object:plane_object 可以应用平移、旋转、缩放等变换操作来调整平面在场景中的位置和方向。它与 plane紧密配合,用于正确呈现物体的阴影效果。
在init中生成平面
- 生成平面并设置其变换:调用 plane->generateSquare() 方法生成一个平面(Square)。配置平面的位置(setTranslation)、旋转(setRotation)和缩放(setScale)。设置平面材质的环境光、漫反射、镜面反射和高光系数(为一个灰色平面)。
- 将平面的顶点数据传递给着色器:使用 bindObjectAndData() 将 plane 对象的数据绑定到 plane_object,并传递给着色器程序。
在display中渲染平面
- 使用 glBindVertexArray() 和 glUseProgram() 绑定 plane_object 对象的顶点数组对象(VAO)和着色器程序。
- 调用 bindLightAndMaterial() 将光源和物体材质的数据传递给着色器。
- 设置平面的变换矩阵 modelMatrix 并传递给着色器。
- 设置 isShadow 参数为 0,表示平面正常渲染。
- 最后绘制平面。
设置相机
设置相机并添加交互,实现从不同位置/角度、以正交或透视投影方式观察场景。
视图矩阵
getViewMatrix 方法通过调用 lookAt() 方法返回当前相机的视图矩阵。
lookAt() 方法使用了glm::lookAt()来根据相机的位置(eye)、目标点(at)和上方向(up)计算视图矩阵。通过 eye、at 和 up 向量来指定相机的方向和位置。
投影矩阵
getProjectionMatrix 方法根据isOrtho标志来选择不同的投影类型:
- 如果是正交投影(isOrtho为真),则调用 ortho() 方法。
- 如果是透视投影(isOrtho为假),则调用 perspective() 方法。
正交投影和透视投影的实现
- ortho:实现正交投影矩阵。正交投影不考虑远近距离,因此物体的大小在视野中保持不变。
- perspective:实现透视投影矩阵。透视投影模拟了相机视野中的深度感知,远离视点的物体看起来更小。
更新相机位置和视角
updateCamera:更新相机的位置和视角。通过rotateAngle和upAngle来调整相机的旋转角度,radius来控制相机与目标点(at)的距离。
- rotateAngle 控制相机绕目标点的水平旋转(水平视角)。
- upAngle 控制相机的俯仰角度(垂直视角)。
- radius 控制相机与目标点的距离。
键盘事件处理
keyboard:根据键盘输入来改变相机的状态(例如旋转、缩放、切换投影方式等)。
- U键:控制相机绕目标点的旋转(顺时针/逆时针)。
- I键:控制相机的俯仰角度(上下调整)。
- O键:控制相机与目标点的距离(放大/缩小)。
- P键:切换投影方式(透视/正交)。
- 空格键:将相机参数重置为默认值。
添加光照和材质效果
在init中设置光源参数
创建并配置光源 light 的位置和光照参数(环境光、漫反射、镜面反射),设置光源在三维空间中的位置 (0.0, 0.0, 2.0)。
在init中设置模型
- 设置物体的变换:mesh(物体)对象的平移、旋转和缩放变换:平移设置为 (0.0, 0.0, 1.0),即物体在z轴上偏移。旋转和缩放设置为默认值 (0.0, 0.0, 0.0) 和 (1.0, 1.0, 1.0)。在这里需要注意的是,如果物体与平面太贴近无法正确观察阴影时,可以调整setTranslation的第三个参数来改变物体和平面的垂直距离,更好地观察阴影。
- 设置物体的材质:配置 mesh 对象的环境光、漫反射、镜面反射和高光系数。
- 绑定物体的顶点数据到着色器:使用 bindObjectAndData() 函数将 mesh 对象的数据传递给着色器程序。
在display中渲染模型
- 使用 glBindVertexArray() 和 glUseProgram() 绑定 mesh_object 对象的顶点数组对象(VAO)和着色器程序。
- 调用 bindLightAndMaterial() 将光源和物体材质的数据传递给着色器。
- 创建物体的变换矩阵 modelMatrix 并将其传递给着色器,使用 glUniformMatrix4fv() 设置物体的变换矩阵、视图矩阵和投影矩阵。
- 使用 glUniform1i() 设置着色器的 isShadow 参数为 0,表示物体不是阴影。
- 绘制物体的三角形顶点。
添加阴影效果
光源的投影矩阵计算
getShadowProjectionMatrix()计算一个阴影投影矩阵,用于将光源的位置投影到一个平面上,通常是在 Y=0 平面上。
- 获取光源的位置:首先,通过 getModelMatrix() 获取光源的模型矩阵。光源的位置存储在 translation 中。使用 modelMatrix * glm::vec4(this->translation, 1.0) 计算光源在世界坐标系中的位置,结果存储在 light_position 中。
- 提取光源的坐标: lx, ly, lz 分别提取光源的世界坐标(light_position[0] 是 x 坐标,light_position[1] 是 y 坐标,light_position[2] 是 z 坐标)。
- 构建阴影投影矩阵:构建一个矩阵用于将物体投影到Y=0平面上。具体来说,该矩阵通过把物体的 z 坐标值映射到 Y=0 平面,将物体位置与光源方向的关系映射出来。
在display中添加阴影
- 计算阴影投影矩阵:将光源的投影矩阵getShadowProjectionMatrix()与物体的 modelMatrix 相乘,得到阴影的变换矩阵。
- 更新变换矩阵并将 shadowLocation 设置为 1,表示这是阴影绘制。
- 绘制阴影的三角形(GL_TRIANGLE_FAN)。
交互控制光源位置并更新阴影
mouse_button_callback 函数是处理鼠标按键事件的回调函数,主要功能是当鼠标左键按下时,获取鼠标的位置并将其映射到标准化设备坐标系,然后更新光源的位置。
- glfwGetCursorPos 用于获取当前鼠标在窗口中的像素位置。
- half_winx 和 half_winy 是窗口宽高的一半,用于将屏幕坐标转换成标准化坐标(NDC)。标准化坐标系的范围是 [-1, 1],其中 (0, 0) 是屏幕中心。
- lx 和 ly 将鼠标的屏幕坐标(像素)转换为标准化坐标,lx 和 ly 的值会在 [-1, 1] 范围内变化。这里需要注意的是ly 需要做反转处理(HEIGHT - y),因为在屏幕坐标系中,y 坐标的原点通常位于左上角,而标准化坐标系中的 y 原点通常在中心,且 y 坐标值是递增的。
- 光源的位置通过 light->getTranslation() 获取,并通过 light->setTranslation() 设置新的位置。这里只更新光源的 x 和 y 坐标,z 坐标保持不变。
三、程序运行结果
运行程序初始界面
调整光源位置
点击鼠标左键,改变光源位置,观察阴影的形成。效果图如下图所示。
观察不同物体形成的阴影
分别点击A、W、S、Q键,改变物体模型,观察不同物体的阴影形成。
调整物体的材质
点击0-9改变物体的材质:
- (shift) + 1/2/3:改变环境光的参数。
- (shift) + 4/5/6:改变漫反射的参数。
- (shift) + 7/8/9:改变镜面反射的参数。
- (shift) + 0:改变光泽度参数。