Reference
Lesson 6: Shaders for the software renderer · ssloy/tinyrenderer Wiki (github.com)
https://www.bilibili.com/video/BV1X7411F744/
从零开始的TinyRenderer(6)——着色(光照模型) - 知乎 (zhihu.com)
从零开始的TinyRenderer(5)——移动相机与代码重构 - 知乎 (zhihu.com)
C++矩阵库Eigen的用法 - 知乎 (zhihu.com)
简要概述
这三课放在一起写比较好
第四第五课没啥实际要写的内容
主要是理解MVP变换,我的评价是最好去看Games101,讲的比这个详细
那么为什么用Eigen,因为他的源代码的矩阵库在我这里报错不知道什么原因
最后写好之后,重构代码,使用Shader的vertext返回一个屏幕坐标,使用segment计算一个颜色
具体MVP推导我的博客里面呢,也有可以直接搜到。
下面有几个问题可以看一下,是我犯下的一些错误:
1.flaot的值 导致撕裂 要四舍五入
2.egine的vector和matrix值不能输多了 报错
3.egine的值不能默认隐式转换 报错
4.灯光的反向和法线反向一定要normalize 不然全是黑色
5.视口变换 矩阵不要打错了
6.定义好右手坐标系
7.检测viewcamera矩阵不要写错了,特别注意-forword方向
8.检查barycentric不要把返回的 $\alpha$ $\beta$ $\gamma$ 顺序搞错了
更加详细的内容,待会再写,现在太晚了
仿射变换
之前的学习中我们从未对模型的坐标做过转换,仅仅是将模型的顶点的xy轴映射到屏幕上,所以我们的摄像机是固定的在z轴负反向。现在我们需要移动这些顶点的位置了,就要对他们做变换。
对应Games101的课程:Lecture 03 Transformation_哔哩哔哩_bilibili
这里我就不讲了,我代码提交中的第三课做了一下几个变换。
大家可以尝试自己做一下。
如果你发现老师的矩阵.cpp+h
文件在你哪里无法使用,请下载eigen,并且学习:C++矩阵库Eigen的用法 - 知乎 (zhihu.com)
ViewPort矩阵-本地坐标->屏幕坐标
Games101对应点:
https://www.bilibili.com/video/BV1X7411F744?t=1265.0&p=5
其实没什么好推导的,一个缩放矩阵 x 一个位移矩阵
就可以了
Projection矩阵-透视
Games101对应点:
https://www.bilibili.com/video/BV1X7411F744?t=2693.9&p=4
不同的是,老师这里的代码只用到了距离相机的z轴距离,而没有近平面和远平面,需要推导可以看我的博客:
MVPP变换推导之透视变换Perspective | AncientElement (gitee.io)
这里我就不多bibi了
ViewCamera矩阵-移动你的摄像机
Games101对应点:
https://www.bilibili.com/video/BV1X7411F744?t=1461.7&p=4
我的推导:
MVPV变换之视图变换ViveCamera | AncientElement (gitee.io)
要注意,相机的forword轴要对齐的是-z轴
代码重构
建立gl.h+cpp
编写trainagle代码,和保存viewcamera、projection、viewport矩阵的转换代码,同时.h
文件中还要有Shader
这个接口类,写法请参照源代码。mian.c
中只编写加载模型、定义光源位置、相机位置代码。
如果你和我一样用了Eigen,你如果不知道如何更改,请拷贝我的源文件中的tagimage.h+cpp、model.h+cpp
文件。
好,可以开始了。
首先我们要明确,在绘制模型的时候,我们是一个面一个面的来绘制的,一个面对应三个顶点,三个顶点对应他包围盒中x*y
个片元(当然是三角形剔除以后的)。
在顶点阶段,我们必须要返回一个每个顶点的屏幕坐标(也就是经过MVP变换),这样triangle的时候才知道你在屏幕上的包围盒在
哪里,你的深度,并且存储一些顶点的信息,供片元阶段使用,比如,world_pos、uv等等。
1 | //iface: 第几个面 |
1 | //遍历三角形 |
我们每三个顶点的信息可以供给这三个顶点中所有的片元使用,那么我们每一个片元都只能使用相同的三个信息吗。
不,我们有重心坐标。
检查barycentric不要把返回的 $\alpha$ $\beta$ $\gamma$ 顺序搞错了
使用重心坐标对三个顶点的信息插值,可以插值顶点UV、顶点法线与光的点乘(兰伯特)等等。
1 | //barycentric: 重心坐标 |
在triangle里面我们这样调用他:
1 | //深度测试 |
OK,下面就可以写Shader类了。
Shader类
一个简单的漫反射Shader
1 | //着色器 |
一个简单的漫反射Shader+纹理
1 | class SimpleShader : public Shader |