本文では、SpriteSheetを使用して、描画パフォーマンスを改善する方法について解説します。
また、実際に、このパフォーマンスの違いを体感できるように、Webアプリを用意しました。
※ Dartで書きましたが、Dart以外の方も読めるようになっています。
SpriteSheetへの違和感がありませんでしたか?
皆さんはSpriteSheet(Texture Atlas)を利用したプログラムに違和感をもった事はありませんでしたか?
「なんで、こんな面倒な事するの?」「別々のファイルの方が変更楽でシンプルではないか?」
はい、その判断は良いと思います。私もそう思います。しかし。これは、GPUに即した設計がAPIに反映されているだけなのです。
それは、DrawCallを減らすテクニックなのだ!!
実際の描画はGPUを通して行われます。そのCPUとGPU間のデータの転送が、DrawCallです。
このDrawCallは比較的に処理が重いのです。ですから、このDrawCallを何度も読んでしまうと処理が重くなるわけです。
画像を表示するたびに毎回DrawCallする場合。
表示したい画像ごとにDrawする場合のWebGLのコードを思い出してみましょう。
※こんなのだったかな、くらいの感じで!!
import 'dart:html';
import 'dart:async';
import 'dart:web_gl';
import 'dart:typed_data';
List<String> vsSource = [
"attribute vec3 vertexPosition;",
"attribute vec2 texCoord;",
"varying vec2 textureCoord;",
"void main() {",
" textureCoord = texCoord;",
" gl_Position = vec4(vertexPosition, 1.0);",
"}"];
List<String> fsSource = [
"precision mediump float;",
"uniform sampler2D texture;",
"varying vec2 textureCoord;",
"void main() {",
" gl_FragColor = texture2D(texture, textureCoord);",
"}"
];
main() async {
ImageElement elm = await loadImage("chara.jpeg");
CanvasElement canvasElement = new CanvasElement(width: 600, height: 500);
document.body.append(canvasElement);
RenderingContext GL = canvasElement.getContext3d();
GL.clearColor(0.5, 0.8, 0.8, 0.7);
GL.clearDepth(1.0);
GL.clear(COLOR_BUFFER_BIT);
GL.flush();
//
// shader
Shader vs = GL.createShader(VERTEX_SHADER);
GL.shaderSource(vs, vsSource.join("\n"));
GL.compileShader(vs);
Shader fs = GL.createShader(FRAGMENT_SHADER);
GL.shaderSource(fs, fsSource.join("\n"));
GL.compileShader(fs);
Program pro = GL.createProgram();
GL.attachShader(pro, vs);
GL.attachShader(pro, fs);
GL.linkProgram(pro);
GL.useProgram(pro);
//
// texture
Texture tex = GL.createTexture();
GL.bindTexture(TEXTURE_2D, tex);
GL.texImage2D(TEXTURE_2D, 0, RGBA, RGBA, UNSIGNED_BYTE, elm);
GL.bindTexture(TEXTURE_2D, null);
GL.bindTexture(TEXTURE_2D, tex);
GL.texParameteri(TEXTURE_2D, TEXTURE_WRAP_S, CLAMP_TO_EDGE);
GL.texParameteri(TEXTURE_2D, TEXTURE_WRAP_T, CLAMP_TO_EDGE);
GL.texParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, NEAREST);
GL.texParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, NEAREST);
//
// vertex
Buffer vertexBuffer = GL.createBuffer();
Buffer indexBuffer = GL.createBuffer();
int vertexPositionLocation = GL.getAttribLocation(pro, "vertexPosition");
int texCoordLocation = GL.getAttribLocation(pro, "texCoord");
GL.bindBuffer(ARRAY_BUFFER, vertexBuffer);
GL.enableVertexAttribArray(vertexPositionLocation);
GL.enableVertexAttribArray(texCoordLocation);
GL.vertexAttribPointer(vertexPositionLocation, 3, FLOAT, false, (3+2)*Float32List.BYTES_PER_ELEMENT, 0);
GL.vertexAttribPointer(texCoordLocation, 2, FLOAT, false, (3+2)*Float32List.BYTES_PER_ELEMENT, 3 * Float32List.BYTES_PER_ELEMENT);
Float32List vertices = new Float32List.fromList(<double>[
-1.0, 1.0, 0.0, 0.0, 0.0,
1.0, 1.0, 0.0, 1.0, 0.0,
-1.0, -1.0, 0.0, 0.0, 1.0,
1.0, -1.0, 0.0, 1.0, 1.0]);
Uint16List indices = new Uint16List.fromList(<int>[
0, 1, 2, 2, 3, 1]);
GL.bindBuffer(ARRAY_BUFFER, vertexBuffer);
GL.bufferDataTyped(ARRAY_BUFFER, vertices, STATIC_DRAW);
GL.bindBuffer(ELEMENT_ARRAY_BUFFER, indexBuffer);
GL.bufferDataTyped(ELEMENT_ARRAY_BUFFER, indices, STATIC_DRAW);
GL.drawElements(TRIANGLES, indices.length, UNSIGNED_SHORT, 0);
}
Future<ImageElement> loadImage(String resName) async {
ImageElement elm = new ImageElement(src: resName);
Completer c = new Completer();
elm.onLoad.listen(c.complete);
await c.future;
return elm;
}
一度のGPUの処理で同時に表示できれば、一度のDrawCallで済む。
ひとつのTextureに複数の画像が含まれていれば、それをGPU側で取捨選択して、複数の画像を表示できるわけです。
さきほどのコードの以下の部分だけ変えてみましょう。画面に画像が2つ表示されます。
Float32List vertices = new Float32List.fromList(<double>[
-0.5, 0.5, 0.0, 0.0, 0.0,
0.5, 0.5, 0.0, 1.0, 0.0,
-0.5, -0.5, 0.0, 0.0, 1.0,
0.5, -0.5, 0.0, 1.0, 1.0,
//
-1.0, -0.5, 0.0, 0.25, 0.25,
-0.5, -0.5, 0.0, 0.75, 0.25,
-1.0, -1.0, 0.0, 0.25, 0.75,
-0.5, -1.0, 0.0, 0.75, 0.75
]);
Uint16List indices = new Uint16List.fromList(<int>[
0, 1, 2, 2, 3, 1,
//
4, 5, 6, 6, 7, 5]);
これが、SpriteSheet的な手法が用いられ続けてる一つ回答です。GPU側からみると、この設計は、とても自然な発想から生まれているのが解るでしょう。
体感してみよう。
bad know-how を実行するとPCがフリーズするかもしれないので注意!!ニコニコ動画とかは一度停止していからクリックしてね!!
(丸) Bad know-how (※シークレットウインドウで開いてね)
[軽め]
[重め]
(丸) Good know-how (※シークレットウインドウで開いてね)
[軽め]
[重め]
※ 僕のPCだと、このくらいでも明らかな差がでるのですが、出てなかったらごめん。Chromeよりも、Firefoxの方が顕著に差がでるかも。
ほそく
DartでWebGL入門-テクスチャ編
Texture Atlas
デモのソース: Umiuni2D
2Dゲームに関する、kyorohiroのメモ(まだ何もない)
今回のノウハウを生かしたゲームのデモ
Thanks!
最後まで、読んでいただきまして、ありがとうございました。
(ref http://pixiv.me/kyorohiro)
(ref https://play.google.com/store/apps/details?id=info.kyorohiro.umiuni2d.demo.mino)
(ref http://kyorohiro.github.io/umiuni2d/mino2/web/main.html)
------- Kyorohiro Work http://kyorohiro.strikingly.com/ http://kyorohiro.github.io/