1.顶点格式改动:

之前写的几个例子都是2D的例子,使用的顶点格式是已经经过变换的顶点格式。而要写3D程序,我们就必须要用未经过变换的顶点。所以在定义顶点时也要有所改变:

//------------绘制图形步骤1.定义灵活顶点格式
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)//坐标为经过变换的屏幕坐标,顶点的颜色//------------绘制图形步骤2.根据上面定义的顶点格式,创建一个顶点的结构体
struct stVertex
{float x, y, z;         //位置坐标DWORD dwColor;            //颜色
};

这里我使用了顶点缓冲区绘图,通过三个顶点绘制了一个三角形,顶点坐标如下:

<span style="white-space:pre">      </span>{-1.0f, -1.0f, 0.0f, D3DCOLOR_XRGB(255, 0, 0)},{ 1.0f, -1.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0)},{ 0.0f,  1.0f, 0.0f, D3DCOLOR_XRGB(0, 0, 255)}

打开的时候吓了我一跳。。。好大。。。

变换总览:

没有经过变换的3D世界,我们只能通过+z方向看。看到的东西也比较单调。要想看到一个比较真实的3D世界,就要通过各种变换了。

常用的变换一共有3种,世界变换,取景变换,投影变换。当然还有一个视口变换,不过个人感觉不需要矩阵操作,只是单纯的显示区域改变,与前面三种变换还是有点差距的。

WorldTransform(世界变换)

 世界变换事实上就是将物体顶点从模型空间转换到世界空间中,模型空间其实就是在三维设计软件(如3DSMAX)中为物体设定的坐标系,也称局部坐标系或者本地坐标系。而世界坐标则是所有物体都是用同一个世界坐标原点的坐标系,变换就是指对模型进行平移,旋转,缩放及它们的任意组合变换。
平移:
//平移D3DXMATRIXA16 matWorld;//矩阵平移操作D3DXMatrixTranslation(&matWorld,         //输出矩阵0.0f, 1.0f, 0.0f    //平移的距离,x,y,z);//进行平移变换g_pDevice->SetTransform(D3DTS_WORLD, &matWorld);
旋转:
    D3DXMATRIXA16 matWorld;//生成绕Y轴旋转矩阵,存储于矩阵中D3DXMatrixRotationY(&matWorld,              //输出矩阵timeGetTime()/150.0f  //角度);g_pDevice->SetTransform(D3DTS_WORLD, &matWorld);

缩放:

//缩放D3DXMATRIXA16 matScaling;//矩阵缩放,存入上面的矩阵中D3DXMatrixScaling(&matScaling,            //输出矩阵0.5f, 0.5f, 1.0f     //缩放的比例);g_pDevice->SetTransform(D3DTS_WORLD, &matScaling);


从这里我们发现,不论是哪种操作,都是预先定义一个矩阵,然后通过一个固定的函数,接受相关参数,生成一个矩阵,存储于我们定义的矩阵中,然后通过SetTransform()方法,将矩阵作为参数,进行相应的变换。

但是如果我们想要同时进行这几种变换要怎么办呢?
答案是将几个矩阵相乘,然后将最终的矩阵SetTransform方法用于变换即可。
通过D3DXMatrixMultiply函数可以计算两个矩阵的乘积。

//矩阵相乘D3DXMATRIXA16 matWorld;D3DXMatrixMultiply(&matWorld,            //结果&matScaling,        //缩放矩阵&matTurn          //旋转矩阵);//将第一次的结果再和平移矩阵相乘D3DXMatrixMultiply(&matWorld,          //结果&matWorld,          //第一次的结果&matMove            //平移矩阵);//将结果用于世界变换g_pDevice->SetTransform(D3DTS_WORLD, &matWorld);

这里,要进行世界变换一定要按照先缩放,再旋转,最后再平移的顺序。
而且貌似只能进行一次变换,即要将每个变换的矩阵求出来,然后相乘之后得到结果,将结果用于世界变换操作。
当然还有一种更简单的乘法操作,DX为我们提供了矩阵关于*的操作符重载,即我们可以直接用*进行乘法操作,而不需要繁琐的API了:
//上面的一大段乘法代码可以用这一句替换。。。
matWorld = matScaling * matTurn * matMove;

ViewTransform (取景变换)

世界变换使一个物体从自身坐标系进入到了世界坐标系,并且可以进行缩放,平移和旋转。但是我们看的方向还是固定的。我们可以通过取景变换,改变观察点位置以及摄像机的位置,使我们换个角度看世界。
取景变换主要涉及三个参数:
1.摄像机位置:我们观察这个世界的位置。
2.观察点位置:我们要观察的点。
3.摄像机上向量:通常为(0.0f, 1.0f, 0.0f)。
然后定义一个矩阵,通过D3DXMatrixLookAtLH函数生成取景变换的矩阵。最后通过SetTransform方法进行取景变换。
//ViewTransform:取景变换D3DXVECTOR3 vEyePt(0.0f, 0.0f, -5.0f);       //摄像机世界坐标D3DXVECTOR3 vLookatPt(0.0f, 0.0f, 0.0f);   //观察点世界坐标D3DXVECTOR3 vUpVec(0.0f, 1.0f, 0.0f);      //摄像机的上向量,通常为(0.0f, 1.0f, 0.0f)D3DXMATRIXA16 matView;                      //View变换的矩阵//根据上面的结果计算出矩阵,存入矩阵中D3DXMatrixLookAtLH(&matView, &vEyePt, &vLookatPt, &vUpVec);//进行取景变换g_pDevice->SetTransform(D3DTS_VIEW, &matView);

这样,我们就可以换一个角度看我们的3D世界了。

ProjectionTransform (投影变换)

上面的取景变换,改变了摄像机的位置以及观察点。但是为了将3D物体显示到2D屏幕上,还是需要经过投影变换。
投影窗口是一个二维平面,用于描述三维物体模型经过透视投影后的二维图像,在Direct3D中投影窗口平面默认定义为z=1的平面。
虚拟摄像机与投影窗口平面共同构成了一个对观察者可见的三维空间。在3D图形学中这部分空间被称作视截体(View Frustum),位于视截体内的物体模型被映射到二维投影投影平面上,而位于视截体外的物体模型或者其中一部分将不可见,这个过程我们称作裁剪。
这里DX也给我们提供好了一个函数D3DXMatrixPerspectiveFovLH,我们只需要填充这个函数,就可以生成一个投影变换的矩阵,再通过SetTransform函数进行投影变换即可。
D3DXMATRIXA16 matProj;                   //投影变换矩阵//生成投影变换矩阵,存入上面的矩阵中D3DXMatrixPerspectiveFovLH(&matProj,       //输出结果矩阵D3DX_PI / 4,        //视域角度,一般为PI/41.0f,              //显示屏的长宽比1.0f,          //视截体中近截面距离摄像机的位置100.0f             //视截体中远截面距离摄像机的位置);//进行投影变换g_pDevice->SetTransform(D3DTS_PROJECTION, &matProj);

ViewportTransform(视口变换)
这个最简单!所谓视口,就是我们在窗口中看世界的一个矩形区域。只需要填充一个结构体,然后通过SetViewPort方法就可以设置视口。
//ViewportTransform:视口变换D3DVIEWPORT9 vp = {0,              //视口的左上角X坐标0,               //视口的左上角Y坐标800,         //视口的宽度500,         //视口的高度0,               //深度缓存中的最小深度值1              //深度缓存中的最大深度值};g_pDevice->SetViewport(&vp);
这样就修改了我们的视口。

完整的Demo

学会了这几种基本的变换,综合运用一下。这里我画了一个三角形,使其绕着Y轴旋转。并改变了视口以及视角。

// D3DDemo.cpp : 定义应用程序的入口点。
//#include "stdafx.h"
#include "D3DDemo.h"#define MAX_LOADSTRING 100// 全局变量:
HINSTANCE hInst;                                // 当前实例
TCHAR szTitle[MAX_LOADSTRING];                  // 标题栏文本
TCHAR szWindowClass[MAX_LOADSTRING];            // 主窗口类名// 此代码模块中包含的函数的前向声明:
HWND                g_hWnd;
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);//---------改造3D窗口需要的内容------------
LPDIRECT3D9 g_pD3D = NULL; //D3D接口指针
LPDIRECT3DDEVICE9 g_pDevice = NULL;//D3D设备指针//------------绘制图形步骤1.定义灵活顶点格式
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)//坐标为经过变换的屏幕坐标,顶点的颜色//------------绘制图形步骤2.根据上面定义的顶点格式,创建一个顶点的结构体
struct stVertex
{float x, y, z;         //位置坐标DWORD dwColor;            //颜色
};//----------绘制图形步骤3.声明一个顶点缓冲区指针&一个索引缓冲区指针
LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;//初始化顶点缓冲区
void initVB()
{//----------绘制图形步骤4.定义一个结构体数组用来给每个顶点赋值//数组中存储当前程序中顶点的数据stVertex vertex[] = {{-1.0f, -1.0f, 0.0f, D3DCOLOR_XRGB(255, 0, 0)},{ 1.0f, -1.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0)},{ 0.0f,  1.0f, 0.0f, D3DCOLOR_XRGB(0, 0, 255)}};//----------绘制图形步骤5.为定点缓冲区分配内存,并将数组中的顶点值拷贝到顶点缓冲区中//通过设备指针来创建顶点缓冲区,用来存储顶点数据g_pDevice->CreateVertexBuffer(sizeof(vertex),                    //顶点缓冲区大小D3DUSAGE_WRITEONLY,                //顶点缓冲区作用D3DFVF_CUSTOMVERTEX,           //通知系统顶点格式D3DPOOL_MANAGED,              //顶点缓冲区存储位置,此处表示由系统处理&g_pVB,                         //返回顶点缓冲区指针NULL                         //系统保留参数,NULL);void* pVertices = NULL;//锁定顶点缓冲区,向其中拷贝数据g_pVB->Lock(0,                             //锁定的偏移量sizeof(vertex),                 //锁定的大小&pVertices,                      //锁定之后存储空间0                             //锁定的标识,0);//将数组中的内容拷贝到缓冲区中memcpy(pVertices, vertex, sizeof(vertex));//解锁g_pVB->Unlock();}void onCreatD3D()
{g_pD3D = Direct3DCreate9(D3D_SDK_VERSION);if (!g_pD3D)return;//检测硬件设备能力的方法/*D3DCAPS9 caps;ZeroMemory(&caps, sizeof(caps));g_pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps);*///获得相关信息,屏幕大小,像素点属性D3DDISPLAYMODE d3ddm;ZeroMemory(&d3ddm, sizeof(d3ddm));g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);//设置全屏模式D3DPRESENT_PARAMETERS d3dpp;ZeroMemory(&d3dpp, sizeof(d3dpp));/*d3dpp.Windowed = false;d3dpp.BackBufferWidth = d3ddm.Width;d3dpp.BackBufferHeight = d3ddm.Height;*/d3dpp.Windowed = true;d3dpp.BackBufferFormat = d3ddm.Format;d3dpp.BackBufferCount = 1;d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;//交换后原缓冲区数据丢弃//是否开启自动深度模板缓冲d3dpp.EnableAutoDepthStencil = true;//当前自动深度模板缓冲的格式d3dpp.AutoDepthStencilFormat = D3DFMT_D16;//每个像素点有16位的存储空间,存储离摄像机的距离g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pDevice);if (!g_pDevice)return;//设置渲染状态,设置启用深度值g_pDevice->SetRenderState(D3DRS_ZENABLE, true);//设置渲染状态,关闭灯光g_pDevice->SetRenderState(D3DRS_LIGHTING, false);//设置渲染状态,裁剪模式g_pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);//g_pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE) ;}void Transform()
{//WorldTransform:世界变换D3DXMATRIXA16 matWorld;//生成绕Y轴旋转矩阵,存储于矩阵中D3DXMatrixRotationY(&matWorld,                //输出矩阵timeGetTime()/150.0f  //角度);g_pDevice->SetTransform(D3DTS_WORLD, &matWorld);//ViewTransform:取景变换D3DXVECTOR3 vEyePt(0.0f, 0.0f, -5.0f);      //摄像机世界坐标D3DXVECTOR3 vLookatPt(0.0f, 0.0f, 0.0f);   //观察点世界坐标D3DXVECTOR3 vUpVec(0.0f, 1.0f, 0.0f);      //摄像机的上向量,通常为(0.0f, 1.0f, 0.0f)D3DXMATRIXA16 matView;                      //View变换的矩阵//根据上面的结果计算出矩阵,存入矩阵中D3DXMatrixLookAtLH(&matView, &vEyePt, &vLookatPt, &vUpVec);//进行取景变换g_pDevice->SetTransform(D3DTS_VIEW, &matView);//ProjectionTransform:投影变换D3DXMATRIXA16 matProj;                   //投影变换矩阵//生成投影变换矩阵,存入上面的矩阵中D3DXMatrixPerspectiveFovLH(&matProj,       //输出结果矩阵D3DX_PI / 4,    //视域角度,一般为PI/41.0f,          //显示屏的长宽比1.0f,          //视截体中近截面距离摄像机的位置100.0f             //视截体中远截面距离摄像机的位置);//进行投影变换g_pDevice->SetTransform(D3DTS_PROJECTION, &matProj);//ViewportTransform:视口变换D3DVIEWPORT9 vp = {0,               //视口的左上角X坐标0,               //视口的左上角Y坐标800,         //视口的宽度500,         //视口的高度0,               //深度缓存中的最小深度值1              //深度缓存中的最大深度值};g_pDevice->SetViewport(&vp);
}void onInit()
{//初始化D3DonCreatD3D();//初始化顶点缓冲区initVB();}void onDestroy()
{if (!g_pDevice)g_pDevice->Release();g_pDevice = NULL;
}void onLogic(float fElapsedTime)
{}void onRender(float fElasedTime)
{//前两个参数是0和NULL时,清空整个游戏窗口的内容(清的是后台)//第三个是清除的对象:前面表示清除颜色缓冲区,后面表示清除深度缓冲区,D3DCLEAR_STENCIL清空模板缓冲区g_pDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,100,100), 1.0f, 0);g_pDevice->BeginScene();Transform();//----------绘制图形步骤6.设置数据源,设置灵活顶点格式,绘制图元//设置数据流来源g_pDevice->SetStreamSource(0,                        //数据流管道号(0-15)g_pVB,                  //数据来源0,                        //数据流偏移量sizeof(stVertex)        //每个数据的字节数大小);//通知系统数据格式,以便解析数据g_pDevice->SetFVF(D3DFVF_CUSTOMVERTEX);//绘制图元 g_pDevice->DrawPrimitive(D3DPT_TRIANGLELIST,     //三角形列0,                     //起始点编号1                        //图元数量);g_pDevice->EndScene();g_pDevice->Present(NULL, NULL, NULL, NULL);
}int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,_In_opt_ HINSTANCE hPrevInstance,_In_ LPTSTR    lpCmdLine,_In_ int       nCmdShow)
{UNREFERENCED_PARAMETER(hPrevInstance);UNREFERENCED_PARAMETER(lpCmdLine);// TODO: 在此放置代码。MSG msg;HACCEL hAccelTable;// 初始化全局字符串LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);LoadString(hInstance, IDC_D3DDEMO, szWindowClass, MAX_LOADSTRING);MyRegisterClass(hInstance);// 执行应用程序初始化:if (!InitInstance (hInstance, nCmdShow)){return FALSE;}hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_D3DDEMO));ZeroMemory(&msg, sizeof(msg));while (msg.message != WM_QUIT){if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){TranslateMessage(&msg);DispatchMessage(&msg);}else{static DWORD dwTime = timeGetTime();DWORD dwCurrentTime = timeGetTime();DWORD dwElapsedTime = dwCurrentTime - dwTime;float fElapsedTime = dwElapsedTime * 0.001f;//------------渲染和逻辑部分代码----------onLogic(fElapsedTime);onRender(fElapsedTime);//-----------------------------------------if (dwElapsedTime < 1000 / 60){Sleep(1000/ 60 - dwElapsedTime);}dwTime = dwCurrentTime;}}onDestroy();return (int) msg.wParam;
}//
//  函数: MyRegisterClass()
//
//  目的: 注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{WNDCLASSEX wcex;wcex.cbSize = sizeof(WNDCLASSEX);wcex.style           = CS_HREDRAW | CS_VREDRAW;wcex.lpfnWndProc = WndProc;wcex.cbClsExtra      = 0;wcex.cbWndExtra        = 0;wcex.hInstance     = hInstance;wcex.hIcon         = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_D3DDEMO));wcex.hCursor       = LoadCursor(NULL, IDC_ARROW);wcex.hbrBackground   = (HBRUSH)(COLOR_WINDOW+1);wcex.lpszMenuName  = MAKEINTRESOURCE(IDC_D3DDEMO);wcex.lpszClassName  = szWindowClass;wcex.hIconSm       = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));return RegisterClassEx(&wcex);
}//
//   函数: InitInstance(HINSTANCE, int)
//
//   目的: 保存实例句柄并创建主窗口
//
//   注释:
//
//        在此函数中,我们在全局变量中保存实例句柄并
//        创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{hInst = hInstance; // 将实例句柄存储在全局变量中g_hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);if (!g_hWnd){return FALSE;}SetMenu(g_hWnd, NULL);ShowWindow(g_hWnd, nCmdShow);UpdateWindow(g_hWnd);onInit();return TRUE;
}//
//  函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目的: 处理主窗口的消息。
//
//  WM_COMMAND  - 处理应用程序菜单
//  WM_PAINT    - 绘制主窗口
//  WM_DESTROY  - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND g_hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{switch (message){case WM_KEYDOWN:if (wParam == VK_ESCAPE)PostQuitMessage(0);break;case WM_CLOSE:DestroyWindow(g_hWnd);break;case WM_DESTROY:PostQuitMessage(0);break;default:return DefWindowProc(g_hWnd, message, wParam, lParam);}return 0;
}

Direct-X学习笔记--变换相关推荐

  1. Direct 3D学习笔记(三)——光照与材质

    Direct 3D学习笔记(三)--光照与材质 现实世界中物体的颜色是一个复杂的系统,物体在不同的光照下,可能呈现在我们面前的就是不同的颜色.根据物理中的光学知识,物体在各种环境光照下,根据物体自身特 ...

  2. 单目深度估计 | Learning Depth from Monocular Videos using Direct Methods 学习笔记

    文章目录 摘要 1. 论文主要贡献: 2. 从视频中学习预测深度 2.1 尺度模糊 2.2 建模姿态估计预测器 3. 可微分直接视觉测距法 3.1 直接视觉测距法(DVO) 3.2 可微分的实现 4 ...

  3. Direct 3D学习笔记三:矩阵

    仍然是基于先前的程序.     首先来介绍三个基本的矩阵,这是direct 3d中必须设置的三个矩阵,即世界矩阵,视图矩阵,投影矩阵.而对应的三个变换就是世界变换(World Transform),摄 ...

  4. dx12 龙书第三章学习笔记 -- 变换

    1.线性变换:   函数的输入和输出都是3D向量,我们称为线性变换 矩阵表示法: ⭐所以已知一个线性变换,只要将i,j,z也就是标准基向量代入线性变换,就能构造一个变换矩阵 A:线性变换的矩阵表示法 ...

  5. 【学习笔记】超简单的快速数论变换(NTT)(FFT的优化)(含全套证明)

    整理的算法模板合集: ACM模板 目录 一.前置知识 二.快速数论变换(NTT) 三.NTT证明(和FFT的关系) 四.NTT模板 数组形式的实现 vector形式的实现 点我看多项式全家桶(●^◡_ ...

  6. OpenCV学习笔记(十六)——CamShift研究 OpenCV学习笔记(十七)——运动分析和物体跟踪Video OpenCV学习笔记(十八)——图像的各种变换(cvtColor*+)imgproc

    OpenCV学习笔记(十六)--CamShift研究 CamShitf算法,即Continuously Apative Mean-Shift算法,基本思想就是对视频图像的多帧进行MeanShift运算 ...

  7. 数字图像处理学习笔记(三):ORB算法(尺度不变特征变换)Oriented FAST and Rotated BRIEF

    数字图像处理学习笔记(三):ORB算法(尺度不变特征变换)Oriented FAST and Rotated BRIEF 一.概述 参考:特征点匹配+特征检测方法汇总 ORB的全称是Oriented ...

  8. 数字图像处理学习笔记(二):SIFT(尺度不变特征变换)算法

    数字图像处理学习笔记(二):SIFT(尺度不变特征变换)算法 一.概述: 提到特征点算法,首先就是大名鼎鼎的SIFT算法了.SIFT的全称是Scale Invariant Feature Transf ...

  9. opengl源码 实现无缝切换图片过场_OpenGL学习笔记(六)变换

    本文为学习LearnOpenGL的学习笔记,如有书写和理解错误还请大佬扶正: 教程链接: https://learnopengl-cn.github.io/01%20Getting%20started ...

最新文章

  1. STM32中GPIO的8种工作模式
  2. oracle spool用法
  3. 豪掷十亿拿下CUBA运营权,阿里体育未来也许并不轻松
  4. Springboot解决IDEA读取properties配置文件的中文乱码问题
  5. Centos5.5上vsftpd安装使用
  6. 一条数据的漫游奇遇记
  7. 从零搭建自己的SpringBoot后台框架(七)
  8. 12个有趣的C语言面试题
  9. sqlserver垮库查询_SQLServer跨库查询--分布式查询
  10. 电源变换适用于非独立源码_适用于非None测试的Python程序
  11. 组策略里更改更新和设置客户端首页
  12. 开源GIS(五)——openlayers中interaction的select、draw与modify
  13. 好的架构不是设计出来的,而是演进出来的
  14. 解决POI导出Excel时无法把单元格格式设置成数值类型,而不是变为货币或者自定义(附带相关问题的解决方法)
  15. python双色球数据抓取及模拟生成高概率的号码
  16. 洛谷 P1007 独木桥 思维
  17. C# 博思得 POSTEK 打印机 打码机 SDK 二次开发 指令打印
  18. JAVA——34.集合函数-List
  19. UINO优锘科技:一台物理发动机带你看懂数字孪生八要素
  20. Minos嵌入式虚拟化方案 - 系统架构介绍

热门文章

  1. 12000字解读瑞幸咖啡:“异军突起”与“绝处逢生”的奥秘
  2. data guard日常维护及管理
  3. 资源:区块链上的编程所有权
  4. 深圳8千名滴滴司机被封号? 官方:只是平台整治
  5. vue 程序执行的过程
  6. 奥比中光Astra Pro Demo示例
  7. 织梦报名系统diy.php,织梦自定义表单在线报名微信支付功能实现
  8. fastclick 解决移动端click事件300ms延迟
  9. JS正则表达式验证数字、非数字、正数、负数
  10. ln函数怎么看奇偶性_学得一团糟,怎么行?!