光照模型相关代码实现

内容:
1、增加顶点法线
2、lightShader
3、示例

增加顶点法线

dataStructures.h

#pragma once
#include "../global/base.h"
#include "../math/math.h"

//VAO之中,用于描述属性读取方式的Description
struct BindingDescription {
uint32_t mVboId{ 0 };
size_t mItemSize{ 0 };
size_t mStride{ 0 };
size_t mOffset{ 0 };
};

struct VsOutput {
float mOneOverW{ 0.0f };
math::vec4f mPosition{ 0.0f, 0.0f, 0.0f, 1.0f };
math::vec4f mColor;//此处颜色改为0.0-1.0之间表达0-255的量
math::vec3f mNormal;//添加法线向量<-------------------------------+
math::vec2f mUV;
};

struct FsOutput {
math::vec2i mPixelPos;
float mDepth;
RGBA mColor;//此处使用0-255来进行颜色显示
};

raster.cpp

对于法线的插值和对颜色的插值一样

void Raster::interpolantTriangle(const VsOutput& v0, const VsOutput& v1, const VsOutput& v2, VsOutput& p) {
auto e1 = math::vec2f(v1.mPosition.x - v0.mPosition.x, v1.mPosition.y - v0.mPosition.y);
auto e2 = math::vec2f(v2.mPosition.x - v0.mPosition.x, v2.mPosition.y - v0.mPosition.y);
float sumArea = std::abs(math::cross(e1, e2));

auto pv0 = math::vec2f(v0.mPosition.x - p.mPosition.x, v0.mPosition.y - p.mPosition.y);
auto pv1 = math::vec2f(v1.mPosition.x - p.mPosition.x, v1.mPosition.y - p.mPosition.y);
auto pv2 = math::vec2f(v2.mPosition.x - p.mPosition.x, v2.mPosition.y - p.mPosition.y);
//计算v0的权重

float v0Area = std::abs(math::cross(pv1, pv2));
float v1Area = std::abs(math::cross(pv0, pv2));
float v2Area = std::abs(math::cross(pv0, pv1));

float weight0 = sumArea != 0 ? v0Area / sumArea : 1.0f;
float weight1 = sumArea != 0 ? v1Area / sumArea : 0.0f;
float weight2 = sumArea != 0 ? v2Area / sumArea : 0.0f;

//插值1/w,用于透视恢复
p.mOneOverW = math::lerp(v0.mOneOverW, v1.mOneOverW, v2.mOneOverW, weight0, weight1, weight2);

//插值深度值
p.mPosition.z = math::lerp(v0.mPosition.z, v1.mPosition.z, v2.mPosition.z, weight0, weight1, weight2);

//对于颜色的插值
p.mColor = math::lerp(v0.mColor, v1.mColor, v2.mColor, weight0, weight1, weight2);

//对于法线的插值<-------------------------------+
p.mNormal = math::lerp(v0.mNormal, v1.mNormal, v2.mNormal, weight0, weight1, weight2);

//对于uv坐标的插值
p.mUV = math::lerp(v0.mUV, v1.mUV, v2.mUV, weight0, weight1, weight2);
}

gpu.cpp

在透视除法和透视恢复中需要加入法线处理

void GPU::perspectiveDivision(VsOutput& vsOutput) {
vsOutput.mOneOverW = 1.0f / vsOutput.mPosition.w;

vsOutput.mPosition *= vsOutput.mOneOverW;
vsOutput.mPosition.w = 1.0f;

vsOutput.mColor *= vsOutput.mOneOverW;
vsOutput.mNormal *= vsOutput.mOneOverW;//<-------------------------------+
vsOutput.mUV *= vsOutput.mOneOverW;

trim(vsOutput);
}

void GPU::perspectiveRecover(VsOutput& vsOutput) {
vsOutput.mColor /= vsOutput.mOneOverW;
vsOutput.mNormal /= vsOutput.mOneOverW;//恢复<-------------------------------+
vsOutput.mUV /= vsOutput.mOneOverW;
}

gpu.cpp

在透视除法和透视恢复中需要加入法线处理

void GPU::perspectiveDivision(VsOutput& vsOutput) {
vsOutput.mOneOverW = 1.0f / vsOutput.mPosition.w;

vsOutput.mPosition *= vsOutput.mOneOverW;
vsOutput.mPosition.w = 1.0f;

vsOutput.mColor *= vsOutput.mOneOverW;
vsOutput.mNormal *= vsOutput.mOneOverW;//<-------------------------------+
vsOutput.mUV *= vsOutput.mOneOverW;

trim(vsOutput);
}

void GPU::perspectiveRecover(VsOutput& vsOutput) {
vsOutput.mColor /= vsOutput.mOneOverW;
vsOutput.mNormal /= vsOutput.mOneOverW;//恢复<-------------------------------+
vsOutput.mUV /= vsOutput.mOneOverW;
}

clipper.cpp

在剪裁产生新顶点时也需要进行法线的插值

//注意,这里只能插值位于平面两侧的点
VsOutput Clipper::intersect(const VsOutput& last, const VsOutput& current, const math::vec4f& plane) {
VsOutput output;

float distanceLast = math::dot(last.mPosition, plane);
float distanceCurrent = math::dot(current.mPosition, plane);
float weight = distanceLast / (distanceLast - distanceCurrent);

output.mPosition = math::lerp(last.mPosition, current.mPosition, weight);
output.mColor = math::lerp(last.mColor, current.mColor, weight);
output.mNormal = math::lerp(last.mNormal, current.mNormal, weight);//产生新顶点的法线向量<-------------------------------+
output.mUV = math::lerp(last.mUV, current.mUV, weight);

return output;
}

lightShader

shader.h

#pragma once
#include "../global/base.h"
#include "dataStructures.h"
#include "bufferObject.h"
#include "texture.h"

class Shader {
public:
Shader() {}
~Shader() {}
virtual VsOutput vertexShader(
//VAO当中的bindingMap
const std::map<uint32_t, BindingDescription>& bindingMap,

//VBO当中的bindingMap
const std::map<uint32_t,BufferObject*>& bufferMap,

//当前要处理的顶点的index
const uint32_t& index
) = 0;

virtual void fragmentShader(const VsOutput& input, FsOutput& output, const std::map<uint32_t, Texture*>& textures) = 0;

//tool functions
public:
//你要先看在VertexShader函数中如何使用,再看逻辑
math::vec4f getVector(
const std::map<uint32_t, BindingDescription>& bindingMap,
const std::map<uint32_t, BufferObject*>& bufferMap,
const uint32_t& attributeLocation,//当前属性的编号
const uint32_t& index);//当前顶点编号

RGBA vectorToRGBA(const math::vec4f& v) {//确保每个颜色通道数值在0到1之间<-------------------------------+
//防止颜色越界
math::vec4f c = v;
c.x = std::clamp(c.x, 0.0f, 1.0f);
c.y = std::clamp(c.y, 0.0f, 1.0f);
c.z = std::clamp(c.z, 0.0f, 1.0f);
c.w = std::clamp(c.w, 0.0f, 1.0f);

RGBA color;
color.mR = c.x * 255.0;
color.mG = c.y * 255.0;
color.mB = c.z * 255.0;
color.mA = c.w * 255.0;

return color;
}
};

lightShader.h

#pragma once
#include "../shader.h"
#include "../../math/math.h"

struct DirectionalLight {//平行光(只要颜色和方向的光束,光源无限远,无限大,如太阳之于地球)
math::vec3f color;//颜色
math::vec3f direction;//方向
};

class LightShader :public Shader {
public:
LightShader();
~LightShader();

VsOutput vertexShader(
const std::map<uint32_t, BindingDescription>& bindingMap,
const std::map<uint32_t, BufferObject*>& bufferMap,
const uint32_t& index
)override;

void fragmentShader(const VsOutput& input, FsOutput& output, const std::map<uint32_t, Texture*>& textures)override;

public:
//uniforms
math::mat4f mModelMatrix;
math::mat4f mViewMatrix;
math::mat4f mProjectionMatrix;

uint32_t mDiffuseTexture{ 0 };
DirectionalLight mDirectionalLight;//平行光<-------------------------------+
math::vec3f mEnvLight;//环境光<-------------------------------+
};

lightShader.cpp

#include "lightShader.h"


LightShader::LightShader() {}
LightShader::~LightShader() {}

VsOutput LightShader::vertexShader(
const std::map<uint32_t, BindingDescription>& bindingMap,
const std::map<uint32_t, BufferObject*>& bufferMap,
const uint32_t& index
) {
VsOutput output;

//取出Attribute数值
math::vec4f position = getVector(bindingMap, bufferMap, 0, index);

//变化为齐次坐标
position.w = 1.0f;
//math::vec4f color = getVector(bindingMap, bufferMap, 1, index);没有使用到颜色

math::vec3f normal = getVector(bindingMap, bufferMap, 1, index);

math::vec2f uv = getVector(bindingMap, bufferMap, 2, index);

output.mPosition = mProjectionMatrix * mViewMatrix * mModelMatrix * position;
output.mNormal = normal;
output.mUV = uv;

return output;
}

void LightShader::fragmentShader(const VsOutput& input, FsOutput& output, const std::map<uint32_t, Texture*>& textures) {
output.mPixelPos.x = static_cast<int>(input.mPosition.x);
output.mPixelPos.y = static_cast<int>(input.mPosition.y);
output.mDepth = input.mPosition.z;

//取出法线
auto normal = math::normalize(input.mNormal);//归一化
auto lightDirection = math::normalize(mDirectionalLight.direction);

//取出texture
auto iter = textures.find(mDiffuseTexture);
Texture* texture = nullptr;
if (iter != textures.end()) {
texture = iter->second;
}

//计算颜色
math::vec4f texColor = {1.0f, 1.0f, 1.0f, 1.0f};
if (texture) {
texColor = texture->getColor(input.mUV.x, input.mUV.y);
}

//计算漫反射光
math::vec4f diffuseColor;//漫反射光最终的颜色
float diff = math::dot(normal, -lightDirection);
diff = std::clamp(diff, 0.0f, 1.0f);//夹逼在0到1之间
diffuseColor = texColor * diff * math::vec4f(mDirectionalLight.color, 1.0f);

//计算环境光
math::vec4f envColor;
envColor = texColor * math::vec4f(mEnvLight, 1.0f);

output.mColor = vectorToRGBA(diffuseColor + envColor);
}

示例(由于是软光栅,调成release模式启动会提高渲染效率)

uint32_t WIDTH = 800;
uint32_t HEIGHT = 600;

Camera* camera = nullptr;

//两个三角形,三个属性对应vbo
uint32_t interleavedVbo = 0;

//三角形的indices
uint32_t ebo = 0;

//两个三角形专属vao
uint32_t vao = 0;

//使用的Shader
LightShader* shader = nullptr;
DirectionalLight directionalLight;
math::vec3f envLight;

//使用的texture
uint32_t texture = 0;
Image* image = nullptr;

//mvp变换矩阵
math::mat4f modelMatrix;
void transform() {
modelMatrix = math::translate(math::mat4f(), 0.0f, 0.0f, -4.0f);
}

void render() {
transform();

shader->mModelMatrix = modelMatrix;
shader->mViewMatrix = camera->getViewMatrix();
shader->mProjectionMatrix = camera->getProjectionMatrix();
shader->mDiffuseTexture = texture;

shader->mDirectionalLight = directionalLight;
shader->mEnvLight = envLight;

sgl->clear();
sgl->useProgram(shader);

sgl->bindVertexArray(vao);
sgl->bindBuffer(ELEMENT_ARRAY_BUFFER, ebo);
sgl->drawElement(DRAW_TRIANGLES, 0, 36);

}

void prepare() {
camera = new Camera(60.0f, (float)WIDTH / (float)HEIGHT, 0.1f, 100.0f, { 0.0f, 1.0f, 0.0f });
app->setCamera(camera);
camera->setSpeed(0.02f);

//光照设置
shader = new LightShader();
directionalLight.color = {1.0f, 1.0f, 1.0f};
directionalLight.direction = {-1.0f, -0.3f, -0.7f};
envLight = {0.1f, 0.1f, 0.1f};

//制造纹理
image = Image::createImage("assets/textures/test.jpg");
texture = sgl->genTexture();
sgl->bindTexture(texture);
sgl->texImage2D(image->mWidth, image->mHeight, image->mData);
sgl->texParameter(TEXTURE_FILTER, TEXTURE_FILTER_LINEAR);
sgl->texParameter(TEXTURE_WRAP_U, TEXTURE_WRAP_REPEAT);
sgl->texParameter(TEXTURE_WRAP_V, TEXTURE_WRAP_REPEAT);
sgl->bindTexture(0);

float vertices[] = {
// positions // normals // texture coords
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,

-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,

-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,

0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,

-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,

-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
};

//六个面的正方体,每个面由两个三角形构成(同一个顶点需要弄成两个,因为法向量不同)共36个点
uint32_t indices[] = {
0, 1, 2, 3, 4, 5,
6, 7, 8, 9, 10, 11,
12,13,14,15,16,17,
18,19,20,21,22,23,
24,25,26,27,28,29,
30,31,32,33,34,35
};

//生成indices对应ebo
ebo = sgl->genBuffer();
sgl->bindBuffer(ELEMENT_ARRAY_BUFFER, ebo);
sgl->bufferData(ELEMENT_ARRAY_BUFFER, sizeof(uint32_t) * 36, indices);
sgl->bindBuffer(ELEMENT_ARRAY_BUFFER, 0);

//总的交叉式vbo
interleavedVbo = sgl->genBuffer();
sgl->bindBuffer(ARRAY_BUFFER, interleavedVbo);
sgl->bufferData(ARRAY_BUFFER, sizeof(float) * 288, vertices);

//生成vao并且绑定
vao = sgl->genVertexArray();
sgl->bindVertexArray(vao);
sgl->bindBuffer(ARRAY_BUFFER, interleavedVbo);

//position
sgl->vertexAttributePointer(0, 3, 8 * sizeof(float), 0);

//color
sgl->vertexAttributePointer(1, 4, 8 * sizeof(float), 3 * sizeof(float));

//uv
sgl->vertexAttributePointer(2, 2, 8 * sizeof(float), 6 * sizeof(float));

sgl->bindBuffer(ARRAY_BUFFER, 0);
sgl->bindVertexArray(0);
}

int APIENTRY wWinMain(
_In_ HINSTANCE hInstance, //本应用程序实例句柄,唯一指代当前程序
_In_opt_ HINSTANCE hPrevInstance, //本程序前一个实例,一般是null
_In_ LPWSTR lpCmdLine, //应用程序运行参数
_In_ int nCmdShow) //窗口如何显示(最大化、最小化、隐藏),不需理会
{
if (!app->initApplication(hInstance, WIDTH, HEIGHT)) {
return -1;
}

//将bmp指向的内存配置到sgl当中
sgl->initSurface(app->getWidth(), app->getHeight(), app->getCanvas());

prepare();

bool alive = true;
while (alive) {
alive = app->peekMessage();
camera->update();

render();
app->show();
}

delete shader;
Image::destroyImage(image);
sgl->deleteTexture(texture);
delete camera;

return 0;
}