OpenGL 几何着色器及billboard广告牌

几何着色器

在顶点和片段着色器之间有一个可选的着色器叫几何着色器(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
//www.greatytc.com/p/4ac4580be213
https://www.cnblogs.com/gxcdream/p/7597865.html

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

推荐阅读更多精彩内容