정보
요청한 주제가 아래에 표시됩니다. 그러나 이 주제는 이 라이브러리에 포함되지 않습니다.

캔버스를 사용한 Mandelbrot 집합의 초기 렌더링

Mandelbrot 집합에 대한 간략한 개요에서 제공한 정보를 기반으로 여기서는 캔버스를 사용하여 Mandelbrot 집합을 렌더링하는 기본 방법에 대해 설명합니다.

Mandelbrot 집합을 그리는 알고리즘을 제공하기 전에 먼저 복소수를 조작하는 JavaScript "클래스"를 만듭니다. Mandelbrot 집합에 대한 간략한 개요에 표시된 대로 복소수를 제곱하고 더한 다음 절대값을 구하기만 하면 됩니다. 유용한 복소수 연산 예제에서는 이 작업을 수행하는 한 가지 방법을 보여 줍니다.

유용한 복소수 연산


<!DOCTYPE html>
<html>

<head>
  <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
  <title>Useful Complex Operations</title>
  <style>
    body {
      text-align: center;
    }
  </style>
</head>

<body>
  <h1>Useful Complex Operations</h1>
  <script>
    function Complex(x, y) {
    // Constructs the complex number x + yi.
      this.x = x || 0; // Default to 0 if this parameter is undefined.
      this.y = y || 0;
    } // Complex
    
    Complex.prototype.toString = function() {
    // Returns a string representing this complex number in the form "x + yi".
      return this.y >= 0 ? this.x + " + " + this.y + "i" : this.x + " - " + (-this.y) + "i";
    } // toString
    
    Complex.prototype.modulus = function() {
    // Returns a real number equal to the absolute value of this complex number.
      return Math.sqrt(this.x*this.x + this.y*this.y);
    } // modulus
        
    Complex.prototype.add = function(z) {
    // Returns a complex number equal to the sum of the given complex number and this complex number.
      return new Complex(this.x + z.x, this.y + z.y);
    } // sum

    Complex.prototype.square = function() {
    // Returns a complex number equal to the square of this complex number.
      var x = this.x*this.x - this.y*this.y;
      var y = 2*this.x*this.y;
      
      return new Complex(x, y);
    } // square
    
    var z1 = new Complex(0.2, 0.4);
    var z2 = new Complex(-0.6, 0.5);
    var z3 = z1.add(z2.square()); // Square z2 and add z1 to it.
    
    alert(z3.toString()); // Print the complex result in x + yi form.
    alert(z3.modulus()); // Print absolute value of z3.
  </script>
</body>

</html>

유용한 복소수 연산 예제에서는 다음과 같은 식을 사용합니다.

  • z1 = 0.2 + 0.4i
  • z2 = -0.6 + 0.5i
  • z3 = (z2)2 + z1 = (-0.6 + 0.5i)2 + (0.2 + 0.4i) = 0.31 - 0.2i
  • |z3| = z3.modulus() ≈ 0.3689

복잡한 JavaScript "클래스"를 기반으로 Mandelbrot 집합을 그리는 간단한 알고리즘을 제공합니다.

  1. 복소 평면 원점의 중심에 있는 4 x 4 제곱을 많은 그리드 점(표시되지 않음)으로 나눕니다.

    4 x 4 복소 평면

  2. 각 그리드 점을 c 값으로 처리합니다.
  3. 이러한 각 c 값에 대해 |zn|이 적절한 반복 횟수 내에서 이스케이프되는지 확인합니다(zn+1 = zn + c, 여기서 z0 = 0).
  4. 그렇지 않을 경우 c 색을 검정색으로 지정합니다(검정색은 c가 Mandelbrot 집합의 구성원임을 나타냄). 여기서는 흰색 배경에 Mandelbrot 집합을 그리고 있다고 가정합니다.

이 알고리즘을 JavaScript로 구현하면 다음과 같은 이미지가 생성됩니다.

Mandelbrot 1 스크린샷

이 이미지에서 빨간색 좌표 축 두 개의 길이는 4입니다. 즉, 복소 평면 원점의 중심에 있는 반지름이 2인 원에 이러한 축이 정확히 포함됩니다. 앞에서 설명한 대로 Mandelbrot 집합은 반지름이 2인 원 내에 완전히 포함되며 이 사실은 다음과 같이 이 이미지를 만드는 데 사용된 코드에서 활용됩니다.

Mandelbrot 1


<!DOCTYPE html>
<html>

<head>
  <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
  <title>Mandelbrot 1</title>
  <style>
    body {
      text-align: center;
    }
    
    canvas {
      border: 1px black solid;
    }
  </style>
</head>

<body>
  <h1>Mandelbrot 1</h1>
  <p>This example demonstrates a basic algorithm for drawing the Mandelbrot set using complex plane coordinates.</p>    
  <canvas width="600" height="600">Canvas not supported - upgrade your browser</canvas>
  <script>
    var CPS = 2; // CPS stands for "complex plane square". That is, we are examining a 2*CPS by 2*CPS square region of the complex plane such that this square (or box) is centered at the complex plane's origin.
    var MAX_ITERATIONS = 300; // Increase to improve detection of complex c values that belong to the Mandelbrot set.
    var DELTA = 0.008; // Decreasing this value increases the number of "pixels" on the canvas, thereby increasing the size of the rendering but without losing image resolution.
    
    function Complex(x, y) {
    // Constructs the complex number x + yi. If any parameter is undefined, 0 is used instead.
      this.x = x || 0;
      this.y = y || 0;
    } // Complex
    
    Complex.prototype.toString = function() {
    // Returns a string representing this complex number in the form "x + yi".
      return this.y >= 0 ? this.x + " + " + this.y + "i" : this.x + " - " + (-this.y) + "i";
    } // toString
    
    Complex.prototype.modulus = function() {
    // Returns a real number equal to the absolute value of this complex number.
      return Math.sqrt(this.x*this.x + this.y*this.y);
    } // modulus
        
    Complex.prototype.add = function(z) {
    // Returns a complex number equal to the sum of the given complex number and this complex number.
      return new Complex(this.x + z.x, this.y + z.y);
    } // sum

    Complex.prototype.square = function() {
    // Returns a complex number equal to the square of this complex number.
      var x = this.x*this.x - this.y*this.y;
      var y = 2*this.x*this.y;
      
      return new Complex(x, y);
    } // square
    
    var globals = {}; // Store all would-be-global-variables in one handy global object.
    globals.canvas = document.getElementsByTagName('canvas')[0];
    globals.canvas.ctx = globals.canvas.getContext('2d');
    globals.canvas.ctx.fillStyle = "black";                   
    
    initializeCoordinateSystem();
    drawMandelbrotSet();
    drawCoordinateAxes();
    
    function initializeCoordinateSystem() {
      var ctx = globals.canvas.ctx;

      ctx.translate(globals.canvas.width / 2, globals.canvas.height / 2); // Move the canvas's coordinate system to the center of the canvas.
      ctx.scale(1/DELTA, -1/DELTA); // Flip the y-axis to produce a standard Cartesian coordinate system and scale the canvas coordinate system to match the region of the complex plane under consideration.
    } // initializeCoordinateSystem  
    
    function drawMandelbrotSet() {
      var ctx = globals.canvas.ctx;
                    
      for (var Re = -CPS; Re <= CPS; Re = Re + DELTA) { // Represents the Re-axis. Re represents the real part of a complex c value.
        next_c_value: // "continue next_c_value;" is equivalent to an old school GOTO statement (which can be very handy in deeply nested loops).
        for (var Im = -CPS; Im <= CPS; Im = Im + DELTA) { // Represents the Im-axis. Im represents the imaginary part of a complex c value.
          var z = new Complex(0, 0); // Represents Zo (where "o" indicates subscript 0).
          var c = new Complex(Re, Im); // Represents a complex c value, which either does or does not belong to the Mandelbrot set, as determined in the next FOR loop.
          
          for (var iterationCount = 1; iterationCount <= MAX_ITERATIONS; iterationCount++) {
            z = c.add( z.square() ); // Performs Zn+1 = (Zn)^2 + c          
            if (z.modulus() > 2) {
              continue next_c_value; // The complex c value is not part of the Mandelbrot set, so immediately check the next one.
            } // if
          } // for
          
          // Assert: z.modulus() <= 2, therefore the complex c value is probably a member of the Mandelbrot set - increase MAX_ITERATIONS to improve this determination.

          ctx.fillRect(Re, Im, DELTA, DELTA); // This c value is probably part of the Mandelbrot set, so color this pixel black. A "pixel" for the canvas is a DELTA x DELTA black square.
        } // for
      } // for
    } // drawMandelbrotSet
    
    function drawCoordinateAxes() {
    /* 
      Draws coordinate axes that are exactly as long as the (square) complex plane region under consideration.
    */
      var ctx = globals.canvas.ctx;
      
      ctx.lineWidth = DELTA;
      ctx.strokeStyle = "red";
      
      // Draw the x-axis:
      ctx.beginPath();
      ctx.moveTo(CPS, 0);
      ctx.lineTo(-CPS, 0);
      ctx.stroke();
      
      // Draw the y-axis:
      ctx.beginPath();
      ctx.moveTo(0, CPS);
      ctx.lineTo(0, -CPS);
      ctx.stroke();      
    } // drawCoordinateAxes
  </script>
</body>

</html>

검사할 주요 함수는 initializeCoordinateSystemdrawMandelbrotSet입니다.

initializeCoordinateSystem

이름을 통해 알 수 있듯이 initializeCoordinateSystem은 600 x 600 픽셀 캔버스에서 사용되는 좌표계를 초기화합니다. 기본적으로 점 (0, 0)은 항상 캔버스의 왼쪽 위 모서리를 나타냅니다. 익숙한 데카르트 좌표계를 사용하려면 다음과 같이 이 원점을 캔버스의 중심으로 translate해야 합니다.


ctx.translate(globals.canvas.width / 2, globals.canvas.height / 2);

이제 좌표계의 원점은 중심에 있지만 양의 y축이 위쪽(증가)을 가리켜야 하는 데 아래쪽(증가)를 가리키고 있습니다. 이 문제를 해결하려면 다음과 같이 scale 메서드를 사용할 수 있습니다.


ctx.scale(1, -1);

이 메서드는 y축을 대칭 이동(x축은 변경되지 않은 상태로 유지)하는 효과가 있어 기존의 데카르트 좌표계를 생성합니다.

drawMandelbrotSet

또한 scale 메서드를 사용하여 렌더링된 이미지의 크기를 늘리거나 줄일 수 있습니다. 배율 인수가 커질수록 렌더링된 이미지도 더 커집니다. 다음과 같이 예제 코드에서 이 메서드를 사용합니다.


ctx.scale(1/DELTA, -1/DELTA);

DELTA가 작고(즉, 0.008) 캔버스의 픽셀의 크기도 나타낼 경우 일반적으로 작은 이미지의 크기는 ctx.scale(1/DELTA, -1/DELTA)ctx.scale(125, -125)가 되도록 현저하게 증가합니다. 이는 drawMandelbrotSet를 검사하면 명확하게 알 수 있습니다.

drawMandelbrotSet


function drawMandelbrotSet() {
  var ctx = globals.canvas.ctx;
        
  for (var Re = -CPS; Re <= CPS; Re = Re + DELTA) { // Represents the Re-axis. Re represents the real part of a complex c value.
    next_c_value: // "continue next_c_value;" is equivalent to an old school GOTO statement (which can be very handy in deeply nested loops).
    for (var Im = -CPS; Im <= CPS; Im = Im + DELTA) { // Represents the Im-axis. Im represents the imaginary part of a complex c value.
      var z = new Complex(0, 0); // Represents Zo (where "o" indicates subscript 0).
      var c = new Complex(Re, Im); // Represents a complex c value, which either does or does not belong to the Mandelbrot set, as determined in the next FOR loop.
      
      for (var iterationCount = 1; iterationCount <= MAX_ITERATIONS; iterationCount++) {
        z = c.add( z.square() ); // Performs Zn+1 = (Zn)^2 + c          
        if (z.modulus() > 2) {
          continue next_c_value; // The complex c value is not part of the Mandelbrot set, so immediately check the next one.
        } // if
      } // for
      
      // Assert: z.modulus() <= 2, therefore the complex c value is probably a member of the Mandelbrot set - increase MAX_ITERATIONS to improve this determination.

      ctx.fillRect(Re, Im, DELTA, DELTA); // This c value is probably part of the Mandelbrot set, so color this pixel black. A "pixel" for the canvas is a DELTA x DELTA black square.
    } // for
  } // for
} // drawMandelbrotSet

CPS가 2인 다음 사각형(그리드 점은 표시되지 않음)에서 각 그리드 점을 검사합니다.

4 x 4 복소 평면

이 사각형 내에 있는 그리드 점의 수는 DELTA에 의해 결정됩니다. DELTA가 감소하면 그리드 점의 수가 증가합니다.

for (var Re = -CPS; Re <= CPS; Re = Re + DELTA) {
  …
  for (var Im = -CPS; Im <= CPS; Im = Im + DELTA) { 
    …
  }
}

다음으로 필요에 따라 z0을 0 + 0i = 0으로 설정하고 현재 검사하고 있는 그리드 점 (Re, Im)을 기준으로 c를 만듭니다.


var z = new Complex(0, 0);
var c = new Complex(Re, Im);

이제 적절한 반복 횟수 내에서 |zn|을 계산하고 |zn|이 2보다 큰지 확인하여 c가 Mandelbrot 집합에 있는지(있을 가능성이 큰지) 확인할 수 있습니다.


for (var iterationCount = 1; iterationCount <= MAX_ITERATIONS; iterationCount++) {
  z = c.add( z.square() ); // Performs Zn+1 = (Zn)^2 + c          
  if (z.modulus() > 2) {
    continue next_c_value; // The complex c value is not part of the Mandelbrot set, so immediately check the next one.
  } // if
} // for

|zn| > 2인 이유는 무엇일까요? 기본 속성에서 설명한 대로 zn의 절대값이 2보다 크면 순서가 항상 무한(c가 Mandelbrot 집합의 일부가 아님을 나타냄)으로 이스케이프되기 때문입니다.

MAX_ITERATIONSzn의 절대값이 2(z.modulus() <= 2)보다 작거나 같으면 zn와 관련된 c 값이 Mandelbrot 집합의 구성원일 가능성이 커집니다. 따라서 c의 그리드 점을 검정색으로 지정합니다.


ctx.fillRect(cx, cy, DELTA, DELTA);

마지막으로 drawCoordinateAxes를 호출하여 빨간색 좌표 축 두 개를 그립니다.

Mandelbrot 1의 가장 심각한 문제점 중 하나는 Mandelbrot 집합의 보다 세부적인 정보를 검사하기 위해 확대할 수 없다는 점입니다. 이러한 기능을 쉽게 수행하려면 먼저 캔버스 화면 점(픽셀)을 복소 평면에 매핑하는 방법을 확인해야 하며, 이 방법은 화면 좌표를 복소 평면에 매핑에서 설명합니다.

관련 항목

화면 좌표를 복소 평면에 매핑

 

 

표시:
© 2014 Microsoft