图形学入门自学笔记——lec4【坐标变换】

The Camera相机

在世界空间定义相机

  • 相机位置点 e ⃗ \vec{e} e
  • 拍摄方向 g ^ \hat{g} g^
  • 向上的方向 t ^ \hat{t} t^(垂直于拍摄方向)
  • 相机空间的正交基 ( g ^ × t ^ , t ^ , g ^ ) (\hat{g}\times\hat{t}, \hat{t}, \hat{g}) (g^×t^,t^,g^)

把世界空间变换到相机空间,标准基(X,Y,-Z)

  • 平移相机位置到原点
  • 旋转相机,使得拍摄方向到-Z,向上方向到Y

    观察变换 M v i e w ⋅ v w o r l d = R v i e w ⋅ T v i e w ⋅ v w o r l d M_{view}\cdot v_{world}=R_{view}\cdot T_{view}\cdot v_{world} Mviewvworld=RviewTviewvworld
    这个方程表示了将世界空间中的一个向量 v w o r l d v_{world} vworld转换到相机或观察空间的过程, M v i e w M_{view} Mview通常是视图变换矩阵, R v i e w R_{view} Rview是旋转变换矩阵, T v i e w T_{view} Tview是平移变换矩阵。
  1. 先将相机从位置 e ⃗ \vec{e} e 移到原点
  2. 再把拍摄方向 g ^ \hat{g} g^旋转到-Z上
  3. 再把向上方向 t ^ \hat{t} t^旋转到Y

OpenGL Transformation

在同一个矩阵设置Model Transformation,Viewing Transformation
Model Transformation,模型变换,指的是将模型从模型坐标系(局部坐标系)变换到世界坐标系中的过程。在OpenGL中,模型变换通常通过设置模型矩阵(Model Matrix)来实现。模型矩阵负责将模型的顶点坐标从局部坐标系变换到世界坐标系,包括平移(Translation)、旋转(Rotation)、缩放(Scaling)等操作。
Viewing Transformation,视图变换,指的是将场景或物体从世界坐标系变换到相机或观察者的视角空间中的过程。在OpenGL中,视图变换通常通过设置视图矩阵(View Matrix)来实现。视图矩阵负责将世界坐标系中的物体位置和方向变换到相机或观察者的视角空间中,用于定义观察者的视角和观察方向。

void glMatrixMode(GL_MODELVIEW):用于设置当前矩阵堆栈模式的函数调用。这个函数告诉OpenGL后续对矩阵堆栈的操作是针对模型视图矩阵(Model-View Matrix)的。默认设置是ModelView,除非用GL_PROJECTION作为参数。

使用glLoadIndentity()初始化矩阵
使用glMultMatrix()复合变换矩阵

Viewing Transformation

OpenGL提供gluLookAt()计算,用户提供:相机位置 e ⃗ = e y e \vec{e}=eye e =eye,拍摄方向 g ^ = c e n t e r − e y e \hat{g}=center-eye g^=centereye,向上方向 t ^ = u p \hat{t}=up t^=up
void gluLookAt(
GLdouble e y e x eye_x eyex, GLdouble e y e y eye_y eyey, GLdouble e y e z eye_z eyez,
GLdouble c e n t e r x center_x centerx, GLdouble c e n t e r y center_y centery, GLdouble c e n t e r z center_z centerz,
GLdouble u p x up_x upx, GLdouble u p y up_y upy, GLdouble u p z up_z upz);
默认设置: gluLookat(0.0, 0.0, 0.0, 0.0, 0.0, -100.0, 0.0, 1.0, 0.0);

Model Transformation

通过复合下列变换,把物体放入视锥

  • void glTranslate{fd}(TYPE x, TYPE y, TYPE z);将当前指定的矩阵(通常是模型视图矩阵或投影矩阵)进行平移变换。
  • void glRotate{fd}(TYPE angle, TYPE x, TYPE y, TYPE z);将当前指定的矩阵进行旋转变换。
  • void glScale{fd}(TYPE x, TYPE y, TYPE z);将当前指定的矩阵进行缩放变换。
  • void glLoadMatrix{fd}(const TYPE *m);将指定的 4x4 矩阵 m 加载到当前指定的矩阵堆栈顶部
  • void glMultMatrix{fd}(const TYPE *m);将指定的 4x4 矩阵 m 与当前指定的矩阵相乘,并将结果替换为当前指定的矩阵。

OpenGL Model Transformation三维流程

  1. 首先
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();用于将当前处理的矩阵重置为单位矩阵
  2. 其次
    gluLookat( e x , e y , e z , c e n t e r x , c e n t e r y , c e n t e r z , u p x , u p y , u p z e_x, e_y, e_z, center_x, center_y, center_z, up_x, up_y, up_z ex,ey,ez,centerx,centery,centerz,upx,upy,upz);
  3. 最后,比如绕着物体中心点( c e n t e r x , c e n t e r y , c e n t e r z center_x, center_y, center_z centerx,centery,centerz),按Z轴转动90°
    glTranslatef( c e n t e r x , c e n t e r y , c e n t e r z center_x, center_y, center_z centerx,centery,centerz),
    glRotate(90, 0, 0, 1),
    glTranslatef( − c e n t e r x , − c e n t e r y , − c e n t e r z -center_x, -center_y, -center_z centerx,centery,centerz)
    Draw_Obj()
  4. 可选项:保存变换栈glPushMatrix()和glPopMatrix()

法向量

法向量来源

  • 来自OBJ文件,读取“VN 1.0 2.0 3.0”
    VN:是OBJ文件中用于表示法向量的关键字。
    1.0 2.0 3.0:表示一个三维向量,其中:
    1.0 是法向量在 X 轴方向上的分量(即法向量的 X 分量)。
    2.0 是法向量在 Y 轴方向上的分量(即法向量的 Y 分量)。
    3.0 是法向量在 Z 轴方向上的分量(即法向量的 Z 分量)。
  • 自己根据网格顶点坐标计算
    1)面法向量
    2)顶点法向量

法向量变换

  • Model-View变换矩阵。法向量变换矩阵是 ( M T ) − 1 (M^T)^{-1} (MT)1。当 M M M是正交矩阵时, M = ( M T ) − 1 M=(M^T)^{-1} M=(MT)1
    推导:法向量 n n n,曲面切线 v v v,变换矩阵 M M M
    切向量 v v v经过线性变换 M M M后, v ′ = M ⋅ v v'=M\cdot v v=Mv
    因为对任意切向量 v v v,都有 < v , n > = v T ⋅ n = 0 <v,n>=v^T\cdot n=0 <v,n>=vTn=0

v T ⋅ M T ⋅ ( M T ) − 1 ⋅ n = 0 ( M ⋅ v ) T ⋅ ( M T ) − 1 ⋅ n = 0 ( v ′ ) T ⋅ ( M T ) − 1 ⋅ n = 0 < v ′ , ( M T ) − 1 ⋅ n > = 0 所以 n ′ = ( M T ) − 1 ⋅ n v^T\cdot M^T\cdot (M^T)^{-1}\cdot n=0\\ (M\cdot v)^T\cdot (M^T)^{-1}\cdot n=0\\ (v')^T\cdot (M^T)^{-1}\cdot n=0\\ <v', (M^T)^{-1}\cdot n>=0\\ 所以n'=(M^T)^{-1}\cdot n vTMT(MT)1n=0(Mv)T(MT)1n=0(v)T(MT)1n=0<v,(MT)1n>=0所以n=(MT)1n

OpenGL法向量设置
在绘制顶点前先指定顶点法向量

glRotate(90,0,0,1),

glBegin(GL_TRIANGLES);
for(int k=0;k<3;k++){
glNormal3f(n.x, n.y, n.z);//设置顶点法向量
glVertex3f(v.x, v.y, v.z);
}
glEnd();

投影变换

正交投影

正交投影
在这里插入图片描述

相机坐标下,相机在原点,看向-Z,向上为Y
移除掉物体的Z坐标
将结果缩放后映射到 [ − 1 , 1 ] 2 [-1,1]^2 [1,1]2

透视投影

平行线相交于一点,满足近大远小
透视投影
将视角锥内点(x,y,z,1)投影至近平面n
再进行正交投影


在这里插入图片描述

如何求透视变换:

  • 先将视角锥内点(x,y,z,1)投影至近平面n
    M p e r s p − > o r t h o 4 × 4 = [ n 0 0 0 0 n 0 0 0 0 n + f − n f 0 0 1 0 ] M^{4\times 4}_{persp->ortho}=\begin{bmatrix}n&0&0&0\\0&n&0&0\\0&0&n+f&-nf\\0&0&1&0\end{bmatrix} Mpersp>ortho4×4= n0000n0000n+f100nf0

M p e r s p − > o r t h o ⋅ [ x y z 1 ] = [ n x n y ( n + f ) z − n f z ] M_{persp->ortho}\cdot\begin{bmatrix}x \\y\\z\\1\end{bmatrix}=\begin{bmatrix}nx\\ny\\(n+f)z-nf\\z\end{bmatrix} Mpersp>ortho xyz1 = nxny(n+f)znfz

  • 再进行正交投影,即合成为 M p e r s p = M o r t h o ⋅ M p e r s p → o r t h o M_{persp}=M_{ortho}\cdot M_{persp\to ortho} Mpersp=MorthoMpersportho

Frustum 视锥

视锥定义:

  • 宽高比(aspect ratio),如4:3,16:9
  • 垂直可视高度(field-of-view Y, fovY)
  • 近平面n
  • 远平面f

计算视锥变换的长方体
t a n f o v Y 2 = t ∣ n ∣ 和 a s p e c t = r t tan\frac{fovY}{2}=\frac{t}{|n|}和aspect=\frac{r}{t} tan2fovY=ntaspect=tr
在这里插入图片描述

在这里插入图片描述

投影转换Projection Transformations

透视投影

void gluPerspective(double f o v y fovy fovy,double a s p e c t aspect aspect, double z N e a r zNear zNear, double z F a r zFar zFar);
用于设置透视投影矩阵。这个函数可以帮助定义透视投影的视角、屏幕宽高比、近裁剪面和远裁剪面的参数,以便正确设置视景体和投影矩阵,从而实现透视投影效果。

void glFrustum(GLdouble l e f t left left, GLdouble r i g h t right right, GLdouble b o t t o m bottom bottom, GLdouble t o p top top, GLdouble n e a r near near, GLdouble f a r far far);
glFrustum 是 OpenGL 中用于设置透视投影矩阵的函数之一,它定义了一个视景体的截锥体(Frustum),用于将三维空间中的物体坐标转换为二维屏幕坐标,实现透视投影效果。
left, right:指定视景体左侧和右侧面的位置,即截锥体左侧和右侧面在 X 轴上的位置坐标。通常 left 小于 right。
bottom, top:指定视景体底部和顶部面的位置,即截锥体底部和顶部面在 Y 轴上的位置坐标。通常 bottom 小于 top。
near, far:指定视景体的近裁剪面和远裁剪面的位置,即截锥体的近端和远端距离视点的距离。near 表示近裁剪面的距离,必须为正值;far 表示远裁剪面的距离,必须大于 near。

正交投影
void glOrtho(double l e f t left left, double r i g h t right right, double b o t t o m bottom bottom, double t o p top top, double z N e a r zNear zNear, double z F a r zFar zFar);
glOrtho 是 OpenGL 中用于设置正交投影矩阵的函数之一,它定义了一个正交投影的视景体(Orthographic Frustum),用于将三维空间中的物体坐标转换为二维屏幕坐标,实现正交投影效果。

投影转换流程

  1. glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

  2. void gluPerspective();
    void glFrustum();
    void glOrtho();

转换顺序

  • 首先Modeling:把物体放入世界空间
    M m o d e l = M r o t a t e ⋅ M t r a n s l a t e ⋅ M m o d e l M_{model}=M_{rotate}\cdot M_{translate}\cdot M_{model} Mmodel=MrotateMtranslateMmodel
  • 再Viewing:摆好相机,切换到相机空间
    M v i e w = R v i e w ⋅ T v i e w M_{view}=R_{view}\cdot T_{view} Mview=RviewTview
  • 然后拍摄,场景投影到近平面
    M p e r s p = M o r t h o ⋅ M p e r s p → o r t h o M_{persp}=M_{ortho}\cdot M_{persp\to ortho} Mpersp=MorthoMpersportho
  • 最后视屏变换: M v i e w p o r t M_{viewport} Mviewport

M t r a n s f o r m = M v i e w p o r t ⋅ M p e r s p ⋅ M v i e w ⋅ M m o d e l M_{transform}=M_{viewport}\cdot M_{persp}\cdot M_{view}\cdot M_{model} Mtransform=MviewportMperspMviewMmodel
y = M v p o r t ⋅ ( M p r o j ⋅ ( M m v ⋅ x ) ) = ( M v p o r t ⋅ M p r o j ⋅ M m v ) ⋅ x y=M_{vport}\cdot(M_{proj}\cdot (M_{mv}\cdot x))=(M_{vport}\cdot M_{proj}\cdot M_{mv})\cdot x y=Mvport(Mproj(Mmvx))=(MvportMprojMmv)x

分层模型

  • 一个复杂的对象可以由相对简单的对象构成
  • 主部件的变换作用到副部件
  • 可以用树结构描述这种变换关系
  • 每个节点有自己的局部坐标

分层模型1:

glMatrixMode(GL_MODEVIEW);
glLoadIdentity();
glTranslatef(bx, by, bz);
  create_base(); //创建基座(base)或基础部分的物体。
glTranslatef(0, j1y, 0);
glRotatef(joint1_orientation);
 create_joint1(); //创建关节1
glTranslatef(0, uay, 0);
 create_upperarm(); //创建上臂
glTranslatef(0, j2y);
glRotatef(joint2_orientation);
 create_joint2(); //创建关节2
glTranslatef(0, lay, 0);
 create_lowerarm(); //创建下臂
glTranslatef(0, py, 0);
glRotatef(point_orientation);
 create_pointer(); //创建指针

在这里插入图片描述

分层模型2:

在这里插入图片描述
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(bx, by, bz);
 create_base();
glTranslatef(0, jy, 0);
glRotatef(joint1_orientation);
 create_joint1();
glTranslatef(0, ay, 0);
 create_upperarm();
glTranslatef(0, wy);
glRotatef(wrist_orientation);
 create_wrist();
glPushMatrix();//save frame
 glTranslatef(-xf, fy0, 0);
 glRotatef(lowerfinger1_orientation);
 glTranslatef(0, fy1, 0);
  create_lowerfinger1();
 glTranslatef(0, fy2, 0);
 glRotatef(upperfinger1_orientation);
  create_fingerjoint1();
 glTranslatef(0, fy3, 0);
  create_upperfinger1();
glPopMatrix(); //restore frame
glPushMatrix();
 // do finger 2…
glPopMatrix();
glPushMatrix();
 // do finger 3…
glPopMatrix();

在这里插入图片描述

剪切 Clipping

投影变换后,齐次剪切空间(Homogeneous Clip Space)
裁剪操作

  • 删除视野外的图元
  • 保留视野内的图元
  • 裁剪部分在视野内的图元
  • 关键在于快速实现
    在这里插入图片描述在这里插入图片描述

线段裁剪

  • Cohen-Sutherland algorithm
    多边形裁剪
  • Sutherland-Hodgman algorithm

线段裁剪

平面分成九个区域:

  • Top: 0x1000
  • Bottom: 0x0100
  • Right: 0x0010
  • Left: 0x0001
    在这里插入图片描述

接受:两个点的位置码(OR)为0
淘汰:两个点的位置码(AND)不为0
其余:按TBRL循环裁剪直到接受或者淘汰

OR和AND指按位或和按位与

多边形裁剪

多边形 P = { p 1 , p 2 , p 3 , p 4 } P=\{p_1,p_2,p_3,p_4\} P={p1,p2,p3,p4}
多边形裁剪窗口(长方形)
按裁剪窗口的边l依次对多边形P裁剪
输入是多边形,输出裁剪后的多边形

foreach clipping edge l:
 SH_CLIPPING(l, in_P, out_P)
 in_P = out_P

在这里插入图片描述
def SH_CLIPPING(l, in_P, out_P):
 foreach edge ( p i , p j p_i,p_j pi,pj) of in_P:
  case 1: Front & Front
  case 2: Front & Back
  case 3: Back & Front
  case 4: Back & Back

在这里插入图片描述
def SH_CLIPPING( l , i n p , o u t p l,in_p,out_p l,inp,outp):
 1. 边 ( p 1 , p 2 ) : o u t p = { p 2 } (p_1,p_2):out_p=\{p_2\} (p1,p2):outp={p2}
 2. 边 ( p 2 , p 3 ) : o u t p = { p 2 , p 3 } (p_2,p_3):out_p=\{p_2,p_3\} (p2,p3):outp={p2,p3}
 3. 边 ( p 3 , p 4 ) : o u t p = { p 2 , p 3 , p 3 ′ } (p_3,p_4):out_p=\{p_2,p_3,p'_3\} (p3,p4):outp={p2,p3,p3}
 4. 边 ( p 4 , p 1 ) : o u t p = { p 2 , p 3 , p 3 ′ , p 4 ′ , p 1 } (p_4,p_1):out_p=\{p_2,p_3,p'_3,p'_4,p_1\} (p4,p1):outp={p2,p3,p3,p4,p1}

归一化处理

透视纠正

用在纹理贴图纠正等。线性插值时对于远的区域插值纠正。齐次坐标保留W(=Z)值的用途之一

从齐次坐标转换成欧式空间坐标(除以W=Z)
生成NDC(Normalized Device Coordinates)

NDC(Normalized Device Coordinates)指的是归一化的设备坐标。它是将物理设备坐标系转换为标准化设备坐标系的过程,这个转换过程是为了方便计算机进行图形渲染和显示。

屏幕映射(Screen Mapping)

从NDC坐标屏幕映射:把标准二维图像缩放到屏幕坐标

输出每个图元顶点的:

  1. 屏幕坐标 ( x , y ) (x,y) (x,y)和深度 z z z坐标
  2. 颜色 ( r , g , b ) (r,g,b) (r,g,b)和透明度alpha
  3. 纹理坐标 ( s , t ) (s,t) (s,t)
    从NDC到屏幕坐标:
  • 屏幕左下角是(0,0)
  • 把(-1,-1)移动到(0,0)
  • x轴放大 width/2,y轴放大height/2
    在这里插入图片描述

M v i e w p o r t = [ w i d t h 2 0 0 w i d t h 2 0 h e i g h t 2 0 h e i g h t 2 0 0 1 0 0 0 0 1 ] M_{viewport}=\begin{bmatrix}\frac{width}{2}&0&0&\frac{width}{2}\\0&\frac{height}{2}&0&\frac{height}{2}\\0&0&1&0\\0&0&0&1\end{bmatrix} Mviewport= 2width00002height0000102width2height01

调用glViewport来设置
void glViewport(GLint x, GLint y,GLsizei width, GLsiezi height)
(x, y):viewport左下角相对于窗口左下角位置
width/height:viewport的宽高
默认是(0,0, window_width, window_height),从NDC映射到(x,y,width,height)

一般来说,viewport的宽高比和视锥的宽高比设置为一样
不然最后屏幕图像会变形

void reshape(int w, int h)
{
	glViewport(0,0,(GLsizei)w,(GLsizei)h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentiy();
	//透视变换1,保持视锥比例,拉伸到整个窗口
	gluPerspective(60,1,1,10);
	//透视变换2,调整视锥宽高比,和窗口一样
	gluPerspective(60,(float)w/(float)h,1,10);
	gluPostRedisplay();
}

Tessellation Shader细分着色器

曲面细分着色器:在已有图元的基础上加入更多Vertex,这些Vertex还是在原始的图元内,以形成更精细的模型

案例

谢尔宾斯基三角形Sieroinski Triangle

在这里插入图片描述

void draw_triangle(vec2 a, vec2 b, vec2 c)
{
	glBegin(GL_TRIANGLES);
	glVertex2f(a.x, a.y);
	glVertex2f(b.x, b.y);
	glVertex2f(c.x, c.y);
	glEnd();
}

void divide_triangle(vec2 a, vec2 b, vec2 c, int depth)
{
	if(depth <= 0)
	{
		return draw_triangle(a, b, c);
	}
	vec2 v1 = (a + b) * 0.5f;
	vec2 v2 = (a + b) * 0.5f;
	vec2 v3 = (b + c) * 0.5f;

	divide_triangle(a, v1, v2, depth - 1);
	divide_triangle(c, v2, v3, depth - 1);
	divide_triangle(b, v3, v1, depth - 1);
}

科赫雪花Koch Snowflake

在这里插入图片描述

vec3 compute_koch_point(vec3 a, vec3 b)//旋转线段
{
	vec3 seg = b - a;
	vec3 ret;
	ret.x = seg.x * cos(radians(60.0f)) - seg.y * sin(radians(60.0f));//旋转矩阵
	ret.y = seg.x * sin(radians(60.0f)) + seg.y * cos(radians(60.0f));
	ret.z = 0.0f;
	return ret + a;
}

void divide_line(vec3 a, vec3 b, int m)
{
	if(m <= 0)
	{
		return draw_line(a, b);
	}
	vec3 v1 = a * (2.f / 3.f) + b * (1.f / 3.f);
	vec3 v3 = a * (1.f / 3.f) + b * (2.f / 3.f);
	vec3 v2 = compute_koch_point(v1, v3);
	divide_line(a, v1, m - 1);
	divide_line(v1, v2, m - 1);
	divide_line(v2, v3, m - 1);
	divide_line(v3, b, m - 1);
}

几何着色器

逐图元着色操作,生成新的图元
与镶嵌相反,在原始图元外增加新的Vertex

  • 22
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值