OpenGL之三维

OpenGL之基础
OpenGL之绘制简单形状
OpenGL之颜色
OpenGL之调整屏幕宽高比

从着色器到屏幕的坐标变换

顶点着色器上的原始 gl_Position 坐标变换为最终的屏幕坐标流程


image-20211221143616767.png

剪裁空间

当顶点着色器把一个值写到 gl_Position 的时候,这个位置是在剪裁空间(clip space)中的。在剪裁空间中,对于任何给定的位置,它的x、y以及z分量都需要在那个位置的-w和w之间。比如,如果一个位置的w是1,那么其x、y以及z分量都需要在-1和1之间。任何这个范围外的事物在屏幕上都不可见的

透视除法

OpenGL 使用透视除法将剪裁空间中的位置坐标变换为归一化设备坐标,不管渲染区域的大小和形状,对于其中的每个可视坐标,其x、y和z分量的取值都位于[-1,1]的范围内

为了在屏幕上创建三维的幻象,OpenGL会把每个 gl_Position 的x、y和z分量都除以它的w分量。因此,当w分量用来表示距离的时候,就使得较远处的物体被移动到距离渲染区域中心更近的地方,这个中心的作用就像一个消失点


image-20211221150118548.png

为什么不把 z 解释为距离,而是保留 z 分量作为深度缓冲区(depth buffer),并增加 w 作为第四个分量,因为我们可以把投影的影响与实际的 z 坐标解耦,以便我们可以在正交投影和透视投影之间切换

视口变换

OpenGL 把归一化设备坐标的 x 和 y 分量映射到屏幕上的一个区域内,这个区域是操作系统预留出来用于显示的,被称为视口(viewport),这些变换后的坐标被称为窗口坐标( window coordinate)

实现三维显示

在顶点数据中加入 z 和 w 分量,近处的点 w 值为 1,越远的 w 的值大,这样确实可以实现三维的显示效果,但不能改变桌子的角度或者缩小、放大。要实现这些变换,不能使用硬编码指定 w 的值,而要用矩阵来生成这些值,用透视投影矩阵自动生成w的值

透视投影矩阵

透视投影矩阵需要和透视除法一起发挥作用。投影矩阵不能自己做透视除法,而透视除法需要 w 才能起作用,因此,透视投影矩阵最重要的任务就是为 w 产生正确的值

能实现这些的方法之一是利用 z 分量,把它作为物体与焦点(观察点)的距离并且把这个距离映射到w,这个距离越大,w值越大,所得物体越小

示意图

透视投影.jpg

可以看出,相比与正交投影矩阵,透视投影矩阵是一个更加通用的投影矩阵,可以同时对宽高比和视野进行调整,因此,使用透视投影矩阵,就可以去掉正交投影矩阵了

创建透视投影矩阵

Matrix.perspectiveM()方法可以创建一个透视投影矩阵,其原型如下

public static void perspectiveM(float[] m, int offset,
      float fovy, float aspect, float zNear, float zFar) {

参数说明

参数 说明
m 最终生成的透视投影矩阵数组,长度至少有16个元素,才能存储透视投影矩阵
offset 正交投影矩阵起始的偏移值
fovy y 方向的视野,以度为单位
aspect 屏幕的宽高比,它等于宽度/高度
zNear 到近处平面的距离,必须是正值。比如,如果此值被设为1,那近处平面就位于一个z值为-1处
zFar 到远处平面的距离,必须是正值且大于到近处平面的距离

也可以自己实现透视矩阵,透视矩阵最终的数值如下


image-20211221154900531.png

a:如果我们想象一个相机拍摄的场景,这个变量就代表那个相机的焦距。焦距是由1/tan(视野/2) 计算得到的。这个视野必须小于180度。比如,一个90度的视野,它的焦距会被设置为1/tan(90° /2),也就是1/1或者1

其他的和Matrix.perspectiveM()方法参数含义一致

自己实现透视矩阵代码

public class MatrixHelper {
    /**
     * @param matrix        矩阵
     * @param yFovInDegrees 视野角度
     * @param aspect        宽高比
     * @param n             近处到平面距离
     * @param f             远处到平面距离
     * @return
     */
    public static float[] perspectiveM(float[] matrix, float yFovInDegrees, float aspect, float n, float f) {
        // 计算焦距
        float angleInRadians = (float) (yFovInDegrees * Math.PI / 180.0);
        float a = (float) (1f / tan(angleInRadians / 2));

        // 输出矩阵
        for (int i = 0; i < matrix.length; i++) {
            switch (i) {
                case 0:
                    matrix[i] = a / aspect;
                    break;
                case 5:
                    matrix[i] = a;
                    break;
                case 10:
                    matrix[i] = -((f + n) / (f - n));
                    break;
                case 11:
                    matrix[i] = -1f;
                    break;
                case 14:
                    matrix[i] = -((2 * f * n) / (f - n));
                    break;
                default:
                    matrix[i] = 0f;
                    break;
            }
        }
        return matrix;
    }
}

使用透视矩阵实现三维效果

// 用45度的视野创建一个透视投影。这个视椎体从z值为-1的位置开始,在z值为-10的位置结束
Matrix.perspectiveM(projectionMatrix, 0, 45, (float) width / height, 1, 10);

使用上面的代码替换之前的正交投影矩阵,运行程序,会发现图像不见了,因为没有给顶点指定 z 的位置,默认情况下它 z 在 0 的位置。而这个视椎体是从z值为-1的位置开始的,除非把它移到那个距离内,否则我们无法看见图像

无法创建包含 z 位置为 0 的四椎体,因为 Matrix.perspectiveM 方法要求 zNear 和 zFar 都必须是正数

因此,在使用投影矩阵进行投影之前,需要使用一个平移矩阵把顶点移到可见的位置。通常把这个矩阵称为模型矩阵(model matrix)

模型矩阵

把模型矩阵设为单位矩阵,再沿着z轴平移 -2,当我们把顶点的坐标与这个矩阵相乘的时候,那些坐标最终会沿着z轴负方向移动2个单位,同时,如果只是平移的话,看不到三维效果,因为每个顶点都平移了,因此还需要旋转一下

Matrix.setIdentityM(modelMatrix, 0);
// 沿着z轴平移 -2
Matrix.translateM(modelMatrix, 0, 0f, 0f, -3f);
// 沿着 x 轴旋转45度
Matrix.rotateM(modelMatrix, 0, -45f, 1f, 0f, 0f);

可以把模型矩阵与投影矩阵相乘,得到一个最终矩阵,然后把这个矩阵传递给顶点着色器,这种方式可以在着色器中仅保留一个矩阵

// 设为单位矩阵
Matrix.setIdentityM(modelMatrix, 0);
// 沿着z轴平移 -2
Matrix.translateM(modelMatrix, 0, 0f, 0f, -3f);
// 沿着 x 轴旋转45度
Matrix.rotateM(modelMatrix, 0, -45f, 1f, 0f, 0f);

// 将视图投影矩阵和模型矩阵相乘
float[] temp = new float[16];
Matrix.multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0);
System.arraycopy(temp, 0, projectionMatrix, 0, temp.length);

不论什么时候把两个矩阵相乘,都需要一个临时变量来存储其结果。如果尝试直接写入这个结果,这个结果将是未定义的

修改渲染器

修改渲染器中的代码,去掉正交投影矩阵,加上模型矩阵和视图投影矩阵的使用

public class MyRenderer implements GLSurfaceView.Renderer {
    private final FloatBuffer mVertexBuffer;
    private Context mContext;
    private int a_color;
    private int a_position;
    private int u_matrix;
    private float[] projectionMatrix = new float[16];
    private float[] modelMatrix = new float[16];

    public MyRenderer(Context context) {
        mContext = context;
        float[] vertex = new float[]{
                0f, 0f, 1f, 1f, 1f,
                -0.5f, -0.8f, 0f, 0f, 0f,
                0.5f, -0.8f, 0f, 0f, 0f,
                0.5f, 0.8f, 0f, 0f, 0f,
                -0.5f, 0.8f, 0f, 0f, 0f,
                -0.5f, -0.8f, 0f, 0f, 0f,

                -0.5f, 0f, 1f, 0f, 0f,
                0.5f, 0f, 1f, 0f, 0f,

                0f, -0.5f, 0f, 1f, 0f,
                0f, 0.5f, 0f, 0f, 1f
        };
        mVertexBuffer = ByteBuffer.allocateDirect(vertex.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(vertex);
        mVertexBuffer.position(0);
    }

    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        glClearColor(1f, 1f, 1f, 1f);

        int vertexShader = ShaderHelper.compileVertexShader(mContext, R.raw.ratio3_vertex);
        int fragmentShader = ShaderHelper.compileFragmentShader(mContext, R.raw.ratio3_fragment);
        int program = ProgramHelper.getProgram(vertexShader, fragmentShader);
        glUseProgram(program);

        a_color = glGetAttribLocation(program, "a_Color");
        a_position = glGetAttribLocation(program, "a_Position");
        u_matrix = glGetUniformLocation(program, "u_Matrix");

        mVertexBuffer.position(0);
        glVertexAttribPointer(a_position, POSITION_COMPONENT_COUNT, GL_FLOAT, false, STRIDE, mVertexBuffer);
        glEnableVertexAttribArray(a_position);

        mVertexBuffer.position(POSITION_COMPONENT_COUNT);
        glVertexAttribPointer(a_color, COLOR_COMPONENT_COUNT, GL_FLOAT, false, STRIDE, mVertexBuffer);
        glEnableVertexAttribArray(a_color);
    }

    @Override
    public void onSurfaceChanged(GL10 gl10, int width, int height) {
        glViewport(0, 0, width, height);

        // 用45度的视野创建一个透视投影。这个视椎体从z值为-1的位置开始,在z值为-10的位置结束
        Matrix.perspectiveM(projectionMatrix, 0, 45, (float) width / height, 1, 10);

        // 设为单位矩阵
        Matrix.setIdentityM(modelMatrix, 0);
        // 沿着z轴平移 -2
        Matrix.translateM(modelMatrix, 0, 0f, 0f, -3f);
        // 沿着 x 轴旋转45度
        Matrix.rotateM(modelMatrix, 0, -45f, 1f, 0f, 0f);

        // 将视图投影矩阵和模型矩阵相乘
        float[] temp = new float[16];
        Matrix.multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0);
        System.arraycopy(temp, 0, projectionMatrix, 0, temp.length);
    }

    @Override
    public void onDrawFrame(GL10 gl10) {
        glClear(GL_COLOR_BUFFER_BIT);
        glUniformMatrix4fv(u_matrix, 1, false, projectionMatrix, 0);
        glDrawArrays(GL_TRIANGLE_FAN, 0, 6);

        glDrawArrays(GL_LINES, 6, 2);

        glDrawArrays(GL_POINTS, 8, 1);
        glDrawArrays(GL_POINTS, 9, 1);
    }
}

效果

image-20211221164736930.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容