在 canvas 中自动生成像素行星

Auto generate pixel planets in canvas

我的目标是用 JavaScript 随机生成一个 "realistic" 行星。我的问题是让它看起来不错,而不仅仅是随机颜色。我当前的脚本采用 2 个随机生成的颜色之间的颜色,并使用 2 个值之间的随机颜色填充球体。 我的目标是拥有看起来接近(呃)这个 的东西 我在 Photoshop 中制作了该图像,是的,我知道我无法以简单的方式接近该结果,但无论如何我都想改进代码以使其看起来更好。而且我不知道如何进行,任何关于如何改进的想法都是有帮助的。

 var colors;
    window.onload = function () {
        generatePlanet();
    }

    function generatePlanet() {
        colors = interpolateColors("rgb(" + getRndColor() + "," + getRndColor() + "," + getRndColor() + ")", "rgb(" + getRndColor() + "," + getRndColor() + "," + getRndColor() + ")", 6000);
        drawPlanet()
    }

    function getRndColor() {
        return Math.floor(Math.random() * 256)
    }

    function interpolateColors(color1, color2, steps) {
        var stepFactor = 1 / (steps - 1),
            interpolatedColorArray = [];

        color1 = color1.match(/\d+/g).map(Number);
        color2 = color2.match(/\d+/g).map(Number);

        for (var i = 0; i < steps; i++) {
            interpolatedColorArray.push(interpolateColor(color1, color2, stepFactor * i));
        }

        return interpolatedColorArray;
    }

    function interpolateColor(color1, color2, factor) {
        if (arguments.length < 3) {
            factor = 0.5;
        }
        var result = color1.slice();
        for (var i = 0; i < 3; i++) {
            result[i] = Math.round(result[i] + factor * (color2[i] - color1[i]));
        }
        return result;
    };

    function drawPlanet() {

        var canvas = document.getElementById("canvas");
        var ctx = canvas.getContext("2d");

        var i = 0, j = 0;
        function animate() {
            ctx.beginPath();
            ctx.fillRect(10 * j, 10 * i, 10, 10);
            ctx.fillStyle = 'rgb(' + colors[Math.floor(Math.random() * 6001)] + ')';
            ctx.fill();
            ctx.closePath();
            j += 1;
            if (j >= 70) {
                i += 1;
                j = 0;
            }
            if (i < 70) {
                animate();
            }
        }
        animate();
    }               
    #canvas {
        border: 10px solid #000000;
        border-radius: 50%;
    }
<button onclick="generatePlanet()">GENERATE</button>
<canvas id="canvas" width="700" height="700"></canvas>

它的任务可能比看起来更复杂。但我可以建议你使用接下来的两步方法来完成

  1. 一些噪声算法(例如Perlin Noise)用于获取行星的主要纹理。使用不同的启动参数,它可以为您带来截然不同的结果。
  2. 为第 2 步纹理添加阴影层,使其更逼真。这可以通过两种更明显的方式实现:使用预定义的阴影纹理或在 one/several 步中使用计算阴影。

如果您对随机生成的行星感兴趣,代码中有注释。 我没有设置任何颜色限制,所以有些颜色搭配看起来有点奇怪。 如果不清楚,请发表评论。

var colors;
    var tileNum = 0;
    var tiles;
    var colorsLand;
    var colorsWater;
    var rndLandColor;
    var rndWaterColor;

    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    window.onload = function () {
        generatePlanet();
    }

    function generatePlanet() {
        //reset
        tileNum = 0;
        tiles = [{ x: 0, y: 0, land: false }];

        //Retrive colors
        colorsLand = interpolateColors("rgb(" + getColor(true) + ")", "rgb(" + getColor(true) + ")", 6000);
        colorsWater = interpolateColors("rgb(" + getColor(false) + ")", "rgb(" + getColor(false) + ")", 6000);

        //Creates a array of my tiles and sets either water or land to them and calculates the % of being water/land
        for (var i = 0; i < 5040; i++) {
            var currentTile = tiles[tiles.length - 1];            
            if (currentTile.x <= 69) {
                var isLand = false;
                if (currentTile.land == true || tiles.length > 70 && tiles[tiles.length - 70].land == true) {                    
                    isLand = (Math.floor(Math.random() * 100) + 1) > 35;
                }
                else if (currentTile.land == true || tiles.length > 70 &&
                    (tiles[tiles.length - 1].land == true ||
                        tiles[tiles.length - 70].land == true)) {
                    isLand = (Math.floor(Math.random() * 100) + 1) > 70;
                }
                else {
                    isLand = (Math.floor(Math.random() * 100) + 1) > 99;
                }
                tiles.push({ x: currentTile.x + 1, y: currentTile.y, land: isLand });
            }
            else {
                tiles.push({ x: 0, y: currentTile.y + 1, land: isLand });
            }
        }
        drawPlanet()
    }

    //retrive a random color if it's a land tile i want it dark water i want light
    function getColor(land) {
        while (true) {
            var r = Math.floor(Math.random() * 256) + 1
            var g = Math.floor(Math.random() * 256) + 1
            var b = Math.floor(Math.random() * 256) + 1
            hsp = Math.sqrt(
                0.299 * (r * r) +
                0.587 * (g * g) +
                0.114 * (b * b)
            );
            //light color
            if (hsp > 127.5 && land == false) {
                return r + "," + g + "," + b;
            }
            //dark color
            else if (hsp < 127.5 && land == true) {

                return r + "," + g + "," + b;
            }
        }
    }

    //these 2 functions interpolateColor(s) takes 2 colors and gives me 'steps' colors between
    function interpolateColors(color1, color2, steps) {
        var stepFactor = 1 / (steps - 1),
            interpolatedColorArray = [];
        color1 = color1.match(/\d+/g).map(Number);
        color2 = color2.match(/\d+/g).map(Number);

        for (var i = 0; i < steps; i++) {
            interpolatedColorArray.push(interpolateColor(color1, color2, stepFactor * i));
        }
        return interpolatedColorArray;
    }

    function interpolateColor(color1, color2, factor) {
        if (arguments.length < 3) {
            factor = 0.5;
        }
        var result = color1.slice();
        for (var i = 0; i < 3; i++) {
            result[i] = Math.round(result[i] + factor * (color2[i] - color1[i]));
        }
        return result;
    };

    //retrives a random color for land
    function rndLandColor() {
        return 'rgb(' + colorsLand[Math.floor(Math.random() * 5999) + 1] + ')';
    }
    //retrives a random color for water
    function rndWaterColor() {
        return 'rgb(' + colorsWater[Math.floor(Math.random() * 5999) + 1] + ')';
    }

    function drawPlanet() {
        var i = 0, j = 0;
        function animate() {
            ctx.beginPath();

            //fill in holes in the land that is bigger then 1
            var score = 0;
            if (tiles[tileNum - 71] !== undefined && tiles[tileNum + 71] !== undefined) {
                if (tiles[tileNum].land == false) {
                    score++;
                }
                if (tiles[tileNum - 1].land == true) {
                    score++;
                }
                if (tiles[tileNum + 1].land == true) {
                    score++;
                }
                if (tiles[tileNum + 71].land == true) {
                    score++;
                }
                if (tiles[tileNum - 71].land == true) {
                    score++;
                }
            }

            if (score >= 3) {
                ctx.fillStyle = rndLandColor;
            }

            //cover single land tiles with water (if land tile is up,down,left and right of this tile)
            else if (
                tiles[tileNum - 71] !== undefined &&
                tiles[tileNum + 71] !== undefined &&
                tiles[tileNum - 1].land == false &&
                tiles[tileNum + 1].land == false &&
                tiles[tileNum - 71].land == false &&
                tiles[tileNum + 71].land == false) {
                ctx.fillStyle = rndWaterColor();
            }

            //cover single water tiles with land (if water tile is up,down,left and right of this tile)
            else if (
                tiles[tileNum - 71] !== undefined &&
                tiles[tileNum + 71] !== undefined &&
                tiles[tileNum - 1].land == true &&
                tiles[tileNum + 1].land == true &&
                tiles[tileNum - 71].land == true &&
                tiles[tileNum + 71].land == true) {
                ctx.fillStyle = rndLandColor();
            }
            //cover tile with land
            else if (tiles[tileNum] !== undefined && tiles[tileNum].land == true) {
                ctx.fillStyle = rndLandColor();
            }

            //cover tile with water
            else if (tiles[tileNum] !== undefined && tiles[tileNum].land == false) {
                ctx.fillStyle = rndWaterColor();
            }
            tileNum++;

            ctx.fill();
            ctx.closePath();
            ctx.fillRect(10 * j, 10 * i, 10, 10);

            j++;
            if (j >= 71) {
                i++;
                j = 0;
            }
            if (i <= 71) {
                animate();
            }
        }
        animate();
    }
#canvas {
        border: 10px solid #000000;
        border-radius: 50%;
        background-color: aquamarine;
    }

    .container {
        width: 720px;
        height: 720px;
        position: relative;
    }

    .gradient {
        position: absolute;
        height: 730px;
        width: 730px;
        top: 0;
        left: 0;
        border-radius: 50%;
        opacity: 0.8;
    }
<button onclick="generatePlanet()">GENERATE</button>
<div class="container">
    <img class="gradient" src="https://www.mediafire.com/convkey/1f5a/cgu50lw1ehcp4fq6g.jpg" />
    <canvas id="canvas" width="710" height="710"></canvas>
</div>