简述
点精灵使使用片元着色器渲染的OpenGL点。运行的时候需要考虑点内的片元坐标,这个坐标由内置的两维向量gl_PointCoord保存,这个变量最常见的作用是当作纹理坐标使用或者用于计算颜色和覆盖率。
给点精灵贴上纹理
在OpenGL绘制的点,只有一个位置坐标。如果要给一个点贴上纹理,则需要使用内置变量gl_PointCoord来查询纹理中的纹素,这样就可以生成一个带纹理的点精灵了。环境:OPenGL3.3,glfw,glew。
简单的点精灵示例源码
着色器
//顶点着色器 #version 330 core layout (location = 0) in vec3 aPos; void main() { gl_Position = vec4(aPos.x, aPos.y, 0, 1.0); //设置点精灵的大小 gl_PointSize = 80; };
//片元着色器
#version
330 core
uniform sampler2D sprite_texture;
out vec4 FragColor;
void main()
{
FragColor = texture(sprite_texture, gl_PointCoord);
};
应用程序
在应用程序中需要生成一个纹理对象,并关联到着色器,这跟普通的纹理映射相同。绘制前开启点精灵相关功能,绘制类型必须为GL_POINTS。
注意:点精灵的大小有两种方法绘制,一种是在应用程序中调用函数glPointSize()设置点的大小,这种方式点的大小是固定的,不会随着相机的离远而变小。还有一种是在顶点着色器中设置,使用内置的输出变两gl_PointSize来设置,可以根据它们相对于近平面的距离来进行一个线性映射。
#define GLFW_STATIC void framebuffer_size_callback(GLFWwindow* window, int width, int height); void processInput(GLFWwindow *window); const unsigned int SCR_WIDTH = 800; const unsigned int SCR_HEIGHT = 600; int main() { //--------------------------------------------------------------------------------- glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL", NULL, NULL); if (window == NULL) { std::cout << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); glewExperimental = GL_TRUE; if (glewInit() != GLEW_OK) { std::cout << "Failed to initialize GLEW" << std::endl; return -1; } glViewport(0, 0, 800, 600); //--------------------------------------------------------------------------------- Shader ourShader("./shader/Points.vs", "./shader/Points.fs"); float vertices[] = { // 位置 // 颜色 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 右下 -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 左下 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // 顶部 }; // ---------------纹理1 --------------- unsigned int texture1; // 创建纹理 glGenTextures(1, &texture1); //绑定纹理 glBindTexture(GL_TEXTURE_2D, texture1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //图像的宽度、高度和颜色通道的个数 int width, height, nrChannels; stbi_set_flip_vertically_on_load(true); //加载纹理 unsigned char *data = stbi_load("bricks2.jpg", &width, &height, &nrChannels, 0); //如果加载成功 if (data) { //使用载入的图片数据生成一个纹理了 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); } else { std::cout << "Failed to load texture" << std::endl; } //释放图像内存 stbi_image_free(data); //创建缓存对象 unsigned int VBO, VAO; glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); //绑定VAO glBindVertexArray(VAO); //绑定VBO glBindBuffer(GL_ARRAY_BUFFER, VBO); //把顶点数组复制到VBO glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 位置属性,第一个参数是着色器位置 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // 颜色属性 glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); while (!glfwWindowShouldClose(window)) { processInput(window); glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); //点精灵设置 glEnable(GL_POINT_SPRITE); glEnable(GL_PROGRAM_POINT_SIZE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); //激活着色器 ourShader.use(); glBindTexture(GL_TEXTURE_2D, texture1); //绑定顶点缓存对象 glBindVertexArray(VAO); //开始绘制 glDrawArrays(GL_POINTS, 0, 3); //解除绑定 glBindVertexArray(0); //交换缓存 glfwSwapBuffers(window); //查询io事件 glfwPollEvents(); } //删除顶点数组对象和顶点缓冲对象 glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO); glfwTerminate(); return 0; } void framebuffer_size_callback(GLFWwindow* window, int width, int height) { glViewport(0, 0, width, height); } void processInput(GLFWwindow *window) { if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { glfwSetWindowShouldClose(window, true); } }
效果
解析点精灵的颜色和形状
纹理的分辨率是有限的,我们并不仅限于从纹理加载数据。gl_PointCoord非常精确,把gl_PointCoord当作点的圆心,然后计算片元离点精灵中心的距离,如果大于0.25,就使用discard关键字拒绝这个片元,这样就生成了一个半径为0.25的圆了。
示例源码
注意:只需要修改着色器代码即可,应用程序同上
//顶点着色器 #version 330 core layout (location = 0) in vec3 aPos; void main() { gl_PointSize = 80; gl_Position = vec4(aPos.x, aPos.y, 0, 1.0); };
//片元着色器 #version 330 core uniform sampler2D sprite_texture; out vec4 FragColor; void main() { const vec4 color1 = vec4(0.6,0.0,0.0,1.0); const vec4 color2 = vec4(0.9,0.7,1.0,0.0); vec2 temp = gl_PointCoord - vec2(0.5); float f = dot(temp, temp); if(f > 0.25) { discard; } FragColor = mix(color1, color2, smoothstep(0.1, 0.25, f)); };
效果