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是经过单位化后的向量:。所以向量right为。
顶点缓冲区中的存放的点的是四边形底部的中心。通过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.pngimage.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