源码&参考:知乎 雪流星

阴影部分的知识打算看完games202后再完善

目录

一、在C++中创建一个窗口

Window类---负责开窗口并实时更新

二、需要的头文件

Color类---定义了颜色信息

Vector3类---三维向量

Matrix类---4x4矩阵

Vertex类---模型的顶点

MyMath类---关键定义了重心坐标的求法

Transform类---定义平移旋转缩放矩阵

Mesh类---用于创建模型

Buffer类---深度缓存

插入知识点 ---视图变换过程

Light类---定义光源

Camera类---用于观测的摄像机

Input类---检测键盘输入

Texture类---加载并存储纹理

插入知识点----shadow map算法

Uniform类

三、软光栅封装

Shader类

Renderer类

ObjFileReader类---读取模型文件,存入Mesh的Buffer中

四、主函数及测试效果


一、在C++中创建一个窗口

为了把渲染的图片放在窗口里,不用反复打开图片查看

不重要,以下代码实现了创建并显示一个窗口的功能,入口函数是WinMain

不过本项目中是用的main为入口函数,手动写了一个Window类

#include<Windows.h>//自定义的窗口过程
LRESULT CALLBACK MyWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {switch (Msg) {case WM_DESTROY:PostQuitMessage(0);return 0;default:return DefWindowProc(hWnd, Msg, wParam, lParam);}return 0;
}int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR ICmdLine, int nCmdShow) {// 创建窗口类实例wndWNDCLASS wnd = {CS_HREDRAW,MyWindowProc,          // 使用自定义的窗口过程函数0,0,hInstance,LoadIcon(NULL,IDI_APPLICATION),LoadCursor(NULL,IDC_ARROW),(HBRUSH)(GetStockObject(WHITE_BRUSH)),NULL, L"mic@Renderer"};// 注册窗口类RegisterClass(&wnd);// 创建窗口HWND hWnd = CreateWindow(L"mic@Renderer", L"mic@Renderer", WS_OVERLAPPEDWINDOW,100, 100, 500, 500, NULL, NULL, hInstance, NULL);// 显示窗口ShowWindow(hWnd, nCmdShow);// 更新窗口UpdateWindow(hWnd);//消息循环MSG msg;while (GetMessage(&msg, NULL, 0, 0)) {    // 循环获取消息TranslateMessage(&msg);               // 翻译消息DispatchMessage(&msg);                // 派发消息}return 0;
}

Window类---负责开窗口并实时更新

#pragma once
#include<Windows.h>class Window {
public:HWND window; //HWND是一个基本类型--窗口句柄(句柄是Windows系统中对象或实例的标识)int windowWidth;int windowHeight;HDC hdc;HDC screenHDC; //HDC是指窗体、控件的句柄//构造和析构Window(int w, int h, const char* name);~Window();};
#include "Window.h"
#include <iostream>
using namespace std;LRESULT WINAPI MsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{Window* window = static_cast<Window*>(GetPropA(hWnd, "Owner"));if (!window)return DefWindowProcA(hWnd, msg, wParam, lParam);switch (msg){}return DefWindowProcA(hWnd, msg, wParam, lParam);
}Window::Window(int w, int h, const char* name) :windowWidth(w), windowHeight(h)
{WNDCLASS wndClass = { CS_BYTEALIGNCLIENT, (WNDPROC)MsgProc, 0, 0, 0, NULL, NULL, NULL, NULL, TEXT("Test") };wndClass.hInstance = GetModuleHandle(NULL);if (!RegisterClass(&wndClass))return;window = CreateWindow(TEXT("Test"), TEXT("Test"), WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,0, 0, 0, 0, NULL, NULL, wndClass.hInstance, NULL);if (window == NULL)return;hdc = GetDC((window));screenHDC = CreateCompatibleDC(hdc);//ReleaseDC(handler, hdc);BITMAPINFO bitmapInfo = { { sizeof(BITMAPINFOHEADER),windowWidth, windowHeight, 1, 32, BI_RGB, windowWidth * windowHeight * 4, 0, 0, 0, 0 } };LPVOID ptr;//创建设备无关的位图HBITMAP bitmapHandler = CreateDIBSection(screenHDC, &bitmapInfo, DIB_RGB_COLORS, &ptr, 0, 0);if (bitmapHandler == NULL)return;HBITMAP screenObject = (HBITMAP)SelectObject(screenHDC, bitmapHandler);SetWindowPos(window, NULL, 50, 50, windowWidth, windowHeight, (SWP_NOCOPYBITS | SWP_NOZORDER | SWP_SHOWWINDOW));ShowWindow(window, SW_NORMAL);UpdateWindow(window);}Window::~Window()
{
}

二、需要的头文件

Color类---定义了颜色信息

有float r,g,b,a四个属性

重载了加减乘除运算符实现颜色间的运算

定义了颜色的插值函数

#pragma onceclass Color {float r, g, b, a;Color(float cr, float cg, float cb, float ca);Color() {};~Color();//操作符重载---Color+ColorColor operator+(const Color& right)const;//操作符重载---Color+常数Color operator+(const float c) {return Color(r + c, g + c, b + c, a);}//操作符重载---Color-ColorColor operator-(const Color& right)const;//操作符重载---Color*ColorColor operator*(const Color& right)const;//操作符重载---Color*常数Color operator*(float value)const;//操作符重载---Color/ColorColor operator/(float value)const;//Color线性插值static Color Lerp(const Color& left, const Color& right, float t);//白色static Color white;
};
#include "Color.h"Color::Color(float cr, float cg, float cb, float ca):r(cr),g(cg),b(cb),a(ca)
{}Color::~Color()
{
}Color Color::operator+(const Color& right) const
{Color returnValue(r + right.r, g + right.g, b + right.b, a + right.a);return returnValue;
}Color Color::operator-(const Color& right) const
{Color returnValue(r - right.r, g - right.g, b - right.b, a - right.a);return returnValue;
}Color Color::operator*(const Color& right) const
{Color returnValue(r * right.r, g * right.g, b * right.b, a * right.a);return returnValue;
}Color Color::operator*(float value) const
{Color returnValue(r * value, g * value, b * value, a * value);return returnValue;
}Color Color::operator/(float value) const
{float rhvalue = 1.0f / value; //倒数,变除法为乘法Color returnValue(r * rhvalue, g * rhvalue, b * rhvalue, a * rhvalue);return returnValue;
}Color Color::Lerp(const Color& left, const Color& right, float t)
{return left + (right - left) * t;
}Color Color::white = Color(1, 1, 1, 1);

Vector3类---三维向量

注意:类模板的声明和定义应该放在一个文件内

有float x,y,z,w四个属性

定义了点乘(对应位相乘,结果是数),叉乘(“求谁盖谁,+ - +”,结果是向量),求模等常见操作,定义了单位化(模长为1)和标准化(w为1)函数

此文件中顺便定义了Vector2类,有 float x y两个属性

#pragma oncetemplate<class T>
class Vector3 {
public:T x, y, z, w;Vector3<T>(T fx, T fy, T fz);Vector3<T>(T fx, T fy, T fz, T fw);//默认构造参数(0,0,0,1)Vector3<T>() {x = y = z = 0;w = 1;}//向量+向量Vector3<T> operator+(const Vector3<T>& right)const;//向量-向量Vector3<T> operator-(const Vector3<T>& right)const;//向量*常数Vector3<T>operator*(float value)const;//向量/常数Vector3<T>operator/(float value)const;//求模长^2float SquareMagnitude();//求模长float Magnitude();//单位化Vector3<T>& Normalize();//点乘float static Dot(const Vector3<T>& left, const Vector3<T>& right);float Dot(const Vector3<T>& right);//叉乘Vector3<T> static Cross(const Vector3<T>& left, const Vector3<T>& right);//插值Vector3<T> static Lerp(const Vector3<T>& left, const Vector3<T>& right, float t);//标准化(w变成1)void standardization(){if (w == 0){std::cout << "error w==0" << std::endl;return;}x /= w;y /= w;z /= w;w = 1;}void Print();
};using Vector3f = Vector3<float>;
using Vector3i = Vector3<int>;template<class T>
Vector3<T>::Vector3(T fx, T fy, T fz) {x = fx;y = fy;z = fz;w = 1;
}template<class T>
Vector3<T> Vector3<T>::operator+(const Vector3<T>& right)const
{Vector3<T> returnValue(x + right.x, y + right.y, z + right.z);return returnValue;
}
template<class T>
Vector3<T> Vector3<T>::operator-(const Vector3<T>& right)const
{Vector3<T> returnValue(x - right.x, y - right.y, z - right.z);return returnValue;
}
template<class T>
Vector3<T> Vector3<T>::operator*(float value)const
{Vector3<T> returnValue(x * value, y * value, z * value);return returnValue;
}
template<class T>
Vector3<T> Vector3<T>::operator/(float value)const
{Vector3<T> returnValue(x / value, y / value, z / value);return returnValue;
}template<class T>
float Vector3<T>::SquareMagnitude() {return Dot(*this, *this);
}template<class T>
float Vector3<T>::Magnitude() {return sqrt(SquareMagnitude());
}template<class T>
Vector3<T>& Vector3<T>::Normalize() {*this = *this / Magnitude();return *this;
}//点乘返回一个值---对应位相乘后求和(没涉及w)
template<class T>
float Vector3<T>::Dot(const Vector3<T>& left, const Vector3<T>& right)
{return left.x * right.x + left.y * right.y + left.z * right.z;
}template<class T>
float Vector3<T>::Dot(const Vector3<T>& right)
{return x * right.x + y * right.y + z * right.z;
}//叉乘
template<class T>
Vector3<T> Vector3<T>::Cross(const Vector3<T>& left, const Vector3<T>& right) {float valueX = left.y * right.z - left.z * right.y;float valueY = left.z * right.x - left.x * right.z;float valueZ = left.x * right.y - left.y * right.x;Vector3<T> returnValue(valueX, valueY, valueZ);return returnValue;
}template<class T>
Vector3<T> Vector3<T>::Lerp(const Vector3<T>& left, const Vector3<T>& right,float t) {return left + (right - left) * t;
}template<class T>
void Vector3<T>::Print() {std::cout << "Vector3<T> x: " << x << " y: " << y << " z: " << z << std::endl;
}//同时定义了二维向量
class Vector2 {
public:float x, y;Vector2() { x = y = 0; }Vector2(float fx,float fy) {x = fx;y = fy;}~Vector2() {};Vector2 operator + (const Vector2& right) const{return Vector2(x + right.x, y + right.y);}Vector2 operator - (const Vector2& right) const{return Vector2(x - right.x, y - right.y);}Vector2 operator * (float value) const{return Vector2(x * value, y * value);}Vector2 operator / (float value) const{return Vector2(x / value, y / value);}
};

Matrix类---4x4矩阵

成员变量是4*4的二维数组

定义了矩阵的运算,矩阵与向量的运算等

(特别记忆:矩阵的乘法:m.value[i][j] += this->value[i][k] *right.value[k][j];)

#pragma once
#include"Vector3.h"class Matrix {
public:float value[4][4]; //4x4的矩阵Matrix();~Matrix();Matrix operator+(const Matrix& right)const;Matrix operator-(const Matrix& right)const;Matrix operator*(const Matrix& right)const;Matrix operator*(float k)const;//矩阵x向量Vector3f MutiplyVector3(const Vector3f& v)const;//正交化矩阵void Identity();//矩阵转置Matrix transpose()const {Matrix trans;for(int i=0;i<4;i++)for (int j = 0; j < 4; j++) {trans.value[i][j] = value[j][i];}return trans;}//打印void Print();
};
#include "Matrix.h"
#include <iostream>Matrix::Matrix()
{Identity();
}Matrix::~Matrix()
{
}Matrix Matrix::operator+(const Matrix& right) const
{Matrix m;for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {m.value[i][j] = this->value[i][j] + right.value[i][j];}}return m;
}Matrix Matrix::operator-(const Matrix& right) const
{Matrix m;for (int i = 0; i < 4; i++){for (int j = 0; j < 4; j++){m.value[i][j] = this->value[i][j] - right.value[i][j];}}return m;
}Matrix Matrix::operator*(const Matrix& right) const
{Matrix m;for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {for (int k = 0; k < 4; k++) {m.value[i][j] += this->value[i][k] *right.value[k][j];}}}return m;
}Matrix Matrix::operator*(float k) const
{Matrix m;for (int i = 0; i < 4; i++){for (int j = 0; j < 4; j++){m.value[i][j] = this->value[i][j] * k;}}return m;
}Vector3f Matrix::MutiplyVector3(const Vector3f& v) const
{float x = v.x * value[0][0] + v.y * value[0][1] + v.z * value[0][2] + v.w * value[0][3];float y = v.x * value[1][0] + v.y * value[1][1] + v.z * value[1][2] + v.w * value[1][3];float z = v.x * value[2][0] + v.y * value[2][1] + v.z * value[2][2] + v.w * value[2][3];float w = v.x * value[3][0] + v.y * value[3][1] + v.z * value[3][2] + v.w * value[3][3];Vector3f returnValue(x, y, z);returnValue.w = w;return returnValue;
}void Matrix::Identity()
{for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {if (i == j)value[i][j] = 1;elsevalue[i][j] = 0;}}
}void Matrix::Print()
{std::cout << "-----------------Matrix Begin--------------" << std::endl;for (int i = 0; i < 4; i++){for (int j = 0; j < 4; j++){std::cout << "[" << value[i][j] << "]   ";}std::cout << std::endl;}std::cout << "-----------------Matrix End----------------" << std::endl;
}

Vertex类---模型的顶点

顶点信息记录了:Vector3f 顶点坐标、Vector3f 法线方向、颜色信息,Vector2 uv坐标

定义了顶点信息插值(深度插值 颜色插值 uv坐标插值)、顶点与矩阵相乘等方法(只有pos和normal成员需要乘上矩阵)

#pragma once
#include"Color.h"
#include"Matrix.h"class Vertex {
public:Vector3f pos;    //顶点坐标Vector3f normal;//法线向量Color color;    //颜色信息Vector2 uv;       //uv坐标Vertex() {};Vertex(const Vector3f& p, const Color& c, Vector2 uv);Vertex(const Vector3f& p, const Color& c, const Vector3f& normal, Vector2 uv);~Vertex();//对顶点信息进行插值void LerpVertexData(Vertex& left, const Vertex& right, float t);//打印顶点坐标和RGB颜色信息void Print();//顶点*矩阵Vertex& operator*(const Matrix& m);static float LerpFloat(float v1, float v2, float t) { return v1 + (v2 - v1) * t; }
};
#include"Vertex.h"
#include"Vector3.h"
#include <iostream>Vertex::Vertex(const Vector3f& p, const Color& c, Vector2 uv): pos(p), color(c), uv(uv)
{}Vertex::Vertex(const Vector3f& p, const Color& c, const Vector3f& normal, Vector2 uv) : pos(p), color(c), normal(normal), uv(uv)
{
}Vertex::~Vertex()
{}void Vertex::LerpVertexData(Vertex& left, const Vertex& right, float t)
{pos.z = LerpFloat(left.pos.z, right.pos.z, t); //深度插值color = Color::Lerp(left.color, right.color, t);//颜色信息插值uv.x = LerpFloat(left.uv.x, right.uv.x, t); //uv坐标插值uv.y = LerpFloat(left.uv.y, right.uv.y, t);}void Vertex::Print()
{std::cout << "Vector3f: " << pos.x << " " << pos.y << " " << pos.z;std::cout << " Color: " << color.r << " " << color.g << " " << color.b << std::endl;
}Vertex& Vertex::operator*(const Matrix& m)
{//顶点乘一个变换矩阵时,只有顶点和法线进行操作pos.x = pos.x * m.value[0][0] + pos.y * m.value[0][1] + pos.z * m.value[0][2] + pos.w * m.value[0][3];pos.y = pos.x * m.value[1][0] + pos.y * m.value[1][1] + pos.z * m.value[1][2] + pos.w * m.value[1][3];pos.z = pos.x * m.value[2][0] + pos.y * m.value[2][1] + pos.z * m.value[2][2] + pos.w * m.value[2][3];pos.w = pos.x * m.value[3][0] + pos.y * m.value[3][1] + pos.z * m.value[3][2] + pos.w * m.value[3][3];//注意,只有旋转+平移才能直接使用MVP矩阵,如果产生了变形,法线再这样乘就是错的normal.x = normal.x * m.value[0][0] + normal.y * m.value[0][1] + normal.z * m.value[0][2] + normal.w * m.value[0][3];normal.y = normal.x * m.value[1][0] + normal.y * m.value[1][1] + normal.z * m.value[1][2] + normal.w * m.value[1][3];normal.z = normal.x * m.value[2][0] + normal.y * m.value[2][1] + normal.z * m.value[2][2] + normal.w * m.value[2][3];normal.w = normal.x * m.value[3][0] + normal.y * m.value[3][1] + normal.z * m.value[3][2] + normal.w * m.value[3][3];return *this;
}

MyMath类---关键定义了重心坐标的求法

作者在此定义了一个clamp方法来限制x的大小

同时定义了重要的方法——求重心坐标

#pragma once
#include"Vector3.h"
#define PI 3.1415926535//将x限制在mi~ma的范围内
float clamp(float x, float mi, float ma);//已知p,求其在v1,v2,v3组成的三角形内的重心坐标
Vector3f centerOfGravity(Vector3f v1, Vector3f v2, Vector3f v3, Vector2 p);
#include"MyMath.h"float clamp(float x, float mi, float ma)
{if (x < mi)x = mi;if (x > ma)x = ma;return x;
}Vector3f centerOfGravity(Vector3f v1, Vector3f v2, Vector3f v3, Vector2 p)
{//这两种情况说明三角形面积为0if ((-(v1.x - v2.x) * (v3.y - v2.y) + (v1.y - v2.y) * (v3.x - v2.x)) == 0)return Vector3f(1, 0, 0);if (-(v2.x - v3.x) * (v1.y - v3.y) + (v2.y - v3.y) * (v1.x - v3.x) == 0)return Vector3f(1, 0, 0);//重心坐标float alpha = (-(p.x - v2.x) * (v3.y - v2.y) + (p.y - v2.y) * (v3.x - v2.x)) / (-(v1.x - v2.x) * (v3.y - v2.y) + (v1.y - v2.y) * (v3.x - v2.x));float beta = (-(p.x - v3.x) * (v1.y - v3.y) + (p.y - v3.y) * (v1.x - v3.x)) / (-(v2.x - v3.x) * (v1.y - v3.y) + (v2.y - v3.y) * (v1.x - v3.x));float gamma = 1 - alpha - beta;return Vector3f(alpha, beta, gamma);
}

Transform类---定义平移旋转缩放矩阵

封装了平移,旋转,缩放所需的参数

定义了成员ObjectToWorld矩阵

定义了根据传参构造T,R,S矩阵的方法

#pragma once
#include"Matrix.h"
#include"MyMath.h"class Transform {
public://平移、旋转缩放参数Vector3f position; Vector3f rotation; Vector3f scale;    Matrix objectToWorld;//(不仅要构造出矩阵,还要把参数赋值给对应的成员变量)Matrix Translate(const Vector3f& v); //平移(根据传的参数v构造平移矩阵)//根据angle构造绕X/Y/Z轴旋转的矩阵Matrix RotateX(float angle);Matrix RotateY(float angle);Matrix RotateZ(float angle);//封装了RotateX Y Z,根据传进来的三个角去旋转Matrix Rotate(const Vector3f& rotAngle);//缩放(根据传的参数s构造缩放矩阵)Matrix Scale(const Vector3f& s);Transform(Vector3f pos, Vector3f rot, Vector3f s) :position(pos), rotation(rot), scale(s) {}Transform() { objectToWorld.Identity(); }
};
#include"Transform.h"
#include <cmath>Matrix Transform::Translate(const Vector3f& v) {position = v;Matrix m;m.Identity();//每行的第四列负责平移m.value[0][3] = v.x;m.value[1][3] = v.y;m.value[2][3] = v.z;return m;
}Matrix Transform::RotateX(float angle)
{rotation.x = angle;Matrix m;m.Identity();float radian = angle / 360.0f * PI;float cosValue = cos(radian);float sinValue = sin(radian);m.value[1][1] = cosValue;m.value[1][2] = -sinValue;m.value[2][1] = sinValue;m.value[2][2] = cosValue;return m;
}Matrix Transform::RotateY(float angle)
{rotation.y = angle;Matrix m;m.Identity();float radian = angle / 360.0f * PI;float cosValue = cos(angle);float sinValue = sin(angle);m.value[0][0] = cosValue;m.value[0][2] = sinValue;m.value[2][0] = -sinValue;m.value[2][2] = cosValue;return m;
}Matrix Transform::RotateZ(float angle)
{rotation.z = angle;Matrix m;m.Identity();float radian = angle / 360.0f * PI;float cosValue = cos(angle);float sinValue = sin(angle);m.value[0][0] = cosValue;m.value[0][1] = -sinValue;m.value[1][0] = sinValue;m.value[1][1] = cosValue;return m;
}Matrix Transform::Rotate(const Vector3f& rotAngle)
{rotation = rotAngle;Matrix rotX = RotateX(rotAngle.x);Matrix rotY = RotateY(rotAngle.y);Matrix rotZ = RotateZ(rotAngle.z);return rotX * rotY * rotZ; //同时执行三种变换
}Matrix Transform::Scale(const Vector3f& s)
{Matrix m;scale = s;m.Identity();m.value[0][0] = s.x;m.value[1][1] = s.y;m.value[2][2] = s.z;return m;
}

Mesh类---用于创建模型

含成员变量Transform transform

数组vertexBuffer                顶点

        positionBuffer            顶点坐标

        normalBuffer              法线

        uvBuffer                      uv坐标

        indexBuffer                f中三个下标

        以上这些除了顶点都读取自.obj模型文件

#pragma once
#include"Vertex.h"
#include"Transform.h"
#include<vector>class Mesh {
public:Transform transform;std::vector<Vertex> vertexBuffer;std::vector<Vector3f>positionBuffer;std::vector<Vector3f>normalBuffer;std::vector<Vector2>uvBuffer;std::vector<Vector3i>indexBuffer;Mesh();~Mesh();Transform GetTransform() { return transform; }int GetIndexBufferLength() { return indexBuffer.size(); }void SetTransform(Transform& t) { transform = t;}Matrix GetObjectToWorld() { return transform.objectToWorld; }void SetObjectToWorld(const Matrix& m) { transform.objectToWorld = m; }void AddVertexData(const Vector3f  pos, float u, float v, const Color color = Color::white);void AddVertexData(float posx, float posy, float posz, float u, float v, const Color color = Color::white);void AddVertexData(float posx, float posy, float posz, float u, float v, Vector3f nor, const Color color = Color::white);
};
#include "Mesh.h"Mesh::Mesh()
{}Mesh::~Mesh()
{
}void Mesh::AddVertexData(const Vector3f pos, float u, float v, const Color color)
{Vertex p(pos, color, Vector2(u, v));vertexBuffer.push_back(p);
}void Mesh::AddVertexData(float posx, float posy, float posz, float u, float v, const Color color)
{AddVertexData(Vector3f(posx, posy, posz), u, v, color);
}void Mesh::AddVertexData(float posx, float posy, float posz, float u, float v, Vector3f nor, const Color color)
{Vertex p(Vector3f(posx, posy, posz), color, nor, Vector2(u, v));vertexBuffer.push_back(p);
}

Buffer类---深度缓存

定义了二维数组depthBuffer

定义了Sample方法

#pragma once
struct Buffer {int height, width;
};struct DepthBuffer :Buffer {float** depthBuffer; //深度缓存(二维数组)DepthBuffer(int width, int height) {this->height = height;this->width = width;//为depthBuffer分配空间depthBuffer = new float* [height];for (int i = 0; i < height; i++) {depthBuffer[i] = new float[width];}for (int i = 0; i < height; i++) {for (int j = 0; j < width; j++) {depthBuffer[i][j] = 1; //深度缓存初始化为1}}}//将value限制在min和max内float Clamp(float min, float max, float value){if (value > max)value = max;if (value < min)value = min;return value;}//采样float Sample(float u, float v) {int y = Clamp(0, height - 1, u);int x = Clamp(0, width - 1, v);return depthBuffer[y][x];}~DepthBuffer() {for (int i = 0; i < height; i++){delete[] depthBuffer[i];}}
};

插入知识点 ---视图变换过程

避免之后产生混淆,在此复习一下视图变换的过程

Light类---定义光源

定义了两种光的类——平行光和点光源(现在主要只看了平行光)

属性有光照强度、光源位置、光源方向

一个bool变量来控制着色方式——逐顶点or逐像素

方法----Matrix LookAt(const Vector3f& upAxis)    这个和相机那个类似

#pragma once
#include"Matrix.h"//平行光
class DirectionLight {
private:Vector3f direction; //光照方向Vector3f position;  //光源位置
public:float intensity; //光照强度bool forVertex; //逐顶点或逐像素光照着色//默认构造:光照强度为1,逐顶点着色DirectionLight() { intensity = 1;  forVertex = true; }DirectionLight(const Vector3f& dir, const Vector3f& pos, float Intensity = 1, bool forV = true){direction = dir;position = pos;intensity = Intensity;forVertex = forV;}
};//点光源
class PointLight
{
public:PointLight() { intensity = 1.0; }PointLight(const Vector3f& pos, float inten) :position(pos), intensity(inten) {}~PointLight() {}Vector3f position;float intensity;};
#include"Light.h"Matrix DirectionLight::LookAt(const Vector3f& upAxis)
{Vector3f lookDir = direction;lookDir.Normalize(); Vector3f rightDir = Vector3f::Cross(upAxis, lookDir);rightDir.Normalize();Vector3f upDir = Vector3f::Cross(lookDir, rightDir);upDir.Normalize();Matrix m;m.value[0][0] = rightDir.x;  m.value[1][0] = upDir.x;  m.value[2][0] = lookDir.x;  m.value[3][0] = 0;m.value[0][1] = rightDir.y;     m.value[1][1] = upDir.y;  m.value[2][1] = lookDir.y;  m.value[3][1] = 0;m.value[0][2] = rightDir.z;  m.value[1][2] = upDir.z;  m.value[2][2] = lookDir.z;  m.value[3][2] = 0;m.value[0][3] = -position.x;       m.value[1][3] = -position.y;   m.value[2][3] = -position.z;     m.value[3][3] = 1;return m;
}

Camera类---用于观测的摄像机

Transform类transform,矩阵v,矩阵p成员

定义了三个方法:透视矩阵(两种定义方法),正交矩阵,摄像机变换LookAt矩阵

透视投影矩阵的推导结论

#pragma once
#include"Transform.h"class Camera {
public:Transform transform; //相当于M矩阵Matrix v, p;Camera(Transform t):transform(t){}Camera() {}//LookAt矩阵Matrix LookAt(const Vector3f& eyePos, const Vector3f& lookat, const Vector3f& upAxis);//透视和正交矩阵Matrix Perspective(float fov, float aspect, float nearPanel, float farPanel);Matrix Perspective(float l, float r, float n, float f, float t, float b);Matrix Orthographic(float l, float r, float n, float f, float t, float b);
};
#include"Camera.h"
#include <cmath>Matrix Camera::LookAt(const Vector3f& eyePos, const Vector3f& lookat, const Vector3f& upAxis)
{Vector3f lookDir = lookat;lookDir.Normalize();Vector3f rightDir = Vector3f::Cross(upAxis, lookDir);rightDir.Normalize();Vector3f upDir = Vector3f::Cross(lookDir, rightDir);upDir.Normalize();//自此,获得了相机的坐标系Matrix m;//原坐标系变换到相机坐标系的 逆 矩阵(也就是相机到原点)m.value[0][0] = rightDir.x;  m.value[1][0] = upDir.x;  m.value[2][0] = lookDir.x;  m.value[3][0] = 0;m.value[0][1] = rightDir.y;    m.value[1][1] = upDir.y;  m.value[2][1] = lookDir.y;  m.value[3][1] = 0;m.value[0][2] = rightDir.z;  m.value[1][2] = upDir.z;  m.value[2][2] = lookDir.z;  m.value[3][2] = 0;m.value[0][3] = -eyePos.x;         m.value[1][3] = -eyePos.y;   m.value[2][3] = -eyePos.z;     m.value[3][3] = 1;v = m; //给成员v赋值return v;
}//https://zhuanlan.zhihu.com/p/122411512
//视锥角,宽高比,远近平面定义的投影转正交->正交
Matrix Camera::Perspective(float fov, float aspect, float nearPanel, float farPanel)
{float tanValue = tan(0.5f * fov * PI / 180);Matrix proj;proj.value[0][0] = 1 / (tanValue * aspect);proj.value[1][1] = 1/tanValue;proj.value[2][2] = (nearPanel + farPanel) / (nearPanel - farPanel);proj.value[2][3] = -2 * nearPanel * farPanel / (nearPanel - farPanel);proj.value[3][2] = 1;proj.value[3][3] = 0;p = proj;return proj;}//几个面定义的投影转正交-->正交
Matrix Camera::Perspective(float l, float r, float n, float f, float t, float b)
{Matrix m;m.value[0][0] = 2 * n / (r - l);  m.value[0][1] = 0;                m.value[0][2] = (l + r) / (l - r);                     m.value[0][3] = 0;m.value[1][0] = 0;              m.value[1][1] = 2 * n / (t - b);  m.value[1][2] = (b + t) / (b - t);                    m.value[1][3] = 0;m.value[2][0] = 0;                m.value[2][1] = 0;                 m.value[2][2] = (n + f) / (n - f);                m.value[2][3] = 2 * n * f / (f - n);m.value[3][0] = 0;               m.value[3][1] = 0;                 m.value[3][2] = 1;                                    m.value[3][3] = 0;p = m;return m;
}//正交投影(缩放*平移矩阵得到的总矩阵)
Matrix Camera::Orthographic(float l, float r, float n, float f, float t, float b)
{Matrix m;m.value[0][0] = 2 / (r - l);  m.value[0][1] = 0;           m.value[0][2] = 0;            m.value[0][3] = -(r + l) / (r - l);m.value[1][0] = 0;        m.value[1][1] = 2 / (t - b);  m.value[1][2] = 0;             m.value[1][3] = -(t + b) / (t - b);m.value[2][0] = 0;         m.value[2][1] = 0;          m.value[2][2] = 2 / (n - f);    m.value[2][3] = -(n + f) / (n - f);m.value[3][0] = 0;          m.value[3][1] = 0;          m.value[3][2] = 0;            m.value[3][3] = 1;p = m;return m;
}

Input类---检测键盘输入

负责检测键盘的输入

#pragma once#define IS_KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define IS_KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

Texture类---加载并存储纹理

用于加载纹理

定义了成员变量---二维数组textureData到时候就是要通过textureData[u][v]来访问纹理

定义成员方法---Sample(和Buffer里的Sample思想一样儿)和LoadTexture(存到textureData里)

#pragma once
#include"Color.h"class Color;
class Texture {
private:int width;int height;Color textureData[1024][1024];
public:Texture();~Texture();//根据路径打开纹理图void LoadTexture(const char*path);Color Sample(float u, float v);float Clamp(float min, float max, float value);
};
#include "Texture.h"
#include<Windows.h>Texture::Texture()
{width = 512;height = 512;
}Texture::~Texture()
{
}void Texture::LoadTexture(const char* path)
{//加载位图HBITMAP bitmap = (HBITMAP)LoadImage(NULL, path, IMAGE_BITMAP, width, height, LR_LOADFROMFILE);//hdc可标识这张图HDC hdc = CreateCompatibleDC(NULL);SelectObject(hdc, bitmap);//获取到纹理图的颜色,赋值给textureData成员for (int i = 0; i < width; i++){for (int j = 0; j < height; j++){//RGB转为intCOLORREF color = GetPixel(hdc, i, j);int r = color % 256;int g = (color >> 8) % 256;int b = (color >> 16) % 256;Color c((float)r / 256, (float)g / 256, (float)b / 256, 1);textureData[i][j] = c;}}
}Color Texture::Sample(float u, float v)
{//把u v限制在0,1u = Clamp(0, 1.0f, u);v = Clamp(0, 1.0f, v);int intu = width * u;int intv = height * v;return textureData[intu][intv];
}float Texture::Clamp(float min, float max, float value)
{if (value > max)value = max;if (value < min)value = min;return value;
}

插入知识点----shadow map算法

将物体顶点通过矩阵运算转移到灯光空间下

在灯光坐标下渲染物体阴影:

创建一张深度贴图,用于保存物体在灯光空间下的深度信息。

创建过程:如果某个顶点的深度值比原来深度贴图中的值更小,就更新深度贴图,反之则丢弃。

获取到深度贴图后,如果某个点在光源视角下深度值大于深度贴图中对应位置的深度值,说明它在阴影中。那么就对该顶点乘上阴影的颜色

Uniform类

一致性变量

三个类:

  • PhongVert

    • ​​​​​​​光照的数组
    • cameraPos观测位置
    • mvp矩阵
    • 三个顶点
    • shadow map要用的lp0,1,2   lightV lightP矩阵
  • PhongFrag
    • ​​​​​​​纹理指针
    • 深度缓存指针
  • ShadowVert
    • ​​​​​​​​​​​​​​mvp矩阵
#pragma once
#include <vector>
#include "Light.h"
#include "Vector3.h"
#include "Texture.h"
#include "Matrix.h"
#include "Buffer.h"struct PhongVert {std::vector<DirectionLight>dirlights; //直射光数组std::vector<PointLight>pointlights; //点光源数组Vector3f cameraPos;  //相机观测位置Vector3f lp0, lp1, lp2; //站在光位置的坐标-用于计算shadow mapMatrix m, v, p;        //视图变换矩阵Vertex v0, v1, v2;  //三个顶点信息Matrix lightV, lightP;  //站在光位置的VP矩阵-用于计算shadow map
};struct PhongFrag {Texture* baseTex;   //基础纹理DepthBuffer* depthBuffer; //深度缓存PhongFrag(Texture* baseTex, DepthBuffer* depthBuffer) :baseTex(baseTex), depthBuffer(depthBuffer) {}~PhongFrag() {}
};struct ShadowVert {Matrix lightV, lightP;    //灯光视角的MVP矩阵Matrix m;
};

接下来是最核心的两个类

三、软光栅封装

Shader类

两个类(其中都含有函数顶点着色器VS和片元着色器FS):

  • PhongShader

    • ​​​​​​​PhongVert v2f
    • PhongFrag frag
    • CalcuteShadow函数
  • ShadowShader
    • ​​​​​​​ShadowVert v2f
#pragma once
#include"MyMath.h"
#include"Vertex.h"
#include"Uniform.h"//抽象类IShader
struct IShader {
public://顶点着色器virtual void VS(Vertex& v0, Vertex& v1, Vertex& v2) = 0;//片元着色器virtual bool FS(Vertex& v, Vector3f g) = 0;
};struct PhongShader :public IShader {
public:PhongVert v2f; //vertex to fragmentPhongFrag frag;
public:PhongShader(PhongVert v2f,PhongFrag frag):v2f(v2f),frag(frag){}~PhongShader(){}//自IShader继承virtual void VS(Vertex& v0, Vertex& v1, Vertex& v2) override;virtual bool FS(Vertex& v, Vector3f g) override;float CalcuteShadow(Vector3f posLightSpace, double bias);
};class ShadowShader :public IShader
{
public:ShadowShader() {}~ShadowShader() {}// 通过 IShader 继承virtual void VS(Vertex& v0, Vertex& v1, Vertex& v2) override;virtual bool FS(Vertex& v, Vector3f g) override;ShadowVert v2f;
};
#include "Shader.h"
using namespace std;
void PhongShader::VS(Vertex& v0, Vertex& v1, Vertex& v2)
{//phongvert获取到参数的顶点v2f.v0 = v0;v2f.v1 = v1;v2f.v2 = v2;Vertex* v = &v0; //v用来遍历三个顶点for (int i = 0; i < 3; i++){switch (i){//从顶点pos获取lp,并进行MVP变换case 0:v = &v0;v2f.lp0 = v0.pos;v2f.lp0 = v2f.m.MultiplyVector3(v2f.lp0);v2f.lp0 = v2f.lightV.MultiplyVector3(v2f.lp0);v2f.lp0 = v2f.lightP.MultiplyVector3(v2f.lp0);break;case 1:v = &v1;v2f.lp1 = v1.pos;v2f.lp1 = v2f.m.MultiplyVector3(v2f.lp1);v2f.lp1 = v2f.lightV.MultiplyVector3(v2f.lp1);v2f.lp1 = v2f.lightP.MultiplyVector3(v2f.lp1);break;case 2:v = &v2;v2f.lp2 = v2.pos;v2f.lp2 = v2f.m.MultiplyVector3(v2f.lp2);v2f.lp2 = v2f.lightV.MultiplyVector3(v2f.lp2);v2f.lp2 = v2f.lightP.MultiplyVector3(v2f.lp2);break;}//顶点的M变换v->pos = v2f.m.MultiplyVector3(v->pos);//此处和transform中的法线处理存疑Matrix nm = v2f.m;//去掉平移.不考虑变形缩放的情况下使用,正常情况下使用逆的转置nm.value[0][3] = 0;nm.value[1][3] = 0;nm.value[2][3] = 0;v->normal = nm.MultiplyVector3(v->normal).Normalize();//可选着色float diffuse = 0;  //漫反射float specular = 0; //镜面反射float ambient = 0.1;//环境光//直射光for (auto light : v2f.dirlights){Vector3f l = light.GetDirection().Normalize();diffuse += max(0.f, l.Dot(v->normal)) * light.intensity;Vector3f h = ((v2f.cameraPos - v->pos).Normalize() + l).Normalize();specular += pow(max(0.f, v->normal.Dot(h)), 1) * light.intensity;//diffuse+ specular超过1会出现渲染错误}//点光源
//      for (auto light : v2f.pointlights)
//      {
//          Vector3f l = (light.position-v->pos).Normalize();
//          diffuse += max(0, l.Dot(v->normal))*light.intensity*min(1, 1 / (light.position - v->pos).Magnitude());
//
//          Vector3f h = ((v2f.cameraPos - v->pos).Normalize() + (light.position-v->pos).Normalize()).Normalize();
//          //h.Normalize();
//
//          specular += pow(max(0, v->normal.Dot(h)), 1)*light.intensity*min(1, 1 / (light.position - v->pos).Magnitude());
//          //float k = (specular + diffuse) *light.intensity*min(1, 1 / (light.position - v.pos).Magnitude());
//          //cout << k << endl;
//          //cout << diffuse <<" "<< specular << endl;  Color::white+
//          // v.color + specular + diffuse;
//              //diffuse+ specular超过1会出现渲染错误
//          //v.color =v.color*(min(1, k));// Color(specular + diffuse, specular + diffuse, specular + diffuse, 1);
//              //Color(diffuse+specular, diffuse + specular, diffuse + specular, 1);
//          //cout << v.color.r << " "<<v.color.g << " " << v.color.b << " " << endl;
//      }v->color = v->color * (min(1.f, specular + diffuse + ambient)); //颜色乘上光照//VP变换v->pos = v2f.v.MultiplyVector3(v->pos);v->pos = v2f.p.MultiplyVector3(v->pos);v->pos.standardization();}//保证v0.y  v1.y  v2.y的顺序if (v1.pos.y < v0.pos.y){std::swap(v2f.lp0, v2f.lp1);std::swap(v1, v0);}if (v2.pos.y < v0.pos.y){std::swap(v2f.lp0, v2f.lp2);std::swap(v2, v0);}if (v2.pos.y < v1.pos.y){std::swap(v2f.lp1, v2f.lp2);std::swap(v2, v1);}}bool PhongShader::FS(Vertex& v, Vector3f g)
{//颜色乘上纹理信息v.color = v.color * frag.baseTex->Sample(v.uv.x, v.uv.y);//法线插值Vector3f normal = v2f.v0.normal * g.x + v2f.v1.normal * g.y + v2f.v2.normal * g.z;//光照空间下的插值Vector3f posLightSpace = v2f.lp0 * g.x + v2f.lp1 * g.y + v2f.lp2 * g.z;//阴影//https://juejin.cn/post/7021462579859947527float bias = 0.005;  //引入偏移量bias的原因:避免Shadow anceif (v2f.dirlights.size() > 0){bias = max(0.02 * (1.0 - abs(Vector3f::Dot(normal.Normalize(), v2f.dirlights[0].GetDirection().Normalize()))), 0.005);//Slope Scale Based Depth Bias}float depth = CalcuteShadow(posLightSpace, bias);v.color = v.color * (1 - depth);return v.color.a > 0;
}//bias可以通过角度计算
float PhongShader::CalcuteShadow(Vector3f posLightSpace, double bias)
{float reciprocalW = 1.0f / posLightSpace.w;//加0.5做之后的四舍五入posLightSpace.x = (posLightSpace.x * reciprocalW + 1.0f) * 0.5f * (frag.depthBuffer->width - 1) + 0.5;posLightSpace.y = (posLightSpace.y * reciprocalW + 1.0f) * 0.5f * (frag.depthBuffer->height - 1) + 0.5;float depth = (posLightSpace.z + 1.0) / 2.0;//此处可以有PCF优化float shadow = 0.0;//普通阴影float closestDepth = frag.depthBuffer->Sample(posLightSpace.y, posLightSpace.x);shadow = depth - bias > closestDepth ? 1 : 0;return shadow;
}void ShadowShader::VS(Vertex& v0, Vertex& v1, Vertex& v2)
{Vertex* v = &v1;for (int i = 0; i < 3; i++){switch (i){case 0:v = &v0;break;case 1:v = &v1;break;case 2:v = &v2;break;}//MVP矩阵v->pos = v2f.m.MultiplyVector3(v->pos);v->pos = v2f.lightV.MultiplyVector3(v->pos);v->pos = v2f.lightP.MultiplyVector3(v->pos);v->pos.standardization();}if (v1.pos.y < v0.pos.y){std::swap(v1, v0);}if (v2.pos.y < v0.pos.y){std::swap(v2, v0);}if (v2.pos.y < v1.pos.y){std::swap(v2, v1);}
}bool ShadowShader::FS(Vertex& v, Vector3f g)
{//啥都不做return false;
}

Renderer类

texture指针

和开窗口有关的变量

光照数组

相机

#pragma once#include "Vertex.h"
#include "Light.h"
#include "Texture.h"
#include "Camera.h"
#include"Shader.h"
#include "Mesh.h"
#include "Buffer.h"
#include<Windows.h>
using namespace std;
class Renderer
{
private:int deviceWidth;int deviceHeight;HDC screenHDC;Texture* tex;public:vector<DirectionLight> directionlights;vector<PointLight> pointlights;Camera* camera;
public:Renderer(HDC hdc, int screenWidth, int screenHeight, Camera* cam);~Renderer();//每渲染一次,都要清除深度缓存void Clear(DepthBuffer* zbuffer);//根据indexBuffer绘制void DrawByIndex(Mesh* m, IShader* shader, DepthBuffer* zbuffer);//根据顶点绘制void DrawByArray(Mesh* m, IShader* shader, DepthBuffer* zbuffer);void DrawMesh(Mesh* m, IShader* shader, DepthBuffer* zbuffer);void DrawPrimitive(Vertex v0, Vertex v1, Vertex v2, IShader* shader, DepthBuffer* zbuffer);void RasterizeTrangle(Vertex v0, Vertex v1, Vertex v2, IShader* shader, DepthBuffer* zbuffer);//swapIndex代表第几个点是插值出来的void DrawTopFlatTrangle(Vertex v0, Vertex v1, Vertex v2, IShader* shader, DepthBuffer* zbuffer, Vertex v3, int swapIndex);void DrawBottomFlatTrangle(Vertex v0, Vertex v1, Vertex v2, IShader* shader, DepthBuffer* zbuffer, Vertex v3, int swapIndex);void DrawLine(Vertex v0, Vertex v1, IShader* shader, DepthBuffer* zbuffer, Vector3f p0, Vector3f p1, Vector3f p2);void DrawPixel(int x, int y, const Color& color);bool ZTestAndWrite(int x, int y, float depth, DepthBuffer* zbuffer);//准备光栅化,透视投影除法,视口映射,三角形数据准备void PrepareRasterization(Vertex& vertex, Buffer* buffer);//简单CVV剔除,只考虑三顶点均不在的情况,未做边界三角形重新构建bool SimpleCVVCullCheck(const Vertex& vertex);};
#include "Renderer.h"
#include "Matrix.h"
#include <iostream>
#include <Windows.h>
using namespace std;
Renderer::Renderer(HDC hdc, int screenWidth, int screenHeight, Camera* cam)
{screenHDC = hdc;deviceWidth = screenWidth;deviceHeight = screenHeight;camera = cam;tex = new Texture();tex->LoadTexture("gezi.bmp");
}Renderer::~Renderer()
{
}void Renderer::DrawByIndex(Mesh* m, IShader* shader, DepthBuffer* zbuffer)
{for (int i = 0; i < m->indexBuffer.size(); i = i + 3){Vertex p1;p1.pos = m->positionBuffer[m->indexBuffer[i].x - 1];p1.uv = m->uvBuffer[m->indexBuffer[i].y - 1];p1.normal = m->normalBuffer[m->indexBuffer[i].z - 1];Vertex p2;p2.pos = m->positionBuffer[m->indexBuffer[i + 1].x - 1];p2.uv = m->uvBuffer[m->indexBuffer[i + 1].y - 1];p2.normal = m->normalBuffer[m->indexBuffer[i + 1].z - 1];Vertex p3;p3.pos = m->positionBuffer[m->indexBuffer[i + 2].x - 1];p3.uv = m->uvBuffer[m->indexBuffer[i + 2].y - 1];p3.normal = m->normalBuffer[m->indexBuffer[i + 2].z - 1];DrawPrimitive(p1, p2, p3, shader, zbuffer);}
}void Renderer::DrawByArray(Mesh* m, IShader* shader, DepthBuffer* zbuffer)
{for (int i = 0; i < m->vertexBuffer.size(); i = i + 3){Vertex p1 = m->vertexBuffer[i];Vertex p2 = m->vertexBuffer[i + 1];Vertex p3 = m->vertexBuffer[i + 2];DrawPrimitive(p1, p2, p3, shader, zbuffer);}
}void Renderer::DrawMesh(Mesh* m, IShader* shader, DepthBuffer* zbuffer)
{if (m->indexBuffer.size() > 0)DrawByIndex(m, shader, zbuffer);elseDrawByArray(m, shader, zbuffer);
}void Renderer::DrawPrimitive(Vertex v1, Vertex v2, Vertex v3, IShader* shader, DepthBuffer* zbuffer)
{shader->VS(v1, v2, v3);//进行CVV简单剔除判断//  if (SimpleCVVCullCheck(v1) && SimpleCVVCullCheck(v2) && SimpleCVVCullCheck(v3))//       return;//透视除法,视口映射,数据准备(全部改为1/z)PrepareRasterization(v1, zbuffer);PrepareRasterization(v2, zbuffer);PrepareRasterization(v3, zbuffer);//三角形三个点重合明显存在问题,直接不渲染,或者两个if (((int)v1.pos.y == (int)v2.pos.y && abs(v2.pos.y - v3.pos.y) <= 1) ||((int)v3.pos.y == (int)v2.pos.y && abs(v2.pos.y - v1.pos.y) <= 1) ||((int)v1.pos.y == (int)v3.pos.y && abs(v2.pos.y - v3.pos.y) <= 1))return;RasterizeTrangle(v1, v2, v3, shader, zbuffer);
}void Renderer::RasterizeTrangle(Vertex v0, Vertex v1, Vertex v2, IShader* shader, DepthBuffer* zbuffer)
{int ty0 = v0.pos.y;int ty1 = v1.pos.y;int ty2 = v2.pos.y;if (ty0 == ty1)  //上三角形{DrawTopFlatTrangle(v0, v1, v2, shader, zbuffer, Vertex(), -1);}else if (ty1 == ty2) //下三角形{DrawBottomFlatTrangle(v0, v1, v2, shader, zbuffer, Vertex(), -1);}else//拆分为一个平顶三角形和一个平底三角形{//中心点为直线(x0, y0),(x2, y2)上取y1的点float x3 = (v1.pos.y - v0.pos.y) * (v2.pos.x - v0.pos.x) / (v2.pos.y - v0.pos.y) + v0.pos.x;float y3 = v1.pos.y;float t = (y3 - v0.pos.y) / (v2.pos.y - v0.pos.y);Vertex v3(Vector3f(x3, y3, 0), Color(0, 0, 0, 0), Vector2(0, 0));v3.LerpVertexData(v0, v2, t);DrawBottomFlatTrangle(v0, v1, v3, shader, zbuffer, v2, 3);DrawTopFlatTrangle(v3, v1, v2, shader, zbuffer, v0, 1);}
}void Renderer::DrawTopFlatTrangle(Vertex v0, Vertex v1, Vertex v2, IShader* shader, DepthBuffer* zbuffer, Vertex v3, int swapIndex)
{float x0 = v0.pos.x;float y0 = v0.pos.y;float x1 = v1.pos.x;float y1 = v1.pos.y;float x2 = v2.pos.x;float y2 = v2.pos.y;for (float y = y0; y <= y2; y++){float t = (y - y0) / (y2 - y0);//用int,不然会有断线int xl = (y - y0) * (x2 - x0) / (y2 - y0) + x0;Vertex vl(Vector3f(xl, y, 0), Color(0, 0, 0, 0), Vector2(0, 0));vl.LerpVertexData(v0, v2, t);int xr = (y - y1) * (x2 - x1) / (y2 - y1) + x1;Vertex vr(Vector3f(xr, y, 0), Color(0, 0, 0, 0), Vector2(0, 0));vr.LerpVertexData(v1, v2, t);switch (swapIndex){case -1:DrawLine(vl, vr, shader, zbuffer, v0.pos, v1.pos, v2.pos);break;case 1:DrawLine(vl, vr, shader, zbuffer, v3.pos, v1.pos, v2.pos);break;case 2:DrawLine(vl, vr, shader, zbuffer, v0.pos, v3.pos, v2.pos);break;case 3:DrawLine(vl, vr, shader, zbuffer, v0.pos, v1.pos, v3.pos);break;default:DrawLine(vl, vr, shader, zbuffer, v0.pos, v1.pos, v2.pos);break;}}
}void Renderer::DrawBottomFlatTrangle(Vertex v0, Vertex v1, Vertex v2, IShader* shader, DepthBuffer* zbuffer, Vertex v3, int swapIndex)
{float x0 = v0.pos.x;float y0 = v0.pos.y;float x1 = v1.pos.x;float y1 = v1.pos.y;float x2 = v2.pos.x;float y2 = v2.pos.y;for (float y = y0; y <= y1; y++){float t = (y - y0) / (y2 - y0);int xl = ((y - y1) * (x0 - x1) / (y0 - y1) + x1);Vertex vl(Vector3f(xl, y, 0), Color(0, 0, 0, 0), Vector2(0, 0));vl.LerpVertexData(v0, v1, t);int xr = ((y - y2) * (x0 - x2) / (y0 - y2) + x2);Vertex vr(Vector3f(xr, y, 0), Color(0, 0, 0, 0), Vector2(0, 0));vr.LerpVertexData(v0, v2, t);switch (swapIndex){case -1:DrawLine(vl, vr, shader, zbuffer, v0.pos, v1.pos, v2.pos);break;case 1:DrawLine(vl, vr, shader, zbuffer, v3.pos, v1.pos, v2.pos);break;case 2:DrawLine(vl, vr, shader, zbuffer, v0.pos, v3.pos, v2.pos);break;case 3:DrawLine(vl, vr, shader, zbuffer, v0.pos, v1.pos, v3.pos);break;default:DrawLine(vl, vr, shader, zbuffer, v0.pos, v1.pos, v2.pos);break;}}
}//y值永远相等的线
void Renderer::DrawLine(Vertex v0, Vertex v1, IShader* shader, DepthBuffer* zbuffer, Vector3f p0, Vector3f p1, Vector3f p2)
{//std::cout << v0.color.r ;float x0 = v0.pos.x;float x1 = v1.pos.x;float y0 = v0.pos.y;float y1 = v1.pos.y;//只考虑x方向扫描线即可int dx = x1 - x0;int stepx = 1;if (dx < 0){stepx = -1;dx = -dx;}int x = x0;int y = y0;Vertex frag;if (x1 == x0){if (ZTestAndWrite(x, y, (v0.pos.z + 1) / 2.0, zbuffer)){Vector3f g = centerOfGravity(p0, p1, p2, Vector2(x, y));if (shader->FS(v0, g)){DrawPixel(x, y, v0.color);}}return;}for (int i = 0; i <= dx; i++){float s = (x - x0) / (x1 - x0);//透视矫正,https://zhuanlan.zhihu.com/p/144331875float t = s * v0.pos.z / (s * v0.pos.z + (1 - s) * v1.pos.z);float z = Vertex::LerpFloat(v0.pos.z, v1.pos.z, t);z = (z + 1) / 2.0;if (ZTestAndWrite(x, y, z, zbuffer)){Color c = Color::Lerp(v0.color, v1.color, t);float u = Vertex::LerpFloat(v0.uv.x, v1.uv.x, t);float v = Vertex::LerpFloat(v0.uv.y, v1.uv.y, t);frag.pos = Vector3f(x, y, z);frag.color = c;frag.uv = Vector2(u, v);Vector3f g = centerOfGravity(p0, p1, p2, Vector2(x, y));if (shader->FS(frag, g)){DrawPixel(x, y, frag.color);}}x += stepx;}
}bool Renderer::ZTestAndWrite(int x, int y, float depth, DepthBuffer* zbuffer)
{if (x >= 0 && x < zbuffer->width && y >= 0 && y < zbuffer->height){if (zbuffer->depthBuffer[y][x] >= depth){zbuffer->depthBuffer[y][x] = depth;return true;}}return false;
}inline void Renderer::DrawPixel(int x, int y, const Color& color)
{//cout << color.r << " " << color.g << " " << color.b << " " << endl;//color值超过1会自动进行tonemappingSetPixel(screenHDC, x, y, RGB(255 * color.r, 255 * color.g, 255 * color.b));
}void Renderer::Clear(DepthBuffer* zbuffer)
{BitBlt(screenHDC, 0, 0, deviceWidth, deviceHeight, NULL, NULL, NULL, BLACKNESS);//ClearZfor (int i = 0; i < zbuffer->height; i++){for (int j = 0; j < zbuffer->width; j++){zbuffer->depthBuffer[i][j] = 1;}}
}inline void Renderer::PrepareRasterization(Vertex& vertex, Buffer* buffer)
{float reciprocalW = 1.0f / vertex.pos.w;//最后加0.5是为了后面取证做四舍五入vertex.pos.x = (vertex.pos.x * reciprocalW + 1.0f) * 0.5f * (buffer->width - 1) + 0.5;vertex.pos.y = (vertex.pos.y * reciprocalW + 1.0f) * 0.5f * (buffer->height - 1) + 0.5;}inline bool Renderer::SimpleCVVCullCheck(const Vertex& vertex)
{float w = vertex.pos.w;if (vertex.pos.x < -w || vertex.pos.x > w)return true;if (vertex.pos.y < -w || vertex.pos.y > w)return true;if (vertex.pos.z < 0.0f || vertex.pos.z > w)return true;return false;
}

一切准备就绪,接下来就可以读取模型文件

ObjFileReader类---读取模型文件,存入Mesh的Buffer中

#pragma once
#include <vector>
#include <string>
#include <fstream>
#include"Mesh.h"
using namespace std;//切分字符串
void StringSplit(string s, char splitchar,vector<string>& vec);void ReadObjFile(string path, Mesh* obj);
#include "ObjFileReader.h"
#include<stdlib.h>//根据splitchar去切分字符串,存到vec里
void StringSplit(string s, char splitchar, vector<string>& vec)
{if (vec.size() > 0)//保证vec是空的  vec.clear();int length = s.length();int start = 0;for (int i = 0; i < length; i++) {if (s[i] == splitchar && i == 0)//第一个就遇到分割符  {start += 1;}else if (s[i] == splitchar) {//遇到分隔符vec.push_back(s.substr(start, i - start));start = i + 1;}else if (i == length - 1)//到达尾部  {vec.push_back(s.substr(start, i + 1 - start));}}
}void ReadObjFile(string path, Mesh* obj)
{ifstream in(path);string txt = "";//能找到该文件if (in) {while (getline(in, txt)) {//读取顶点坐标if (txt[0] == 'v' && txt[1] == ' ') {vector<string>num;txt.erase(0, 2);StringSplit(txt, ' ', num);Vector3f pos;pos = Vector3f((float)atof(num[0].c_str()), (float)atof(num[1].c_str()), (float)atof(num[2].c_str()));obj->positionBuffer.push_back(pos);}//读取顶点法线else if (txt[0] == 'v' && txt[1] == 'n') {vector<string>num;txt.erase(0, 3);StringSplit(txt, ' ', num);Vector3f n;n = Vector3f((float)atof(num[0].c_str()), (float)atof(num[1].c_str()), (float)atof(num[2].c_str()));obj->normalBuffer.push_back(n);}//读取uv坐标else if (txt[0] == 'v' && txt[1] == 't'){vector<string> num;txt.erase(0, 3);StringSplit(txt, ' ', num);Vector2 uv;uv=Vector2((float)atof(num[0].c_str()), (float)atof(num[1].c_str()));obj->uvBuffer.push_back(uv);}//读取索引编号,'/'分隔的分别是每个面的顶点 该顶点uv坐标和法线坐标else if (txt[0] == 'f' && txt[1] == ' ') {vector<string>num;txt.erase(0, 2);StringSplit(txt, ' ', num);for (int i = 0; i < num.size(); i++) {vector<string> threeIndex;StringSplit(num[i], '/', threeIndex);Vector3i indexes = { atoi(threeIndex[0].c_str()), atoi(threeIndex[1].c_str()), atoi(threeIndex[2].c_str()) };obj->indexBuffer.push_back(indexes);}}}}else {cout << "no file" << endl;}
}

四、主函数及测试效果

#include "Renderer.h"
#include "ObjFileReader.h"
#include "Window.h"
#include "Input.h"
#include <iostream>
#include <cmath>
#pragma comment( lib,"winmm.lib" )
using namespace std;static const int windowWidth = 500;
static const int windowHeight = 500;Renderer* device = NULL;
Camera* camera = new Camera(Transform(Vector3f(0,0, 100), Vector3f(0, 0, 0), Vector3f(0, 0, 0))); //右边是相机的位置
Mesh* currentMesh = new Mesh();
Mesh* m = new Mesh();
Mesh* plane = new Mesh();
Vector3f moveVector, rotateVector; //根据键盘输入去变换
PhongShader* phongShader;
PhongShader* planeShader;
ShadowShader* depthShader = new ShadowShader();
DepthBuffer* depthBuffer = new DepthBuffer(windowWidth * 2, windowHeight * 2);
DepthBuffer* zBuffer = new DepthBuffer(windowWidth, windowHeight);Matrix M;
void UpdateInput(Mesh* m)
{if (IS_KEY_DOWN('A')){moveVector.x -= 0.01f;}if (IS_KEY_DOWN('D')){moveVector.x += 0.01f;}if (IS_KEY_DOWN('W')){moveVector.y += 0.01f;}if (IS_KEY_DOWN('S')){moveVector.y -= 0.01f;}if (IS_KEY_DOWN('E')){moveVector.z -= 0.1f; }if (IS_KEY_DOWN('Q')){moveVector.z += 0.1f; }if (IS_KEY_DOWN('I')){rotateVector.y += 0.1f;}if (IS_KEY_DOWN('K')){rotateVector.y -= 0.1f;}if (IS_KEY_DOWN('L')){rotateVector.x += 0.5f;}if (IS_KEY_DOWN('J')){rotateVector.x -= 0.5f;}if (IS_KEY_DOWN('U')){rotateVector.z -= 0.1f;}if (IS_KEY_DOWN('O')){rotateVector.z += 0.1f;}Matrix s = m->GetTransform().Scale(Vector3f(1, 1, 1));Matrix r = m->GetTransform().Rotate(rotateVector);Matrix t = m->GetTransform().Translate(moveVector);//注意矩阵乘法顺序!!M = t * r * s;phongShader->v2f.m = M;depthShader->v2f.m = M;}
void Update(Window* w);
void DoRender(Window* w);
void ShowFPS(Window* w);
void CreateCube();
void CreatePlane();int main()
{Window* w = new Window(windowWidth, windowHeight, "Test");device = new Renderer(w->screenHDC, windowWidth, windowHeight, camera);//规定相机永远往-z方向看,这决定zbuffer初始化为最大值还是最小值(最大值,因为深度值为负数)//视口的范围应该是0-负无穷,相反,如果往z轴方向看,视口的范围应该是0-正无穷Matrix cameraV = camera->LookAt(camera->transform.position, Vector3f(0, 0, -1), Vector3f(0, 1, 0));//这里远近平面的值相对于物体变换到相机坐标系的位置,范围从相机位置-1到-120,此时物体的位置在-100左右,近平面越靠近-100,深度值越趋近于1,,相反越趋近于-1//Matrix cameraP = camera->Perspective(0.1, 0.1, -1, -120, 0.1, -0.1);//Matrix cameraP = camera->Perspective(90, 1, 1, 10);Matrix cameraP = camera->Orthographic(-10, 10, 0, -120, 10, -10);Matrix lightP = camera->Orthographic(-10, 10, 0, -120, 10, -10);DirectionLight light(Vector3f(0.2, 0.2, -1), Vector3f(0, 0, 100));//PointLight poingt(Vector3f(5, 5, -5), 2);Texture* gezi = new Texture();gezi->LoadTexture("gezi.bmp");phongShader = new PhongShader(PhongVert(), PhongFrag(gezi, depthBuffer));phongShader->v2f.cameraPos = camera->transform.position;phongShader->v2f.dirlights.push_back(light);//phongShader->v2f.pointlights.push_back(poingt);phongShader->v2f.v = cameraV;phongShader->v2f.p = cameraP;phongShader->v2f.lightV = light.LookAt(Vector3f(0, 1, 0));phongShader->v2f.lightP = lightP;depthShader->v2f.lightV = light.LookAt(Vector3f(0, 1, 0));depthShader->v2f.lightP = lightP;planeShader = new PhongShader(PhongVert(), PhongFrag(gezi, depthBuffer));;planeShader->v2f.cameraPos = camera->transform.position;planeShader->v2f.dirlights.push_back(light);//planeShader->v2f.pointlights.push_back(poingt);planeShader->v2f.v = cameraV;planeShader->v2f.p = lightP;planeShader->v2f.lightV = light.LookAt(Vector3f(0, 1, 0));planeShader->v2f.lightP = lightP;//ReadObjFile("cube.obj", m);CreateCube();CreatePlane();device->directionlights.push_back(light);//device->pointlights.push_back(poingt);Update(w);system("pause");return 0;
}void Update(Window* w)
{MSG msg = { 0 };while (msg.message != WM_QUIT){UpdateInput(currentMesh);if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)){TranslateMessage(&msg);DispatchMessage(&msg);}else{DoRender(w);ShowFPS(w);}}
}void DoRender(Window* w)
{device->Clear(depthBuffer);device->Clear(zBuffer);device->DrawMesh(currentMesh, depthShader, depthBuffer);device->DrawMesh(plane, depthShader, depthBuffer);device->DrawMesh(currentMesh, phongShader, zBuffer);device->DrawMesh(plane, planeShader, zBuffer);//双缓冲BitBlt(w->hdc, 0, 0, windowWidth, windowHeight, w->screenHDC, 0, 0, SRCCOPY);
}void ShowFPS(Window* w)
{static float  fps = 0;static int    frameCount = 0;static float  currentTime = 0.0f;static float  lastTime = 0.0f;frameCount++;currentTime = timeGetTime() * 0.001f;if (currentTime - lastTime > 1.0f){fps = (float)frameCount / (currentTime - lastTime);lastTime = currentTime;frameCount = 0;}char strBuffer[20];sprintf_s(strBuffer, 20, "%0.3f", fps);TextOut(w->hdc, 0, 0, strBuffer, 6);
}void CreateCube()
{currentMesh->AddVertexData(-1.0f, -1.0f, -1.0f, 0.0f, 0.0f, Vector3f(0, 0, -1), Color(1, 0, 0, 1));currentMesh->AddVertexData(1.0f, -1.0f, -1.0f, 1.0f, 0.0f, Vector3f(0, 0, -1), Color(1, 0, 0, 1));currentMesh->AddVertexData(1.0f, 1.0f, -1.0f, 1.0f, 1.0f, Vector3f(0, 0, -1), Color(1, 0, 0, 1));currentMesh->AddVertexData(1.0f, 1.0f, -1.0f, 1.0f, 1.0f, Vector3f(0, 0, -1), Color(1, 0, 0, 1));currentMesh->AddVertexData(-1.0f, 1.0f, -1.0f, 0.0f, 1.0f, Vector3f(0, 0, -1), Color(1, 0, 0, 1));currentMesh->AddVertexData(-1.0f, -1.0f, -1.0f, 0.0f, 0.0f, Vector3f(0, 0, -1), Color(1, 0, 0, 1));//前currentMesh->AddVertexData(-1.0f, -1.0f, 1.0f, 0.0f, 0.0f, Vector3f(0, 0, 1), Color(0, 1, 0, 1));currentMesh->AddVertexData(1.0f, -1.0f, 1.0f, 1.0f, 0.0f, Vector3f(0, 0, 1), Color(0, 1, 0, 1));currentMesh->AddVertexData(1.0f, 1.0f, 1.0f, 1.0f, 1.0f, Vector3f(0, 0, 1), Color(0, 1, 0, 1));currentMesh->AddVertexData(1.0f, 1.0f, 1.0f, 1.0f, 1.0f, Vector3f(0, 0, 1), Color(0, 1, 0, 1));currentMesh->AddVertexData(-1.0f, 1.0f, 1.0f, 0.0f, 1.0f, Vector3f(0, 0, 1), Color(0, 1, 0, 1));currentMesh->AddVertexData(-1.0f, -1.0f, 1.0f, 0.0f, 0.0f, Vector3f(0, 0, 1), Color(0, 1, 0, 1));//左currentMesh->AddVertexData(-1.0f, 1.0f, 1.0f, 1.0f, 0.0f, Vector3f(-1, 0, 0));currentMesh->AddVertexData(-1.0f, 1.0f, -1.0f, 1.0f, 1.0f, Vector3f(-1, 0, 0));currentMesh->AddVertexData(-1.0f, -1.0f, -1.0f, 0.0f, 1.0f, Vector3f(-1, 0, 0));currentMesh->AddVertexData(-1.0f, -1.0f, -1.0f, 0.0f, 1.0f, Vector3f(-1, 0, 0));currentMesh->AddVertexData(-1.0f, -1.0f, 1.0f, 0.0f, 0.0f, Vector3f(-1, 0, 0));currentMesh->AddVertexData(-1.0f, 1.0f, 1.0f, 1.0f, 0.0f, Vector3f(-1, 0, 0));//右currentMesh->AddVertexData(1.0f, 1.0f, 1.0f, 1.0f, 0.0f, Vector3f(1, 0, 0), Color(0, 0, 1, 1));currentMesh->AddVertexData(1.0f, 1.0f, -1.0f, 1.0f, 1.0f, Vector3f(1, 0, 0), Color(0, 0, 1, 1));currentMesh->AddVertexData(1.0f, -1.0f, -1.0f, 0.0f, 1.0f, Vector3f(1, 0, 0), Color(0, 0, 1, 1));currentMesh->AddVertexData(1.0f, -1.0f, -1.0f, 0.0f, 1.0f, Vector3f(1, 0, 0), Color(0, 0, 1, 1));currentMesh->AddVertexData(1.0f, -1.0f, 1.0f, 0.0f, 0.0f, Vector3f(1, 0, 0), Color(0, 0, 1, 1));currentMesh->AddVertexData(1.0f, 1.0f, 1.0f, 1.0f, 0.0f, Vector3f(1, 0, 0), Color(0, 0, 1, 1));//下currentMesh->AddVertexData(-1.0f, -1.0f, -1.0f, 0.0f, 1.0f, Vector3f(0, -1, 0), Color(1, 1, 0, 1));currentMesh->AddVertexData(1.0f, -1.0f, -1.0f, 1.0f, 1.0f, Vector3f(0, -1, 0), Color(1, 1, 0, 1));currentMesh->AddVertexData(1.0f, -1.0f, 1.0f, 1.0f, 0.0f, Vector3f(0, -1, 0), Color(1, 1, 0, 1));currentMesh->AddVertexData(1.0f, -1.0f, 1.0f, 1.0f, 0.0f, Vector3f(0, -1, 0), Color(1, 1, 0, 1));currentMesh->AddVertexData(-1.0f, -1.0f, 1.0f, 0.0f, 0.0f, Vector3f(0, -1, 0), Color(1, 1, 0, 1));currentMesh->AddVertexData(-1.0f, -1.0f, -1.0f, 0.0f, 1.0f, Vector3f(0, -1, 0), Color(1, 1, 0, 1));//上currentMesh->AddVertexData(-1.0f, 1.0f, -1.0f, 0.0f, 1.0f, Vector3f(0, 1, 0));currentMesh->AddVertexData(1.0f, 1.0f, -1.0f, 1.0f, 1.0f, Vector3f(0, 1, 0));currentMesh->AddVertexData(1.0f, 1.0f, 1.0f, 1.0f, 0.0f, Vector3f(0, 1, 0));currentMesh->AddVertexData(1.0f, 1.0f, 1.0f, 1.0f, 0.0f, Vector3f(0, 1, 0));currentMesh->AddVertexData(-1.0f, 1.0f, 1.0f, 0.0f, 0.0f, Vector3f(0, 1, 0));currentMesh->AddVertexData(-1.0f, 1.0f, -1.0f, 0.0f, 1.0f, Vector3f(0, 1, 0));
}void CreatePlane()
{plane->AddVertexData(-1.0f, -1.0f, -1.0f, 0.0f, 0.0f, Vector3f(0, 0, 1), Color(1, 1, 1, 1));plane->AddVertexData(1.0f, -1.0f, -1.0f, 1.0f, 0.0f, Vector3f(0, 0, 1), Color(1, 1, 1, 1));plane->AddVertexData(1.0f, 1.0f, -1.0f, 1.0f, 1.0f, Vector3f(0, 0, 1), Color(1, 1, 1, 1));plane->AddVertexData(1.0f, 1.0f, -1.0f, 1.0f, 1.0f, Vector3f(0, 0, 1), Color(1, 1, 1, 1));plane->AddVertexData(-1.0f, 1.0f, -1.0f, 0.0f, 1.0f, Vector3f(0, 0, 1), Color(1, 1, 1, 1));plane->AddVertexData(-1.0f, -1.0f, -1.0f, 0.0f, 0.0f, Vector3f(0, 0, 1), Color(1, 1, 1, 1));Matrix M;Matrix s = m->GetTransform().Scale(Vector3f(5, 5, 5));Matrix r = m->GetTransform().Rotate(Vector3f(0, 0, 0));Matrix t = m->GetTransform().Translate(Vector3f(0, 0, -5));//注意矩阵乘法顺序!!M = t * r * s;planeShader->v2f.m = M;plane->SetObjectToWorld(M);
}

SoftRender学习笔记相关推荐

  1. PyTorch 学习笔记(六):PyTorch hook 和关于 PyTorch backward 过程的理解 call

    您的位置 首页 PyTorch 学习笔记系列 PyTorch 学习笔记(六):PyTorch hook 和关于 PyTorch backward 过程的理解 发布: 2017年8月4日 7,195阅读 ...

  2. 容器云原生DevOps学习笔记——第三期:从零搭建CI/CD系统标准化交付流程

    暑期实习期间,所在的技术中台-效能研发团队规划设计并结合公司开源协同实现符合DevOps理念的研发工具平台,实现研发过程自动化.标准化: 实习期间对DevOps的理解一直懵懵懂懂,最近观看了阿里专家带 ...

  3. 容器云原生DevOps学习笔记——第二期:如何快速高质量的应用容器化迁移

    暑期实习期间,所在的技术中台-效能研发团队规划设计并结合公司开源协同实现符合DevOps理念的研发工具平台,实现研发过程自动化.标准化: 实习期间对DevOps的理解一直懵懵懂懂,最近观看了阿里专家带 ...

  4. 2020年Yann Lecun深度学习笔记(下)

    2020年Yann Lecun深度学习笔记(下)

  5. 2020年Yann Lecun深度学习笔记(上)

    2020年Yann Lecun深度学习笔记(上)

  6. 知识图谱学习笔记(1)

    知识图谱学习笔记第一部分,包含RDF介绍,以及Jena RDF API使用 知识图谱的基石:RDF RDF(Resource Description Framework),即资源描述框架,其本质是一个 ...

  7. 计算机基础知识第十讲,计算机文化基础(第十讲)学习笔记

    计算机文化基础(第十讲)学习笔记 采样和量化PictureElement Pixel(像素)(链接: 采样的实质就是要用多少点(这个点我们叫像素)来描述一张图像,比如,一幅420x570的图像,就表示 ...

  8. Go 学习推荐 —(Go by example 中文版、Go 构建 Web 应用、Go 学习笔记、Golang常见错误、Go 语言四十二章经、Go 语言高级编程)

    Go by example 中文版 Go 构建 Web 应用 Go 学习笔记:无痕 Go 标准库中文文档 Golang开发新手常犯的50个错误 50 Shades of Go: Traps, Gotc ...

  9. MongoDB学习笔记(入门)

    MongoDB学习笔记(入门) 一.文档的注意事项: 1.  键值对是有序的,如:{ "name" : "stephen", "genda" ...

最新文章

  1. pandas基于时序数据计算模型预测推理需要的统计数据(累计时间、长度变化、变化率、方差、均值、最大、最小等):数据持续的时间(分钟)、获得某一节点之后的数据总变化量、获得范围内的统计量
  2. windows给应用断网
  3. A Bug's Life(向量偏移)
  4. 桌面笔记工具KeepNote
  5. Discuz1.5 密码错误次数过多,请 15 分钟后重新登录
  6. 2020年通信网络基础期末复习
  7. mybatis学习2之ResultMap结果集映射
  8. Asp.net生成工作流、审批流的解决方案(asp.net workflow svg)
  9. dojo Quick Start/dojo入门手册--json
  10. linux-02-常用的命令-必须掌握
  11. Spring4配置文件模板
  12. django目录介绍
  13. 【HNOI 2016】大数
  14. ES6 iterator 迭代器
  15. mysql远程连接出错
  16. 一定要做自己最内行的东西,一定要在自己本身的职位上来提升自己
  17. 国内外最好用的6款Bug跟踪管理软件,测试员不可不知!
  18. 在vue里使用reset.css
  19. 车载环境下的噪声分析
  20. 爬虫第四篇-爬虫对网站改版快速解决思路

热门文章

  1. linux 打版本包,mysql官网下载linux版本安装包
  2. idea maven 发布跳过测试方式
  3. 杭电oj —— 2010
  4. Dmitrinbsp;Fomin风光摄影作品
  5. 经典手眼标定算法之Tsai-Lenz
  6. OTA【空中下载】简介
  7. python猴子偷桃递归_用matlab编程解决猴子吃桃问题
  8. html5 video 手机上播放和下载的问题
  9. 2022年中级会计实务考试冲刺题及答案
  10. 辽宁启迪电商:拼多多推荐词优化升级怎么做?