01OpenGL基本学习

第一节

1、概念

图形API:跨平台,跨编程语言的图形程序接口,用于调用GPU上的指令功能。

立即模式vs核心模式
  • 立即模式:3.0版本之前不会暴露太多细节
  • 核心渲染模式:3.0版本之后推出大量自由的功能

2、问题

1、图形API是什么?

  • 是一个由Khronos组织制定并且维护的规范
    2、ARB与Khronos是什么?
    -ARB组织缩写 Khronos是一个非盈利的联盟。
    3、游戏引擎与图形API
    -游戏引擎由图形API组成

第二节

1、三维坐标系三维坐标系

2、什么是模型?

网格(Mesh):存储了一个模型的几何形状数据

模型的概念
比如三角形:由三个顶点按顺序构成。(逆时针转)

3、什么是颜色?

RGB:红绿蓝(三个通道0-255)
光:意味着红绿蓝的光的强度
物体:意味着对红绿蓝的光的反射百分比
颜色理解

4、材质

描述了物体表面如何与光发生反应
比如:颜色 金属非金属 光滑粗糙等
Mesh+Material = 各种效果

第三节渲染管线

渲染
渲染过程:

渲染过程
梳理:
顶点数据
在这里插入图片描述

三维变换
三维变换
图元装配
图元装配

剪裁剔除
剪裁剔除

(前面是第一阶段)
光栅化
光栅化
片元着色
片元着色
混合与测试(第二阶段)
混合与测试

第四节GLAD配置流程

1、为什么需要GLAD?

1、为什么需要GLAD?

2、导入函数

在这里插入图片描述
配置网站:https://glad.dav1d.de/

第五节创建窗体

如图所示:
创建窗体的过程

#include<iostream>

//注意:glad头文件必须在glfw之前引用
#include<glad/glad.h>
#include<GLFW/glfw3.h>
using namespace std;
/*
创建glfw窗体系统
*/

const int height = 600;
const int width = 800;
int main()
{
    //1、初始化GLWF基本环节
    glfwInit();
    // 设置主版本号和次版本号
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    // 设置OpenGL核心配置
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    //2、创建窗体对象
    GLFWwindow*window = glfwCreateWindow(width,height, "LearnOpenGL",NULL,NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    // 设置窗体为当前上下文
    glfwMakeContextCurrent(window);


    //3、执行窗体循环
    while(!glfwWindowShouldClose(window))
    {
        //接收并且分发窗体消息
        //检查是否有需要处理的消息
        glfwPollEvents();
    }
    

    //4、退出GLFW
    glfwTerminate();

    return 0;
}

第六节事件回调

事件回调函数:

  • 事件回调函数:窗体激活响应鼠标等
    在这里插入图片描述

第七节GLAD函数加载

GLAD函数加载
在这里插入图片描述

2、OpenGL状态机

Opengl状态机
在这里插入图片描述
在这里插入图片描述

第七节封装

错误检查的封装

checkError.h
#pragma once
#ifdef Debug
#define GL_CALL(x) x;checkError();
#endif
void checkError();

checkError.cpp
#include "checkError.h"
#include<iostream>
#include<assert.h>
#include<glad/glad.h>
#include<GLFW/glfw3.h>
void checkError()
{
    GLenum errorCode = glGetError();

    
    switch(errorCode)
    {
        case GL_NO_ERROR:
            break;
        case GL_INVALID_ENUM:
            std::cout<<"GL_INVALID_ENUM"<<std::endl;
            break;
        case GL_INVALID_VALUE:
            std::cout<<"GL_INVALID_VALUE"<<std::endl;
            break;
        case GL_INVALID_OPERATION:
            std::cout<<"GL_INVALID_OPERATION"<<std::endl;
            break;
        case GL_INVALID_FRAMEBUFFER_OPERATION:
            std::cout<<"GL_INVALID_FRAMEBUFFER_OPERATION"<<std::endl;
            break;
        case GL_OUT_OF_MEMORY:
            std::cout<<"GL_OUT_OF_MEMORY"<<std::endl;
            break;
        default:
            std::cout<<"未知错误"<<std::endl;
    }
    assert(errorCode == GL_NO_ERROR);
}

第八节封装Application以及回调函数

封装思路
在这里插入图片描述
封装后的Application代码:
Application.h

#pragma once
#include<glad/glad.h>
#include "GLFW/glfw3.h"
#include<iostream>
#define app Application::getInstance()

class GLFWwindow;
using ResizeCallback = void(*)(int width,int height);
using KeyBoardCallback = void(*)(int key,int action,int mods);
using MouseButtonCallback = void(*)(int button,int action,int mods);

class Application
{

    public:
    ~Application();
    //访问实例
    static Application* getInstance(); 

    bool init(const int width,const int height);
    bool update();
    bool destroy();

    uint32_t getWidth() const;
    uint32_t getHeight() const;
    //存放变量
    private:
    uint32_t mWidth{0};
    uint32_t mHeight{0};
    GLFWwindow*mWindow{nullptr};

    static Application*m_instance;
    
   
    private:
    //1、回到函数指针
    ResizeCallback mResizeCallback{nullptr}; 
    KeyBoardCallback mKeyBoardCallback{nullptr};
    MouseButtonCallback mMouseButtonCallback{nullptr};

    public:
    //2、设置回调函数
    void setResizeCallback(ResizeCallback callback)
    {
        mResizeCallback = callback;
    }

    void setKeyBoardCallback(KeyBoardCallback callback)
    {
        mKeyBoardCallback = callback;   
    }

    void setMouseButtonCallback(MouseButtonCallback callback)
    {
        mMouseButtonCallback = callback;
    }
    private:
    static void frameBufferSizeCallback(GLFWwindow* window,int width,int height);
    static void keyBoardCallback(GLFWwindow* window,int key,int action,int scancode,int mods);
    static void mouseButtonCallback(GLFWwindow* window,int button,int action,int mods);


    


    private:
    Application();
    void operator=(const Application&){};
    Application(const Application&){};
    void operator=(const Application&&){};
    Application(const Application&&){};

};

Application.cpp
#include "Application.h"
#include "GLFW/glfw3.h"


Application*Application::m_instance = nullptr;
// 非线程安全的封装
Application* Application::getInstance()
{
    if(m_instance == nullptr)
    {
        m_instance = new Application();
    }
    return m_instance;
}

uint32_t Application::getWidth() const
{
    return mWidth;
}
uint32_t Application::getHeight() const
{
    return mHeight;
}

bool Application::init(const int width = 800,const int height= 600)
{
    mWidth = width;
    mHeight = height;
        //1、初始化GLWF基本环节
    glfwInit();
    // 设置主版本号和次版本号
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    // 设置OpenGL核心配置
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    
    //2、创建窗体对象
    mWindow = glfwCreateWindow(mWidth,mHeight, "LearnOpenGL",NULL,NULL);
    if (mWindow == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return false;
    }
    // 设置窗体为当前上下文
    glfwMakeContextCurrent(mWindow);

    //使用glad加载所有当期啊版本的opengl函数
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return false;
    }

    glfwSetFramebufferSizeCallback(mWindow, frameBufferSizeCallback);
    glfwSetKeyCallback(mWindow, keyBoardCallback);
    glfwSetMouseButtonCallback(mWindow, mouseButtonCallback);

    //this就是当前全局唯一的Application对象
    glfwSetWindowUserPointer(mWindow, this);
    return true;
}
bool Application::update()
{
    if(glfwWindowShouldClose(mWindow))
    {
        return false;
    }
     //接收并且分发窗体消息
    //检查是否有需要处理的消息
    glfwPollEvents();
    //渲染操作


    glfwSwapBuffers(mWindow); //双缓冲
    return true;
}
bool Application::destroy()
{
    //4、退出GLFW
    glfwTerminate();
}
//4
void Application::frameBufferSizeCallback(GLFWwindow* window,int width,int height)
{
    std::cout<<"Resize: "<<width<<" "<<height<<std::endl;
    Application*self = (Application*)glfwGetWindowUserPointer(window);
    self->mResizeCallback(width,height);

    // if(Application::getInstance()->mResizeCallback != nullptr)
    // {
    //     Application::getInstance()->mResizeCallback(width,height);
    // }
}
void Application::keyBoardCallback(GLFWwindow* window,int key,int scancode,int action,int mods)
{
    std::cout<<"key: "<<key<<" "<<action<<std::endl;
    if(key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
    {
        glfwSetWindowShouldClose(window, true);
    }
    Application*self = (Application*)glfwGetWindowUserPointer(window);
    self->mKeyBoardCallback(key,action,mods);
}

void Application::mouseButtonCallback(GLFWwindow* window,int button,int action,int mods)
{
    std::cout<<"mouse: "<<button<<" "<<action<<std::endl;
    Application*self = (Application*)glfwGetWindowUserPointer(window);
    self->mMouseButtonCallback(button,action,mods);
}



Application::Application()
{

}

Application::~Application()
{

}

第9节CPU与GPU

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第十节标准化设备坐标(NDC)

1、为什么要NDC

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第十一节VBO

1、什么是VBO

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第十一节VAO

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1、准备VBO

在这里插入图片描述
2、构建VAO
在这里插入图片描述
在这里插入图片描述
代码:

GLuint vao;
void prepareShader()
{
    GLuint posvbo,colorvbo;
    float vertices[] = {
        0.0f, 0.5f, 0.0f,
        -0.5f, -0.5f, 0.0f,
        0.5f, -0.5f, 0.0f
    };
    float colors[] = {
        1.0f, 0.0f, 0.0f,
        0.0f, 1.0f, 0.0f,
        0.0f, 0.0f, 1.0f
    };
    //创建vao
    GL_CALL(glGenVertexArrays(1, &vao));
    GL_CALL(glBindVertexArray(vao));
    //创建vbo
    GL_CALL(glGenBuffers(1, &posvbo));
    GL_CALL(glBindBuffer(GL_ARRAY_BUFFER,posvbo));
    GL_CALL(glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW));

    GL_CALL(glGenBuffers(1, &colorvbo));
    GL_CALL(glBindBuffer(GL_ARRAY_BUFFER,colorvbo));
    GL_CALL(glBufferData(GL_ARRAY_BUFFER,sizeof(colors),colors,GL_STATIC_DRAW));

    //设置顶点属性指针
    glBindBuffer(GL_ARRAY_BUFFER,posvbo);
    GL_CALL(glEnableVertexAttribArray(0));
    GL_CALL(glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0));

    glBindBuffer(GL_ARRAY_BUFFER,colorvbo);
    GL_CALL(glEnableVertexAttribArray(1));
    GL_CALL(glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0));

    //释放绑定
    glBindBuffer(GL_ARRAY_BUFFER,0);
    glBindVertexArray(0);

}

在这里插入图片描述

第十二节Shader

什么是shader?
一种运行在GPU上的着色器程序(类c语言)
在这里插入图片描述

1、GLSL语言

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、shader的编译

在这里插入图片描述

第十三节绘制一个三角形

#include<iostream>

//注意:glad头文件必须在glfw之前引用
#include<glad/glad.h>
#include<GLFW/glfw3.h>
#include<assert.h>
#include<wrapper/checkError.h>
#include<app/Application.h>
using namespace std;
/*
        单例类(全局唯一实例)
        成员变量 +成员函数
        2.1 成员函数-init(初始化)
        2.2 成员函数-update(每一帧执行)
        2.3 成员函数-destroy(结尾执行)
        响应回调函数(Resize)
        3.1 声明一个函数指针ResizeCallback
        3.2 声明一个ResizeCallback类型的成员变量
        3.3 声明一个SetResizeCallback的函数 ,设置窗体变化响应回调函数
        3.4 声明一个static的静态函数,用于响应glfw窗体变化
        3.5 将静态函数设置到glfw的监听Resize监听当中
        3.6*学会使用glfw的UserPointer
        响应键盘消息(OnKeyBoard)

        4.1VBO的创建销毁
        4.2练习绑定vbo,向vbo传输数据
            -glBindBuffer
            -glBufferData
*/
void OnResize(int width,int height)
{
    std::cout<<"OnResize"<<std::endl;
}
void OnMouse(int button, int action, int mod)
{
    std::cout<<"OnMouse"<<std::endl;
}
void OnKeyBoard(int key, int action, int mod)
{
    std::cout<<"OnKeyBoard"<<std::endl;
}


const int height = 600;
const int width = 800;

GLuint vao;
GLuint shaderProgram;
void prepareShader()
{
    GLuint posvbo,colorvbo;
    float vertices[] = {
        0.0f, 0.5f, 0.0f,
        -0.5f, -0.5f, 0.0f,
        0.5f, -0.5f, 0.0f
    };
    float colors[] = {
        1.0f, 0.0f, 0.0f,
        0.0f, 1.0f, 0.0f,
        0.0f, 0.0f, 1.0f
    };
    //创建vao
    GL_CALL(glGenVertexArrays(1, &vao));
    GL_CALL(glBindVertexArray(vao));
    //创建vbo
    GL_CALL(glGenBuffers(1, &posvbo));
    GL_CALL(glBindBuffer(GL_ARRAY_BUFFER,posvbo));
    GL_CALL(glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW));

    GL_CALL(glGenBuffers(1, &colorvbo));
    GL_CALL(glBindBuffer(GL_ARRAY_BUFFER,colorvbo));
    GL_CALL(glBufferData(GL_ARRAY_BUFFER,sizeof(colors),colors,GL_STATIC_DRAW));

    //设置顶点属性指针
    glBindBuffer(GL_ARRAY_BUFFER,posvbo);
    GL_CALL(glEnableVertexAttribArray(0));
    GL_CALL(glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0));

    glBindBuffer(GL_ARRAY_BUFFER,colorvbo);
    GL_CALL(glEnableVertexAttribArray(1));
    GL_CALL(glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0));

    //释放绑定
    glBindBuffer(GL_ARRAY_BUFFER,0);
    glBindVertexArray(0);

}

void prepare()
{
    // 1、完成vs和fs
    const char* vertexShaderSource = 
    "#version 460 core\n"
    "layout (location = 0) in vec3 aPos;\n"
    "layout (location = 1) in vec3 aColor;\n"  // 接收颜色数据
    "out vec3 vertexColor;\n"  // 输出颜色数据给片段着色器
    "void main()\n"
    "{\n"
    "   gl_Position = vec4(aPos, 1.0);\n"
    "   vertexColor = aColor;\n"  // 将颜色传递给片段着色器
    "}\n\0";

    const char* fragmentShaderSource =
    "#version 460 core\n"
    "out vec4 FragColor;\n"
    "in vec3 vertexColor;\n"  // 接收传入的颜色数据
    "void main()\n"
    "{\n"
    "   FragColor = vec4(vertexColor, 1.0f);\n"  // 使用传入的颜色数据
    "}\n\0";

    // 2、创建shader程序(fs,vs)
    GLuint vertex,fragment;
    vertex = glCreateShader(GL_VERTEX_SHADER);
    fragment = glCreateShader(GL_FRAGMENT_SHADER);

    //3、编译shader程序
    glShaderSource(vertex,1,&vertexShaderSource,NULL);
    glCompileShader(vertex);
    glShaderSource(fragment,1,&fragmentShaderSource,NULL);
    glCompileShader(fragment);

    //4、创建shader程序对象
   
    shaderProgram = glCreateProgram();
    //5、将shader程序对象链接到shader程序
    glAttachShader(shaderProgram,vertex);
    glAttachShader(shaderProgram,fragment);
    glLinkProgram(shaderProgram);
    //6、使用shader程序
    // glUseProgram(shaderProgram);
    //7、释放shader程序
    glDeleteShader(vertex);
    glDeleteShader(fragment);
    
}

int main()
{
    if(!app->init(width, height))
    {
        return -1;
    }
    app->setResizeCallback(OnResize);
    app->setKeyBoardCallback(OnKeyBoard);
    app->setMouseButtonCallback(OnMouse);
    GL_CALL(glViewport(0, 0, width, height));
    //准备着色器
    prepareShader();
    prepare();



    GL_CALL(glClearColor(0.2f, 0.3f, 0.3f, 1.0f));
    while(app->update())
    {
        GL_CALL(glClear(GL_COLOR_BUFFER_BIT));
        //画三角形
        GL_CALL(glUseProgram(shaderProgram));
        GL_CALL(glad_glBindVertexArray(vao));
        GL_CALL(glDrawArrays(GL_TRIANGLES, 0, 3));
    }
    app->destroy();

    return 0;
}

在这里插入图片描述
实现效果:
在这里插入图片描述
在这里插入图片描述

第十四节绘制流程

在这里插入图片描述

第十四节总结绘制流程

在这里插入图片描述

第十五节EBO

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第十六节彩色三角形分析

光栅化
在这里插入图片描述

第十七节Shader封装

shader.h
#pragma once
#include "core.h"
#include<string>

class Shader
{
    public:
    Shader(const char* vertexPath, const char* fragmentPath);
    ~Shader();
    void begin();//开始使用当前shader
    void end(); //结束使用当前shader

    private:
    void checkShaderErrors(GLuint target,std::string type);


    private:
    GLuint mprogramID{0}; //shader程序id
};

shader.cpp
#include "shader.h"
#include "core.h"
#include <wrapper/checkError.h>
#include<iostream>
#include<fstream> //读取文件
#include <sstream>
#include <string>
Shader::Shader(const char* vertexPath, const char* fragmentPath)
{
    std::string vertexCode; //顶点着色器代码
    std::string fragmentCode; //片段着色器代码

    std::ifstream vShaderFile; //顶点着色器文件
    std::ifstream fShaderFile; //片段着色器文件

    vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
    fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);

    try
    {
        vShaderFile.open(vertexPath);
        fShaderFile.open(fragmentPath);

        std::stringstream vShaderStream,fShaderStream;

        vShaderStream << vShaderFile.rdbuf();//读取文件内容
        fShaderStream << fShaderFile.rdbuf();

        vShaderFile.close();
        fShaderFile.close();
        
        vertexCode = vShaderStream.str();
        fragmentCode = fShaderStream.str();
    }
    catch(const std::ifstream::failure e)
    {
        std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
    }
    const char*vertexShaderCode = vertexCode.c_str();
    const char*fragmentShaderCode = fragmentCode.c_str();

    GLuint vertex,fragment;
    vertex = glCreateShader(GL_VERTEX_SHADER);
    fragment = glCreateShader(GL_FRAGMENT_SHADER);

    //输入代码
    glShaderSource(vertex,1,&vertexShaderCode,NULL);
    glShaderSource(fragment,1,&fragmentShaderCode,NULL);

    //编译着色器
    glCompileShader(vertex);
    checkShaderErrors(vertex, "VERTEX");
    glCompileShader(fragment);
    checkShaderErrors(fragment, "FRAGMENT");

    //创建着色器程序
    mprogramID = glCreateProgram();
    glAttachShader(mprogramID,vertex);
    glAttachShader(mprogramID,fragment);

    glLinkProgram(mprogramID);
    checkShaderErrors(mprogramID, "PROGRAM");
    //销毁
    glDeleteShader(vertex);
    glDeleteShader(fragment);
}
Shader::~Shader()
{

}
void Shader::begin()//开始使用当前shader
{
    GL_CALL(glUseProgram(mprogramID)); //shader程序id
}
void Shader::end() //结束使用当前shader
{
    GL_CALL(glUseProgram(0));
}

void Shader::checkShaderErrors(GLuint target,std::string type)
{
    int success = 0;
    char infoLog[1024];
    if(type == "VERTEX"|| type == "FRAGMENT")
    {
        glGetShaderiv(target,GL_COMPILE_STATUS,&success);
        if(!success)
        {
            glGetShaderInfoLog(target,1024,NULL,infoLog);
            std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
        }
    }
    else if(type == "PROGRAM")
    {
        glGetProgramiv(target,GL_LINK_STATUS,&success);
        if(!success)
        {
            glGetProgramInfoLog(target,1024,NULL,infoLog);
            std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
        }
    }
    else {
        std::cout << "ERROR::SHADER::TYPE_NOT_SUPPORTED" << std::endl;
    }
}

第十九节:变量与向量

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第二十节Uniform和Attribute的区别

在这里插入图片描述
在这里插入图片描述

第二十一节纹理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第二十一节Mipmap

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第二十二节:数学部分

1、叉乘公式

在这里插入图片描述
在这里插入图片描述

2、三维向量

在这里插入图片描述
在这里插入图片描述

3、缩放

在这里插入图片描述
翻转
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4、视图正交投影

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

视口变换

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第二十三节:绘制球体

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值