我們已經完善了光照的參數,以及模型空間-世界空間-視角空間-裁剪空間-屏幕空間的轉換。并且搭建了我們demo的框架,即一個正方體的各部分的信息結構,如頂點+索引+貼圖+材質等。這次,我們來把一切聯系起來,創建出真正的軟渲染demo。
在上述結構中,我們定義出Moe3DDevice與Moe3DDeviceContext,用來表示設備信息。
設備上下文DC是一個Windows數據結構,它包含了某個設備的繪制屬性。通常,繪制調用都是借助于上下文對象,而這些設備上下文對象封裝了用于畫線、形狀、文本等的Windows API。設備上下文是設備無關的,所以它既可以用于繪制屏幕,也可以用于繪制打印機甚至元文件。設備上下文在內存中創建,而內存經常受到擾動,所以它的地址是不固定的。因此,一個設備上下文句柄不是直接指向設備上下文對象,而是指向另外一個跟蹤設備上下文地址的指針。
我個人認為設備上下文相當于畫圖過程中的畫布(畫紙),在VS中,這個畫布可以是顯示器,也可以使打印機,設備上下文決定了畫布的屬性,而且封裝了在畫布上畫畫的方法,比如畫線,畫點,等等,例如: pDc->LineTo(512,0); //從左下角到右上角的一條紅色直線 。我們在VS中畫圖時,首先要得到這塊畫布才可以畫畫,所以要進行獲取設備環境。
1.1Moe3DDevice.h 描述設備信息
#pragma once
#include <windows.h>
#include "MVector.h"
#include "Vertex.h"
class Moe3DDevice
{
public:
Moe3DDevice(int width, int height);
~Moe3DDevice();
public:
void DrawPixel(int x, int y, MVector color);
float GetZ(int x, int y) const;
void SetZ(int x, int y, float z);
inline UINT*& GetFrameBuffer() { return m_pFramebuffer; }
inline int GetClientWidth() { return m_width; }
inline int getClientHeight() { return m_height; }
void ClearBuffer(MVector color);
private:
int m_width;
int m_height;
UINT* m_pFramebuffer;
float **m_zBuffer; //z緩存
};
1.2Moe3DDevice.cpp 獲取設備信息
#include "Moe3DDevice.h"
#include "MathUtil.h"
using namespace MathUtil;
Moe3DDevice::Moe3DDevice(int width, int height)
{
m_width = width;
m_height = height;
m_zBuffer = new float*[width];
for (int i = 0; i < width; ++i)
{
m_zBuffer[i] = new float[height];
}
}
Moe3DDevice::~Moe3DDevice()
{
if (m_pFramebuffer)
delete m_pFramebuffer;
if (m_zBuffer)
for (int i = 0; i < m_width; ++i)
{
delete[] m_zBuffer[i];
}
}
//畫像素
void Moe3DDevice::DrawPixel(int x, int y, MVector color)
{
m_pFramebuffer[m_width*y + x] = MathUtil::ColorToUINT(color);
}
float Moe3DDevice::GetZ(int x, int y) const
{
if (x >= 0 && x < m_width && y >= 0 && y < m_height)
return m_zBuffer[x][y];
else
return 1.f;
}
void Moe3DDevice::SetZ(int x, int y, float z)
{
if (x >= 0 && x < m_width && y >= 0 && y < m_height)
{
m_zBuffer[x][y] = z;
}
}
void Moe3DDevice::ClearBuffer(MVector color)
{
for (int x = 0; x < m_width; ++x)
{
for (int y = 0; y < m_height; ++y)
{
m_pFramebuffer[m_width*y + x] = MathUtil::ColorToUINT(color);
m_zBuffer[x][y] = 0;
}
}
}
2.1Moe3DDeviceContext.h 相當于在此作畫
#pragma once
#include "Moe3DDevice.h"
#include "Vertex.h"
#include <vector>
#include "ShaderBase.h"
enum MOE3D_FILL_MODE //渲染模式
{
MOE3D_FILL_WIREFRAME,//線框模式
MOE3D_FILL_SOLIDE //實體模式
};
class Moe3DDeviceContext
{
public:
Moe3DDeviceContext();
~Moe3DDeviceContext();
public:
void Init(Moe3DDevice* pDevice); //初始化
void SetRenderMode(MOE3D_FILL_MODE mode); //設置渲染模式
void SetVertexBuffer(std::vector<VertexIn> vertices); //設置頂點緩沖
void SetCameraPos(const MVector& pos); //設置相機位置
void SetIndexBuffer(std::vector<UINT> indices); //設置索引緩沖
void SetShader(ShaderBase* base); //設置著色器
void DrawIndexed(UINT indexCount,UINT startIndexLocation,UINT startVertexLocation); //繪制圖形
private:
void ToCVV(VertexOut& v); //投影后的坐標轉化為cvv
bool Clip(const VertexOut& v); //cvv裁剪
VertexOut TransformToProj(const VertexIn& v); //轉到齊次裁剪空間
void TransformToScreen(const MMatrix& m,VertexOut& v); //轉換到屏幕坐標
bool BackFaceCulling(const VertexIn& v1, const VertexIn& v2, const VertexIn& v3); //背面消隱測試
void BresenhamDrawLine(int x1, int y1, int x2, int y2); //畫線
void ScanlineFill(const VertexOut& left, const VertexOut& right, int yIndex); //掃描線
void DrawTriangle(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3); //畫三角形
void DrawTriangleTop(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3); //畫平頂三角形
void DrawTriangleBottom(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3); //畫平底三角形
void TriangleRasterization(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3); //光柵化三角形
private:
Moe3DDevice* m_pDevice; //設備
MOE3D_FILL_MODE m_renderMode; //渲染狀態
std::vector<VertexIn> m_vertices; //頂點緩沖
std::vector<UINT> m_indices; //索引緩沖
ShaderBase* m_pShader; //著色器
MVector m_cameraPos; //相機位置 用于背面消隱
};
2.2Moe3DDeviceContext.h 具體實現
#include "Moe3DDeviceContext.h"
#include "MathUtil.h"
#include <algorithm>
Moe3DDeviceContext::Moe3DDeviceContext():m_renderMode(MOE3D_FILL_WIREFRAME),m_cameraPos(MVector(0.f,0.f,0.f,1.f))
{
}
Moe3DDeviceContext::~Moe3DDeviceContext()
{
}
void Moe3DDeviceContext::Init(Moe3DDevice* pDevice)
{
m_pDevice = pDevice;
}
//設置渲染模式
void Moe3DDeviceContext::SetRenderMode(MOE3D_FILL_MODE mode)
{
m_renderMode = mode;
}
//設置頂點緩沖
void Moe3DDeviceContext::SetVertexBuffer(std::vector<VertexIn> vertices)
{
m_vertices = vertices;
}
//設置相機位置
void Moe3DDeviceContext::SetCameraPos(const MVector& pos)
{
m_cameraPos = pos;
}
//設置索引緩沖
void Moe3DDeviceContext::SetIndexBuffer(std::vector<UINT> indices)
{
m_indices = indices;
}
//設置著色器
void Moe3DDeviceContext::SetShader(ShaderBase* base)
{
m_pShader = base;
}
//繪制頂點緩沖中的三角形
void Moe3DDeviceContext::DrawIndexed(UINT indexCount, UINT startIndexLocation, UINT startVertexLocation)
{
//得到屏幕變換矩陣
MMatrix screenTransformMat = MathUtil::MMatrixScreenTransform(m_pDevice->GetClientWidth(),
m_pDevice->getClientHeight());
for (int i = startIndexLocation; i < indexCount / 3; ++i)
{
VertexIn p1 = m_vertices[startVertexLocation + m_indices[3 * i]];
VertexIn p2 = m_vertices[startVertexLocation + m_indices[3 * i + 1]];
VertexIn p3 = m_vertices[startVertexLocation + m_indices[3 * i + 2]];
//背面消隱
if (BackFaceCulling(p1, p2, p3) == false)
{
continue;
}
//轉換到齊次裁剪空間,即投影后的坐標
VertexOut v1 = TransformToProj(p1);
VertexOut v2 = TransformToProj(p2);
VertexOut v3 = TransformToProj(p3);
//判斷是否通過cvv裁剪
if (Clip(v1) == false || Clip(v2) == false || Clip(v3) == false)
{
continue;
}
//進行透視除法 轉到cvv
ToCVV(v1);
ToCVV(v2);
ToCVV(v3);
//將投影得到的坐標轉化為屏幕坐標
TransformToScreen(screenTransformMat, v1);
TransformToScreen(screenTransformMat, v2);
TransformToScreen(screenTransformMat, v3);
DrawTriangle(v1, v2, v3);
}
}
//轉化到cvv
void Moe3DDeviceContext::ToCVV(VertexOut& v)
{
v.posH.x /= v.posH.w;
v.posH.y /= v.posH.w;
v.posH.z /= v.posH.w;
v.posH.w = 1;
}
//簡單cvv裁剪
bool Moe3DDeviceContext::Clip(const VertexOut& v)
{
//cvv為 x-1,1 y-1,1 z0,1
if (v.posH.x >= -v.posH.w && v.posH.x <= v.posH.w &&
v.posH.y >= -v.posH.w && v.posH.y <= v.posH.w &&
v.posH.z >= 0.f && v.posH.z <= v.posH.w)
{
return true;
}
return false;
}
//轉到齊次裁剪空間
VertexOut Moe3DDeviceContext::TransformToProj(const VertexIn& v)
{
VertexOut out = m_pShader->VS(v);
//設置oneDivZ
out.oneDivZ = 1 / out.posH.w;
//由于1/z和x,y成線性關系
//這里將需要插值的信息都乘以1/z 得到 s/z和t/z等,方便光柵化階段進行插值
out.color.x *= out.oneDivZ;
out.color.y *= out.oneDivZ;
out.color.z *= out.oneDivZ;
out.normal.x *= out.oneDivZ;
out.normal.y *= out.oneDivZ;
out.normal.z *= out.oneDivZ;
out.tex.u *= out.oneDivZ;
out.tex.v *= out.oneDivZ;
return out;
}
//轉換到屏幕坐標
void Moe3DDeviceContext::TransformToScreen(const MMatrix& m, VertexOut& v)
{
v.posH = v.posH * m;
}
//背面消隱
bool Moe3DDeviceContext::BackFaceCulling(const VertexIn& v1, const VertexIn& v2, const VertexIn& v3)
{
//線框模式不進行背面消隱
if (m_renderMode == MOE3D_FILL_WIREFRAME)
{
return true;
}
else
{
MVector vector1 = v2.pos - v1.pos;
MVector vector2 = v3.pos - v2.pos;
//頂點緩存中順序為順時針
//叉積得到的方向與右手系一致
MVector normal = vector1.Cross(vector2);
MVector viewDir = v1.pos - m_cameraPos;
if (normal.Dot(viewDir) < 0)
{
return true;
}
return false;
}
}
//畫三角形
void Moe3DDeviceContext::DrawTriangle(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3)
{
//線框模式
if (m_renderMode == MOE3D_FILL_WIREFRAME)
{
BresenhamDrawLine(v1.posH.x, v1.posH.y, v2.posH.x, v2.posH.y);
BresenhamDrawLine(v1.posH.x, v1.posH.y, v3.posH.x, v3.posH.y);
BresenhamDrawLine(v2.posH.x, v2.posH.y, v3.posH.x, v3.posH.y);
}
else if (m_renderMode == MOE3D_FILL_SOLIDE)
{
TriangleRasterization(v1, v2, v3);
}
}
//bresenham畫線
void Moe3DDeviceContext::BresenhamDrawLine(int x1, int y1, int x2, int y2)
{
int dx = x2 - x1;
int dy = y2 - y1;
int stepx = 1;
int stepy = 1;
if (dx >= 0)
{
stepx = 1;
}
else
{
stepx = -1;
dx = abs(dx);
}
if (dy >= 0)
{
stepy = 1;
}
else
{
stepy = -1;
dy = abs(dy);
}
int deltaX = 2 * dx;
int deltaY = 2 * dy;
if (dx > dy)
{
int error = deltaY - dx;
for (int i = 0; i <= dx; ++i)
{
if(x1 >= 0 && x1 < m_pDevice->GetClientWidth() && y1 >= 0 && y1 < m_pDevice->getClientHeight())
m_pDevice->DrawPixel(x1, y1, MVector(0.f, 0.f, 0.f,1.f));
if (error >= 0)
{
error -= deltaX;
y1 += stepy;
}
error += deltaY;
x1 += stepx;
}
}
else
{
int error = deltaX - dy;
for (int i = 0; i <= dy; i++)
{
if (x1 >= 0 && x1 < m_pDevice->GetClientWidth() && y1 >= 0 && y1 < m_pDevice->getClientHeight())
m_pDevice->DrawPixel(x1, y1, MVector(0.f, 0.f, 0.f,1.f));
if (error >= 0)
{
error -= deltaY;
x1 += stepx;
}
error += deltaX;
y1 += stepy;
}
}
}
//掃描線填充
//left 左端點 right 右端點
void Moe3DDeviceContext::ScanlineFill(const VertexOut& left, const VertexOut& right, int yIndex)
{
float dx = right.posH.x - left.posH.x;
for (float x = left.posH.x; x <= right.posH.x; x += 1.f)
{
//四舍五入
int xIndex = static_cast<int>(x + .5f);
if(xIndex >= 0 && xIndex < m_pDevice->GetClientWidth())
{
//插值系數
float lerpFactor = 0;
if (dx != 0)
{
lerpFactor = (x - left.posH.x) / dx;
}
//深度測試
//1/z’與x’和y'是線性關系的
float oneDivZ = MathUtil::Lerp(left.oneDivZ, right.oneDivZ, lerpFactor);
if (oneDivZ >= m_pDevice->GetZ(xIndex,yIndex))
{
m_pDevice->SetZ(xIndex, yIndex, oneDivZ);
float w = 1 / oneDivZ;
//插值頂點 原先需要插值的信息均乘以oneDivZ
//現在得到插值后的信息需要除以oneDivZ得到真實值
VertexOut out = MathUtil::Lerp(left, right, lerpFactor);
out.posH.y = yIndex;
out.tex.u *= w;
out.tex.v *= w;
out.normal.x *= w;
out.normal.y *= w;
out.normal.z *= w;
out.color.x *= w;
out.color.y *= w;
out.color.z *= w;
//畫像素點顏色
m_pDevice->DrawPixel(xIndex, yIndex, m_pShader->PS(out));
}
}
}
}
//畫平頂三角形 v3為下頂點
//y方向每次增加一個像素 根據y插值頂點
void Moe3DDeviceContext::DrawTriangleTop(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3)
{
float dy = 0;//每次y增加一個像素
for (float y = v1.posH.y; y <= v3.posH.y; y += 1.f)
{
//四舍五入
int yIndex = static_cast<int>(y + 0.5f);
if (yIndex >= 0 && yIndex < m_pDevice->getClientHeight())
{
float t = dy / (v3.posH.y - v1.posH.y);
//插值生成左右頂點
VertexOut new1 = MathUtil::Lerp(v1, v3, t);
VertexOut new2 = MathUtil::Lerp(v2, v3, t);
dy += 1.f;
//掃描線填充
if (new1.posH.x < new2.posH.x)
{
ScanlineFill(new1, new2, yIndex);
}
else
{
ScanlineFill(new2, new1, yIndex);
}
}
}
}
//畫平底三角形 v1為上頂點
void Moe3DDeviceContext::DrawTriangleBottom(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3)
{
float dy = 0;//每次y增加一個像素
for (float y = v1.posH.y; y <= v2.posH.y; y += 1.f)
{
//四舍五入
int yIndex = static_cast<int>(y + 0.5f);
if (yIndex >= 0 && yIndex < m_pDevice->getClientHeight())
{
float t = dy / (v2.posH.y - v1.posH.y);
//插值左右頂點
VertexOut new1 = MathUtil::Lerp(v1, v2, t);
VertexOut new2 = MathUtil::Lerp(v1, v3, t);
dy += 1.f;
//掃描線填充
if (new1.posH.x < new2.posH.x)
{
ScanlineFill(new1, new2, yIndex);
}
else
{
ScanlineFill(new2, new1, yIndex);
}
}
}
}
//光柵化三角形
void Moe3DDeviceContext::TriangleRasterization(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3)
{
//判斷是否是平底或者平頂三角形
if (v1.posH.y == v2.posH.y)
{
if (v1.posH.y < v3.posH.y)
{//平頂
DrawTriangleTop(v1, v2, v3);
}
else
{//平底
DrawTriangleBottom(v3, v1, v2);
}
}
else if (v1.posH.y == v3.posH.y)
{
if (v1.posH.y < v2.posH.y)
{//平頂
DrawTriangleTop(v1, v3, v2);
}
else
{//平底
DrawTriangleBottom(v2, v1, v3);
}
}
else if (v2.posH.y == v3.posH.y)
{
if (v2.posH.y < v1.posH.y)
{//平頂
DrawTriangleTop(v2, v3, v1);
}
else
{//平底
DrawTriangleBottom(v1, v2, v3);
}
}
//一般三角形 將其分割成平底三角形和平頂三角形
else
{
//根據y值將三個頂點排序
std::vector<VertexOut> vertices{v1,v2,v3};
std::sort(vertices.begin(), vertices.end(), [](VertexOut v1,VertexOut v2) {
return v1.posH.y < v2.posH.y;});
VertexOut top = vertices[0];
VertexOut middle = vertices[1];
VertexOut bottom = vertices[2];
//插值求中間點
float middleX = (middle.posH.y - top.posH.y) * (bottom.posH.x - top.posH.x) /
(bottom.posH.y - top.posH.y) + top.posH.x;
float dy = middle.posH.y - top.posH.y;
float t = dy / (bottom.posH.y - top.posH.y);
VertexOut newMiddle = MathUtil::Lerp(top, bottom, t);
newMiddle.posH.x = middleX;
newMiddle.posH.y = middle.posH.y;
//平頂
DrawTriangleTop(middle, newMiddle, bottom);
//平底
DrawTriangleBottom(top, middle, newMiddle);
}
}
3.1BoxDemo.cpp 實現我們的demo
過程:頂點->索引->著色器->紋理->光源->材質。
并且實現鼠標操作,來改變我們的視角。
#include <vector>
#include "BoxDemo.h"
#include "BoxShader.h"
#include "Moe3DDevice.h"
#include "Moe3DDeviceContext.h"
BoxDemo::BoxDemo():m_theta(1.5f * MathUtil::PI),m_phi(0.4*MathUtil::PI),m_radius(5.0f)
{
m_lastMousePos.x = 0;
m_lastMousePos.y = 0;
m_world = MathUtil::MMatrixIdentity();
//平行光
m_dirLight.ambient = MVector(0.2f, 0.2f, 0.2f, 1.0f);
m_dirLight.diffuse = MVector(0.5f, 0.5f, 0.5f, 1.0f);
m_dirLight.specular = MVector(0.5f, 0.5f, 0.5f, 1.0f);
m_dirLight.direction = MVector(0.57735f, 0.57735f, 0.57735f);
//材質
m_material.ambient = MVector(0.7f, 0.85f, 0.7f, 1.0f);
m_material.diffuse = MVector(0.7f, 0.85f, 0.7f, 1.0f);
m_material.specular = MVector(0.8f, 0.8f, 0.8f, 16.0f);
}
BoxDemo::~BoxDemo()
{
Clear();
}
bool BoxDemo::Init(HINSTANCE hInstance,HWND hWnd)
{
m_hWnd = hWnd;
m_hInstance = hInstance;
RECT rc;
GetClientRect(m_hWnd, &rc);
m_width = rc.right - rc.left;
m_height = rc.bottom - rc.top;
m_pDevice = new Moe3DDevice(m_width, m_height);
m_pImmediateContext = new Moe3DDeviceContext();
m_pImmediateContext->Init(m_pDevice);
m_pShader = new BoxShader();
//創建頂點緩存
GeometryGenerator::GetInstance()->CreateBox(2.f, 2.f, 2.f, m_box);
m_vertices.resize(m_box.vertices.size());
for (UINT i = 0; i < m_box.vertices.size(); ++i)
{
m_vertices[i].pos = m_box.vertices[i].pos;
m_vertices[i].tex = m_box.vertices[i].tex;
m_vertices[i].normal = m_box.vertices[i].normal;
m_vertices[i].color = m_box.vertices[i].color;
}
m_pImmediateContext->SetVertexBuffer(m_vertices);
//創建索引緩存
//create index buffer
m_indices.resize(m_box.indices.size());
for (UINT i = 0; i < m_box.indices.size(); ++i)
{
m_indices[i] = m_box.indices[i];
}
m_pImmediateContext->SetIndexBuffer(m_indices);
//設置著色器
m_pImmediateContext->SetShader(m_pShader);
//加載紋理
m_tex = MathUtil::LoadBitmapToColorArray(L"../Texture/nico.bmp");
//設置著色器紋理
//由于紋理加載一次不在改變,故不用再Update中設置
m_pShader->SetTexture(m_tex);
//設置著色器光源、材質
m_pShader->SetDirLight(m_dirLight);
m_pShader->SetMaterial(m_material);
return true;
}
void BoxDemo::Update(float dt)
{
float x = m_radius * sinf(m_phi) * cosf(m_theta);
float z = m_radius * sinf(m_phi) * sinf(m_theta);
float y = m_radius * cosf(m_phi);
MVector pos(x, y, z, 1.f);
MVector target(0.f, 0.f, 0.f, 1.f);
MVector up(0.f, 1.f, 0.f, 0.f);
MMatrix view = MathUtil::MMatrixLookAtLH(pos, target, up);
MMatrix proj = MathUtil::MMatrixPerspectiveFovLH(MathUtil::PI / 4, m_pDevice->GetClientWidth() /
static_cast<float>(m_pDevice->getClientHeight()), 1.f, 100.f);
MMatrix world = MathUtil::MMatrixIdentity();
m_worldViewProj = world*view*proj;
m_world = world;
//計算逆矩陣的轉置
m_worldInvTranspose = MathUtil::MMatrixTranspose(MathUtil::MMatrixInverse(world));
//設置相機位置 以便背面消隱
m_pImmediateContext->SetCameraPos(pos);
//設置著色器中eyePos
m_pShader->SetEyePos(pos);
}
void BoxDemo::Render()
{
//清空后緩沖圖片
m_pDevice->ClearBuffer(MVector(0.75f, 0.75f, 0.75f,1.f));
//設置渲染狀態
m_pImmediateContext->SetRenderMode(MOE3D_FILL_SOLIDE);
//設置著色器變量
m_pShader->SetWorldViewProj(m_worldViewProj);
m_pShader->SetWorld(m_world);
m_pShader->SetWorldInvTranspose(m_worldInvTranspose);
m_pImmediateContext->DrawIndexed(m_indices.size(), 0, 0);
}
void BoxDemo::Clear()
{
if(m_pDevice)
delete m_pDevice;
if(m_pImmediateContext)
delete m_pImmediateContext;
if (m_pShader)
delete m_pShader;
}
void BoxDemo::OnMouseDown(WPARAM btnState, int x, int y)
{
m_lastMousePos.x = x;
m_lastMousePos.y = y;
SetCapture(m_hWnd);
}
void BoxDemo::OnMouseUp(WPARAM btnState, int x, int y)
{
ReleaseCapture();
}
void BoxDemo::OnMouseMove(WPARAM btnState, int x, int y)
{
if ((btnState & MK_LBUTTON) != 0)
{
//角度轉弧度
float dx = MathUtil::MConvertToRadians(0.25f*static_cast<float>(x - m_lastMousePos.x));
float dy = MathUtil::MConvertToRadians(0.25f*static_cast<float>(y - m_lastMousePos.y));
m_theta -= dx;
m_phi += dy;
m_phi = MathUtil::Clamp(m_phi, 0.1f, MathUtil::PI - 0.1f);
}
else if ((btnState & MK_RBUTTON) != 0)
{
float dx = 0.2f*static_cast<float>(x - m_lastMousePos.x);
float dy = 0.2f*static_cast<float>(y - m_lastMousePos.y);
m_radius += dx - dy;
m_radius = MathUtil::Clamp(m_radius, 3.0f, 300.0f);
}
m_lastMousePos.x = x;
m_lastMousePos.y = y;
}
以上,我們已經完成基本所有的軟渲染demo條件,包括軟渲染的流程、設備上下文模擬、以及demo的結構。
- vs2017宇宙最偉大IDE用Console等調試匯總
- c++Win32起始鼠標作圖181101
- 用迭代法找(兩圓的)交點-精確計算迭代并改進-數值周期1810
- 精度-比例關系181110P2點
- 用迭代法求找兩圓交點-精度計算181111A
- 月亮型-大小圓-上下圓算法181121
- 用c++的數學計算及圖形繪制總結之1/共4-181101
- 用c++做數學計算及圖形繪制總結之2/4-181102
- 用c++做數學計算及圖形繪制總結之3/4-181103
- 用c++做數學計算及圖形繪制總結4/4-181104
- 用c++的移位代替乘除運算181105
- 重構billiard2圓相交-非遞歸181101-非預料內圖形-原因分析181201
- 重構月亮型billiard202圓相交-非遞歸181102-非預料內圖形-原因分析181202
- 重構月亮型billiard202圓相交-非遞歸181102b-非預料內圖形-原因分析181202b
- 單橢圓(非遞歸)18圣誕后-ok版181225