LearnOpenGL之3D显示

news2024/9/22 3:39:39

        ===============================  前序================================

         AndroidLearnOpenGL是本博主自己实现的LearnOpenGL练习集合:

        Github地址:https://github.com/wangyongyao1989/AndroidLearnOpenGL   

        系列文章:

        1、LearnOpenGL之入门基础

        2、LearnOpenGL之3D显示

        ===================================================================

        ============================== 显示效果 ===============================

               

        ===================================================================

         根据上一篇文章的LearnOpenGL入门基础的学习,我们已经了解了OpenGL一些基础概念,接我们一起下来实现3D效果。

          一、3D概序:   

        

        1、模型矩阵(Model Matrix):

        在开始进行3D绘图时,我们首先创建一个模型矩阵。这个模型矩阵包含了位移、缩放与旋转操作,它们会被应用到所有物体的顶点上,以变化它们到全局的世界空间。让我们变换一下我们的平面,将其绕着x轴旋转,使它看起来像放在地上一样。这个模型矩阵看起来是这样的

    glm::mat4 model;
    model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));

        通过将顶点坐标乘以这个模型矩阵,我们将该顶点坐标变换到世界坐标。我们的平面看起来就是在地板上,代表全局世界里的平面。

        2、观察矩阵(View Matrix):

        我们想要在场景里面稍微往后移动,以使得物体变成可见的(当在世界空间时,我们位于原点(0,0,0))。摄像机向后移动,和将整个场景向前移动是一样的。

    glm::mat4 projection;
    projection = glm::perspective(glm::radians(45.0f),
     screenWidth / screenHeight, 0.1f, 100.0f);

        这正是观察矩阵所做的,我们以相反于摄像机移动的方向移动整个场景。因为我们想要往后移动,并且OpenGL是一个右手坐标系(Right-handed System),所以我们需要沿着z轴的正方向移动。我们会通过将场景沿着z轴负方向平移来实现。它会给我们一种我们在往后移动的感觉。        

 3、投影矩阵(Projection Matrix):

        定义一个投影矩阵。我们希望在场景中使用透视投影,所以像这样声明一个投影矩阵:

    glm::mat4 projection;
    projection = glm::perspective(glm::radians(45.0f)
    , screenWidth / screenHeight, 0.1f, 100.0f);

  4、变换矩阵结合,从顶点glsl程序传入着色:

      我们应该将它们传入着色器。首先,让我们在顶点着色器中声明一个uniform变换矩阵然后将它乘以顶点坐标:         

#version 320 es

layout (location = 0) in vec3 aPos;     // 位置变量的属性位置值为0
layout (location = 1) in vec3 aTexCoord;

out vec2 TexCoord;

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

void main()
{
    // 注意乘法要从右向左读
    gl_Position = projection * view * model * vec4(aPos, 1.0f);
   	TexCoord = vec2(aTexCoord.x, aTexCoord.y);
}

        将矩阵传入着色器(这通常在每次的渲染迭代中进行,因为变换矩阵会经常变动。

     // create transformations
    // make sure to initialize matrix to identity matrix first
    glm::mat4 model = glm::mat4(1.0f);          //模型矩阵(Model Matrix)
    glm::mat4 view = glm::mat4(1.0f);           //观察矩阵(View Matrix)
    glm::mat4 projection = glm::mat4(1.0f);     //投影矩阵(Projection Matrix)
    double timeValue = clock() * 10 / CLOCKS_PER_SEC;
    model = glm::rotate(model, (float) timeValue,
                        glm::vec3(0.5f, 1.0f, 0.0f));
    view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
    projection = glm::perspective(glm::radians(45.0f)
    , (float) screenW / (float) screenH, 0.1f, 100.0f);

    // retrieve the matrix uniform locations
    unsigned int modelLoc = glGetUniformLocation(shaderProgram, "model");
    unsigned int viewLoc = glGetUniformLocation(shaderProgram, "view");
    // pass them to the shaders (3 different ways)
    glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
    glUniformMatrix4fv(viewLoc, 1, GL_FALSE, &view[0][0]);
    setMat4("projection", projection);

        5、Z缓冲:

        OpenGL存储它的所有深度信息于一个Z缓冲(Z-buffer)中,也被称为深度缓冲(Depth Buffer)。GLFW会自动为你生成这样一个缓冲(就像它也有一个颜色缓冲来存储输出图像的颜色)。深度值存储在每个片段里面(作为片段的z值),当片段想要输出它的颜色时,OpenGL会将它的深度值和z缓冲进行比较,如果当前的片段在其它片段之后,它将会被丢弃,否则将会覆盖。这个过程称为深度测试(Depth Testing),它是由OpenGL自动完成的。

        然而,如果我们想要确定OpenGL真的执行了深度测试,首先我们要告诉OpenGL我们想要启用深度测试;它默认是关闭的。我们可以通过glEnable函数来开启深度测试。glEnable和glDisable函数允许我们启用或禁用某个OpenGL功能。这个功能会一直保持启用/禁用状态,直到另一个调用来禁用/启用它。现在我们想启用深度测试,需要开启GL_DEPTH_TEST:

    glEnable(GL_DEPTH_TEST);

        因为我们使用了深度测试,我们也想要在每次渲染迭代之前清除深度缓冲(否则前一帧的深度信息仍然保存在缓冲中)。就像清除颜色缓冲一样,我们可以通过在glClear函数中指定DEPTH_BUFFER_BIT位来清除深度缓冲:     

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   二、代码实现概览:

        1、代码地址:

         Github地址:https://github.com/wangyongyao1989/AndroidLearnOpenGL   

        2、 调用流程图:

        

      3、代码调用的过程:

代码从三个方法setMultiCube3DGLSLPath()、initMultiCube3DOpengl()、MultiCube3DOpenGLRenderFrame()对native层代码进行赋值、初始化、渲染的操作。

  • setMultiCube3DGLSLPath():把glsl文件的顶点和片段程序的路径地址传入C++层
  •  initMultiCube3DOpengl():初始OpenGL的相关操作,包括“获取着色器的string代码”,“加载着色器”,“链接编译顶点、片段程序”。
  • MultiCube3DOpenGLRenderFrame():渲染frame。

三、代码源码分析:

        1、顶点着色器程序:

#version 320 es

layout (location = 0) in vec3 aPos;     // 位置变量的属性位置值为0
layout (location = 1) in vec3 aTexCoord;

out vec2 TexCoord;

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

void main()
{
    // 注意乘法要从右向左读
    gl_Position = projection * view * model * vec4(aPos, 1.0f);
   	TexCoord = vec2(aTexCoord.x, aTexCoord.y);
}

        2、片段着色器程序:

#version 320 es
precision mediump float;
out vec4 FragColor;

in vec3 ourColor;
in vec2 TexCoord;

// texture sampler
uniform sampler2D texture1;
uniform sampler2D texture2;

void main()
{
    // linearly interpolate between both textures (80% container, 20% awesomeface)
    FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}

        3、链接编译顶点、片段程序:

        从1和2中的顶点着色器程序、片段着色器程序的地址传入到OpenGLBase的C++类中进行解析、加载着色器、链接编译顶点和片段程序的操作。

        a.文件解析:

        把顶点着色器程序、片段着色器程序解析成const char*

bool OpenGLBase::getSharderPath(const char *vertexPath, const char *fragmentPath) {
    ifstream vShaderFile;
    ifstream fShaderFile;

    // ensure ifstream objects can throw exceptions:
    vShaderFile.exceptions(ifstream::failbit | ifstream::badbit);
    fShaderFile.exceptions(ifstream::failbit | ifstream::badbit);
    try {
        // open files
        vShaderFile.open(vertexPath);
        fShaderFile.open(fragmentPath);
        stringstream vShaderStream, fShaderStream;
        // read file's buffer contents into streams
        vShaderStream << vShaderFile.rdbuf();
        fShaderStream << fShaderFile.rdbuf();
        // close file handlers
        vShaderFile.close();
        fShaderFile.close();
        // convert stream into string
        vertexCode = vShaderStream.str();
        fragmentCode = fShaderStream.str();
    }
    catch (ifstream::failure &e) {
        LOGE("Could not getSharderPath error :%s", e.what());
        return false;
    }

    gVertexShaderCode = vertexCode.c_str();
    gFragmentShaderCode = fragmentCode.c_str();

    return true;
}

        

       b.加载着色器:

            顶点着色器程序、片段着色器程序都可以通过这个方法进行加载:

/**
 * 加载着色器
 * @param shaderType
 * @param pSource
 * @return
 */
GLuint OpenGLBase::loadShader(GLenum shaderType, const char *pSource) {
    GLuint shader = glCreateShader(shaderType);     //创建着色器
    if (shader) {
        glShaderSource(shader, 1, &pSource, NULL);  //着色器源码附加到着色器对象上
        glCompileShader(shader);                    //编译着着色器
        GLint compiled = 0;
        glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
        if (!compiled) {
            GLint infoLen = 0;
            glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
            if (infoLen) {
                char *buf = (char *) malloc(infoLen);
                if (buf) {
                    glGetShaderInfoLog(shader, infoLen, NULL, buf);
                    LOGE("Could not compile shader %d:\n%s\n",
                         shaderType, buf);
                    free(buf);
                }
                glDeleteShader(shader);     //删除着色器对象
                shader = 0;
            }
        }
    }
    return shader;
}

        c.链接编译顶点和片段程序的操作:

/**
 *  连接编译顶点和片元程序
 * @param pVertexSource  顶点程序
 * @param pFragmentSource 片元程序
 * @return
 */
GLuint OpenGLBase::createProgram(const char *pVertexSource
, const char *pFragmentSource) {
    vertexShader = loadShader(GL_VERTEX_SHADER, pVertexSource);
    if (!vertexShader) {
        return 0;
    }

    fraShader = loadShader(GL_FRAGMENT_SHADER, pFragmentSource);
    if (!fraShader) {
        return 0;
    }

    shaderProgram = glCreateProgram();      //创建一个着色程序对象
    if (shaderProgram) {
        //把着色器附加到了程序对象上
        glAttachShader(shaderProgram, vertexShader);   
        checkGlError("glAttachShader");
        glAttachShader(shaderProgram, fraShader);
        checkGlError("glAttachShader");
        glLinkProgram(shaderProgram);   //链接程序对象
        GLint linkStatus = GL_FALSE;
        //检测链接着色器程序是否失败
        glGetProgramiv(shaderProgram, GL_LINK_STATUS, &linkStatus);  
        if (linkStatus != GL_TRUE) {
            GLint bufLength = 0;
            glGetProgramiv(shaderProgram, GL_INFO_LOG_LENGTH, &bufLength);
            if (bufLength) {
                char *buf = (char *) malloc(bufLength);
                if (buf) {
                    glGetProgramInfoLog(shaderProgram, bufLength, NULL, buf);
                    LOGE("Could not link shaderProgram:\n%s\n", buf);
                    free(buf);
                }
            }
            glDeleteProgram(shaderProgram);     //
            shaderProgram = 0;
        }
    }
    return shaderProgram;
}

        4、初始化OpenGL相关操作:

        从Android获取GLSurfaceView承载窗口获取width和height,设置glViewport显示窗口。绑定VAO/VBO/EBO——加载创建texture(纹理)——使用着色器程序。

//
// Created by MMM on 2024/7/30.
//
#include "OpenglesMultiCube3D.h"
#include <iostream>

bool OpenglesMultiCube3D::setupGraphics(int w, int h) {
    screenW = w;
    screenH = h;
    LOGI("setupGraphics(%d, %d)", w, h);
    LOGI("gVertexShaderCode :%s", gVertexShaderCode);
    LOGI("gFragmentShaderCode :%s", gFragmentShaderCode);
    gProgram = createProgram(gVertexShaderCode, gFragmentShaderCode);
    if (!gProgram) {
        LOGE("Could not create shaderProgram.");
        return false;
    }
    gvPositionHandle = glGetAttribLocation(gProgram, "vPosition");
    checkGlError("glGetAttribLocation");
    LOGI("glGetAttribLocation(\"vPosition\") = %d\n",
         gvPositionHandle);

    glViewport(0, 0, w, h);
    checkGlError("glViewport");
    LOGI("glViewport successed!");

    //清屏
    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    checkGlError("glClear");


    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);

    //绑定VAO
    glBindVertexArray(VAO);
    //把顶点数组复制到缓冲中供OpenGL使用
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices)
                , cubeVertices, GL_STATIC_DRAW);


    // 1. 设置顶点属性指针
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE
                    , 5 * sizeof(float), (void *) 0);
    glEnableVertexAttribArray(0);

    // texture coord attribute
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float),
                          (void *) (3 * sizeof(float)));
    glEnableVertexAttribArray(1);


    // load and create a texture
    glGenTextures(1, &texture1);
    glBindTexture(GL_TEXTURE_2D, texture1);

    // set texture wrapping to GL_REPEAT (default wrapping method)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // set texture filtering parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER
            , GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    // tell stb_image.h to flip loaded texture's on the y-axis.
    stbi_set_flip_vertically_on_load(true);

    if (data1) {
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB
                    , width1, height1
                    , 0, GL_RGB
                    , GL_UNSIGNED_BYTE, data1);
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    stbi_image_free(data1);


    // tell opengl for each sampler to which
    // texture unit it belongs to (only has to be done once)
    
    // don't forget to activate/use the shader before setting uniforms!
    glUseProgram(shaderProgram); 
    glUniform1i(glGetUniformLocation(shaderProgram, "texture1"), 0);

    return true;
}

        5、帧的渲染:

             绑定texture(纹理)——使用程序——开启深度测试——  模型矩阵(Model Matrix)观察矩阵(View Matrix)、投影矩阵(Projection Matrix)的初始化创建并与顶点着色器中model/view/projection 的uniform进行数据交换。模型矩阵(Model Matrix)观察矩阵(View Matrix)、投影矩阵(Projection Matrix)组合矩阵传入到gl_position做对应的顶点数据的位置变换。

void OpenglesMultiCube3D::renderFrame() {
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    // also clear the depth buffer now!
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 
    // bind Texture
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture1);
    //2、使用程序
    glUseProgram(gProgram);
    checkGlError("glUseProgram");
    //开启深度测试
    glEnable(GL_DEPTH_TEST);

    // create transformations
    glm::mat4 view = glm::mat4(1.0f);           //观察矩阵(View Matrix)
    glm::mat4 projection = glm::mat4(1.0f);     //投影矩阵(Projection Matrix)

    //观察矩阵(View Matrix)平移,类似于相机移动
    view = glm::translate(view, glm::vec3(0.0f, 0.0f, -6.0f));  
    projection = glm::perspective(glm::radians(45.0f)
        , (float) screenW / (float) screenH, 0.1f,100.0f);
    // pass them to the shaders (3 different ways)
    setMat4("projection", projection);
    setMat4("view", view);

    // render boxes
    glBindVertexArray(VAO);
    for (unsigned int i = 0; i < 10; i++) {
        // calculate the model matrix for each object 
        //and pass it to shader before drawing
        glm::mat4 model = glm::mat4(1.0f);      //模型矩阵(Model Matrix)
        //对获取到的模型移动到对应位置
        model = glm::translate(model, cubePositions[i]);    
        float angle = 20.0f * i;
        if (i < 6) {
            double timeValue = clock() * 10 / CLOCKS_PER_SEC;
            angle = timeValue * 25.0f;
        }
        //让模型经过旋转矩阵的变化
        model = glm::rotate(model, glm::radians(angle)
                , glm::vec3(1.0f, 0.3f, 0.5f));
        setMat4("model", model);
        glDrawArrays(GL_TRIANGLES, 0, 36);
    }

    checkGlError("glDrawArrays");
}

        四、效果展示:

        

MultiCube

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1972467.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

结构型设计模式:桥接/组合/装饰/外观/享元

结构型设计模式&#xff1a;适配器/代理 (qq.com)

浮动IP(Floating IP)计费;OpenStack算力共享;OpenStack实现资源虚拟化;算力调度策略

目录 浮动IP(Floating IP)计费 浮动IP的定义与作用 计费中的浮动IP数据 浮动IP在计费中的作用 OpenStack算力共享 一、OpenStack在算力共享中的角色 二、OpenStack与算力共享的结合方式 三、实际应用案例 算力调度策略 算力计费策略 OpenStack实现资源虚拟化 1.虚…

用于仅摄像头闭环驾驶的视觉语言模型

CarLLaVA: Vision language models for camera-only closed-loop driving 用于仅摄像头闭环驾驶的视觉语言模型 Abstract In this technical report, we present CarLLaVA, a Vision Language Model (VLM) for autonomous driving, developed for the CARLA Autonomous Driv…

【云原生】kubernetes最新版本1.30.2,集群搭建部署全方位攻略

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…

SimGCL graph contrastive learning by finding homophily in heterophily

发表于: Knowledge and Information Systems, ccfb 推荐指数: #paper/ ⭐ 总结: 重新定义了相似度矩阵, 重新定义了特征, 重新设计了节点删除概率等, 但是, 换汤不换药, 引入了大量的超参 (快 10 个了吧). 创新点不够, 所以 ccf B 期刊理所应该. (甚至我觉得更低) 相关知识: 本…

详细教程 MySQL 数据库 下载 安装 连接 环境配置 全面

数据库就是储存和管理数据的仓库&#xff0c;对数据进行增删改查操作&#xff0c;其本质是一个软件。 首先数据有两种&#xff0c;一种是关系型数据库&#xff0c;另一种是非关系型数据库。 关系型数据库是以表的形式来存储数据&#xff0c;表和表之间可以有很多复杂的关系&a…

通俗易懂玩Qt:时间滑动选择器实现(内附主要源码)

时间滑动选择器实现 组件说明&#xff1a; 本组件命名为时间滑动选择器&#xff0c;主要运用于 arm 平台下的触摸屏上&#xff0c;虽然 QT 自带有时间选择组件&#xff0c;但是对触摸屏的使用并不友好&#xff0c;为了提升项目界面的交互性&#xff0c;于是就有了时间滑动选择器…

【深海王国】初中生也能画的电路板?番外1:Arduino其他家族成员的拓展板开发(1)

Hi~ (o^^o)♪, 各位深海王国的同志们&#xff0c;早上下午晚上凌晨好呀~ 辛苦工作的你今天也辛苦啦(/≧ω) 今天大都督为大家带来电路板的番外系列——初中生也能画的电路板&#xff1f;番外1&#xff1a;Arduino其他家族成员的拓展板开发&#xff0c;带你给其他Arduino家族成…

数据库漫游记:表、视图、函数、存储过程及触发器之跨平台兼容性分析(上)

先言之 &#x1f31f;余撰此文&#xff0c;乃为导引初窥数据库之学人&#xff0c;俾其明了表、视图、函数、存储过程及触发器之义理&#xff0c;及其于诸般平台之上创建、修改与废弃之法式。盖初学之人&#xff0c;常陷于迷雾之中&#xff0c;难辨东西&#xff0c;故须详述而明…

lombok使用@slf4j 运行时提示找不到符号log(Missing POM for org.projectors:lombok:jar)

1.问题表现 原本是之前搭建好的工程&#xff0c;只是换了个开发环境重新启动就不行了。一直编译不通过&#xff01; 可以看到IDEA其实是引入了依赖的 都没有出现红色波浪线 <mapstruct.version>1.5.5.Final</mapstruct.version> <lombok.version>1.18.30<…

鸿蒙(API 12 Beta2版)NDK开发【JSVM-API使用规范】

JSVM-API使用规范 生命周期管理 【规则】 合理使用OH_JSVM_OpenHandleScope和OH_JSVM_CloseHandleScope管理JSVM_Value的生命周期&#xff0c;做到生命周期最小化&#xff0c;避免发生内存泄漏问题。 每个JSVM_Value属于特定的HandleScope&#xff0c;HandleScope通过OH_JSV…

【Python实战因果推断】71_图因果模型6

目录 Positivity Assumption An Identification Example with Data Confounding Bias Positivity Assumption 调整公式同样强调了正则性&#xff08;positivity&#xff09;的重要性。因为你正在对治疗和结果之间的差异在X的条件下求平均&#xff0c;你必须确保对于所有X的…

【32单片机篇】项目:WIFI天气预报

一、项目需求 使用 ESP8266 通过 HTTP 获取天气数据&#xff08;心知天气&#xff09;&#xff0c;并显示在 OLED 屏幕上。 按键 1 &#xff1a;循环切换今天/明天/后天天气数据&#xff1b; 按键 2 &#xff1a;更新天气 二、项目框图 三、硬件部分 四、项目源码及实现 1.项…

MySQL是怎样运行的——第1章 初识MySQL

文章目录 1. 1 MySQL的客户端/服务器架构1.2 安装MySQL&#xff08;略&#xff09;1.3 启动MySQL服务器程序1.4 启动MySQL客户端程序1.5 客户端与服务器连接的过程1.6 服务器处理客户端请求 1. 1 MySQL的客户端/服务器架构 MySQL的运行过程就是C/S架构。多个客户端程序连接到服…

洛谷 P1868 饥饿的奶牛

原题 题目描述 有一条奶牛冲出了围栏&#xff0c;来到了一处圣地&#xff08;对于奶牛来说&#xff09;&#xff0c;上面用牛语写着一段文字。 现用汉语翻译为&#xff1a; 有 N 个区间&#xff0c;每个区间x,y 表示提供的x∼y 共y−x1 堆优质牧草。你可以选择任意区间但不…

dockerfile定制镜像 docker-compose编排容器

1 dockerfile dockerfile本质上是利用了Linux系统的挂载&#xff08;UnionFS&#xff09;&#xff0c;将多个目录挂载到同一目录下&#xff0c;实现镜像的层叠式结构&#xff0c;从而实现功能聚合。 1.1 一个最简单的程序 package mainimport "fmt"func main() {f…

【leetcode详解】覆盖所有点的最少矩形数目(C++思路详解)

思路详解&#xff1a; 0. 题目情境并未限制矩形高度&#xff0c;故矩形数目的判断只和点的横坐标有关 1. 为了不重不漏地考虑到所有点&#xff0c;故笔者选择首先将二维数组中的点按横坐标的大小排序 //说明&#xff1a;本来笔者以为需要自定义sort排序&#xff0c;后来发现…

智慧水务项目(三)django(drf)+angular 18 创建系统管理的用户、角色、部门、权限管理等model

一、说明 添加各model 添加requirement.txt中的库 添加env.py中的动态配置 二、env.py全文 import os from smartwater.settings import BASE_DIR# # # ************** mysql数据库 配置 ************** # # # # 数据库地址 DATABASE_ENGINE "django.db.backends.…

SQL查询注意事项

判断字符串长度要用函数CHAR_LENGTH(str)&#xff0c;他会返回字符串的长度&#xff0c;如果使用length(str)函数&#xff0c;在对中文字符或特殊字符时&#xff0c;返回的是在当前编码下该字符的字节数。如在mysql中的utf-8编码情况下&#xff0c;length(&#xffe5;)返回结果…

ASUS/华硕幻14 2021 GA401Q系列 原厂win10系统 工厂文件 带F12 ASUS Recovery恢复

华硕工厂文件恢复系统 &#xff0c;安装结束后带隐藏分区&#xff0c;一键恢复&#xff0c;以及机器所有驱动软件。 系统版本&#xff1a;windows10 原厂系统下载网址&#xff1a;http://www.bioxt.cn 需准备一个20G以上u盘进行恢复 请注意&#xff1a;仅支持以上型号专用…