总结出两点:1.屏幕坐标变换数据来源是在透视投影后,硬件进行透视除法裁剪后的DNS设备坐标系中的数据。
2.透视投影是将视图(摄像机)坐标系,转换到4D空间中(2D正交投影不用考虑透视除法), 并且注意zn,zf正负值,aspect是用倒数参数xScale缩放的,aspect越小xScale越大,放到屏幕中也越大。屏幕转换那一步会将DNS坐标中的值等比的缩放充满整个屏幕。屏幕坐标逆向转换到世界坐标中,需要提供正确的z值,2D除外。
3.glViewport中宽高比是glPerspective中的aspect,和glOrtho中的宽高比也要一致,否则会发生变形。设置摄像机投影矩阵时候也要考虑到viewPort这一层的变换。
4.可以用glViewport创建多个视口匹配多个摄像机(多视角游戏),窗口系统中一般用DrawRect绘制一个视口即可(视图矩阵)。
// 3DVIEWPORT9设置屏幕坐标系大小定义,多视图,可以实现屏幕分割
D3DVIEWPORT9 viewport = {0, 0, width, height, 0.0f, 1.0f} ; (*device)->SetViewport(&viewport); // 设备规范坐标系到屏幕坐标系D3D会自动进行变换,3D透视投影中通过:
D3D中的变换矩阵:
W/2, 0, 0, 0
0, -H/2, 0,0
0, 0, MaxZ-MinZ, 0
X0+W/2, Y0+H/2, MinZ,1
从中可以看出左手坐标系屏幕变换是:
xScreen = (xClip * winResX) / (2) + winCenterXyScreen = (-yClip * winResY) / (2 ) + winCenterY右手坐标系应该是:
xScreen = (xClip * winResX) / (2) + winCenterXyScreen = (yClip * winResY) / (2 ) + winCenterY
xScale 0 0 0 0 yScale 0 0 0 0 zf/(zf-zn) 1 0 0 -zn*zf/(zf-zn) 0 where: yScale = cot(fovY/2) xScale = yScale / aspect ratio
假如:fovY等于90度,cot(45) = 1
aspect = w / h。
w = winResX, h = winResY
矩阵变为:
h/w 0 0 0 0 1 0 0 0 0 zf/(zf-zn) 1 0 0 -zn*zf/(zf-zn) 0
x, y, z中z等于1,x = 0, y = 0那么在屏幕中间。
如果x = winResX / 2, y = winResY / 2,那么xClip = (winResX / 2) *(winResY / winResX) = winResY / 2
yClip= winResY / 2。
screenX = winResX * winResY / 4 + winCenterX
screenY = winResY * winResY / 4 + winCenterY.
需要从将DNS x值Xclip / Wclip 属于[-1,1]角度来考虑,DNS y值也一样。
这样当DNS x为-1时候,screenX = 0, x值为1时候 screenX = winResX, y值也类似。
所以透视投影中:
因为:aspect:aspect = w / h,fovy = 90度,由投影矩阵的计算过程,投影yScale = cot(fovy/2); xScale = yScale /aspect.
当屏幕w:h = 100:20时,当aspect = 5:1,那么视锥内的[5,1]正方形截面世界将被变换映射到(1,1)的平面内,
xScale压缩了1/5,当透视投影映射到屏幕坐标时候[5,1],xScale方向需要根据屏幕放大5倍,这样视锥体里面的世界等比缩放到屏幕。
当aspect = 1变小;那么yScale = 1, xScale = 1,视锥体内的xy正方形截面[1,1]世界被放置到(1,1); 映射到屏幕为[5,1]xScale映射到屏幕被放大了5倍,yScale不变。
当aspect = 10变大,那么yScale = 1,xScale = 1/10,视锥体内的xy正方形截面[10,1]世界->(1,1)->[5,1],xScale被缩小了2倍,yScale不变。
xScreen = ( xClip * winResX) / (2 * wClip) + winCenterXyScreen = ( yClip * winResY) / (2 * wClip) + winCenterY
即aspect变小时候,倒数变大,x屏幕值会被放大,指定的视图坐标放到了大的DNS坐标系中,再铺满整个窗口,故屏幕中被放大了。
aspect变大时候,倒数变小,x屏幕值会被放大, 指定的视图坐标放到了小的DNS坐标系中,再铺满整个窗口,故屏幕中被缩小了。
透视投影中将屏幕坐标中的位置转换到世界坐标系中,一定要先从世界坐标系中根据已知的z值变换到屏幕坐标系的值赋值给屏幕坐标,从而求得其值。
D3D中:
// 3DVIEWPORT9设置屏幕坐标系大小定义,多视图,可以实现屏幕分割OGL中:
D3DVIEWPORT9 viewport = {0, 0, width, height, 0.0f, 1.0f} ; (*device)->SetViewport(&viewport); // 设备规范坐标系到屏幕坐标系D3D会自动进行变换,3D透视投影中通过: /* W/2, 0, 0, 0 0, -H/2, 0,0 0, 0, MaxZ-MinZ, 0 X0+W/2, Y0+H/2, MinZ,1 上述的参数含义就是D3DVIEWPORT9中定义的参数含义。 3D透视投影矩阵的Z轴是对1/z进行插值,因为要在1/z后插值到[0,1]中,所以得到的常数是矩阵中的z位置,1/z是矩阵中的平移位置。 2D正交投影矩阵只要对z插值在[0,1]中就可以了,常数矩阵中平移位置,z插值是矩阵中的z位置
glViewport — set the viewport
C Specification
void glViewport(
GLint x,
GLint y,
GLsizei width,
GLsizei height )
;Parameters
x
,y
Specify the lower left corner of the viewport rectangle, in pixels. The initial value is (0,0).
width
,height
Specify the width and height of the viewport. When a GL context is first attached to a window,
width
andheight
are set to the dimensions of that window.Description
glViewport
specifies the affine transformation of x and y from normalized device coordinates to window coordinates. Let x nd y nd be normalized device coordinates. Then the window coordinates x w y w are computed as follows:x w = x nd + 1 width 2 + xy w = y nd + 1 height 2 + yViewport width and height are silently clamped to a range that depends on the implementation. To query this range, call glGet with argument
GL_MAX_VIEWPORT_DIMS
.Errors
GL_INVALID_VALUE
is generated if eitherwidth
orheight
is negative.