• 码农干货系列【9】javascript光线追踪基础


    简介

    光线追踪(ray tracing)(也叫raytracing或者光束投射法)是一个在二维(2D)屏幕上呈现三维(3D)图像的方法。为了尝试光线追踪算法,并且尽可能得保证javascript代码精炼,我做了一些尝试。

    射线与球体相交检测

    最开始尝试了射线与球体的相交检测(不计算交点),只判断相交还是未相交。代码如下所示:

    var Vector3 = function (x, y, z) { this.x = x; this.y = y; this.z = z; };
    Vector3.prototype ={
        dot: function (v) { return this.x * v.x + this.y * v.y + this.z * v.z; },
        sub: function (v) { return new Vector3(this.x - v.x, this.y - v.y, this.z - v.z); },
        normalize: function () {      return this.divideScalar(this.length());  },
        divideScalar: function (s) {  return new Vector3(this.x/s, this.y/s, this.z/s);   },
        length: function () {     return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);  },
        sqrDistanceToline:function(a,b){
            var ab = b.sub(a), ac = this.sub(a), bc = this.sub(b);
            var e = ac.dot(ab.normalize());
            var f = ac.length();
            return f * f - e * e;
        }
    } 

    其中sqrDistanceToline为计算点到直线之间的距离的平方(开跟号性能损耗大)。使用如下:

    ball.p.sqrDistanceToline(v, camera.p) < sqrBallR

    其中v是屏幕上的点的坐标,camera.p为视点的坐标,ball.p为球体中心坐标,sqrBallR为球体半径的平方。当上面返回true时,判定为相交;反之亦然。

    相交测试

    for (var y = 0; y < canvas.height; y++) {
        for (var x = 0; x < canvas.width; x++) {
            var v = new Vector3(-canvas.width / 2 + x, canvas.height - y, 0);
            var cv = new Vector3(camera.p.y * v.x / (camera.p.y - v.y), 0, camera.p.z * v.y / (v.y - camera.p.y));
               
            if (cv.z > -planeLength && cv.z < 0) {
                if (ball.p.sqrDistanceToline(v, camera.p) < sqrBallR) {
                    pixels[i] = pixels[i + 1] = pixels[i + 2] = 111;
                } else {
                    pixels[i] = pixels[i + 1] = pixels[i + 2] = (Math.ceil(cv.x / sideLength) + Math.ceil(cv.z / sideLength)) % 2 === 0 ? 148 : 0;
                }
                pixels[i + 3] = 255 * (planeLength - Math.abs(cv.z)) / planeLength;
            }
            i += 4;
        }
    }

    由于没有获取交点坐标,无法计算视点到球体上点的距离,所以无法进行球体深度渲染。所以得到了以下的图像:

    image

    获取交点

    所以现在目的很明确,不仅要判定相交不相交,还需要找到交点的坐标。当然,上面的方法不是一无是处,可以进行一些初步坐标的筛选(如果性能好于找交点计算一个数量级的话,这个待测试)。那么怎么获取射线与球体的交点呢?该点要满足以下两个条件:

    1.交点在在光线上
    x=S+dt

    2.交点在球上
    |x-C|=r

    C 表示球心,r 表示半径,光线起点是 S,方向是 d(单位向量),交点 x。

    所以得到:8140727a73c4b1e91b3ec987e4d780e0

    简化 dfd1908c7d8603bab746c9e5a487fb06

    那么a27ce21daa6b36c14bc25f8646d44e79

    所以a44286afce8014729e85436a846749fd

    最后得到:039de90713792a92c20398f4a059962a

    所有小球的代码如下所示:

    var Ball = function (p, r) {
        this.p = p;
        this.r = r;
        this.sqrR = this.r * this.r;
    }
    Ball.prototype = {
        intersect: function (p1, p2) {
            var v = p1.sub(this.p);
            var a0 = v.sqrLength() - this.sqrR;
            var np = p2.sub(p1).normalize();
            var dotV = np.dot(v);
            if (dotV <= 0) {
                var discr = dotV * dotV - a0;
                if (discr >= 0) {
                    return p1.add(np.multiplyScalar(-dotV - Math.sqrt(discr)));
                }
            }
            return null;
        }
    }

    拿到了交点坐标,现在可以做深度渲染:

    var result = ball.intersect(camera.p, screenP);
    if (result) {
        (pixels[i] = pixels[i + 1] = pixels[i + 2] = ((result.z - ball.p.z) / ball.r) * 255)
        pixels[i + 3] = 255;
    }

    在线演示

    请使用现代浏览器,你的浏览器过时了!

    修改里面的参数试一试!

    参考文献

    用JavaScript玩转计算机图形学(一)光线追踪入门

    光线跟踪 - 维基百科,自由的百科全书

    Have Fun!

  • 相关阅读:
    可扩展性的四个维度
    系统的可伸缩性
    Spring.factories扩展机制
    Java扩展方法之SPI
    2019第16周日
    影响圈和关注圈
    看张溪梦讲座的一点想法:制造数据反馈
    何为重构
    贾扬清:我对人工智能方向的一点浅见
    python类和实例以及__call__/__del__
  • 原文地址:https://www.cnblogs.com/iamzhanglei/p/2955944.html
Copyright © 2020-2023  润新知