OpenCLで画像処理
今回はOpenCLを用いて画像処理を行ってみたいと思います。
下図のように、赤と青を入れ替える処理を実装していきます。
アプリケーションコード
clCreateImageでもいいですが、今回はclCreateBufferで実装してみました。
BMPの読み書きは下記記事のものを利用しています。
Bitmap読み書き - 何でもプログラミング
#include <cl/cl.h> #define ASSERT_CL(expr) if (expr != CL_SUCCESS) { throw std::exception(#expr); } int main() { // 画像読み込み int width, height; std::vector<byte> srcPixels; LoadBitmap24("Parrots.bmp", &width, &height, &srcPixels); // device, context作成 cl_platform_id platform; ASSERT_CL(clGetPlatformIDs(1, &platform, nullptr)); cl_device_id device; ASSERT_CL(clGetDeviceIDs(platform, CL_DEVICE_TYPE_ALL, 1, &device, nullptr)); cl_context context = clCreateContext(nullptr, 1, &device, nullptr, nullptr, nullptr); assert(context); // kernel作成 const char* source = "__kernel void main(__global const uchar* src, __global uchar *dst) { \n" " int i = get_global_id(0); \n" " dst[3 * i] = src[3 * i + 2]; \n" " dst[3 * i + 1] = src[3 * i + 1]; \n" " dst[3 * i + 2] = src[3 * i]; \n" "} \n"; cl_program program = clCreateProgramWithSource(context, 1, &source, nullptr, nullptr); assert(program); ASSERT_CL(clBuildProgram(program, 1, &device, nullptr, nullptr, nullptr)); char buildLog[1024]; ASSERT_CL(clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 1024, buildLog, nullptr)); printf("build %s\n", buildLog); cl_kernel kernel = clCreateKernel(program, "main", nullptr); assert(kernel); // 引数設定 int size = (int)srcPixels.size(); cl_mem srcBuffer = clCreateBuffer(context, CL_MEM_READ_ONLY, size, nullptr, nullptr); assert(srcBuffer); cl_mem dstBuffer = clCreateBuffer(context, CL_MEM_WRITE_ONLY, size, nullptr, nullptr); assert(dstBuffer); ASSERT_CL(clSetKernelArg(kernel, 0, sizeof(srcBuffer), &srcBuffer)); ASSERT_CL(clSetKernelArg(kernel, 1, sizeof(dstBuffer), &dstBuffer)); // Queue作成 cl_command_queue queue = clCreateCommandQueue(context, device, 0, nullptr); assert(queue); ASSERT_CL(clEnqueueWriteBuffer(queue, srcBuffer, CL_TRUE, 0, size, srcPixels.data(), 0, nullptr, nullptr)); size_t global_work_size = size / 3; ASSERT_CL(clEnqueueNDRangeKernel(queue, kernel, 1, nullptr, &global_work_size, nullptr, 0, nullptr, nullptr)); std::vector<byte> dstPixels(size); ASSERT_CL(clEnqueueReadBuffer(queue, dstBuffer, CL_TRUE, 0, size, dstPixels.data(), 0, nullptr, nullptr)); // 実行 ASSERT_CL(clFlush(queue)); ASSERT_CL(clFinish(queue)); // 画像保存 SaveBitmap24("Parrots2.bmp", width, height, dstPixels.data()); return 0; }
SDKなしでの開発
OpenCLのSDKは、IntellやNVIDIAなどから提供されています。
ちょっとしたものであれば、ヘッダファイルだけ取得して動的にdllをロードすることで開発できます。
ヘッダは下記よりダウンロードできます。(最低限、cl.h と cl_platform.h で大丈夫です。)
Khronos OpenCL Registry - The Khronos Group Inc
dllのロードは、下記コードのように行っています。
ヘッダに関数郡がすでに定義されているため、一段namespaceで包んで関数をロードしています。
#include <cl/cl.h> namespace App { HMODULE hOpenCL = LoadLibraryA("opencl.dll"); #define GET_PROC(name) decltype(::name)* name = (decltype(::name)*)GetProcAddress(hOpenCL, #name) GET_PROC(clGetPlatformIDs); GET_PROC(clGetDeviceIDs); GET_PROC(clCreateContext); ... int main() { .... } } int main() { return App::main(); }
WebGL2.0でボリュームレンダリング(TypeScript)
下記記事にて、Direct3D11を用いてボリュームレンダリングを実装してみました。
Direct3D11でボリュームレンダリング - 何でもプログラミング
今回はWebGL2.0を利用して同様のものを実装してみたいと思います。
ボリュームレンダリングの手法やデータは上記記事を参照してみてください。
※現時点ではWebGL2.0はChromeとFirefoxでしかサポートされていません。
HTML
表示のため、下記のhtmlを用意しました。
canvasはWebGLレンタリング用で、file inputはデータを読み込むためのものです。
最終的に下図のような表示になります。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>webGL</title> </head> <body> <canvas id="gl" width="200" height="200"></canvas> <input id="file" type="file"> <script src="script.js"></script> </body> </html>
WebGL2.d.ts
WebGL2の定義ファイルはデフォルトで含まれていないため、下記のものをダウンロードして利用しました。
GitHub - MaxGraey/WebGL2-TypeScript: WebGL2 bindings for TypeScript
いくつかWebGLRenderingContextの定義とかち合う関数があるので、適宜コメントアウトして利用してください。
main関数
Direct3Dの時と同様に、三方向のスライスセットを準備し適宜切り替えています。
また陰影もボリューム値の勾配を法線として算出しています。
function main() : void { // context取得 const canvas = <HTMLCanvasElement>document.getElementById("gl"); const gl = canvas.getContext("webgl2")!; // Shader作成 const vertexCode = `#version 300 es layout(location = 0) in vec4 position; layout(location = 1) in vec3 texcoord; uniform mat4 matrix; out vec3 v_texcoord; void main() { gl_Position = matrix * position; v_texcoord = texcoord; } `; const fragmentCode = `#version 300 es precision mediump float; precision mediump sampler3D; in vec3 v_texcoord; uniform sampler3D tex; out vec4 fragColor; void main() { if (texture(tex, v_texcoord).r < 0.5) discard; float dx = textureOffset(tex, v_texcoord, ivec3(1, 0, 0)).r - textureOffset(tex, v_texcoord, ivec3(-1, 0, 0)).r; float dy = textureOffset(tex, v_texcoord, ivec3(0, 1, 0)).r - textureOffset(tex, v_texcoord, ivec3( 0, -1, 0)).r; float dz = textureOffset(tex, v_texcoord, ivec3(0, 0, 1)).r - textureOffset(tex, v_texcoord, ivec3( 0, 0, -1)).r; vec3 normal = normalize(vec3(dx, dy, dz)); vec3 light = normalize(vec3(1, 1, 1)); float gray = abs(dot(normal, light)); fragColor = vec4(gray, gray, gray, 1); } `; const program = createProgram(gl, vertexCode, fragmentCode); // Texture3D、VertexBuffer、IndexBuffer作成(Fileが読み込まれた際) let texture : WebGLTexture | undefined; let slices : VolumeSlices | undefined; const fileInput = <HTMLInputElement>document.getElementById("file"); fileInput.onchange = ev => { const file = (<HTMLInputElement>ev.target).files![0]; const reader = new FileReader(); reader.onload = _ev => { const buf = new Uint8Array(<ArrayBuffer>reader.result); texture = createTexture3D(gl, 512, 512, 360, buf); slices = new VolumeSlices(gl, 512, 512, 360); }; reader.readAsArrayBuffer(file); }; // 固定の設定 gl.useProgram(program); gl.enableVertexAttribArray(0); gl.enableVertexAttribArray(1); const matrixUniform = gl.getUniformLocation(program, "matrix")!; gl.enable(gl.DEPTH_TEST); // 描画関数 function draw(rotX : number, rotY : number) : void { gl.clearColor(0.5, 0.6, 1.0, 1); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); if (slices == undefined || texture == undefined) return; // 行列設定 const mat = Matrix4.rotate(rotY, new Vector3(0, 1, 0)) .mul(Matrix4.rotate(rotX, new Vector3(1, 0, 0))) .mul(Matrix4.scale(1.5 / 512)) .mul(Matrix4.translate(new Vector3(-512.0 / 2, -512.0 / 2, -360.0 / 2))); gl.uniformMatrix4fv(matrixUniform, false, mat.transpose().data); // VertexBuffer、IndexBuffer設定 const { vertexBuffer, indexBuffer } = slices.getBuffer(mat); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 24, 0); gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 24, 12); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); // 描画 const indexCount = gl.getBufferParameter(gl.ELEMENT_ARRAY_BUFFER, gl.BUFFER_SIZE) / 2; gl.drawElements(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0); } // MouseMoveで描画 canvas.onmousemove = ev => { const rotX = 2 * Math.PI * ev.y / canvas.height; const rotY = 2 * Math.PI * ev.x / canvas.width; draw(rotX, rotY); }; }
Slices
三方向のスライスセットを作成、適切なものを取得できるクラスになります。
class VolumeSlices { private vertexBufferX : WebGLBuffer; private indexBufferX : WebGLBuffer; private vertexBufferY : WebGLBuffer; private indexBufferY : WebGLBuffer; private vertexBufferZ : WebGLBuffer; private indexBufferZ : WebGLBuffer; constructor(gl : WebGLRenderingContext, width : number, height : number, depth : number) { const verticesZ = normalizedRange(depth, 0, 1) .concatMap(t => [ 0, 0, t * depth, 0, 0, t, width, 0, t * depth, 1, 0, t, 0, height, t * depth, 0, 1, t, width, height, t * depth, 1, 1, t, ]); const verticesY = normalizedRange(height, 0, 1) .concatMap(t => [ 0, t * height, 0, 0, t, 0, width, t * height, 0, 1, t, 0, 0, t * height, depth, 0, t, 1, width, t * height, depth, 1, t, 1, ]); const verticesX = normalizedRange(width, 0, 1) .concatMap(t => [ t * width, 0, 0, t, 0, 0, t * width, height, 0, t, 1, 0, t * width, 0, depth, t, 0, 1, t * width, height, depth, t, 1, 1, ]); const createIndices = (count : number) => range(count).concatMap<number>(i => [0, 2, 1, 1, 2, 3].map<number>(x => x + 4 * i)); this.vertexBufferX = createVertexBuffer(gl, new Float32Array(verticesX)); this.vertexBufferY = createVertexBuffer(gl, new Float32Array(verticesY)); this.vertexBufferZ = createVertexBuffer(gl, new Float32Array(verticesZ)); this.indexBufferX = createIndexBuffer(gl, new Uint16Array(createIndices(width))); this.indexBufferY = createIndexBuffer(gl, new Uint16Array(createIndices(height))); this.indexBufferZ = createIndexBuffer(gl, new Uint16Array(createIndices(depth))); } getBuffer(m : Matrix4) : { vertexBuffer : WebGLBuffer, indexBuffer : WebGLBuffer } { const dotX = Math.abs(m.transform3x3(Vector3.ex).z); const dotY = Math.abs(m.transform3x3(Vector3.ey).z); const dotZ = Math.abs(m.transform3x3(Vector3.ez).z); if (dotX < dotZ && dotY < dotZ) return { vertexBuffer : this.vertexBufferZ, indexBuffer : this.indexBufferZ }; else if (dotX < dotY) return { vertexBuffer : this.vertexBufferY, indexBuffer : this.indexBufferY }; else return { vertexBuffer : this.vertexBufferX, indexBuffer : this.indexBufferX }; } }
WebGL関数群
main関数内で利用されているWebGL関連の関数は下記のように実装しています。
function compileShader(gl : WebGLRenderingContext, shader : WebGLShader, code : string) : void { gl.shaderSource(shader, code); gl.compileShader(shader); console.log(gl.getShaderInfoLog(shader)); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) throw new Error("compile error"); } function createProgram(gl : WebGLRenderingContext, vertexCode : string, fragmentCode : string) : WebGLProgram { const vertexShader = gl.createShader(gl.VERTEX_SHADER)!; compileShader(gl, vertexShader, vertexCode); const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)!; compileShader(gl, fragmentShader, fragmentCode); const program = gl.createProgram()!; gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); console.log(gl.getProgramInfoLog(program)); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) throw new Error("program error"); return program; }
function createVertexBuffer(gl : WebGLRenderingContext, vertices : Float32Array) : WebGLBuffer { const buffer = gl.createBuffer()!; gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); return buffer; }
function createIndexBuffer(gl : WebGLRenderingContext, indices : Uint16Array) : WebGLBuffer { const buffer = gl.createBuffer()!; gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW); return buffer; }
function createTexture3D(gl : WebGL2RenderingContext, width : number, height : number, depth : number, voxels : Uint8Array) : WebGLTexture { const texture = gl.createTexture()!; gl.bindTexture(gl.TEXTURE_3D, texture); gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); gl.texImage3D(gl.TEXTURE_3D, 0, gl.R8, width, height, depth, 0, gl.RED, gl.UNSIGNED_BYTE, voxels); gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE); return texture; }
Vector3、Matrix4
動作確認レベルの実装ですので、お好みの行列ライブラリで置き換えてください。
class Vector3 { constructor( readonly x : number, readonly y : number, readonly z : number, ) {} add(v : Vector3) : Vector3 { return new Vector3(this.x + v.x, this.y + v.y, this.z + v.z); } sub(v : Vector3) : Vector3 { return new Vector3(this.x - v.x, this.y - v.y, this.z - v.z); } mul(s : number) : Vector3 { return new Vector3(this.x * s, this.y * s, this.z * s); } div(s : number) : Vector3 { return new Vector3(this.x / s, this.y / s, this.z / s); } length() : number { return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); } normalize() : Vector3 { return this.div(this.length()); } dot(v : Vector3) : number { return this.x * v.x + this.y * v.y + this.z * v.z; } cross(v : Vector3) : Vector3 { return new Vector3(this.y * v.z - this.z * v.y, this.z * v.x - this.x * v.z, this.x * v.y - this.y * v.x); } static readonly ex = new Vector3(1, 0, 0); static readonly ey = new Vector3(0, 1, 0); static readonly ez = new Vector3(0, 0, 1); }
class Matrix4 { constructor(readonly data: number[]) { } mul(m : Matrix4) : Matrix4 { return new Matrix4([ this.data[0] * m.data[0] + this.data[1] * m.data[4] + this.data[2] * m.data[8] + this.data[3] * m.data[12], this.data[0] * m.data[1] + this.data[1] * m.data[5] + this.data[2] * m.data[9] + this.data[3] * m.data[13], this.data[0] * m.data[2] + this.data[1] * m.data[6] + this.data[2] * m.data[10] + this.data[3] * m.data[14], this.data[0] * m.data[3] + this.data[1] * m.data[7] + this.data[2] * m.data[11] + this.data[3] * m.data[15], this.data[4] * m.data[0] + this.data[5] * m.data[4] + this.data[6] * m.data[8] + this.data[7] * m.data[12], this.data[4] * m.data[1] + this.data[5] * m.data[5] + this.data[6] * m.data[9] + this.data[7] * m.data[13], this.data[4] * m.data[2] + this.data[5] * m.data[6] + this.data[6] * m.data[10] + this.data[7] * m.data[14], this.data[4] * m.data[3] + this.data[5] * m.data[7] + this.data[6] * m.data[11] + this.data[7] * m.data[15], this.data[8] * m.data[0] + this.data[9] * m.data[4] + this.data[10] * m.data[8] + this.data[11] * m.data[12], this.data[8] * m.data[1] + this.data[9] * m.data[5] + this.data[10] * m.data[9] + this.data[11] * m.data[13], this.data[8] * m.data[2] + this.data[9] * m.data[6] + this.data[10] * m.data[10] + this.data[11] * m.data[14], this.data[8] * m.data[3] + this.data[9] * m.data[7] + this.data[10] * m.data[11] + this.data[11] * m.data[15], this.data[12] * m.data[0] + this.data[13] * m.data[4] + this.data[14] * m.data[8] + this.data[15] * m.data[12], this.data[12] * m.data[1] + this.data[13] * m.data[5] + this.data[14] * m.data[9] + this.data[15] * m.data[13], this.data[12] * m.data[2] + this.data[13] * m.data[6] + this.data[14] * m.data[10] + this.data[15] * m.data[14], this.data[12] * m.data[3] + this.data[13] * m.data[7] + this.data[14] * m.data[11] + this.data[15] * m.data[15], ]); } transform(v : Vector3) : Vector3 { const x = this.data[0] * v.x + this.data[1] * v.y + this.data[2] * v.z + this.data[3]; const y = this.data[4] * v.x + this.data[5] * v.y + this.data[6] * v.z + this.data[7]; const z = this.data[8] * v.x + this.data[9] * v.y + this.data[10] * v.z + this.data[11]; const w = this.data[12] * v.x + this.data[13] * v.y + this.data[14] * v.z + this.data[15]; return new Vector3(x / w, y / w, z / w); } transform3x3(v : Vector3) : Vector3 { return new Vector3( this.data[0] * v.x + this.data[1] * v.y + this.data[2] * v.z, this.data[4] * v.x + this.data[5] * v.y + this.data[6] * v.z, this.data[8] * v.x + this.data[9] * v.y + this.data[10] * v.z, ); } transpose() : Matrix4 { return new Matrix4([ this.data[0], this.data[4], this.data[8], this.data[12], this.data[1], this.data[5], this.data[9], this.data[13], this.data[2], this.data[6], this.data[10], this.data[14], this.data[3], this.data[7], this.data[11], this.data[15], ]); } getMatrix3Data() : number[] { return [ this.data[0], this.data[1], this.data[2], this.data[4], this.data[5], this.data[6], this.data[8], this.data[9], this.data[10], ]; } static readonly identity = new Matrix4([ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]); static translate(v : Vector3) : Matrix4 { return new Matrix4([ 1, 0, 0, v.x, 0, 1, 0, v.y, 0, 0, 1, v.z, 0, 0, 0, 1 ]); } static rotate(radian : number, axis : Vector3) : Matrix4 { const x = axis.x, y = axis.y, z = axis.z; const s = Math.sin(radian); const c = Math.cos(radian); return new Matrix4([ x * x * (1 - c) + c, x * y * (1 - c) - z * s, z * x * (1 - c) + y * s, 0, x * y * (1 - c) + z * s, y * y * (1 - c) + c, y * z * (1 - c) - x * s, 0, z * x * (1 - c) - y * s, y * z * (1 - c) + x * s, z * z * (1 - c) + c, 0, 0, 0, 0, 1 ]); } static scale(s : number) : Matrix4 { return new Matrix4([ s, 0, 0, 0, 0, s, 0, 0, 0, 0, s, 0, 0, 0, 0, 1 ]); } static ortho(width : number, height : number, near : number, far : number) : Matrix4 { return new Matrix4([ 2.0 / width, 0, 0, 0, 0, 2.0 / height, 0, 0, 0, 0, -2.0 / (far - near), -(far + near) / (far - near), 0, 0, 0, 1 ]); } static perspective(fovy : number, aspect : number, near : number, far : number) : Matrix4 { const f = 1.0 / Math.tan(fovy / 2.0); return new Matrix4([ f / aspect, 0, 0, 0, 0, f, 0, 0, 0, 0, (far + near) / (near - far), 2 * far * near / (near - far), 0, 0, -1, 0 ]); } }
Array関連の拡張
Slices作成内で利用しているArray関連の関数は下記のように実装してあります。
interface Array<T> { concatMap<U>(f : (value : T) => U[]) : U[]; } Array.prototype.concatMap = function(f : any) { return this.reduce((dst, x) => dst.concat(f(x)), []); }; function range(count : number) : number[] { const ary = Array<number>(count); for (let i = 0; i < count; ++i) ary[i] = i; return ary; } function normalizedRange(count : number, min : number, max : number) : number[] { return range(count).map<number>(i => ((count - 1 - i) * min + i * max) / (count - 1)); }
Direct3D11でボリュームレンダリング
ボリュームレンダリングの手法はいくつかありますが、今回はもっとも単純な、下図のように長方形のスライスを何枚も重ねて実現したものを実装したいと思います。
ボリュームデータ
下記ページにある、bunnyのデータを利用いたしました。
The Stanford volume data archive
もとは1ボクセルあたり16bitだったため、8bitに変換して1ファイルにまとめました。
アプリケーションコード
下図のような、マウスの位置によって回転角度の変わるアプリケーションを実装します。
main関数とWndProcは下記の様に実装しました。
VolumeRenderer renderer; float rotX = 0; float rotY = 0; LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_PAINT: renderer.Render(rotX, rotY); return 0; case WM_MOUSEMOVE: { RECT rect; ::GetClientRect(hwnd, &rect); int x = LOWORD(lParam); int y = HIWORD(lParam); rotY = 2 * 3.14f * (float)(x - rect.left) / (rect.right - rect.left); rotX = 2 * 3.14f * (float)(y - rect.top) / (rect.bottom - rect.top); InvalidateRect(hwnd, nullptr, FALSE); return 0; } case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, msg, wParam, lParam); }
int main() { HWND hwnd = CreateHWND(WndProc); renderer = CreateVolumeRenderer(hwnd); Run(hwnd); return 0; }
VolumeRenderer構造体とCreateVolumeRenderer関数
今回はRender関数のみをメンバとして持ったものとしました。
struct VolumeRenderer { std::function<void(float rotX, float rotY)> Render; };
HWNDを受け取りVolumeRendererを作成する関数を定義します。
複数のスライスを重ねてボリュームを描画する際、横から見てしまうとボリュームにならないため、X、Y、Z三方向のスライスセットを用意しておき随時適切なものを利用して描画するようにしています。
陰影はTextureのX、Y、Z方向の勾配を計算して、それを法線として利用しています。
VolumeRenderer CreateVolumeRenderer(HWND hwnd) { // device, context, swapChain作成 ComPtr<ID3D11Device> device; ComPtr<ID3D11DeviceContext> context; ComPtr<IDXGISwapChain> swapChain; std::tie(device, context, swapChain) = CreateDeviceAndSwapChain(hwnd); // RenderTarget、DepthBuffer作成 ComPtr<ID3D11RenderTargetView> renderTarget = CreateRenderTarget(device.Get(), swapChain.Get()); SIZE bufferSize = GetBufferSize(swapChain.Get()); ComPtr<ID3D11DepthStencilView> depthBuffer = CreateDepthBuffer(device.Get(), bufferSize.cx, bufferSize.cy); // Shader作成 std::string vertexCode = "cbuffer c0{ \n" " float4x4 Matrix; \n" "} \n" "struct VS_OUTPUT { \n" " float4 Position : SV_POSITION; \n" " float3 Texcoord : TEXCOORD; \n" "}; \n" "VS_OUTPUT main(float4 position : POSITION, float3 texcoord : TEXCOORD) { \n" " VS_OUTPUT output = (VS_OUTPUT)0; \n" " output.Position = mul(position, Matrix); \n" " output.Texcoord = texcoord; \n" " return output; \n" "} \n"; std::vector<D3D11_INPUT_ELEMENT_DESC> inputDesc = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, { "TEXCOORD", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 } }; ComPtr<ID3D11VertexShader> vertexShader; ComPtr<ID3D11InputLayout> inputLayout; std::tie(vertexShader, inputLayout) = CreateVertexShader(device.Get(), vertexCode, inputDesc); ComPtr<ID3D11Buffer> vertexConstBuffer = CreateConstantBuffer<VertexConst>(device.Get()); std::string pixelCode = "Texture3D texture0 : register(t0); \n" "SamplerState sampler0 { \n" " Filter = MIN_MAG_MIP_POINT; \n" " AddressU = Clamp; \n" " AddressV = Clamp; \n" " AddressW = Clamp; \n" "}; \n" "float4 main(float4 position : SV_POSITION, float3 texcoord : TEXCOORD) : SV_TARGET { \n" " if (texture0.Sample(sampler0, texcoord).r < 0.5) \n" " discard; \n" " float dx = texture0.Sample(sampler0, texcoord, int3(1, 0, 0)).r - texture0.Sample(sampler0, texcoord, int3(-1, 0, 0)).r; \n" " float dy = texture0.Sample(sampler0, texcoord, int3(0, 1, 0)).r - texture0.Sample(sampler0, texcoord, int3( 0, -1, 0)).r; \n" " float dz = texture0.Sample(sampler0, texcoord, int3(0, 0, 1)).r - texture0.Sample(sampler0, texcoord, int3( 0, 0, -1)).r; \n" " float3 normal = normalize(float3(dx, dy, dz)); \n" " float3 light = normalize(float3(1, 1, 1)); \n" " float gray = abs(dot(normal, light)); \n" " return float4(gray, gray, gray, 1.0); \n" "} \n"; ComPtr<ID3D11PixelShader> pixelShader = CreatePixelShader(device.Get(), pixelCode); // Texture3D作成 std::ifstream file("bunny", std::ios::binary | std::ios::ate); std::vector<byte> data((size_t)file.tellg()); file.seekg(0, std::ios::beg); file.read((char*)data.data(), data.size()); ComPtr<ID3D11ShaderResourceView> volumeTexture = CreateTexture3D(device.Get(), 512, 512, data); // VertexBuffer, IndexBuffer作成(X、Y、Z三方向のBuffer) Slices slices = CreateSlices(device.Get(), 512, 512, 360); // culling off用 RasterrizerState作成 ComPtr<ID3D11RasterizerState> rasterizerState = CreateRasterizerState(device.Get(), [](D3D11_RASTERIZER_DESC& desc) { desc.CullMode = D3D11_CULL_NONE; }); // Render関数作成 auto render = [=](float rotX, float rotY) { // Matrix作成 Matrix4 mat = Matrix4::Rotate(rotY, Vector3{ 0, 1, 0 }) * Matrix4::Rotate(rotX, Vector3{ 1, 0, 0 }) * Matrix4::Scale(1.5 / 512.0) * Matrix4::Translate(Vector3{ -512 / 2, -512 / 2, -360 / 2 }); // RenderTarget、DepthBuffer設定 context->OMSetRenderTargets(1, renderTarget.GetAddressOf(), depthBuffer.Get()); // RasterizerState設定 context->RSSetState(rasterizerState.Get()); // Shader設定 context->VSSetShader(vertexShader.Get(), nullptr, 0); context->IASetInputLayout(inputLayout.Get()); context->PSSetShader(pixelShader.Get(), nullptr, 0); // Buffer設定 ID3D11Buffer *vertexBuffer, *indexBuffer; std::tie(vertexBuffer, indexBuffer) = GetBuffers(slices, mat); SetVertexBuffer<Vertex>(context.Get(), vertexBuffer); context->IASetIndexBuffer(indexBuffer, DXGI_FORMAT_R16_UINT, 0); VertexConst vertexConst = { Matrix4::OrthoD3D(2, 2, -1, 1) * mat }; context->UpdateSubresource(vertexConstBuffer.Get(), 0, nullptr, &vertexConst, 0, 0); context->VSSetConstantBuffers(0, 1, vertexConstBuffer.GetAddressOf()); // Texture3D設定 context->PSSetShaderResources(0, 1, volumeTexture.GetAddressOf()); // Viewport設定 CD3D11_VIEWPORT viewport(0.0f, 0.0f, (float)bufferSize.cx, (float)bufferSize.cy); context->RSSetViewports(1, &viewport); // 描画 float clearColor[4] = { 0.5f, 0.6f, 1.0f, 1.0f }; context->ClearRenderTargetView(renderTarget.Get(), clearColor); context->ClearDepthStencilView(depthBuffer.Get(), D3D11_CLEAR_DEPTH, 1.0f, 0); context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); context->DrawIndexed(GetBufferSize<ushort>(indexBuffer), 0, 0); AssertHR(swapChain->Present(0, 0)); }; return{ render }; }
VertexConstとVertexは下記の様に定義してあります。
struct VertexConst { Matrix4 Matrix; }; struct Vertex { Vector3 Position; Vector3 Texcoord; };
Slices作成
一方向のスライスセットでは横から見たときボリュームにならないため、三方向のスライスセットを用意しておきます。
Slices構造体は下記の様に定義します。
struct Slices {
ComPtr<ID3D11Buffer> VertexBufferX;
ComPtr<ID3D11Buffer> IndexBufferX;
ComPtr<ID3D11Buffer> VertexBufferY;
ComPtr<ID3D11Buffer> IndexBufferY;
ComPtr<ID3D11Buffer> VertexBufferZ;
ComPtr<ID3D11Buffer> IndexBufferZ;
};
Slicesを作成する関数を下記の様に実装します。
(0, 0, 0)を原点として、(width, height, depth)のボックスのSlicesを作成しています。
Slices CreateSlices(ID3D11Device* device, int width, int height, int depth) { float w = (float)width, h = (float)height, d = (float)depth; std::vector<Vertex> verticesZ; for (int i = 0; i < depth; ++i) { float t = (float)i / (depth - 1); verticesZ.push_back({ { 0, 0, t * d }, { 0, 0, t } }); verticesZ.push_back({ { w, 0, t * d }, { 1, 0, t } }); verticesZ.push_back({ { 0, h, t * d }, { 0, 1, t } }); verticesZ.push_back({ { w, h, t * d }, { 1, 1, t } }); } std::vector<Vertex> verticesY; for (int i = 0; i < height; ++i) { float t = (float)i / (height - 1); verticesY.push_back({ { 0, t * h, 0 }, { 0, t, 0 } }); verticesY.push_back({ { w, t * h, 0 }, { 1, t, 0 } }); verticesY.push_back({ { 0, t * h, d }, { 0, t, 1 } }); verticesY.push_back({ { w, t * h, d }, { 1, t, 1 } }); } std::vector<Vertex> verticesX; for (int i = 0; i < width; ++i) { float t = (float)i / (width - 1); verticesX.push_back({ { t * w, 0, 0 }, { t, 0, 0 } }); verticesX.push_back({ { t * w, h, 0 }, { t, 1, 0 } }); verticesX.push_back({ { t * w, 0, d }, { t, 0, 1 } }); verticesX.push_back({ { t * w, h, d }, { t, 1, 1 } }); } auto createIndices = [](int count) { std::vector<ushort> indices; for (int i = 0; i < count; ++i) for (ushort j : { 0, 2, 1, 1, 2, 3 }) indices.push_back(4 * i + j); return indices; }; return{ CreateVertexBuffer(device, verticesX), CreateIndexBuffer(device, createIndices(width)), CreateVertexBuffer(device, verticesY), CreateIndexBuffer(device, createIndices(height)), CreateVertexBuffer(device, verticesZ), CreateIndexBuffer(device, createIndices(depth)) }; }
変換行列から適切なスライスセットを返す関数も定義します。
最も視線方向に向いているスライスセットを選択しています。
std::tuple<ID3D11Buffer*, ID3D11Buffer*> GetBuffers(const Slices& slices, const Matrix4& m) { float dotX = std::abs(m.Transform3x3(Vector3{ 1, 0, 0 }).Z); float dotY = std::abs(m.Transform3x3(Vector3{ 0, 1, 0 }).Z); float dotZ = std::abs(m.Transform3x3(Vector3{ 0, 0, 1 }).Z); if (dotX < dotZ && dotY < dotZ) return{ slices.VertexBufferZ.Get(), slices.IndexBufferZ.Get() }; else if (dotX < dotY) return{ slices.VertexBufferY.Get(), slices.IndexBufferY.Get() }; else return{ slices.VertexBufferX.Get(), slices.IndexBufferX.Get() }; }
Direct3D11用関数群
コード内で利用したDirect3D11関連の関数は下記の様に実装してあります。
std::tuple<ComPtr<ID3D11Device>, ComPtr<ID3D11DeviceContext>, ComPtr<IDXGISwapChain>> CreateDeviceAndSwapChain(HWND hwnd) { #ifdef _DEBUG DWORD createDeviceFlag = D3D11_CREATE_DEVICE_DEBUG; #else DWORD createDeviceFlag = 0; #endif DXGI_SWAP_CHAIN_DESC desc = { 0 }; desc.BufferDesc.Width = 0; // 現在のWindowサイズが自動で設定される desc.BufferDesc.Height = 0; desc.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; desc.BufferDesc.RefreshRate.Numerator = 60; desc.BufferDesc.RefreshRate.Denominator = 1; desc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; desc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; desc.SampleDesc.Count = 1; desc.SampleDesc.Quality = 0; desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; desc.BufferCount = 1; desc.OutputWindow = hwnd; desc.Windowed = TRUE; desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; desc.Flags = 0; ComPtr<ID3D11Device> device; ComPtr<ID3D11DeviceContext> context; ComPtr<IDXGISwapChain> swapChain; AssertHR(D3D11CreateDeviceAndSwapChain( nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlag, nullptr, 0, D3D11_SDK_VERSION, &desc, &swapChain, &device, nullptr, &context )); return { device, context, swapChain }; }
ComPtr<ID3D11RenderTargetView> CreateRenderTarget(ID3D11Device* device, IDXGISwapChain* swapChain) { ComPtr<ID3D11Texture2D> backBuffer; AssertHR(swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer))); ComPtr<ID3D11RenderTargetView> view; AssertHR(device->CreateRenderTargetView(backBuffer.Get(), nullptr, &view)); return view; }
SIZE GetBufferSize(IDXGISwapChain* swapChain) {
DXGI_SWAP_CHAIN_DESC desc;
AssertHR(swapChain->GetDesc(&desc));
return{ (LONG)desc.BufferDesc.Width, (LONG)desc.BufferDesc.Height };
}
ComPtr<ID3D11DepthStencilView> CreateDepthBuffer(ID3D11Device* device, int width, int height) { D3D11_TEXTURE2D_DESC textureDesc = { 0 }; textureDesc.Width = width; textureDesc.Height = height; textureDesc.Format = DXGI_FORMAT_D16_UNORM; textureDesc.MipLevels = 1; textureDesc.ArraySize = 1; textureDesc.SampleDesc.Count = 1; textureDesc.SampleDesc.Quality = 0; textureDesc.Usage = D3D11_USAGE_DEFAULT; textureDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL; textureDesc.CPUAccessFlags = 0; ComPtr<ID3D11Texture2D> texture; AssertHR(device->CreateTexture2D(&textureDesc, nullptr, &texture)); CD3D11_DEPTH_STENCIL_VIEW_DESC viewDesc(D3D11_DSV_DIMENSION_TEXTURE2D); ComPtr<ID3D11DepthStencilView> view; AssertHR(device->CreateDepthStencilView(texture.Get(), &viewDesc, &view)); return view; }
ComPtr<ID3DBlob> CompileShader(std::string code, std::string target) { #ifdef _DEBUG UINT flags1 = D3DCOMPILE_DEBUG | D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_WARNINGS_ARE_ERRORS; #else UINT flags1 = D3DCOMPILE_OPTIMIZATION_LEVEL3 | D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_WARNINGS_ARE_ERRORS; #endif ComPtr<ID3DBlob> compiled, errorMessage; D3DCompile( code.c_str(), code.size(), nullptr, nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE, "main", target.c_str(), flags1, 0, &compiled, &errorMessage ); if (errorMessage) throw std::exception((char*)errorMessage->GetBufferPointer()); return compiled; } std::tuple<ComPtr<ID3D11VertexShader>, ComPtr<ID3D11InputLayout>> CreateVertexShader( ID3D11Device* device, const std::vector<byte>& cso, const std::vector<D3D11_INPUT_ELEMENT_DESC>& inputDesc) { ComPtr<ID3D11VertexShader> shader; AssertHR(device->CreateVertexShader(cso.data(), cso.size(), NULL, &shader)); ComPtr<ID3D11InputLayout> inputLayout; AssertHR(device->CreateInputLayout(inputDesc.data(), inputDesc.size(), cso.data(), cso.size(), &inputLayout)); return { shader, inputLayout }; } ComPtr<ID3D11PixelShader> CreatePixelShader(ID3D11Device* device, const std::vector<byte>& cso) { ComPtr<ID3D11PixelShader> shader; AssertHR(device->CreatePixelShader(cso.data(), cso.size(), NULL, &shader)); return shader; } std::tuple<ComPtr<ID3D11VertexShader>, ComPtr<ID3D11InputLayout>> CreateVertexShader( ID3D11Device* device, std::string code, const std::vector<D3D11_INPUT_ELEMENT_DESC>& inputDesc) { auto blob = CompileShader(code, "vs_4_0"); auto p = (byte*)blob->GetBufferPointer(); std::vector<byte> cso(p, p + blob->GetBufferSize()); return CreateVertexShader(device, cso, inputDesc); } ComPtr<ID3D11PixelShader> CreatePixelShader(ID3D11Device* device, std::string code) { auto blob = CompileShader(code, "ps_4_0"); auto p = (byte*)blob->GetBufferPointer(); std::vector<byte> cso(p, p + blob->GetBufferSize()); return CreatePixelShader(device, cso); }
template <class T> ComPtr<ID3D11Buffer> CreateVertexBuffer(ID3D11Device* device, const std::vector<T>& vertices) { D3D11_BUFFER_DESC desc = { 0 }; desc.Usage = D3D11_USAGE_DEFAULT; desc.ByteWidth = vertices.size() * sizeof(T); desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; desc.CPUAccessFlags = 0; D3D11_SUBRESOURCE_DATA data = { 0 }; data.pSysMem = vertices.data(); ComPtr<ID3D11Buffer> buffer; AssertHR(device->CreateBuffer(&desc, &data, &buffer)); return buffer; }
template <class T> void SetVertexBuffer(ID3D11DeviceContext* context, ID3D11Buffer* buffer) { UINT stride = sizeof(T); UINT offset = 0; context->IASetVertexBuffers(0, 1, &buffer, &stride, &offset); }
template <class T> ComPtr<ID3D11Buffer> CreateIndexBuffer(ID3D11Device* device, const std::vector<T>& indices) { D3D11_BUFFER_DESC desc = { 0 }; desc.Usage = D3D11_USAGE_DEFAULT; desc.ByteWidth = indices.size() * sizeof(T); desc.BindFlags = D3D11_BIND_INDEX_BUFFER; desc.CPUAccessFlags = 0; D3D11_SUBRESOURCE_DATA data = { 0 }; data.pSysMem = indices.data(); ComPtr<ID3D11Buffer> buffer; AssertHR(device->CreateBuffer(&desc, &data, &buffer)); return buffer; }
template<class T> UINT GetBufferSize(ID3D11Buffer* buffer) { D3D11_BUFFER_DESC desc; buffer->GetDesc(&desc); return desc.ByteWidth / sizeof(T); }
template <class T> ComPtr<ID3D11Buffer> CreateConstantBuffer(ID3D11Device* device) { D3D11_BUFFER_DESC desc = { 0 }; desc.Usage = D3D11_USAGE_DEFAULT; desc.ByteWidth = sizeof(T); desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; desc.CPUAccessFlags = 0; ComPtr<ID3D11Buffer> buffer; AssertHR(device->CreateBuffer(&desc, nullptr, &buffer)); return buffer; }
ComPtr<ID3D11ShaderResourceView> CreateTexture3D(ID3D11Device* device, int width, int height, const std::vector<byte>& voxels) { D3D11_TEXTURE3D_DESC desc = { 0 }; desc.Width = width; desc.Height = height; desc.Depth = voxels.size() / width / height; desc.Format = DXGI_FORMAT_R8_UNORM; desc.MipLevels = 1; desc.Usage = D3D11_USAGE_DEFAULT; desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; desc.CPUAccessFlags = 0; desc.MiscFlags = 0; D3D11_SUBRESOURCE_DATA data = { 0 }; data.pSysMem = voxels.data(); data.SysMemPitch = width; data.SysMemSlicePitch = width * height; ComPtr<ID3D11Texture3D> texture; AssertHR(device->CreateTexture3D(&desc, &data, &texture)); ComPtr<ID3D11ShaderResourceView> view; AssertHR(device->CreateShaderResourceView(texture.Get(), nullptr, &view)); return view; }
ComPtr<ID3D11RasterizerState> CreateRasterizerState(ID3D11Device* device, std::function<void(D3D11_RASTERIZER_DESC&)> f) { CD3D11_RASTERIZER_DESC desc = CD3D11_RASTERIZER_DESC(CD3D11_DEFAULT()); f(desc); ComPtr<ID3D11RasterizerState> state; AssertHR(device->CreateRasterizerState(&desc, &state)); return state; }
Vector3、Matrix4
動作確認レベルの実装ですので、お好みの行列ライブラリで置き換えてください。
struct Vector3 { float X, Y, Z; Vector3 operator - () const { return{ -X, -Y, -Z }; } Vector3 operator + (const Vector3& v) const { return{ X + v.X, Y + v.Y, Z + v.Z }; } Vector3 operator - (const Vector3& v) const { return{ X - v.X, Y - v.Y, Z - v.Z }; } Vector3 operator * (float s) const { return{ s * X, s * Y, s * Z }; } Vector3 operator / (float s) const { return{ X / s, Y / s, Z / s }; } friend Vector3 operator * (float s, const Vector3& v) { return v * s; } float Length() const { return std::sqrt(X * X + Y * Y + Z * Z); } Vector3 Normalize() const { float length = this->Length(); return{ X / length, Y / length, Z / length }; } float Dot(const Vector3& v) const { return X * v.X + Y * v.Y + Z * v.Z; } Vector3 Cross(const Vector3& v) const { return{ Y * v.Z - Z * v.Y, Z * v.X - X * v.Z, X * v.Y - Y * v.X }; } };
struct Matrix4 { float M00, M01, M02, M03; float M10, M11, M12, M13; float M20, M21, M22, M23; float M30, M31, M32, M33; Matrix4 operator * (const Matrix4& m) const { return{ M00 * m.M00 + M01 * m.M10 + M02 * m.M20 + M03 * m.M30, M00 * m.M01 + M01 * m.M11 + M02 * m.M21 + M03 * m.M31, M00 * m.M02 + M01 * m.M12 + M02 * m.M22 + M03 * m.M32, M00 * m.M03 + M01 * m.M13 + M02 * m.M23 + M03 * m.M33, M10 * m.M00 + M11 * m.M10 + M12 * m.M20 + M13 * m.M30, M10 * m.M01 + M11 * m.M11 + M12 * m.M21 + M13 * m.M31, M10 * m.M02 + M11 * m.M12 + M12 * m.M22 + M13 * m.M32, M10 * m.M03 + M11 * m.M13 + M12 * m.M23 + M13 * m.M33, M20 * m.M00 + M21 * m.M10 + M22 * m.M20 + M23 * m.M30, M20 * m.M01 + M21 * m.M11 + M22 * m.M21 + M23 * m.M31, M20 * m.M02 + M21 * m.M12 + M22 * m.M22 + M23 * m.M32, M20 * m.M03 + M21 * m.M13 + M22 * m.M23 + M23 * m.M33, M30 * m.M00 + M31 * m.M10 + M32 * m.M20 + M33 * m.M30, M30 * m.M01 + M31 * m.M11 + M32 * m.M21 + M33 * m.M31, M30 * m.M02 + M31 * m.M12 + M32 * m.M22 + M33 * m.M32, M30 * m.M03 + M31 * m.M13 + M32 * m.M23 + M33 * m.M33 }; } Vector3 Transform(const Vector3& v) const { float x = M00 * v.X + M01 * v.Y + M02 * v.Z + M03; float y = M10 * v.X + M11 * v.Y + M12 * v.Z + M13; float z = M20 * v.X + M21 * v.Y + M22 * v.Z + M23; float w = M30 * v.X + M31 * v.Y + M32 * v.Z + M33; return Vector3{x, y, z} / w; } Vector3 Transform3x3(const Vector3& v) const { return Vector3{ M00 * v.X + M01 * v.Y + M02 * v.Z, M10 * v.X + M11 * v.Y + M12 * v.Z, M20 * v.X + M21 * v.Y + M22 * v.Z, }; } static Matrix4 Identity() { return{ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }; } static Matrix4 Translate(const Vector3& v) { return{ 1, 0, 0, v.X, 0, 1, 0, v.Y, 0, 0, 1, v.Z, 0, 0, 0, 1 }; } static Matrix4 Rotate(float radian, const Vector3& axis) { float x = axis.X, y = axis.Y, z = axis.Z; float s = std::sin(radian); float c = std::cos(radian); return{ x * x * (1 - c) + c, x * y * (1 - c) - z * s, z * x * (1 - c) + y * s, 0, x * y * (1 - c) + z * s, y * y * (1 - c) + c, y * z * (1 - c) - x * s, 0, z * x * (1 - c) - y * s, y * z * (1 - c) + x * s, z * z * (1 - c) + c, 0, 0, 0, 0, 1 }; } static Matrix4 Scale(float s) { return{ s, 0, 0, 0, 0, s, 0, 0, 0, 0, s, 0, 0, 0, 0, 1 }; } static Matrix4 OrthoD3D(float width, float height, float nearz, float farz) { return{ 2 / width, 0, 0, 0, 0, 2 / height, 0, 0, 0, 0, 1 / (farz - nearz), -nearz / (farz - nearz), 0, 0, 0, 1 }; } static Matrix4 PerspectiveD3D(float fovy, float aspect, float nearz, float farz) { float f = 1 / std::tan(fovy / 2.0f); return{ f / aspect, 0, 0, 0, 0, f, 0, 0, 0, 0, farz / (farz - nearz), -nearz * farz / (farz - nearz), 0, 0, 1, 0 }; } };
WebGL(TypeScript)--- 立方体描画&回転
下記記事にて、WebGLを用いて三角形を描画しました。
WebGL(TypeScript)--- 三角形描画 - 何でもプログラミング
今回は引き続き、立方体を描画&回転を実装してみたいと思います。
上記記事をベースに実装していきますので、解説のない部分は参照をお願いいたします。
Index Buffer作成関数
今回はIndex Bufferを用いて描画を行ってみたいと思います。
ですので、作成関数を準備します。
function createIndexBuffer(gl : WebGLRenderingContext, indices : number[]) : WebGLBuffer { const buffer = gl.createBuffer()!; gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW); return buffer; }
Matrix4クラス
回転を実現するため、最低限の行列クラスを準備します。
回転行列を作成するrotate関数、転置を行うtranspose関数(WebGLは列優先のため)、左上の3x3行列を取り出すgetMatrix3Data関数(法線変換用)を定義しています。
class Matrix4 { constructor(readonly data: number[]) { } transposed() : Matrix4 { return new Matrix4([ this.data[0], this.data[4], this.data[8], this.data[12], this.data[1], this.data[5], this.data[9], this.data[13], this.data[2], this.data[6], this.data[10], this.data[14], this.data[3], this.data[7], this.data[11], this.data[15], ]); } getMatrix3Data() : number[] { return [ this.data[0], this.data[1], this.data[2], this.data[4], this.data[5], this.data[6], this.data[8], this.data[9], this.data[10], ]; } static rotate(degree : number, x : number, y : number, z : number) : Matrix4 { const length = Math.sqrt(x * x + y * y + z * z); x /= length; y /= length; z /= length; const radian = degree * Math.PI / 180.0; const s = Math.sin(radian); const c = Math.cos(radian); return new Matrix4([ x * x * (1 - c) + c, x * y * (1 - c) - z * s, z * x * (1 - c) + y * s, 0, x * y * (1 - c) + z * s, y * y * (1 - c) + c, y * z * (1 - c) - x * s, 0, z * x * (1 - c) - y * s, y * z * (1 - c) + x * s, z * z * (1 - c) + c, 0, 0, 0, 0, 1 ]); } }
main関数
ライティングを行うShaderの作成と、立方体のVertex Buffer&Index Bufferの作成を行い、requestAnimationFrameにて回転させつつ描画を行っています。
実行すると下記のような表示になります。(実際はアニメーションします。)
let gl : WebGLRenderingContext; function main() : void { const canvas = <HTMLCanvasElement>document.getElementById("gl"); gl = canvas.getContext("webgl")!; // Vertex Shader(頂点と法線を変換して次へ) const vertexCode = ` attribute vec4 position; attribute vec3 normal; uniform mat4 matrix; uniform mat3 normalMatrix; varying vec3 v_normal; void main() { gl_Position = matrix * position; v_normal = normalMatrix * normal; } `; // Fragment Shader(ライティングを行って描画) const fragmentCode = ` precision mediump float; varying vec3 v_normal; void main() { vec3 light = vec3(0.0, 0.0, -1.0); float g = dot(light, normalize(v_normal)); g = max(0.0, g); gl_FragColor = vec4(0.0, g, 0.0, 1.0); } `; const program = createProgram(gl, vertexCode, fragmentCode); // 立方体定義(頂点、法線) const vertices = [ 0.5, 0.5, 0.5, 0.0, 0.0, 1.0, -0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 0.5, -0.5, 0.5, 0.0, 0.0, 1.0, -0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 0.5, 0.5, -0.5, 0.0, 0.0, -1.0, -0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 0.5, -0.5, -0.5, 0.0, 0.0, -1.0, -0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 0.5, 0.5, 0.5, 1.0, 0.0, 0.0, 0.5, -0.5, 0.5, 1.0, 0.0, 0.0, 0.5, 0.5, -0.5, 1.0, 0.0, 0.0, 0.5, -0.5, -0.5, 1.0, 0.0, 0.0, -0.5, 0.5, 0.5, -1.0, 0.0, 0.0, -0.5, -0.5, 0.5, -1.0, 0.0, 0.0, -0.5, 0.5, -0.5, -1.0, 0.0, 0.0, -0.5, -0.5, -0.5, -1.0, 0.0, 0.0, 0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 0.5, 0.5, -0.5, 0.0, 1.0, 0.0, -0.5, 0.5, 0.5, 0.0, 1.0, 0.0, -0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 0.5, -0.5, -0.5, 0.0, -1.0, 0.0, -0.5, -0.5, 0.5, 0.0, -1.0, 0.0, -0.5, -0.5, -0.5, 0.0, -1.0, 0.0, ]; const vertexBuffer = createVertexBuffer(gl, vertices); const indices = [ 0, 2, 1, 1, 2, 3, 4, 5, 6, 5, 7, 6, 8, 10, 9, 9, 10, 11, 12, 13, 14, 13, 15, 14, 16, 18, 17, 17, 18, 19, 20, 21, 22, 21, 23, 22, ]; const indexBuffer = createIndexBuffer(gl, indices); // 初期設定 gl.useProgram(program); const positionAttrib = gl.getAttribLocation(program, "position"); const normalAttrib = gl.getAttribLocation(program, "normal"); const matrixUniform = gl.getUniformLocation(program, "matrix")!; const normalMatrixUniform = gl.getUniformLocation(program, "normalMatrix")!; gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.enableVertexAttribArray(positionAttrib); gl.enableVertexAttribArray(normalAttrib); gl.vertexAttribPointer(positionAttrib, 3, gl.FLOAT, false, 24, 0); gl.vertexAttribPointer(normalAttrib, 3, gl.FLOAT, false, 24, 12); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); gl.enable(gl.DEPTH_TEST); gl.enable(gl.CULL_FACE); // デフォルトで反時計回りが表 // 描画関数 let rotation = 0; function step(time : number) : void { rotation++; const mat = Matrix4.rotate(rotation, 1, 1, 1).transposed(); gl.uniformMatrix4fv(matrixUniform, false, mat.data); gl.uniformMatrix3fv(normalMatrixUniform, false, mat.getMatrix3Data()); gl.clearColor(0.8, 0.8, 0.8, 1); gl.clear(gl.COLOR_BUFFER_BIT); gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0); requestAnimationFrame(step); } // 描画スタート requestAnimationFrame(step); } main();
WebGL(TypeScript)--- 三角形描画
今回はWebGLを用いてcanvasに三角形を描画してみたいと思います。
ライブラリを用いず、一から実装してみます。
HTMLの準備
表示のため、下記htmlを用意しました。
<!DOCTYPE html> <html> <body> <canvas id="gl_canvas" width="100" height="100"></canvas> <script src="script.js"></script> </body> </html>
Shader作成関数
Vertex ShaderのコードとFragment Shaderのコードから、WebGLProgramを作成する関数を準備します。
function createProgram(gl : WebGLRenderingContext, vertexCode : string, fragmentCode : string) : WebGLProgram { // Vertex Shader コンパイル const vertexShader = gl.createShader(gl.VERTEX_SHADER)!; compileShader(gl, vertexShader, vertexCode); // Fragment Shader コンパイル const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)!; compileShader(gl, fragmentShader, fragmentCode); // Program作成 const program = gl.createProgram()!; gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); // エラー確認 console.log(gl.getProgramInfoLog(program)); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) throw new Error("program error"); return program; }
上記で利用しているcompileShaderは次のようになります。
function compileShader(gl : WebGLRenderingContext, shader : WebGLShader, code : string) : void { gl.shaderSource(shader, code); gl.compileShader(shader); // エラー確認 console.log(gl.getShaderInfoLog(shader)); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) throw new Error("compile error"); }
VertexBuffer作成関数
頂点データの配列からWebGLBufferを作成する関数を準備します。
function createVertexBuffer(gl : WebGLRenderingContext, vertices : number[]) : WebGLBuffer { const buffer = gl.createBuffer()!; gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); return buffer; }
main関数
三角形を描画する関数は下記の様になります。
canvasにはこのように描画されます。
let gl : WebGLRenderingContext; function main() : void { const canvas = <HTMLCanvasElement>document.getElementById("gl_canvas"); gl = canvas.getContext("webgl")!; const vertexCode = ` attribute vec4 position; void main() { gl_Position = position; } `; const fragmentCode = ` void main() { gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); } `; const program = createProgram(gl, vertexCode, fragmentCode); const vertices = [ 0.0, 1.0, 0.0, -1.0, -1.0, 0.0, 1.0, -1.0, 0.0, ]; const vertexBuffer = createVertexBuffer(gl, vertices); gl.useProgram(program); const positionAttrib = gl.getAttribLocation(program, "position"); gl.enableVertexAttribArray(positionAttrib); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.vertexAttribPointer(positionAttrib, 3, gl.FLOAT, false, 0, 0); gl.clearColor(0.3, 0.3, 0.3, 1); gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(gl.TRIANGLES, 0, 3); } main();
Direct2D --- 画像読み込み
Direct2Dにおいて画像ファイルを読み込んで表示する場合、Windows Imaging Component (WIC)を利用します。
今回は実際にそれを実装してみたいと思います。
画像ファイルを32bitRGBAで読み込み
まずIWICImagingFactoryを作成し、factory経由でデコードを行います。
また32bitRGBAに変換するため、IWICFormatConverterを利用します。
#include <wincodec.h> #include <wrl\client.h> using namespace Microsoft::WRL; ComPtr<IWICBitmapSource> WICBitmapFromFile(wchar_t* path) { ComPtr<IWICImagingFactory> factory; AssertHR(CoCreateInstance( CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&factory) )); ComPtr<IWICBitmapDecoder> decoder; AssertHR(factory->CreateDecoderFromFilename( path, NULL, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &decoder )); ComPtr<IWICBitmapFrameDecode> frame; AssertHR(decoder->GetFrame(0, &frame)); // 32bit RGBAに変換 ComPtr<IWICFormatConverter> converter; AssertHR(factory->CreateFormatConverter(&converter)); AssertHR(converter->Initialize( frame.Get(), GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, NULL, 0.0f, WICBitmapPaletteTypeCustom )); return converter; }
Direct2Dアプリケーションで利用
ID2D1RenderTargetのDrawBitmapを利用して描画します。
ComPtr<ID2D1Bitmap> image = WICBitmapFromFile(L"Parrots.bmp"); renderTarget->DrawBitmap(image.Get(), D2D1::RectF(0, 0, width, height));
ただしWICを利用するに際し、アプリケーションの始めにCoInitializeを呼んでおく必要があります。
AssertHR(CoInitialize(NULL));
...
CoUninitialize();
Direct2Dの基本的な使い方は下記を参照してみてください。
Direct2D導入(ID2D1HwndRenderTarget) - 何でもプログラミング
Direct2D導入(ID2D1DeviceContext) - 何でもプログラミング
SDL導入
画像処理コードの確認の際、C++で簡単にウィンドウを表示したいと思い、SDLを少し使ってみました。
SDLダウンロード
下記ページよりdllをダウンロードします。
Simple DirectMedia Layer - SDL version 2.0.7 (stable)
shared_ptrに変換する関数定義(任意)
SDLはCのライブラリのため、ポインタの開放は手動でやる必要があります。(例:SDL_CreateWindow → SDL_DestroyWindow)
ポインタの管理を楽にするため、shared_ptrに変換する関数を準備します。
shared_ptr作成時に対応するデストラクタを渡すようにしています。
#define DEEFINE_TO_SHARED_PTR(type, destructor) std::shared_ptr<type> ToSharedPtr(type* p) { return std::shared_ptr<type>(p, destructor); } DEEFINE_TO_SHARED_PTR(SDL_Window, SDL_DestroyWindow) DEEFINE_TO_SHARED_PTR(SDL_Renderer, SDL_DestroyRenderer) DEEFINE_TO_SHARED_PTR(SDL_Surface, SDL_FreeSurface) DEEFINE_TO_SHARED_PTR(SDL_Texture, SDL_DestroyTexture)
簡単な実装
画像を読み込んで表示するアプリケーションを実装してみます。
ついでにマウスの位置に正方形も描画してみます。
#include <SDL.h> #pragma comment(lib, "SDL2.lib") #pragma comment(lib, "SDL2main.lib") int main(int, char**) { SDL_assert(SDL_Init(SDL_INIT_VIDEO) == 0); std::shared_ptr<SDL_Window> window = ToSharedPtr(SDL_CreateWindow( "SDL Sample", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, 0 )); SDL_assert(window); std::shared_ptr<SDL_Renderer> renderer = ToSharedPtr(SDL_CreateRenderer(window.get(), -1, 0)); SDL_assert(renderer); std::shared_ptr<SDL_Surface> surface = ToSharedPtr(SDL_LoadBMP("Parrots.bmp")); SDL_assert(surface); std::shared_ptr<SDL_Texture> texture = ToSharedPtr(SDL_CreateTextureFromSurface(renderer.get(), surface.get())); SDL_assert(texture); while (1) { SDL_Event event; if (SDL_PollEvent(&event) == 0) continue; if (event.type == SDL_QUIT) break; else if (event.type == SDL_MOUSEMOTION) { SDL_assert(SDL_RenderClear(renderer.get()) == 0); // 画像描画 SDL_assert(SDL_RenderCopy(renderer.get(), texture.get(), nullptr, nullptr) == 0); // マウスの位置に正方形描画 SDL_Rect rect = { event.motion.x, event.motion.y, 50, 50 }; SDL_assert(SDL_SetRenderDrawColor(renderer.get(), 255, 255, 255, 255) == 0); SDL_assert(SDL_RenderFillRect(renderer.get(), &rect) == 0); SDL_RenderPresent(renderer.get()); } } SDL_Quit(); return 0; }