All articles

计算机图形学、OpenGL 、VTK、OpenCV

覆盖图形学基础、渲染管线,以及 OpenGL、VTK、OpenCV 的核心概念与实践。

2025-10-26 · Updated 2025-11-24 · 51 分钟阅读

计算机图形学基础

什么是计算机图形学?

计算机图形学(Computer Graphics)是研究如何在计算机中表示、生成和处理图形的学科。

计算机图形学的主要应用:

  • 游戏开发:3D 游戏渲染
  • 电影特效:CG 动画、特效制作
  • CAD/CAM:计算机辅助设计/制造
  • 虚拟现实:VR/AR 应用
  • 数据可视化:科学计算可视化
  • 医学影像:CT、MRI 图像处理

图形学的基本概念

像素(Pixel):

  • 图像的基本单位
  • 每个像素包含颜色信息(RGB、RGBA)

分辨率(Resolution):

  • 图像的宽度和高度(像素数)
  • 例如:1920×1080

帧率(Frame Rate,FPS):

  • 每秒显示的帧数
  • 60 FPS 表示每秒显示 60 帧图像

渲染(Rendering):

  • 将 3D 场景转换为 2D 图像的过程

光栅化(Rasterization):

  • 将几何图形转换为像素的过程

图形渲染管线(Graphics Pipeline)

经典的图形渲染管线流程:

输入顶点数据
    ↓
顶点着色器(Vertex Shader)
    ↓
图元装配(Primitive Assembly)
    ↓
几何着色器(Geometry Shader,可选)
    ↓
光栅化(Rasterization)
    ↓
片段着色器(Fragment Shader)
    ↓
深度测试(Depth Test)
    ↓
混合(Blending)
    ↓
帧缓冲区(Frame Buffer)

各个阶段的作用:

1. 顶点着色器(Vertex Shader):

  • 处理每个顶点
  • 进行坐标变换(模型、视图、投影变换)
  • 计算顶点颜色

2. 图元装配(Primitive Assembly):

  • 将顶点组装成图元(点、线、三角形)

3. 几何着色器(Geometry Shader):

  • 处理图元
  • 可以生成新的图元

4. 光栅化(Rasterization):

  • 将图元转换为片段(Fragment)
  • 确定哪些像素被图元覆盖

5. 片段着色器(Fragment Shader):

  • 处理每个片段
  • 计算片段颜色
  • 进行纹理采样

6. 深度测试(Depth Test):

  • 确定可见性
  • 丢弃被遮挡的片段

7. 混合(Blending):

  • 将新片段与已有颜色混合
  • 实现透明度效果

坐标系变换

图形学中的坐标系:

1. 模型坐标系(Model Space):

  • 物体自身的坐标系
  • 以物体中心为原点

2. 世界坐标系(World Space):

  • 场景的全局坐标系
  • 所有物体共享

3. 视图坐标系(View Space / Camera Space):

  • 以摄像机为原点的坐标系
  • 摄像机朝向为 Z 轴负方向

4. 裁剪坐标系(Clip Space):

  • 投影后的坐标系
  • 范围通常是 [-1, 1]

5. 屏幕坐标系(Screen Space):

  • 屏幕像素坐标系
  • 以屏幕左上角为原点

变换矩阵:

模型矩阵(Model Matrix):

M = T * R * S
T:平移矩阵
R:旋转矩阵
S:缩放矩阵

视图矩阵(View Matrix):

V = lookAt(eye, center, up)
eye:摄像机位置
center:观察点
up:上方向向量

投影矩阵(Projection Matrix):

  • 正交投影:平行投影,物体大小不变
  • 透视投影:符合人眼视觉,远小近大

MVP 矩阵:

MVP = P * V * M
M:模型矩阵
V:视图矩阵
P:投影矩阵

OpenGL 基础

什么是 OpenGL?

OpenGL(Open Graphics Library)是一个跨平台的图形 API,用于渲染 2D 和 3D 图形。

OpenGL 的特点:

  • 跨平台:Windows、Linux、macOS
  • 硬件加速:利用 GPU 进行渲染
  • 状态机:通过状态控制渲染行为
  • 着色器语言:GLSL(OpenGL Shading Language)

OpenGL 版本

OpenGL 版本:

  • OpenGL 2.x:固定管线
  • **OpenGL 3.x+**:可编程管线
  • OpenGL ES:移动平台版本
  • WebGL:浏览器中的 OpenGL

OpenGL 基本概念

上下文(Context):

  • OpenGL 的状态集合
  • 一个线程只有一个上下文

顶点缓冲对象(VBO,Vertex Buffer Object):

  • 存储顶点数据
  • 在 GPU 内存中

顶点数组对象(VAO,Vertex Array Object):

  • 封装 VBO 和属性配置
  • OpenGL 3.0+ 支持

索引缓冲对象(IBO/EBO,Index Buffer Object / Element Buffer Object):

  • 存储顶点索引
  • 避免重复顶点

纹理(Texture):

  • 存储图像数据
  • 用于贴图

帧缓冲区(Frame Buffer):

  • 存储渲染结果
  • 可以渲染到纹理

OpenGL 渲染流程

基本渲染流程:

// 1. 创建并绑定 VAO
GLuint VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);

// 2. 创建并绑定 VBO
GLuint VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

// 3. 配置顶点属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

// 4. 编译着色器
GLuint shaderProgram = createShaderProgram(vertexShaderSource, fragmentShaderSource);

// 5. 渲染循环
while (!glfwWindowShouldClose(window)) {
    glUseProgram(shaderProgram);
    glBindVertexArray(VAO);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    
    glfwSwapBuffers(window);
    glfwPollEvents();
}

GLSL 着色器语言

顶点着色器示例:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

out vec3 FragColor;

void main() {
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    FragColor = aColor;
}

片段着色器示例:

#version 330 core
in vec3 FragColor;
out vec4 FragColorOut;

void main() {
    FragColorOut = vec4(FragColor, 1.0);
}

OpenGL 常用函数

缓冲区操作:

glGenBuffers(GLsizei n, GLuint* buffers);  // 生成缓冲区
glBindBuffer(GLenum target, GLuint buffer);  // 绑定缓冲区
glBufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage);  // 传输数据
glDeleteBuffers(GLsizei n, const GLuint* buffers);  // 删除缓冲区

着色器操作:

GLuint glCreateShader(GLenum shaderType);  // 创建着色器
glShaderSource(GLuint shader, GLsizei count, const GLchar** string, const GLint* length);  // 加载源码
glCompileShader(GLuint shader);  // 编译着色器
GLuint glCreateProgram(void);  // 创建程序
glAttachShader(GLuint program, GLuint shader);  // 附加着色器
glLinkProgram(GLuint program);  // 链接着色器程序
glUseProgram(GLuint program);  // 使用程序

绘制函数:

glDrawArrays(GLenum mode, GLint first, GLsizei count);  // 绘制数组
glDrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices);  // 绘制索引

VTK 基础

什么是 VTK?

VTK(Visualization Toolkit)是一个用于 3D 计算机图形学、图像处理和可视化的开源软件系统。

VTK 的特点:

  • 强大的可视化功能:支持多种可视化算法
  • 科学计算可视化:医学影像、工程分析
  • 跨平台:Windows、Linux、macOS
  • 多语言绑定:C++、Python、Java

VTK 架构

VTK 的架构:

  • 数据模型层:vtkDataSet 及其派生类
  • 算法层:vtkAlgorithm 及其派生类
  • 可视化管道:Source → Filter → Mapper → Actor → Renderer → RenderWindow

VTK 管道(Pipeline):

数据源(Source)
    ↓
过滤器(Filter)
    ↓
映射器(Mapper)
    ↓
演员(Actor)
    ↓
渲染器(Renderer)
    ↓
渲染窗口(RenderWindow)

VTK 基本类

数据类:

  • vtkPolyData:多边形数据(点、线、三角形)
  • vtkImageData:图像数据
  • vtkStructuredGrid:结构化网格
  • vtkUnstructuredGrid:非结构化网格

算法类:

  • vtkSphereSource:生成球体
  • vtkCylinderSource:生成圆柱体
  • vtkMarchingCubes:等值面提取
  • vtkContourFilter:等值线提取

映射器类:

  • vtkPolyDataMapper:多边形数据映射器
  • vtkDataSetMapper:数据集映射器

演员类:

  • vtkActor:3D 演员
  • vtkActor2D:2D 演员

渲染类:

  • vtkRenderer:渲染器
  • vtkRenderWindow:渲染窗口
  • vtkRenderWindowInteractor:交互器

VTK 基本示例

简单渲染示例:

#include <vtkSphereSource.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>

int main() {
    // 创建球体数据源
    vtkSmartPointer<vtkSphereSource> sphereSource = 
        vtkSmartPointer<vtkSphereSource>::New();
    sphereSource->SetRadius(1.0);
    sphereSource->SetThetaResolution(50);
    sphereSource->SetPhiResolution(50);
    
    // 创建映射器
    vtkSmartPointer<vtkPolyDataMapper> mapper = 
        vtkSmartPointer<vtkPolyDataMapper>::New();
    mapper->SetInputConnection(sphereSource->GetOutputPort());
    
    // 创建演员
    vtkSmartPointer<vtkActor> actor = 
        vtkSmartPointer<vtkActor>::New();
    actor->SetMapper(mapper);
    
    // 创建渲染器
    vtkSmartPointer<vtkRenderer> renderer = 
        vtkSmartPointer<vtkRenderer>::New();
    renderer->AddActor(actor);
    renderer->SetBackground(0.1, 0.2, 0.3);
    
    // 创建渲染窗口
    vtkSmartPointer<vtkRenderWindow> renderWindow = 
        vtkSmartPointer<vtkRenderWindow>::New();
    renderWindow->AddRenderer(renderer);
    renderWindow->SetSize(800, 600);
    
    // 创建交互器
    vtkSmartPointer<vtkRenderWindowInteractor> interactor = 
        vtkSmartPointer<vtkRenderWindowInteractor>::New();
    interactor->SetRenderWindow(renderWindow);
    
    // 开始渲染
    renderWindow->Render();
    interactor->Start();
    
    return 0;
}

VTK 读取和显示图像

#include <vtkImageReader2.h>
#include <vtkImageActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>

int main() {
    // 读取图像
    vtkSmartPointer<vtkImageReader2> reader = 
        vtkSmartPointer<vtkImageReader2>::New();
    reader->SetFileName("image.png");
    reader->Update();
    
    // 创建图像演员
    vtkSmartPointer<vtkImageActor> actor = 
        vtkSmartPointer<vtkImageActor>::New();
    actor->SetInputData(reader->GetOutput());
    
    // 创建渲染器
    vtkSmartPointer<vtkRenderer> renderer = 
        vtkSmartPointer<vtkRenderer>::New();
    renderer->AddActor(actor);
    
    // 创建渲染窗口
    vtkSmartPointer<vtkRenderWindow> renderWindow = 
        vtkSmartPointer<vtkRenderWindow>::New();
    renderWindow->AddRenderer(renderer);
    
    // 创建交互器
    vtkSmartPointer<vtkRenderWindowInteractor> interactor = 
        vtkSmartPointer<vtkRenderWindowInteractor>::New();
    interactor->SetRenderWindow(renderWindow);
    
    renderWindow->Render();
    interactor->Start();
    
    return 0;
}

OpenCV 基础

什么是 OpenCV?

OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习软件库。

OpenCV 的主要功能:

  • 图像处理:滤波、变换、形态学操作
  • 特征检测:角点检测、边缘检测
  • 目标跟踪:运动跟踪、目标识别
  • 机器学习:分类、聚类
  • 相机标定:相机参数校准

OpenCV 基本数据结构

Mat(矩阵):

cv::Mat image;  // 图像矩阵
cv::Mat image(height, width, CV_8UC3);  // 创建指定大小的图像
cv::Mat gray;  // 灰度图像

Point(点):

cv::Point2i pt1(10, 20);  // 2D 整数点
cv::Point2f pt2(10.5f, 20.5f);  // 2D 浮点
cv::Point3f pt3(10.0f, 20.0f, 30.0f);  // 3D 点

Rect(矩形):

cv::Rect rect(10, 20, 100, 200);  // (x, y, width, height)

Scalar(标量):

cv::Scalar color(255, 0, 0);  // BGR 颜色

OpenCV 图像操作

读取和显示图像:

#include <opencv2/opencv.hpp>

int main() {
    // 读取图像
    cv::Mat image = cv::imread("image.jpg");
    
    if (image.empty()) {
        std::cerr << "Failed to load image" << std::endl;
        return -1;
    }
    
    // 显示图像
    cv::imshow("Image", image);
    cv::waitKey(0);
    
    // 保存图像
    cv::imwrite("output.jpg", image);
    
    return 0;
}

图像基本操作:

// 图像信息
int width = image.cols;
int height = image.rows;
int channels = image.channels();

// 访问像素
cv::Vec3b pixel = image.at<cv::Vec3b>(y, x);  // BGR
unsigned char blue = pixel[0];
unsigned char green = pixel[1];
unsigned char red = pixel[2];

// 修改像素
image.at<cv::Vec3b>(y, x) = cv::Vec3b(255, 0, 0);

// 图像缩放
cv::Mat resized;
cv::resize(image, resized, cv::Size(640, 480));

// 图像裁剪
cv::Rect roi(100, 100, 200, 200);
cv::Mat cropped = image(roi);

OpenCV 图像处理

颜色空间转换:

cv::Mat gray;
cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);

cv::Mat hsv;
cv::cvtColor(image, hsv, cv::COLOR_BGR2HSV);

图像滤波:

// 高斯模糊
cv::Mat blurred;
cv::GaussianBlur(image, blurred, cv::Size(5, 5), 1.0);

// 中值滤波
cv::Mat median;
cv::medianBlur(image, median, 5);

// 双边滤波
cv::Mat bilateral;
cv::bilateralFilter(image, bilateral, 9, 75, 75);

边缘检测:

// Canny 边缘检测
cv::Mat edges;
cv::Canny(gray, edges, 50, 150);

// Sobel 边缘检测
cv::Mat sobelX, sobelY;
cv::Sobel(gray, sobelX, CV_16S, 1, 0, 3);
cv::Sobel(gray, sobelY, CV_16S, 0, 1, 3);

形态学操作:

cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));

// 腐蚀
cv::Mat eroded;
cv::erode(image, eroded, kernel);

// 膨胀
cv::Mat dilated;
cv::dilate(image, dilated, kernel);

// 开运算(先腐蚀后膨胀)
cv::Mat opened;
cv::morphologyEx(image, opened, cv::MORPH_OPEN, kernel);

// 闭运算(先膨胀后腐蚀)
cv::Mat closed;
cv::morphologyEx(image, closed, cv::MORPH_CLOSE, kernel);

特征检测:

// 角点检测(Harris)
cv::Mat corners;
cv::cornerHarris(gray, corners, 2, 3, 0.04);

// 关键点检测(ORB)
std::vector<cv::KeyPoint> keypoints;
cv::Mat descriptors;
cv::Ptr<cv::ORB> detector = cv::ORB::create();
detector->detectAndCompute(gray, cv::Mat(), keypoints, descriptors);

C++ 三维图形可视化

三维数据结构

点云(Point Cloud):

struct Point3D {
    float x, y, z;
    float r, g, b;  // 颜色
    float nx, ny, nz;  // 法向量
};

class PointCloud {
public:
    std::vector<Point3D> points;
    
    void loadFromFile(const std::string& filename);
    void render();
};

网格(Mesh):

struct Vertex {
    glm::vec3 position;
    glm::vec3 normal;
    glm::vec2 texCoord;
};

struct Mesh {
    std::vector<Vertex> vertices;
    std::vector<unsigned int> indices;
    
    void loadFromOBJ(const std::string& filename);
    void render();
};

三维变换

使用 GLM 库进行矩阵运算:

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

// 模型矩阵
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(1.0f, 0.0f, 0.0f));
model = glm::rotate(model, glm::radians(45.0f), glm::vec3(0.0f, 1.0f, 0.0f));
model = glm::scale(model, glm::vec3(1.5f, 1.5f, 1.5f));

// 视图矩阵
glm::mat4 view = glm::lookAt(
    glm::vec3(0.0f, 0.0f, 5.0f),  // 摄像机位置
    glm::vec3(0.0f, 0.0f, 0.0f),  // 观察点
    glm::vec3(0.0f, 1.0f, 0.0f)   // 上方向
);

// 投影矩阵
glm::mat4 projection = glm::perspective(
    glm::radians(45.0f),           // FOV(视野角度)
    (float)width / (float)height,  // 宽高比
    0.1f,                          // 近平面
    100.0f                         // 远平面
);

// MVP 矩阵
glm::mat4 mvp = projection * view * model;

三维渲染优化

视锥体剔除(Frustum Culling):

bool isInFrustum(const glm::vec3& position, const glm::vec3& size, 
                 const Frustum& frustum) {
    // 检查包围盒是否在视锥体内
    for (int i = 0; i < 6; i++) {
        float distance = frustum.planes[i].distanceToPoint(position);
        if (distance < -size.length()) {
            return false;  // 在视锥体外
        }
    }
    return true;  // 在视锥体内
}

遮挡剔除(Occlusion Culling):

// 使用遮挡查询(Occlusion Query)
GLuint query;
glGenQueries(1, &query);
glBeginQuery(GL_SAMPLES_PASSED, query);
// 绘制包围盒
drawBoundingBox();
glEndQuery(GL_SAMPLES_PASSED);

GLuint samples;
glGetQueryObjectuiv(query, GL_QUERY_RESULT, &samples);
if (samples > 0) {
    // 物体可见,绘制详细模型
    drawDetailedModel();
}

LOD(Level of Detail):

class LODManager {
public:
    enum LODLevel {
        HIGH,   // 高细节
        MEDIUM, // 中等细节
        LOW,    // 低细节
        BILLBOARD // 广告牌
    };
    
    LODLevel getLODLevel(const glm::vec3& position, const Camera& camera) {
        float distance = glm::distance(position, camera.getPosition());
        
        if (distance < 10.0f) return HIGH;
        else if (distance < 50.0f) return MEDIUM;
        else if (distance < 100.0f) return LOW;
        else return BILLBOARD;
    }
    
    void render(const glm::vec3& position, const Camera& camera) {
        LODLevel level = getLODLevel(position, camera);
        switch (level) {
            case HIGH:
                renderHighDetail();
                break;
            case MEDIUM:
                renderMediumDetail();
                break;
            case LOW:
                renderLowDetail();
                break;
            case BILLBOARD:
                renderBillboard();
                break;
        }
    }
};

实例化渲染(Instancing):

// 使用实例化数组
struct InstanceData {
    glm::mat4 model;
    glm::vec3 color;
};

std::vector<InstanceData> instances;

// 创建实例化 VBO
GLuint instanceVBO;
glGenBuffers(1, &instanceVBO);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glBufferData(GL_ARRAY_BUFFER, instances.size() * sizeof(InstanceData),
             instances.data(), GL_DYNAMIC_DRAW);

// 设置实例属性
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(InstanceData),
                      (void*)offsetof(InstanceData, model));
glVertexAttribDivisor(2, 1);  // 每个实例一个属性

// 实例化绘制
glDrawArraysInstanced(GL_TRIANGLES, 0, vertexCount, instances.size());

加速优化部署技术

GPU 加速计算

CUDA(Compute Unified Device Architecture):

// CUDA 核函数示例
__global__ void vectorAdd(float* A, float* B, float* C, int N) {
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (i < N) {
        C[i] = A[i] + B[i];
    }
}

// 主机端调用
int main() {
    int N = 10000;
    size_t size = N * sizeof(float);
    
    // 分配主机内存
    float *h_A = (float*)malloc(size);
    float *h_B = (float*)malloc(size);
    float *h_C = (float*)malloc(size);
    
    // 分配设备内存
    float *d_A, *d_B, *d_C;
    cudaMalloc(&d_A, size);
    cudaMalloc(&d_B, size);
    cudaMalloc(&d_C, size);
    
    // 复制数据到设备
    cudaMemcpy(d_A, h_A, size, cudaMemcpyHostToDevice);
    cudaMemcpy(d_B, h_B, size, cudaMemcpyHostToDevice);
    
    // 启动核函数
    int threadsPerBlock = 256;
    int blocksPerGrid = (N + threadsPerBlock - 1) / threadsPerBlock;
    vectorAdd<<<blocksPerGrid, threadsPerBlock>>>(d_A, d_B, d_C, N);
    
    // 复制结果回主机
    cudaMemcpy(h_C, d_C, size, cudaMemcpyDeviceToHost);
    
    // 清理
    cudaFree(d_A);
    cudaFree(d_B);
    cudaFree(d_C);
    free(h_A);
    free(h_B);
    free(h_C);
    
    return 0;
}

OpenCL(Open Computing Language):

// OpenCL 示例
#include <CL/cl.hpp>

int main() {
    // 获取平台和设备
    std::vector<cl::Platform> platforms;
    cl::Platform::get(&platforms);
    
    std::vector<cl::Device> devices;
    platforms[0].getDevices(CL_DEVICE_TYPE_GPU, &devices);
    
    // 创建上下文和命令队列
    cl::Context context(devices);
    cl::CommandQueue queue(context, devices[0]);
    
    // 创建程序
    std::string kernelCode = R"(
        __kernel void vectorAdd(__global float* A,
                                __global float* B,
                                __global float* C) {
            int i = get_global_id(0);
            C[i] = A[i] + B[i];
        }
    )";
    
    cl::Program program(context, kernelCode);
    program.build(devices);
    
    // 创建核函数
    cl::Kernel kernel(program, "vectorAdd");
    
    // 分配缓冲区
    int N = 10000;
    size_t size = N * sizeof(float);
    cl::Buffer bufferA(context, CL_MEM_READ_ONLY, size);
    cl::Buffer bufferB(context, CL_MEM_READ_ONLY, size);
    cl::Buffer bufferC(context, CL_MEM_WRITE_ONLY, size);
    
    // 设置参数
    kernel.setArg(0, bufferA);
    kernel.setArg(1, bufferB);
    kernel.setArg(2, bufferC);
    
    // 执行核函数
    queue.enqueueNDRangeKernel(kernel, cl::NullRange, 
                                cl::NDRange(N), cl::NullRange);
    queue.finish();
    
    return 0;
}

多线程并行渲染

OpenMP 并行处理:

#include <omp.h>

// 并行处理图像
void processImageParallel(cv::Mat& image) {
    int rows = image.rows;
    int cols = image.cols;
    
    #pragma omp parallel for
    for (int y = 0; y < rows; y++) {
        for (int x = 0; x < cols; x++) {
            cv::Vec3b& pixel = image.at<cv::Vec3b>(y, x);
            // 处理像素
            pixel[0] = 255 - pixel[0];  // 反色
            pixel[1] = 255 - pixel[1];
            pixel[2] = 255 - pixel[2];
        }
    }
}

C++11 线程池:

#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>

class ThreadPool {
public:
    ThreadPool(size_t numThreads) : stop(false) {
        for (size_t i = 0; i < numThreads; i++) {
            workers.emplace_back([this] {
                while (true) {
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock(this->queue_mutex);
                        this->condition.wait(lock, [this] {
                            return this->stop || !this->tasks.empty();
                        });
                        
                        if (this->stop && this->tasks.empty()) {
                            return;
                        }
                        
                        task = std::move(this->tasks.front());
                        this->tasks.pop();
                    }
                    task();
                }
            });
        }
    }
    
    template<class F>
    void enqueue(F&& f) {
        {
            std::unique_lock<std::mutex> lock(queue_mutex);
            tasks.emplace(std::forward<F>(f));
        }
        condition.notify_one();
    }
    
    ~ThreadPool() {
        {
            std::unique_lock<std::mutex> lock(queue_mutex);
            stop = true;
        }
        condition.notify_all();
        for (std::thread &worker : workers) {
            worker.join();
        }
    }
    
private:
    std::vector<std::thread> workers;
    std::queue<std::function<void()>> tasks;
    std::mutex queue_mutex;
    std::condition_variable condition;
    bool stop;
};

内存优化

内存池(Memory Pool):

template<typename T>
class MemoryPool {
public:
    MemoryPool(size_t blockSize, size_t numBlocks) 
        : blockSize(blockSize), numBlocks(numBlocks) {
        pool = static_cast<char*>(malloc(blockSize * numBlocks));
        freeList = new std::stack<void*>();
        
        // 初始化空闲列表
        for (size_t i = 0; i < numBlocks; i++) {
            freeList->push(pool + i * blockSize);
        }
    }
    
    void* allocate() {
        std::lock_guard<std::mutex> lock(mutex);
        if (freeList->empty()) {
            return nullptr;  // 池已满
        }
        void* ptr = freeList->top();
        freeList->pop();
        return ptr;
    }
    
    void deallocate(void* ptr) {
        std::lock_guard<std::mutex> lock(mutex);
        freeList->push(ptr);
    }
    
    ~MemoryPool() {
        free(pool);
        delete freeList;
    }
    
private:
    char* pool;
    size_t blockSize;
    size_t numBlocks;
    std::stack<void*>* freeList;
    std::mutex mutex;
};

对象池(Object Pool):

template<typename T>
class ObjectPool {
public:
    template<typename... Args>
    std::shared_ptr<T> acquire(Args... args) {
        std::lock_guard<std::mutex> lock(mutex);
        
        if (!pool.empty()) {
            auto obj = pool.top();
            pool.pop();
            return std::shared_ptr<T>(obj, [this](T* ptr) {
                this->release(ptr);
            });
        } else {
            auto obj = new T(args...);
            return std::shared_ptr<T>(obj, [this](T* ptr) {
                this->release(ptr);
            });
        }
    }
    
private:
    void release(T* obj) {
        std::lock_guard<std::mutex> lock(mutex);
        obj->reset();  // 重置对象状态
        pool.push(obj);
    }
    
    std::stack<T*> pool;
    std::mutex mutex;
};

性能分析工具

Profiling(性能分析):

// 使用计时器
#include <chrono>

class Timer {
public:
    Timer() : start(std::chrono::high_resolution_clock::now()) {}
    
    ~Timer() {
        auto end = std::chrono::high_resolution_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::milliseconds>
                       (end - start);
        std::cout << "Time: " << duration.count() << " ms" << std::endl;
    }
    
private:
    std::chrono::high_resolution_clock::time_point start;
};

// 使用
{
    Timer timer;
    // 执行代码
    renderScene();
}

常见面试题

1. 什么是渲染管线(Graphics Pipeline)?

答案:
渲染管线是将 3D 场景转换为 2D 图像的过程。

主要阶段:

  1. 顶点着色器:处理顶点坐标变换
  2. 图元装配:将顶点组装成图元(点、线、三角形)
  3. 几何着色器(可选):处理图元,可以生成新图元
  4. 光栅化:将图元转换为片段(Fragment)
  5. 片段着色器:计算每个片段的颜色
  6. 深度测试:确定可见性
  7. 混合:将新颜色与已有颜色混合

2. MVP 矩阵是什么?

答案:
MVP 矩阵是三个矩阵的组合:

  • M(Model Matrix):模型矩阵,将物体从模型空间变换到世界空间
  • V(View Matrix):视图矩阵,将世界空间变换到摄像机空间
  • P(Projection Matrix):投影矩阵,将摄像机空间变换到裁剪空间
MVP = P * V * M

3. 正交投影和透视投影的区别?

答案:

特性正交投影透视投影
投影方式平行投影中心投影
物体大小不变(与距离无关)变化(远小近大)
适用场景CAD、工程图游戏、电影
实现glOrthoglPerspective

正交投影:

glm::ortho(left, right, bottom, top, near, far);

透视投影:

glm::perspective(fov, aspect, near, far);

4. 什么是深度测试(Depth Test)?

答案:
深度测试用于确定像素的可见性,通过比较片段的深度值(Z 值)来决定是否绘制。

深度测试函数:

  • GL_LESS:默认,深度值小于缓冲区的才绘制
  • GL_LEQUAL:深度值小于等于缓冲区的绘制
  • GL_GREATER:深度值大于缓冲区的绘制
  • GL_ALWAYS:总是绘制(禁用深度测试)
  • GL_NEVER:从不绘制

使用:

glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);

深度冲突(Z-fighting):
当两个表面距离很近时,会出现深度冲突,可以通过:

  1. 增加深度缓冲精度
  2. 增加近远平面距离
  3. 避免重叠的几何体

5. 什么是纹理映射(Texture Mapping)?

答案:
纹理映射是将 2D 图像贴到 3D 物体表面的技术。

纹理坐标(UV 坐标):

  • U:水平方向(0.0 到 1.0)
  • V:垂直方向(0.0 到 1.0)

纹理过滤:

  • GL_NEAREST:最近邻过滤,像素化效果
  • GL_LINEAR:线性过滤,平滑效果
  • Mipmap:多级渐远纹理,提高性能

纹理环绕:

  • GL_REPEAT:重复纹理
  • GL_MIRRORED_REPEAT:镜像重复
  • GL_CLAMP_TO_EDGE:边缘拉伸
  • GL_CLAMP_TO_BORDER:边框颜色

6. OpenGL 中 VBO、VAO、EBO 的区别?

答案:

  • VBO(Vertex Buffer Object):存储顶点数据(位置、颜色、法向量等)
  • VAO(Vertex Array Object):封装 VBO 和顶点属性配置
  • EBO/IBO(Element Buffer Object / Index Buffer Object):存储顶点索引,避免重复顶点

使用流程:

// 1. 创建并绑定 VAO
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);

// 2. 创建并绑定 VBO
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

// 3. 创建并绑定 EBO
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

// 4. 配置顶点属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

7. 什么是双缓冲(Double Buffering)?

答案:
双缓冲使用两个缓冲区来避免闪烁:

  • 前台缓冲区(Front Buffer):当前显示的图像
  • 后台缓冲区(Back Buffer):正在渲染的图像

工作流程:

  1. 在后台缓冲区渲染场景
  2. 渲染完成后交换前后缓冲区
  3. 显示后台缓冲区的内容

优点:

  • 避免闪烁
  • 流畅的动画效果

8. 什么是视锥体剔除(Frustum Culling)?

答案:
视锥体剔除是剔除不在摄像机视野范围内的物体,减少渲染的几何体数量。

视锥体:

  • 由近平面、远平面和四个侧面组成
  • 只有在这个锥体内的物体才可见

实现方法:

  1. 包围盒检测:计算物体的包围盒,检查是否与视锥体相交
  2. 层次包围盒(Bounding Volume Hierarchy):使用树结构加速检测

9. 什么是 LOD(Level of Detail)?

答案:
LOD 是根据物体到摄像机的距离使用不同细节级别的模型。

LOD 级别:

  • 高细节:近距离,使用完整模型
  • 中等细节:中距离,使用简化模型
  • 低细节:远距离,使用更简化的模型
  • 广告牌:很远的距离,使用单面片

优点:

  • 减少渲染的三角形数量
  • 提高帧率
  • 保持视觉效果

缺点:

  • 需要准备多个模型
  • LOD 切换可能可见

10. 什么是实例化渲染(Instancing)?

答案:
实例化渲染是一次绘制多个相同几何体的技术,避免重复的顶点数据。

优点:

  • 减少 Draw Call
  • 减少 CPU 开销
  • 提高渲染性能

应用场景:

  • 绘制大量相同物体(树木、粒子等)
  • 场景中的重复元素

11. OpenGL 中的着色器(Shader)是什么?

答案:
着色器是在 GPU 上运行的小程序,用于控制渲染过程。

着色器类型:

1. 顶点着色器(Vertex Shader):

  • 处理每个顶点
  • 进行坐标变换
  • 计算顶点颜色、法向量等

2. 片段着色器(Fragment Shader):

  • 处理每个片段(像素)
  • 计算最终颜色
  • 进行纹理采样、光照计算

3. 几何着色器(Geometry Shader):

  • 处理图元(点、线、三角形)
  • 可以生成新的图元
  • 可以丢弃图元

4. 曲面细分着色器(Tessellation Shader):

  • OpenGL 4.0+ 支持
  • 动态增加几何体细节

5. 计算着色器(Compute Shader):

  • OpenGL 4.3+ 支持
  • 通用计算,不限于图形

12. VTK 的管道(Pipeline)是什么?

答案:
VTK 管道是数据处理和可视化的流水线,数据通过多个阶段处理。

VTK 管道组成:

数据源(Source)
    ↓
过滤器(Filter)
    ↓
映射器(Mapper)
    ↓
演员(Actor)
    ↓
渲染器(Renderer)
    ↓
渲染窗口(RenderWindow)

数据源(Source):

  • 生成或读取数据
  • 例如:vtkSphereSource, vtkImageReader2

过滤器(Filter):

  • 处理数据
  • 例如:vtkMarchingCubes, vtkContourFilter

映射器(Mapper):

  • 将数据映射为图元
  • 例如:vtkPolyDataMapper, vtkDataSetMapper

演员(Actor):

  • 3D 场景中的对象
  • 包含映射器和属性(颜色、材质等)

渲染器(Renderer):

  • 管理场景
  • 包含多个演员和灯光

渲染窗口(RenderWindow):

  • 显示渲染结果
  • 管理多个渲染器

13. VTK 的数据结构有哪些?

答案:

1. vtkDataSet(数据集基类):

  • 所有数据集的基类
  • 定义基本接口

2. vtkPolyData(多边形数据):

  • 存储点、线、三角形
  • 用于不规则网格

3. vtkImageData(图像数据):

  • 存储规则网格数据
  • 用于图像和体积数据

4. vtkStructuredGrid(结构化网格):

  • 规则拓扑,不规则几何
  • 用于流场数据

5. vtkUnstructuredGrid(非结构化网格):

  • 不规则拓扑和几何
  • 用于有限元分析

6. vtkRectilinearGrid(直线网格):

  • 坐标轴是直线
  • 用于规则采样数据

14. OpenCV 的主要功能有哪些?

答案:

1. 图像处理:

  • 滤波:高斯、中值、双边滤波
  • 变换:几何变换、仿射变换
  • 形态学:腐蚀、膨胀、开闭运算

2. 特征检测:

  • 角点检测:Harris、Shi-Tomasi
  • 边缘检测:Canny、Sobel
  • 关键点:SIFT、SURF、ORB

3. 目标跟踪:

  • 运动跟踪:光流法、卡尔曼滤波
  • 目标识别:模板匹配、特征匹配

4. 相机标定:

  • 相机参数校准
  • 畸变校正
  • 立体视觉

5. 机器学习:

  • 分类:SVM、KNN
  • 聚类:K-means
  • 深度学习:DNN 模块

15. OpenCV 中的 Mat 是什么?

答案:
Mat 是 OpenCV 中表示图像的矩阵数据结构。

Mat 的特点:

  • 自动内存管理:不需要手动释放内存
  • 引用计数:多个 Mat 可以共享同一块内存
  • 多通道支持:可以表示单通道、多通道图像
  • 多种数据类型:CV_8U, CV_16U, CV_32F 等

Mat 的创建:

// 创建空 Mat
cv::Mat image;

// 创建指定大小的 Mat
cv::Mat image(height, width, CV_8UC3);  // 3 通道,8 位无符号整数

// 从文件读取
cv::Mat image = cv::imread("image.jpg");

// 创建并初始化
cv::Mat image = cv::Mat::zeros(height, width, CV_8UC3);
cv::Mat image = cv::Mat::ones(height, width, CV_8UC1) * 255;

Mat 的内存管理:

cv::Mat image1 = cv::imread("image.jpg");
cv::Mat image2 = image1;  // 共享内存,浅拷贝
cv::Mat image3 = image1.clone();  // 深拷贝

16. 什么是 CUDA?如何使用 CUDA 加速计算?

答案:
CUDA(Compute Unified Device Architecture)是 NVIDIA 的并行计算平台和编程模型。

CUDA 的特点:

  • GPU 并行计算:利用 GPU 的并行处理能力
  • C/C++ 扩展:扩展 C/C++ 语言
  • 自动内存管理:简化内存操作

CUDA 内存类型:

  1. 全局内存(Global Memory):所有线程可访问
  2. 共享内存(Shared Memory):同一线程块内共享
  3. 寄存器(Registers):线程私有
  4. 常量内存(Constant Memory):只读,缓存
  5. 纹理内存(Texture Memory):只读,缓存

CUDA 编程模型:

  • Grid:由多个 Block 组成
  • Block:由多个 Thread 组成
  • Thread:执行的最小单位

17. 什么是 OpenCL?它与 CUDA 的区别?

答案:
OpenCL(Open Computing Language)是跨平台的并行计算框架。

OpenCL vs CUDA:

特性CUDAOpenCL
平台NVIDIA GPU多平台(GPU、CPU、FPGA)
语言C/C++ 扩展C 语言扩展
性能针对 NVIDIA 优化通用,性能略低
生态工具丰富工具较少
学习曲线相对容易相对复杂

OpenCL 的优势:

  • 跨平台:支持多种硬件
  • 开放标准:不受厂商限制
  • 通用性:可以在多种设备上运行

CUDA 的优势:

  • 性能:针对 NVIDIA GPU 优化
  • 工具:Nsight、NVIDIA Nsight 等
  • 生态:库和框架丰富

18. 如何进行三维渲染优化?

答案:

1. 减少 Draw Call:

  • 合并网格(Mesh Combining)
  • 使用实例化渲染
  • 批处理绘制

2. 减少三角形数量:

  • 使用 LOD(细节级别)
  • 网格简化(Mesh Simplification)
  • 剔除不可见面(Backface Culling)

3. 视锥体剔除:

  • 只渲染摄像机视野内的物体
  • 使用层次包围盒(BVH)加速

4. 遮挡剔除:

  • 使用遮挡查询
  • 层次 Z 缓冲(Hierarchical Z-Buffer)

5. 纹理优化:

  • 使用 Mipmap
  • 压缩纹理
  • 纹理图集(Texture Atlas)

6. 着色器优化:

  • 减少着色器指令数
  • 避免分支(Branch)
  • 使用内置函数

7. 内存优化:

  • 减少内存分配
  • 使用对象池
  • 缓存常用数据

19. 什么是帧缓冲区(Frame Buffer)?

答案:
帧缓冲区是存储渲染结果的内存区域。

帧缓冲区组成:

  • 颜色缓冲区(Color Buffer):存储颜色信息
  • 深度缓冲区(Depth Buffer / Z-Buffer):存储深度信息
  • 模板缓冲区(Stencil Buffer):存储模板信息

帧缓冲对象(FBO):

  • 可以创建离屏渲染目标
  • 可以渲染到纹理
  • 用于后处理效果

使用示例:

// 创建帧缓冲对象
GLuint FBO;
glGenFramebuffers(1, &FBO);
glBindFramebuffer(GL_FRAMEBUFFER, FBO);

// 创建纹理作为颜色附件
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);

// 创建渲染缓冲对象作为深度附件
GLuint RBO;
glGenRenderbuffers(1, &RBO);
glBindRenderbuffer(GL_RENDERBUFFER, RBO);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, RBO);

// 检查完整性
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
    std::cerr << "Framebuffer is not complete!" << std::endl;
}

// 渲染到帧缓冲
glBindFramebuffer(GL_FRAMEBUFFER, FBO);
renderScene();
glBindFramebuffer(GL_FRAMEBUFFER, 0);

// 使用纹理
glBindTexture(GL_TEXTURE_2D, texture);

20. 什么是后处理(Post Processing)?

答案:
后处理是在渲染完成后对图像进行处理的效果。

常见的后处理效果:

1. 高斯模糊(Gaussian Blur):

// 高斯模糊着色器
uniform sampler2D screenTexture;

void main() {
    vec2 offsets[9] = vec2[](
        vec2(-offset.x,  offset.y), vec2( 0.0f,    offset.y), vec2( offset.x,  offset.y),
        vec2(-offset.x,  0.0f),     vec2( 0.0f,    0.0f),     vec2( offset.x,  0.0f),
        vec2(-offset.x, -offset.y), vec2( 0.0f,   -offset.y), vec2( offset.x, -offset.y)
    );
    
    float kernel[9] = float[](
        1.0/16, 2.0/16, 1.0/16,
        2.0/16, 4.0/16, 2.0/16,
        1.0/16, 2.0/16, 1.0/16
    );
    
    vec3 color = vec3(0.0);
    for (int i = 0; i < 9; i++) {
        color += texture(screenTexture, TexCoords + offsets[i]).rgb * kernel[i];
    }
    FragColor = vec4(color, 1.0);
}

2. 色调映射(Tone Mapping):

  • 将 HDR 图像转换为 LDR
  • 常用算法:Reinhard、ACES、Filmic

3. 泛光(Bloom):

  • 提取亮部,模糊后叠加
  • 产生光晕效果

4. 屏幕空间环境光遮蔽(SSAO):

  • 在屏幕空间计算环境光遮蔽
  • 增加细节和深度感

5. 景深(Depth of Field):

  • 模拟相机焦点效果
  • 前景和背景模糊

21. 如何实现 C++ 三维可视化?

答案:

1. 使用 OpenGL:

#include <GL/gl.h>
#include <GLFW/glfw3.h>

class Renderer {
public:
    void init() {
        // 初始化 OpenGL
        glEnable(GL_DEPTH_TEST);
        glEnable(GL_CULL_FACE);
        
        // 创建着色器程序
        shaderProgram = createShaderProgram(vertexShader, fragmentShader);
    }
    
    void render(const Scene& scene, const Camera& camera) {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glUseProgram(shaderProgram);
        
        // 设置 MVP 矩阵
        glm::mat4 mvp = camera.getProjectionMatrix() * 
                        camera.getViewMatrix() * 
                        scene.getModelMatrix();
        setUniform("mvp", mvp);
        
        // 渲染场景
        for (const auto& mesh : scene.getMeshes()) {
            renderMesh(mesh);
        }
    }
    
private:
    GLuint shaderProgram;
    void renderMesh(const Mesh& mesh);
    void setUniform(const std::string& name, const glm::mat4& value);
};

2. 使用 VTK:

#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>

class Visualizer {
public:
    void init() {
        renderer = vtkSmartPointer<vtkRenderer>::New();
        renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
        interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New();
        
        renderWindow->AddRenderer(renderer);
        interactor->SetRenderWindow(renderWindow);
    }
    
    void addMesh(const vtkSmartPointer<vtkPolyData>& mesh) {
        vtkSmartPointer<vtkPolyDataMapper> mapper = 
            vtkSmartPointer<vtkPolyDataMapper>::New();
        mapper->SetInputData(mesh);
        
        vtkSmartPointer<vtkActor> actor = 
            vtkSmartPointer<vtkActor>::New();
        actor->SetMapper(mapper);
        
        renderer->AddActor(actor);
    }
    
    void render() {
        renderWindow->Render();
        interactor->Start();
    }
    
private:
    vtkSmartPointer<vtkRenderer> renderer;
    vtkSmartPointer<vtkRenderWindow> renderWindow;
    vtkSmartPointer<vtkRenderWindowInteractor> interactor;
};

3. 使用 OSG(OpenSceneGraph):

#include <osgViewer/Viewer>
#include <osg/Group>
#include <osg/Geode>
#include <osg/ShapeDrawable>

class Scene {
public:
    osg::ref_ptr<osg::Group> createScene() {
        osg::ref_ptr<osg::Group> root = new osg::Group();
        
        // 创建球体
        osg::ref_ptr<osg::Geode> geode = new osg::Geode();
        geode->addDrawable(new osg::ShapeDrawable(
            new osg::Sphere(osg::Vec3(0.0f, 0.0f, 0.0f), 1.0f)));
        
        root->addChild(geode);
        return root;
    }
    
    void run() {
        osgViewer::Viewer viewer;
        viewer.setSceneData(createScene());
        viewer.run();
    }
};

22. 如何进行图形程序的部署?

答案:

1. 静态链接 vs 动态链接:

静态链接:

  • 将所有库打包到可执行文件中
  • 文件较大,但部署简单
  • 不需要依赖外部库

动态链接:

  • 使用共享库(.dll、.so、.dylib)
  • 文件较小,但需要库文件
  • 需要配置库路径

CMake 配置:

# 静态链接
target_link_libraries(my_app ${CMAKE_SOURCE_DIR}/libs/libopengl.a)

# 动态链接
target_link_libraries(my_app opengl)

2. 依赖管理:

Windows 部署:

# 使用 vcpkg 管理依赖
vcpkg install opengl glfw3 glm

# 部署所需的 DLL 文件
copy /path/to/vcpkg/installed/x64-windows/bin/*.dll /path/to/output/

Linux 部署:

# 使用包管理器安装依赖
sudo apt-get install libgl1-mesa-dev libglfw3-dev

# 打包依赖库
ldd my_app  # 查看依赖的库

3. Docker 部署:

FROM ubuntu:20.04

# 安装依赖
RUN apt-get update && apt-get install -y \
    libgl1-mesa-dev \
    libglfw3-dev \
    libopencv-dev \
    && rm -rf /var/lib/apt/lists/*

# 复制程序文件
COPY my_app /usr/local/bin/

# 设置环境变量
ENV DISPLAY=:0

CMD ["my_app"]

4. 跨平台部署:

使用 CMake:

cmake_minimum_required(VERSION 3.10)
project(MyGraphicsApp)

# 查找依赖
find_package(OpenGL REQUIRED)
find_package(glfw3 REQUIRED)
find_package(OpenCV REQUIRED)

# 创建可执行文件
add_executable(my_app main.cpp)

# 链接库
target_link_libraries(my_app 
    ${OPENGL_LIBRARIES}
    glfw
    ${OpenCV_LIBS}
)

# 安装
install(TARGETS my_app DESTINATION bin)
install(FILES ${PROJECT_SOURCE_DIR}/assets DESTINATION assets)

23. 什么是光照模型?常见的光照模型有哪些?

答案:
光照模型用于计算物体表面的颜色,模拟光线与物体的相互作用。

常见的光照模型:

1. Phong 光照模型:

  • 环境光(Ambient):模拟全局光照
  • 漫反射(Diffuse):Lambert 定律
  • 镜面反射(Specular):Phong 反射

Phong 模型公式:

I = Ka * Ia + Kd * Id * (N · L) + Ks * Is * (R · V)^n

Ka:环境光系数
Kd:漫反射系数
Ks:镜面反射系数
Ia, Id, Is:环境光、漫反射、镜面反射强度
N:法向量
L:光线方向
R:反射方向
V:视线方向
n:高光指数

Phong 着色器实现:

#version 330 core
in vec3 FragPos;
in vec3 Normal;
in vec3 LightPos;

uniform vec3 objectColor;
uniform vec3 lightColor;
uniform vec3 viewPos;

out vec4 FragColor;

void main() {
    // 环境光
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * lightColor;
    
    // 漫反射
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(LightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = diff * lightColor;
    
    // 镜面反射
    float specularStrength = 0.5;
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
    vec3 specular = specularStrength * spec * lightColor;
    
    vec3 result = (ambient + diffuse + specular) * objectColor;
    FragColor = vec4(result, 1.0);
}

2. Blinn-Phong 光照模型:

  • 使用半角向量(Halfway Vector)代替反射向量
  • 计算更高效,效果类似

Blinn-Phong 公式:

I = Ka * Ia + Kd * Id * (N · L) + Ks * Is * (N · H)^n

H = normalize(L + V)  // 半角向量

3. PBR(Physically Based Rendering):

  • 基于物理的渲染:更真实的光照模型
  • 能量守恒:出射光不能超过入射光
  • 微表面理论:使用粗糙度(Roughness)和金属度(Metallic)

PBR 核心概念:

  • BRDF(Bidirectional Reflectance Distribution Function):双向反射分布函数
  • Fresnel 效应:不同角度反射率不同
  • 法线分布:微表面法线分布

24. 什么是阴影映射(Shadow Mapping)?

答案:
阴影映射是一种实现实时阴影的技术。

阴影映射步骤:

  1. 从光源视角渲染场景:生成深度贴图(Shadow Map)
  2. 从摄像机视角渲染场景:将片段位置变换到光源空间
  3. 比较深度:如果片段深度大于深度贴图中的深度,则在阴影中

阴影映射实现:

// 深度贴图生成着色器
#version 330 core
layout (location = 0) in vec3 aPos;

uniform mat4 lightSpaceMatrix;
uniform mat4 model;

void main() {
    gl_Position = lightSpaceMatrix * model * vec4(aPos, 1.0);
}

// 阴影计算片段着色器
#version 330 core
uniform sampler2D shadowMap;
uniform vec3 lightPos;
uniform vec3 viewPos;

float ShadowCalculation(vec4 fragPosLightSpace) {
    // 透视除法
    vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
    // 变换到 [0,1] 范围
    projCoords = projCoords * 0.5 + 0.5;
    
    // 获取最近深度
    float closestDepth = texture(shadowMap, projCoords.xy).r;
    // 获取当前深度
    float currentDepth = projCoords.z;
    
    // 检查是否在阴影中
    float shadow = currentDepth > closestDepth ? 1.0 : 0.0;
    
    return shadow;
}

PCF(Percentage Closer Filtering):

  • 软阴影技术
  • 对周围像素采样,平均阴影值
float ShadowCalculation(vec4 fragPosLightSpace) {
    vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
    projCoords = projCoords * 0.5 + 0.5;
    
    float shadow = 0.0;
    vec2 texelSize = 1.0 / textureSize(shadowMap, 0);
    for (int x = -1; x <= 1; ++x) {
        for (int y = -1; y <= 1; ++y) {
            float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r;
            shadow += currentDepth > pcfDepth ? 1.0 : 0.0;
        }
    }
    shadow /= 9.0;
    
    return shadow;
}

25. 什么是抗锯齿(Anti-Aliasing)?

答案:
抗锯齿是减少图像锯齿状边缘的技术。

常见的抗锯齿方法:

1. MSAA(Multi-Sample Anti-Aliasing):

  • 多重采样抗锯齿
  • 对每个像素的多个采样点进行采样
  • OpenGL 硬件支持
// 启用 MSAA
glEnable(GL_MULTISAMPLE);

// 创建多采样帧缓冲
GLuint msaaFBO;
glGenFramebuffers(1, &msaaFBO);
glBindFramebuffer(GL_FRAMEBUFFER, msaaFBO);

// 创建多采样纹理
GLuint msaaTexture;
glGenTextures(1, &msaaTexture);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msaaTexture);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGB, width, height, GL_TRUE);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, msaaTexture, 0);

2. FXAA(Fast Approximate Anti-Aliasing):

  • 快速近似抗锯齿
  • 后处理技术,不依赖硬件
  • 性能好,质量一般

3. TAA(Temporal Anti-Aliasing):

  • 时间抗锯齿
  • 利用多帧信息
  • 效果好,但可能有拖影

4. SMAA(Subpixel Morphological Anti-Aliasing):

  • 子像素形态抗锯齿
  • 后处理技术
  • 质量和性能平衡

26. 什么是延迟渲染(Deferred Rendering)?

答案:
延迟渲染是一种渲染技术,将几何处理与光照计算分离。

延迟渲染流程:

  1. 几何阶段(Geometry Pass)

    • 渲染场景到 G-Buffer
    • 存储位置、法向量、颜色、材质等信息
  2. 光照阶段(Lighting Pass)

    • 使用 G-Buffer 信息计算光照
    • 不依赖几何体,只依赖像素

G-Buffer 结构:

// G-Buffer 着色器
layout (location = 0) out vec3 gPosition;   // 位置
layout (location = 1) out vec3 gNormal;     // 法向量
layout (location = 2) out vec3 gAlbedo;     // 颜色
layout (location = 3) out vec3 gSpecular;   // 镜面反射

void main() {
    gPosition = FragPos;
    gNormal = normalize(Normal);
    gAlbedo = texture(diffuseMap, TexCoords).rgb;
    gSpecular = texture(specularMap, TexCoords).rgb;
}

延迟渲染的优点:

  • 支持大量光源
  • 光照计算与几何复杂度解耦
  • 适合复杂场景

延迟渲染的缺点:

  • 内存占用大(G-Buffer)
  • 不支持透明物体
  • 需要额外的透明度渲染通道

27. 什么是 GPU 加速的图像处理?

答案:
GPU 加速的图像处理是利用 GPU 的并行计算能力处理图像。

OpenCV + CUDA:

#include <opencv2/opencv.hpp>
#include <opencv2/cudaimgproc.hpp>
#include <opencv2/cudafilters.hpp>

// 使用 CUDA 加速图像处理
void processImageCUDA(const cv::Mat& input, cv::Mat& output) {
    // 上传到 GPU
    cv::cuda::GpuMat gpuInput, gpuOutput;
    gpuInput.upload(input);
    
    // GPU 处理
    cv::cuda::cvtColor(gpuInput, gpuOutput, cv::COLOR_BGR2GRAY);
    cv::Ptr<cv::cuda::Filter> gaussianFilter = 
        cv::cuda::createGaussianFilter(CV_8UC1, CV_8UC1, cv::Size(5, 5), 1.0);
    gaussianFilter->apply(gpuOutput, gpuOutput);
    
    // 下载到 CPU
    gpuOutput.download(output);
}

OpenGL 计算着色器:

#version 430
layout (local_size_x = 16, local_size_y = 16) in;
layout (rgba8, binding = 0) uniform image2D inputImage;
layout (rgba8, binding = 1) uniform image2D outputImage;

void main() {
    ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);
    
    // 高斯模糊
    vec4 color = vec4(0.0);
    float kernel[9] = float[](
        1.0/16, 2.0/16, 1.0/16,
        2.0/16, 4.0/16, 2.0/16,
        1.0/16, 2.0/16, 1.0/16
    );
    
    for (int y = -1; y <= 1; y++) {
        for (int x = -1; x <= 1; x++) {
            ivec2 offset = pixel + ivec2(x, y);
            color += imageLoad(inputImage, offset) * 
                    kernel[(y + 1) * 3 + (x + 1)];
        }
    }
    
    imageStore(outputImage, pixel, color);
}

28. 什么是点云(Point Cloud)?如何处理点云数据?

答案:
点云是一组三维点的集合,每个点包含位置和可能的颜色、法向量等信息。

点云数据结构:

struct Point3D {
    float x, y, z;
    float r, g, b;
    float nx, ny, nz;  // 法向量
    float intensity;
};

class PointCloud {
public:
    std::vector<Point3D> points;
    
    void loadFromPLY(const std::string& filename);
    void loadFromPCD(const std::string& filename);
    void saveToPLY(const std::string& filename);
    void render();
};

点云处理:

1. 点云滤波:

// 统计滤波(去除离群点)
void statisticalFilter(PointCloud& cloud, int k, double stddev) {
    // 对每个点,计算其 k 个最近邻的平均距离
    // 如果平均距离超过阈值(mean + stddev * std),则认为是离群点
    std::vector<bool> isOutlier(cloud.points.size(), false);
    
    for (size_t i = 0; i < cloud.points.size(); i++) {
        // 计算 k 近邻
        std::vector<float> distances;
        // ... 计算距离 ...
        
        // 计算平均距离和标准差
        float mean = calculateMean(distances);
        float std = calculateStdDev(distances, mean);
        
        if (mean > stddev * std) {
            isOutlier[i] = true;
        }
    }
    
    // 移除离群点
    PointCloud filtered;
    for (size_t i = 0; i < cloud.points.size(); i++) {
        if (!isOutlier[i]) {
            filtered.points.push_back(cloud.points[i]);
        }
    }
    cloud = filtered;
}

// 体素滤波(降采样)
void voxelFilter(PointCloud& cloud, float voxelSize) {
    std::map<std::tuple<int, int, int>, std::vector<size_t>> voxels;
    
    // 将点分配到体素
    for (size_t i = 0; i < cloud.points.size(); i++) {
        int x = static_cast<int>(cloud.points[i].x / voxelSize);
        int y = static_cast<int>(cloud.points[i].y / voxelSize);
        int z = static_cast<int>(cloud.points[i].z / voxelSize);
        
        voxels[std::make_tuple(x, y, z)].push_back(i);
    }
    
    // 每个体素只保留一个点(质心或随机点)
    PointCloud filtered;
    for (const auto& voxel : voxels) {
        if (!voxel.second.empty()) {
            // 计算质心
            Point3D centroid{0, 0, 0, 0, 0, 0};
            for (size_t idx : voxel.second) {
                centroid.x += cloud.points[idx].x;
                centroid.y += cloud.points[idx].y;
                centroid.z += cloud.points[idx].z;
            }
            int count = voxel.second.size();
            centroid.x /= count;
            centroid.y /= count;
            centroid.z /= count;
            
            filtered.points.push_back(centroid);
        }
    }
    cloud = filtered;
}

2. 点云配准:

// ICP(Iterative Closest Point)配准
class ICP {
public:
    struct Result {
        glm::mat4 transform;
        float error;
    };
    
    Result align(const PointCloud& source, const PointCloud& target, 
                 int maxIterations = 50, float threshold = 0.01f) {
        Result result;
        result.transform = glm::mat4(1.0f);
        result.error = std::numeric_limits<float>::max();
        
        PointCloud transformed = source;
        
        for (int iter = 0; iter < maxIterations; iter++) {
            // 找最近邻
            std::vector<std::pair<size_t, size_t>> correspondences;
            findCorrespondences(transformed, target, correspondences);
            
            // 计算变换矩阵
            glm::mat4 transform = computeTransform(transformed, target, correspondences);
            
            // 应用变换
            applyTransform(transformed, transform);
            result.transform = transform * result.transform;
            
            // 计算误差
            float error = computeError(transformed, target, correspondences);
            
            if (error < threshold) {
                break;
            }
            
            result.error = error;
        }
        
        return result;
    }
    
private:
    void findCorrespondences(const PointCloud& source, const PointCloud& target,
                            std::vector<std::pair<size_t, size_t>>& correspondences);
    glm::mat4 computeTransform(const PointCloud& source, const PointCloud& target,
                              const std::vector<std::pair<size_t, size_t>>& correspondences);
    void applyTransform(PointCloud& cloud, const glm::mat4& transform);
    float computeError(const PointCloud& source, const PointCloud& target,
                      const std::vector<std::pair<size_t, size_t>>& correspondences);
};

3. 点云渲染:

// 使用 OpenGL 渲染点云
class PointCloudRenderer {
public:
    void init() {
        // 创建着色器程序
        shaderProgram = createShaderProgram(pointShader, fragmentShader);
        
        // 创建 VAO 和 VBO
        glGenVertexArrays(1, &VAO);
        glGenBuffers(1, &VBO);
        
        glBindVertexArray(VAO);
        glBindBuffer(GL_ARRAY_BUFFER, VBO);
        
        // 配置顶点属性
        // 位置
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Point3D), (void*)0);
        glEnableVertexAttribArray(0);
        
        // 颜色
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Point3D), 
                             (void*)offsetof(Point3D, r));
        glEnableVertexAttribArray(1);
    }
    
    void render(const PointCloud& cloud, const Camera& camera) {
        glUseProgram(shaderProgram);
        glBindVertexArray(VAO);
        
        // 设置 MVP 矩阵
        glm::mat4 mvp = camera.getProjectionMatrix() * 
                       camera.getViewMatrix() * 
                       camera.getModelMatrix();
        setUniform("mvp", mvp);
        
        // 更新缓冲区数据
        glBindBuffer(GL_ARRAY_BUFFER, VBO);
        glBufferData(GL_ARRAY_BUFFER, cloud.points.size() * sizeof(Point3D),
                    cloud.points.data(), GL_DYNAMIC_DRAW);
        
        // 绘制点云
        glPointSize(2.0f);
        glDrawArrays(GL_POINTS, 0, cloud.points.size());
    }
    
private:
    GLuint VAO, VBO;
    GLuint shaderProgram;
};

29. 什么是体积渲染(Volume Rendering)?

答案:
体积渲染是直接渲染三维体积数据的技术,常用于医学影像和科学可视化。

体积渲染方法:

1. 光线投射(Ray Casting):

// 体积光线投射
vec4 rayCast(vec3 rayOrigin, vec3 rayDirection, sampler3D volume, 
             int steps, float stepSize) {
    vec4 color = vec4(0.0);
    float opacity = 0.0;
    
    for (int i = 0; i < steps; i++) {
        vec3 pos = rayOrigin + rayDirection * float(i) * stepSize;
        
        // 采样体积数据
        float density = texture(volume, pos).r;
        
        // 分类和着色
        vec4 sampleColor = classify(density);
        
        // 累积颜色和透明度
        float alpha = sampleColor.a * (1.0 - opacity);
        color.rgb += sampleColor.rgb * alpha;
        opacity += alpha;
        
        if (opacity >= 1.0) break;  // 提前终止
    }
    
    return vec4(color.rgb, opacity);
}

2. 纹理切片(Texture Slicing):

  • 将体积数据切片
  • 从后向前渲染每个切片
  • 使用 Alpha 混合

3. 等值面提取(Iso-surface Extraction):

  • 使用 Marching Cubes 算法
  • 提取等值面并渲染

30. 什么是 Marching Cubes 算法?

答案:
Marching Cubes 是一种从体积数据中提取等值面的算法。

算法步骤:

  1. 遍历体素:遍历体积数据中的每个体素
  2. 判断顶点状态:判断体素 8 个顶点是否在等值面内外
  3. 查找配置:根据 8 个顶点的状态(256 种配置)查找对应的三角面片
  4. 插值计算:在边上的交点位置进行插值
  5. 生成网格:生成三角面片

实现:

class MarchingCubes {
public:
    struct Vertex {
        glm::vec3 position;
        glm::vec3 normal;
    };
    
    void extractIsoSurface(const VolumeData& volume, float isovalue,
                          std::vector<Vertex>& vertices, 
                          std::vector<unsigned int>& indices) {
        int nx = volume.getWidth();
        int ny = volume.getHeight();
        int nz = volume.getDepth();
        
        for (int z = 0; z < nz - 1; z++) {
            for (int y = 0; y < ny - 1; y++) {
                for (int x = 0; x < nx - 1; x++) {
                    // 获取体素的 8 个顶点值
                    float values[8];
                    values[0] = volume.getValue(x,     y,     z);
                    values[1] = volume.getValue(x + 1, y,     z);
                    values[2] = volume.getValue(x + 1, y + 1, z);
                    values[3] = volume.getValue(x,     y + 1, z);
                    values[4] = volume.getValue(x,     y,     z + 1);
                    values[5] = volume.getValue(x + 1, y,     z + 1);
                    values[6] = volume.getValue(x + 1, y + 1, z + 1);
                    values[7] = volume.getValue(x,     y + 1, z + 1);
                    
                    // 计算配置索引
                    int config = calculateConfig(values, isovalue);
                    
                    // 查找三角面片
                    const Triangle* triangles = lookupTable[config];
                    
                    // 生成三角面片
                    for (int i = 0; triangles[i].edge[0] != -1; i++) {
                        Vertex v1, v2, v3;
                        
                        // 在边上插值计算顶点位置
                        v1.position = interpolate(volume, x, y, z, 
                                                triangles[i].edge[0], isovalue);
                        v2.position = interpolate(volume, x, y, z,
                                                triangles[i].edge[1], isovalue);
                        v3.position = interpolate(volume, x, y, z,
                                                triangles[i].edge[2], isovalue);
                        
                        // 计算法向量
                        v1.normal = calculateNormal(volume, v1.position);
                        v2.normal = calculateNormal(volume, v2.position);
                        v3.normal = calculateNormal(volume, v3.position);
                        
                        // 添加到网格
                        unsigned int baseIdx = vertices.size();
                        vertices.push_back(v1);
                        vertices.push_back(v2);
                        vertices.push_back(v3);
                        
                        indices.push_back(baseIdx);
                        indices.push_back(baseIdx + 1);
                        indices.push_back(baseIdx + 2);
                    }
                }
            }
        }
    }
    
private:
    struct Triangle {
        int edge[3];
    };
    
    Triangle lookupTable[256][15];  // 查找表
    
    int calculateConfig(const float values[8], float isovalue);
    glm::vec3 interpolate(const VolumeData& volume, int x, int y, int z,
                         int edge, float isovalue);
    glm::vec3 calculateNormal(const VolumeData& volume, const glm::vec3& pos);
};

31. 什么是网格简化(Mesh Simplification)?

答案:
网格简化是减少网格中三角形数量的技术,同时尽量保持原始形状。

简化算法:

1. 边折叠(Edge Collapse):

class MeshSimplifier {
public:
    struct Edge {
        size_t v1, v2;  // 边的两个顶点
        float cost;      // 折叠代价
    };
    
    void simplify(Mesh& mesh, size_t targetFaces) {
        // 计算每条边的折叠代价
        std::priority_queue<Edge> edges;
        for (const auto& edge : mesh.getEdges()) {
            float cost = calculateCollapseCost(mesh, edge);
            edges.push({edge.v1, edge.v2, cost});
        }
        
        // 迭代折叠边
        size_t currentFaces = mesh.getFaceCount();
        while (currentFaces > targetFaces && !edges.empty()) {
            Edge edge = edges.top();
            edges.pop();
            
            // 折叠边
            if (canCollapse(mesh, edge)) {
                collapseEdge(mesh, edge);
                currentFaces--;
                
                // 更新相关边的代价
                updateEdges(mesh, edge, edges);
            }
        }
    }
    
private:
    float calculateCollapseCost(const Mesh& mesh, const Edge& edge);
    bool canCollapse(const Mesh& mesh, const Edge& edge);
    void collapseEdge(Mesh& mesh, const Edge& edge);
    void updateEdges(Mesh& mesh, const Edge& collapsedEdge, 
                    std::priority_queue<Edge>& edges);
};

2. 顶点聚类(Vertex Clustering):

  • 将顶点分组到网格中
  • 每组顶点合并为一个顶点
  • 移除退化的三角形

3. 四边网格简化(Quadric Mesh Simplification):

  • 使用二次误差度量
  • 更高质量的简化结果

32. 如何进行图形程序的性能优化?

答案:

1. CPU 优化:

  • 减少 Draw Call:合并网格、使用实例化渲染
  • 减少 CPU-GPU 通信:批量传输数据
  • 使用多线程:并行处理、并行渲染
  • 优化算法:使用空间索引(BVH、Octree)

2. GPU 优化:

  • 减少三角形数量:LOD、网格简化
  • 减少着色器指令:优化着色器代码
  • 减少纹理采样:使用 Mipmap、纹理图集
  • 减少状态切换:批处理绘制

3. 内存优化:

  • 减少内存分配:使用对象池
  • 减少内存拷贝:使用指针和引用
  • 内存对齐:优化数据结构布局
  • 缓存友好:顺序访问数据

4. 渲染优化:

  • 视锥体剔除:只渲染可见物体
  • 遮挡剔除:不渲染被遮挡的物体
  • LOD:使用不同细节级别
  • 批处理:合并绘制调用

5. 分析工具:

  • CPU Profiler:VTune、perf
  • GPU Profiler:Nsight、RenderDoc
  • 内存分析:Valgrind、AddressSanitizer

33. OpenCV 中如何实现图像拼接?

答案:

1. 特征检测和匹配:

#include <opencv2/opencv.hpp>
#include <opencv2/features2d.hpp>
#include <opencv2/imgproc.hpp>

void stitchImages(const std::vector<cv::Mat>& images, cv::Mat& result) {
    if (images.size() < 2) return;
    
    // 特征检测器
    cv::Ptr<cv::ORB> detector = cv::ORB::create();
    
    // 检测和匹配特征点
    std::vector<std::vector<cv::KeyPoint>> keypoints(images.size());
    std::vector<cv::Mat> descriptors(images.size());
    
    for (size_t i = 0; i < images.size(); i++) {
        detector->detectAndCompute(images[i], cv::Mat(), 
                                  keypoints[i], descriptors[i]);
    }
    
    // 匹配特征点
    cv::BFMatcher matcher(cv::NORM_HAMMING);
    std::vector<cv::DMatch> matches;
    matcher.match(descriptors[0], descriptors[1], matches);
    
    // 筛选匹配点
    std::vector<cv::Point2f> srcPoints, dstPoints;
    for (const auto& match : matches) {
        if (match.distance < 50) {  // 阈值
            srcPoints.push_back(keypoints[0][match.queryIdx].pt);
            dstPoints.push_back(keypoints[1][match.trainIdx].pt);
        }
    }
    
    // 计算单应性矩阵
    cv::Mat H = cv::findHomography(srcPoints, dstPoints, 
                                   cv::RANSAC, 5.0);
    
    // 计算拼接画布大小
    std::vector<cv::Point2f> corners(4);
    corners[0] = cv::Point2f(0, 0);
    corners[1] = cv::Point2f(images[0].cols, 0);
    corners[2] = cv::Point2f(images[0].cols, images[0].rows);
    corners[3] = cv::Point2f(0, images[0].rows);
    
    cv::perspectiveTransform(corners, corners, H);
    
    // 计算边界
    float minX = std::min({corners[0].x, corners[1].x, 
                          corners[2].x, corners[3].x, 0.0f});
    float maxX = std::max({corners[0].x, corners[1].x, 
                          corners[2].x, corners[3].x, (float)images[1].cols});
    float minY = std::min({corners[0].y, corners[1].y, 
                          corners[2].y, corners[3].y, 0.0f});
    float maxY = std::max({corners[0].y, corners[1].y, 
                          corners[2].y, corners[3].y, (float)images[1].rows});
    
    // 调整单应性矩阵
    cv::Mat T = (cv::Mat_<float>(3, 3) << 
                 1, 0, -minX,
                 0, 1, -minY,
                 0, 0, 1);
    H = T * H;
    
    // 图像拼接
    cv::Size resultSize(maxX - minX, maxY - minY);
    cv::warpPerspective(images[0], result, H, resultSize);
    
    // 融合第二张图像
    cv::Mat mask = cv::Mat::ones(images[1].size(), CV_8U) * 255;
    cv::warpPerspective(mask, mask, T, resultSize);
    
    images[1].copyTo(result(cv::Rect(-minX, -minY, images[1].cols, images[1].rows)),
                    mask(cv::Rect(-minX, -minY, images[1].cols, images[1].rows)));
}

2. 图像融合:

// 多频带融合(Multi-band Blending)
void multiBandBlend(const cv::Mat& img1, const cv::Mat& img2, 
                   const cv::Mat& mask, cv::Mat& result) {
    // 使用拉普拉斯金字塔进行融合
    std::vector<cv::Mat> pyramid1, pyramid2, maskPyramid;
    
    buildLaplacianPyramid(img1, pyramid1);
    buildLaplacianPyramid(img2, pyramid2);
    buildGaussianPyramid(mask, maskPyramid);
    
    // 融合每层
    std::vector<cv::Mat> blendedPyramid;
    for (size_t i = 0; i < pyramid1.size(); i++) {
        cv::Mat blended;
        cv::addWeighted(pyramid1[i], 1.0 - maskPyramid[i], 
                       pyramid2[i], maskPyramid[i], 0.0, blended);
        blendedPyramid.push_back(blended);
    }
    
    // 重建图像
    reconstructFromLaplacianPyramid(blendedPyramid, result);
}

34. 什么是 GPU 加速的深度学习推理?

答案:
使用 GPU 加速深度学习模型的推理,提高处理速度。

实现方法:

1. 使用 TensorRT(NVIDIA):

#include <NvInfer.h>
#include <NvOnnxParser.h>

class TensorRTEngine {
public:
    void loadModel(const std::string& onnxPath) {
        // 创建构建器
        nvinfer1::IBuilder* builder = nvinfer1::createInferBuilder(logger);
        nvinfer1::INetworkDefinition* network = builder->createNetwork();
        
        // 解析 ONNX 模型
        nvonnxparser::IParser* parser = nvonnxparser::createParser(*network, logger);
        parser->parseFromFile(onnxPath.c_str(), 0);
        
        // 构建引擎
        nvinfer1::IBuilderConfig* config = builder->createBuilderConfig();
        config->setMaxWorkspaceSize(1ULL << 30);  // 1GB
        
        engine = builder->buildEngineWithConfig(*network, *config);
        
        // 创建执行上下文
        context = engine->createExecutionContext();
    }
    
    void inference(const float* input, float* output) {
        // 分配 GPU 内存
        void* buffers[2];
        cudaMalloc(&buffers[0], inputSize * sizeof(float));
        cudaMalloc(&buffers[1], outputSize * sizeof(float));
        
        // 复制输入数据
        cudaMemcpy(buffers[0], input, inputSize * sizeof(float),
                  cudaMemcpyHostToDevice);
        
        // 执行推理
        context->executeV2(buffers);
        
        // 复制输出数据
        cudaMemcpy(output, buffers[1], outputSize * sizeof(float),
                  cudaMemcpyDeviceToHost);
        
        // 清理
        cudaFree(buffers[0]);
        cudaFree(buffers[1]);
    }
    
private:
    nvinfer1::ICudaEngine* engine;
    nvinfer1::IExecutionContext* context;
};

2. 使用 OpenCV DNN:

#include <opencv2/dnn.hpp>

cv::dnn::Net loadModel(const std::string& modelPath, 
                      const std::string& configPath) {
    cv::dnn::Net net;
    
    if (modelPath.ends_with(".onnx")) {
        net = cv::dnn::readNetFromONNX(modelPath);
    } else if (modelPath.ends_with(".pb")) {
        net = cv::dnn::readNetFromTensorflow(modelPath, configPath);
    } else if (modelPath.ends_with(".weights")) {
        net = cv::dnn::readNetFromDarknet(configPath, modelPath);
    }
    
    // 设置为使用 GPU
    net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
    net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA);
    
    return net;
}

cv::Mat inference(cv::dnn::Net& net, const cv::Mat& input) {
    // 创建 blob
    cv::Mat blob;
    cv::dnn::blobFromImage(input, blob, 1.0 / 255.0, 
                          cv::Size(224, 224), cv::Scalar(), true, false);
    
    // 设置输入
    net.setInput(blob);
    
    // 前向传播
    cv::Mat output;
    net.forward(output);
    
    return output;
}

35. 如何进行三维可视化性能优化?

答案:

1. 渲染优化:

  • 视锥体剔除:只渲染可见物体
  • 遮挡剔除:使用层次遮挡查询
  • LOD 系统:根据距离切换细节级别
  • 实例化渲染:批量绘制相同物体

2. 几何优化:

  • 网格简化:减少三角形数量
  • 压缩格式:使用压缩的网格格式
  • 索引缓冲:使用索引避免重复顶点
  • 顶点缓存优化:优化顶点顺序

3. 纹理优化:

  • Mipmap:多级渐远纹理
  • 纹理压缩:使用压缩纹理格式(DXT、ETC)
  • 纹理图集:合并小纹理
  • 纹理流式加载:按需加载纹理

4. 着色器优化:

  • 减少分支:避免 if/else 语句
  • 使用内置函数:使用 GPU 优化函数
  • 减少纹理采样:复用采样结果
  • 预计算:在 CPU 上预计算常量

5. 内存优化:

  • 减少 Draw Call:合并绘制调用
  • 使用对象池:复用对象
  • 流式加载:按需加载资源
  • 压缩数据:使用压缩格式

6. 多线程优化:

  • 并行处理:使用多线程处理数据
  • 异步加载:异步加载资源
  • 任务队列:使用任务队列分配工作

36. 什么是着色器(Shader)?如何优化着色器?

答案:
着色器是在 GPU 上运行的小程序,用于控制渲染过程。

着色器优化技巧:

1. 减少分支:

// 不好的做法
if (condition) {
    color = color1;
} else {
    color = color2;
}

// 好的做法
color = mix(color1, color2, step(0.0, condition));

2. 使用内置函数:

// 不好的做法
float sqrt(float x) {
    // 自定义实现
}

// 好的做法
float y = sqrt(x);  // 使用内置函数

3. 减少纹理采样:

// 不好的做法
vec4 color1 = texture(tex1, uv);
vec4 color2 = texture(tex1, uv);  // 重复采样

// 好的做法
vec4 color = texture(tex1, uv);
vec4 color1 = color;
vec4 color2 = color;  // 复用结果

4. 预计算常量:

// 在 CPU 上计算,作为 uniform 传入
uniform mat4 mvp;  // 预计算 MVP 矩阵
uniform vec3 lightDir;  // 预计算光照方向

5. 使用低精度类型:

// 使用 mediump 或 lowp
mediump float value;
lowp vec3 color;

37. VTK 中如何实现交互式可视化?

答案:

1. 交互器(Interactor):

#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleTrackballCamera.h>

class CustomInteractorStyle : public vtkInteractorStyleTrackballCamera {
public:
    static CustomInteractorStyle* New();
    
    void OnLeftButtonDown() override {
        // 自定义左键按下行为
        int* clickPos = this->GetInteractor()->GetEventPosition();
        
        // 拾取操作
        vtkSmartPointer<vtkCellPicker> picker = 
            vtkSmartPointer<vtkCellPicker>::New();
        picker->SetTolerance(0.0005);
        
        int pickResult = picker->Pick(clickPos[0], clickPos[1], 0, renderer);
        
        if (pickResult) {
            // 处理拾取结果
            vtkActor* actor = picker->GetActor();
            // ...
        }
        
        vtkInteractorStyleTrackballCamera::OnLeftButtonDown();
    }
    
    void OnMouseWheelForward() override {
        // 自定义滚轮向前行为
        // 放大
        renderer->GetActiveCamera()->Zoom(1.1);
        renderWindow->Render();
    }
    
    void OnMouseWheelBackward() override {
        // 自定义滚轮向后行为
        // 缩小
        renderer->GetActiveCamera()->Zoom(0.9);
        renderWindow->Render();
    }
};

// 使用
vtkSmartPointer<vtkRenderWindowInteractor> interactor = 
    vtkSmartPointer<vtkRenderWindowInteractor>::New();
interactor->SetRenderWindow(renderWindow);

vtkSmartPointer<CustomInteractorStyle> style = 
    vtkSmartPointer<CustomInteractorStyle>::New();
interactor->SetInteractorStyle(style);

interactor->Start();

2. 回调函数:

class InteractionCallback : public vtkCommand {
public:
    static InteractionCallback* New() {
        return new InteractionCallback;
    }
    
    void Execute(vtkObject* caller, unsigned long eventId, void* callData) override {
        if (eventId == vtkCommand::InteractionEvent) {
            // 交互事件发生时的处理
            vtkRenderWindowInteractor* interactor = 
                static_cast<vtkRenderWindowInteractor*>(caller);
            
            // 获取鼠标位置
            int* pos = interactor->GetEventPosition();
            
            // 更新可视化
            updateVisualization(pos[0], pos[1]);
        }
    }
    
private:
    void updateVisualization(int x, int y);
};

// 添加回调
vtkSmartPointer<InteractionCallback> callback = 
    vtkSmartPointer<InteractionCallback>::New();
interactor->AddObserver(vtkCommand::InteractionEvent, callback);

38. 什么是光线追踪(Ray Tracing)?

答案:
光线追踪是一种渲染技术,通过追踪光线路径来生成图像。

光线追踪原理:

  1. 发射光线:从摄像机向屏幕像素发射光线
  2. 求交测试:检测光线与场景中物体的交点
  3. 递归追踪:追踪反射、折射等次级光线
  4. 着色计算:根据材质和光照计算颜色

光线追踪实现:

struct Ray {
    glm::vec3 origin;
    glm::vec3 direction;
};

struct HitInfo {
    bool hit;
    float t;  // 交点距离
    glm::vec3 position;
    glm::vec3 normal;
    Material material;
};

HitInfo traceRay(const Ray& ray, const Scene& scene) {
    HitInfo hitInfo;
    hitInfo.hit = false;
    hitInfo.t = std::numeric_limits<float>::max();
    
    // 遍历场景中的所有物体
    for (const auto& object : scene.getObjects()) {
        HitInfo temp = object->intersect(ray);
        if (temp.hit && temp.t < hitInfo.t) {
            hitInfo = temp;
        }
    }
    
    return hitInfo;
}

glm::vec3 shade(const Ray& ray, const HitInfo& hit, const Scene& scene, int depth) {
    if (!hit.hit || depth > maxDepth) {
        return scene.getBackgroundColor();
    }
    
    glm::vec3 color = glm::vec3(0.0f);
    
    // 环境光
    color += hit.material.ambient * scene.getAmbientLight();
    
    // 遍历所有光源
    for (const auto& light : scene.getLights()) {
        glm::vec3 lightDir = normalize(light.position - hit.position);
        
        // 阴影测试
        Ray shadowRay;
        shadowRay.origin = hit.position;
        shadowRay.direction = lightDir;
        
        HitInfo shadowHit = traceRay(shadowRay, scene);
        if (shadowHit.hit && shadowHit.t < distance(light.position, hit.position)) {
            continue;  // 在阴影中
        }
        
        // 漫反射
        float diff = max(dot(hit.normal, lightDir), 0.0f);
        color += hit.material.diffuse * light.color * diff;
        
        // 镜面反射
        glm::vec3 reflectDir = reflect(-lightDir, hit.normal);
        glm::vec3 viewDir = normalize(ray.origin - hit.position);
        float spec = pow(max(dot(viewDir, reflectDir), 0.0f), hit.material.shininess);
        color += hit.material.specular * light.color * spec;
    }
    
    // 递归追踪反射光线
    if (hit.material.reflectance > 0.0f && depth < maxDepth) {
        Ray reflectRay;
        reflectRay.origin = hit.position;
        reflectRay.direction = reflect(ray.direction, hit.normal);
        
        glm::vec3 reflectColor = shade(reflectRay, traceRay(reflectRay, scene), 
                                      scene, depth + 1);
        color += hit.material.reflectance * reflectColor;
    }
    
    return color;
}

void render(const Scene& scene, const Camera& camera, cv::Mat& image) {
    int width = image.cols;
    int height = image.rows;
    
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            // 生成光线
            Ray ray = camera.generateRay(x, y, width, height);
            
            // 追踪光线
            HitInfo hit = traceRay(ray, scene);
            
            // 着色
            glm::vec3 color = shade(ray, hit, scene, 0);
            
            // 写入图像
            image.at<cv::Vec3b>(y, x) = cv::Vec3b(
                clamp(color.b * 255, 0, 255),
                clamp(color.g * 255, 0, 255),
                clamp(color.r * 255, 0, 255)
            );
        }
    }
}

GPU 光线追踪(RTX):

// GLSL 光线追踪着色器
#version 460
#extension GL_NV_ray_tracing : require

layout(location = 0) rayPayloadNV vec3 hitValue;

void main() {
    vec3 origin = gl_WorldRayOriginNV;
    vec3 direction = gl_WorldRayDirectionNV;
    
    traceRayEXT(topLevelAS,           // acceleration structure
                gl_RayFlagsNoneEXT,   // rayFlags
                0xFF,                 // cullMask
                0,                    // sbtRecordOffset
                0,                    // sbtRecordStride
                0,                    // missIndex
                origin,               // origin
                0.0,                  // Tmin
                direction,            // direction
                10000.0,              // Tmax
                0);                   // payload
    
    // 使用 hitValue
    vec4 color = vec4(hitValue, 1.0);
}

光线追踪的优势:

  • 真实感:可以模拟反射、折射、阴影等效果
  • 全局光照:可以计算间接光照
  • 准确性:基于物理的光线传播

光线追踪的劣势:

  • 计算量大:需要大量计算资源
  • 实时性差:传统上难以实时渲染
  • RTX 技术:NVIDIA 的硬件加速使实时光线追踪成为可能

39. 什么是全局光照(Global Illumination)?

答案:
全局光照是考虑场景中所有光源和物体之间相互作用的渲染技术。

全局光照的方法:

1. 路径追踪(Path Tracing):

  • 随机追踪光线路径
  • 累积间接光照贡献
  • 蒙特卡洛方法

2. 光子映射(Photon Mapping):

  • 从光源发射光子
  • 存储光子能量
  • 重建光照分布

3. 辐射度算法(Radiosity):

  • 计算表面间的能量交换
  • 适合漫反射场景

4. 预计算辐射度传递(Precomputed Radiance Transfer):

  • 预计算光照传递
  • 实时渲染

40. 什么是纹理压缩?常见的纹理压缩格式有哪些?

答案:
纹理压缩是减少纹理内存占用和提高加载速度的技术。

常见的纹理压缩格式:

1. DXT(S3TC):

  • DXT1:1 位 Alpha,8:1 压缩比
  • DXT3:显式 Alpha,4:1 压缩比
  • DXT5:插值 Alpha,4:1 压缩比

2. ETC(Ericsson Texture Compression):

  • ETC1:Android 标准格式
  • ETC2:改进版本,支持 Alpha

3. ASTC(Adaptive Scalable Texture Compression):

  • 可变的压缩比
  • 高质量

4. BC(Block Compression):

  • BC1-BC7:DirectX 标准格式
  • 多种质量选项

使用示例:

// 加载压缩纹理
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);

// 加载 DXT 压缩纹理
glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT,
                       width, height, 0, dataSize, data);

41. 什么是 Mipmap?它的作用是什么?

答案:
Mipmap 是预计算的多级渐远纹理,用于提高渲染性能和视觉质量。

Mipmap 的作用:

  1. 减少锯齿:使用合适的纹理级别
  2. 提高性能:减少纹理采样次数
  3. 减少内存带宽:加载更小的纹理

Mipmap 生成:

// 生成 Mipmap
glGenerateMipmap(GL_TEXTURE_2D);

// 设置 Mipmap 过滤
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

Mipmap 级别:

  • Level 0:原始纹理(最大)
  • Level 1:1/2 大小
  • Level 2:1/4 大小

42. 什么是 Occlusion Culling(遮挡剔除)?

答案:
遮挡剔除是剔除被其他物体完全遮挡的物体的技术。

遮挡剔除方法:

1. 层次遮挡查询(Hierarchical Occlusion Query):

  • 使用遮挡查询测试物体
  • 层次结构加速

2. 遮挡图(Occlusion Map):

  • 预计算遮挡信息
  • 实时查询

3. 软件遮挡剔除(Software Occlusion Culling):

  • CPU 上计算
  • 低开销

实现示例:

// 遮挡查询
GLuint query;
glGenQueries(1, &query);

glBeginQuery(GL_SAMPLES_PASSED, query);
// 绘制包围盒
drawBoundingBox();
glEndQuery(GL_SAMPLES_PASSED);

GLuint samples;
glGetQueryObjectuiv(query, GL_QUERY_RESULT, &samples);

if (samples > 0) {
    // 物体可见,绘制详细模型
    drawDetailedModel();
}

43. 什么是法线贴图(Normal Mapping)?

答案:
法线贴图是使用纹理存储法向量信息的贴图技术,用于增加表面细节而不增加几何复杂度。

法线贴图原理:

  • 将法向量存储在纹理中(RGB = XYZ)
  • 在片段着色器中采样法向量
  • 使用切线空间变换法向量

法线贴图着色器:

#version 330 core
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;
in vec3 Tangent;

uniform sampler2D diffuseMap;
uniform sampler2D normalMap;

out vec4 FragColor;

void main() {
    // 从法线贴图采样法向量
    vec3 normal = texture(normalMap, TexCoords).rgb;
    normal = normal * 2.0 - 1.0;  // 从 [0,1] 映射到 [-1,1]
    
    // 切线空间到世界空间的变换
    vec3 N = normalize(Normal);
    vec3 T = normalize(Tangent);
    T = normalize(T - dot(T, N) * N);  // 正交化
    vec3 B = cross(N, T);
    
    mat3 TBN = mat3(T, B, N);
    normal = normalize(TBN * normal);
    
    // 使用法向量计算光照
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(normal, lightDir), 0.0);
    
    vec3 diffuse = diff * lightColor;
    vec3 ambient = 0.1 * lightColor;
    
    vec3 color = texture(diffuseMap, TexCoords).rgb;
    FragColor = vec4((ambient + diffuse) * color, 1.0);
}

44. 什么是 HDR(High Dynamic Range)渲染?

答案:
HDR 渲染是使用更大动态范围的颜色值进行渲染的技术。

HDR 的特点:

  • 更大的颜色范围:超过 0-1 的范围
  • 更真实的场景:模拟真实世界的光照
  • 需要色调映射:转换为显示设备的范围

HDR 渲染流程:

  1. 渲染到 HDR 帧缓冲:使用浮点纹理
  2. 色调映射(Tone Mapping):转换为 LDR
  3. 后处理:泛光、色彩校正等

色调映射:

// Reinhard 色调映射
vec3 reinhard(vec3 hdr) {
    return hdr / (hdr + vec3(1.0));
}

// ACES 色调映射
vec3 aces(vec3 x) {
    const float a = 2.51;
    const float b = 0.03;
    const float c = 2.43;
    const float d = 0.59;
    const float e = 0.14;
    return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);
}

45. OpenCV 中如何实现相机标定?

答案:

相机标定步骤:

#include <opencv2/opencv.hpp>
#include <opencv2/calib3d.hpp>

struct CameraCalibration {
    cv::Mat cameraMatrix;      // 相机内参矩阵
    cv::Mat distCoeffs;        // 畸变系数
    cv::Size imageSize;        // 图像尺寸
    std::vector<cv::Mat> rvecs; // 旋转向量
    std::vector<cv::Mat> tvecs; // 平移向量
};

CameraCalibration calibrateCamera(const std::vector<cv::Mat>& images,
                                   cv::Size boardSize, float squareSize) {
    CameraCalibration result;
    
    // 准备标定板角点
    std::vector<std::vector<cv::Point3f>> objectPoints;
    std::vector<std::vector<cv::Point2f>> imagePoints;
    
    // 标定板的 3D 点
    std::vector<cv::Point3f> objp;
    for (int i = 0; i < boardSize.height; i++) {
        for (int j = 0; j < boardSize.width; j++) {
            objp.push_back(cv::Point3f(j * squareSize, i * squareSize, 0));
        }
    }
    
    // 检测角点
    for (const auto& img : images) {
        cv::Mat gray;
        cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
        
        std::vector<cv::Point2f> corners;
        bool found = cv::findChessboardCorners(gray, boardSize, corners);
        
        if (found) {
            // 亚像素精度
            cv::cornerSubPix(gray, corners, cv::Size(11, 11), cv::Size(-1, -1),
                            cv::TermCriteria(cv::TermCriteria::EPS + 
                                           cv::TermCriteria::MAX_ITER, 30, 0.1));
            
            objectPoints.push_back(objp);
            imagePoints.push_back(corners);
        }
    }
    
    // 标定相机
    cv::Mat cameraMatrix, distCoeffs;
    std::vector<cv::Mat> rvecs, tvecs;
    
    double rms = cv::calibrateCamera(objectPoints, imagePoints,
                                     images[0].size(), cameraMatrix,
                                     distCoeffs, rvecs, tvecs,
                                     cv::CALIB_FIX_K4 | cv::CALIB_FIX_K5);
    
    result.cameraMatrix = cameraMatrix;
    result.distCoeffs = distCoeffs;
    result.imageSize = images[0].size();
    result.rvecs = rvecs;
    result.tvecs = tvecs;
    
    return result;
}

// 畸变校正
cv::Mat undistort(const cv::Mat& image, const CameraCalibration& calib) {
    cv::Mat undistorted;
    cv::undistort(image, undistorted, calib.cameraMatrix,
                  calib.distCoeffs);
    return undistorted;
}

总结

面试重点:

  • 渲染管线:顶点着色器、片段着色器、光栅化等阶段
  • MVP 矩阵:模型、视图、投影矩阵的作用和计算
  • 纹理技术:纹理映射、Mipmap、法线贴图
  • 光照模型:Phong、Blinn-Phong、PBR
  • 渲染优化:视锥体剔除、遮挡剔除、LOD
  • GPU 加速:CUDA、OpenCL、计算着色器
  • 点云处理:滤波、配准、渲染
  • VTK 管道:数据流和可视化流程

实际应用:

在实际项目中:

  • 选择合适的渲染技术:根据需求选择光栅化或光线追踪
  • 优化渲染性能:使用 LOD、剔除、批处理等技术
  • GPU 加速:利用 CUDA、OpenCL 加速计算
  • 内存管理:使用对象池减少内存分配
  • 调试和性能分析:使用 Profiler 工具定位瓶颈
  • 跨平台部署:考虑不同平台的兼容性

参考资料:

Originally published on mlangTse's Blog. View source