Skip to content

第15章:前沿技术与发展方向

15.1 实时渲染技术

15.1.1 现代渲染管线

实时渲染技术的演进

传统渲染:
光栅化 + 预计算光照 + 纹理 → 简单场景

现代渲染:
延迟渲染 + PBR + 大量后处理 → 真实感场景


延迟渲染(Deferred Rendering):

传统前向渲染:
for each 物体:
    for each 光源:
        计算光照   → O(物体 × 光源)

延迟渲染:
Pass 1: 渲染 G-Buffer(几何信息)
Pass 2: for each 光源:
            在屏幕空间计算光照  → O(光源 × 像素)


G-Buffer 内容:
┌────────────────────────────────────────────┐
│ 位置(Position)                            │
├────────────────────────────────────────────┤
│ 法向量(Normal)                            │
├────────────────────────────────────────────┤
│ 反照率(Albedo)                            │
├────────────────────────────────────────────┤
│ 材质参数(Roughness, Metallic, AO...)     │
└────────────────────────────────────────────┘

15.1.2 屏幕空间技术

常用屏幕空间效果

1. SSAO(屏幕空间环境光遮蔽)
   - 模拟物体间的环境光遮挡
   - 增加深度感

   原图:              加 SSAO:
   ┌───────────┐      ┌───────────┐
   │    │      │      │▓▓▓ │      │
   │    │      │  →   │▓▓▓ │      │ ← 角落变暗
   │────┘      │      │▓▓▓─┘      │
   └───────────┘      └───────────┘


2. SSR(屏幕空间反射)
   - 利用屏幕空间信息计算反射
   - 比光线追踪快,但有局限

3. SSGI(屏幕空间全局光照)
   - 屏幕空间的间接光照
   - 近似效果


4. 运动模糊(Motion Blur)
   - 基于速度缓冲

5. 景深(Depth of Field)
   - 模拟相机焦外模糊

6. 抗锯齿
   - TAA(时域抗锯齿)
   - FXAA/SMAA(后处理抗锯齿)

15.2 光线追踪

15.2.1 实时光线追踪

实时光线追踪(Real-time Ray Tracing)

硬件加速:
- NVIDIA RTX(RT Cores)
- AMD RDNA 2
- Intel Arc


光线追踪的优势:

1. 精确的反射
   传统:环境贴图近似
   光追:真实反射

2. 精确的折射
   传统:难以实现
   光追:自然支持

3. 软阴影
   传统:阴影贴图 + 模糊
   光追:多采样直接计算

4. 环境光遮蔽
   传统:SSAO 近似
   光追:准确计算


混合渲染管线:

┌─────────────────────────────────────────────┐
│                                             │
│  光栅化 ──────────┬───────────────► 主要场景│
│                   │                         │
│  光线追踪 ────────┼───────────────► 反射     │
│      │            │                         │
│      └────────────┼───────────────► 阴影     │
│                   │                         │
│                   └───────────────► 合成     │
│                                             │
└─────────────────────────────────────────────┘

15.2.2 路径追踪基础

javascript
/**
 * 简单路径追踪器
 */
class PathTracer {
    constructor(scene, maxBounces = 5) {
        this.scene = scene;
        this.maxBounces = maxBounces;
    }
    
    /**
     * 追踪路径
     */
    tracePath(ray, depth = 0) {
        if (depth >= this.maxBounces) {
            return { r: 0, g: 0, b: 0 };
        }
        
        // 求交
        const hit = this.scene.intersect(ray);
        
        if (!hit) {
            return this.scene.background(ray.direction);
        }
        
        const material = hit.material;
        
        // 自发光
        const emission = material.emission || { r: 0, g: 0, b: 0 };
        
        // 俄罗斯轮盘赌终止
        const p = Math.max(material.albedo.r, material.albedo.g, material.albedo.b);
        if (Math.random() > p) {
            return emission;
        }
        
        // 采样 BRDF
        const sample = this.sampleBRDF(hit, ray.direction, material);
        
        // 递归追踪
        const newRay = {
            origin: this.offsetPoint(hit.point, hit.normal),
            direction: sample.direction
        };
        
        const incoming = this.tracePath(newRay, depth + 1);
        
        // 渲染方程
        const cosTheta = Math.max(0, this.dot(sample.direction, hit.normal));
        
        return {
            r: emission.r + (material.albedo.r * incoming.r * cosTheta) / (sample.pdf * p),
            g: emission.g + (material.albedo.g * incoming.g * cosTheta) / (sample.pdf * p),
            b: emission.b + (material.albedo.b * incoming.b * cosTheta) / (sample.pdf * p)
        };
    }
    
    /**
     * 采样漫反射 BRDF
     */
    sampleBRDF(hit, viewDir, material) {
        // 余弦加权采样
        const u1 = Math.random();
        const u2 = Math.random();
        
        const phi = 2 * Math.PI * u1;
        const cosTheta = Math.sqrt(1 - u2);
        const sinTheta = Math.sqrt(u2);
        
        // 局部坐标
        const localDir = {
            x: Math.cos(phi) * sinTheta,
            y: Math.sin(phi) * sinTheta,
            z: cosTheta
        };
        
        // 转换到世界坐标
        const worldDir = this.localToWorld(localDir, hit.normal);
        
        return {
            direction: worldDir,
            pdf: cosTheta / Math.PI
        };
    }
    
    /**
     * 渲染图像
     */
    render(width, height, samplesPerPixel) {
        const image = new Float32Array(width * height * 3);
        const camera = this.scene.camera;
        
        for (let y = 0; y < height; y++) {
            for (let x = 0; x < width; x++) {
                let color = { r: 0, g: 0, b: 0 };
                
                // 多次采样
                for (let s = 0; s < samplesPerPixel; s++) {
                    const u = (x + Math.random()) / width;
                    const v = (y + Math.random()) / height;
                    
                    const ray = camera.generateRay(u, v);
                    const sample = this.tracePath(ray);
                    
                    color.r += sample.r;
                    color.g += sample.g;
                    color.b += sample.b;
                }
                
                // 平均
                const idx = (y * width + x) * 3;
                image[idx] = color.r / samplesPerPixel;
                image[idx + 1] = color.g / samplesPerPixel;
                image[idx + 2] = color.b / samplesPerPixel;
            }
            
            // 进度显示
            console.log(`Progress: ${Math.round((y + 1) / height * 100)}%`);
        }
        
        return image;
    }
    
    // 辅助方法
    dot(a, b) {
        return a.x * b.x + a.y * b.y + a.z * b.z;
    }
    
    offsetPoint(p, n) {
        const epsilon = 0.001;
        return {
            x: p.x + n.x * epsilon,
            y: p.y + n.y * epsilon,
            z: p.z + n.z * epsilon
        };
    }
    
    localToWorld(local, normal) {
        // 构建正交基
        const up = Math.abs(normal.y) < 0.99 
            ? { x: 0, y: 1, z: 0 } 
            : { x: 1, y: 0, z: 0 };
        
        const tangent = this.normalize(this.cross(up, normal));
        const bitangent = this.cross(normal, tangent);
        
        return {
            x: local.x * tangent.x + local.y * bitangent.x + local.z * normal.x,
            y: local.x * tangent.y + local.y * bitangent.y + local.z * normal.y,
            z: local.x * tangent.z + local.y * bitangent.z + local.z * normal.z
        };
    }
}

15.3 体积渲染

15.3.1 体积渲染原理

体积渲染(Volume Rendering)

用于可视化 3D 数据集(如 CT、MRI 扫描)。

体素数据:
       ┌─────────────────┐
      ╱                 ╱│
     ╱                 ╱ │
    ╱                 ╱  │
   ┌─────────────────┐   │
   │  ▒▒▒            │   │
   │  ▒▓▓▒           │   │
   │  ▒▓▓▓▒          │  ╱
   │   ▒▒▒           │ ╱
   └─────────────────┘╱


光线投射(Ray Casting):

对每个像素:
1. 发射光线穿过体积
2. 沿光线采样密度值
3. 应用传递函数(密度 → 颜色/不透明度)
4. 前向后或后向前合成


前向后合成:

C_out = C_in + (1 - α_in) × C_sample
α_out = α_in + (1 - α_in) × α_sample

15.3.2 体积渲染实现

javascript
/**
 * 简单体积渲染
 */
class VolumeRenderer {
    constructor(volumeData, dimensions) {
        this.data = volumeData;     // Float32Array
        this.dim = dimensions;      // { x, y, z }
        this.transferFunction = null;
    }
    
    /**
     * 设置传递函数
     */
    setTransferFunction(tf) {
        this.transferFunction = tf;
    }
    
    /**
     * 采样体积
     */
    sample(x, y, z) {
        // 三线性插值
        const x0 = Math.floor(x);
        const y0 = Math.floor(y);
        const z0 = Math.floor(z);
        
        const x1 = Math.min(x0 + 1, this.dim.x - 1);
        const y1 = Math.min(y0 + 1, this.dim.y - 1);
        const z1 = Math.min(z0 + 1, this.dim.z - 1);
        
        const tx = x - x0;
        const ty = y - y0;
        const tz = z - z0;
        
        const getValue = (xi, yi, zi) => {
            const idx = zi * this.dim.x * this.dim.y + yi * this.dim.x + xi;
            return this.data[idx];
        };
        
        // 三线性插值
        const c000 = getValue(x0, y0, z0);
        const c100 = getValue(x1, y0, z0);
        const c010 = getValue(x0, y1, z0);
        const c110 = getValue(x1, y1, z0);
        const c001 = getValue(x0, y0, z1);
        const c101 = getValue(x1, y0, z1);
        const c011 = getValue(x0, y1, z1);
        const c111 = getValue(x1, y1, z1);
        
        const c00 = c000 * (1 - tx) + c100 * tx;
        const c10 = c010 * (1 - tx) + c110 * tx;
        const c01 = c001 * (1 - tx) + c101 * tx;
        const c11 = c011 * (1 - tx) + c111 * tx;
        
        const c0 = c00 * (1 - ty) + c10 * ty;
        const c1 = c01 * (1 - ty) + c11 * ty;
        
        return c0 * (1 - tz) + c1 * tz;
    }
    
    /**
     * 渲染单条射线
     */
    rayMarch(origin, direction, tMin, tMax, stepSize) {
        let color = { r: 0, g: 0, b: 0 };
        let alpha = 0;
        
        let t = tMin;
        
        while (t < tMax && alpha < 0.99) {
            const pos = {
                x: origin.x + direction.x * t,
                y: origin.y + direction.y * t,
                z: origin.z + direction.z * t
            };
            
            // 检查边界
            if (pos.x >= 0 && pos.x < this.dim.x &&
                pos.y >= 0 && pos.y < this.dim.y &&
                pos.z >= 0 && pos.z < this.dim.z) {
                
                const density = this.sample(pos.x, pos.y, pos.z);
                const { color: sampleColor, alpha: sampleAlpha } = 
                    this.transferFunction(density);
                
                // 前向后合成
                color.r += (1 - alpha) * sampleColor.r * sampleAlpha;
                color.g += (1 - alpha) * sampleColor.g * sampleAlpha;
                color.b += (1 - alpha) * sampleColor.b * sampleAlpha;
                alpha += (1 - alpha) * sampleAlpha;
            }
            
            t += stepSize;
        }
        
        return { color, alpha };
    }
}

15.4 虚拟现实

15.4.1 VR/AR 技术概述

VR/AR 核心技术

┌─────────────────────────────────────────────────┐
│                 VR/AR 技术栈                     │
├─────────────────────────────────────────────────┤
│                                                 │
│  显示技术:                                      │
│  - 头戴显示器(HMD)                             │
│  - 透视显示(AR)                                │
│  - 空间光场显示                                  │
│                                                 │
│  追踪技术:                                      │
│  - 头部追踪(6DoF)                              │
│  - 手部追踪                                      │
│  - 眼动追踪                                      │
│  - SLAM(定位与建图)                            │
│                                                 │
│  渲染技术:                                      │
│  - 立体渲染                                      │
│  - 时间扭曲(Time Warp)                         │
│  - 注视点渲染(Foveated Rendering)              │
│  - 多视图渲染                                    │
│                                                 │
└─────────────────────────────────────────────────┘


立体渲染:

左眼                右眼
┌─────────┐        ┌─────────┐
│         │        │         │
│    ●    │        │    ●    │
│         │        │         │
└─────────┘        └─────────┘

两个视角 → 深度感知


注视点渲染:

中央高分辨率,周边低分辨率:

┌─────────────────────────────┐
│░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│
│░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│
│░░░░░░░░▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░│
│░░░░░░░░▓██████▓░░░░░░░░░░░░░│ ← 注视点
│░░░░░░░░▓██████▓░░░░░░░░░░░░░│
│░░░░░░░░▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░│
│░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│
└─────────────────────────────┘

减少 50%+ 的像素计算

15.4.2 WebXR API

javascript
/**
 * WebXR 基础示例
 */
class WebXRExample {
    constructor(canvas) {
        this.canvas = canvas;
        this.gl = canvas.getContext('webgl2', { xrCompatible: true });
        this.xrSession = null;
        this.xrRefSpace = null;
    }
    
    /**
     * 检查 WebXR 支持
     */
    async checkSupport() {
        if (!navigator.xr) {
            console.log('WebXR not supported');
            return false;
        }
        
        const isSupported = await navigator.xr.isSessionSupported('immersive-vr');
        console.log('Immersive VR supported:', isSupported);
        return isSupported;
    }
    
    /**
     * 开始 XR 会话
     */
    async startXRSession() {
        try {
            this.xrSession = await navigator.xr.requestSession('immersive-vr', {
                requiredFeatures: ['local-floor'],
                optionalFeatures: ['hand-tracking']
            });
            
            // 设置渲染层
            const glLayer = new XRWebGLLayer(this.xrSession, this.gl);
            this.xrSession.updateRenderState({ baseLayer: glLayer });
            
            // 获取参考空间
            this.xrRefSpace = await this.xrSession.requestReferenceSpace('local-floor');
            
            // 开始渲染循环
            this.xrSession.requestAnimationFrame(this.onXRFrame.bind(this));
            
        } catch (error) {
            console.error('Failed to start XR session:', error);
        }
    }
    
    /**
     * XR 渲染帧
     */
    onXRFrame(time, frame) {
        const session = frame.session;
        session.requestAnimationFrame(this.onXRFrame.bind(this));
        
        const glLayer = session.renderState.baseLayer;
        
        // 绑定帧缓冲
        this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, glLayer.framebuffer);
        this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
        
        // 获取姿态
        const pose = frame.getViewerPose(this.xrRefSpace);
        
        if (pose) {
            // 为每个视图(眼睛)渲染
            for (const view of pose.views) {
                const viewport = glLayer.getViewport(view);
                this.gl.viewport(
                    viewport.x, viewport.y, 
                    viewport.width, viewport.height
                );
                
                // 获取变换矩阵
                const viewMatrix = view.transform.inverse.matrix;
                const projectionMatrix = view.projectionMatrix;
                
                // 渲染场景
                this.renderScene(viewMatrix, projectionMatrix);
            }
        }
    }
    
    /**
     * 渲染场景
     */
    renderScene(viewMatrix, projectionMatrix) {
        // 实际的渲染代码
        // ...
    }
    
    /**
     * 结束会话
     */
    async endXRSession() {
        if (this.xrSession) {
            await this.xrSession.end();
            this.xrSession = null;
        }
    }
}

15.5 神经渲染

15.5.1 NeRF 简介

NeRF(Neural Radiance Fields)

使用神经网络表示 3D 场景。

输入:
- 3D 位置 (x, y, z)
- 视角方向 (θ, φ)

输出:
- 颜色 (r, g, b)
- 密度 σ


网络结构:

    (x, y, z)


   ┌─────────────┐
   │  位置编码   │
   └──────┬──────┘


   ┌─────────────┐
   │   MLP       │ ×8 层
   └──────┬──────┘

    ┌─────┴─────┐
    │           │
    ▼           ▼
   密度σ     特征向量

                ├── + 视角编码


         ┌─────────────┐
         │   MLP       │
         └──────┬──────┘


            颜色(r,g,b)


体积渲染:

沿射线积分:

C = ∫ T(t) × σ(t) × c(t) dt

其中 T(t) = exp(-∫σ(s)ds) 是透射率

15.5.2 3D 高斯溅射

3D Gaussian Splatting

用 3D 高斯表示场景,实现实时渲染。

每个高斯:
- 位置(均值)
- 协方差矩阵(形状、方向)
- 颜色(球谐函数)
- 不透明度


优势:
- 比 NeRF 快 100-1000 倍
- 可以实时渲染
- 质量接近 NeRF


渲染流程:

1. 将 3D 高斯投影到 2D
2. 按深度排序
3. 在图像空间进行 α 混合

             3D 高斯


         ┌─────────────┐
         │  投影到 2D   │
         └──────┬──────┘


         ┌─────────────┐
         │  深度排序    │
         └──────┬──────┘


         ┌─────────────┐
         │  α 混合      │
         └──────┬──────┘


            最终图像

15.6 WebGPU

15.6.1 WebGPU 概述

WebGPU:下一代 Web 图形 API

WebGL 的问题:
- 基于 OpenGL ES 2.0/3.0(老旧)
- 状态机模型
- 无法利用现代 GPU 特性
- 计算着色器支持有限


WebGPU 的优势:
- 现代设计(类似 Vulkan/Metal/DX12)
- 显式资源管理
- 计算着色器支持
- 更低的 CPU 开销
- 更好的验证


对比:

WebGL:
┌────────────────────────────────┐
│  应用层                        │
│      │                        │
│      ▼                        │
│  WebGL API (OpenGL ES)        │
│      │                        │
│      ▼                        │
│  浏览器 + ANGLE               │
│      │                        │
│      ▼                        │
│  原生 API (DX/Metal/Vulkan)   │
└────────────────────────────────┘

WebGPU:
┌────────────────────────────────┐
│  应用层                        │
│      │                        │
│      ▼                        │
│  WebGPU API                   │
│      │                        │
│      ▼                        │
│  Dawn/wgpu                    │
│      │                        │
│      ▼                        │
│  原生 API (DX/Metal/Vulkan)   │
└────────────────────────────────┘

15.6.2 WebGPU 基础示例

javascript
/**
 * WebGPU 基础示例
 */
async function webgpuExample() {
    // 1. 获取适配器和设备
    const adapter = await navigator.gpu.requestAdapter();
    const device = await adapter.requestDevice();
    
    // 2. 配置画布
    const canvas = document.querySelector('canvas');
    const context = canvas.getContext('webgpu');
    const format = navigator.gpu.getPreferredCanvasFormat();
    
    context.configure({
        device,
        format,
        alphaMode: 'opaque'
    });
    
    // 3. 创建着色器模块
    const shaderModule = device.createShaderModule({
        code: `
            @vertex
            fn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> @builtin(position) vec4f {
                var pos = array<vec2f, 3>(
                    vec2f(0.0, 0.5),
                    vec2f(-0.5, -0.5),
                    vec2f(0.5, -0.5)
                );
                return vec4f(pos[vertexIndex], 0.0, 1.0);
            }
            
            @fragment
            fn fragmentMain() -> @location(0) vec4f {
                return vec4f(1.0, 0.0, 0.0, 1.0);
            }
        `
    });
    
    // 4. 创建渲染管线
    const pipeline = device.createRenderPipeline({
        layout: 'auto',
        vertex: {
            module: shaderModule,
            entryPoint: 'vertexMain'
        },
        fragment: {
            module: shaderModule,
            entryPoint: 'fragmentMain',
            targets: [{ format }]
        }
    });
    
    // 5. 渲染帧
    function frame() {
        const commandEncoder = device.createCommandEncoder();
        
        const passEncoder = commandEncoder.beginRenderPass({
            colorAttachments: [{
                view: context.getCurrentTexture().createView(),
                loadOp: 'clear',
                clearValue: { r: 0.1, g: 0.1, b: 0.1, a: 1 },
                storeOp: 'store'
            }]
        });
        
        passEncoder.setPipeline(pipeline);
        passEncoder.draw(3);
        passEncoder.end();
        
        device.queue.submit([commandEncoder.finish()]);
        requestAnimationFrame(frame);
    }
    
    frame();
}

/**
 * WebGPU 计算着色器示例
 */
async function computeExample() {
    const adapter = await navigator.gpu.requestAdapter();
    const device = await adapter.requestDevice();
    
    // 创建缓冲区
    const bufferSize = 1024 * 4; // 1024 个 float
    const inputBuffer = device.createBuffer({
        size: bufferSize,
        usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
    });
    
    const outputBuffer = device.createBuffer({
        size: bufferSize,
        usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
    });
    
    const readBuffer = device.createBuffer({
        size: bufferSize,
        usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
    });
    
    // 上传数据
    const inputData = new Float32Array(1024);
    for (let i = 0; i < 1024; i++) {
        inputData[i] = i;
    }
    device.queue.writeBuffer(inputBuffer, 0, inputData);
    
    // 创建计算着色器
    const computeModule = device.createShaderModule({
        code: `
            @group(0) @binding(0) var<storage, read> input: array<f32>;
            @group(0) @binding(1) var<storage, read_write> output: array<f32>;
            
            @compute @workgroup_size(64)
            fn main(@builtin(global_invocation_id) id: vec3<u32>) {
                output[id.x] = input[id.x] * 2.0;
            }
        `
    });
    
    // 创建计算管线
    const computePipeline = device.createComputePipeline({
        layout: 'auto',
        compute: {
            module: computeModule,
            entryPoint: 'main'
        }
    });
    
    // 创建绑定组
    const bindGroup = device.createBindGroup({
        layout: computePipeline.getBindGroupLayout(0),
        entries: [
            { binding: 0, resource: { buffer: inputBuffer } },
            { binding: 1, resource: { buffer: outputBuffer } }
        ]
    });
    
    // 执行计算
    const commandEncoder = device.createCommandEncoder();
    
    const computePass = commandEncoder.beginComputePass();
    computePass.setPipeline(computePipeline);
    computePass.setBindGroup(0, bindGroup);
    computePass.dispatchWorkgroups(16); // 16 * 64 = 1024
    computePass.end();
    
    // 复制结果
    commandEncoder.copyBufferToBuffer(outputBuffer, 0, readBuffer, 0, bufferSize);
    
    device.queue.submit([commandEncoder.finish()]);
    
    // 读取结果
    await readBuffer.mapAsync(GPUMapMode.READ);
    const result = new Float32Array(readBuffer.getMappedRange());
    console.log('Result:', result.slice(0, 10));
    readBuffer.unmap();
}

15.7 本章小结

技术发展趋势

图形学发展方向

1. 实时光线追踪
   - 硬件加速普及
   - 混合渲染成为主流

2. AI 与图形学融合
   - 神经渲染
   - AI 超分辨率
   - 程序化内容生成

3. 沉浸式体验
   - VR/AR 普及
   - 元宇宙应用
   - 空间计算

4. Web 图形增强
   - WebGPU 普及
   - 浏览器中的专业应用
   - 云渲染

5. 实时全局光照
   - 软件光线追踪优化
   - 新的 GI 近似技术

学习建议

方向推荐技术学习资源
实时渲染Vulkan, DX12, WebGPU官方文档、LearnOpenGL
离线渲染PBRT, 光线追踪PBRT 书籍
VR/ARWebXR, Unity XR官方教程
AI 渲染NeRF, 3DGS论文、开源实现

关键要点

  1. 光线追踪正在从离线走向实时
  2. 神经渲染是重要的新方向
  3. WebGPU 将大幅提升 Web 图形能力
  4. VR/AR 需要特殊的渲染优化
  5. 图形学与 AI 的结合越来越紧密

系列总结

恭喜完成《计算机图形学原理》系列学习!

本系列涵盖了:

  • 数学基础(向量、矩阵、变换)
  • 经典算法(光栅化、裁剪、曲线)
  • 渲染技术(光照、纹理、阴影)
  • 高级主题(碰撞、空间索引、图像处理)
  • 前沿方向(光追、神经渲染、WebGPU)

建议后续学习路径:

  1. 实践:实现软件光栅化器或光线追踪器
  2. 深入:学习一个专业方向(游戏、影视、可视化)
  3. 应用:参与开源项目或开发自己的项目

文档版本:v1.0
字数统计:约 10,000 字
系列完结

如有转载或 CV 的请标注本站原文地址