Dart と WebGL でマンデルブロ集合を描画する
概要
GLSL とフラクタルの勉強をしていて、 試しに(Wikipedia) マンデルブロ集合 を Dart と WebGL で描いてみた。
マウスでちょっとだけグリグリできるようにしてみた。
デモは↓から閲覧できる。
サンプルコード
外部のライブラリなどは使っていないので、 以下のコードをコピペするだけで動く。
mandelbrot.html
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Webgl</title>
<script async type="application/dart" src="mandelbrot.dart"></script>
<script async src="packages/browser/dart.js"></script>
</head>
<body>
<canvas id="main"></canvas>
</body>
</html>
mandelbrot.dart
library mandelbrot;
import 'dart:html';
import 'dart:web_gl' as gl;
import 'dart:typed_data' as typed;
String GET(String url) {
return (new HttpRequest()
..open("GET", url, async: false)
..send()).responseText;
}
typed.Float32List s2fs(String s) =>
new typed.Float32List.fromList(
s.split(",").map(double.parse).toList());
typed.Uint16List s2is(String s) =>
new typed.Uint16List.fromList(
s.split(",").map(int.parse).toList());
gl.Shader makeShader(gl.RenderingContext ctx, String src, int type) {
gl.Shader shader = ctx.createShader(type);
ctx.shaderSource(shader, src);
ctx.compileShader(shader);
if (ctx.getShaderParameter(shader, gl.COMPILE_STATUS)) {
return shader;
} else {
var message = ctx.getShaderInfoLog(shader);
throw new Exception("Failed to compile vertex shader: $message");
}
}
gl.Program makeProgram(gl.RenderingContext ctx, List<gl.Shader> shaders) {
gl.Program program = ctx.createProgram();
shaders.forEach((shader) => ctx.attachShader(program, shader));
ctx.linkProgram(program);
if (ctx.getProgramParameter(program, gl.LINK_STATUS)) {
ctx.useProgram(program);
return program;
} else {
var message = ctx.getProgramInfoLog(program);
throw new Exception("Failed to link program $message");
}
}
gl.Program mandelbrotProgram(gl.RenderingContext ctx) {
const VERTEX_SHADER_SOURCE = """
attribute vec3 position;
void main(void){
gl_Position = vec4(position, 1.0);
}
""";
const FRAGMENT_SHADER_SOURCE = """
precision highp float;
uniform vec2 point;
uniform vec2 resolution;
const int ITERATION = 200;
const vec3 white = vec3(1., 1., 1.);
float min(vec2 e){ return min(e.x, e.y); }
void main(void){
vec2 p = (gl_FragCoord.xy * 2.0 - resolution)/min(resolution);
vec2 z = vec2(0.0, 0.0);
int count = 0;
for(int j = 0; j < int(ITERATION); j++){
if(length(z) > 2.0)
break;
z = vec2( z.x*z.x - z.y*z.y + point.x*2.*z.x*z.y,
(1. - point.y)*2.*z.x*z.y) + p;
count++;
}
float brightness = sin(float(count)/float(ITERATION)*100.);
gl_FragColor = vec4(vec3(brightness),1.);
}
""";
gl.Shader vert =
makeShader(ctx, VERTEX_SHADER_SOURCE, gl.VERTEX_SHADER);
gl.Shader frag =
makeShader(ctx, FRAGMENT_SHADER_SOURCE, gl.FRAGMENT_SHADER);
gl.Program program = makeProgram(ctx, <gl.Shader>[vert, frag]);
typed.Float32List vertices = s2fs("""
-1.0, 1.0, 0.0,
1.0, 1.0, 0.0,
-1.0, -1.0, 0.0,
1.0, -1.0, 0.0""");
gl.Buffer vBuf = ctx.createBuffer();
int vAttrLoc = ctx.getAttribLocation(program, "position");
ctx.bindBuffer(gl.ARRAY_BUFFER, vBuf);
ctx.enableVertexAttribArray(vAttrLoc);
ctx.vertexAttribPointer(vAttrLoc, 3, gl.FLOAT, false, 0, 0);
ctx.bufferDataTyped(gl.ARRAY_BUFFER, vertices, gl.STREAM_DRAW);
typed.Uint16List vindices = s2is("""0, 2, 1, 1, 2, 3""");
gl.Buffer iBuf = ctx.createBuffer();
ctx.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, iBuf);
ctx.bufferDataTyped(gl.ELEMENT_ARRAY_BUFFER, vindices, gl.STATIC_DRAW);
return program;
}
void draw(gl.RenderingContext ctx, gl.Program program, Point p) {
ctx.clearColor(1.0, 0.0, 0.0, 1);
ctx.clear(gl.COLOR_BUFFER_BIT);
gl.UniformLocation point =
ctx.getUniformLocation(program, "point");
gl.UniformLocation resolution =
ctx.getUniformLocation(program, "resolution");
String _point = "${p.x / ctx.canvas.width}, ${p.y / ctx.canvas.height}";
ctx.uniform2fv(point, s2fs(_point));
ctx.uniform2fv(resolution, s2fs("${ctx.canvas.width}, ${ctx.canvas.height}"));
ctx.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
ctx.flush();
}
void main() {
CanvasElement canvas = querySelector("#main");
document.onKeyDown.listen((KeyboardEvent e) {
if (e.keyCode == KeyCode.F) canvas.requestFullscreen();
});
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.RenderingContext ctx = canvas.getContext3d();
gl.Program program = mandelbrotProgram(ctx);
draw(ctx, program, new Point(0, 0));
canvas.onMouseMove.listen((MouseEvent e) {
print("${e.client},${e.movement},${e.offset}");
draw(ctx, program, e.offset);
});
}