转发,请保持地址:http://blog.csdn.net/stalendp/article/details/50775450
本文介绍OpenGL中的投影,同时讲解一下ZBuffer。先看一下古人是怎么进行透视作画的:
Perspective drawing in the Renaissance: “Man drawing a lute” by Albrecht Dürer, 1525
这么严谨!肯定是德国人了 ;-)
除了Graphic Pipeline外,ZBuffer是我最想介绍的部分了,因为有一些很酷实现都依赖于此(比如Shadow、Field Of View、SSOA)。为了具有说服力,我们以公式推导的形式给出结论。公式推导利用了中学的知识“相似三角形法则”,虽然有些繁琐但并不复杂。另外,文章的组织流程大致是沿着Graphic Pipeline的顺序进行的,其中剖析了一个正方形面片的显示过程。这个过程大致为:点->三角形->光栅化->透视矩阵->属性插值。关于ZBuffer的理解,可以带着如下几个问题进行探索:ZBuffer是什么环节下的产物,有什么特性,可以用在哪些地方。
3D模型在经过Model Transform和View Transform之后(以后介绍),会进入到本文介绍的Projection(投影);投影把View(可视区域,也叫“截头椎体”)转化为一个边长为2的立方体(中心点在立方体的中央,xyz轴的范围都为[-1,1]),这个立方体叫做Normalized device coordinates,简称为NDC。以透视投影为例:
从空间到平面
图6
$$x=-\frac{n}{P_z}P_x \text{ and } y=-\frac{n}{P_z}P_y$$ | (1) |
$$x'=(x-l)\frac{2}{r-l}-1$$ | (2) |
$$y'=(y-b)\frac{2}{t-b}-1$$ | (3) |
$$x'=\frac{2n}{r-l}\left(-\frac{P_x}{P_z}\right)-\frac{r+l}{r-l}$$ | (4) |
$$y'=\frac{2n}{t-b}\left(-\frac{P_y}{P_z}\right)-\frac{t+b}{t-b}$$ | (5) |
光栅化(Rasterization)
透视矩阵
图10
$$z'=\frac{A}{z}+B$$ | (6) |
$$-1=\frac{A}{-n}+b\text{ and }1=\frac{A}{-f}+B.$$ | (7) |
$$A=\frac{2nf}{f-n}\text{ and } B=\frac{f+n}{f-n}.$$ | (8) |
$$z'=-\frac{2nf}{f-n}\left(-\frac{1}{P_z}\right)+\frac{f+n}{f-n}.$$ | (9) |
$$ \begin{equation} \begin{cases} -x'P_z&=&\frac{2n}{r-l}&P_x+&\frac{r+l}{r-l}&P_z,\\ -y'P_z&=&\frac{2n}{t-b}&P_y+&\frac{t+b}{t-b}&P_z,\\ -z'P_z&=-&\frac{f+n}{f-n}&P_z-&\frac{2nf}{f-n}\\ \end{cases} \end{equation} $$ |
(10) |
$$P'=\langle-x'P_z, -y'P_z, -z'P_z, -P_z\rangle$$ | (11) |
注意:这里的z‘是NDC中的值,范围是[-1,1]。在之后的操作中,OpenGL把z’的范围变为[0,1]后,存到ZBuffer中,参考下文“Z-Buffer的特性”
(12) |
图11
图12
透视下的插值(Perspective-Correct Interpolation)
OpenGL显示3D模型是以三角形为单位来处理的。我们在屏幕上看到的模型,其实是三角形内部的所对应的像素包含的属性(包括颜色、uv、法线等,参考Built-in Vertex Input Parameters),结合外部光照等其它因素综合计算得到。在显示空间三角形时,首先要更具三个点计算其在屏幕上的投影,然后对三角形内部进行填充,根据算法得到每个像素点的颜色。填充过程的第一步是一个光栅化,具体到某一像素点的颜色,其实就是根据当前像素点的属性,通过相应的算法得到颜色。这种取色的途径可以直接在贴图上取色,或则还要通过某种算法得到(比如光照算法)。取色算法是要依赖于当前像素点的信息的,比如uv,法线等。这些值也是通过插值得到的。
下面将展示插值的计算方法并解释属性差值的方法。
$$ax+bz=c,$$ | (13) |
图13
$$\frac{P}{x}=\frac{-e}{z}$$ | (14) |
$$\left(-\frac{ap}{e}+b\right)z=c$$ | (15) |
$$\frac{1}{z}=-\frac{ap}{ce}+\frac{b}{c}.$$ | (16) |
(17) |
$$z_3=\frac{1}{\frac{1}{z_1}(1-t)+\frac{1}{z_2}t}$$ | (18) |
$$\frac{b_3-b_1}{b_2-b_1}=\frac{z_3-z_1}{z_2-z_1}$$ | (19) |
$$b_3=\frac{b_1z_2(1-t)+b_2z_1t}{z_2(1-t)+z_1t}$$ | (20) |
$$b_3=\frac{\frac{b_1}{z_1}(1-t)+\frac{b_2}{z_2}t}{\frac{1}{z_1}(1-t)+\frac{1}{z_2}t}=z_3\left[\frac{b_1}{z_1}(1-t)+\frac{b_2}{z_2}t\right]$$ | (21) |
NDC中Z的特性
Z-Buffer的应用
辅助函数
inline float4 ComputeScreenPos (float4 pos) {
float4 o = pos * 0.5f;
#if defined(UNITY_HALF_TEXEL_OFFSET)
o.xy = float2(o.x, o.y*_ProjectionParams.x) + o.w * _ScreenParams.zw;
#else
o.xy = float2(o.x, o.y*_ProjectionParams.x) + o.w;
#endif
o.zw = pos.zw;
return o;
}
// Z buffer to linear 0..1 depth (0 at eye, 1 at far plane)
inline float Linear01Depth( float z )
{
return 1.0 / (_ZBufferParams.x * z + _ZBufferParams.y);
}
// Z buffer to linear depth
inline float LinearEyeDepth( float z )
{
return 1.0 / (_ZBufferParams.z * z + _ZBufferParams.w);
}
// Depth pass vertex shader
output.vPositionCS = mul(input.vPositionOS, g_matWorldViewProj);
output.vDepthCS.xy = output.vPositionCS.zw;
// Depth pass pixel shader (output z/w)
return input.vDepthCS.x / input.vDepthVS.y;
// Function for converting depth to view-space position
// in deferred pixel shader pass. vTexCoord is a texture
// coordinate for a full-screen quad, such that x=0 is the
// left of the screen, and y=0 is the top of the screen.
float3 VSPositionFromDepth(float2 vTexCoord)
{
// Get the depth value for this pixel
float z = tex2D(DepthSampler, vTexCoord);
// Get x/w and y/w from the viewport position
float x = vTexCoord.x * 2 - 1;
float y = (1 - vTexCoord.y) * 2 - 1;
float4 vProjectedPos = float4(x, y, z, 1.0f);
// Transform by the inverse projection matrix
float4 vPositionVS = mul(vProjectedPos, g_matInvProjection);
// Divide by w to get the view-space position
return vPositionVS.xyz / vPositionVS.w;
}
总结
参考:
1)《Mathematics for 3D Game Programming and Computer Graphics(3rd Edition)》Chapter 5.5 Projections.
2)《Real-Time Rendering(3rd Edition)》Chapter 4.6 Projections
3) Cg Programming/Vertex Transformations
4) Cg Programming/Rasterization
5)Cg Programming/Unity/Debugging of Shaders
7)Built-in shader variables (也可以参考官方buildin shader中的 “UnityShaderVariables.cginc”)
8)Scintillating Snippets: Reconstructing Position From Depth