-
2021-07-27 08:02:00
文档介绍:
计算机图形学实验及课程设计
孔令德
2012年春于太原
计算机图形学安验环境
实验任务书
实验目的与要求
实验目的:巩固学生对计算机图形学的直线扫描转换原理、
有效边表填充原理、三维***投影原理、 ZBuffer深度缓冲消隐
原理和真实感图形生成原理的理解,增加学生对真实感图形生
成算法的感性认识,强化训练学生使用Ⅴ isual c++的MFC编写
相关图形类的技能。
此前,课堂上已经完成《计算机图形学实践教程( Visual
C++版)》的43个验证性实验的讲解,在此基础上,要求学生
能综合使用全部教学内容完成综合性实验
实验要求:要求学生在实验前了解综合性实验的目的和要
求,观察实验效果图。在实验中认真理解每个类的结构,通过
搭积木的方式完成实验任务。实验结束后按要求整理相关类的
源程序,撰写实验报告,尤其需要对难点和重点进行详细说明。
计算机图形学安验环境
实验任务书
二、实验项目与提要
学时:教学总学时48,其中实验学时8。
制任意件军的线段
撿下绘制直起
必做
立方线框平正交报影
使用夏放边法坛酉项充多边形。立方
4·颈色渐受立方体
沛使用逶视投绘制,使用凸多面体消隐
选做
球体ad光照资型
形面片内点的强,球体转,视点和光必做
a:球体2e照
沐面片内点时法矢根据们片内点白选前
获得点的光强。球体旋转,视点和
计算机图形学安验环境
实验任务书
三、成绩考核方法
本实验与计算机图形学课程同步开设,成绩占期末总成绩
的20%~40%。
四、本课程与其他课程的联系和分工
先修课程:高等数学、线性代数、MFC程序设计、数据结
构
计算机图形学安验环境
实验1绘制金刚石图案
11实验目的
掌握二维坐标系模式映射方法。
掌握动态内存的分配和释放方法
掌握二维点类的定义方法。
掌握对话框的创建及调用方法。
掌握对话框的数据交换和数据校验方法
掌握TeSt工程实验框架的创建方法
掌握金刚石图案的设计方法
计算机图形学安验环境
实验1绘制金刚石图案
12实验要求
定义二维坐标系原点位于屏幕中心,x轴水平向右为正,y轴铅
直向上为正。
■以二维坐标系原点为圆心绘制半径为r的圆,将圆的n等分点使
用直线彼此连接形成金刚石图案
程序运行界面提供“文件”、“绘图”和“帮助”三个弹出菜
单项。“文件”菜单提供“退出”子菜单项,用于退出应用程
序;“绘图”菜单提供“金刚石”子菜单项,用于绘制金刚石
图案;“帮助”菜单提供“关于”子菜单项,用于说明开发信
■选择“金刚石”子菜单项,打开“输入参数”对话框,输入
“等分点个数”和“圆的半径”。
■在屏幕客户区中心绘制金刚石图案。
计算机图形学安验环境
实验1绘制金刚石图案
13效果图
将半径为300的圆,划分30个等分点后,得到的金刚石图案实
验效果如图1-1所示。
图1-1金刚石图案效果图
计算机图形学安验环境
实验2绘制任意斜率的直线段
21验目的
掌握任意斜率直线段的中点 Breseηham扫描转
换算法
掌握 CLine直线类的设计方法。
■掌握状态栏编程方法。
计算机图形学安验环境
实验2绘制任意斜率的直线段
22实验要求
■设计 CLine直线类,其数据成员为直线段的起点坐标P和终点
坐标P1,成员函数为 MoveTo和 Lineto函数。
CLine类的 Lineto(函数使用中点 Bresenham算法绘制任意斜
率k的直线段,包括k=±、k>1、0≤k≤1、-1≤k<0和k
种情况。
在屏幕客户区按下鼠标左键选择直线的起点,保持鼠标左键按
下并移动鼠标到另一位置,弹起鼠标左键绘制任意斜率的直线
段
在状态栏动态显示鼠标光标移动时的位置坐标。
计算机图形学安验环境
健实验2绘制任意斜率的直线段
23效果图
任意斜率的直线段绘制效果如图2-1所示。
图2-1任意效率直线段绘制效果图
内容来自淘豆网www.taodocs.com转载请标明出处.
更多相关内容 -
计算机图形学综合实验
2013-11-15 19:32:52该资源为计算机图形学课程综合实验内容,是在visualstudio平台下用OpenGL编写的程序。该程序综合了坐标变换、键盘鼠标交互控制、视点移动、光源、纹理贴图等图形学基本要求,主要实现了以下功能:1、在三维空间内... -
福州大学计算机图形学综合实验(代码+报告).zip
2021-08-09 13:31:11实验内容包括: 基于WebGL的交互式图形绘制 基于WebGL的摄像漫游与环视(三维镂垫或迷宫等) 基于WebGL的纹理映射与光照渲染 内含代码与报告(报告已去掉个人心得部分) -
计算机图形学综合实验设计
2013-03-16 09:04:58贝赛尔曲线屏保程序 (1) 通过对话框设置曲线的宽度(即同时显示曲线的条数) (2) 曲线的颜色是渐变的。 (3) 具有较好的动画效果,消除闪烁现象 -
计算机图形学实验和综合实验和综合实验报告
2012-01-11 16:28:15此拼图游戏是一个运用程序,用户通过鼠标点击小图片进行拼图,用户可根据自己的爱好选择图片,还有背景音乐,游戏有计时功能,游戏过程中可暂停,继续,结束。如果拼图完成,还有一个排行榜,每次只显示所有时间最少... -
华南理工大学计算机图形学实验
2018-05-20 22:43:45本实验为综合实验, 任务是利用光线跟踪算法进行Whitted全局光照计算,并对读入场景进行真实感绘制。(特别提醒: 网上类似的projects可以参考,但不能照抄. 如... -
中北大学大数据学院计算机图形学实验报告(共4个)(2018届)(可运行).zip
2021-08-23 17:44:54实验一 二维镂空正三角形绘制 熟悉 VC++&OpenGL 的开发环境,设计镂空正三角形图形...能够进行三维图形绘制算法设计,描述真实感图形绘制方法,综合应用图形设计和工程设计的方法,设计动画软件,完成规范的实验报告。 -
山东大学计算机图形学实验二
2020-06-08 23:22:38【教学目标和要求】了解图形系统的性能, 掌握可交互的OpenGL应用程序的开发设计的方法,掌握系统处理鼠标和键盘事件的编程方法,掌握OpenGL应用程序的拾取机制,掌握并学会利用OpenGL开发场景漫游程序的编程方法,掌握并... -
计算机图形学OpenGL、codeblock、计算机图形学综合性实验
2014-12-20 13:09:091.1实现绘制Bresenham直线 运用Bresenham算法,通过点绘制实现直线。 1.2实现种子填充算法 运用4连通种子填充算法,实现填充矩形的颜色填充和矩形框内的颜色填充。 1.3实现几何变换 运用glRotated、glTranslated、... -
计算机图形学实验5-绘制动态三视图
2022-05-22 14:32:33①下载后,解压(记住一定要解压后再用vs打开代码,不解压就打开代码,vs会无法运行) ②运行后,点击左上角图标,即可看到结果 -
3D计算机图形学(OpenGL版).pdf
2014-03-05 02:14:483D计算机图形学(OpenGL版).pdf--作者:巴斯(中文版) -
计算机图形学实验
2021-01-24 23:07:31大二学期的计算机图形学课程,内容包含扫描转换算法,裁剪算法,图形变换,三维图形投影和消隐,曲线和曲面;从完成情况来看虽算不上十分出色,但也花了很多时间和精力,代码必然存在一些冗余、不足和待改进之处,故...实验介绍
大二学期的计算机图形学课程,内容包含扫描转换算法,裁剪算法,图形变换,三维图形投影和消隐,曲线和曲面;从完成情况来看虽算不上十分出色,但也花了很多时间和精力,代码必然存在一些冗余、不足和待改进之处,故公开和大家交流和参考
软件环境
- VC++ 6.0
- easyX库
实验内容
实验一 —— 扫描转换算法
(●’◡’●) 效果展示 实验题1-1:绘制任意斜率的直线 实验题1-3:用中点画圆法画圆 实验题1-4:中点画圆法画椭圆 实验题1-5:中点画圆法画圆弧 实验题1-7:具有宽度的直线 实验题1-8:具有宽度的圆和椭圆 实验一中的代码并未进行优化,从截图可以看出,UI界面不够友好(毕竟刚学习这门课),操作起来比后面的实验更复杂
实验二 —— 裁剪算法
(●’◡’●) 效果展示 实验题2-1:Cohen-Sutherland算法实现直线段裁剪 实验题2-2:Liang-Barsky算法实现直线段裁剪 实验题2-5:综合算法实现多边形裁剪(自创) 多边形裁剪:该算法实现了对任意多边形的裁剪,程序中设置多边形的顶点为固定的9个。算法上的实现分为三步:
- 第一,先画裁剪框内的线段:用直线段裁剪算法对多边形进行裁剪,最后留下在裁剪框内的线段
- 第二,画裁剪框四个顶点旁的线。先判断裁剪框的四个顶点是否在多边形内,具体判断方法是:过该顶点一条射线与多边形的每条线段相交,如果交点有奇数个,则在多边形内,反之则在多边形外。如果顶点在多边形内,那么它邻接的在裁剪框边上的点与它之间一定有连线
- 第三,画裁剪框四条边上与顶点无交点的线段。取出在多边形外的四边形顶点,从这一点开始,往邻接的两条边方向画,得到该边和多边形线段的交点,然后画第奇数个到下一个点上的线段,若有奇数个点,则略去最后一个。例如:假设左下角的点在多边形外,则从该点向上和向右画,当向上画时,假如有5个交点(y1,y2,y3,y4,y5),则画线段(y1,y2),(y3,y4),y5则省略
实验三 —— 图形变换
(●’◡’●) 效果展示 实验题3-2:实现多步复合变换,并显示动画效果 实验题3-3:任意直线的对称变换 不足之处:各种二维变换在数学上理论上都能得到结果,但实现在程序里难免有舍入,会导致一些四边形放大倍数时,四边形的同一边可能有错位
实验四 —— 三维图形投影和消隐
(●’◡’●) 效果展示 实验题4-4和4-5:完成图形绕某一坐标轴旋转(包括三视图和正等轴测图) 为了完成正十二面体的旋转,作者查了很多资料,最终得以实现,是一件很有趣的事情,如下是源码中坐标对应的点,如有需要可以参考
不足之处:由于作者采用的消隐算法是外法线消隐算法,此算法只对凸多面体有效,这是该消隐算法的局限;且该消隐算法是按照每个面的边数都相等的多面体来具体实现的,于是对于由三角形和四边形等组成的混合型多面体无法在原有算法中实现,若有需要还得进一步修改
实验五 —— 曲线和曲面
(●’◡’●) 效果展示 实验题5-2绘制Bezier曲线 实验题5-4绘制B样条曲面 存在问题:Bezier曲线的代码中对点采用递归方式计算,当增加一个点时,需要对之前已经计算过的低阶Bezier函数重新进行计算,使得空间消耗和时间消耗增加(当时并未进行优化)。所以当点越来越多时,程序运行速度会大大降低,甚至可能程序崩溃(当有10几个点时)
实验源码
- 下载方式一
本实验部分内容可通过资源下载,包含Bezier曲线和B样条曲面,正十二面体旋转——外法线消隐,直线和多边形裁剪等
- 下载方式二
通过GitHub免费下载完整源码,click here
-
江苏大学-计算机图形学第三次实验报告-二维图形变换
2021-07-20 04:47:201、计算机科学与通信工程学院实验报告课程计算机图形学实验题目二维图形变换学生姓名学号专业班级指导教师日期成绩评定表评价内容具体内容权重得分论证分析方案论证与综合分析的正确、合理性20%算法设计算法描述的...《江苏大学-计算机图形学第三次实验报告-二维图形变换》由会员分享,可在线阅读,更多相关《江苏大学-计算机图形学第三次实验报告-二维图形变换(13页珍藏版)》请在人人文库网上搜索。
1、计算机科学与通信工程学院实验报告课程计算机图形学实验题目二维图形变换学生姓名学号专业班级指导教师日期成绩评定表评价内容具体内容权重得分论证分析方案论证与综合分析的正确、合理性20%算法设计算法描述的正确性与可读性20%编码实现源代码正确性与可读性30%程序书写规范标识符定义规范,程序书写风格规范20%报告质量报告清晰,提交准时10%总 分指导教师签名二维图形变换1. 实验内容完成对北极星图案的缩放、平移、旋转、对称等二维变换。首先要建好图示的北极星图案的数据模型(顶点表、边表)。另外,可重复调用“清屏”和“暂停”等函数,使整个变换过程具有动态效果。2. 实验环境操作系统:Windows XP开。
2、发工具:visual studio 20083. 问题分析为了建立北极星图形,首先在二维空间中根据坐标绘制出北极星图形。并且在此坐标系中确定好走笔顺序以便于进行连线操作。同时需要好好的使用清屏函数以使得显示正常。1. 放大缩小变换 放大缩小变换公式为:x=x.a, y=y.d; 其中a,d分别为x,y方向的放缩比例系数。 可通过不同的比例系数来显示程序运行结果。当a=d时为等比例放缩操作。可令变换矩阵为T。2. 对称变换 包括以x轴对称、y轴对称和原点O对称三种。由于屏幕坐标只有第一象限,我们可以将原点平移到(500,240)处。在第一象限画出一个三角形,然后分别求出三个对称图形。3. 旋转变。
3、换 将图形上的点(x,y)旋转角度,得到新的坐标(x,y)为:x=xcos-ysin, y=xsin+ycos; 旋转矩阵T为4平移变换4. 算法设计5. 源代码/北极星void hzbjx(CDC* pDC,long x18,long y18) CPen newPen1,*oldPen; newPen1.CreatePen(PS_SOLID,2,RGB(255,0,0);oldPen = pDC-SelectObject(&newPen1); POINT vertex111=x1,y1,x2,y2,x3,y3,x4,y4,x5,y5,x3,y3,x1,y1,x6,y6,x3,y3,x7,y7。
4、,x5,y5; pDC-Polyline(vertex1, 11); newPen1.DeleteObject(); newPen1.CreatePen(PS_SOLID, 2, RGB(0,255,0); oldPen = pDC-SelectObject(&newPen1); POINT vertex25=x6,y6,x8,y8,x9,y9,x3,y3,x8,y8; pDC-Polyline(vertex2, 5); POINT vertex35=x4,y4,x10,y10,x11,y11,x3,y3,x10,y10; pDC-Polyline(vertex3, 5); newPen1.D。
5、eleteObject(); newPen1.CreatePen(PS_SOLID, 2, RGB(255,0,90); oldPen = pDC-SelectObject(&newPen1); POINT vertex411=x12,y12,x13,y13,x3,y3,x9,y9,x14,y14,x15,y15,x3,y3,x11,y11,x12,y12,x3,y3,x14,y14; pDC-Polyline(vertex4, 11); newPen1.DeleteObject(); newPen1.CreatePen(PS_SOLID, 2, RGB(0,100,255); oldPen 。
6、= pDC-SelectObject(&newPen1); POINT vertex55=x15,y15,x16,y16,x3,y3,x16,y16,x7,y7; pDC-Polyline(vertex5, 5); POINT vertex65=x2,y2,x17,y17,x3,y3,x17,y17,x13,y13; pDC-Polyline(vertex6, 5); pDC-SelectObject(oldPen); Sleep(10); void CDiamondView:Polaris()InvalidateRgn(NULL);UpdateWindow();CDC *pDC = GetD。
7、C();long x18,y18; x1=553,y1=100;x2=515,y2=251;x3=553,y3=338; x4=516,y4=426; x5=553,y5=551; x6=589,y6=253; x7=591,y7=426; x8=678,y8=212; x9=641,y9=311; x10=454,y10=438; x11=478,y11=364; x12=415,y12=338; x13=466,y13=301; x14=703,y14=338; x15=640,y15=375;x16=665,y16=450; x17=440,y17=226; hzbjx(pDC,x,y)。
8、;Sleep(500);InvalidateRect(NULL); UpdateWindow(); long x118,y118; /缩小for(double n=1;n=0.5;n-=0.01)for (int i=1;i=0;t-=0.01)for(int k=1;k18;k+) x6k=Round(xk*cos(t)-yk*sin(t)-x3*cos(t)+y3*sin(t)+x3); y6k=Round(xk*sin(t)+yk*cos(t)-x3*sin(t)-y3*cos(t)+y3); hzbjx(pDC,x6,y6);InvalidateRect(NULL); UpdateWi。
9、ndow(); Sleep(500); long x718,y718;/沿X=1000对称 for(int l=1;l18;l+) xl=Round(xl*0.5);yl=Round(yl*0.5);x7l=1000-xl; y7l=yl; hzbjx(pDC,x,y);hzbjx(pDC,x7,y7);Sleep(200);long x818,y818;/沿Y=600对称 for(int l=1;l18;l+) x8l=xl; y8l=600-yl; hzbjx(pDC,x,y);hzbjx(pDC,x8,y8);Sleep(200);long x918,y918;for(int l=1;l18;l+) x9l=1000-xl; y9l=600-yl; hzbjx(pDC,x,y);hzbjx(pDC,x9,y9);6. 程序运行结果图1 北极星图案的数据模型图2 北极星图案的缩放图3 北极星图案的平移图4 北极星图案的旋转图5 北极星图案的对称7. 总结在这次的实验中,我根据图形变换的基本原理简单实现了图形的几种变换。但是由于自己对框架和函数运用的不熟悉,在实验中遇到了一些本可以被避免的困难。因此,以后必须要在这方面有所改进。通过这次的实验,我知道了如何利用程序进行二维图形的一些基本变换。在以后的学习过程我还需要不断努力,这样才能在这门课程的学习中有所收获。
-
计算机图形学 实验三 相机、阴影、光照
2020-12-08 10:32:48文章目录实验3.1 相机定位一、 实验目的二、 理论背景1.各个坐标系世界坐标系相机坐标系2. 齐次坐标3. OpenGL观察变换三、 实验内容LookAt函数的推导四、 示例和练习 好了这一章开始难度比之前要大了 其实是我太菜 ...实验3.1 相机定位
一、 实验目的
- 了解OpenGL中观察变换(模视变换和投影变化)的基本原理
- 掌握OpenGL中相机观察变换矩阵的推导
- 掌握OpenGL中实现相机定位观察变换
二、 理论背景
1.各个坐标系
坐标系这里没完全搞懂 建议先别看我的 可以直接看openGL的别人怎么写的:
https://learnopengl-cn.readthedocs.io/zh/latest/01%20Getting%20started/08%20Coordinate%20Systems/
在讲述相机之前,这里额外补充几个坐标系的内容,在之后会用到在坐标系间的切换:
世界坐标系
即在场景中的绝对坐标,通过世界坐标系的坐标值可以定位任何一个地方的物体
相机坐标系
总结下有这几个属性:- 相机坐标系的初始位置位于原点,默认世界坐标系与相机坐标系重合。
- 相机位于原点,无法观察同样位于原点的模型。
- 相机朝向z轴负向,因此如果观察的某对象需要在z轴正方向的投影平面(在OpenGL中通常是近裁剪平面)上成像,那么必须要将相机沿着z轴正方向往后移动一定距离。
- 当移动相机时,相机所在的坐标系也会跟着移动(因此有时就需要坐标系的转换)
相机还具有这两个属性:VUP和VPN
其中VPN指定了相机的胶片平面,而平面是由其法向量决定的。
但是如果只是指定了VPN,相机还可以绕VPN旋转,此时我们再加上VUP,就能完全固定一个相机的位置了。
VUP和VRP这两个属性会随着相机的旋转而发生变化。换言之,只有确定了这两个属性才能完全确定。
但是在OpenGL中我们习惯用目标点减去眼睛的方向向量来获得VPN,详细见面的内容。
模型视图矩阵
说实话这里并没有搞很懂,以后哪天来仔细研究吧
这个矩阵构造起来很麻烦,但是我们可以写一个函数来实现,就是LookAt函数,可以见“三、实验内容”有讲函数的推导和使用。我们使用LookAt函数传入eye at 和up
前两个参数分别是眼睛和目标点(都是世界坐标系),up是世界坐标系下的向量(0,1,0)
虽然这里理解的不是很懂,但是应该可以简单的理解为
“对于相机坐标,我们本质上是将世界坐标进行平移和旋转,然后让它正确地显示出从相机视角观察的特性。我们同样通过一个变换矩阵来将世界坐标转换为相机坐标:”
其实就是由LookAt函数实现了世界坐标系到相机坐标系下的切换。ndc坐标系
Normalized Device coordinate System(标准化设备坐标),因为不同显示器长宽比不同,我们没办法以像素来衡量他们的坐标。因此我们利用百分比的思想,将坐标归一化到 [-1, 1] 的范围内。
但是这里需要注意一点的是,在上篇博客所讨论到的mouse函数中传入的x和y,并不是这里的x和y,而是传入的是一个像素值,(坐标轴在原点,范围为0到500)淦 下面这图的坐标系好像写错了,变换推导大概没错,坐标系的知识有点复杂
想把这个像素值转化为-1到1的坐标系的话,可以通过以下函数来转化:这里节选自我计图实验的推导写的,(随便写的太丑别介意)
然后在mouse函数中修改:
2. 齐次坐标
首先回顾一下齐次坐标的概念,详细内容请参考实验2.3。
3维数据可以通过3维向量与3×3矩阵的乘法操作,来完成缩放和旋转的线性变换,也就是说三维空间的缩放矩阵和旋转矩阵都可以采用3维方阵表示。但是对3维坐标的平移是无法通过这样类似的操作完成,此时我们还需要额外的一个向量,将三维空间中的点移动到另外的位置,如下表示。
这里将原始点加上第四个维度,就是齐次坐标的表示。三维空间中的点的坐标通过将齐次坐标中前三维度的坐标值除以最后一个维度值得到。
在后面章节中将会看到,齐次坐标多出的第四分量是用来实现透视投影变换的。
3. OpenGL观察变换
计算机图形学中的观察是建立在虚拟照相机模型的基础上,如下左图所示。而下右图则是对虚拟照相机模型的抽象表示,其本质上都包含有相同的元素:对象、观察者、投影线和投影平面。投影线相交于投影中心(COP:Center of Projection),而COP对应于人眼或者相机镜头,从COP点发出投影光线将物体投影在投影平面上而成像。所以在虚拟照相机模型中,如果需要正确的表现出投影对象在投影平面上的成像效果,我们必须首先确定相机的位置和方向。
在前面的章节中,确定了这样一个事实:只要顶点着色器输出的顶点位于裁剪体(或视见体)内部,那么这些顶点会被送入绘制流水线后面的光栅化模块。如果我们加入自定义的相机姿态,那么对相机的控制则尤为重要,因为相机的位置和朝向将决定怎么发出投影射线而将物体投影到投影平面上。
为了使得观察过程更加灵活,我们把它分解成两个基本的操作。- 首先,必须设置照相机的位置和方向,该操作由模-视变换来完成,当顶点经过该变换之后将位于相机坐标系中。
- 然后是使用投影变换,也就是说把指定的投影(正交投影或透视投影)应用到顶点上。并将默认的视见体内部对象变换到指定的裁剪立方体的内部,整个过程如下图所示。
这一节中我们主要考虑相机的观察变换。所谓变换就是设置一个矩阵,然后乘以三维顶点的坐标(考虑齐次坐标系),得到变换之后的坐标值。正如上述,模-视变换将顶点变换到相机坐标系,所以相机的观察变换必须要包含相机的位置和朝向等信息。
模-视变换是建模变换和观察变换的级联,通常建模变换矩阵是将对象变换到世界坐标,而观察变换将世界坐标转换到相机坐标。一般来说,建模变换矩阵是单位阵,我们不需要额外处理。即,
而对于照相机来说,其初始方向通常指向z轴负方向,这样才能看见位于相机前方的模型。考虑一个位于原点的对象,由于照相机初始位置也在原点,方向指向z轴负方向,如果该对象需要在z轴正方向的投影平面(在OpenGL中通常是近裁剪平面)上成像,那么必须要将相机沿着z轴正方向往后移动一定距离。
相机移动之后的位置称为观察参考点(VRP,View Reference Point)。此时,我们考虑采用规范化变换(Normalization Transformation)来表示相机坐标系并指定相机的位置,可以分为两个部分:
观察平面法向量(VPN:View-plane Normal)和观察正向向量(VUP:View-up Vector)。
一点两向量
其中VPN指定了相机的胶片平面,而平面是由其法向量决定的。
所以通过指定VRP,VPN和VUP可以定位照相机。
但通常在OpenGL中,我们更倾向于在对象坐标系下,采用如下图所示的定位方法,将相机放置于 的位置上称为视点,相机指向的另一个点 称为参考点。这两个点就确定了VPN和VRP,即:
归一化得到:
(注意,这里需要加上一个负号)
然后通过VUP和VPN生成与它们都垂直的方向向量:
最后再计算得到VUP在照相机胶片平面上的投影:
这样通过采用u,n和v即可定义出相机的观察变换矩阵(其实就是相机的局部坐标系)如下:
由于在最开始,我们还需要将相机从坐标原点移动到视点,所以还需要一个平移矩阵如下:
所以最终的相机观察矩阵为:
结合模型变换矩阵和相机观察矩阵之后得到模-视变化矩阵如下:
三、 实验内容
实验内容主要是设计并实现相机定位观察变换接口函数如下。
mat4 void lookAt(vec4 eye, vec4 at, vec4 up) 或 mat4 void lookAt(float eyex, float eyey, float eyez, float atx, float aty, float atz, float upx, float upy, float upz)
请按照如下说明顺序实现并提交作业。
a) 按照实验1.1配置环境,具体请参考实验1.1相关文档。注意:会编译出错,因为缺少文件“TriMesh.h”,请在Blackboard下载新的include文件夹(部分其他文件有更新),将其拷贝到你的include文件夹中。后续实验都会用到文件,请更新好你的include文件夹。
b) 读取cube.off模型
c) 构造相机观察矩阵,并传到顶点着色器中,作用于几何体。mat4 lookAt( const vec4& eye, const vec4& at, const vec4& up ) { // 请参考上述公式推导过程,将该部分补全 } // init():从顶点着色器中获取模-视变换矩阵位置 modelViewprojectMatrixID = glGetUniformLocation(programID, "modelViewprojectMatrix"); // display():计算相机观察矩阵并传入顶点着色器 mat4 modelMatrix = mat4(1.0); mat4 viewMatrix = lookAt(eye, at, up); mat4 projectMatrix = mat4(1.0); mat4 modelViewprojectMatrix = projectMatrix * viewMatrix * modelMatrix; glUniformMatrix4fv(modelViewprojectMatrixID, 1, GL_TRUE, &modelViewprojectMatrix[0][0]);
d) 实现键盘控制,分别改变相机视点,参考点和向上方向以观察不同参数对结果的影响。具体控制可参考教材P157-158页代码或者P434页代码。
LookAt函数的推导
由于我们移动物体,可以通过移动相机的负距离来实现,因此我们可以通过对相机的model矩阵乘以一个这个矩阵来实现
四、 示例和练习
a) 完成上述基本内容。
b) 实现键盘控制,改变相机视点,参考点和向上方向等,并观察输出。
c) 提示:正方体不显示在窗口或不在窗口中心。可能原因:忘填路径;lookAt()返回的矩阵不对;括号前忘写vec4(大概率,且编译能通过);eye,at,r,θ等参数初始值不对。其实观察off文件可以发现这个正方体是在1的范围内的,并且还是在原点:
于是乎我们可以在半径为1的单位圆上从各个角度来观察正方体。
我们参照课本157页和434页的代码:设置eye的参数为这样:
vec4 eye(radius * sin(theta) * cos(phi), radius * sin(theta) * sin(phi), radius * cos(theta), 1.0);//参考课本模型
于是需要初始化全局变量:
float theta = 0.0; float phi = 0.0;
在键盘响应函数中这样来修改上面的参数:
void keyboard(unsigned char key, int x, int y) { // Todo:键盘控制相机的位置和朝向 switch(key) { case 033: // ESC键 和 'q' 键退出游戏 exit(EXIT_SUCCESS); break; case 'q': exit (EXIT_SUCCESS); break; case 'x': l *= 1.1; r *= 1.1; break; case 'X': r *= 0.9; r *= 0.9; break; case 'y': b *= 1.1; t *= 1.1; break; case 'Y': b *= 0.9; t *= 0.9; break; case 'z': n *= 1.1; f *= 1.1; break; case 'Z': n *= 0.9; f *= 0.9; break;//这几个参数是perspective所用到的参数,详细下节在内容 case 'r': radius *= 2.0; break; case 'R': radius *= 0.5; break; case 'o': theta += dr; break; case 'O': theta -= dr; break; case 'p': phi += dr; break; case 'P': phi -= dr; break; case 't': // 重置所有的属性为初始值 l = -1.0; r = 1.0; b = -1.0; t = 1.0; n = -1.0; f = 1.0; radius = 1.0; theta = 0.0; phi = 0.0; break; } glutPostRedisplay(); }
在display函数中,我们只需要设定好eye at(原点)和up(固定数值),然后调用lookat函数生成view矩阵即可。
void display() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glUseProgram(programID); // TODO 设置相机参数 vec4 eye(radius * sin(theta) * cos(phi), radius * sin(theta) * sin(phi), radius * cos(theta), 1.0);//参考课本模型 vec4 at(0.0, 0.0, 0.0, 1.0);//物体所在的地方 vec4 up(0.0, 1.0, 0.0, 0.0);//观察正向向量VUP Camera::modelMatrix = mat4(1.0); Camera::viewMatrix = Camera::lookAt(eye, at, up);//调用lookat函数来构造view矩阵 Camera::projectMatrix = Camera::perspective(45.0, 1.0, 1.0, 100.0); //调用perspective来实现透视投影矩阵 mat4 modelViewprojectMatrix = Camera::projectMatrix * Camera::viewMatrix * Camera::modelMatrix; glUniformMatrix4fv(modelViewprojectMatrixID, 1, GL_TRUE, &modelViewprojectMatrix[0][0]); }
关于此处涉及到的投影矩阵 :perspective的是下节实验的内容,这里只需要知道加入这个矩阵可以得到好的效果,不会出现被裁切的情况。
随后我们运行 按下o和p键就可以实现立方体的转动。
实现相机的移动
相机的平移
相机的移动无法像物体一样直接乘以矩阵来实现,但是我们知道构造view矩阵的lookAt函数有三个参数,eye,at,up
其中up一般来说是固定的(0,1,0),但是在某些情况下并不能设置为(0,1,0),这是在我3.4的实验中,从y轴正上方观察小球你就会发现,此时什么都看不到了,这是因为此时从y轴正向朝下看,那么at就变成了(0,-1,0),此时由于两个向量在一条直线上,则无法实现向量叉乘了,那么计算view矩阵就会出问题,(
除了修改up矩阵的方法暂时不知道怎么解决)但是我们可以修改eye和at矩阵,eye就是相机位置,at就是我们所看的方向。
而eye是一个三维向量,我们简单的修改其xyz值就可以实现相机的移动了。
所以初始时我们设定好三个变量:vec4 cameraPosition(0.0, 0.0, 0.0, 1.0); vec4 cameraDirection(0.0, 0.0, 2.0, 0.0); vec4 cameraUp(0.0, 1.0, 0.0, 0.0);
在lookAt函数中传入的也是这三个数值:
mat4 view=lookAt(cameraPosition, cameraPosition + cameraDirection, cameraUp);
然后通过键盘函数来实现position的变换,
void keyboard(unsigned char key, int x, int y) { switch(key) { case 'w': cameraPosition.z -= 0.05f; //为什么这里是减?因为初始时我们是面对z轴负方向的 break; case 's': cameraPosition.z += 0.05f; break; case 'a': cameraPosition.x -= 0.05f; break; case 'd': cameraPosition.x += 0.05f; case ' ': cameraPosition.y += 0.05f; case 'c': cameraPosition.y -= 0.05f; } glutPostRedisplay(); }
此时我们就会观察到可以通过键盘控制来移动物体:
起始:
现在:
第一人称相机
仅仅是平移相机是十分乏味的,接下来实现第一人称游戏所用的相机。
在此之前需要先学一个概念欧拉角:欧拉角
一张图带你了解欧拉角:
我们联系现实,其中ψ可以理解为我们现实中往前后左右转向所转的角度,而θ角就是我们抬头和低头。
至于Φ角,其实就是你歪脖子看世界所转的角度。由于此处只实现简单的第一人称射击游戏,故不深入探讨欧拉角,有关于旋转的其他表示方法,欧拉角的缺陷(万向节死锁)、以及四元数在这里不涉及因此不讲。
对于简单的第一人称相机,我们不去实现“歪头”的功能,因此只需要修改两个参数
我们用以下的方法来表示:
pitch 俯仰角
yaw 偏航角
roll 横滚角于是设置三个全局变量:
float pitch = 0.0f; float roll = 0.0f; float yaw = 0.0f;
经过推导(
引用)这里先暂时懒得推导直接借用结论了那么这两个角如何影响我们的 cameraDirection 变量,首先我们来看 xoz 平面,注意这里我们的相机是看向世界坐标 z 轴负方向的,所以 z 的计算要乘一个负号:
然后我们来看 pitch 如何影响 y 轴分量,我们注意到 pitch 分配到 xoz 平面的分量(圆的半径)为 cos(pitch) 而 y 轴的分量为 sin(pitch)// 计算欧拉角以确定相机朝向 cameraDirection.x = cos(radians(pitch)) * sin(radians(yaw)); cameraDirection.y = sin(radians(pitch)); cameraDirection.z = -cos(radians(pitch)) * cos(radians(yaw)); // 相机看向z轴负方向
但是计算角度需要进行角度到弧度的转换,我们知道:
1°=π/180°
于是我们可以自己写一个角度到弧度的转换float radians(double degree) { float PI = 3.1415926; return PI / 180 * degree; }
此外,接下来还需要用到取模(不知道为什么不能直接用%,于是自己写了一个函数)和限制范围的函数
float clamp(float x, float min, float max) { if (x > max) return max; if (x < min) return min; return x; } float mod(float a, float b) { while (a> b) { a = a - b; } return a; }
在键盘交互函数中:由于四维向量不能直接相加减,所以我们在定义初始向量的时候将其定义为三维向量,方便在keyboard函数中直接对position进行加减
vec3 cameraPosition(0.0, 0.0, 2.0); vec3 cameraDirection(0.0, 0.0, -1.0); // 相机视线方向 vec3 cameraUp(0.0, 1.0, 0.0);
void keyboard(unsigned char key, int x, int y) { // Todo:键盘控制相机的位置和朝向 switch(key) { case 033: // ESC键 和 'q' 键退出游戏 exit(EXIT_SUCCESS); break; case 'w': cameraPosition += 0.05f*cameraDirection;//为什么这里是减?因为初始时我们是面对z轴负方向的 std::cout << "w"; break; case 's': cameraPosition -= 0.05f*cameraDirection; std::cout << "s"; break; case 'a': cameraPosition -= 0.05f*normalize(cross(cameraDirection, cameraUp)); std::cout << "a"; break; case 'd': cameraPosition += 0.05f*normalize(cross(cameraDirection, cameraUp)); std::cout << "d"; break; case ' ': cameraPosition.y += 0.05f; break; case 'c': cameraPosition.y -= 0.05f; break; } glutPostRedisplay(); }
因此传入lookAt矩阵这里也需要修改成这样:
vec4(cameraPositon,1.0)
效果:
https://www.bilibili.com/video/BV1Rz4y1r7cM/
见视频完整代码
/* * Computer Graphics Course - Shenzhen University * Week 6 - Camera Position and Control Skeleton Code * ============================================================ * * - 本代码仅仅是参考代码,具体要求请参考作业说明,按照顺序逐步完成。 * - 关于配置OpenGL开发环境、编译运行,请参考第一周实验课程相关文档。 */ #include "include/Angel.h" #include "include/TriMesh.h" #pragma comment(lib, "glew32.lib") #include <cstdlib> #include <iostream> using namespace std; GLuint programID; GLuint vertexArrayID; GLuint vertexBufferID; GLuint vertexIndexBuffer; GLuint vPositionID; GLuint modelViewprojectMatrixID; vec3 scaleTheta(1.0, 1.0, 1.0); vec3 rotateTheta(0.0, 0.0, 0.0); vec3 translateTheta(0.0, 0.0, 0.0); int windowWidth = 512; // 窗口宽 int windowHeight = 512; TriMesh* mesh = new TriMesh(); // Viewing transformation parameters float radius = 2.0; float theta = 0.0; float phi = 0.0; float l = -1.0, r = 1.0; //left right float b = -1.0, t = 1.0;//bottom top float n =1.0, f = 100.0; //near far const float dr = 5.0 * DegreesToRadians; float pitch = 0.0f; float roll = 0.0f; float yaw = 0.0f; vec3 cameraPosition(0.0, 0.0, 2.0); vec3 cameraDirection(0.0, 0.0, -1.0); // 相机视线方向 vec3 cameraUp(0.0, 1.0, 0.0); vec4 eye(1.0, 0.0, 2.0, 1.0);//参考课本模型 namespace Camera { mat4 modelMatrix; //模型变换矩阵 mat4 viewMatrix; //相机观察矩阵 mat4 projectMatrix; //投影矩阵 mat4 ortho(const GLfloat left, const GLfloat right, const GLfloat bottom, const GLfloat top, const GLfloat zNear, const GLfloat zFar) { mat4 c; c[0][0] = 2.0 / (right - left); c[1][1] = 2.0 / (top - bottom); c[2][2] = 2.0 / (zNear - zFar); c[3][3] = 1.0; c[0][3] = -(right + left) / (right - left); c[1][3] = -(top + bottom) / (top - bottom); c[2][3] = -(zFar + zNear) / (zFar - zNear); return c; } mat4 perspective(const GLfloat fovy, const GLfloat aspect, const GLfloat zNear, const GLfloat zFar) { GLfloat top = tan(fovy*DegreesToRadians / 2) * zNear; GLfloat right = top * aspect; mat4 c; c[0][0] = zNear / right; c[1][1] = zNear / top; c[2][2] = -(zFar + zNear) / (zFar - zNear); c[2][3] = -2.0*zFar*zNear / (zFar - zNear); c[3][2] = -1.0; return c; } mat4 lookAt( const vec4& eye, const vec4& at, const vec4& up ) { // TODO 请按照实验课内容补全相机观察矩阵的计算 vec4 n = normalize(eye - at); vec3 uu = normalize(cross(up, n)); vec4 u = vec4(uu.x, uu.y, uu.z, 0.0); vec3 vv = normalize(cross(n, u)); vec4 v = vec4(vv.x, vv.y, vv.z, 0.0); vec4 t = vec4(0.0, 0.0, 0.0, 1.0); mat4 c = mat4(u, v, n, t); return c * Translate(-eye); } } // // OpenGL 初始化 void init() { glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // 加载shader并且获取变量的位置 programID = InitShader("vshader.glsl", "fshader.glsl"); vPositionID = glGetAttribLocation(programID, "vPosition"); modelViewprojectMatrixID = glGetUniformLocation(programID, "modelViewprojectMatrix"); // 从外部读取三维模型文件 mesh->read_off("cube.off"); vector<vec3f> vs = mesh->v(); vector<vec3i> fs = mesh->f(); // 生成VAO glGenVertexArrays(1, &vertexArrayID); glBindVertexArray(vertexArrayID); // 生成VBO,并绑定顶点坐标 glGenBuffers(1, &vertexBufferID); glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); glBufferData(GL_ARRAY_BUFFER, vs.size() * sizeof(vec3f), vs.data(), GL_STATIC_DRAW); // 生成VBO,并绑定顶点索引 glGenBuffers(1, &vertexIndexBuffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vertexIndexBuffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, fs.size() * sizeof(vec3i), fs.data(), GL_STATIC_DRAW); // OpenGL相应状态设置 glEnable(GL_LIGHTING); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); } // // 渲染 float radians(double degree) { float PI = 3.1415926; return PI / 180 * degree; } void display() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glUseProgram(programID); // TODO 设置相机参数 vec4 at(0.0, 0.0, 0.0, 1.0);//物体所在的地方 vec4 up(0.0, 1.0, 0.0, 0.0);//观察正向向量VUP // 计算欧拉角以确定相机朝向 cameraDirection.x = cos(radians(pitch)) * sin(radians(yaw)); cameraDirection.y = sin(radians(pitch)); cameraDirection.z = -cos(radians(pitch)) * cos(radians(yaw)); // 相机看向z轴负方向 Camera::modelMatrix =mat4(1.0); Camera::viewMatrix = Camera::lookAt(vec4(cameraPosition,1.0), vec4(cameraPosition,1.0)+vec4(cameraDirection,0.0), vec4(cameraUp,1.0));//调用lookat函数来构造view矩阵 Camera::projectMatrix = Camera::perspective(45.0, 1.0, 1.0, 100.0); //调用perspective来实现透视投影矩阵 mat4 modelViewprojectMatrix = Camera::projectMatrix * Camera::viewMatrix * Camera::modelMatrix; glUniformMatrix4fv(modelViewprojectMatrixID, 1, GL_TRUE, &modelViewprojectMatrix[0][0]); //Camera::projectMatrix = mat4(1.0); //Camera::projectMatrix = Camera::ortho(l, r, b, t, n, f); //调用ortho函数来实现正交投影矩阵,然后将这三个矩阵按照特定的顺序相乘 glEnableVertexAttribArray(vPositionID); glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); glVertexAttribPointer( vPositionID, 3, GL_FLOAT, GL_FALSE, 0, (void*)0 ); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vertexIndexBuffer); glDrawElements( GL_TRIANGLES, int(mesh->f().size() * 3), GL_UNSIGNED_INT, (void*)0 ); glDisableVertexAttribArray(vPositionID); glUseProgram(0); glutSwapBuffers(); } // // 重新设置窗口 void reshape(GLsizei w, GLsizei h) { glViewport(0, 0, w, h); } // // 鼠标响应函数 float clamp(float x, float min, float max) { if (x > max) return max; if (x < min) return min; return x; } float mod(float a, float b) { while (a> b) { a = a - b; } return a; } void mouse(int x, int y) { //std::cout << x << " " << y << endl; yaw += 35 * (x - float(windowWidth) / 2.0) / windowWidth; yaw = mod(yaw + 180.0f, 360.0f) - 180.0f; // 取模范围 -180 ~ 180 pitch += -35 * (y - float(windowHeight) / 2.0) / windowHeight; pitch = clamp(pitch, -89.0f, 89.0f); glutWarpPointer(windowWidth / 2.0, windowHeight / 2.0); // 将指针钉死在屏幕正中间 glutPostRedisplay(); return; } // // 键盘响应函数 void keyboard(unsigned char key, int x, int y) { // Todo:键盘控制相机的位置和朝向 switch(key) { case 033: // ESC键 和 'q' 键退出游戏 exit(EXIT_SUCCESS); break; case 'q': exit (EXIT_SUCCESS); break; /*case 'x': l *= 1.1; r *= 1.1; break; case 'X': r *= 0.9; r *= 0.9; break; case 'y': b *= 1.1; t *= 1.1; break; case 'Y': b *= 0.9; t *= 0.9; break; case 'z': n *= 1.1; f *= 1.1; break; case 'Z': n *= 0.9; f *= 0.9; break; case 'r': radius *= 2.0; break; case 'R': radius *= 0.5; break; case 'o': theta += dr; break; case 'O': theta -= dr; break; case 'p': phi += dr; break; case 'P': phi -= dr; break; case 't': // 重置所有的属性为初始值 l = -1.0; r = 1.0; b = -1.0; t = 1.0; n = -1.0; f = 1.0; radius = 1.0; theta = 0.0; phi = 0.0; break;*/ case 'w': cameraPosition += 0.05f*cameraDirection;//为什么这里是减?因为初始时我们是面对z轴负方向的 std::cout << "w"; break; case 's': cameraPosition -= 0.05f*cameraDirection; std::cout << "s"; break; case 'a': cameraPosition -= 0.05f*normalize(cross(cameraDirection, cameraUp)); std::cout << "a"; break; case 'd': cameraPosition += 0.05f*normalize(cross(cameraDirection, cameraUp)); std::cout << "d"; break; case ' ': cameraPosition.y += 0.05f; break; case 'c': cameraPosition.y -= 0.05f; break; } glutPostRedisplay(); } // void idle(void) { glutPostRedisplay(); } // void clean() { glDeleteBuffers(1, &vertexBufferID); glDeleteProgram(programID); glDeleteVertexArrays(1, &vertexArrayID); if (mesh) { delete mesh; mesh = NULL; } } int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE); glutInitWindowSize(500, 500); glutCreateWindow("OpenGL-Tutorial"); glewInit(); init(); glutDisplayFunc(display); glutReshapeFunc(reshape); //glutMouseFunc(mouse); glutPassiveMotionFunc(mouse); glutKeyboardFunc(keyboard); glutIdleFunc(idle); glutMainLoop(); clean(); return 0; }
实验3.2 投影和阴影
一、 实验目的
- 了解OpenGL中正交投影和透视投影变换
- 了解在OpenGL中实现正交投影和透视投影变换
- 了解使用投影变换实现场景的硬阴影效果
二、 理论背景
- 投影变换
在经过三维物体的模-视变换后,场景中的三维物体即被放在了相机能够观察到的位置。
而投影变换的目的则是定义一个视景体(View Volume),使得视景体外多余的部分被裁减掉,最终进入到投影平面上的只是视景体内的部分。
裁剪应该可以类比为人类的视野其实也是有限的,因此在计算机中只需要将视景体内的物体保留下来即可。
投影包含正交投影(Orthographic Projection)和透视投影(Perspective Project)两种。
可以通过看图来快速理解:
正交投影:
正交投影的效果现实中是不存在的,现实中都是透视投影
透视投影:
透视投影效果更接近现实
接下来我们细讲正交投影
正交投影的投影线垂直于观察平面,如果用照相机实现正交投影,那么这个照相机的胶片平面应该平行于镜头。如下图所示,投影平面是 。
在OpenGL中,通常使用的正交投影是定义在一个平行六面体的视景体(或者说是裁剪体)中,如下图所示,该六面体由六个参数决定,分别为左右裁剪平面(left和right),上下裁剪平面(top和bottom),远近裁剪平面(near和far)。需要注意的是,这些参数的定义都是在相机坐标系下。举例来说,远近裁剪平面相当于是在z轴方向离相机的距离。
但是,在OpenGL的渲染管线中定义了一个标准视景体如下,也就是说,在渲染的最后过程中,我们需要将上述定义的正交投影视景体变换到该标准视景体中,使用的方法是通过平移和旋转变换将相机坐标系下经过裁剪的顶点变换到默认的标准视景体下,这个处理过程称为投影规范化(ProjectionNormalization),如下图所示。
而使用的投影矩阵如下,具体推导过程请参考书本相应章节。透视投影
在真实世界中的投影方式是透视投影,即满足人类视觉基本感知原理:近大远小。如下图所示,将坐标原点作为投影中心,任意顶点 经过投影平面 之后均相交于坐标原点,那么基于比例关系即可求出在投影平面上的投影点坐标。
这里都是实验手册里说的,你看到这里的时候可能无法完全理解(
看完了跟没看一样),我也是,现在来细说一下这一块:透视除法
如果你曾经体验过实际生活给你带来的景象,你就会注意到离你越远的东西看起来更小。这个神奇的效果我们称之为透视(Perspective)。透视的效果在我们看一条无限长的高速公路或铁路时尤其明显,由于透视的原因,平行线似乎在很远的地方看起来会相交。这正是透视投影(Perspective Projection)想要模仿的效果,它是使用透视投影矩阵来完成的。
首先思考正交投影和透视投影的区别,例如看张图的前后两根柱子,在正交投影中,我们可以建立坐标系
如图所示我们可以看到,投影其实就是将三维场景投影到一个二维平面,在正交投影中,我们可以看到1-6号柱子的x、y坐标是完全一样的,那么投影方式是平行的,那么第一根柱子就会完全挡住2-6号柱子,此时就丧失了深度信息。那么如果我们想将深度信息保留下来,需要做的就是实现近大远小的功能。
如果实现?
我们再来看一次透视投影的效果:
我们可以看到,为什么透视投影效果看起来真实很多,就是因为它体现了深度,具体是怎么体现的呢?
我们可以看到最后一根柱子也就是6号柱子,它在投影平面内的x坐标明显和1号柱子的x坐标不同。
越远的柱子,它的x坐标越小。
所以也就是说,对于z越大的物体,它经过投影后的x坐标应该越小!
此处借用知乎大佬的推导:
在图形学中表示:
经过与上面类似的相似三角形的推导,不难得出:
于是可以得到投影后的齐次坐标:
当然除了上面那种推导方式,还可以来体会另外一种方式:
我们知道齐次坐标的最后一个信息(也就是w)如果是1则代表坐标值,此时我们可以用这个位置来储存深度信息:
所以可以构造一个投影矩阵:
再将其乘以坐标:
此时这个坐标的深度信息就被保留了下来,但是在齐次坐标中最后一个的取值只有0和1,如果z/d不为1,我们就需要变换。
因此,最终就得到了透视投影的坐标。投影矩阵的建立
同样,对于透视投影,我们也需要设置一个视景体来裁剪三维物体如下,也就是说在视景体内部的三维物体才能被投影到投影平面上,剩下超出的部分则被裁减掉。在透视投影中,由于投影中心在理想情况下是一个点,所以形成的视景体构成一个截头椎体(Frustum)。
在OpenGL中,通常有两种定义透视投影视见体的方法,如下左图的类似于正交投影的棱台视见体和下右图利用视域(FoV,Field of View)定义的视见体。其中棱台视见体是由左右裁剪平面(left和right),上下裁剪平面(top和bottom),远近裁剪平面(near和far)。而视域视见体则由视角(Field of View),投影平面长宽比(aspect)和远近裁剪平面(near和far)决定。
这个图和人类看世界的感觉是一样的,都是从一个点往外看
另外,在透视投影中,同样需要执行投影规范化过程,如下图所示,将视景体内部的顶点变换到标准视景体范围内:
以透视投影的棱台视见体为例,其透视投影矩阵如下,具体推导过程请参考书本相应章节,这里不再赘述。(请尝试推导出视域视景体的投影变换矩阵,并理解这两种视景体定义方式的等价性。)
(注意和教材上第166页公式不一样,第三行第三列数值教材上的写错了。)当然像我这种懒狗是不会去推导的,推导是不可能推导的,只有调用函数才能维持得了生活这样
透视投影函数有两个,perspective和frustum,这里主要展示perspective:mat4 perspective( const GLfloat fovy, const GLfloat aspect, const GLfloat zNear, const GLfloat zFar) { // TODO 请按照实验课内容补全相机观察矩阵的计算 GLfloat top = tan(fovy * M_PI / 180 / 2) * zNear; GLfloat right = top * aspect; mat4 c; c[0][0] = zNear / right; c[1][1] = zNear / top; c[2][2] = -(zFar + zNear) / (zFar - zNear); c[2][3] = -(2.0*zFar*zNear) / (zFar - zNear); c[3][2] = -1.0; c[3][3] = 0.0; return c; }
投影和硬阴影
[注意]本节内容和书本上通过模-视变换矩阵来计算阴影投影矩阵方法不一样。
投影变换矩阵的一个应用就是生成简单的阴影。从物理上看,有光源才会产生阴影,如果光源投射的光线被物体遮挡,那么遮挡的部分被投影在地面上即产生了阴影。为了简单起见,我们假定阴影落在了地面上,即OpenGL坐标系下的 平面。
如下图所示,我们假定光源位置在 ,物体由三角形表示,投影平面上的黑色三角形区域即为阴影,我们称之为阴影多边形(Shadow Polygon)。
下面我们来推导阴影投影矩阵如下。假设三角形任意一个顶点坐标为 ,投影到投影平面之后的坐标为 ,因为该点在 平面上,所以 。根据比例关系可得如下公式:
求解可得,
为了能够方便的通过矩阵表示出投影关系,我们将所有坐标设置在齐次坐标系下,那么投影关系就能表示成如下公式:
即可得到硬阴影对应的投影矩阵:
最后通过透视除法得到最终的投影坐标:
所以生成硬阴影的关键在于投影矩阵的求解。三、 实验内容
1. 正交投影和透视投影的实现
本实验主要目标是实现正交投影矩阵生成函数
mat4 ortho( const GLfloat left, const GLfloat right,
const GLfloat bottom, const GLfloat top,
const GLfloat zNear, const GLfloat zFar )
和透视投影生成矩阵
mat4 perspective( const GLfloat fovy, const GLfloat aspect,
const GLfloat zNear, const GLfloat zFar)
a) 按照实验1.1配置环境,具体请参考实验1.1相关文档。注意:会编译出错,因为缺少文件“TriMesh.h”,请在Blackboard找实验3.1的压缩包下载后,将其拷贝到你的include文件夹中。后续实验都会用到该文件,请将其保留在你的include文件夹中。
b) 读入立方体文件
c) 根据上述理论说明部分,实现正交投影和透视投影生成矩阵函数。
d) 将生成的矩阵传入顶点着色器,并观察结果。
// init():从顶点着色器中读取投影变换矩阵的位置
modelViewProjMatrixID = glGetUniformLocation(programID, “modelViewProjMatrix”);// display():计算观察矩阵和投影变换矩阵并传入顶点着色器 vec4 eye( radius * sin(theta) * cos(phi), radius * sin(theta) * sin(phi), radius * cos(theta), 1.0); vec4 at(0.0, 0.0, 0.0, 1.0); vec4 up(0.0, 1.0, 0.0, 0.0); Camera::modelMatrix = mat4(1.0);
Camera::viewMatrix = Camera::lookAt(eye, at, up);
Camera::projMatrix = Camera::ortho/perspective。。。
mat4 modelViewProjMatrix = Camera::projMatrix * Camera::viewMatrix * Camera::modelMatrix;glUniformMatrix4fv(modelViewProjMatrixID, 1, GL_TRUE, &modelViewProjMatrix[0][0]);
e) 通过键盘控制,完成对不同参数的控制。具体控制可参考教材P157-158页代码。
投影和硬阴影的实现
请按照如下说明顺序阅读代码并完成实验。
a) 绘制三角形。参考实验1,配置开发环境并画出平面三角形。然后修改三角形顶点坐标为:(-0.5,0.5,0.5),(0.5,0.5,0.5)和(0.0,0.75,0.0),绘制得到一个空间三角形。并使用glClearColor函数将窗口背景色改成(0.9,0.9,0.9,0.0),因为之后我们将阴影设置为黑色,所以这里将窗口背景改成其他颜色,以突出阴影颜色的显示。代码说明如下:// init():设置窗口背景颜色 glClearColor(0.9, 0.9, 0.9, 0.0); // init():生成顶点坐标 vec3 points[3]; points[0] = vec3(-0.5, 0.5, 0.5); points[1] = vec3(0.5, 0.5, 0.5); points[2] = vec3(0.0, 0.75, 0.0); // init():从顶点着色器中获取顶点坐标位置 vPositionID = glGetAttribLocation(programID, "vPosition"); // init():生成缓存将顶点坐标传入着色器中 glGenBuffers(1, &vertexBufferID); glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);
b) 调整相机视角。因为本节课重点不在观察和投影,所以简单的将模型变换矩阵和相机观察矩阵设置为默认参数,并将投影矩阵设置为正交投影,调用实验1:投影变换中实现的ortho函数将参数分别设置为左右裁剪平面-3和+3,上下裁剪平面-3和+3,远近裁剪平面-3和+3(请思考这些参数的意义是什么。)。将这些矩阵在程序中计算后传入顶点着色器中。代码说明如下:
// init():从顶点着色器中获取变换矩阵位置
modelMatrixID = glGetUniformLocation(programID, “modelMatrix”);
viewMatrixID = glGetUniformLocation(programID, “viewMatrix”);
projMatrixID = glGetUniformLocation(programID, “projMatrix”);// display():设置模型视图矩阵和观察矩阵 // 将投影矩阵设置为正交投影,参数为左右平面-3和3,上下平面-3和3,远近-3和3。
// 由于使用默认相机参数时,相机看的方向与投影平面平行,因此,调整一下相机的方向和位置
Camera::modelMatrix = mat4(1.0);
vec4 eye( 0.5, 2.0, -0.5, 1.0); //
vec4 at( 0, 0, 0, 1); // 原点
vec4 up( 0, 1, 0, 0); // 默认方向
Camera::viewMatrix = Camera::lookAt(eye, at, up);
Camera::modelMatrix = mat4(1.0);
Camera::projMatrix = Camera::ortho(-3, 3, -3, 3, -3, 3);// display():计算变换矩阵并将结果传入顶点着色器中 glUniformMatrix4fv(modelMatrixID, 1, GL_TRUE, &Camera::modelMatrix[0][0]); glUniformMatrix4fv(viewMatrixID, 1, GL_TRUE, &Camera::viewMatrix[0][0]); glUniformMatrix4fv(projMatrixID, 1, GL_TRUE, &Camera::projMatrix[0][0]);
c) 计算阴影投影矩阵。我们定义一个全局变量lightPos表示光源位置,在参考程序中设置为(-0.5,2.0,0.5),继而参考上述理论说明部分计算阴影投影矩阵,更新模型视图矩阵,并将该矩阵传入到顶点着色器中,以执行对原始三角形顶点的投影变换。代码说明如下:(请思考在模型视图矩阵的更新方式)
// 全局变量:设置光源位置
float lightPos[3] = { -0.5, 2.0, 0.5 };// display():按照公式计算投影矩阵 float lx = lightPos[0]; float ly = lightPos[1]; float lz = lightPos[2]; mat4 shadowProjMatrix(-ly, 0.0, 0.0, 0.0, lx, 0.0, lz, 1.0, 0.0, 0.0, -ly, 0.0, 0.0, 0.0, 0.0, -ly); // display():将投影矩阵传入顶点着色器中的模-视变换矩阵
Camera::modelMatrix = shadowProjMatrix
glUniformMatrix4fv(modelMatrixID, 1, GL_TRUE, &Camera::modelMatrix[0][0]);d) 投影三角形的绘制。在绘制过程中,我们可以简单地将三角形绘制两次即可。第一次是按照常规方式绘制,第二次是使用了阴影变换矩阵之后对新的阴影三角形进行绘制。也就是说,第一次绘制时,将阴影投影矩阵设置为单位矩阵,而第二次绘制是计算出矩阵值之后变换三角形得到投影三角形。请参考代码中的display函数,这里需要将上述各部分进行整合,而得到最终的绘制结果。
e) 键盘控制阴影的显示。观察当物体(三角形)绕着Y轴旋转时,投影的变化效果(可通过修改modelMatrix来实现)。请参考代码中的key函数,实现对应的键盘控制。
f) 请仔细读顶点着色器的内容(vshader.glsl),并思考原因。四、 示例和练习
a) 完成上述实验内容并实现对任意简单几何体的阴影变换。
b) 键盘控制三角形的旋转。
c) 在上述实验内容的基础上,实现采用鼠标控制光源位置,并在鼠标点击之后计算阴影投影矩阵和绘制阴影多边形。按照上述步骤完成之后会得到如下图所示的结果,通过Y键能够观察旋转的三角形(红色)和投影生成的阴影(黑色)。
正交投影和透视投影变换:
相比3.1只需要修改两个地方,因此我直接以3.1为模型进行修改:
在camera里添加以下两个函数:添加两个函数: ortho正交投影函数
和perspective透视投影函数
如此便可得到实现平行投影的正方体,效果如图:
那么接下来要实现透视投影,只需要按照下图来实现即可:
硬阴影
阴影的实现有两种办法,一种方法是使用两个着色器,传入两次矩阵。另外一直方法是在着色器里添加一个bool变量,传入物体的位置矩阵的时候,判断需要着色的物体还是阴影。
(在本次实验中,由于物体的阴影比较简单,直接传入绘制即可)
在display函数中绘制完物体后,修改传入着色器的model矩阵和颜色,然后再调用glDrawArray绘制即可。float lx = lightPos[0]; float ly = lightPos[1]; float lz = lightPos[2]; mat4 shadowProjMatrix(-ly, 0.0, 0.0, 0.0, lx, 0.0, lz, 1.0, 0.0, 0.0, -ly, 0.0, 0.0, 0.0, 0.0, -ly);//阴影矩阵 Camera::modelMatrix = shadowProjMatrix * Camera::modelMatrix; glUniform4fv(fColorID, 1, black); glDrawArrays(GL_TRIANGLES, 0, 3);
键盘交互:
在keyboard函数中修改自己定义的rotationAngle变量,void keyboard(unsigned char key, int x, int y) { switch (key) { case 033: exit(EXIT_SUCCESS); break; case 'q': exit(EXIT_SUCCESS); break; case 'y': // 'y' 键使得三角形旋转-1.0度 rotationAngle -= 1.0; break; case 'Y': // 'Y' 键使得三角形旋转+1.0度 rotationAngle += 1.0; break; } glutPostRedisplay(); }
然后修改物体的model矩阵,让物体的model矩阵乘以RotateY函数的矩阵即可
Camera::modelMatrix = mat4(1.0)* RotateY(rotationAngle);
完整代码:
#include "Angel.h" #pragma comment(lib, "glew32.lib") #include <cstdlib> #include <iostream> using namespace std; GLuint programID; GLuint vertexArrayID; GLuint vertexBufferID; GLuint vertexIndexBuffer; GLuint vPositionID; GLuint rotationMatrixID; GLuint modelMatrixID; GLuint viewMatrixID; GLuint projMatrixID; GLuint fColorID; // 相机视角参数 float l = -3.0, r = 3.0; // 左右裁剪平面 float b = -3.0, t = 3.0; // 上下裁剪平面 float n = -3.0, f = 3.0; // 远近裁剪平面 float rotationAngle = 0.0; // 旋转角度 vec4 red(1.0, 0.0, 0.0, 1.0); vec4 black(0.0, 0.0, 0.0, 1.0); float lightPos[3] = { -0.5, 2.0, 0.5 }; // // 相机参数控制 namespace Camera { mat4 modelMatrix; mat4 viewMatrix; mat4 projMatrix; mat4 ortho(const GLfloat left, const GLfloat right, const GLfloat bottom, const GLfloat top, const GLfloat zNear, const GLfloat zFar) { // TODO 请按照实验课讲解补全正交投影矩阵的计算 mat4 c; c[0][0] = 2.0 / (right - left); c[1][1] = 2.0 / (top - bottom); c[2][2] = 2.0 / (zNear - zFar); c[3][3] = 1.0; c[0][3] = -(right + left) / (right - left); c[1][3] = -(top + bottom) / (top - bottom); c[2][3] = -(zFar + zNear) / (zFar - zNear); return c; } mat4 lookAt(const vec4& eye, const vec4& at, const vec4& up) { // TODO 请按照实验课内容补全相机观察矩阵的计算 vec4 n = normalize(eye - at); vec3 uu = normalize(cross(up, n)); vec4 u = vec4(uu.x, uu.y, uu.z, 0.0); vec3 vv = normalize(cross(n, u)); vec4 v = vec4(vv.x, vv.y, vv.z, 0.0); vec4 t = vec4(0.0, 0.0, 0.0, 1.0); mat4 c = mat4(u, v, n, t); return c * Translate(-eye); } } // // OpenGL 初始化 void init() { // 设置窗口背景颜色 glClearColor(0.9f, 0.9f, 0.9f, 0.0f); programID = InitShader("vshader.glsl", "fshader.glsl"); // init():生成顶点坐标 vec3 points[3]; points[0] = vec3(-0.5, 0.5, 0.5); points[1] = vec3(0.5, 0.5, 0.5); points[2] = vec3(0.0, 0.75, 0.0); // 从顶点着色器中获取相应变量的位置 vPositionID = glGetAttribLocation(programID, "vPosition"); //rotationMatrixID = glGetUniformLocation(programID, "rotationMatrix"); modelMatrixID = glGetUniformLocation(programID, "modelMatrix"); viewMatrixID = glGetUniformLocation(programID, "viewMatrix"); projMatrixID = glGetUniformLocation(programID, "projMatrix"); fColorID = glGetUniformLocation(programID, "fColor"); // 生成VAO glGenVertexArrays(1, &vertexArrayID); glBindVertexArray(vertexArrayID); // 生成VBO,并绑定顶点坐标 glGenBuffers(1, &vertexBufferID); glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW); // OpenGL相应状态设置 glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); } // // 渲染 void display() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glUseProgram(programID); // TODO 设置模-视变换矩阵,投影矩阵 // 由于使用默认相机参数时,相机看的方向与投影平面平行,因此,调整一下相机的方向和位置 vec4 eye(0.5, 2.0, -0.5, 1.0); // 光源关于y-z平面的对称方向 vec4 at(0, 0, 0, 1); // 原点 vec4 up(0, 1, 0, 0); // 默认方向 // (因为本节重点不在于投影变换,所以将投影矩阵设置为正交投影即可) Camera::viewMatrix = Camera::lookAt(eye, at, up); //用lookAt函数来使得相机朝向视点 Camera::modelMatrix = mat4(1.0)* RotateY(rotationAngle); //乘上rotate使得模型矩阵可以旋转 Camera::projMatrix = Camera::ortho(l, r, b, t, n, f); //正交投影的方法来实现投影 //mat4 rotationMatrix = RotateX(rotationAngle); //glUniformMatrix4fv(rotationMatrixID, 1, GL_TRUE, &rotationMatrix[0][0]); glEnableVertexAttribArray(vPositionID); glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); glVertexAttribPointer( vPositionID, 3, GL_FLOAT, GL_FALSE, 0, (void*)0 ); // TODO 在正常的投影矩阵下绘制原始的三角形(用红色表示) glUniformMatrix4fv(viewMatrixID, 1, GL_TRUE, &Camera::viewMatrix[0][0]); glUniformMatrix4fv(projMatrixID, 1, GL_TRUE, &Camera::projMatrix[0][0]); glUniformMatrix4fv(modelMatrixID, 1, GL_TRUE, &Camera::modelMatrix[0][0]); glUniform4fv(fColorID, 1, red); glDrawArrays(GL_TRIANGLES, 0, 3); // TODO 计算阴影投影矩阵,绘制投影之后的三角形(用黑色表示) // mat4 shadowProjMatrix = ...; float lx = lightPos[0]; float ly = lightPos[1]; float lz = lightPos[2]; mat4 shadowProjMatrix(-ly, 0.0, 0.0, 0.0, lx, 0.0, lz, 1.0, 0.0, 0.0, -ly, 0.0, 0.0, 0.0, 0.0, -ly);//阴影矩阵 Camera::modelMatrix = shadowProjMatrix * Camera::modelMatrix; //给阴影矩阵也乘上一个旋转矩阵,使得其可以跟随上面的三角形一样一起旋转 //glUniformMatrix4fv(modelMatrixID, 1, GL_TRUE, &Camera::modelMatrix[0][0]); // 请阅读顶点着色器的内容,对照实验1的异同 //glUniformMatrix4fv(viewMatrixID, 1, GL_TRUE, &Camera::viewMatrix[0][0]); //glUniformMatrix4fv(projMatrixID, 1, GL_TRUE, &Camera::projMatrix[0][0]); glUniformMatrix4fv(modelMatrixID, 1, GL_TRUE, &Camera::modelMatrix[0][0]); //将留空代码中的注释打开,使得能正常绘制阴影矩阵 //Camera::modelMatrix = shadowProjMatrix; //glUniformMatrix4fv(modelMatrixID, 1, GL_TRUE, &Camera::modelMatrix[0][0]); glUniform4fv(fColorID, 1, black); glDrawArrays(GL_TRIANGLES, 0, 3); glDisableVertexAttribArray(vPositionID); glUseProgram(0); glutSwapBuffers(); } // // 重新设置窗口 void reshape(GLsizei w, GLsizei h) { glViewport(0, 0, w, h); } // // 鼠标响应函数 void mouse(int button, int state, int x, int y) { if (state == GLUT_LEFT_BUTTON && state == GLUT_DOWN) { lightPos[0] = x;//将光源的xy坐标设置为鼠标左键按下的x、y的坐标 lightPos[1] = y; } return; } // // 键盘响应函数 void keyboard(unsigned char key, int x, int y) { switch (key) { case 033: exit(EXIT_SUCCESS); break; case 'q': exit(EXIT_SUCCESS); break; case 'y': // 'y' 键使得三角形旋转-1.0度 rotationAngle -= 1.0; break; case 'Y': // 'Y' 键使得三角形旋转+1.0度 rotationAngle += 1.0; break; } glutPostRedisplay(); } void printHelp() { printf("%s\n\n", "Camera"); printf("Keyboard options:\n"); printf("y: Decrease rotation angle (aroud Y axis)\n"); printf("Y: Increase rotation angle (aroud Y axis)\n"); } // void idle(void) { glutPostRedisplay(); } // void clean() { glDeleteBuffers(1, &vertexBufferID); glDeleteProgram(programID); glDeleteVertexArrays(1, &vertexArrayID); } // int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE); glutInitWindowSize(500, 500); glutCreateWindow("OpenGL-Tutorial"); glewInit(); init(); glutDisplayFunc(display); glutReshapeFunc(reshape); glutMouseFunc(mouse); glutKeyboardFunc(keyboard); glutIdleFunc(idle); printHelp(); glutMainLoop(); clean(); return 0; }
实验3.3 Phong反射模型(1)
一、 实验目的
- 了解OpenGL中基本的光照模型
- 掌握OpenGL中实现基于顶点的光照计算
二、 理论背景
通过仔细观察前面章节中所有绘制的例子可以发现,所得到的图像基本上是平的,没有深度感,从而不能显示出模型的三维特性,不是很符合真实场景中人类视觉对三维物体的感知。这是因为我们假设物体表面只有单独一种颜色,在这样的假设下,球面的正投影将是均匀着色的圆。类比一下真实场景,我们能看到物体具体不同的颜色是因为眼睛接收了特定波长的光,不透明物体的颜色取决于它反射的色光,而透明物体的颜色则取决于透过它的色光。简单来说,物体只有在光照条件下才能呈现出不同的颜色,并且由于光照可以产生不均匀着色和明暗度的效果。在本周我们将考虑简单的光源模型和几种描述光线-材质之间相互作用的模型,目的是在图形绘制流水线中增加明暗处理功能。在现代OpenGL绘制管线中,我们可以将光照模型加在顶点着色器中,或者在片元着色器中。
光照模型包括局部光照和全局光照。局部光照指物体表面上一点的颜色只取决于表面的材质属性、表面的局部集合性质以及光源的位置和属性,而与场景中其他的表面无关。而全局光照则需要考虑场景中所有表面和光源相互作用的照射效果。如下图所示,左图中x点接收到周围环境的光线照射表示全局光照,右图中点x接收来自光源的直接照射表示局部光照。在本周我们只考虑局部光照。
在局部光照模型中,我们考虑从光源发出的光线,并对这些光线和场景中反射光线的表面之间的相互作用进行建模。这个问题包括两个独立的部分,首先是对场景中的光源建模,其次是必须构建一个描述物体表面和光线之间相互作用的模型。本周我们主要讨论Phong反射模型,它是对基于物理模型绘制方法的一种近似,并且具有很高的效率,能支持各种各样的光照条件和材质属性。
Phong反射模型中涉及到了4个基本向量,如下图所示。其中p为三维物体表面上的一点,l是从点p指向光源位置的向量,n表示p点的法向量,v是从p点指向相机(观察者)的向量,r是沿着l方向入射光线按照反射定于的出射方向。注意,在下面的计算中,我们假定所有的向量都已经归一化。
Phong反射模型考虑了光线和材质之间的三种相互作用:环境光反射、漫反射和镜面反射,最终在三维物体表面上的每个点的颜色由这三个成分组合表示如下。
环境光反射 :在物体表面所有点处的环境光强度都是相同的。环境光一部分被表面吸收,一部分被表面反射,被反射部分由环境光反射系数决定,所以最终得到的环境光部分可以由如下公式得到:
其中 可以表示任何单独的光源,也可以代表全局环境光。
在实际场景中,当一个光源发出的一部分光线直接照射到物体表面的时候,通常对最终渲染图像有贡献的部分大可通过漫反射和镜面发射进行建模而近似表示,但是考虑场景的复杂性,通常光线会被场景中其他物体反射多次后重新射入我们正在考虑的这个表面上,为了降低计算的复杂性同时又更加逼真的表示场景的光照效果,我们通过加入环境光分量来近似这部分贡献。
漫反射 :理想的漫反射表面会把光线向所有方向均匀散射,这样的表面称之为Lambert表面(Lambertian Surface),根据Lambert定律,只有入射光线的垂直分量才对照明起到作用,也就是说漫反射部分的光照效果与法向量n和光源向量l的夹角大小有关。如下图所示。同时考虑漫反射系数kd表示表面对漫反射光的反射程度,最终结果如下所示:
其中 表示漫反射光线的强度。另外需要注意的地方,如果光源位于物体表面以下,那么上述公式中 值将取负数,但在实际情况下这时物体表面并没有受到漫反射光的照射,所以当取负数时应该直接取0而消除此时漫反射光对最终结果的影响。此外,从物理角度来看,光照的强度会随着光源与物体间距离变长而逐渐衰减,综合考虑上述因素得到漫反射成分的计算公式如下:
其中d表示光源和三维物体表面上点的距离,a,b和c表示距离衰减系数。
镜面反射 :在漫反射光照模型中,我们假设了物体表面是均匀粗糙的。而镜面反射成分是为了模拟光滑表面,而且表面越光滑,反射出去的光线越集中在一个角度附近,越接近真实的镜子(不透明镜子),如下图所示。
Phong提出了一个近似模型考虑镜面反射光部分,将表面看成是光滑的,观察者看到的光线强度取决于物体表面反射光的方向r和观察方向v这两者之间的夹角,用公式表示如下。
类似于漫反射,我们可以像计算漫反射分量那样加上一个距离因子。
综合上述几种不同的光照情况,我们得到最终的Phong模型公式如下。
三、 实验内容
请按照如下说明顺序阅读代码并完成实验。
需要说明的是,在OpenGL绘制流水线中,光照计算需要等到三维物体经过相机变换和投影变换之后才能进行,因为此时物体在三维空间中的位置才完全确定下来。另外,通常情况下,我们在相机坐标系下来计算所有向量。具体实验过程如下。
a) 参考实验2.2,读入球模型并绘制。另外,关于OpenGL开发环境配置,详细请参考实验1.1。
b) 在main.cpp程序中计算球面上每个顶点的法向量,并传入顶点着色器。假设球心位于 ,球面上顶点坐标为 ,那么该点的法向量为 ,注意计算完成后对法向量进行归一化。在程序的初始化阶段,为法向量创建缓存,并将数据传入到顶点着色器如下。
// 获取三维物体的顶点坐标
vector vs = mesh->v();
vector ns;// 计算球面上每个顶点的法向量并归一化,默认球心位于坐标系原点 for (int i = 0; i < vs.size(); ++i) { ns.push_back(vs[i] - vec3(0.0, 0.0, 0.0)); } // 从顶点着色器中获取法向量位置,并传入数值 glGenBuffers(1, &vertexNormalID); glBindBuffer(GL_ARRAY_BUFFER, vertexNormalID); glBufferData(GL_ARRAY_BUFFER, ns.size() * sizeof(vec3f), ns.data(), GL_STATIC_DRAW);
c) 在main.cpp程序中的display函数中,计算相机观察矩阵和投影矩阵,并将矩阵和相机位置传入顶点着色器。
// 计算相机观察矩阵和投影矩阵,并传入顶点着色器
mat4 modelViewMatrix = Camera::viewMatrix * Camera::modelMatrix;
mat4 modelViewProjMatrix = Camera::projMatrix * modelViewMatrix;// TODO 将相机位置传入顶点着色器
// glUniformMatrix4fv(modelViewMatrixID, 1, GL_TRUE, …);
// glUniformMatrix4fv(modelViewProjMatrixID, 1, GL_TRUE, …);
// 将相机位置传入顶点着色器
// glUniform3fv(lightPosID, 1, …);
d) 在顶点着色器中将顶点坐标和法向量变换到相机坐标系下。
vec4 vertPos_cameraspace = modelViewMatrix * vec4(vPosition, 1.0);
vec3 V = vertPos_cameraspace.xyz / vertPos_cameraspace.w;
vec3 N = (modelViewMatrix * vec4(vNormal, 0.0)).xyz;
需要注意的是,在变换得到相机坐标系下的顶点位置时,需要执行透视除法将其坐标变换到正常坐标系下。
e) 在顶点着色器中执行为每个顶点执行光照计算。
首先在着色器中定义环境光反射系数、漫反射系数和镜面反射系数的每个颜色分量(ka,kd,ks)如下,
vec3 ambiColor = vec3(0.2, 0.2, 0.2);
vec3 diffColor = vec3(0.5, 0.5, 0.5);
vec3 specColor = vec3(0.3, 0.3, 0.3);
在本例中,光源的La,Ld,Ls,均为白光,即,光源所包含的环境光,漫反射光,和镜面反射光均为(1.0,1.0,1.0)的白光。
然后计算Phong反射模型涉及到的四个向量,并归一化,其中使用的normalize函数,reflect函数均为GLSL语言内置函数,分别是归一化向量和依据入射向量和法向量计算反射向量。如下所示:
vec3 N_norm = normalize(N);
vec3 L_norm = normalize(lightPos - V);
vec3 V_norm = normalize(-V);
vec3 R_norm = reflect(-L_norm, N_norm);
依据上述公式,计算漫反射分量和镜面反射分量如下,注意代码中使用了GLSL语言内置函数clamp,主要作用是将函数值裁剪到规定的两个值之间,具体使用方法请参考相关书籍。
float lambertian = clamp(dot(L_norm, N_norm), 0.0, 1.0);
float specular = clamp(dot(R_norm, V_norm), 0.0, 1.0);
最后为累加三个部分的颜色分量,得到每个顶点的颜色如下:
color = vec4(ambiColor +
diffColor * lambertian +
specColor * pow(specular, 10.0), 1.0);
f) 在片元着色器中输出颜色。
fragmentColor = color;四、 示例和练习
a) 在顶点着色器中,分别只用环境光反射分量、漫反射分量和镜面反射分量作为最后的光照结果,观察输出。
b) 上述代码示例中的实现的光照模型并没有考虑到光源和物体表面顶点之间距离的影响,请尝试加入这一项。
c) 参考教材第189页,在着色器中实现改进的Phong反射模型(即Blinn-Phong反射模型)。
d) 加入鼠标控制,用鼠标控制光源位置,实现不同光源位置下的光照效果。按照上述实验步骤,读入sphere_coarse.off模型,得到的光照效果如下图所示。
首先在init函数中读入球的模型,并计算计算球模型在每个顶点的法向量,并存储到ns数组中。
然后从顶点着色器中获取法向量位置,并传入数值
然后在display函数中,参照3.2的方法计算相机观察矩阵和投影矩阵,并将矩阵和相机位置传入顶点着色器。
在顶点着色器中将顶点坐标和法向量变换到相机坐标系下。
需要注意的是,在变换得到相机坐标系下的顶点位置时,需要执行透视除法将其坐标变换到正常坐标系下。
然后计算Phong反射模型涉及到的四个向量,并归一化,其中使用的normalize函数,reflect函数均为GLSL语言内置函数,分别是归一化向量和依据入射向量和法向量计算反射向量。如下所示:
依据上述公式,计算漫反射分量和镜面反射分量如下,注意代码中使用了GLSL语言内置函数clamp,主要作用是将函数值裁剪到规定的两个值之间,具体使用方法请参考相关书籍。
最后为累加三个部分的颜色分量,得到每个顶点的颜色如下:
代码写完后我们运行可以得到如图所示的结果:
二、 示例和练习
a) 在顶点着色器中,分别只用环境光反射分量、漫反射分量和镜面反射分量作为最后的光照结果,观察输出。
如果只用环境光,则只需要在顶点着色器进行如图所示的操作:
剩下的同理。b) 上述代码示例中的实现的光照模型并没有考虑到光源和物体表面顶点之间距离的影响,请尝试加入这一项。
当我们考虑加入距离情况时,则需要对漫反射和镜面反射光进行修改,不需要对环境光进行修改。
我们参照漫反射的公式:
以及镜面反射的公式
然后将着色器中的代码修改成如图所示:
然后此时可以看到运行结果如图所示:
c) 参考教材第189页,在着色器中实现改进的Phong反射模型(即Blinn-Phong反射模型)。
根据教材189页可以推导:出以下的模型计算公式
使用这个方法计算出来的光照效果要更好一些
d) 加入鼠标控制,用鼠标控制光源位置,实现不同光源位置下的光照效果。
鼠标点击可以得到xy值,而这个xy值的范围是0到500,我们想要将其转换到坐标系范围(-1,1)
在mouse函数中对函数进行修改,将相机的xy坐标修改成这个范围内的
Main函数中的鼠标回调函数也需要进行修改:
随后即可通过鼠标点击改变光照的范围:
在这里可以补充的一点,如果修改主函数调用mouse的函数,并且修改mouse函数参数的内容void mouse(/*int button,int state,*/int x, int y)//需要修改传入的参数 { //if (button == GLUT_LEFT && state == GLUT_DOWN) { lightPos = vec3(float(x - 250) / 250, float(250 - y) / 250, 2.0); //} // TODO 用鼠标控制光源的位置lightPos,以实时更新光照效果 }
//main函数中 glutPassiveMotionFunc(mouse);//Main函数中的鼠标回调函数也需要进行修改
就可以实现柔和的光照位置随鼠标移动的效果了。
完整代码
/* * Computer Graphics Course - Shenzhen University * Week 8 - Phong Reflectance Model (per-vertex shading) * ============================================================ * * - 本代码仅仅是参考代码,具体要求请参考作业说明,按照顺序逐步完成。 * - 关于配置OpenGL开发环境、编译运行,请参考第一周实验课程相关文档。 */ #include "include/Angel.h" #include "include/TriMesh.h" #pragma comment(lib, "glew32.lib") #include <cstdlib> #include <iostream> using namespace std; GLuint programID; GLuint vertexArrayID; GLuint vertexBufferID; GLuint vertexNormalID; GLuint vertexIndexBuffer; GLuint vPositionID; GLuint vNormalID; GLuint modelViewMatrixID; GLuint modelViewProjMatrixID; GLuint lightPosID; TriMesh* mesh = new TriMesh(); vec3 lightPos(0.0, 0.0, 2.0); // // 相机参数设置,因为本周不涉及相机观察变换和投影变换,因此可以使用固定相机视角和投影变换。 // 可使用默认设置,注意默认设置不同于单位矩阵。 namespace Camera { mat4 modelMatrix; mat4 viewMatrix; mat4 projMatrix; mat4 ortho( const GLfloat left, const GLfloat right, const GLfloat bottom, const GLfloat top, const GLfloat zNear, const GLfloat zFar ) { // TODO 请按照实验课内容补全相机观察矩阵的计算 mat4 c; c[0][0] = 2.0 / (right - left); c[1][1] = 2.0 / (top - bottom); c[2][2] = -2.0 / (zFar - zNear); c[3][3] = 1.0; c[0][3] = -(right + left) / (right - left); c[1][3] = -(top + bottom) / (top - bottom); c[2][3] = -(zFar + zNear) / (zFar - zNear); return c; } mat4 perspective( const GLfloat fovy, const GLfloat aspect, const GLfloat zNear, const GLfloat zFar) { // TODO 请按照实验课内容补全相机观察矩阵的计算 GLfloat top = tan(fovy * M_PI / 180 / 2) * zNear; GLfloat right = top * aspect; mat4 c; c[0][0] = zNear / right; c[1][1] = zNear / top; c[2][2] = -(zFar + zNear) / (zFar - zNear); c[2][3] = -(2.0*zFar*zNear) / (zFar - zNear); c[3][2] = -1.0; c[3][3] = 0.0; return c; } mat4 lookAt( const vec4& eye, const vec4& at, const vec4& up ) { // TODO 请按照实验课内容补全相机观察矩阵的计算 vec4 n = normalize(eye - at); vec4 u = normalize(vec4(cross(up, n), 0.0)); vec4 v = normalize(vec4(cross(n, u), 0.0)); vec4 t = vec4(0.0, 0.0, 0.0, 1.0); mat4 c = mat4(u, v, n, t); return c * Translate(-eye); } } // // OpenGL 初始化 void init() { glClearColor(0.0f, 0.0f, 0.0f, 0.0f); programID = InitShader("vshader_vert.glsl", "fshader_vert.glsl"); // 从顶点着色器和片元着色器中获取变量的位置 vPositionID = glGetAttribLocation(programID, "vPosition"); vNormalID = glGetAttribLocation(programID, "vNormal"); modelViewMatrixID = glGetUniformLocation(programID, "modelViewMatrix"); modelViewProjMatrixID = glGetUniformLocation(programID, "modelViewProjMatrix"); lightPosID = glGetUniformLocation(programID, "lightPos"); // TODO 读取外部三维模型 mesh->read_off("sphere_coarse.off"); vector<vec3f> vs = mesh->v(); vector<vec3i> fs = mesh->f(); vector<vec3f> ns; // TODO 计算球模型在每个顶点的法向量,并存储到ns数组中 for (int i = 0; i < vs.size(); ++i) { ns.push_back(vs[i] - vec3(0.0, 0.0, 0.0)); } // 生成VAO glGenVertexArrays(1, &vertexArrayID); glBindVertexArray(vertexArrayID); // 生成VBO,并绑定顶点数据 glGenBuffers(1, &vertexBufferID); glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); glBufferData(GL_ARRAY_BUFFER, vs.size() * sizeof(vec3f), vs.data(), GL_STATIC_DRAW); // 生成VBO,并绑定法向量数据 glGenBuffers(1, &vertexNormalID); glBindBuffer(GL_ARRAY_BUFFER, vertexNormalID); glBufferData(GL_ARRAY_BUFFER, ns.size() * sizeof(vec3f), ns.data(), GL_STATIC_DRAW); // 生成VBO,并绑定顶点索引 glGenBuffers(1, &vertexIndexBuffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vertexIndexBuffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, fs.size() * sizeof(vec3i), fs.data(), GL_STATIC_DRAW); // OpenGL相应状态设置 glEnable(GL_LIGHTING); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); } // // 渲染 void display() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glUseProgram(programID); // TODO 计算相机观察矩阵和投影矩阵,并传入顶点着色器 vec4 eye(0,0, 3.0, 1.0); //由于物体在坐标原点,是单位圆,光源在0,0,2的地方,因此我们可以将观察点设置在0,0,3 vec4 at(0, 0, 0, 1); // 原点 vec4 up(0, 1, 0, 0); // 默认方向 Camera::modelMatrix = mat4(1.0); Camera::viewMatrix = Camera::lookAt(eye, at, up); Camera::projMatrix = Camera::perspective(45, 1,0.1,100); //使用与3.2实验类似的方法来创建三个矩阵 mat4 modelViewMatrix = Camera::viewMatrix * Camera::modelMatrix; mat4 modelViewProjMatrix = Camera::projMatrix * modelViewMatrix; // TODO 将相机位置传入顶点着色器 glUniformMatrix4fv(modelViewMatrixID, 1, GL_TRUE, &modelViewMatrix[0][0]); glUniformMatrix4fv(modelViewProjMatrixID, 1, GL_TRUE,&modelViewProjMatrix[0][0]); // TODO 将光源位置传入顶点着色器 glUniform3fv(lightPosID, 1, &lightPos[0]); glEnableVertexAttribArray(vPositionID); glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); glVertexAttribPointer( vPositionID, 3, GL_FLOAT, GL_FALSE, 0, (void*)0 ); glEnableVertexAttribArray(vNormalID); glBindBuffer(GL_ARRAY_BUFFER, vertexNormalID); glVertexAttribPointer( vNormalID, 3, GL_FLOAT, GL_FALSE, 0, (void*)0 ); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vertexIndexBuffer); glDrawElements( GL_TRIANGLES, int(mesh->f().size() * 3), GL_UNSIGNED_INT, (void*)0 ); glDisableVertexAttribArray(vPositionID); glUseProgram(0); glutSwapBuffers(); } // // 重新设置窗口 void reshape(GLsizei w, GLsizei h) { glViewport(0, 0, w, h); } // // 鼠标响应函数 void mouse(/*int button,int state,*/int x, int y)//需要修改传入的参数 { //if (button == GLUT_LEFT && state == GLUT_DOWN) { lightPos = vec3(float(x - 250) / 250, float(250 - y) / 250, 2.0); //} // TODO 用鼠标控制光源的位置lightPos,以实时更新光照效果 } // // 键盘响应函数 void keyboard(unsigned char key, int x, int y) { switch(key) { case 033: // ESC键 和 'q' 键退出游戏 exit(EXIT_SUCCESS); break; case 'q': exit (EXIT_SUCCESS); break; } glutPostRedisplay(); } // void idle(void) { glutPostRedisplay(); } // void clean() { glDeleteBuffers(1, &vertexBufferID); glDeleteProgram(programID); glDeleteVertexArrays(1, &vertexArrayID); if (mesh) { delete mesh; mesh = NULL; } } // int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE); glutInitWindowSize(500, 500); glutCreateWindow("OpenGL-Tutorial"); glewInit(); init(); glutDisplayFunc(display); glutReshapeFunc(reshape); glutPassiveMotionFunc(mouse);//Main函数中的鼠标回调函数也需要进行修改 glutKeyboardFunc(keyboard); glutIdleFunc(idle); glutMainLoop(); clean(); return 0; }
vshader:
#version 330 core in vec3 vPosition; in vec3 vNormal; uniform vec3 lightPos; uniform mat4 modelViewProjMatrix; uniform mat4 modelViewMatrix; out vec4 color; // Phong 光照模型的实现 (per-vertex shading) void main() { gl_Position = modelViewProjMatrix * vec4(vPosition, 1.0); // TODO 将顶点坐标变换到相机坐标系 vec4 vertPos_cameraspace = modelViewMatrix * vec4(vPosition, 1.0); vec3 V = vertPos_cameraspace.xyz / vertPos_cameraspace.w; vec3 N = (modelViewMatrix * vec4(vNormal, 0.0)).xyz; // TODO 设置三维物体的材质属性 vec3 ambiColor = vec3(0.2, 0.2, 0.2); vec3 diffColor = vec3(0.5, 0.5, 0.5); vec3 specColor = vec3(0.3, 0.3, 0.3); // TODO 计算N,L,V,R四个向量并归一化 vec3 N_norm = normalize(N); vec3 L_norm = normalize(lightPos - V); vec3 V_norm = normalize(-V); vec3 R_norm = reflect(-L_norm, N_norm); // TODO 计算漫反射系数和镜面反射系数 float lambertian = clamp(dot(L_norm, N_norm), 0.0, 1.0); float specular = clamp(dot(R_norm, V_norm), 0.0, 1.0); //使用BLINN_PHONG模型 //vec3 H_norm = normalize(L_norm + V_norm); //float specular = clamp(dot(N_norm, H_norm), 0.0, 1.0); // float shininess = 10.0; // TODO 计算最终每个顶点的输出颜色 // color = ...; m,,., float d = length(lightPos - V); int a=0,b=0,c=1; color = vec4( ambiColor+diffColor * lambertian+specColor * pow(specular, 10.0), 1.0); //color = vec4( ambiColor+diffColor * lambertian/(a+b*d+c*d*d)+specColor * pow(specular, 10.0)/(a+b*d+c*d*d), 1.0); }
fshader:
#version 330 core in vec4 color; out vec4 fragmentColor; void main() { fragmentColor = color; }
实验3.4 Phong反射模型(2)
一、 实验目的
- 了解OpenGL中基本的光照模型
- 掌握OpenGL中实现基于片元的光照计算
二、 理论背景
一句话总结:利用片元着色器进行对光照的绘制,效果会更加真实。
程序中有三个地方可以执行光照计算:在OpenGL应用程序代码中、在顶点着色器中或者在片元着色器中。无论在何处执行光照计算,都使用相同的基本光照模型,主要区别是在于绘制的效率和外观。
如果在顶点着色器中执行光照计算,那么在光栅化模块中将对顶点的颜色进行插值计算从而得到每个片元的颜色。而本周主要内容是基于片元的光照计算,区别在于片元着色器的输入数据不是顶点着色器计算之后的每个顶点的颜色,而是每个顶点的法向量,在渲染过程中对法向量进行插值而计算片元的颜色,从而输出到光栅化模块中。需要注意的是,输入的每个顶点必须是经过模-视变换和投影变换之后位于裁剪体内部的顶点,因为只有这些顶点最后在光栅化模块中才能得到渲染并显示在绘制窗口中。
如下图所示,左边是采用逐顶点的光照计算的效果,右边采用逐片元光照计算的效果,从结果上来看,因为逐片元计算会对三维物体表面法向量进行差值,从而使得物体表面的法向量场更加均匀,所以产生的光照效果也更加均匀。
三、 实验内容
请按照如下说明顺序阅读代码并完成实验。
需要说明的是,在OpenGL绘制流水线中,光照计算需要等到三维物体经过相机变换和投影变换之后才能进行,因为此时物体在三维空间中的位置才完全确定下来。另外,通常情况下,我们在相机坐标系下来计算所有向量。具体实验过程如下。
a) 参考实验2.2,读入球模型并绘制。另外,关于OpenGL开发环境配置,详细请参考实验1.1。
b) 在程序中计算球面上每个顶点的法向量,并传入顶点着色器。假设球心位于 ,球面上顶点坐标为 ,那么该点的法向量为 ,注意计算完成后对法向量进行归一化。在程序的初始化阶段,对为法向量创建缓存,并将数据传入到顶点着色器如下。
// 获取三维物体的顶点坐标
vector vs = mesh->v();
vector ns;// 计算球面上每个顶点的法向量并归一化,默认球心位于坐标系原点 for (int i = 0; i < vs.size(); ++i) { ns.push_back(vs[i] - vec3(0.0, 0.0, 0.0)); } // 从顶点着色器中获取法向量位置,并传入数值 glGenBuffers(1, &vertexNormalID); glBindBuffer(GL_ARRAY_BUFFER, vertexNormalID); glBufferData(GL_ARRAY_BUFFER, ns.size() * sizeof(vec3f), ns.data(), GL_STATIC_DRAW);
c) 在顶点着色器中将顶点坐标、光源位置和法向量变换到相机坐标系下。
vec4 vertPos_cameraspace = modelViewMatrix * vec4(vPosition, 1.0);
vec4 lightPos_cameraspace = modelViewMatrix * vec4(lightPos, 1.0);V = vertPos_cameraspace.xyz / vertPos_cameraspace.w;
lightPos_new = lightPos_cameraspace.xyz / lightPos_cameraspace.w;
N = (modelViewMatrix * vec4(vNormal, 0.0)).xyz;在上述代码中,首先将顶点位置由模-视变换得到在相机坐标系下的表示,然后需要注意的一点是对输出位置做透视除法以将顶点变换到三维坐标系下。同样,对法向量做类似的计算。
d) 在片元着色器中计算上述环境光反射,漫反射和镜面反射分量。
首先在着色器中定义环境光反射系数、漫反射系数和镜面反射系数的每个颜色分量(ka,kd,ks)如下,
vec3 ambiColor = vec3(0.1, 0.1, 0.1);
vec3 diffColor = vec3(0.5, 0.5, 0.5);
vec3 specColor = vec3(0.3, 0.3, 0.3);
然后计算Phong反射模型涉及到的四个向量,并归一化,其中使用的normalize函数,reflect函数均为GLSL语言内置函数,分别是归一化向量和依据入射向量和法向量计算反射向量。如下所示:
vec3 N_norm = normalize(N);
vec3 L_norm = normalize(lightPos_new - V);
vec3 V_norm = normalize(-V);
vec3 R_norm = reflect(-L_norm, N_norm);
依据上述公式,计算漫反射分量和镜面反射分量如下,注意代码中使用了GLSL语言内置函数clamp,主要作用是将函数值裁剪到规定的两个值之间,具体使用方法请参考相关书籍。
float lambertian = clamp(dot(L_norm, N_norm), 0.0, 1.0);
float specular = clamp(dot(R_norm, V_norm), 0.0, 1.0);
最后,我们将上述几个部分相加,得到最终输出的顶点颜色值如下。
fragmentColor = vec4(ambiColor +
diffColor * lambertian +
specColor * pow(specular, 5.0), 1.0);四、 示例和练习
a) 在片元着色器中,分别只用环境光反射分量、漫反射分量和镜面反射分量作为最后的光照结果,观察输出。
b) 上述代码示例中的实现的光照模型并没有考虑到光源和物体表面顶点之间距离的影响,请尝试加入这一项。
c) 参考教材第189页,在着色器中实现改进的Phong反射模型(即Blinn-Phong反射模型)。
d) 加入鼠标控制,用鼠标控制光源位置,实现不同光源位置下的光照效果。按照上述实验步骤,读入sphere_coarse.off模型,得到的光照效果如下图所示。
(请注意和上一节课程最终光照结果的区别,本节基于片元的光照结果会更加光滑。)
完整代码
与3.3完全类似,只是着色器代码变了而已,因此此处只放出vshader和fshader的代码
#version 330 core in vec3 vPosition; in vec3 vNormal; uniform vec3 lightPos; uniform mat4 modelViewProjMatrix; uniform mat4 modelViewMatrix; out vec3 N; out vec3 V; out vec3 lightPos_new; // Phong 光照模型的实现 (per-fragment shading) void main() { gl_Position = modelViewProjMatrix * vec4(vPosition, 1.0); // TODO 将顶点变换到相机坐标系下 vec4 vertPos_cameraspace = modelViewMatrix * vec4(vPosition, 1.0); // 对顶点坐标做透视投影 V = vertPos_cameraspace.xyz / vertPos_cameraspace.w; // TODO 将光源位置变换到相机坐标系下 vec4 lightPos_cameraspace = modelViewMatrix * vec4(lightPos, 1.0); // 对光源坐标做透视投影 lightPos_new = lightPos_cameraspace.xyz / lightPos_cameraspace.w; // TODO 将法向量变换到相机坐标系下并传入片元着色器 N = (modelViewMatrix * vec4(vNormal, 0.0)).xyz; }
fshader:
#version 330 core in vec3 N; in vec3 V; in vec3 lightPos_new; out vec4 fragmentColor; void main() { // TODO 设置三维物体的材质属性 vec3 ambiColor = vec3(0.1, 0.1, 0.1); vec3 diffColor = vec3(0.5, 0.5, 0.5); vec3 specColor = vec3(0.3, 0.3, 0.3); // TODO 计算N,L,V,R四个向量并归一化 vec3 N_norm = normalize(N); vec3 L_norm = normalize(lightPos_new - V); vec3 V_norm = normalize(-V); vec3 R_norm = reflect(-L_norm, N_norm); // TODO 计算漫反射系数和镜面反射系数 float lambertian = clamp(dot(L_norm, N_norm), 0.0, 1.0); //float specular = clamp(dot(R_norm, V_norm), 0.0, 1.0); //使用BLINN_PHONG模型 vec3 H_norm = normalize(L_norm + V_norm); float specular = clamp(dot(N_norm, H_norm), 0.0, 1.0); float shininess = 10.0; float d = length(lightPos_new - V); int a=0,b=0,c=1; // TODO 计算最终每个片元的输出颜色 fragmentColor = vec4(ambiColor + diffColor * lambertian + specColor * pow(specular, 5.0), 1.0); //fragmentColor=vec4(ambiColor+diffColor * lambertian/(a+b*d+c*d*d)+specColor * pow(specular, 5.0)/(a+b*d+c*d*d), 1.0); }
实验三 光照与阴影
一、 实验内容
实现场景的光照和阴影。示例场景是一个球,需要达到的目标是实现对几何体的光照并以光源为投影中心生成阴影,如下图所示:
具体内容包括:- 场景绘制和背景色设置
创建OpenGL绘制窗口,然后参考实验2.2内容读入三维场景文件(实验课程提供正方体,球等简单几何体的*.off文件)并绘制。为了和后期的阴影颜色区分,将窗口背景色设置为灰色。 - 添加光照效果
参考实验3.3或实验3.4,实现Phong光照效果。 - 添加阴影效果
参考实验3.2,以步骤2中的光源位置作为投影中心,自定义投影平面(为计算方便,推荐使用y=0平面),计算阴影投影矩阵,为三维物体生成阴影。 - 鼠标交互控制光源位置并更新阴影
参考实验2.1,加上鼠标点击,控制光源位置并更新光照效果。另外,由于光源位置同时表示投影中心,所以同时计算三维物体的阴影。
实验三代码
以下绘制阴影采用的是多使用了一个着色器的方法,下面有另外一种方法,就是使用bool变量
#include "include/Angel.h" #include "include/TriMesh.h" #include <cstdlib> #include <iostream> #pragma comment(lib, "glew32.lib") using namespace std; GLuint programID; GLuint vertexArrayID; GLuint vertexBufferID; GLuint vertexNormalID; GLuint vertexIndexBuffer; GLuint vPositionID; GLuint vNormalID; GLuint modelViewMatrixID; GLuint modelViewProjMatrixID; GLuint lightPosID; GLuint ShadowProgramID; GLuint shadowmodelMatrixID; GLuint shadowviewMatrixID; GLuint shadowprojMatrixID; GLuint vShadowPositionID; float rotationAngle = 0; float cameraXpos = 0; float cameraYpos = 0; float cameraZpos = 0; TriMesh* mesh = new TriMesh(); vec3 lightPos(-0.5, 1.5, 2.0); // // 相机参数设置 namespace Camera { mat4 modelMatrix; mat4 viewMatrix; mat4 projMatrix; mat4 ortho( const GLfloat left, const GLfloat right, const GLfloat bottom, const GLfloat top, const GLfloat zNear, const GLfloat zFar ) { // TODO 请按照实验课内容补全相机观察矩阵的计算 mat4 c; c[0][0] = 2.0 / (right - left); c[1][1] = 2.0 / (top - bottom); c[2][2] = -2.0 / (zFar - zNear); c[3][3] = 1.0; c[0][3] = -(right + left) / (right - left); c[1][3] = -(top + bottom) / (top - bottom); c[2][3] = -(zFar + zNear) / (zFar - zNear); return c; } mat4 perspective( const GLfloat fovy, const GLfloat aspect, const GLfloat zNear, const GLfloat zFar) { // TODO 请按照实验课内容补全相机观察矩阵的计算 GLfloat top = tan(fovy * M_PI / 180 / 2) * zNear; GLfloat right = top * aspect; mat4 c; c[0][0] = zNear / right; c[1][1] = zNear / top; c[2][2] = -(zFar + zNear) / (zFar - zNear); c[2][3] = -(2.0*zFar*zNear) / (zFar - zNear); c[3][2] = -1.0; c[3][3] = 0.0; return c; } mat4 lookAt( const vec4& eye, const vec4& at, const vec4& up ) { // TODO 请按照实验课内容补全相机观察矩阵的计算 vec4 n = normalize(eye - at); vec4 u = normalize(vec4(cross(up, n), 0.0)); vec4 v = normalize(vec4(cross(n, u), 0.0)); vec4 t = vec4(0.0, 0.0, 0.0, 1.0); mat4 c = mat4(u, v, n, t); return c * Translate(-eye); } } // // OpenGL 初始化 vector<vec3f> vs; void init() { glClearColor(0.5f, 0.5f, 0.5f, 0.0f); programID = InitShader("vshader_frag.glsl", "fshader_frag.glsl"); // 从顶点着色器和片元着色器中获取变量的位置 vPositionID = glGetAttribLocation(programID, "vPosition"); vNormalID = glGetAttribLocation(programID, "vNormal"); modelViewMatrixID = glGetUniformLocation(programID, "modelViewMatrix"); modelViewProjMatrixID = glGetUniformLocation(programID, "modelViewProjMatrix"); lightPosID = glGetUniformLocation(programID, "lightPos"); // 读取外部三维模型 mesh->read_off("sphere.off"); vs = mesh->v(); vector<vec3i> fs = mesh->f(); vector<vec3f> ns; // TODO 计算球模型在每个顶点的法向量,并存储到ns数组中 for (int i = 0; i < vs.size(); ++i) { ns.push_back(vs[i] - vec3(0.0, 0.0, 0.0)); } //由于球中心在原点,球有一半在平面以下,因此可以将球整体往上平移0.5方便查看阴影 for (int i = 0; i < vs.size(); i++) { vs[i] += vec3(0.0, 0.5, 0.0); } // 生成VAO glGenVertexArrays(1, &vertexArrayID); glBindVertexArray(vertexArrayID); // 生成VBO,并绑定顶点数据 glGenBuffers(1, &vertexBufferID); glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); glBufferData(GL_ARRAY_BUFFER, vs.size() * sizeof(vec3f), vs.data(), GL_STATIC_DRAW); // 生成VBO,并绑定法向量数据 glGenBuffers(1, &vertexNormalID); glBindBuffer(GL_ARRAY_BUFFER, vertexNormalID); glBufferData(GL_ARRAY_BUFFER, ns.size() * sizeof(vec3f), ns.data(), GL_STATIC_DRAW); // 生成VBO,并绑定顶点索引 glGenBuffers(1, &vertexIndexBuffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vertexIndexBuffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, fs.size() * sizeof(vec3i), fs.data(), GL_STATIC_DRAW); //由于绘制阴影和绘制球所用到的着色器不同,因此我们可以换一个着色器来使用 ShadowProgramID = InitShader("vshader_shadow.glsl", "fshader_shadow.glsl"); // 从顶点着色器和片元着色器中获取变量的位置 vShadowPositionID = glGetAttribLocation(ShadowProgramID, "vPosition"); shadowmodelMatrixID = glGetUniformLocation(ShadowProgramID, "modelMatrix"); shadowviewMatrixID = glGetUniformLocation(ShadowProgramID, "viewMatrix"); shadowprojMatrixID = glGetUniformLocation(ShadowProgramID, "projMatrix"); // OpenGL相应状态设置 glEnable(GL_LIGHTING); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); } // // 渲染 void display() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glUseProgram(programID); // 将光源位置传入顶点着色器 // TODO 计算相机观察矩阵和投影矩阵,并传入顶点着色器 vec4 eye(0.0, 3.0, 3.0, 1.0); //由于物体在坐标原点,是单位圆,光源在0,0,2的地方,因此我们可以将观察点设置在0,0,3 vec4 at(0, 0, 0, 1); // 原点 vec4 up(0, 1, 0, 0); // 默认方向 Camera::modelMatrix = mat4(1.0)*Translate(cameraXpos,cameraYpos,cameraZpos); Camera::viewMatrix = Camera::lookAt(eye, at, up); Camera::projMatrix = Camera::perspective(45, 1, 0.1, 100); //使用与3.2实验类似的方法来创建三个矩阵 mat4 modelViewMatrix = Camera::viewMatrix * Camera::modelMatrix; mat4 modelViewProjMatrix = Camera::projMatrix * modelViewMatrix; // TODO 将相机位置传入顶点着色器 glUniformMatrix4fv(modelViewMatrixID, 1, GL_TRUE, &modelViewMatrix[0][0]); glUniformMatrix4fv(modelViewProjMatrixID, 1, GL_TRUE, &modelViewProjMatrix[0][0]); glUniform3fv(lightPosID, 1, &lightPos[0]); glEnableVertexAttribArray(vPositionID); glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); glVertexAttribPointer( vPositionID, 3, GL_FLOAT, GL_FALSE, 0, (void*)0 ); glEnableVertexAttribArray(vNormalID); glBindBuffer(GL_ARRAY_BUFFER, vertexNormalID); glVertexAttribPointer( vNormalID, 3, GL_FLOAT, GL_FALSE, 0, (void*)0 ); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vertexIndexBuffer); glDrawElements( GL_TRIANGLES, int(mesh->f().size() * 3), GL_UNSIGNED_INT, (void*)0 ); glDisableVertexAttribArray(vPositionID); glUseProgram(0); // //此处为我自己添加的部分 glUseProgram(ShadowProgramID); float lx = lightPos[0]; float ly = lightPos[1]; float lz = lightPos[2]; //计算阴影的矩阵 mat4 shadowProjMatrix(-ly, 0.0, 0.0, 0.0, lx, 0.0, lz, 1.0, 0.0, 0.0, -ly, 0.0, 0.0, 0.0, 0.0, -ly); //分别传入三个矩阵 glUniformMatrix4fv(shadowmodelMatrixID, 1, GL_TRUE, shadowProjMatrix*Camera::modelMatrix ); glUniformMatrix4fv(shadowviewMatrixID, 1, GL_TRUE, Camera::viewMatrix); glUniformMatrix4fv(shadowprojMatrixID, 1, GL_TRUE, Camera::projMatrix); glEnableVertexAttribArray(vShadowPositionID);//此处复制上面的代码,只是把vpositionID换成了shadowPositionID glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); glVertexAttribPointer( vShadowPositionID, 3, GL_FLOAT, GL_FALSE, 0, (void*)0 ); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vertexIndexBuffer); glDrawElements( GL_TRIANGLES, int(mesh->f().size() * 3), GL_UNSIGNED_INT, (void*)0 ); glDisableVertexAttribArray(vShadowPositionID); glUseProgram(0); glutSwapBuffers(); } // // 重新设置窗口 void reshape(GLsizei w, GLsizei h) { glViewport(0, 0, w, h); } // // 鼠标响应函数 //vec3 lightPos(-0.5, 1.5, 2.0); void mouse(int button, int state, int x, int y) { // TODO 用鼠标控制光源的位置,并实时更新光照效果 if (state == GLUT_DOWN) { int h = glutGet(GLUT_WINDOW_HEIGHT); int w = glutGet(GLUT_WINDOW_WIDTH); int pos = (h - y) - h / 2; float delta = float(pos) / float(h); lightPos[1] = 1.5 + delta;//调整光在y方向上的变化 } glutPostRedisplay(); } // // 键盘响应函数 void keyboard(unsigned char key, int x, int y) { switch(key) { case 033: // ESC键 和 'q' 键退出游戏 exit(EXIT_SUCCESS); break; case 'w': cameraYpos += 0.1; break; case 'W': cameraYpos += 0.1; break; case 's': cameraYpos -= 0.1; if (cameraYpos <= 0)cameraYpos = 0; break; case 'S': cameraXpos -= 0.1; break; case 'a': cameraXpos -= 0.1; break; case 'A': cameraXpos -= 0.1; break; case 'd': cameraXpos += 0.1; break; case 'D': cameraXpos += 0.1; break; case 'r': cameraXpos = 0; cameraYpos = 0; break; case 'q': cameraZpos -= 0.1; break; case 'Q': cameraZpos -= 0.1; break; case 'e': cameraZpos += 0.1; break; case 'E': cameraZpos += 0.1; break; case 'R': cameraXpos = 0; cameraYpos = 0; break; } glutPostRedisplay(); } // void idle(void) { glutPostRedisplay(); } // void clean() { glDeleteBuffers(1, &vertexBufferID); glDeleteProgram(programID); glDeleteVertexArrays(1, &vertexArrayID); if (mesh) { delete mesh; mesh = NULL; } } // int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE); glutInitWindowSize(500, 500); glutCreateWindow("OpenGL-Tutorial"); glewInit(); init(); glutDisplayFunc(display); glutReshapeFunc(reshape); glutMouseFunc(mouse); glutKeyboardFunc(keyboard); glutIdleFunc(idle); glutMainLoop(); clean(); return 0; }
顶点着色器:
#version 330 core in vec3 vPosition; in vec3 vNormal; uniform vec3 lightPos; uniform mat4 modelViewProjMatrix; uniform mat4 modelViewMatrix; out vec3 N; out vec3 V; out vec3 lightPos_new; // Phong 光照模型的实现 (per-fragment shading) void main() { gl_Position = modelViewProjMatrix * vec4(vPosition, 1.0); // TODO 将顶点变换到相机坐标系下 vec4 vertPos_cameraspace = modelViewMatrix * vec4(vPosition, 1.0); // 对顶点坐标做透视投影 V = vertPos_cameraspace.xyz / vertPos_cameraspace.w; // TODO 将光源位置变换到相机坐标系下 vec4 lightPos_cameraspace = modelViewMatrix * vec4(lightPos, 1.0); // 对光源坐标做透视投影 lightPos_new = lightPos_cameraspace.xyz / lightPos_cameraspace.w; // TODO 将法向量变换到相机坐标系下并传入片元着色器 N = (modelViewMatrix * vec4(vNormal, 0.0)).xyz; }
片元着色器:
#version 330 core in vec3 N; in vec3 V; in vec3 lightPos_new; out vec4 fragmentColor; void main() { // TODO 设置三维物体的材质属性 vec3 ambiColor = vec3(0.1, 0.1, 0.1); vec3 diffColor = vec3(0.5, 0.5, 0.5); vec3 specColor = vec3(0.3, 0.3, 0.3); // TODO 计算N,L,V,R四个向量并归一化 vec3 N_norm = normalize(N); vec3 L_norm = normalize(lightPos_new - V); vec3 V_norm = normalize(-V); vec3 R_norm = reflect(-L_norm, N_norm); // TODO 计算漫反射系数和镜面反射系数 float lambertian = clamp(dot(L_norm, N_norm), 0.0, 1.0); //float specular = clamp(dot(R_norm, V_norm), 0.0, 1.0); //使用BLINN_PHONG模型 vec3 H_norm = normalize(L_norm + V_norm); float specular = clamp(dot(N_norm, H_norm), 0.0, 1.0); float shininess = 10.0; float d = length(lightPos_new - V); int a=0,b=0,c=1; // TODO 计算最终每个片元的输出颜色 fragmentColor = vec4(ambiColor + diffColor * lambertian + specColor * pow(specular, 5.0), 1.0); //fragmentColor=vec4(ambiColor+diffColor * lambertian/(a+b*d+c*d*d)+specColor * pow(specular, 5.0)/(a+b*d+c*d*d), 1.0); }
顶点阴影着色器:
#version 330 core in vec3 vPosition; uniform mat4 modelMatrix; uniform mat4 viewMatrix; uniform mat4 projMatrix; out vec4 fColor; void main() { vec4 v1 = modelMatrix*vec4(vPosition, 1.0); // 由于modelMatrix有可能为阴影矩阵,为了得到正确位置,我们需要做一次透视除法 vec4 v2 = vec4(v1.xyz / v1.w, 1.0); // 考虑相机和投影 vec4 v3 = projMatrix*viewMatrix*v2; // 再做一次透视除法 (为了明确概念,我们显式做一次透视除法) vec4 v4 = vec4(v3.xyz / v3.w, 1.0); gl_Position = v4; fColor =vec4(0.0,0.0,0.0,1.0);//传出黑色作为阴影 }
片元阴影着色器:
#version 330 core in vec4 fColor; out vec4 color; void main() { color = fColor; }
效果:
另外一种方法是使用传入bool变量的方式:GLuint drawShadowID; drawShadowID = glGetUniformLocation(programID, "drawShadow"); //在绘制物体和绘制阴影的时候都需要传给drawshadow这个变量值 glUniform1i(drawShadowID, 0);
然后在片元着色器中,如果是阴影则需要:
if (drawShadow) color = vec4(shadowColor, 1.0); //shadowColor
vshader:
#version 330 core in vec3 vPosition; in vec3 vNormal; out vec3 N; out vec3 V; out vec3 L; uniform vec3 lightPos; //uniform mat4 transformMatrix; uniform mat4 modelViewMatrix; uniform mat4 projMatrix; void main() { vec4 v1 = projMatrix * modelViewMatrix * vec4(vPosition, 1.0); vec4 v2 = vec4(v1.xyz / v1.w, 1.0); //gl_Position = transformMatrix * v2; gl_Position = v2; // vec4 vertPos_cameraspace = modelViewMatrix * vec4(vPosition, 1.0); V = vertPos_cameraspace.xyz / vertPos_cameraspace.w; vec4 lightPos_cameraspace = modelViewMatrix * vec4(lightPos, 1.0); L = lightPos_cameraspace.xyz / lightPos_cameraspace.w; N = (modelViewMatrix * vec4(vNormal, 0.0)).xyz; }
片元着色器:
#version 330 core in vec3 vPosition; in vec3 vNormal; out vec3 N; out vec3 V; out vec3 L; uniform vec3 lightPos; //uniform mat4 transformMatrix; uniform mat4 modelViewMatrix; uniform mat4 projMatrix; void main() { vec4 v1 = projMatrix * modelViewMatrix * vec4(vPosition, 1.0); vec4 v2 = vec4(v1.xyz / v1.w, 1.0); //gl_Position = transformMatrix * v2; gl_Position = v2; // vec4 vertPos_cameraspace = modelViewMatrix * vec4(vPosition, 1.0); V = vertPos_cameraspace.xyz / vertPos_cameraspace.w; vec4 lightPos_cameraspace = modelViewMatrix * vec4(lightPos, 1.0); L = lightPos_cameraspace.xyz / lightPos_cameraspace.w; N = (modelViewMatrix * vec4(vNormal, 0.0)).xyz; }
-
第三次图形学实验
2019-01-30 22:14:45本实验为综合实验, 任务是利用光线跟踪算法进行Whitted全局光照计算,并对读入场景进行真实感绘制。(特别提醒: 网上类似的projects可以参考,但不能照抄. 如... -
计算机图形学光照模型和面绘制算法.ppt
2020-07-23 15:27:46计算机图形学 成晓静 XIAOJING CHENGO163 COM 第8章光照模型与面绘制算法 光源 表面光照效果 基本光照模型 多边形绘制算法 光线跟踪方法 辐射度光照模型 添加表面细节 纹理映射 几个基本概念 3光照模型 主要用于对象... -
深圳大学计算机图形学大作业实验报告.doc
2021-07-27 01:53:07深圳大学计算机图形学大作业实验报告深 圳 大 学 实 验 报 告实验课程名称: 计算机图形学实验项目名称: 计算机期末综合大作业学院: 计算机与软件 专业: 计算机科学与技术报告人: 班级: 一班组员:指导教师: ... -
计算机图形学实验(范文).doc
2020-11-18 20:57:42计算机图形学实验(范文) 第 页 PAGE \* Arabic 1第 页 PAGE \* Arabic 1 实验报告 学院系名称理学院 姓名 杨建涛 学号 20104886 专业 数学与应用数学 班级 2 实验项目 第五次试验综合实验 课程名称 计算机图形学 ... -
计算机图形学 mfc vs 实验一 陈老师
2022-03-16 14:06:39掌握二维基本图元直线段生成的DDA算法、中点算法、Bresenham算法; 掌握二维基本图元的圆的扫描算法、中点画圆算法和Bresenham算法; 掌握二维图元填充的种子填充算法和多边形...掌握综合应用画线、画圆和填充算法。 -
计算机图形学实验一报告.pdf
2020-07-20 00:17:44计算机科学与通信工程学院 实验报告 课 程 计算机图形学 实验题 目 二维图形绘制 学生姓名 学 号 专业班 级 指 导教 师 日 期 成绩评定表 评价 权 得 具体内容 内容 重 分 论证 方案论证 综合分析的正 20 分析 确... -
计算机图形学实验二报告.pdf
2020-07-20 00:17:41计算机科学与通信工程学院 实验报告 课 程 计算机图形学 实验题 目 曲线拟合 学生姓名 学 号 专业班 级 指 导教 师 日 期 成绩评定表 评价 权 得 具体内容 内容 重 分 论证 方案论证与综合分析的正 20 分析 确合理性... -
计算机图形学实验三报告﹎.pdf
2020-10-21 08:48:09计算机科学与通信工程学院 实验报告 课 程 计算机图形学 实 验 题 目 二维图形变换 学 生 姓 名 学 号 专 业 班 级 指 导 教 师 日 期 ' . 成绩评定表 评价内容 具体内容 权重 得分 方案论证与综合分析的正确 论证... -
计算机图形学实验二报告总结计划.docx
2020-11-19 21:30:27计算机科学与通信工程学院 实验报告 课 程 计算机图形学 实 验 题 目 曲线拟合 学 生 姓 名 学 号 专 业 班 级 指 导 教 师 日 期 成绩评定表 评价内容 具体内容 权重 得分 方案论证与综合分析的正确 20% 论证分析 ... -
基于OpenGL的计算机图形学教学改革探索
2021-01-30 21:42:22针对传统的本科计算机图形学教学中存在的问题,提出了基于OpenGL实验平台的教学改革方法,设计了基于OpenGL计算机图形学实验项目,并给出了综合评定考核方法。实践表明,该教学模式不仅能让学生较好掌握所学习的内容... -
计算机图形学综合(画图形、旋转、缩放等)实验
2009-06-04 14:55:04计算机图形学的综合实验。但是制作得比较简单。能实验所要求的基本功能。如画直线、椭圆、矩形、多边形。能对图形进行旋转、平移、缩小、放大。能够实现对多变形的裁剪。 -
计算机图形学完整笔记(一):图形学概论
2020-08-14 08:57:33文章目录第一章 计算机图形学概论1.1 全书概述1.2 计算机图形学定义1.3 计算机图形学研究内容1.4 计算机图形学的发展历史1.5 计算机图形学的应用领域1.6 计算机图形系统组成1.7 图形显示设备1.8 图形学相关概念1.9 ...