山东大学计算机图形学期末复习6——CG10下
##CG10下
-
将世界坐标中的任意点 P P P 变换到以相机为中心的“观察坐标系”下(右手坐标系)
- n \mathbf{n} n:从相机眼睛朝向观察点的反方向,代表“前方”;
- u \mathbf{u} u:观察坐标系的 x 轴,向右;
- v \mathbf{v} v:观察坐标系的 y 轴,向上(通过 up 向量决定);
最终矩阵应该能把顶点从世界坐标系变换到 ( u , v , n ) (\mathbf{u}, \mathbf{v}, \mathbf{n}) (u,v,n) 的坐标系中。
第一步:建立摄像机坐标系(右手系)
设:
- e = ( e x , e y , e z ) \mathbf{e} = (e_x, e_y, e_z) e=(ex,ey,ez):相机位置;
- a = ( a x , a y , a z ) \mathbf{a} = (a_x, a_y, a_z) a=(ax,ay,az):观察点(“at”);
- u p = ( u p x , u p y , u p z ) \mathbf{up} = (up_x, up_y, up_z) up=(upx,upy,upz):相机头顶方向;
定义:
- n = e − a ∥ e − a ∥ \mathbf{n} = \frac{\mathbf{e} - \mathbf{a}}{\|\mathbf{e} - \mathbf{a}\|} n=∥e−a∥e−a:从观察点朝眼睛方向(注意方向反了);
- u = u p × n ∥ u p × n ∥ \mathbf{u} = \frac{\mathbf{up} \times \mathbf{n}}{\|\mathbf{up} \times \mathbf{n}\|} u=∥up×n∥up×n:右方向(x轴);
- v = n × u \mathbf{v} = \mathbf{n} \times \mathbf{u} v=n×u:上方向(y轴);
这一步建立了观察坐标系中三个正交单位轴 u , v , n \mathbf{u}, \mathbf{v}, \mathbf{n} u,v,n。
第二步:坐标旋转(将世界坐标轴对齐到相机轴)
将世界中的点表示成相对于 u , v , n \mathbf{u}, \mathbf{v}, \mathbf{n} u,v,n 的新基坐标形式。这本质是将世界坐标投影到新坐标系上。
写成矩阵:
R = [ u x u y u z 0 v x v y v z 0 n x n y n z 0 0 0 0 1 ] R = \begin{bmatrix} u_x & u_y & u_z & 0 \\ v_x & v_y & v_z & 0 \\ n_x & n_y & n_z & 0 \\ 0 & 0 & 0 & 1 \\ \end{bmatrix} R= uxvxnx0uyvyny0uzvznz00001
这是“旋转矩阵”,它将点从世界坐标系变换到摄像机的局部坐标轴。第三步:坐标平移(把相机移动到原点)
我们还需要把摄像机从世界坐标系中的位置 e \mathbf{e} e 移动到原点。
写成矩阵:
T = [ 1 0 0 − e x 0 1 0 − e y 0 0 1 − e z 0 0 0 1 ] T = \begin{bmatrix} 1 & 0 & 0 & -e_x \\ 0 & 1 & 0 & -e_y \\ 0 & 0 & 1 & -e_z \\ 0 & 0 & 0 & 1 \\ \end{bmatrix} T= 100001000010−ex−ey−ez1 第四步:组合成观察矩阵
先平移(T),再旋转(R),也就是右乘:
M view = R ⋅ T M_{\text{view}} = R \cdot T Mview=R⋅T这样一个点 p world p_{\text{world}} pworld 在世界坐标系中,经过:
p camera = R ⋅ T ⋅ p world p_{\text{camera}} = R \cdot T \cdot p_{\text{world}} pcamera=R⋅T⋅pworld
就变换到了观察坐标系中。最终观察变换矩阵公式
将旋转和平移乘在一起,得:
M view = [ u x u y u z − u ⋅ e v x v y v z − v ⋅ e n x n y n z − n ⋅ e 0 0 0 1 ] M_{\text{view}} = \begin{bmatrix} u_x & u_y & u_z & -\mathbf{u} \cdot \mathbf{e} \\ v_x & v_y & v_z & -\mathbf{v} \cdot \mathbf{e} \\ n_x & n_y & n_z & -\mathbf{n} \cdot \mathbf{e} \\ 0 & 0 & 0 & 1 \end{bmatrix} Mview= uxvxnx0uyvyny0uzvznz0−u⋅e−v⋅e−n⋅e1
这个矩阵正是gluLookAt()
背后的核心变换。 -
正交投影(Orthographic Projection)
- 投影方向:沿 z 轴 平行投影到 x-y 平面上。
- 不考虑透视,不管物体离相机远近,显示尺寸恒定。
将三维点 ( x , y , z ) (x, y, z) (x,y,z) 投影为:
( x ′ , y ′ , z ′ ) = ( x , y , 0 ) (x', y', z') = (x, y, 0) (x′,y′,z′)=(x,y,0)
变换矩阵(简化型):
[ 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 ] \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} 1000010000000001
此变换将所有点的 z 值清零。使用
glOrtho
或gluOrtho2D
设置 投影平行六面体(view box):glOrtho(left, right, bottom, top, near, far);
矩阵本质上把这个六面体映射到标准立方体 [ − 1 , 1 ] 3 [-1, 1]^3 [−1,1]3。
正交投影矩阵如下(OpenGL定义):
M ortho = [ 2 r − l 0 0 − r + l r − l 0 2 t − b 0 − t + b t − b 0 0 − 2 f − n − f + n f − n 0 0 0 1 ] M_{\text{ortho}} = \begin{bmatrix} \frac{2}{r - l} & 0 & 0 & -\frac{r + l}{r - l} \\ 0 & \frac{2}{t - b} & 0 & -\frac{t + b}{t - b} \\ 0 & 0 & -\frac{2}{f - n} & -\frac{f + n}{f - n} \\ 0 & 0 & 0 & 1 \end{bmatrix} Mortho= r−l20000t−b20000−f−n20−r−lr+l−t−bt+b−f−nf+n1
参数含义:- l , r l, r l,r:x 方向左右边界
- b , t b, t b,t:y 方向上下边界
- n , f n, f n,f:z 方向近平面和远平面(注意 OpenGL 中这两个必须是正数)
-
透视投影(Perspective Projection)
- 模拟人眼视角:近处大,远处小
- 所有投影线收敛于“投影中心”(即相机位置)
- 常使用一个“视锥体”(frustum)包围视野范围
设观察点为原点,投影面为 z = − d z = -d z=−d,三维点为 ( x , y , z ) (x, y, z) (x,y,z),投影为:
x ′ = d x z , y ′ = d y z , z ′ = d x' = \frac{d x}{z}, \quad y' = \frac{d y}{z}, \quad z' = d x′=zdx,y′=zdy,z′=d
齐次矩阵形式:
$$
\begin{bmatrix}
1 & 0 & 0 & 0 \
0 & 1 & 0 & 0 \
0 & 0 & 1 & 0 \
0 & 0 & \frac{1}{d} & 0
\end{bmatrix}
\cdot
\begin{bmatrix}
x \ y \ z \ 1
\end{bmatrix}\begin{bmatrix}
x \ y \ z \ \frac{z}{d}
\end{bmatrix}
\Rightarrow
\left( \frac{x}{z/d}, \frac{y}{z/d}, 1 \right)
$$- 当我们将这个矩阵与齐次坐标相乘时,得到新的齐次坐标 ( x ′ , y ′ , z ′ , w ′ ) \left(x^{\prime}, y^{\prime}, z^{\prime}, w^{\prime}\right) (x′,y′,z′,w′) 。
- 在齐次坐标中,最终的二维坐标是通过将前三维除以第四维 ( w ′ ) \left(w^{\prime}\right) (w′) 得到的:
( x ′ w ′ , y ′ w ′ , z ′ w ′ ) \left(\frac{x^{\prime}}{w^{\prime}}, \frac{y^{\prime}}{w^{\prime}}, \frac{z^{\prime}}{w^{\prime}}\right) (w′x′,w′y′,w′z′)
- 根据矩阵乘法的结果:
x ′ = x , y ′ = y , z ′ = z , w ′ = z d x^{\prime}=x, \quad y^{\prime}=y, \quad z^{\prime}=z, \quad w^{\prime}=\frac{z}{d} x′=x,y′=y,z′=z,w′=dz
- 因此,透视除法后的坐标为:
( x z / d , y z / d , z z / d ) = ( x d z , y d z , d ) \left(\frac{x}{z / d}, \frac{y}{z / d}, \frac{z}{z / d}\right)=\left(\frac{x d}{z}, \frac{y d}{z}, d\right) (z/dx,z/dy,z/dz)=(zxd,zyd,d)
- 这正好对应于透视投影的公式。
结果需透视除法 x / w x/w x/w 实现拉伸效果。
使用
glFrustum
定义视锥体:glFrustum(left, right, bottom, top, near, far);
使用
gluPerspective
更常见:gluPerspective(fovy, aspect, near, far);
-
fovy
: 垂直视角(以度为单位) -
aspect
: 宽高比 = width / height -
near
,far
: 深度裁剪距离(注意必须正数)
正交归一化
- 正交归一化是一种将指定的剪裁体积(clipping volume)转换为默认视图体积(default view volume)的技术。默认视图体积是一个边长为2的立方体,其坐标范围在x、y和z方向上均为从-1到1。这个过程主要包括三个步骤:平移、缩放和反射。
步骤1:平移原点到中心
平移变换的目的是将剪裁体积的中心移动到原点。剪裁体积的中心可以通过以下公式计算:
( right + left 2 , top + bottom 2 , − far + near 2 ) \left(\frac{\text { right }+ \text { left }}{2}, \frac{\text { top }+ \text { bottom }}{2},-\frac{\text { far }+ \text { near }}{2}\right) (2 right + left ,2 top + bottom ,−2 far + near )
平移变换矩阵如下:
[ 1 0 0 − right + left 0 1 0 − top + bottom 2 0 0 1 far + near 2 0 0 0 1 ] \left[\begin{array}{cccc}1 & 0 & 0 & -\frac{\text { right }+ \text { left }}{} \\ 0 & 1 & 0 & -\frac{\text { top }+ \text { bottom }}{2} \\ 0 & 0 & 1 & \frac{\text { far }+ \text { near }}{2} \\ 0 & 0 & 0 & 1\end{array}\right] 100001000010− right + left −2 top + bottom 2 far + near 1
步骤2:缩放块
缩放变换的目的是将剪裁体积在x、y和z方向上缩放,使其范围从-1到1。缩放因子分别为:
2 right − left , 2 top − bottom , 2 far − near \frac{2}{\text { right }- \text { left }}, \frac{2}{\text { top }- \text { bottom }}, \frac{2}{\text { far }- \text { near }} right − left 2, top − bottom 2, far − near 2
缩放变换矩阵如下:
[ 2 right-left 0 0 0 0 2 top-bottom 0 0 0 0 2 far-near 0 0 0 0 1 ] \left[\begin{array}{cccc}\frac{2}{\text { right-left }} & 0 & 0 & 0 \\ 0 & \frac{2}{\text { top-bottom }} & 0 & 0 \\ 0 & 0 & \frac{2}{\text { far-near }} & 0 \\ 0 & 0 & 0 & 1\end{array}\right] right-left 20000 top-bottom 20000 far-near 200001
步骤3:相对于x-y平面反射
反射变换的目的是解决深度值(z值)的反直觉问题。在归一化后,近平面(near plane)的z值会大于远平面(far plane)的z值,这与直觉相反。为了解决这个问题,可以对模型进行反射变换,使其相对于x-y平面进行镜像。
反射变换矩阵如下:
[ 1 0 0 0 0 1 0 0 0 0 − 1 0 0 0 0 1 ] \left[\begin{array}{cccc}1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & -1 & 0 \\ 0 & 0 & 0 & 1\end{array}\right] 1000010000−100001
步骤1和步骤2的累积变换
步骤1和步骤2的累积变换矩阵是通过将平移矩阵和缩放矩阵相乘得到的。累积变换矩阵如下:
[ 2 right-left 0 0 − right-left right-left 0 2 top-bottom 0 − top+bottom top-bottom 0 0 2 far-near far+near far-near 0 0 0 1 ] \left[\begin{array}{cccc}\frac{2}{\text { right-left }} & 0 & 0 & -\frac{\text { right-left }}{\text { right-left }} \\ 0 & \frac{2}{\text { top-bottom }} & 0 & -\frac{\text { top+bottom }}{\text { top-bottom }} \\ 0 & 0 & \frac{2}{\text { far-near }} & \frac{\text { far+near }}{\text { far-near }} \\ 0 & 0 & 0 & 1\end{array}\right] right-left 20000 top-bottom 20000 far-near 20− right-left right-left − top-bottom top+bottom far-near far+near 1
综合步骤1、2、3:
[ 1 0 0 0 0 1 0 0 0 0 − 1 0 0 0 0 1 ] [ 2 right-left 0 0 0 0 2 top-botom 0 0 0 0 − 2 far- near 0 0 0 0 1 ] [ 1 0 0 − right + left 2 0 1 0 − top + bottom 2 0 0 1 far + near 2 0 0 0 1 ] \left[\begin{array}{cccc}1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & -1 & 0 \\ 0 & 0 & 0 & 1\end{array}\right]\left[\begin{array}{cccc}\frac{2}{\text { right-left }} & 0 & 0 & 0 \\ 0 & \frac{2}{\text { top-botom }} & 0 & 0 \\ 0 & 0 & -\frac{2}{\text { far- near }} & 0 \\ 0 & 0 & 0 & 1\end{array}\right]\left[\begin{array}{cccc}1 & 0 & 0 & -\frac{\text { right }+ \text { left }}{2} \\ 0 & 1 & 0 & -\frac{\text { top }+ \text { bottom }}{2} \\ 0 & 0 & 1 & \frac{\text { far }+ \text { near }}{2} \\ 0 & 0 & 0 & 1\end{array}\right] 1000010000−100001 right-left 20000 top-botom 20000− far- near 200001 100001000010−2 right + left −2 top + bottom 2 far + near 1
The overall matrix for Steps 1,2 & 3
[ 2 right − left 0 0 − right + left right − left 0 2 top − bottom 0 − top + bottom top − bottom 0 0 − 2 far − near − far + near far − near 0 0 0 1 ] \left[\begin{array}{cccc} \frac{2}{\text { right }- \text { left }} & 0 & 0 & -\frac{\text { right }+ \text { left }}{\text { right }- \text { left }} \\ 0 & \frac{2}{\text { top }- \text { bottom }} & 0 & -\frac{\text { top }+ \text { bottom }}{\text { top }- \text { bottom }} \\ 0 & 0 & -\frac{2}{\text { far }- \text { near }} & -\frac{\text { far }+ \text { near }}{\text { far }- \text { near }} \\ 0 & 0 & 0 & 1 \end{array}\right] right − left 20000 top − bottom 20000− far − near 20− right − left right + left − top − bottom top + bottom − far − near far + near 1
-
深度缓冲区算法
深度缓冲区算法是一种用于解决三维图形渲染中物体遮挡关系(即哪个物体在前面,哪个物体在后面)的问题。它通过记录每个像素的深度值(即从视点到物体表面的距离)来确保屏幕上只显示最靠近视点的物体。
深度缓冲区算法概述
- 原理:对于每个像素,记录从视点到物体表面的距离(即深度值)。每次渲染一个新的物体时,如果该物体的深度值比当前像素已有的深度值更小(表示这个物体更靠近观察者),则更新该像素的颜色,否则保持原值。
- 关键操作:只有当新的物体表面距离视点更近时,才会更新像素的颜色。
组件
- 显示器:用来输出最终的图像。
- 处理器:负责执行渲染和深度测试计算。
- 帧缓冲区:存储图像的颜色信息(即每个像素的RGB值)。
- 深度缓冲区:存储每个像素的深度值,用来跟踪物体与视点之间的距离。
深度缓冲区算法工作流程
- 更新像素:当一个新的表面被绘制时,检查该表面对应像素的深度值。如果该深度值小于当前深度值(即新表面比当前表面距离视点更近),则更新像素的颜色,并将该像素的深度值更新为新表面的深度值。
- 示例:
- 青色面绘制:首次绘制青色面时,像素的颜色被设置为青色,并记录该面的深度值。
- 绿色面绘制:接着绘制绿色面,由于绿色面的深度值大于青色面,因此该像素颜色不发生变化。
- 红色三角形绘制:最后绘制红色三角形,它比前两个面更靠近视点,因此会更新该像素的颜色为红色,并将深度值更新为红色三角形的深度值。
深度缓冲区算法伪代码(Z-buffer)
伪代码描述了深度缓冲区算法的实现步骤:
For each pixel at (x, y):color(x, y) = background_color; // 初始化为背景色depth(x, y) = infinity; // 初始化深度为无穷大For each surface:For each pixel (x, y) covered by surface projection:z = depth of surface at pixel; // 计算当前像素的深度值if (z < depth(x, y)): // 如果新表面更近color(x, y) = surface_color; // 更新颜色depth(x, y) = z; // 更新深度值
- 初始化:每个像素的颜色初始化为背景色,深度初始化为无穷大(表示没有物体被绘制)。
- 遍历表面:对于每个物体表面,遍历所有覆盖该物体的像素。
- 深度比较:对于每个像素,如果该像素的深度值小于当前记录的深度值(即物体更靠近观察者),则更新该像素的颜色和深度值。
OpenGL中深度缓冲区的设置
在OpenGL中,设置深度缓冲区的步骤如下:
-
启用深度缓冲区:
glutInitDisplayMode(GLUT_DEPTH); // 初始化显示模式,启用深度缓冲区
-
启用深度测试:
glEnable(GL_DEPTH_TEST); // 开启深度测试,启用深度缓冲区算法
-
清空深度缓冲区:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清空颜色缓冲区和深度缓冲区
- 需要在每次渲染时清除颜色和深度缓冲区,以确保每帧的渲染是从干净的状态开始的。
-
背面剔除:
背面剔除是优化渲染效率的一种方法,去除视角看不到的表面。通过启用背面剔除,可以避免渲染被隐藏的面。glEnable(GL_CULL_FACE); // 启用剔除 glCullFace(GL_BACK); // 剔除背面
深度缓冲区的优点和应用
- 优点:
- 提高渲染效率:通过在每个像素点进行深度比较,减少了不必要的渲染计算,确保只有可见的物体表面被渲染。
- 解决遮挡问题:深度缓冲区有效解决了3D场景中的遮挡问题,使得物体在不同深度位置正确显示。
- 应用:
- 深度缓冲区广泛应用于三维游戏、模拟和虚拟现实等领域,用于处理复杂的3D场景和确保准确的物体渲染。
-
阴影的详细讲解
阴影是计算机图形学中增强场景真实性的重要技术,通常用于模拟光照与物体之间的关系。本文详细介绍了基于方向光源的阴影生成方法,重点在于如何通过数学推导和OpenGL代码实现阴影投影。
阴影生成的基本原理是将三维物体在光源投射下的投影计算出来。不同类型的光源生成的阴影有所不同,本篇重点讲解平行光源(如太阳光)生成的阴影。
平行光源是指光线平行的光源,通常用于模拟太阳光。计算阴影的过程涉及将三维顶点投影到一个平面上,从而确定阴影的位置。
-
阴影的计算公式:
光线方向是由光源到目标表面的方向决定的,我们假设光线方向向量为 ( d x , d y , d z ) (dx, dy, dz) (dx,dy,dz)。为了计算阴影,我们需要沿着光线方向将顶点 ( x , y , z ) (x, y, z) (x,y,z) 移动一段距离,直到它投影到平面上。
顶点 ( x , y , z ) (x, y, z) (x,y,z) 沿着光线方向移动后的新坐标 ( x ′ , y ′ , z ′ ) (x', y', z') (x′,y′,z′) 可以通过以下公式表示:
x ′ = x + α d x , y ′ = y + α d y , z ′ = z + α d z x' = x + \alpha dx, \quad y' = y + \alpha dy, \quad z' = z + \alpha dz x′=x+αdx,y′=y+αdy,z′=z+αdz
其中, α \alpha α 是一个参数,表示从顶点 ( x , y , z ) (x, y, z) (x,y,z) 沿着光线方向移动的距离(经过我的思考,这个表述并不准确, α \alpha α实际上相当于直线的参数方程的自变量,而dx,dy,dz相当于控制每个方向前进程度),直到与平面相交。平面方程约束
我们要求移动后的点 ( x ′ , y ′ , z ′ ) (x', y', z') (x′,y′,z′) 必须落在平面 a x + b y + c z + d = 0 ax + by + cz + d = 0 ax+by+cz+d=0 上。为了满足这个条件,我们将顶点的投影坐标 ( x ′ , y ′ , z ′ ) (x', y', z') (x′,y′,z′) 代入平面方程,并解出 α \alpha α:
a ( x ′ + α d x ) + b ( y ′ + α d y ) + c ( z ′ + α d z ) + d = 0 a(x' + \alpha dx) + b(y' + \alpha dy) + c(z' + \alpha dz) + d = 0 a(x′+αdx)+b(y′+αdy)+c(z′+αdz)+d=0
将 x ′ x' x′, y ′ y' y′, 和 z ′ z' z′ 的表达式代入上式,得到:
a ( x + α d x ) + b ( y + α d y ) + c ( z + α d z ) + d = 0 a(x + \alpha dx) + b(y + \alpha dy) + c(z + \alpha dz) + d = 0 a(x+αdx)+b(y+αdy)+c(z+αdz)+d=0
展开并整理,得到:
α ( a d x + b d y + c d z ) = − ( a x + b y + c z + d ) \alpha (a dx + b dy + c dz) = -(a x + b y + c z + d) α(adx+bdy+cdz)=−(ax+by+cz+d)
因此, α \alpha α 可以表示为:
α = − a x + b y + c z + d a d x + b d y + c d z \alpha = -\frac{a x + b y + c z + d}{a dx + b dy + c dz} α=−adx+bdy+cdzax+by+cz+d
这就是计算顶点投影到平面上的移动距离 α \alpha α 的公式。
通过将上述公式代入到平面方程中,可以得到投影矩阵。该矩阵用于计算顶点在投影平面上的坐标。
怎么来的?带入:
x ′ = x − a × x + b × y + c × z + d a × d x + b × d y + c × d x d x x ′ = ( b × d y + c × d x ) x − ( b × d x ) y − ( c × d x ) z − d × d x a × d x + b × d y + c × d x y ′ = … z ′ = … \begin{aligned} & x^{\prime}=x-\frac{a \times x+b \times y+c \times z+d}{a \times d x+b \times d y+c \times d x} d x \\ & x^{\prime}=\frac{(b \times d y+c \times d x) x-(b \times d x) y-(c \times d x) z-d \times d x}{a \times d x+b \times d y+c \times d x} \\ & y^{\prime}=\ldots \\ & z^{\prime}=\ldots \end{aligned} x′=x−a×dx+b×dy+c×dxa×x+b×y+c×z+ddxx′=a×dx+b×dy+c×dx(b×dy+c×dx)x−(b×dx)y−(c×dx)z−d×dxy′=…z′=…
以X为例,不难发现分子上的几项对应了x,y,z,和一个常数项,这些系数对应变换矩阵第一行,表示要对x进行点积。至于分母,由于所有分母都一样,正好齐次坐标最后要除以w,所以我们把分母放到w的位置,最后一除,w变为1表示点,x、y、z坐标也有了分母。投影矩阵如下:
$$
\begin{bmatrix}
x’ \
y’ \
z’ \
w’
\end{bmatrix}\begin{bmatrix}
b \cdot dy + c \cdot dz & -b \cdot dx & -c \cdot dx & -d \cdot dx \
-a \cdot dy & a \cdot dx + c \cdot dz & -c \cdot dy & -d \cdot dy \
-a \cdot dz & -b \cdot dz & a \cdot dx + b \cdot dy & -d \cdot dz \
0 & 0 & 0 & a \cdot dx + b \cdot dy + c \cdot dz
\end{bmatrix}
\begin{bmatrix}
x \
y \
z \
1
\end{bmatrix}
$$
这个矩阵包含了光源方向的参数 (dx, dy, dz) 和投影平面的方程 (a, b, c, d),用来将顶点 (x, y, z) 映射到阴影平面 (x’, y’, z’)。 -
-
点光源阴影生成
点光源阴影生成的过程与平行光源有所不同,因为点光源从一个具体的点发射光线,而平行光源则假设所有光线平行。在点光源的情况下,阴影的形成与光源的具体位置和场景中物体的相对位置密切相关。
以下是根据点光源的阴影生成过程的详细推导:
假设光源位于空间中的某个点 ( l x , l y , l z ) (lx, ly, lz) (lx,ly,lz),我们需要计算顶点 ( x , y , z ) (x, y, z) (x,y,z) 在某个平面上的阴影位置。与平行光源不同,点光源的光线是从光源到物体发射的,因此,阴影的生成是基于光线从光源到物体的射线方程来计算的。
对于顶点 ( x , y , z ) (x, y, z) (x,y,z) 和一个平面 a x + b y + c z + d = 0 ax + by + cz + d = 0 ax+by+cz+d=0,我们希望计算该顶点在光源射线方向上的投影,最终确定阴影点 ( x ′ , y ′ , z ′ ) (x', y', z') (x′,y′,z′) 的位置。
2.1 定义光源的方向
点光源的光线方向从光源点 ( l x , l y , l z ) (lx, ly, lz) (lx,ly,lz) 指向目标顶点 ( x , y , z ) (x, y, z) (x,y,z),因此光线方向可以用向量 ( d x , d y , d z ) (dx, dy, dz) (dx,dy,dz) 表示:
d x = x − l x , d y = y − l y , d z = z − l z dx = x - lx, \quad dy = y - ly, \quad dz = z - lz dx=x−lx,dy=y−ly,dz=z−lz
这表示从光源到顶点的方向。
2.2 顶点沿光线方向移动
阴影点 ( x ′ , y ′ , z ′ ) (x', y', z') (x′,y′,z′) 是顶点 ( x , y , z ) (x, y, z) (x,y,z) 沿着光线方向移动的结果。通过引入一个参数 α \alpha α,可以表示顶点沿光线方向移动后的新坐标为:
x ′ = α x , y ′ = α y , z ′ = α z x' = \alpha x, \quad y' = \alpha y, \quad z' = \alpha z x′=αx,y′=αy,z′=αz
其中, α \alpha α 是一个未知的参数,表示沿光线方向移动的距离(依旧不准确, α \alpha α并不严格代表这个距离,而是相当于直线参数方程的自变量。至于为什么可以这么表示,看ppt图就明白了,默认点光源位于原点)。
2.3 平面方程约束
阴影点 ( x ′ , y ′ , z ′ ) (x', y', z') (x′,y′,z′) 必须位于平面上,因此我们将投影后的点代入平面方程 a x + b y + c z + d = 0 ax + by + cz + d = 0 ax+by+cz+d=0:
a ( α x ) + b ( α y ) + c ( α z ) + d = 0 a(\alpha x) + b(\alpha y) + c(\alpha z) + d = 0 a(αx)+b(αy)+c(αz)+d=0
展开后得到:
α ( a x + b y + c z ) + d = 0 \alpha (a x + b y + c z) + d = 0 α(ax+by+cz)+d=0
从中解出 α \alpha α:
α = − d a x + b y + c z \alpha = -\frac{d}{a x + b y + c z} α=−ax+by+czd
这表示阴影点沿光线方向的移动距离。
2.4 计算阴影坐标
一旦得到了 α \alpha α,就可以计算阴影点 ( x ′ , y ′ , z ′ ) (x', y', z') (x′,y′,z′) 的坐标:
x ′ = − d x a x + b y + c z , y ′ = − d y a x + b y + c z , z ′ = − d z a x + b y + c z x' = -\frac{d x}{a x + b y + c z}, \quad y' = -\frac{d y}{a x + b y + c z}, \quad z' = -\frac{d z}{a x + b y + c z} x′=−ax+by+czdx,y′=−ax+by+czdy,z′=−ax+by+czdz
这个公式计算了点光源下顶点的阴影位置。
为了在图形学中高效处理阴影,尤其是使用 OpenGL 渲染阴影,可以将上述过程转化为矩阵形式。
首先,将阴影计算转化为齐次坐标,可以将投影过程用 4x4 矩阵来表示。投影矩阵通过平面方程和光源的方向来生成。
3.1 投影矩阵
根据之前的公式,阴影投影可以用以下投影矩阵来表示:
$$
\begin{bmatrix}
x’ \
y’ \
z’ \
w’
\end{bmatrix}
\begin{bmatrix}
\frac{a dx + b dy + c dz}{a x + b y + c z} & -\frac{a dx}{a x + b y + c z} & -\frac{b dx}{a x + b y + c z} & -\frac{d dx}{a x + b y + c z} \
\frac{a dy + b dz + c dz}{a x + b y + c z} & a dx + c dz & -c dy & -d dy \
\frac{a dz + b dy + c dz}{a x + b y + c z} & -c dz & b dy + c dy & -d dz \
0 & 0 & 0 & a dx + b dy + c dz \
\end{bmatrix}
$$
这表示投影矩阵的元素基于平面参数 ( a , b , c , d ) (a, b, c, d) (a,b,c,d) 和光源的方向 ( d x , d y , d z ) (dx, dy, dz) (dx,dy,dz)。
相关文章:
山东大学计算机图形学期末复习6——CG10下
##CG10下 将世界坐标中的任意点 P P P 变换到以相机为中心的“观察坐标系”下(右手坐标系) n \mathbf{n} n:从相机眼睛朝向观察点的反方向,代表“前方”; u \mathbf{u} u:观察坐标系的 x 轴,向…...
【Spring Cloud Gateway】Nacos整合遇坑记:503 Service Unavailable
一、场景重现 最近在公司进行微服务架构升级,将原有的 Spring Cloud Hoxton 版本升级到最新的 2021.x 版本,同时使用 Nacos 作为服务注册中心和配置中心。在完成基础框架搭建后,我使用 Spring Cloud Gateway 作为API 网关,通过 N…...
[Linux]从零开始的STM32MP157 Busybox根文件系统测试及打包
一、前言 在上一篇教程中,我们成功编译了Busybox根文件系统并且能够正常使用,但是大家应该也发现了我们构建的根文件系统存在许多问题,比如一些找不到文件的报错。并且在实际的产品中一般都是将根文件系统烧录到EMMC中,并不是像我…...
【Pandas】pandas DataFrame eval
Pandas2.2 DataFrame Computations descriptive stats 方法描述DataFrame.abs()用于返回 DataFrame 中每个元素的绝对值DataFrame.all([axis, bool_only, skipna])用于判断 DataFrame 中是否所有元素在指定轴上都为 TrueDataFrame.any(*[, axis, bool_only, skipna])用于判断…...
考研408《计算机组成原理》复习笔记,第二章(2)数值数据的表示和运算(浮点数篇)
一、回顾定点数知识点 ——定点小数机器码表示 ——定点整数机器码表示 ——【原码】和【移码】的作用 二、浮点数表示 1、概念引入 我们生活中有很多 “带小数”,也就是浮点数,也就是【整数部分】和【纯小数部分】都不为0,那么这样的小数…...
【虚幻引擎】UE5独立游戏开发全流程(商业级架构)
本套课程我将会讲解一下知识 1.虚幻引擎的常用功能节点、模块包含但不限于动画模块、UI模块、AI模块、碰撞模块、伤害模块、背包模块、准心模块、武器模块、可拾取物品模块、死亡等模块。 2.整个游戏的设计思路(游戏架构),本套教程讲解了如…...
大语言模型 08 - 从0开始训练GPT 0.25B参数量 - MiniMind 单机多卡 torchrun deepspeed
写在前面 GPT(Generative Pre-trained Transformer)是目前最广泛应用的大语言模型架构之一,其强大的自然语言理解与生成能力背后,是一个庞大而精细的训练流程。本文将从宏观到微观,系统讲解GPT的训练过程,…...
使用gitbook 工具编写接口文档或博客
步骤一:在项目目录中初始化一个 GitBook 项目 mkdir mybook && cd mybook git init npm init -y步骤二:添加书籍结构(如 book.json, README.md) echo "# 我的书" > README.md echo "{}" > bo…...
Mysql视图详解
文章目录 1、视图简介 && 前置准备2、基本crud语法3、检查选项(with check option)CASCADEDLOCAL总结 4、视图更新限定条件 1、视图简介 && 前置准备 视图 (View) 是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,…...
leetcode 56. 合并区间
题目描述 代码: class Solution {struct Interval{int left;int right;Interval(int l0,int r0):left(l),right(r){}bool operator<(const Interval& rhs) const{return left<rhs.left;}};public:vector<vector<int>> merge(vector<vecto…...
Mac 环境下 JDK 版本切换全指南
概要 在 macOS 上安装了多个 JDK 后,可以通过系统自带的 /usr/libexec/java_home 工具来查询并切换不同版本的 Java。只需在终端中执行 /usr/libexec/java_home -V 列出所有已安装的 JDK,然后将你想使用的版本路径赋值给环境变量 JAVA_HOME,…...
【生活相关-日语-日本-东京-搬家后-引越(ひっこし)(3)-踩坑点:国民健康保险】
【生活相关-日语-日本-东京-搬家后-引越(ひっこし)(3)-注意点:国民健康保险】 1、前言2、情况说明(1)问题说明(2)情况说明(1)收到情况(…...
C++ asio网络编程(6)利用C11模拟伪闭包实现连接的安全回收
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、智能指针管理Session二、用智能指针来实现Server的函数1.start_accept()1.引用计数注意点2.std::bind 与异步回调函数的执行顺序分析 2.handle_accept1.异步…...
【视频】解决FFmpeg将RTSP转RTMP流时,出现的卡死、出错等问题
1、简述 如果不修改图像内容,可以使用FFmpeg命令来将RTSP转RTMP流。 SRS视频服务器就是这么干的,它没有使用FFmpeg接口,而是直接使用FFmpeg命令来转流。 但是在使用中,约到了一些问题,比如转流时卡死、转流出错等等,下面描述怎么解决这些问题 2、出错重启 在shell脚本…...
飞牛NAS本地部署开源TTS文本转语音工具EasyVoice与远程使用流程
文章目录 前言1. 环境准备2. Docker部署与运行3. 简单使用测试4. 安装内网穿透4.1 开启ssh连接安装cpolar4.2 创建公网地址 5. 配置固定公网地址总结 前言 本文主要介绍如何在fnOS飞牛云NAS使用Docker本地部署一款非常好用的开源TTS文本转语音工具EasyVoice,并结合…...
STC51系列单片机引脚分类与功能速查表(以STC89C52为例)
1. 基本I/O端口 端口引脚范围类型主要功能特殊说明P0P0.0~P0.7开漏双向I/O1. 通用I/O(需外接上拉电阻) 2. 数据总线(D0-D7) 3. 低8位地址总线(A0-A…...
recvfrom和sendto函数中地址参数的作用
在 UDP 通信中,recvfrom 和 sendto 函数中的地址参数起着至关重要的作用。 以下是对这两个函数中地址参数的作用、所属方以及缺失地址时的后果的详细解释。 recvfrom 函数 int recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_add…...
运维职业发展思维导图
主要内容如下: 一、 初级入行阶段 这是职业生涯的起点,主要涉及基础技能的学习和实践。 Linux初学: 重点是学习Linux系统的基础命令和操作。IDC机房运维: 负责数据中心机房内设备的管理和日常维护工作。Helpdesk桌面运维: 提供桌面技术支持,帮助用户解决遇到的计算机软硬…...
【数据处理】Python对CMIP6数据进行插值——详细解析实现(附源码)
目录 Python对CMIP6数据进行插值一、引言代码概览思维导图 二、数据预处理三、数据区域裁剪四、插值(一) 垂直插值(二) 水平插值 五、保存插值好的文件六、文件合并与气候态计算七、代码优化技巧八、多线程处理九、全部代码 Pytho…...
worldquant rank函数
https://support.worldquantbrain.com/hc/en-us/community/posts/13869304934935-%E6%80%8E%E6%A0%B7%E7%90%86%E8%A7%A3rank%E5%87%BD%E6%95%B0 链接。进的话可以填我的邀请码JS34795我可以带你 现在学习rank函数 我们所说的做多和做空 首先,当我们讨论Long和S…...
工业4.0神经嫁接术:ethernet ip转profinet协议通信步骤图解
在现代工业自动化领域,不同品牌的设备和协议之间的兼容性问题一直是个挑战。我们的包装线项目就遇到了这样的难题:需要将Rockwell Allen-Bradley的EtherNet/IP伺服系统与西门子PLC的PROFINET主站进行无缝对接。为了解决这一问题,我们采用了et…...
数据库——数据操作语言DML
(2)数据操作语言DML 简称DML——Data Manipulation Language用来对数据库中表的记录进行更新关键字:insert,delete,update A、 插入表记录 向表中插入数据 格式:insert into 表名(字段1,字段2,字段3……) values(值1,值2,值3);…...
文件防泄密的措施有哪些?
文件防泄密措施需要从技术、管理和物理三个层面综合施策,以下为常见措施分类整理: 一、技术防护措施 华途加密技术 文件加密:使用AES、RSA等算法对敏感文件加密。 传输加密:通过SSL/TLS、VPN保障传输安全,禁止明文传…...
C++ Mac 打包运行方案(cmake)
文章目录 背景动态库梳理打包方案静态库处理动态库处理(PCL库)编译链接动态库后处理逻辑 批量信任 背景 使用C编写的一个小项目,需要打包成mac下的可执行文件(免安装版本),方便分发给其他mac执行,需要把项目的动态库都…...
数学复习笔记 10
前言 我觉得数学的高分乃至满分属于那些,聪明,坚韧,勇敢,细致的人。我非常惭愧自己不是这样的人,我在生活中发现了这样的同学,和他们交流的时候我常常感到汗流浃背,因为他们非常扎实的基础知识…...
Oracle-相关笔记
Oracle Database Online Documentation 11g 连接 WinR sqlplus username/passwordhostname:port/service_namesqlplus user02/123456192.xxx:1521/orclsqlplus / as sysdba #SQL*Plus 終端编码使用UTF-8 chcp 65001#打开SQL*Plus程序 sqlplus /nolog#使用dba角色登录(用 1.…...
mac安装cast
背景 pycharm本地运行脚本时提示cast没有安装 问题原因 脚本尝试调用cast命令(以太坊开发工具foundry中的子命令),但您的系统未安装该工具。 从日志可见,错误发生在通过sysutil.py执行shell命令时。 解决方案 方法1…...
CodeEdit:macOS上一款可以让Xcode退休的IDE
CodeEdit 是一款轻量级、原生构建的代码编辑器,完全免费且开源。它使用纯 swift 实现,而且专为 macOS 设计,旨在为开发者提供更高效、更可靠的编程环境,同时释放 Mac 的全部潜力。 Stars 数21,719Forks 数1,081 主要特点 macOS 原…...
opencv4.11编译Debug提示缺少python312_d.lib或python3*_d.lib的解决办法
前言 当我们编译OpenCV 4.11的时候可能会遇到提示缺少库文件,这个时候我们可以下载Python源码编译这个lib。 也可以下载我上传的版本(python312_d.lib),但是如果是其他版本需要自己编译。编译步骤如下,大概几分钟搞定…...
html的鼠标点击事件有哪些写法
在HTML中,鼠标点击事件的实现方式多样,以下从基础语法到现代实践为您详细梳理: 一、基础写法:直接内联事件属性 在HTML标签内通过on前缀事件属性绑定处理函数,适合简单交互场景: <!-- 单击事件 -->…...
深度解析物理机服务器故障修复时间:影响因素与优化策略
一、物理机故障修复的核心影响因素 物理机作为企业 IT 基础设施的核心载体,其故障修复效率直接关系到业务连续性。故障修复时间(MTTR)受多重因素交叉影响: 1. 故障类型的复杂性 硬件级故障: 简单故障:内存…...
蓝桥杯 2024 C++国 B最小字符串
P10910 [蓝桥杯 2024 国 B] 最小字符串 题目描述 给定一个长度为 N N N 且只包含小写字母的字符串 S S S,和 M M M 个小写字母 c 1 , c 2 , ⋯ , c M c_1, c_2, \cdots, c_M c1,c2,⋯,cM。现在你要把 M M M 个小写字母全部插入到字符串 S S S 中&…...
解密企业级大模型智能体Agentic AI 关键技术:MCP、A2A、Reasoning LLMs-docker MCP解析
解密企业级大模型智能体Agentic AI 关键技术:MCP、A2A、Reasoning LLMs-docker MCP解析 这里面有很重要的原因其中一个很其中一个原因是因为如果你使用docker的方式,你可以在虚拟环境下就类似于这个沙箱的这个机制可以进行隔离。这对于安全,…...
访问 Docker 官方镜像源(包括代理)全部被“重置连接”或超时
华为云轻量应用服务器(Ubuntu 系统) 遇到的问题是: 🔒 访问 Docker 官方镜像源(包括代理)全部被“重置连接”或超时了,说明你这台服务器的出境网络对这些国外域名限制很严格,常见于华…...
前馈神经网络回归(ANN Regression)从原理到实战
前馈神经网络回归(ANN Regression)从原理到实战 一、回归问题与前馈神经网络的适配性分析 在机器学习领域,回归任务旨在建立输入特征与连续型输出变量之间的映射关系。前馈神经网络(Feedforward Neural Network)作为最基础的神经网络架构&a…...
RNN/LSTM原理与 PyTorch 时间序列预测实战
🕰️ RNN / LSTM 原理与 PyTorch 时间序列预测实战 在处理时间序列数据、语音信号、文本序列等连续性强的问题时,循环神经网络(RNN)及其改进版本 LSTM(长短期记忆网络)是最常见也最有效的模型之一。本文将深入讲解 RNN 和 LSTM 的核心原理,并通过 PyTorch 实现一个时间…...
Docker容器镜像与容器常用操作指南
一、镜像基础操作 搜索镜像 docker search <镜像名>在Docker Hub中查找公开镜像,例如: docker search nginx拉取镜像 docker pull <镜像名>:<标签>从仓库拉取镜像到本地,标签默认为latest: docker pull nginx:a…...
1:OpenCV—图像基础
OpenCV教程 头文件 您只需要在程序中包含 opencv2/opencv.hpp 头文件。该头文件将包含应用程序的所有其他必需头文件。因此,您不再需要费心考虑程序应包含哪些头文件。 例如 - #include <opencv2/opencv.hpp>命名空间 所有 OpenCV 类和函数都在 cv 命名空…...
测试--BUG(软件测试⽣命周期 bug的⽣命周期 与开发产⽣争执怎么办)
1. 软件测试的⽣命周期 软件测试贯穿于软件的整个⽣命周期,针对这句话我们⼀起来看⼀下软件测试是如何贯穿软件的整个⽣命周期。 软件测试的⽣命周期是指测试流程,这个流程是按照⼀定顺序执⾏的⼀系列特定的步骤,去保证产品质量符合需求。在软…...
基于大模型预测围术期麻醉苏醒时间的技术方案
目录 一、数据收集与处理(一)数据来源(二)数据预处理二、大模型构建与训练(一)模型选择(二)模型训练三、围术期麻醉苏醒时间预测(一)术前预测(二)术中动态预测四、并发症风险预测(一)风险因素分析(二)风险预测模型五、基于预测制定手术方案(一)个性化手术规划…...
QT6 源(101)阅读与注释 QPlainTextEdit,其继承于QAbstractScrollArea,属性学习与测试
(1) (2) (3)属性学习与测试 : (4) (5) 谢谢...
电池组PACK自动化生产线:多领域电池生产的“智能引擎”
在电池产业蓬勃发展的当下,电池组PACK自动化生产线凭借其高效、精准、智能的优势,成为众多电池生产领域的核心装备。它广泛适用于数码电池、工具电池、储能电池、电动车电池以及动力电池的生产,有力推动了相关产业的升级与发展。 数码电池领…...
生成式AI在编程中的应用场景:从代码生成到安全检测
引言 生成式AI正在深刻改变软件开发的方式,从代码编写到测试、文档和维护,AI技术正在为每个环节带来革命性的变革。本文将深入探讨生成式AI在编程中的主要应用场景,分析其优势与局限性,并展望未来发展趋势。 主要应用场景 1. 代…...
安全牛报告解读《低空经济发展白皮书(3.0)安全体系》
一、概述 《低空经济发展白皮书(3.0)安全体系》由粤港澳大湾区数字经济研究院(IDEA研究院)发布,旨在构建低空经济安全发展的系统性框架,解决规模化低空飞行中的安全挑战。核心目标是明确安全体系需覆盖的飞…...
“2W2H”分析方法
“2W2H”是一种常用的分析方法,它通过回答**What(是什么)、Why(为什么)、How(怎么做)、How much(多少)**这四个问题来全面了解和分析一个事物或问题。这种方法可以帮助你…...
【数据挖掘笔记】兴趣度度量Interest of an association rule
在数据挖掘中,关联规则挖掘是一个重要的任务。兴趣度度量是评估关联规则的重要指标,以下是三个常用的兴趣度度量:支持度、置信度和提升度。 支持度(Support) 计算方法 支持度表示包含项集的事务占总事务的比例&…...
ArcGIS Pro调用多期历史影像
一、访问World Imagery Wayback,基本在我国范围 如下图: 二、 放大到您感兴趣的区域 三、 查看影像版本信息 点击第二步的按钮后,便可跳转至World Imagery (Wayback 2025-04-24)的相关信息。 四 、点击上图影像版本信息,页面跳转…...
Web3.0:互联网的去中心化未来
随着互联网技术的不断发展,我们正站在一个新时代的门槛上——Web3.0时代。Web3.0不仅仅是一个技术升级,它更是一种全新的互联网理念,旨在通过去中心化技术重塑网络世界。本文将深入探讨Web3.0的核心概念、技术基础、应用场景以及它对未来的深…...
java17
1.常见API之BigDecimal 底层存储方式: 2.如何分辨过时代码: 有横线的代码表示该代码已过时 3.正则表达式之字符串匹配 注意:如果X不是单一字符,需要加[]中括号 注意:1.想要表达正则表达式里面的.需要\\. 2.想要表…...
游戏引擎学习第283天:“让‘Standing-on’成为一个更严谨的概念
如果同时使用多个OpenGL上下文,并且它们都有工作负载,GPU或GPU驱动程序如何决定调度这些工作?我注意到Windows似乎优先处理活动窗口的OpenGL上下文(即活动窗口表现更好),挺有意思的…… 当多个OpenGL上下文…...