1762 字
9 分钟
Cull Face
面剔除基本原理
三角形顶点的定义环绕顺序,决定 GPU 判断正面、背面的规则。OpenGL 默认规则:顶点逆时针(CCW)环绕构成的面为正向面,正常保留渲染;顺时针环绕的背向面会被自动剔除,不参与后续渲染流程。
使用场景
适用场景
适用于立方体等封闭立体模型。封闭模型内部背面永远不会被看到,无需渲染,可安全开启面剔除。
禁用场景
不适用于草面片、广告牌、面片模型等非封闭单面结构。这类模型需要正面、背面均可正常显示,必须禁用面剔除,否则会出现模型镂空、缺失问题。
性能优势
片元着色器是渲染管线中开销最大的阶段,面剔除可在早期直接丢弃背面面片,使其完全不进入片元着色器计算,大幅节省性能。
相关OpenGL 指令
开启面剔除
// 默认:开启剔除,自动剔除背向面glEnable(GL_CULL_FACE);设置剔除面类型glCullFace
用于指定需要被剔除的面片类型:
- **GL_BACK:**剔除背向面(默认配置)
- **GL_FRONT:**剔除正向面
- **GL_FRONT_AND_BACK:**同时剔除正面、背面(模型完全不渲染)
重定义正向面规则 glFrontFace
- **GL_CCW:**逆时针环绕为正向面(默认)
- **GL_CW:**顺时针环绕为正向面
正面规则与剔除逻辑组合效果
顶点顺序分为 CCW(逆时针)、CW(顺时针) 两种,通过搭配 glFrontFace 和 glCullFace 可灵活控制渲染面片:
- 设置顺时针(GL_CW)为正面 + 剔除背面:仅保留背对摄像机的面
- 设置逆时针(GL_CCW)为正面 + 剔除正面:仅保留背对摄像机的面
**顶点顺序(CCW / CW)**定义面的朝向属性,glFrontFace 判定何为正面,glCullFace 决定哪一类面需要被剔除。
example1: 尝试设置面剔除的相关指令
#define STB_IMAGE_IMPLEMENTATION#include <glad/glad.h>#include <GLFW/glfw3.h>#include <stb_image.h>
#include <glm/glm.hpp>#include <glm/gtc/matrix_transform.hpp>#include <glm/gtc/type_ptr.hpp>
#include <myShader.h>#include <myCamera.h>#include <Model.h>
#include <iostream>
const unsigned int SCR_WIDTH = 800;const unsigned int SCR_HEIGHT = 400;
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));float lastX = (float)SCR_WIDTH / 2.0;float lastY = (float)SCR_HEIGHT / 2.0;bool firstCamera = true;
float deltaTime = 0.0f;float lastFrame = 0.0f;
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)) { glfwSetWindowShouldClose(window, true); } if (glfwGetKey(window, GLFW_KEY_W)) { camera.ProcessKeyboard(FORWARD, deltaTime); } if (glfwGetKey(window, GLFW_KEY_S)) { camera.ProcessKeyboard(BACKWARD, deltaTime); } if (glfwGetKey(window, GLFW_KEY_A)) { camera.ProcessKeyboard(LEFT, deltaTime); } if (glfwGetKey(window, GLFW_KEY_D)) { camera.ProcessKeyboard(RIGHT, deltaTime); }}void processMovement(GLFWwindow* window, double xpos, double ypos) { if (firstCamera) { lastX = static_cast<float>(xpos); lastY = static_cast<float>(ypos); firstCamera = false; } float xoffset = static_cast<float>(xpos) - lastX; float yoffset = lastY - static_cast<float>(ypos); lastX = static_cast<float>(xpos); lastY = static_cast<float>(ypos); camera.ProcessMouseMovement(xoffset, yoffset);}void processScroll(GLFWwindow* window, double xoffset, double yoffset) { camera.ProcessScroll(static_cast<float>(yoffset));}unsigned int loadTexture(const char* path) { unsigned int texture; glGenTextures(1, &texture);
int width, height, nrChannels; unsigned char* data = stbi_load(path, &width, &height, &nrChannels, 0); if (data) { GLenum format; if (nrChannels == 1) { format = GL_RED; } else if (nrChannels == 3) { format = GL_RGB; } else if (nrChannels == 4) { format = GL_RGBA; } glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } else { cout << "failed to load tetxure" << endl; } stbi_image_free(data);
return texture;}int main() { glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "learnOpenGL", NULL, NULL);
if (window == NULL) { cout << "failed to create GLFW window" << endl; glfwTerminate(); return -1; }
glfwMakeContextCurrent(window); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); glfwSetCursorPosCallback(window, processMovement); glfwSetScrollCallback(window, processScroll);
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
if (!gladLoadGLLoader(GLADloadproc(glfwGetProcAddress))) { cout << "failed to initialize glad" << endl; glfwTerminate(); return -1; }
stbi_set_flip_vertically_on_load(true);
glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_CULL_FACE); glCullFace(GL_FRONT); glFrontFace(GL_CW);
Shader shader("src/Shader/vertexShader.txt", "src/Shader/fragmentShader.txt");
float cubeVertices[] = { // Back face -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, // Bottom-left 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-right 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, // bottom-right 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-right -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, // bottom-left -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, // top-left // Front face -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-left 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, // bottom-right 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, // top-right 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, // top-right -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, // top-left -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-left // Left face -0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // top-right -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-left -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // bottom-left -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // bottom-left -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-right -0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // top-right // Right face 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // top-left 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // bottom-right 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-right 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // bottom-right 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // top-left 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-left // Bottom face -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // top-right 0.5f, -0.5f, -0.5f, 1.0f, 1.0f, // top-left 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, // bottom-left 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, // bottom-left -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-right -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // top-right // Top face -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, // top-left 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // bottom-right 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-right 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // bottom-right -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, // top-left -0.5f, 0.5f, 0.5f, 0.0f, 0.0f // bottom-left };
float planeVertices[] = { 5.0f, -0.5f, 5.0f, 2.0f, 0.0f, -5.0f, -0.5f, 5.0f, 0.0f, 0.0f, -5.0f, -0.5f, -5.0f, 0.0f, 2.0f,
5.0f, -0.5f, 5.0f, 2.0f, 0.0f, -5.0f, -0.5f, -5.0f, 0.0f, 2.0f, 5.0f, -0.5f, -5.0f, 2.0f, 2.0f };
float transparentVertices[] = { 0.0f, 0.5f, 0.0f, 0.0f, 1.0f,//左上 0.0f, -0.5f, 0.0f, 0.0f, 0.0f,//左下 1.0f, -0.5f, 0.0f, 1.0f, 0.0f,//右下
0.0f, 0.5f, 0.0f, 0.0f, 1.0f,//左上 1.0f, -0.5f, 0.0f, 1.0f, 0.0f,//右下 1.0f, 0.5f, 0.0f, 1.0f, 1.0f//右上 };
vector<glm::vec3> windowQuad{ glm::vec3(-1.5f, 0.0f, -0.48f), glm::vec3(1.5f, 0.0f, 0.51f), glm::vec3(0.0f, 0.0f, 0.7f), glm::vec3(-0.3f, 0.0f, -2.3f), glm::vec3(0.5f, 0.0f, -0.6f) };
map<float, glm::vec3> sorted; //map会自动根据键值即这里的float进行从小到大的排序,那么以distance作为key,从后往前取map中的value,就能得到从远到近的quad的位置 for (int i = 0; i < windowQuad.size(); i++) { float distance = glm::length(camera.Position - windowQuad[i]); sorted[distance] = windowQuad[i]; }
unsigned int cubeVAO, cubeVBO; glGenVertexArrays(1, &cubeVAO); glGenBuffers(1, &cubeVBO);
glBindVertexArray(cubeVAO); glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), cubeVertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1);
glBindVertexArray(0);
unsigned int planeVAO, planeVBO; glGenVertexArrays(1, &planeVAO); glGenBuffers(1, &planeVBO);
glBindVertexArray(planeVAO); glBindBuffer(GL_ARRAY_BUFFER, planeVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), planeVertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1);
glBindVertexArray(0);
unsigned int transparentVAO, transparentVBO; glGenVertexArrays(1, &transparentVAO); glGenBuffers(1, &transparentVBO);
glBindVertexArray(transparentVAO); glBindBuffer(GL_ARRAY_BUFFER, transparentVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(transparentVertices), transparentVertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1);
glBindVertexArray(0);
unsigned int cubeTexture = loadTexture("resources/textures/marble.jpg"); unsigned int floorTexture = loadTexture("resources/textures/metal.jpg"); unsigned int transparentTexture = loadTexture("resources/textures/blending_transparent_window.png");
shader.use(); shader.setInt("texture1", 0); shader.setInt("texture2", 1); shader.setInt("texture3", 2);
while (!glfwWindowShouldClose(window)) { float currentFrame = (float)glfwGetTime(); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame;
processInput(window);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
shader.use(); glm::mat4 model = glm::mat4(1.0f); glm::mat4 view = camera.GetCameraView(); glm::mat4 project = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); shader.setMatrix("view", view); shader.setMatrix("project", project);
glBindVertexArray(cubeVAO); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, cubeTexture); shader.setInt("useTex", 1); model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f)); shader.setMatrix("model", model); glDrawArrays(GL_TRIANGLES, 0, 36);
model = glm::mat4(1.0f); model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f)); shader.setMatrix("model", model); glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
glBindVertexArray(planeVAO); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, floorTexture); shader.setInt("useTex", 2); shader.setMatrix("model", glm::mat4(1.0f)); glDrawArrays(GL_TRIANGLES, 0, 6); glBindVertexArray(0);
glBindVertexArray(transparentVAO); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, transparentTexture); shader.setInt("useTex", 3); for (map<float, glm::vec3>::reverse_iterator it = sorted.rbegin(); it != sorted.rend(); it++) { model = glm::mat4(1.0f); model = glm::translate(model, it->second); shader.setMatrix("model", model); shader.setMatrix("view", view); shader.setMatrix("project", project); glDrawArrays(GL_TRIANGLES, 0, 6); } glBindVertexArray(0);
glfwSwapBuffers(window); glfwPollEvents(); } glDeleteVertexArrays(1, &cubeVAO); glDeleteVertexArrays(1, &planeVAO); glDeleteBuffers(1, &cubeVBO); glDeleteBuffers(1, &planeVBO);
glfwTerminate(); return 0;}homework1
你能够重新定义顶点数据,将每个三角形设置为顺时针顺序,并将顺时针的三角形设置为正向面,仍将场景渲染出来吗? 思路:三角形设置为顺时针,且顺时针为正向,要想保留靠近我们的面,剔除远离我们的面。从观察者的视角看背对我们的面是逆时针渲染的,是背向面,那么应该剔除背向面。
glCullFace(GL_BACK);glFrontFace(GL_CW);同时,在这种定义下,之前的透明窗户正面不再可见。因为这些面片顶点的环绕顺序为逆时针,且默认逆时针为正向,现在定义顺时针为正向,则朝向我们的面由于是逆时针定义,属于背向面,被剔除,背对我们的面被保留。