上一节我们自己写好了一个数学库,以供使用。
接下来,我们首先搭建如下架构:
ShaderBase:绘制shader。GeometryGenerator:几何体结构。LightHelper:描述光照。BoxShader:我们demo的着色器实现。BoxDemo:我们最终展示的demo。
我们先实现以上部分功能。
1.ShderBase
我们只定义顶点着色器与像素/片元着色器(自己实现这两个都差不多,下文统一称呼为像素着色器)的基本实现。
ShaderBase.h
#pragma once
#include "Vertex.h"
#include "MVector.h"
#include "MMath.h"
#include "LightHelper.h"
class ShaderBase
{
public:
ShaderBase();
virtual ~ShaderBase();
public:
virtual VertexOut VS(const VertexIn& vin) = 0; //顶点着色器
virtual MVector PS(VertexOut& pin) = 0; //像素着色器
};
ShaderBase.cpp
#include "ShaderBase.h"
ShaderBase::ShaderBase()
{
}
ShaderBase::~ShaderBase()
{
}
2.1GeometryGenerator.h
我们定义几何体结构,由顶点+索引组成,同时为该结构创建一个单例,并且创建一个立方体作为以后的demo。
#pragma once
#include "MMath.h"
class GeometryGenerator
{
private:
GeometryGenerator() {}
public:
//单例模式
static GeometryGenerator* GetInstance()
{
static GeometryGenerator instance;
return &instance;
}
//基本网络结构:顶点集合+索引集合
struct MeshData
{
std::vector<VertexIn> vertices;
std::vector<UINT> indices;
};
//创建一个立方体:指定宽(X方向)、高(Y方向)、深(Z方向)
void CreateBox(float width, float height, float depth, MeshData &mesh);
};
2.1GeometryGenerator.cpp
填入各个部分的顶点和索引信息。
#include "./GeometryGenerator.h"
void GeometryGenerator::CreateBox(float width, float height, float depth, MeshData &mesh)
{
mesh.vertices.clear();
mesh.indices.clear();
//一共24个顶点(每面4个)
mesh.vertices.resize(24);
//一共36个索引(每面6个)
mesh.indices.resize(36);
float halfW = width * 0.5f;
float halfH = height * 0.5f;
float halfD = depth * 0.5f;
//眼睛面向z轴正方向
//构建顶点
//前面
mesh.vertices[0].pos = MVector(-halfW, -halfH, -halfD,1.f);
mesh.vertices[0].normal = MVector(0.f, 0.f, -1.f);
mesh.vertices[0].color = MVector(1.f, 0.f, 0.f,1.f);
mesh.vertices[0].tex = MFLOAT2(0.f, 1.f);
mesh.vertices[1].pos = MVector(-halfW, halfH, -halfD,1.f);
mesh.vertices[1].normal = MVector(0.f, 0.f, -1.f);
mesh.vertices[1].color = MVector(0.f, 0.f, 0.f, 1.f);
mesh.vertices[1].tex = MFLOAT2(0.f, 0.f);
mesh.vertices[2].pos = MVector(halfW, halfH, -halfD, 1.f);
mesh.vertices[2].normal = MVector(0.f, 0.f, -1.f);
mesh.vertices[2].color = MVector(1.f, 0.f, 0.f, 1.f);
mesh.vertices[2].tex = MFLOAT2(1.f, 0.f);
mesh.vertices[3].pos = MVector(halfW, -halfH, -halfD, 1.f);
mesh.vertices[3].normal = MVector(0.f, 0.f, -1.f);
mesh.vertices[3].color = MVector(0.f, 1.f, 0.f, 1.f);
mesh.vertices[3].tex = MFLOAT2(1.f, 1.f);
//省略其他面
......
}
3.1LightHelper.h:
描述光照,包含点光源,平行光,聚光灯以及材质。内容很简单,自己看注释吧。
注意
//入射光线关于法线的反射向量
MVector v = MathUtil::Reflect(-lightVec, normal);
float specFactor = pow(max(v.Dot(toEye), 0.0f), mat.specular.w);
//计算漫反射光
diffuse = mat.diffuse * L.diffuse * diffuseFactor;
//计算高光
spec = mat.specular * L.specular * specFactor;
#pragma once
#include <windows.h>
#include <algorithm>
#include "../Math/MMath.h"
namespace Lights {
//平行光
struct DirectionalLight
{
DirectionalLight() { ZeroMemory(this, sizeof(this)); }
MVector ambient; //环境光
MVector diffuse; //漫反射光
MVector specular; //高光
MVector direction; //光照方向
};
//点光源
struct PointLight
{
PointLight() { ZeroMemory(this, sizeof(this)); }
MVector ambient;
MVector diffuse;
MVector specular;
MVector position;//光源位置
MVector att; //衰减系数
float range; //光照范围
};
//聚光灯
struct SpotLight
{
SpotLight() { ZeroMemory(this, sizeof(this)); }
MVector ambient;
MVector diffuse;
MVector specular;
MVector position; //光照位置
MVector direction; //光照方向
MVector att; //衰减系数
float range; //光照范围
float spot; //光照强度系数
};
//材质
struct Material
{
Material() { ZeroMemory(this, sizeof(this)); }
MVector ambient;
MVector diffuse;
MVector specular;//w表示高光强度
MVector reflect;
};
//计算平行光
inline void ComputeDirectionalLight(
const Material& mat, //材质
const DirectionalLight& L, //平行光
MVector normal, //顶点法线
MVector toEye, //顶点到眼睛的向量
MVector & ambient, //计算结果:环境光
MVector & diffuse, //计算结果:漫反射光
MVector & spec) //计算结果:高光
{
// 结果初始化为0
ambient = MVector( 0.0f, 0.0f, 0.0f, 0.0f );
diffuse = MVector(0.0f, 0.0f, 0.0f, 0.0f);
spec = MVector(0.0f, 0.0f, 0.0f, 0.0f);
// 光线方向
MVector lightVec = -L.direction;
// 环境光直接计算
ambient = mat.ambient * L.ambient;
// 计算漫反射系数
//光线、法线方向归一化
lightVec.Normalize();
float diffuseFactor = lightVec.Dot(normal);
// 顶点背向光源不再计算
if (diffuseFactor > 0.0f)
{
//入射光线关于法线的反射向量
MVector v = MathUtil::Reflect(-lightVec, normal);
float specFactor = pow(max(v.Dot(toEye), 0.0f), mat.specular.w);
//计算漫反射光
diffuse = mat.diffuse * L.diffuse * diffuseFactor;
//计算高光
spec = mat.specular * L.specular * specFactor;
}
}
//计算点光源
inline void ComputePointLight(
const Material& mat, //材质
PointLight L, //点光源
MVector pos, //顶点位置
MVector normal, //顶点法线
MVector toEye, //顶点到眼睛的向量
MVector& ambient, //计算结果:环境光
MVector& diffuse, //计算结果:漫反射光
MVector& spec) //计算结果:高光
{
ambient = MVector(0.0f, 0.0f, 0.0f, 0.0f);
diffuse = MVector(0.0f, 0.0f, 0.0f, 0.0f);
spec = MVector(0.0f, 0.0f, 0.0f, 0.0f);
//光照方向:顶点到光源
MVector lightVec = L.position - pos;
//顶点到光源距离
float d = MathUtil::Length(lightVec);
//超过范围不再计算
if (d > L.range)
return;
//归一化光照方向
lightVec = lightVec * (1.f / d);
//计算环境光
ambient = mat.ambient * L.ambient;
//漫反射系数
float diffuseFactor = lightVec.Dot(normal);
if (diffuseFactor > 0.0f)
{
MVector v = MathUtil::Reflect(-lightVec, normal);
float specFactor = pow(max(v.Dot(toEye), 0.0f), mat.specular.w);
//计算漫反射光
diffuse = mat.diffuse * L.diffuse * diffuseFactor;
//计算高光
spec = mat.specular * L.specular * specFactor;
}
// 计算衰减
float att = 1.f / L.att.Dot(MVector(1.f, d, d*d));
diffuse = diffuse * att;
spec = diffuse * att;
}
//计算聚光灯
inline void ComputeSpotLight(
const Material& mat, //材质
const SpotLight& L, //聚光灯
MVector pos, //顶点位置
MVector normal, //顶点法线
MVector toEye, //顶点到眼睛向量
MVector& ambient, //计算结果:环境光
MVector& diffuse, //计算结果:漫反射光
MVector& spec) //计算结果:高光
{
//初始化结果
ambient = MVector(0.0f, 0.0f, 0.0f, 0.0f);
diffuse = MVector(0.0f, 0.0f, 0.0f, 0.0f);
spec = MVector(0.0f, 0.0f, 0.0f, 0.0f);
//光照方向:顶点到光源
MVector lightVec = L.position - pos;
//顶点到光源距离
float d = MathUtil::Length(lightVec);
//距离大于光照方向不再计算
if (d > L.range)
return;
//归一化光照方向
lightVec = lightVec * (1.f / d);
//计算环境光
ambient = mat.ambient * L.ambient;
//计算漫反射系数
float diffuseFactor = lightVec.Dot(normal);
if (diffuseFactor > 0.0f)
{
MVector v = MathUtil::Reflect(-lightVec, normal);
float specFactor = pow(max(v.Dot(toEye), 0.0f), mat.specular.w);
//漫反射光
diffuse = mat.diffuse * L.diffuse * diffuseFactor;
//高光
spec = mat.specular * L.specular * specFactor;
}
//聚光衰减系数
float spot = pow(max(-lightVec.Dot(L.direction), 0.0f), L.spot);
//衰减系数
float att = spot / L.att.Dot(MVector(1.0f, d, d*d));
ambient = ambient * spot;
diffuse = diffuse * att;
spec = spec * att;
}
}
4.1BoxShader.h:
定义下我们demo的着色器实现。
把我们的box实现model->world->view->clip->screen。
#pragma once
#include "ShaderBase.h"
#include "MMath.h"
class BoxShader : public ShaderBase
{
public:
BoxShader();
~BoxShader();
private:
MMatrix m_worldViewProj; //世界视角投影矩阵
Texture2D m_tex; //纹理
MMatrix m_world; //世界矩阵
MMatrix m_worldInvTranspose; //世界矩阵逆矩阵的转置,用于调整法线
Lights::Material m_material; //材质
Lights::DirectionalLight m_dirLight; //平行光
MVector m_eyePos; //观察点
public:
void SetWorldViewProj(const MMatrix& worldViewProj);
void SetTexture(const Texture2D& tex);
void SetWorld(const MMatrix& world);
void SetWorldInvTranspose(const MMatrix& worldInvTrans);
void SetEyePos(const MVector& eyePos);
void SetMaterial(const Lights::Material& material);
void SetDirLight(const Lights::DirectionalLight& dirLight);
public:
VertexOut VS(const VertexIn& vin); //顶点着色器
MVector PS(VertexOut& pin);
};
4.2BoxShader.cpp
重点只在于
//纹理+光照计算公式: 纹理*(环境光+漫反射光)+高光
MVector litColor = texColor * (ambient + diffuse) + specular;
//最终颜色透明度使用漫反射光的透明度
litColor.w = m_material.diffuse.w * texColor.w;
#include "BoxShader.h"
BoxShader::BoxShader()
{
}
BoxShader::~BoxShader()
{
}
void BoxShader::SetWorldViewProj(const MMatrix& worldViewProj)
{
m_worldViewProj = worldViewProj;
}
void BoxShader::SetTexture(const Texture2D& tex)
{
m_tex = tex;
}
void BoxShader::SetWorld(const MMatrix& world)
{
m_world = world;
}
void BoxShader::SetWorldInvTranspose(const MMatrix& worldInvTrans)
{
m_worldInvTranspose = worldInvTrans;
}
void BoxShader::SetEyePos(const MVector& eyePos)
{
m_eyePos = eyePos;
}
void BoxShader::SetMaterial(const Lights::Material& material)
{
m_material.ambient = material.ambient;
m_material.diffuse = material.diffuse;
m_material.reflect = material.reflect;
m_material.specular = material.specular;
}
void BoxShader::SetDirLight(const Lights::DirectionalLight& dirLight)
{
m_dirLight.ambient = dirLight.ambient;
m_dirLight.diffuse = dirLight.diffuse;
m_dirLight.direction = dirLight.direction;
m_dirLight.specular = dirLight.specular;
}
VertexOut BoxShader::VS(const VertexIn& vin)
{
VertexOut out;
out.posH = vin.pos * m_worldViewProj;
out.posTrans = vin.pos * m_world;
out.normal = out.normal * m_worldInvTranspose;
out.color = vin.color;
out.tex = vin.tex;
return out;
}
MVector BoxShader::PS(VertexOut& pin)
{
//单位化法线
pin.normal.Normalize();
//纹理采样
MVector texColor = m_tex.Sample(pin.tex);
//顶点到观察点向量
MVector toEye = (m_eyePos - pin.posTrans).Normalize();
//初始化颜色值全部为0
MVector ambient(0.f, 0.f, 0.f, 0.f);
MVector diffuse(0.f, 0.f, 0.f, 0.f);
MVector specular(0.f, 0.f, 0.f, 0.f);
//光源计算后得到的环境光、漫反射 、高光
MVector A, D, S;
Lights::ComputeDirectionalLight(m_material, m_dirLight, pin.normal, toEye, A, D, S);
ambient = ambient + A;
diffuse = diffuse + D;
specular = specular + S;
//纹理+光照计算公式: 纹理*(环境光+漫反射光)+高光
MVector litColor = texColor * (ambient + diffuse) + specular;
//最终颜色透明度使用漫反射光的透明度
litColor.w = m_material.diffuse.w * texColor.w;
return litColor;
}
5.1BoxDemo.h
我们定义出自己demo各部分所需要的数据结构,包括各种矩阵的运算,顶点、材质、纹理、光照等。
#pragma once
#include "Moe3D.h"
#include "BoxShader.h"
#include "MMath.h"
#include "GeometryGenerator.h"
class BoxDemo
{
public:
BoxDemo();
~BoxDemo();
public:
bool Init(HINSTANCE hInstance, HWND hWnd);
void Update(float dt);
void Render();
void Clear();
//鼠标事件控制
void OnMouseDown(WPARAM btnState, int x, int y);
void OnMouseUp(WPARAM btnState, int x, int y);
void OnMouseMove(WPARAM btnState, int x, int y);
public:
inline Moe3DDevice*& GetDevice() { return m_pDevice; }
private:
int m_width, m_height;
HINSTANCE m_hInstance;
HWND m_hWnd;
Moe3DDevice* m_pDevice;
Moe3DDeviceContext* m_pImmediateContext;
BoxShader* m_pShader;
MMatrix m_worldViewProj; //世界视角投影矩阵
MMatrix m_world; //世界变换矩阵
MMatrix m_worldInvTranspose; //世界变换逆矩阵的转置 用于调整法线
std::vector<VertexIn> m_vertices; //顶点缓冲
std::vector<UINT> m_indices; //索引缓冲
GeometryGenerator::MeshData m_box;
Texture2D m_tex; //纹理
Lights::Material m_material; //材质
Lights::DirectionalLight m_dirLight; //平行光源
//控制摄像机位置角度等
float m_theta;
float m_phi;
float m_radius;
POINT m_lastMousePos;
};
经过以上的过程,我们自己搭建了一套数学库,然后自己实现了图片的读取,这次我们又完善了光照的参数,以及模型空间-世界空间-视角空间-裁剪空间-屏幕空间的转换。并且搭建了我们的Billiard框架,即一个正方体的各部分的信息结构,如顶点+索引+贴图+材质等。
这节就到这,下次再把整个案例完成。
- 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