零蚀
前言
第三维度
-
简介
- 其实在绘制方面,古老的艺术家们,会通过绘制一些看不见的点,来故意“诱拐”我们的视觉思维,让我们感觉我们正在欣赏一个三维的立体空间,而OpenGL则不是这么干的,这里OpenGL用到的理念,叫做
line projection
(线性投影)。其实这也很好理解,就像一束平行光看到某个物体,产生一种透视的效果。(但是我觉得书上关于线性投影的解释不够严谨)
-
clip space:这里我觉得怎么翻译都很变扭,clip是切的意思,其实就是一个范围空间,这个范围空间制定了我们的 x,y,z的取值范围是在[-w,w]之间,而opengl的设置的空间在[-1,1],也就是w=1,-w=-1,如果我们的图形超过了这个空间,那么绘制就不会被显示在界面上。
-
Perspective Division(透视分配法):在点由逻辑坐标绘制到机器上的物理坐标之前,他会进行一个步骤:Perspective Division,这个步骤,将我们的的x,y, z点都除以w,当w为距离时,越远的点,越靠近屏幕中心,opengl 以此来让我们误以为这是个3D的图形。
-
我们在坐标中加入一个w变量,而不是用x , y除以z,以z为标准的做法,这样做,一方面是为x,y,z解藕,另一方面,为后面的深度缓冲区提供便利(后话)。
-
然后我们了解一下齐次坐标,齐次坐标,就是一类同等倍数的坐标,例如,(1,1,1,2)、(2,2,2,4)、(9,9,9,18)他们都可以化为(1,1,1,2),则称此类坐标为齐次坐标。
- 其实在绘制方面,古老的艺术家们,会通过绘制一些看不见的点,来故意“诱拐”我们的视觉思维,让我们感觉我们正在欣赏一个三维的立体空间,而OpenGL则不是这么干的,这里OpenGL用到的理念,叫做
-
添加透视坐标W
-
我们的坐标模型现在是(X,Y,Z,W,R,Q,B),所以我们可以如下修改我们的坐标参数。
public class AirHockeyConstant { static float tableVertices[] = { // coordinates:X Y Z W R G B 0f, 0f, 0f, 1.5f, 1f, 1f, 1f, -0.5f, -0.8f, 0f, 1f, 0.7f, 0.7f, 0.7f, 0.5f, -0.8f, 0f, 1f, 0.7f, 0.7f, 0.7f, 0f, 0f, 0f, 1.5f, 1f, 1f, 1f, 0.5f, -0.8f, 0f, 1f, 0.7f, 0.7f, 0.7f, 0.5f, 0.8f, 0f, 2f, 0.7f, 0.7f, 0.7f, 0f, 0f, 0f, 1.5f, 1f, 1f, 1f, 0.5f, 0.8f, 0f, 2f, 0.7f, 0.7f, 0.7f, -0.5f, 0.8f, 0f, 2f, 0.7f, 0.7f, 0.7f, 0f, 0f, 0f, 1.5f, 1f, 1f, 1f, -0.5f, 0.8f, 0f, 2f, 0.7f, 0.7f, 0.7f, -0.5f, -0.8f, 0f, 1f, 0.7f, 0.7f, 0.7f, // 定义中位线 -0.5f, 0f, 0f, 1.5f, 1f, 0f, 0f, 0.5f, 0f, 0f, 1.5f, 1f, 0f, 0f, // 定义曲棍球的曲棍位置 0f, -0.4f, 0f, 1.25f, 0f, 0f, 1f, 0f, 0.4f, 0f, 1.75f, 1f, 0f, 0f }; }
-
然后修改我们的坐标参数个数
POSITION_COMPONENT_COUNT=4
,我们就可以得到如下的图像。 -
这里我们z轴为0 ,我们是通过w的比例变化,来让x,y的大小发生比例大小的变换,这样,我们就看起来好像确实更像3D效果了,但是我们想动态改变这种显示样式,我们该如何做呢?对w值进行硬编码,肯定不会这样,答案是利用矩阵,这就用了我们之前所说的透视投影(Perspective Projection)
-
-
Perspective Projection(透视投影)
-
平截头体(Frustum):平截头体就是指刚好能包裹着个物体对象的(紫色边框,黄色填充)棱台。这是通过中心投影,分割而成的空间。这里的眼睛相当于所有的线性的线相交于一处的点,而这个点我们称之为焦点,这个点到棱台的上底面的距离叫做焦距。
-
我们通常会使用透视投影来处理3D问题,一般我们将焦点放在z轴上,然后由w来控制投影的距离,这中控制方式都是通过矩阵来完成的。在应用中,有
frustumM()
和perspectiveM()
来完成透视投影的功能,但是frustumM()
在一些功能上是存在bug的,而perspectiveM()
是只支持4以上的Android手机系统。 -
构建我们的透视矩阵,这里和书上不一样,我们使用的是
perspectiveM()
来完成一个透视矩阵。首先我们来看一下透视矩阵的方法使用。透视投影是构建了一个大型的平截头体,将内容包含在里面,这个可是控件的平截头体,就是我们的透视矩阵。平截头体内只会显示内部的物体,一旦物体超出了这个区间,就不会再显示,它的方法解释如下- 第一个参数 m ,是我们的的项目绘制矩阵
projectionMatrix
- 第二个参数 offset ,是第一个矩阵参数中元素的偏移量
- 第三个参数是我们的视角角度fovy,一般真实的视角感官是45度
- 第四个参数是我们的宽高比。
- 第五个参数 zNear 是视觉的内容平截头体距离平截头体的距离如下图所示,一般zNear设置为0.1,zFar设置为100f,如果zNear设置过大,那么在[0~zNear]之间的物体会被剪切掉,就像游戏里有个物体距离你10m,结果走近一步,他就不见了,或者进入它内部。
Matrix.perspectiveM(float[] m, int offset,float fovy, float aspect, float zNear, float zFar);
- 第一个参数 m ,是我们的的项目绘制矩阵
-
当我们设置了平截头体之后,我们运行发现任然无法看到我们想看到的图形,如果我们设置的zNear是近视觉的近距离,这个坐标点位于z轴的负轴上,距离为到原点的距离,zFar同理。而我们的视角默认是处在z轴的原点位置。下图就是我们看不到物体的原因,所以我们得偏移我们的视觉空间内。
-
但是我们是位置数据是有矩阵完成的, 我们该如何运算才能进行我们想要的平移呢?我们可一用一个单位矩阵(原子矩阵),通过矩阵之间的相加减来使得图像发生变化
@Override public void onSurfaceChanged(GL10 gl, int width, int height) { GLES20.glViewport(0,0,width,height); final float aspectRatio = width > height ? (float)width/(float)height:(float)height/(float)width; if(width>height){ // 横屏 landscapes Matrix.orthoM(projectionMatrix,0,-aspectRatio,aspectRatio,-1f, 1f, -1f, 1f); }else{ // 竖屏 portrait Matrix.orthoM(projectionMatrix,0,-1f, 1f, -aspectRatio,aspectRatio,-1f, 1f); } Matrix.perspectiveM(projectionMatrix, 0, 45f, aspectRatio, 0f, 10f); float[] modelMatrix = new float[16]; Matrix.setIdentityM(modelMatrix, 0); Matrix.translateM(modelMatrix, 0, 0f, 0f, -2f); final float[] temp = new float[16]; Matrix.multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0); System.arraycopy(temp, 0, projectionMatrix, 0, temp.length); }
-
在这里我们定义了一个单位矩阵modelMatrix,方法是
setIdentityM
然后我们将单位举证向后z的负半轴移动了2各单位,坐标点是-2。这样就将图像移到了我们视线的平截头体内。 -
然后我们用
Matrix.multiplyMM
将连个矩阵相乘保留最后的结果到temp这个临时矩阵中,最后我们将temp的result拷贝进我们的projectionMatrix矩阵中。 -
当然除了平移,我们还可以使用其他的方法,比如说旋转。第一个参数是矩阵,第二个参数是偏移量,第三个是角度,后面是(x,y,z)旋转的旋转轴(以坐标和原点连线为轴),淡然我们可以把这个设置成一个动态的过程。
Matrix.rotateM(modelMatrix, 0, -60f, 1f, 0f, 0f);
-
代码可以这么写,防燃你为了方便可以把它移到
onDrawFrame
里面,这样就不用什么线程了。大致代码如下:// 正投影矩阵 private final float[] projectionMatrix = new float[16]; // 矩阵的结果 private final float[] resultMatrix = new float[16]; ... @Override public void onSurfaceChanged(GL10 gl, int width, int height) { ... new Thread(){ @Override public void run() { while(true){ SystemClock.sleep(100); i=(i+1)%360; float[] modelMatrix= new float[16]; Matrix.setIdentityM(modelMatrix, 0); Matrix.translateM(modelMatrix, 0, 0f, 0f, -2f); Matrix.rotateM(modelMatrix, 0, i, 1f, 0f, 0f); final float[] temp = new float[16]; Matrix.multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0); System.arraycopy(temp, 0, resultMatrix, 0, temp.length); } } }.start(); } public void onDrawFrame(GL10 gl) { GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); GLES20.glUniformMatrix4fv(matrixLocation, 1, false, resultMatrix, 0); GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 12); GLES20.glDrawArrays(GLES20.GL_LINES,12,2); GLES20.glDrawArrays(GLES20.GL_POINTS, 14, 1); GLES20.glDrawArrays(GLES20.GL_POINTS, 15, 1); }
-
🔗 前言
🔗 OpenGL ES 篇
🔗 NO.1 OpenGL ES 前言
🔗 NO.2 从OpenGL与GC的矛盾说起
🔗 NO.3 OpenGl 着色器-绘制
🔗 NO.4 OpenGl 色彩过渡&正投影
🔗 NO.6 OpenGl 纹理