ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
上一节我们自己写好了一个数学库,以供使用。 接下来,我们首先搭建如下架构: 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框架,即一个正方体的各部分的信息结构,如顶点+索引+贴图+材质等。 这节就到这,下次再把整个案例完成。