OpenGL 几何着色器及billboard广告牌

2022-02-13  本文已影响0人  神迹12

几何着色器

在顶点和片段着色器之间有一个可选的着色器叫几何着色器(Geometry Shader)。如果编译程序的时候不使用几何着色器,则图元会简单的从顶点着色器进入片元着色器。
几何着色器以一个或多个表示为一个单独基本图形(primitive)的顶点作为输入,比如可以是一个点或者三角形,通过它的处理转变成完全不同的基本图形(primitive),从而生成比原来多得多的顶点

Billboard广告牌

Billboard 是一个始终朝向相机的四边形。当相机在场景中发生运动时,Billboard 也随之转动,因此,从 billboard 到相机的矢量一直垂直于 billboard 的表面。
形象的来说,就好像一个人举着牌子,无论你从哪个方向看向牌子,那个人都会把牌子朝着你的方向旋转,你永远只能看到牌子的正面。Billboard可以创建大量面向相机的场景物体,如创建大量的树来营造森林的效果。

每一个Billboard占用4个顶点,通过几何着色器生产这样的4个顶点,其确定的4边行始终朝向相机,并为这4个顶点生成合适的纹理坐标。此处需要使用向量叉乘。

向量叉乘

image.png
其中 image.png

根据i、j、k间关系,有:


image.png

在三维几何中,向量a和向量b的外积结果是一个向量,有个更通俗易懂的叫法是法向量,该向量垂直于a和b向量构成的平面。使用右手定则确定其方向。

例子

几何着色器代码如下:

#version 320 es
layout (points) in; //声明输入的基本图形(primitive)类型,从顶点着色器中接收到的
layout (triangle_strip, max_vertices = 4) out; //几何着色器所输出的基本图形类型,输出一个矩形的4个顶点,所以max_vertices为4
uniform mat4 vMatrix; //vp矩阵相乘结果
uniform vec3 gCameraPos;//相机位置

out vec2 TexCoord;//输出的纹理坐标

void main() {
    // 计算x乘结果矢量
    vec3 Pos = gl_in[0].gl_Position.xyz;
    vec3 toCamera = normalize(gCameraPos - Pos);
    vec3 up = vec3(0.0, 1.0, 0.0);
    vec3 right = cross(up, toCamera);

    //billboard
    mat4 gVP = vMatrix;
    Pos -= (right * 0.5);//左下角
    gl_Position = gVP * vec4(Pos, 1.0);
    TexCoord = vec2(0.0, 1.0);
    EmitVertex();
    Pos.y += 1.0;//左上角
    gl_Position = gVP * vec4(Pos, 1.0);
    TexCoord = vec2(0.0, 0.0);
    EmitVertex();
    Pos.y -= 1.0;
    Pos += right;//右下角
    gl_Position = gVP * vec4(Pos, 1.0);
    TexCoord = vec2(1.0, 1.0);
    EmitVertex();
    Pos.y += 1.0;//右上角
    gl_Position = gVP * vec4(Pos, 1.0);
    TexCoord = vec2(1.0, 0.0);
    EmitVertex();
    EndPrimitive();
}

对于几何着色器的输入输出等相关参数说明,参见:
https://learnopengl-cn.readthedocs.io/zh/latest/04%20Advanced%20OpenGL/09%20Geometry%20Shader/

以如下1个顶点为例。假定传给顶点着色器的顶点数据为1个,即原点(0,0,0)。相机坐标为(0,0,4)。则根据右手定则,得到的向量正好朝向X轴正向的向量。toCamera是经过单位化后的向量:\vec{(0,0,1)}。所以向量right\vec{(1,0,0)}

image.png

顶点缓冲区中的存放的点的是四边形底部的中心。通过right向量得到平面的左下角的点,进而计算得到四边形的其他3个顶点坐标(-0.5,0,0)、(-0.5,1,0)、(0.5,0,0)、(0.5,1,0)。并同时生成对应的纹理坐标,通过内置函数 EmitVertex()将产生的新顶点传递到下一管线。

顶点着色器代码:

#version 320 es
layout (location = 0) in vec4 vPosition;

void main() {
     gl_Position  = vPosition;
}

片元着色器代码:

#version 320 es
precision mediump float;
uniform sampler2D gColorMap;
in vec2 TexCoord;
out vec4 FragColor;
void main(){
    FragColor = texture(gColorMap, TexCoord);
}

render代码如下:

class GeometryShaderRenderer:GLSurfaceView.Renderer {
    private val TAG = "GeometryShaderRenderer"
    private var vertexBuffer: FloatBuffer? = null

    //渲染程序
    private var mProgram = 0

    //定点
    var triangleCoords = floatArrayOf( // 矩形全部点位
//            -0.5f, 0.5f, 0.0f,  // top  0.5202312
//            // 左上
//            0.5f, 0.5f, 0.0f,  // bottom left
//            // 右上
//            0.5f, -0.5f, 0.0f, // bottom right
//            // 右下
//            -0.5f, -0.5f, 0.0f
            0.0f,0.0f,0.0f
    )
    

    // camera坐标  (0 0 4)  (0 3 4) (3 3 4)
    private val camPos = floatArrayOf( // 矩形全部点位
            0.0f, 0.0f, 4.0f
    )

    //相机矩阵
    private val mViewMatrix = FloatArray(16)

    //投影矩阵
    private val mProjectMatrix = FloatArray(16)

    //最终变换矩阵
    private val mMVPMatrix = FloatArray(16)


    //纹理id
    private var textureId = 0
    private var mRatio = 0.5f

    init {
        val byteBuffer = ByteBuffer.allocateDirect(triangleCoords.size * 4)
        byteBuffer.order(ByteOrder.nativeOrder())
        vertexBuffer = byteBuffer.asFloatBuffer()
        vertexBuffer!!.put(triangleCoords)
        vertexBuffer!!.position(0)
    }

    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
        GLES30.glClearColor(0.5f, 0.5f, 0.5f, 1.0f)
        //编译顶点着色程序
        val vertexShaderStr = ResReadUtils.readResource(R.raw.vertex_geometry_billboard_shader)
        val vertexShaderId = ShaderUtils.compileVertexShader(vertexShaderStr)
        //编译片段着色程序
        val fragmentShaderStr = ResReadUtils.readResource(R.raw.fragment_geometry_billboard_shader)
        val fragmentShaderId = ShaderUtils.compileFragmentShader(fragmentShaderStr)
        //编译几何着色程序
        val geometryShaderStr = ResReadUtils.readResource(R.raw.geometry_line_billboard_shader)
        val geometryShaderId = ShaderUtils.compileGeometryShader(geometryShaderStr)
        //连接程序
        mProgram = ShaderUtils.linkProgram(vertexShaderId,geometryShaderId, fragmentShaderId)
        //在OpenGLES环境中使用程序
        GLES30.glUseProgram(mProgram)
        //加载纹理
        textureId = TextureUtils.loadTexture(AppCore.getInstance().context, R.drawable.monster_2)
    }

    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
        GLES30.glViewport(0, 0, width, height)
        val ratio = width.toFloat() / height
        mRatio = ratio
        //设置透视投影
        Matrix.frustumM(mProjectMatrix, 0, -ratio, ratio, -1f, 1f, 2f, 7f)
        //        Matrix.orthoM(mProjectMatrix,0,-ratio,ratio,-1,1,3,7);
        //设置相机位置
        Matrix.setLookAtM(mViewMatrix, 0, camPos[0], camPos[1], camPos[2],  //摄像机坐标
                0f, 0f, 0f,  //目标物的中心坐标
                0f, 1.0f, 0.0f) //相机方向
        Matrix.multiplyMM(mMVPMatrix, 0, mProjectMatrix, 0, mViewMatrix, 0)
    }

    override fun onDrawFrame(gl: GL10?) {
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT or GLES30.GL_DEPTH_BUFFER_BIT)
        GLES30.glClearColor(0f,0f,0f,1.0f)
        //左乘矩阵
        val uMaxtrixLocation = GLES30.glGetUniformLocation(mProgram, "vMatrix")
        // 将前面计算得到的mMVPMatrix(frustumM setLookAtM 通过multiplyMM 相乘得到的矩阵) 传入vMatrix中,与顶点矩阵进行相乘
        GLES30.glUniformMatrix4fv(uMaxtrixLocation, 1, false, mMVPMatrix, 0)
        val aPositionLocation = GLES30.glGetAttribLocation(mProgram, "vPosition")
        GLES30.glEnableVertexAttribArray(aPositionLocation)
        //x y z 所以数据size 是3
        GLES30.glVertexAttribPointer(aPositionLocation, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer)
        val camPosLocation = GLES30.glGetUniformLocation(mProgram, "gCameraPos")

        GLES30.glUniform3f(camPosLocation,camPos[0], camPos[1], camPos[2])


        GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
        //绑定纹理
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId)
        //三角形
//        GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, triangleCoords.size / 3)
        // 画点,通过几何着色器生成其他图形
        GLES30.glDrawArrays(GLES30.GL_POINTS, 0, triangleCoords.size / 3)


        //禁止顶点数组的句柄
        GLES30.glDisableVertexAttribArray(aPositionLocation)
    }
    
}

效果

image.png
image.png

代码

https://github.com/godtrace12/DOpenglTest

参考

https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/09%20Geometry%20Shader/
https://wiki.jikexueyuan.com/project/modern-opengl-tutorial/tutorial27.html
https://www.jianshu.com/p/4ac4580be213
https://www.cnblogs.com/gxcdream/p/7597865.html

上一篇下一篇

猜你喜欢

热点阅读