ps做网站效果图制作过程/四川seo排名
回顾
在上一节中,我们已经完成了:
- 架好了相机(Camera),摆放好场景(Scene);
- 射出射线、计算交点(IntersectResult);
- 实现了渲染器(Render),即将交点信息返回到画布(Canvas)上
接下来,我们将完成:
- 手写材质(Material),其中,材质最重要的方法是取样(sample),即获得颜色
- 手写光线追踪过程,这是一个递归
- 渲染出成品
颜色(Color)
计算出交点处的颜色(color),返回并绘制到画布上,是我们的基本思路。
Color = function (r, g, b) {this.r = r;this.g = g;this.b = b;
};Color.prototype = {copy: function () {return new Color(this.r, this.g, this.b);},add: function (c) {return new Color(this.r + c.r, this.g + c.g, this.b + c.b);},mul: function (s) {return new Color(this.r * s, this.g * s, this.b * s);},mod: function (c) {return new Color(this.r * c.r, this.g * c.g, this.b * c.b);}};Color.black = new Color(0, 0, 0);
Color.white = new Color(1, 1, 1);
Color.red = new Color(1, 0, 0);
Color.green = new Color(0, 1, 0);
Color.blue = new Color(0, 0, 1);
Phong材质(PhongMaterial)
经典的Phong材质,是 漫反射+镜反射 的共同作用(这两个都是用公式算的,不要和递归时用的反射弄混了)。
其取样函数(sample⭐️)需要的参数有:入射光线ray、交点位置position、交点法线normal;光源方向lightDir、光源颜色lightColor —— 求出该交点处的颜色(color),正是取样函数的目的。
至于取样计算使用的数学公式和数学参数,不必关心。
PhongMaterial = function (diffuse, specular, shininess, reflectiveness) {this.diffuse = diffuse; // 漫反射this.specular = specular; // 镜反射this.shininess = shininess; this.reflectiveness = reflectiveness;
};// 光源为平行光(方向+颜色,取样计算颜色时要用到)
var lightDir = new Vector3(1, 1, 1).normalize();
var lightColor = Color.white;PhongMaterial.prototype = {sample: function (ray, position, normal) {var NdotL = normal.dot(lightDir);var H = (lightDir.sub(ray.direction)).normalize();var NdotH = normal.dot(H);var diffuseTerm = this.diffuse.mul(Math.max(NdotL, 0));var specularTerm = this.specular.mul(Math.pow(Math.max(NdotH, 0), this.shininess));return lightColor.mod(diffuseTerm.add(specularTerm));}};
棋盘材质(CheckerMaterial)
取样函数(sample) 比较简单,只有黑色和白色。
CheckerMaterial = function (scale, reflectiveness) {this.scale = scale;this.reflectiveness = reflectiveness;
};CheckerMaterial.prototype = {sample: function (ray, position, normal) {return Math.abs((Math.floor(position.x * 0.1) + Math.floor(position.z * this.scale)) % 2) < 1 ? Color.black : Color.white;}};
光线追踪(RayTrace)
代码思路:
function rayTraceRecursion(场景scene, 射线ray, 最大递归深度maxReflect):计算交点 = scene.intersect(ray);计算交点上的颜色 color;计算反射光线 new_ray交点上的颜色 = color + rayTraceRecursion(scene, new_ray, maxReflect - 1);
代码:
function rayTraceRecursive(scene, ray, maxReflect) {var result = scene.intersect(ray);if (result.geometry) {var reflectiveness = result.geometry.material.reflectiveness;var color = result.geometry.material.sample(ray, result.position, result.normal);color = color.mul(1 - reflectiveness);if (reflectiveness > 0 && maxReflect > 0) {var r = result.normal.mul(-2 * result.normal.dot(ray.direction)).add(ray.direction);ray = new Ray3(result.position, r);var reflectedColor = rayTraceRecursive(scene, ray, maxReflect - 1);color = color.add(reflectedColor.mul(reflectiveness));}return color;} else {return Color.black;}
}
渲染器(Render)
和上一节中渲染器的写法完全相同,不再赘述。
只是,上一节的渲染器绘制的是返回来的深度/法向量等信息,这次绘制的是真正的颜色(color)。
function rayTraceReflection(canvas, scene, camera, maxReflect) {var canvas = document.getElementById("canvas"); // 获取画布var context = canvas.getContext("2d"); // 获取画布内容var w = canvas.attributes.width.value;var h = canvas.attributes.height.value;context.fillStyle = "rgb(0, 0, 0)";context.fillRect(0, 0, w, h);var imageData = context.getImageData(0, 0, w, h);var pixels = imageData.data;scene.init();camera.init();var i = 0;for (var y = 0; y < h; y++) {var sy = 1 - y / h;for (var x = 0; x < w; x++) {var sx = x / w;var ray = camera.generateRay(sx, sy);var color = rayTraceRecursive(scene, ray, maxReflect);pixels[i++] = color.r * 255;pixels[i++] = color.g * 255;pixels[i++] = color.b * 255;pixels[i++] = 255;}}context.putImageData(imageData, 0, 0);}
最终效果展示
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>光线追踪(RayTracing)</title><script src="Vector3.js"></script><script src="Ray3.js"></script><script src="PerspectiveCamera.js"></script><script src="IntersectResult.js"></script><script src="Sphere.js"></script><script src="Plane.js"></script><script src="Scene.js"></script><script src="Render.js"></script><script src="Color.js"></script><script src="PhongMaterial.js"></script><script src="CheckerMaterial.js"></script><script src="RayTrace.js"></script></head>®<body><canvas width="256" height="256" id="canvas"></canvas><script>var plane = new Plane(new Vector3(0, 1, 0), 0);var sphere1 = new Sphere(new Vector3(-10, 10, -10), 10);var sphere2 = new Sphere(new Vector3(10, 10, -10), 10);plane.material = new CheckerMaterial(0.1, 0.5);sphere1.material = new PhongMaterial(Color.red, Color.white, 16, 0.25);sphere2.material = new PhongMaterial(Color.blue, Color.white, 16, 0.25);rayTraceReflection(document.getElementById("canvas"),new Scene([plane, sphere1, sphere2]),new PerspectiveCamera(new Vector3(0, 5, 15), new Vector3(0, 0, -1), new Vector3(0, 1, 0), 90),3);</script>
</body></html>
>>> 这个演示有两个缺憾:
>>> 一是,光追是反射和折射的混合(要用到菲涅耳方程),但我们只递归了反射,并未递归折射
>>> 二是,光追的基本思路是“射出主射线primaryRay——命中后射出阴影线shadowRay”,我们没有考虑阴影线被遮挡的情况
上一篇:《【计算机图形学】从0实现光线追踪 · 前篇》