屏幕空间变换在透视除法裁剪的DNS空间后进行和glViewport

总结出两点: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) + winCenterX
yScreen = (-yClip * winResY) / (2 ) + winCenterY
右手坐标系应该是:

    
    
    
    
xScreen = (xClip * winResX) / (2) + winCenterX
yScreen = (yClip * winResY) / (2 ) + winCenterY


xScreen = (xClip * winResX) / (2 * wClip) + winCenterX
yScreen = (yClip * winResY) / (2 * wClip) + winCenterY
这里除以wClip其实是将透视投影4D空间中的值转换为3D空间,例如:

左手2D变换中:
得到的变换矩阵为:
2/w  0    0           0
0    2/h  0           0
0    0    1/(zf-zn)   0
0    0    zn/(zn-zf)  1
wClip为0是不用除的。
左手坐标系中winPosX,winPosY是窗口左上角的位置。
winCenterX, winCenterY是屏幕中间位置的像素值。
winResX, winResY其实是窗口的w,h。
devResX, devResY是设备屏幕区域的宽高,全屏模式下等于winResX, winResY,否则不等。
如果视图空间中的x,y都是0那么就在屏幕的中间。
如果x = winResX / 2, y = winResY / 2,那么 xScreen = winResX / 2 + winCenterX 等于w, y也等于h就在屏幕的右下角。

如果是左手透视投影中:
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值也类似。
所以透视投影中:

aspectaspect = 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) + winCenterX
yScreen = ( yClip * winResY) / (2 * wClip) + winCenterY
即aspect变小时候,倒数变大,x屏幕值会被放大,指定的视图坐标放到了大的DNS坐标系中,再铺满整个窗口,故屏幕中被放大了。
aspect变大时候,倒数变小,x屏幕值会被放大, 指定的视图坐标放到了小的DNS坐标系中,再铺满整个窗口,故屏幕中被缩小了。
透视投影中将屏幕坐标中的位置转换到世界坐标系中,一定要先从世界坐标系中根据已知的z值变换到屏幕坐标系的值赋值给屏幕坐标,从而求得其值。
D3D中:
// 3DVIEWPORT9设置屏幕坐标系大小定义,多视图,可以实现屏幕分割
    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位置
OGL中:
   
   
   
   

glViewport — set the viewport

C Specification

void glViewport( GLint x,

GLint y,

GLsizei width,

GLsizei height);
 

Parameters

xy

Specify the lower left corner of the viewport rectangle, in pixels. The initial value is (0,0).

widthheight

Specify the width and height of the viewport. When a GL context is first attached to a window, width and height 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 + x
y w = y nd + 1 ⁢ height 2 + y

Viewport 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 either width or height is negative.



你可能感兴趣的:(屏幕空间变换在透视除法裁剪的DNS空间后进行和glViewport)