计算机图形学基础

什么是计算机图形学?

计算机图形学(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)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
输入顶点数据

顶点着色器(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):

1
2
3
4
M = T * R * S
T:平移矩阵
R:旋转矩阵
S:缩放矩阵

视图矩阵(View Matrix):

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

投影矩阵(Projection Matrix):

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

MVP 矩阵:

1
2
3
4
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 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 着色器语言

顶点着色器示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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;
}

片段着色器示例:

1
2
3
4
5
6
7
#version 330 core
in vec3 FragColor;
out vec4 FragColorOut;

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

OpenGL 常用函数

缓冲区操作:

1
2
3
4
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); // 删除缓冲区

着色器操作:

1
2
3
4
5
6
7
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); // 使用程序

绘制函数:

1
2
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):

1
2
3
4
5
6
7
8
9
10
11
数据源(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 基本示例

简单渲染示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#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 读取和显示图像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#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(矩阵):

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

Point(点):

1
2
3
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(矩形):

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

Scalar(标量):

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

OpenCV 图像操作

读取和显示图像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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;
}

图像基本操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 图像信息
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 图像处理

颜色空间转换:

1
2
3
4
5
cv::Mat gray;
cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);

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

图像滤波:

1
2
3
4
5
6
7
8
9
10
11
// 高斯模糊
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);

边缘检测:

1
2
3
4
5
6
7
8
// 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);

形态学操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);

特征检测:

1
2
3
4
5
6
7
8
9
// 角点检测(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):

1
2
3
4
5
6
7
8
9
10
11
12
13
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):

1
2
3
4
5
6
7
8
9
10
11
12
13
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 库进行矩阵运算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#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):

1
2
3
4
5
6
7
8
9
10
11
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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 使用遮挡查询(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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 使用实例化数组
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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 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 并行处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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 线程池:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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(性能分析):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 使用计时器
#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):投影矩阵,将摄像机空间变换到裁剪空间
1
MVP = P * V * M

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

答案:

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

正交投影:

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

透视投影:

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

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

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

深度测试函数:

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

使用:

1
2
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 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 管道组成:

1
2
3
4
5
6
7
8
9
10
11
数据源(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 的创建:

1
2
3
4
5
6
7
8
9
10
11
12
// 创建空 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 的内存管理:

1
2
3
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:

特性 CUDA OpenCL
平台 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):

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

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 创建帧缓冲对象
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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 高斯模糊着色器
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#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 配置:

1
2
3
4
5
# 静态链接
target_link_libraries(my_app ${CMAKE_SOURCE_DIR}/libs/libopengl.a)

# 动态链接
target_link_libraries(my_app opengl)

2. 依赖管理:

Windows 部署:

1
2
3
4
5
# 使用 vcpkg 管理依赖
vcpkg install opengl glfw3 glm

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

Linux 部署:

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

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

3. Docker 部署:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 模型公式:

1
2
3
4
5
6
7
8
9
10
11
I = Ka * Ia + Kd * Id * (N · L) + Ks * Is * (R · V)^n

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

Phong 着色器实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#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 公式:

1
2
3
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. 比较深度:如果片段深度大于深度贴图中的深度,则在阴影中

阴影映射实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 深度贴图生成着色器
#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):

  • 软阴影技术
  • 对周围像素采样,平均阴影值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 硬件支持
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 启用 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 结构:

1
2
3
4
5
6
7
8
9
10
11
12
// 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#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 计算着色器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#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)?如何处理点云数据?

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

点云数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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. 点云滤波:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 统计滤波(去除离群点)
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. 点云配准:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 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. 点云渲染:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 使用 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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 体积光线投射
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. 生成网格:生成三角面片

实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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. 特征检测和匹配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#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. 图像融合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 多频带融合(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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#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. 减少分支:

1
2
3
4
5
6
7
8
9
// 不好的做法
if (condition) {
color = color1;
} else {
color = color2;
}

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

2. 使用内置函数:

1
2
3
4
5
6
7
// 不好的做法
float sqrt(float x) {
// 自定义实现
}

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

3. 减少纹理采样:

1
2
3
4
5
6
7
8
// 不好的做法
vec4 color1 = texture(tex1, uv);
vec4 color2 = texture(tex1, uv); // 重复采样

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

4. 预计算常量:

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

5. 使用低精度类型:

1
2
3
// 使用 mediump 或 lowp
mediump float value;
lowp vec3 color;

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

答案:

1. 交互器(Interactor):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#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. 回调函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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. 着色计算:根据材质和光照计算颜色

光线追踪实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 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 标准格式
  • 多种质量选项

使用示例:

1
2
3
4
5
6
7
8
// 加载压缩纹理
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 生成:

1
2
3
4
5
6
// 生成 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 上计算
  • 低开销

实现示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 遮挡查询
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)
  • 在片段着色器中采样法向量
  • 使用切线空间变换法向量

法线贴图着色器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#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. 后处理:泛光、色彩校正等

色调映射:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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 中如何实现相机标定?

答案:

相机标定步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#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 工具定位瓶颈
  • 跨平台部署:考虑不同平台的兼容性

参考资料: