前几天想写一个描边的效果,当然在unity中非常容易实现,但是换到基于openGL底层接口的时候却犯了难,本人已经不写openGL半年了,有些都忘了差不多了,再重新拾忆起来,调试了半天终于搞定。记录一下,以作备忘。
凡事都有个大思路:
描边其实很简单,判断相邻像素点的Z值和该像素所属面片的法向量只差。若大于一个阈值则判断为边界。
正所谓理论到实践总有一条漫长坎坷的路要走。因为fragment shader只能获取到上层vertex shader输出的值,而vertex shader又只能获得当前处理面片的信息,所以单单从渲染的路径中是获取不到相邻面片的法向量。故必须以另外的方式事先储存每个像素点所对应的面片法向量值,然后在渲染的路径中去提取这些信息才行。这时候只有纹理贴图能帮忙了。
思路大概是这样的:
第一步从视角拍摄一张纹理贴图,从vertex shader中获取顶点法向量,也是面片法向量,传递给fragment shader,然后fragment shader把法向量作为rgb值,像素深度值作为alpha值储存在一个纹理贴图中。
第二步以正常的渲染路径出发,在fragment shader中获取先前具有像素法向量和深度信息的纹理单元,因为我们有了当前处理像素的坐标,只要移动这个像素的坐标,并获取贴图中的相应坐标即可获得该像素的法向量和深度值,突破了渲染路径中不能获取其他面片信息的障碍。
上述还只是理论,但好歹给了我们一个努力的方向。
下面开始具体实践了。
首先我们需要一个fbo来储存texture的操作信息。
初始化init
//创建一个framebuffer object
glGenFramebuffers(1, &depth_fbo);
//绑定framebuffer object到GL_FRAMEBUFFER对象
glBindFramebuffer(GL_FRAMEBUFFER, depth_fbo);
glGenTextures(1, &depth_debug_tex);
glBindTexture(GL_TEXTURE_2D, depth_debug_tex);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32F, DEPTH_TEXTURE_SIZE, DEPTH_TEXTURE_SIZE); //注意这里的GL_RGBA32F参数值必须和所要储存的数据结构相对应,否则数据会被抛弃或者加零。比如GL_RGA32F的话我们从fragment shader中output一个vec4的color值,则rgb会被保留而alpha值则被抛弃
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//depth_debug_tex以第一个GL_COLOR_ATTACHMENT0绑定到GL_FRAMEBUFFER(所对应fbo)中去
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, depth_debug_tex, 0);
//此时fbo已经对应texture 脱离绑定
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
渲染到纹理贴图
static const GLfloat zero[] = { 0.0f, 0.0f, 0.0f, 0.0f };
static const GLfloat ones[] = { 1.0f, 1.0f, 1.0f, 1.0f };
//先绑定fbo,由于初始化这时fbo已经知道要给depth_debug_tex赋值了
glBindFramebuffer(GL_FRAMEBUFFER, depth_fbo);
glViewport(0, 0, DEPTH_TEXTURE_SIZE, DEPTH_TEXTURE_SIZE);
//glEnable(GL_POLYGON_OFFSET_FILL);
//glPolygonOffset(4.0f, 4.0f);
//depth_texture_program中vertex shader是很简单的传递面片法向量
//fragment shader很简单的把法向量作为rgb,Z深度作为alpha值输出
glUseProgram(depth_texture_program);
static const GLenum buffs[] = { GL_COLOR_ATTACHMENT0 };
//绑定渲染输出到GL_COLOR_ATTACHMENT0 而不是窗口
glDrawBuffers(1, buffs);
glClearBufferfv(GL_COLOR, 0, zero);
glClearBufferfv(GL_DEPTH, 0, ones);
然后渲染物体。这时候物体的渲染都会从该shader pass中经过,所有的信息被绘制到depth_debug_tex纹理贴图中
最后别忘了取消绑定fbo
glDisable(GL_POLYGON_OFFSET_FILL);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
至此信息全部被输出到纹理贴图中。
当然如果我们要看纹理贴图就要写一个shader吧纹理输出到屏幕
glDisable(GL_DEPTH_TEST);
glBindVertexArray(quad_vao);
glUseProgram(depth_render_program);
glBindTexture(GL_TEXTURE_2D, depth_debug_tex);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
其中depth_render_program就是把贴图输出的shader。
我们来看下该shader
//depth_render_program /vertex shader.glsl
#version 420 core
void main(void)
{
const vec4 vertices[] = vec4[](vec4(-1.0, -1.0, 0.5, 1.0),
vec4( 1.0, -1.0, 0.5, 1.0),
vec4(-1.0, 1.0, 0.5, 1.0),
vec4( 1.0, 1.0, 0.5, 1.0));
gl_Position = vertices[gl_VertexID];
}
//depth_render_program /fragment shader.glsl
#version 420
layout (binding = 0) uniform sampler2D tex_depth;
layout (location = 0) out vec4 color;
void main(void)
{
float r = texelFetch(tex_depth, ivec2(gl_FragCoord.xy * 3.0) + ivec2(450, 850), 0).r;
float g = texelFetch(tex_depth, ivec2(gl_FragCoord.xy * 3.0) + ivec2(450, 850), 0).g;
float b = texelFetch(tex_depth, ivec2(gl_FragCoord.xy * 3.0) + ivec2(450, 850), 0).b;
float a = texelFetch(tex_depth, ivec2(gl_FragCoord.xy * 3.0) + ivec2(450, 850), 0).a;
r = (r - 0.95) * 15.0;
g = (g - 0.95) * 15.0;
b = (b - 0.95) * 15.0;
color = vec4(a);
}