【4kb】Visual Studio 2017で小さいexeを作る【メガデモ】
Cluster,Inc. Advent Calendar 2018、12日目の記事です。 qiita.com
以前書いたこちらの記事のリライトになります。 wordpress.notargs.com
概要
プログラムで描いた映像、音楽を前のスクリーンにデカデカと写し、その年最強のデモシーナー(プログラムでエモいの作る人)を決める「TokyoDemoFest」というイベントがあります。
自分も毎年参加しているのですが、「吐き出されるexeが小さければ小さいほど評価が高い」という傾向があるので、最新のVisualStudioで小さいexeを吐き出す方法を調べてみました。
作ったもの
TokyoDemoFestのトップページ用に書いたOptical StreamというシェーダーをMinifyしてexeに固めてみたところ、1.64kb
になりました。
どうやっているのか
LinkerにCrinkler、シェーダーのMinifyにShader Minifierを使い、OpenGLで描画します。
The Crinkler executable file compressor
Shader Minifier - Ctrl-Alt-TestCtrl-Alt-Test
また、すべての既定のライブラリの無視
をはい(/NODEFAULTLIB)
に設定し、不要なライブラリがリンクされないようにします。
設定する箇所
空のプロジェクト
から新規プロジェクトを作成し、下記の設定をしていきます。
x64はcrinklerが正しく動かないためスルーし、x86のみ設定していきます。
Release/Debug共通
構成プロパティ/全般/文字セット
をマルチバイト文字セットを使用する
に変更します。構成プロパティ/C/C++/追加のインクルードディレクトリ
に$(ProjectDir)
を設定します。構成プロパティ/リンカー/入力/追加の依存ファイル
にopengl32.lib;%(AdditionalDependencies)
を設定します。
Debugのみ
構成プロパティ/リンカー/システム/サブシステム
にコンソール(/SUBSYSTEM:CONSOLE)
を設定します。
Releaseのみ
構成プロパティ/全般/プログラム全体の最適化
を、プログラム全体の最適化なし
に設定します。構成プロパティ/VC++ディレクトリ/実行可能ファイルディレクトリ
に$(SolutionDir);$(ExecutablePath)
を設定します。構成プロパティ/リンカー/入力/すべての既定のライブラリの無視
にはい (/NODEFAULTLIB)
を設定します。構成プロパティ/リンカー/システム/サブシステム
にWindows(/SUBSYSTEM:WINDOWS)
を設定します。構成プロパティ/リンカー/コマンドライン
に/CRINKLER
を設定します。
Crinklerの配置
Crinklerを下記のページから落としてきてlink.exe
にリネームし、ソリューションディレクトリに配置します。
The Crinkler executable file compressor
シェーダーのコンパイル
まず適当なフラグメントシェーダー、頂点シェーダーを用意します。 ここでは、Optical Streamを適当にコンバートしたものを使います。
Fragment.glsl
#version 120 varying vec2 vF; uniform float iTime = 0.0; vec2 iResolution = vec2(1920.0, 1080.0); const float PI = 3.1415926; vec3 rgb2hsv(vec3 hsv) { vec4 t = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); vec3 p = abs(fract(vec3(hsv.x) + t.xyz) * 6.0 - vec3(t.w)); return hsv.z * mix(vec3(t.x), clamp(p - vec3(t.x), 0.0, 1.0), hsv.y); } mat2 rotate(float a) { float s = sin(a); float c = cos(a); return mat2( c, -s, s, c ); } float rand(vec4 co) { return fract(sin(dot(co, vec4(12.9898, 78.233, 15.2358, 29.23851))) * 43758.5453); } float groundDist(vec3 pos) { pos.y += sin(pos.z * 0.2 + pos.x + iTime * 10.0) * 0.5; pos.x = mod(pos.x, 4.0) - 2.0; return length(pos.yx); } float particleDist(vec3 pos) { pos += cross(sin(pos * 0.05 + iTime), cos(pos * 0.05 + iTime)) * 3.0; pos.z += iTime * 200.0; vec3 id = floor(pos / 16.0); pos = mod(pos, 16.0) - 8.0; pos += vec3(rand(vec4(id, 0.0)), rand(vec4(id, 1.0)), rand(vec4(id, 2.0))) * 10.0 - 5.0; return max(length(pos.yx), abs(pos.z) - 2.0); } float skyDist(vec3 pos) { pos.z += iTime * 50.0; vec3 id = floor(pos / 50.0); vec3 t = iTime * vec3(0.0125, 0.25, 0.5); vec3 a = vec3(rand(vec4(id, floor(t.x))), rand(vec4(id + 10.0, floor(t.y))), rand(vec4(id + 20.0, floor(t.z)))); vec3 b = vec3(rand(vec4(id, floor(t.x + 1.0))), rand(vec4(id + 10.0, floor(t.y + 1.0))), rand(vec4(id + 20.0, floor(t.z + 1.0)))); vec3 c = mix(a, b, pow(fract(t), vec3(1.0 / 4.0))); float s = sign(mod(id.x + id.y + id.z + 0.5, 2.0) - 1.0); vec3 u = iTime / 3.0 + vec3(1.0, 2.0, 3.0) / 3.0; vec3 d = floor(u); vec3 e = floor(u + 1.0); vec3 f = mix(d, e, pow(fract(u), vec3(1.0 / 8.0))); pos = mod(pos, 50.0) - 25.0; for (int i = 0; i < 3; ++i) { pos.yz = rotate(f.x * PI / 2.0 * s) * pos.yz; pos.xz = rotate(f.y * PI / 2.0 * s) * pos.xz; pos.xy = rotate(f.z * PI / 2.0 * s) * pos.xy; pos = abs(pos); pos -= (c * 12.0); pos *= 2.0; if (pos.x > pos.z) pos.xz = pos.zx; if (pos.y > pos.z) pos.yz = pos.zy; if (pos.x < pos.y) pos.xy = pos.yx; } return length(pos.xz) / 8.0; } float dist(vec3 pos) { float d = 3.402823466E+38; d = min(d, groundDist(pos)); d = min(d, skyDist(pos)); return d; } vec3 calcNormal(vec3 pos) { vec2 ep = vec2(0.001, 0.0); return normalize(vec3( dist(pos + ep.xyy) - dist(pos - ep.xyy), dist(pos + ep.yxy) - dist(pos - ep.yxy), dist(pos + ep.yyx) - dist(pos - ep.yyx) )); } vec3 calcColor(vec3 pos) { return rgb2hsv(vec3(pos.x * 0.04 + iTime, 1, 1)); } vec3 march(vec3 pos, vec3 dir) { vec3 color = vec3(0.0, 0.0, 0.0); for (int i = 0; i < 32; ++i) { float d = dist(pos); pos += dir * d * 0.9; color += max(vec3(0.0), 0.02 / d * calcColor(pos)); } return color; } vec3 marchParticle(vec3 pos, vec3 dir) { vec3 color = vec3(0.0, 0.0, 0.0); for (int i = 0; i < 32; ++i) { float d = particleDist(pos); pos += dir * d * 0.9; color += max(vec3(0.0), 0.005 / d * vec3(1.0, 1.0, 1.0)); } return color; } void mainImage( out vec4 fragColor, in vec2 fragCoord ) { vec2 p = (fragCoord.xy * 2.0 - iResolution.xy) / iResolution.y; vec3 pos = vec3(0, 0.0, -10); vec3 dir = normalize(vec3(p, 1.0)); dir.yz = rotate(-0.5) * dir.yz; pos.yz = rotate(-0.5) * pos.yz; dir.xz = rotate(sin(iTime) * 0.3) * dir.xz; pos.xz = rotate(sin(iTime) * 0.3) * pos.xz; dir.xy = rotate(0.1 + sin(iTime * 0.7) * 0.1) * dir.xy; pos.xy = rotate(0.1 + sin(iTime * 0.7) * 0.1) * pos.xy; vec3 color = vec3(0, 0, 0) * length(p.xy) * sin(iTime * 10.0); color += march(pos, dir); color += marchParticle(pos, dir); fragColor = vec4(color, 1.0); } void main(void) { mainImage(gl_FragColor, vF); }
Vertex.glsl
#version 120 varying vec2 vF; void main(){ gl_Position = gl_Vertex; vF = (gl_Vertex.xy + 1) / 2.0 * vec2(1920, 1080); }
下記のページからShader Minifier
をダウンロードし、シェーダーと同じディレクトリに配置します。
Shader Minifier - Ctrl-Alt-TestCtrl-Alt-Test
Shader Minifierを実行するbatファイル
下記のバッチファイルを作り、Shader Minifier
、各種シェーダーのあるディレクトリに配置します。
cd /d %~dp0 shader_minifier.exe Fragment.glsl --preserve-externals -o Fragment.h shader_minifier.exe Vertex.glsl --preserve-externals -o Vertex.h
実行すると、Vertex.h
、Fragment.h
というファイルが出力されたことがわかります。
出力されたFragment.h
/* File generated with Shader Minifier 1.1.5 * http://www.ctrl-alt-test.fr */ #ifndef FRAGMENT_H_ # define FRAGMENT_H_ const char *Fragment_glsl = "#version 120\n" "varying vec2 vF;" "uniform float iTime=0.;" "vec2 z=vec2(1920.,1080.);" "const float v=3.14159;" "vec3 n(vec3 v)" "{" "vec4 i=vec4(1.,2./3.,1./3.,3.);" "vec3 f=abs(fract(vec3(v.x)+i.xyz)*6.-vec3(i.w));" "return v.z*mix(vec3(i.x),clamp(f-vec3(i.x),0.,1.),v.y);" "}" "mat2 r(float v)" "{" "float f=sin(v),i=cos(v);" "return mat2(i,-f,f,i);" "}" "float f(vec4 v)" "{" "return fract(sin(dot(v,vec4(12.9898,78.233,15.2358,29.2385)))*43758.5);" "}" "float x(vec3 v)" "{" "return v.y+=sin(v.z*.2+v.x+iTime*10.)*.5,v.x=mod(v.x,4.)-2.,length(v.yx);" "}" "float m(vec3 v)" "{" "v+=cross(sin(v*.05+iTime),cos(v*.05+iTime))*3.;" "v.z+=iTime*200.;" "vec3 i=floor(v/16.);" "v=mod(v,16.)-8.;" "v+=vec3(f(vec4(i,0.)),f(vec4(i,1.)),f(vec4(i,2.)))*10.-5.;" "return max(length(v.yx),abs(v.z)-2.);" "}" "float s(vec3 i)" "{" "i.z+=iTime*50.;" "vec3 z=floor(i/50.),y=iTime*vec3(.0125,.25,.5),s=vec3(f(vec4(z,floor(y.x))),f(vec4(z+10.,floor(y.y))),f(vec4(z+20.,floor(y.z)))),x=vec3(f(vec4(z,floor(y.x+1.))),f(vec4(z+10.,floor(y.y+1.))),f(vec4(z+20.,floor(y.z+1.)))),m=mix(s,x,pow(fract(y),vec3(.25)));" "float e=sign(mod(z.x+z.y+z.z+.5,2.)-1.);" "vec3 n=iTime/3.+vec3(1.,2.,3.)/3.,c=floor(n),p=floor(n+1.),l=mix(c,p,pow(fract(n),vec3(.125)));" "i=mod(i,50.)-25.;" "for(int t=0;t<3;++t)" "{" "i.yz=r(l.x*v/2.*e)*i.yz;" "i.xz=r(l.y*v/2.*e)*i.xz;" "i.xy=r(l.z*v/2.*e)*i.xy;" "i=abs(i);" "i-=m*12.;" "i*=2.;" "if(i.x>i.z)" "i.xz=i.zx;" "if(i.y>i.z)" "i.yz=i.zy;" "if(i.x<i.y)" "i.xy=i.yx;" "}" "return length(i.xz)/8.;" "}" "float i(vec3 v)" "{" "float i=3.40282e+38;" "i=min(i,x(v));" "i=min(i,s(v));" "return i;" "}" "vec3 t(vec3 v)" "{" "vec2 z=vec2(.001,0.);" "return normalize(vec3(i(v+z.xyy)-i(v-z.xyy),i(v+z.yxy)-i(v-z.yxy),i(v+z.yyx)-i(v-z.yyx)));" "}" "vec3 d(vec3 v)" "{" "return n(vec3(v.x*.04+iTime,1,1));" "}" "vec3 d(vec3 v,vec3 m)" "{" "vec3 z=vec3(0.,0.,0.);" "for(int y=0;y<32;++y)" "{" "float e=i(v);" "v+=m*e*.9;" "z+=max(vec3(0.),.02/e*d(v));" "}" "return z;" "}" "vec3 f(vec3 v,vec3 i)" "{" "vec3 z=vec3(0.,0.,0.);" "for(int y=0;y<32;++y)" "{" "float e=m(v);" "v+=i*e*.9;" "z+=max(vec3(0.),.005/e*vec3(1.,1.,1.));" "}" "return z;" "}" "void i(out vec4 v,in vec2 i)" "{" "vec2 y=(i.xy*2.-z.xy)/z.y;" "vec3 e=vec3(0,0.,-10),x=normalize(vec3(y,1.));" "x.yz=r(-.5)*x.yz;" "e.yz=r(-.5)*e.yz;" "x.xz=r(sin(iTime)*.3)*x.xz;" "e.xz=r(sin(iTime)*.3)*e.xz;" "x.xy=r(.1+sin(iTime*.7)*.1)*x.xy;" "e.xy=r(.1+sin(iTime*.7)*.1)*e.xy;" "vec3 n=vec3(0,0,0)*length(y.xy)*sin(iTime*10.);" "n+=d(e,x);" "n+=f(e,x);" "v=vec4(n,1.);" "}" "void main()" "{" "i(gl_FragColor,vF);" "}"; #endif // FRAGMENT_H_
出力されたVertex.h
/* File generated with Shader Minifier 1.1.5 * http://www.ctrl-alt-test.fr */ #ifndef VERTEX_H_ # define VERTEX_H_ const char *Vertex_glsl = "#version 120\n" "varying vec2 vF;" "void main()" "{" "gl_Position=gl_Vertex,vF=(gl_Vertex.xy+1)/2.*vec2(1920,1080);" "}"; #endif // VERTEX_H_
これらはそのままC++でincludeして使うことができます。
コードを書く
あとは適当に↑をつかって画像を描画するコードを書いて完成です。
Main.cpp
#include <windows.h> #include <gl/GL.h> #include "gl/glext.h" #include "Fragment.h" #include "Vertex.h" #include <cstdio> #define SCREEN_WIDTH 1920 #define SCREEN_HEIGHT 1080 #ifndef _DEBUG #define FULL_SCREEN #endif inline bool IsNotClosed() { #ifndef FULL_SCREEN MSG msg; PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE); if (msg.message == WM_NCLBUTTONDOWN && msg.wParam == HTCLOSE) { return false; } DispatchMessage(&msg); #endif return !GetAsyncKeyState(VK_ESCAPE); } #if _DEBUG inline void printShaderInfoLog(GLuint shader) { GLsizei bufSize; PFNGLGETSHADERIVPROC(wglGetProcAddress("glGetShaderiv"))(shader, GL_INFO_LOG_LENGTH, &bufSize); if (bufSize > 1) { const auto infoLog = static_cast<GLchar *>(malloc(bufSize)); if (infoLog != nullptr) { GLsizei length; PFNGLGETSHADERINFOLOGPROC(wglGetProcAddress("glGetShaderInfoLog"))(shader, bufSize, &length, infoLog); fprintf(stderr, "InfoLog:\n%s\n\n", infoLog); free(infoLog); } else { fprintf(stderr, "Could not allocate InfoLog buffer.\n"); } } } #endif inline GLuint CompileShaderProgram() { const auto program = PFNGLCREATEPROGRAMPROC(wglGetProcAddress("glCreateProgram"))(); const auto vertex = PFNGLCREATESHADERPROC(wglGetProcAddress("glCreateShader"))(GL_VERTEX_SHADER); PFNGLSHADERSOURCEPROC(wglGetProcAddress("glShaderSource"))(vertex, 1, &Vertex_glsl, 0); PFNGLCOMPILESHADERPROC(wglGetProcAddress("glCompileShader"))(vertex); #if _DEBUG printf("Vertex: \n"); printShaderInfoLog(vertex); #endif PFNGLATTACHSHADERPROC(wglGetProcAddress("glAttachShader"))(program, vertex); const auto fragment = PFNGLCREATESHADERPROC(wglGetProcAddress("glCreateShader"))(GL_FRAGMENT_SHADER); PFNGLSHADERSOURCEPROC(wglGetProcAddress("glShaderSource"))(fragment, 1, &Fragment_glsl, 0); PFNGLCOMPILESHADERPROC(wglGetProcAddress("glCompileShader"))(fragment); #if _DEBUG printf("Fragment: \n"); printShaderInfoLog(fragment); #endif PFNGLATTACHSHADERPROC(wglGetProcAddress("glAttachShader"))(program, fragment); PFNGLLINKPROGRAMPROC(wglGetProcAddress("glLinkProgram"))(program); PFNGLUSEPROGRAMPROC(wglGetProcAddress("glUseProgram"))(program); return program; } inline void InitializeWindow(HDC& deviceContext, HWND& windowHandle, HGLRC& renderingContext) { #ifdef FULL_SCREEN static DEVMODE dmScreenSettings = { "", 0, 0, sizeof(dmScreenSettings), 0, DM_PELSWIDTH | DM_PELSHEIGHT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN); windowHandle = CreateWindow("edit", nullptr, WS_POPUP | WS_VISIBLE | WS_MAXIMIZE, 0, 0, 0, 0, 0, 0, 0, 0); ShowCursor(0); #else windowHandle = CreateWindow("edit", nullptr, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, nullptr, nullptr, nullptr, nullptr); #endif deviceContext = GetDC(windowHandle); static const PIXELFORMATDESCRIPTOR g_pixelFormatDescriptor = { 0, 1, PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0 }; SetPixelFormat(deviceContext, ChoosePixelFormat(deviceContext, &g_pixelFormatDescriptor), &g_pixelFormatDescriptor); renderingContext = wglCreateContext(deviceContext); wglMakeCurrent(deviceContext, renderingContext); } inline int run() { HDC deviceContext; HWND windowHandle; HGLRC renderingContext; InitializeWindow(deviceContext, windowHandle, renderingContext); const auto program = CompileShaderProgram(); const auto startTime = GetTickCount(); while (IsNotClosed()) { const auto timeLocation = PFNGLGETUNIFORMLOCATIONARBPROC(wglGetProcAddress("glGetUniformLocation"))(program, "iTime"); PFNGLUNIFORM1FPROC(wglGetProcAddress("glUniform1f"))(timeLocation, (GetTickCount() - startTime) / 1000.0f ); glRecti(1, 1, -1, -1); SwapBuffers(deviceContext); } wglMakeCurrent(nullptr, nullptr); wglDeleteContext(renderingContext); ReleaseDC(windowHandle, deviceContext); PostQuitMessage(0); ExitProcess(0); } #if _DEBUG int main(void) { run(); } #else void WinMainCRTStartup() { run(); } #endif
エラーの対処
gl/glext.h
が無いよ!khr/khrplatform.h
が無いよ!などと怒られるので、KhronosGropu公式ページから落としてきて入れてあげましょう。
https://www.khronos.org/registry/OpenGL/api/GL/glext.h
https://www.khronos.org/registry/EGL/api/KHR/khrplatform.h
ビルド
Releaseでビルドすると、1.64kbのexeが吐き出されます。 お疲れ様でした!