Qt OpenGL(10)光照模型基础

news2024/11/24 20:52:20

文章目录

  • 物体的光照模型
    • 立方体坐标
    • 构建立方体的6个面
  • 代码框架
    • widget.cpp
    • 顶点着色器
    • 片元着色器
  • Ambient 环境光
  • Diffuse 漫反色
    • 法向量
    • 计算漫反射分量
  • Specular Highlight镜面高光
    • 计算镜面反射分量
    • 补充:半程向量的使用

物体的光照模型

出于性能的原因,一般使用冯氏光照模型(Phong Lighting Model)。

冯氏光照模型的主要结构由3个分量组成:环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照。下面这张图展示了这些光照分量看起来的样子:
在这里插入图片描述

  • 环境光照(Ambient Lighting) :让物体从各个角度都能看到一些微弱的但很均匀的色彩
  • 漫反射光照(Diffuse Lighting):让物体产生明暗对比效果的一个重要的分量,产生了表面的粗糙度的感觉
  • 镜面光照(Specular Lighting) :让物体表面出现光泽,产生了表面的光滑度的感觉

基础光照 - LearnOpenGL CN

立方体坐标

直观上这几个点很容易就能获取,但注意这8个点不是单位球面上的点。如果想细分立方体获取球面,简单的方式是做归一化处理。

// 中心位于坐标原点,边与坐标轴平行的单位立方体的顶点
QVector3D v[] = {
    { -0.5, -0.5,  0.5 },
    { -0.5,  0.5,  0.5 },
    {  0.5,  0.5,  0.5 },
    {  0.5, -0.5,  0.5 },
    { -0.5, -0.5, -0.5 },
    { -0.5,  0.5, -0.5 },
    {  0.5,  0.5, -0.5 },
    {  0.5, -0.5, -0.5 },
};

// 在细分前归一化处理8个顶点
    for (int i = 0; i < 8; ++i) {
        v[i].normalize ();
        qout << v[i];
    }

    recursion(0);

构建立方体的6个面

在这里插入图片描述

// 组成6个面,12个三角形,36个顶点(每个顶点赋一种颜色),按照右手法则,保证正面朝外
int tindices[6][4] = {
   {1,0,3,2},	// 103  和 132 两个三角形,右手法则
   {2,3,7,6}, // 237  和 276,下同里
   {3,0,4,7},
   {6,5,1,2},
   {4,5,6,7},
   {5,4,0,1}
};

// 6个面,每面两个三角形,可进一步细分,如果不想细分,传入n=0即可
void recursion( int n ){
    for (int i = 0; i < 6; ++i) {
        divide_triangle (v[ tindices[i][0] ],v[tindices[i][1]],v[tindices[i][2]], n);
        divide_triangle (v[ tindices[i][0] ],v[tindices[i][2]],v[tindices[i][3]], n);
    }
}

在这里插入图片描述 在这里插入图片描述
   recursion(0);           recursion(3);

代码框架

widget.cpp

#include "Widget.h"
#include "qmatrix4x4.h"
#include "qvector3d.h"
#include "qvector4d.h"
#include <QApplication>
#include <QMouseEvent>
#include <QThread>
#include <QVector3D>
#include <vector>

#define qRandom   QRandomGenerator::global ()
#define qout if( 1 ) qDebug() << __FILE__ << __LINE__ << ": "

bool rotateFlag = false;


// 中心位于坐标原点,边与坐标轴平行的单位立方体的顶点
QVector3D v[] = {
    { -0.5, -0.5,  0.5 },
    { -0.5,  0.5,  0.5 },
    {  0.5,  0.5,  0.5 },
    {  0.5, -0.5,  0.5 },
    { -0.5, -0.5, -0.5 },
    { -0.5,  0.5, -0.5 },
    {  0.5,  0.5, -0.5 },
    {  0.5, -0.5, -0.5 },
};


int tindices[6][4] = {
   {1,0,3,2},
   {2,3,7,6},
   {3,0,4,7},
   {6,5,1,2},
   {4,5,6,7},
   {5,4,0,1}
};

std::vector<QVector3D> vdata;
std::vector<QVector3D> normals;

void triangle(QVector3D a, QVector3D b, QVector3D c){
    vdata.push_back (a);
    vdata.push_back (b);
    vdata.push_back (c);

    auto normal = QVector3D::crossProduct (a-b,b-c).normalized();

    normals.push_back (normal);
    normals.push_back (normal);
    normals.push_back (normal);
}

void divide_triangle(QVector3D a, QVector3D b, QVector3D c, int n ){
    QVector3D ab, bc, ca;
    if( n > 0 ) {
        // 如果使用归一化,顶点和的结果是否除以 2 都不影响结果
        ab = ( a + b )/2;
        ca = ( a + c )/2;
        bc = ( b + c )/2;

        ab.normalize ();
        ca.normalize ();
        bc.normalize ();

        divide_triangle(a, ab, ca, n-1);
        divide_triangle(b, bc, ab, n-1);
        divide_triangle(c, ca, bc, n-1);
        divide_triangle(ab,bc, ca, n-1);
    }
    else
        triangle (a,b,c);
}


void recursion( int n ){
    for (int i = 0; i < 6; ++i) {
        divide_triangle (v[tindices[i][0]],v[tindices[i][1]],v[tindices[i][2]], n);
        divide_triangle (v[tindices[i][0]],v[tindices[i][2]],v[tindices[i][3]], n);
    }
}



// 相对于每个坐标轴的旋转角度(°)
enum {Xaxis, Yaxis, Zaxis, NumAxes};
int         Axis  = Zaxis;
//float   Theta[NumAxes] = { 0.0, 0.0, 0.0 };
//QVector3D   Theta = { 20.0, -10.0, 0.0 };
QVector3D   Theta = { 70.0, 0.0, 0.0 };
//QVector3D   Theta;


Widget::Widget(QWidget *parent)
    : QOpenGLWidget(parent)
{
    setWindowTitle ("12_basic_lighting");
    resize (100,100);

    for (int i = 0; i < 8; ++i) {
        v[i].normalize ();
        qout << v[i];
    }

    recursion(0);

}

Widget::~Widget()
{
    makeCurrent ();
    glDeleteBuffers (1,&VBO);
    glDeleteVertexArrays (1,&VAO);
    doneCurrent ();
}


void Widget::initializeGL()
{
    initializeOpenGLFunctions ();


    const char *version =(const char *) glGetString (GL_VERSION);
    qout << QString(version);

    // ---------------------------------
    // 创建一个顶点数组对象
    glGenVertexArrays (1,&VAO);
    glBindVertexArray(VAO);

    // ---------------------------------

    // 创建并初始化一个缓冲区对象
    glGenBuffers (1,&VBO);
    glBindBuffer (GL_ARRAY_BUFFER,VBO);
    glBufferData (GL_ARRAY_BUFFER,
                  sizeof(QVector3D)*vdata.size ()  + sizeof(QVector3D)*normals.size () ,
                  nullptr,
                  GL_STATIC_DRAW);

    glBufferSubData (GL_ARRAY_BUFFER, 0 , sizeof(QVector3D)*vdata.size () , vdata.data ());
    glBufferSubData (GL_ARRAY_BUFFER, sizeof(QVector3D)*vdata.size ()  ,sizeof(QVector3D)*normals.size ()  , normals.data ());

    glVertexAttribPointer(0,
                          3,        // 变量中元素的个数,
                          GL_FLOAT, // 类型
                          GL_FALSE, // 标准化,是否在 [-1,1] 之间
                          sizeof(QVector3D),  // 步长
                          (void*)0 );   // 变量的偏移量,在多个变量混合时指定变量的偏移
    glEnableVertexAttribArray(0); // 使用 location = 0 的索引

    glVertexAttribPointer(1,
                          3,        // 变量中元素的个数,
                          GL_FLOAT, // 类型
                          GL_FALSE, // 标准化,是否在 [-1,1] 之间
                          sizeof(QVector3D),  // 步长
                          (void*)( sizeof(QVector3D) * vdata.size ()  ) );   // 变量的偏移量,在多个变量混合时指定变量的偏移
    glEnableVertexAttribArray(1); // 使用 location = 0 的索引



    // ---------------------------------
    QString filename = ":/shader";
    shaderProgram.addShaderFromSourceFile (QOpenGLShader::Vertex,   filename+".vert");
    shaderProgram.addShaderFromSourceFile (QOpenGLShader::Fragment, filename+".frag");
    shaderProgram.link ();

    glBindBuffer (GL_ARRAY_BUFFER,0);

    // 控制多边形的正面和背面的绘图模式
//    glPolygonMode (GL_FRONT_AND_BACK,GL_LINE);

    // 深度测试
    glEnable(GL_DEPTH_TEST);

}



void Widget::paintGL()
{
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);   // 设置背景色
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glBindVertexArray(VAO);
    shaderProgram.bind ();

    // uniform 变量
    QVector3D lightColor (  1.0f, 1.0f,  1.0f );
    QVector3D objectColor(  1.0f, 0.5f,  0.31f);
    QVector3D objectPos  ( -1.5f, 0.0f, -5.0f );
    QVector3D lightSourcePos   ( 0, 1.0f, 1.0f );
    float ambientStrength = 0.2f;
    float specularStrength = 0.5;

    // ---------------------------------
    // // 绘制 物体
    QMatrix4x4 translateModel;
//    translateModel.translate (objectPos);

    QMatrix4x4 rx,ry,rz;
    rx.rotate (Theta[Xaxis],1.0,0.0,0.0);
    ry.rotate (Theta[Yaxis],0.0,1.0,0.0);
    rz.rotate (Theta[Zaxis],0.0,0.0,1.0);


    QMatrix4x4 scaleModel;
    scaleModel.scale (0.5);


    QMatrix4x4 model = translateModel * rx * ry * rz * scaleModel;

    QMatrix4x4 view;
    view.setColumn (3,QVector4D(0,0,-3,1));


    QMatrix4x4 projection;
    projection.perspective (45.0f, (float)width ()/height (), 0.1f, 100.0f);


    shaderProgram.setUniformValue ("model", model);
    shaderProgram.setUniformValue ("view", view);
    shaderProgram.setUniformValue ("projection", projection);
    shaderProgram.setUniformValue ("lightColor",  lightColor);
    shaderProgram.setUniformValue ("objectColor", objectColor  );
    shaderProgram.setUniformValue ("lightPos", lightSourcePos  );
    shaderProgram.setUniformValue ("ambientStrength",ambientStrength  );
    shaderProgram.setUniformValue ("specularStrength",specularStrength  );
    shaderProgram.setUniformValue ("lighting", true  );
    shaderProgram.setUniformValue ("viewPos", QVector3D(0,0,3) );

//    qout << lightColor * objectColor;

    glDrawArrays (GL_TRIANGLES, 0, (int)vdata.size ());


    // 绘制 灯
    QMatrix4x4 lightModel;
    lightModel.translate (5,5,-15);  // 灯是随意摆放,对光照计算没啥用处。只是为了简单示意
//    lightModel.scale (0.2f);
//    lightColor /= ambientStrength;
    shaderProgram.setUniformValue ("model", lightModel);
    shaderProgram.setUniformValue ("objectColor", 0.2f, 0.2f, 0.2f);
    shaderProgram.setUniformValue ("lightColor",  lightColor / ambientStrength);
    shaderProgram.setUniformValue ("lighting",  false);
    glDrawArrays (GL_TRIANGLES, 0, (int)vdata.size () );


    if( rotateFlag ){
        QThread::currentThread ()->msleep (50);
        Theta[Axis] += 2;
        if( Theta[Axis] > 360.0 )
            Theta[Axis] = - 360.0;
        update ();
    }
}

void Widget::mousePressEvent(QMouseEvent *e)
{
    rotateFlag = !rotateFlag;
    if(rotateFlag == false) {
        qout << Theta;
        return;
    }

    auto b = e->button ();
    switch (b) {
        case Qt::LeftButton:
            Axis = Xaxis;
            break;
        case Qt::MiddleButton:
            Axis = Yaxis;
            break;
        case Qt::RightButton:
            Axis = Zaxis;
            break;
        default:
            break;
    }
    update ();
}

顶点着色器

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

out vec3 FragPos;
out vec3 Normal;

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

void main()
{
    FragPos = vec3(model * vec4(aPos, 1.0));
    Normal = mat3(transpose(inverse(model))) * aNormal;

    gl_Position = projection * view * vec4(FragPos, 1.0);
}

片元着色器

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

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

void main()
{
    // ambient
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * lightColor;

    // diffuse
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = diff * lightColor;

    // specular
    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;
    gl_FragColor = vec4(result, 1.0);
}

Ambient 环境光

在顶点着色器中实现,简单来水就是用一个很小的环境光强度乘上光 再乘上物体的颜色就行。

效果:均匀着色

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

out vec3 result;

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

uniform vec3 lightColor;
uniform vec3 objectColor;

uniform float ambientStrength;

void main()
{

    gl_Position = projection * view * model * vec4(aPos, 1.0);

    vec3 ambient = ambientStrength * lightColor;

    result = ( ambient   ) * objectColor;
}

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述
  strength : 0.1      strength : 0.2       strength: 0.3       strength: 0.4

Diffuse 漫反色

漫反射开始对物体产生显著的明暗效果。

从反射角度可以看到明亮的表面,偏离反射角度观察,物体表面开始暗淡下去。

在这里插入图片描述

所以,计算漫反射光照需要什么?

  • 法向量:一个垂直于顶点表面的向量。
  • 定向的光线:作为光源的位置与片段的位置之间向量差的方向向量。

法向量

法向量是一个垂直于顶点表面的(单位)向量。

由于顶点本身并没有表面(它只是空间中一个独立的点),我们利用它周围的顶点来计算出这个顶点的表面。

我们对构成立方体的每一个三角形的3个顶点计算法向量,可以简单地把法线数据手工添加到顶点数据中。

void triangle(QVector3D a, QVector3D b, QVector3D c){
    vdata.push_back (a);
    vdata.push_back (b);
    vdata.push_back (c);

    auto normal = QVector3D::crossProduct (a-b,b-c).normalized();
    normals.push_back (normal);
    normals.push_back (normal);
    normals.push_back (normal);
}

计算漫反射分量

  1. 计算光源和片段位置之间的光线向量

    vec3 lightDir = normalize(lightPos - FragPos);
    
  2. 计算光线向量和法向量的点积

    float diff = max(dot(norm, lightDir), 0.0);
    
  3. 得到漫反射分量

    vec3 diffuse = diff * lightColor;
    

在这里插入图片描述

Specular Highlight镜面高光

和漫反射光照一样,镜面光照也决定于光的方向向量和物体的法向量,但是它也决定于观察方向,例如玩家是从什么方向看向这个片段的。

镜面光照决定于表面的反射特性。如果我们把物体表面设想为一面镜子,那么镜面光照最强的地方就是我们看到表面上反射光的地方。你可以在下图中看到效果:

img

计算镜面反射分量

  1. 定义一个镜面强度

    float specularStrength = 0.5;
    
  2. 计算视线和片段位置之间的向量

    vec3 viewDir = normalize(viewPos - FragPos);
    
  3. 计算光线的镜面反射向量

    vec3 reflectDir = reflect(-lightDir, norm);
    

    reflect函数是个内置函数,要求第一个向量是光源指向片段位置的向量,但是lightDir当前正好相反,是从片段指向光源(由先前我们计算lightDir向量时,减法的顺序决定)。为了保证我们得到正确的reflect向量,我们通过对lightDir向量取反来获得相反的方向。第二个参数要求是一个法向量,所以我们提供的是已标准化的norm向量。

  4. 计算视线向量和镜面反射向量的点积

    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
    

在这里插入图片描述

计算得到点乘(并确保不是负值),然后取它的32次幂。这个32是高光的反光度(Shininess)。一个物体的反光度越高,反射光的能力越强,散射得越少,高光点就会越小。从上面的图片里,你会看到不同反光度的视觉效果影响。

  1. 计算镜面高光分量

    vec3 specular = specularStrength * spec * lightColor;
    

另外:这些光照计算可以在 顶点着色器中实现。在顶点着色器中实现的冯氏光照模型叫做Gouraud着色(Gouraud Shading),而不是冯氏着色(Phong Shading)。

补充:半程向量的使用

参考 Blinn-Phong光照模型解析及其实现_晴夏。的博客-CSDN博客_blinn phong

相关的讲解在 GAMES101-现代计算机图形学入门-闫令琪 视频中学习

在镜面反射中,我们通过计算出射方向和视点的夹角来确定反射光的强度,在计算机中计算反射向量R的计算量比较大。因此我们可以用其他计算方法近似替代。

float specularStrength = 0.50;
vec3  viewDir = normalize(viewPos - FragPos);
vec3  H_norm = normalize(lightDir + viewDir);  // 使用半程向量,只是简单的加减就可以获取到
float spec = pow(max(dot(H_norm, norm), 0.0), 256);
vec3  specular = specularStrength * spec * lightColor;

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

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

相关文章

思科Cisco交换机的基本命令

一、设备的工作模式1、用户模式Switch>可以查看交换机的基本简单信息&#xff0c;且不能做任何修改配置&#xff01;2、特权模式Switch> enable Switch#可以查看所有配置&#xff0c;且不能修改配置&#xff01;3、全局配置模式switch# configure terminal switch(config…

Redis基础——SpringDataRedis快速入门

文章目录1. SpringDataRedis介绍2. SpringDataRedis快速入门2.1 SpringDataRedis的使用步骤1. SpringDataRedis介绍 SpringData是Spring中数据操作的模块&#xff0c;包含对各种数据库的集成&#xff0c;其中对Redis的集成模块就叫做SpringDataRedis 官方网址 提供了对不同Redi…

参加猿代码超算实习生计划靠谱吗?

猿代码近期推出了超级实习生计划&#xff0c;相比市面上同类型实习类产品&#xff0c;超算实习生计划服务群体范围更小一些&#xff0c;主要服务于有志于从事芯片行业的大学生们&#xff0c;专做芯片赛道实习就业产品。那么至今为止有人参加过猿代码超算实习生计划吗?这个产品…

〖产品思维训练白宝书 - 核心竞争力篇①〗- 产品经理 的核心竞争力解读

大家好&#xff0c;我是 哈士奇 &#xff0c;一位工作了十年的"技术混子"&#xff0c; 致力于为开发者赋能的UP主, 目前正在运营着 TFS_CLUB社区。 &#x1f4ac; 人生格言&#xff1a;优于别人,并不高贵,真正的高贵应该是优于过去的自己。&#x1f4ac; &#x1f4e…

Top 命令中的 Irix 模式与 Solaris 模式(解释单个进程cpu占比为何会超过100%?)

文章目录 背景top cpu 栏位说明Solaris 模式Irix ModeTOP -H切换线程总结背景 关于top命令用了很久了,但是一直对单进程占用cpu占比为何会超过100%认识不够深刻。 top cpu 栏位说明 1. %CPU -- CPU UsageThe tasks share of the elapsed CPU time since the last screen…

深度学习入门基础CNN系列——卷积计算

卷积计算 卷积是数学分析中的一种积分变换的方法&#xff0c;在图像处理中采用的是卷积的离散形式。这里需要说明的是&#xff0c;在卷积神经网络中&#xff0c;卷积层的实现方式实际上是数学中定义的互相关 &#xff08;cross-correlation&#xff09;运算&#xff0c;与数学…

【项目实战】package.json你需要了解内容

package.json文件^和~区别 在项目开发中常引用npm包&#xff0c;那么package.json文件^和~区别是什么&#xff1f; ^意思是将当前库的版本更新到第一个数字&#xff0c; 例&#xff1a;"^4.1.0"是库会更新到4.X.X的最新版本&#xff0c;但不会更新到5.X.X版本。~意…

基于卷积深度神经网络的句子单子关系分类(附完整版代码)

基于卷积深度神经网络的关系分类 直接先上结果: 用于关系分类的最先进的方法主要基于统计机器学习,并且它们的性能很大程度上取决于提取的特征的质量。提取的特征通常来自预先存在的自然语言处理(NLP)系统的输出,这导致错误在现有工具中的传播和阻碍这些系统的性能。在本文…

计算机网络学习笔记(四)网络层 - 数据层面

文章目录网络层概述1.转发和路由选择2.网络服务模型网际协议1.IPv4(1) IPv4数据报格式&#xff08;2&#xff09;IPv4数据报分片&#xff08;3&#xff09;IPv4编址2.IPv6(1) IPv6数据报格式&#xff08;2&#xff09;IPv4迁移到IPv6网络层概述 1.转发和路由选择 网络层的作用…

【虹科回顾】2022网络安全精选内容回顾

“我们身上最有价值的东西&#xff0c; 不是证书和技能&#xff0c; 而是过去一切经历的总和。” 2022年已落幕&#xff0c;过去再也不会重来&#xff0c;无论是怎样的一年&#xff0c;都是我们自己生命中特别的一年。 2023年已来&#xff0c;我无法给您提供证书或者技能&a…

Jupyter Lab 的 10 个有用技巧

JupyterLab是 Jupyter Notebook「新」界面。它包含了jupyter notebook的所有功能&#xff0c;并升级增加了很多功能。它最大的更新是模块化的界面&#xff0c;可以在同一个窗口以标签的形式同时打开好几个文档&#xff0c;同时插件管理非常强大&#xff0c;使用起来要比jupyter…

el-date-picker实现通过其他方式触发日期选择器

el-date-picker 目前只能通过点击input输入框触发日期选择器&#xff0c;项目中需要通过其他方式触发日期选择器同时把input输入框去掉&#xff0c;如点击按钮 该模块由于后端接口数据传输限制 在前面文章里做了些许改动。 需求左右切换 可以快速找到年份&#xff0c;于是添加…

大数据技术架构(组件)——Hive:环境准备3

1.0.2、服务启动在搭建Hadoop的环节中&#xff0c;已经将Hadoop服务启动了&#xff0c;这里将Hive Metastore服务启动hive --service metastore1.0.2.1、服务端启动Debug模式为了方便学习&#xff0c;大家可以在IDEA中打开Terminal&#xff0c;开启debug模式和metastore服务启动…

C++设计模式实践——线上购物系统

一、系统的主要目标与功能 在本次设计中&#xff0c;考虑到目前疫情反复不断&#xff0c;为了方便群众&#xff0c;超市都推出在线购物并有配送员送货&#xff0c;于是我设计了一个超市在线网上购物送货的系统&#xff0c;这个系统的主要目标是帮助人们在家里购买自己需要的套…

Rust 学习笔记

参考自Rust 程序设计语言 简体中文版 1. Hello world 2. Cargo&#xff08;Rust 的构建系统和包管理器&#xff09; 使用 Cargo 创建项目 Cargo 配置文件 Cargo 目录结构 构建并运行 Cargo 项目 发布&#xff08;release&#xff09;构建 Cargo 常用命令 cargo build&#xf…

【青训营】Go的测试

Go的测试 测试主要包括&#xff1a;回归测试、集成测试、单元测试 一、单元测试 其中测试单元可以是函数&#xff0c;也可以是模块 规则&#xff1a; 1.所有测试文件都以_test.go结尾 2.测试函数命名规范:func TestXxx(*Testing.T) 3.初始化逻辑需要放置在TestMain中 以下是…

django框架【待续】

目录简介MVC与MTV模型MVCMTV创建项目目录生命周期静态文件配置&#xff08;无用&#xff09;启动django[启动](https://www.cnblogs.com/xiaoyuanqujing/articles/11902303.html)路由分组无名分组有名分组路由分发反向解析反向解析结合分组名称空间re_path与path自定义转换器视…

为什么普通人赚钱这么难?普通人的赚钱之路在哪里

前几天听一个老家的朋友说辛辛苦苦一整年&#xff0c;发现并没有赚到什么钱。付出与收入不成正比。首先要知道勤奋、努力并不一定就能够赚到钱像送外卖的&#xff0c;工地上班的&#xff0c;厂里上班的哪个不勤奋但他们即使非常努力工作一个月&#xff0c;扣除基本开支&#xf…

这是一篇知识帖:终于能明白云原生技术的概念和可落地的应用分享

随着云计算的发展和普及&#xff0c;云原生概念的热度也越来越高&#xff0c;到底什么是云原生&#xff1f;和我们日常工作有什么关系&#xff1f;本文是向大家介绍云原生技术的概念和要点&#xff0c;帮助大家快速了解和学习云原生&#xff0c;&#xff0c;便于大家了解工作的…

【PHP了解】PHP脚本语言基础

PHP PHP 文件的默认文件扩展名是 “.php”PHP 文件通常包含 HTML 标签和一些 PHP 脚本代码 例&#xff1a; <!DOCTYPE html> <html> <body><?php xxxxxxxxxxxxxxxxx; ?></body> </html>语法 代码框架 <?php 开始&#xff0c;以 ?…