添加镜面反射
在有了阴影之后,我们小球看起非常的不错了。
现在我们让球更加的真实吧。
我们的效果如下
镜面反射
实际上我们一直做的事情光线追踪,只不过是非常简单的。
我们定义的球体是一个可以拥有反射,闪亮程度的物体,所以做到这样子不成问题的。
那么如何去做呢。
实际上我们看图中的反射部分,相当于在球体的其他部分画出来他能看出的球体,反射和阴影而已。正如同我们在一些物体上可以到看到其他的物体,如果仔细看反射的物体又在反射其他,最后相互嵌套。
这个是叫做德罗斯特效应(Droste effect)效应
分析
实现这样的效果,实际上挺简单,就是对这个概念需要一定的理解,因为绘制这样效果的一切工具都已经满足了。
当我们的光线追踪函数发出一个射线并接触到一个点时,我们就会绘制出这个点的颜色,光的效果。
实际上这个我们这个点是有可能是反射之后存在的效果。
也即是说,我们看到的也许是这个反射后的效果,如果我们想要呈现真实的情况,我们应该继续追踪光线,并绘制出每次追踪点的颜色,光的效果。
什么意思呢,就是此时我发出射线看到P,然后我们应该继续发射射线追踪光L,看看是否是其他点反射过来的。
也就是说我们计算完第一次画布视线的射线所接触到的点后, 应该从这个点发射出一个正好是画布射线镜像的射线,如果这个射线接触到点,那么就绘制这个点,然后嵌套继续运行...
很明显,这是一个递归过程。
为了保证不会卡死运行,我们设置一个递归层数。
反射
怎么求呢?实际上我们添加光的镜面反射时已经做好了,我们只需要把这个计算的内容提取出来单独做一个函数就行了啊。
我们第一次从画布的视线射线做镜像,值得注意的时我们对光的所有射线都是反着,这样是被方便计算,毕竟光实际上时存在,只是描述方向变化了,不影响实际效果.
那么我们就实际开始代码吧
代码:
镜像函数
Vector3D reflectiveray(Vector3D R, Vector3D N) { return 2 * N * Vector3D.DotProduct(N, R) - R; }
改光线追踪的函数,可以运行递归,如果反射效果是0或者递归深度小于0就返回好了,对反射的颜色的,我们让他们除去原有反射之后,混合再一起就行了。
Color tracrray(Vector3D origin, Vector3D dline, double min, double max, int deepth = 3) { double closet = double.PositiveInfinity; var clp = closestIntersection(origin, dline, min, max); if (clp == null) { return Colors.Black; } closet = clp.Closet; var claset_sharp = clp.Claset_sharp; var p = origin + (closet * dline); var n = p - claset_sharp.center; n = n / n.Length; var cl = ComputeLighting(p, n, -dline, claset_sharp.specular); //保证颜色上下限正确 var M = Color.FromRgb((byte)(Math.Min(255, Math.Max(0, cl * claset_sharp.color.R))), (byte)(Math.Min(255, Math.Max(0, (cl * claset_sharp.color.G)))), (byte)(Math.Min(255, Math.Max(0, (cl * claset_sharp.color.B))))); if (claset_sharp.reflective <= 0 || deepth <= 0) { return M; } var r = reflectiveray(-dline, n); var color = tracrray(p, r, 0.001, double.PositiveInfinity, deepth - 1); var k = 1 - claset_sharp.reflective; var rgb1 = Math.Min(255, Math.Max(0, M.R * k)); var rgb2 = Math.Min(255, Math.Max(0, M.G * k)); var rgb3 = Math.Min(255, Math.Max(0, M.B * k)); var m2 = Color.Multiply(M, (float)k); var m3 = Color.Multiply(color, (float)claset_sharp.reflective); var m4 = Color.Add(m2, m3); return m4; }
启动时 默认递归10 其实3次以上就看挺好的了
void Start() { WriteableBitmap.Lock(); for (double x = 0 - cw / 2; x < cw / 2; x++) { for (double y = 0 - ch / 2; y < ch / 2; y++) { var D = canvastoviewport(new Point(x, y)); var color = tracrray(new Vector3D(), D, 1, double.MaxValue, 10); var p = MidPoint(new Point(x, y)); byte[] colorData = { color.B, color.G, color.R, color.A }; int stride = (WriteableBitmap.PixelWidth * WriteableBitmap.Format.BitsPerPixel) / 8; WriteableBitmap.WritePixels(new Int32Rect((int)p.X, (int)p.Y, 1, 1), colorData, stride, 0); } } WriteableBitmap.Unlock(); }
最终代码 计算机图形学入门_3D渲染指南: 由C#作为编程语言,WPF作为输出。 计算机图形学入门3D渲染指南 - Gitee.com