欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

为什么可以将mesh的顶点位置数组直接设置成屏幕坐标系下的值?——mesh的渲染过程

程序员文章站 2022-04-19 18:23:05
...

问题起源

// 获取算法的运行结果:背景形变后的顶点坐标和uv坐标
        for (int k = 0; k < result.pointsLen / 2; ++k)
        {
            // 0, 2, 4, ……等偶数索引中存储x分量; 同理奇数索引存储y分量
            // 除以宽/高以及减去0.5乘以2.0的运算时为了将分辨率坐标还原为屏幕坐标
            vertices.pushBack(Vector3f((result.pointsDeformed[2 * k] / surfaceWidth - 0.5) * 2.0, -(result.pointsDeformed[2 * k + 1] / surfaceHeight - 0.5) * 2.0, 0.0));
            uvs.pushBack(Vector2f(result.pointsUndeform[2 * k] / surfaceWidth, 1.0 - result.pointsUndeform[2 * k + 1] / surfaceHeight));
        }

        for (int k = 0; k < result.triangleListSize; k++)
        {
            indices.pushBack(result.triangleList[k]);
        }

        if (vertices.size() != 0)
        {
            Vector subMeshes = deformationMesh->getSubMeshes();
            auto subMesh = AMAZING_CREATE(SubMesh);
            subMesh->setPrimitiveType(AMGPrimitive::TRIANGLES);
            subMesh->onLoadEnd();
            subMesh->setMesh(deformationMesh);
            subMesh->setIndices16(indices);
            subMeshes.pushBack(subMesh);
            // 用屏幕坐标来设置背景形变mesh的顶点坐标和uv坐标
            // 此处可以直接设置成屏幕坐标,是由mesh使用的material中的shader而决定的
            // 对应vertex shader中计算gl_position的方式为直接赋值,而未像普通的mesh渲染一样在vertex shader中做u_mvp * model positon的操作
            deformationMesh->setVertexArray(vertices);
            deformationMesh->setUvArray(0, uvs);
        }

上述代码块中,vertices是已经处在屏幕空间的顶点坐标数组,它被设置成了deformationMesh的顶点数组。
mesh中的顶点坐标怎么能够不是在模型空间,而是直接处在屏幕空间?
这是最初让我感到疑惑的地方,一直以来我对mesh的顶点坐标的理解如下:
mesh中的顶点坐标处在模型空间中,经过vertex shader中乘以mvp矩阵的操作,顶点坐标从模型空间转换到裁剪空间,并赋值到gl_position;gl_position从vertex shader传递到fragment shader之后,自动进行了投影变换,继续将坐标从裁剪空间转换到屏幕空间。
(下方代码块是一个最简单的gles2的vertex shader,只完成了gl_position的赋值)

void main()
{
    gl_Position = u_MVP * (position);
}

这就引出了我对mesh到底是如何被渲染出来的思考。

mesh渲染过程

考虑最简单的情况,mesh中含有处于模型空间的这个mesh的所有顶点的坐标。为了渲染这个mesh,还必须赋予其一个材质material,这个材质中会有两个着色器shader,分别为顶点着色器vertex shader和片段着色器fragment shader。

vertex shader:
mesh的顶点坐标数组会被传入vertex shader中,并在其中做一个乘以mvp矩阵的操作,以将此时处于模型空间的顶点坐标转换到裁剪空间。其中mvp矩阵(在我所使用的引擎中)是这样设定的:
model矩阵由mesh所属的entity的transform的空间坐标决定
view矩阵一般是单位矩阵
projection矩阵可以用相机的fov计算得出

fragment shader:
vertex shader的计算结果传送到fragment shader的过程中自动完成了投影变换,在fragment shader中的坐标处于屏幕空间,fragment shader为屏幕上的每个像素设置颜色,比如用一张2d texture和uv坐标来完成颜色的设定

uniform sampler2D Texture;
void main()
{
    gl_FragColor = texture2D(Texture, uv0);
}

经过顶点着色器,原本mesh中处于模型空间的顶点转换到了屏幕空间上。
经过片段着色器,屏幕空间上的顶点被涂上了颜色。
这就完成了mesh的渲染。

问题解决

梳理mesh的渲染过程之后,最开始的问题已经有了答案。
既然坐标空间的变换发生在顶点着色器中,那么那个mesh所使用的顶点着色器自然是关键。其代码如下:

precision highp float;

attribute vec2 position;
attribute vec2 texcoord0;
varying vec2 uv0;
uniform mat4 u_MVP;
void main()
{
    // gl_Position = sign(vec4(position.xy, 0.0, 1.0));
    gl_Position = vec4(position.x, position.y, 0.0, 1.0);
    uv0 = vec2(texcoord0.x, texcoord0.y);
}

果然,该顶点着色器并未出现乘以mvp矩阵的操作,而是直接将mesh中传来的坐标设置到gl_position中,并将w分量设置为1,这也就避免了gl_position往片段着色器传递时必定会发生的投影变换(xy分量除以w分量以实现近大远小)的错误。

ps:本文基于作者使用的引擎所写,可能不普适,请酌情分析。