🐶
WebGLでフレーミーを描こう!
はじめに
WebGLの勉強がてらフレーミーを描いてみました
フレーミーとはNHK教育のピタゴラスイッチに登場する犬のキャラクターです
骨が好物で掃除が苦手
※ピクシブ百科事典参考
開発環境
Nuxt3 + WebGL API
完成物
ソースコード
pagesファイル
pages/framy.vue
<script setup lang="ts">
import { Ref } from 'vue'
const canvas: Ref<HTMLCanvasElement> = ref()
useFramy(canvas)
</script>
<template>
<div>
<canvas ref="canvas"></canvas>
</div>
</template>
composablesファイル
composables/framy.ts
import { Ref } from 'vue'
import { createProgramFromCode } from './webgl'
import { VSHADER_CODE } from './shader/framy-v-shader'
import { FSHADER_CODE } from './shader/framy-f-shader'
export const useFramy = (canvas: Ref<HTMLCanvasElement>) => {
onMounted(() => {
if (!(canvas.value instanceof HTMLCanvasElement)) {
throw new Error('canvas要素がありません')
}
canvas.value.width = 512
canvas.value.height = 512
const gl = canvas.value.getContext('webgl2')
if (!(gl instanceof WebGL2RenderingContext)) {
throw new Error('WebGLの初期化に失敗しました')
}
// GLSLプログラムをGPUにアップロード
const program = createProgramFromCode(gl, VSHADER_CODE, FSHADER_CODE)
// 作成したプログラムを設定する
gl.useProgram(program)
// 頂点バッファ
const positionBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)
const index = gl.getAttribLocation(program, 'a_position')
const size = 2
const type = gl.FLOAT
const normalized = false
const stride = 0
const offset = 0
gl.vertexAttribPointer(index, size, type, normalized, stride, offset)
gl.enableVertexAttribArray(index)
gl.clearColor(0, 0, .0, .0)
gl.clear(gl.COLOR_BUFFER_BIT)
const oneEight = 0.125
// 四角を描画
const drawRectangle = (pos) => {
pos = pos.map(index => index * oneEight)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(pos), gl.STATIC_DRAW)
gl.drawArrays(gl.LINE_LOOP, 0, pos.length / 2)
}
// 塗りつぶしの四角を描画
const fillRectangle = (pos) => {
pos = pos.map(index => index * oneEight)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(pos), gl.STATIC_DRAW)
gl.drawArrays(gl.TRIANGLES, 0, pos.length / 2)
}
// 点を描画
const drawPoints = (pos) => {
pos = pos.map(index => index * oneEight)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(pos), gl.STATIC_DRAW)
gl.drawArrays(gl.POINTS, 0, 2)
}
// 頭
const headPosition = [
-5 , 5 ,
-1, 5,
-1, 1,
-5, 1
]
drawRectangle(headPosition)
// 体
const bodyPosition = [
-1.5, 2,
6.5, 2,
6.5, -2,
-1.5, -2
]
drawRectangle(bodyPosition)
// 口
const mouthPosition = [
-7, 2.5,
-4, 2.5,
-4, 1,
-7, 1
]
drawRectangle(mouthPosition)
// 右前足
const rightFrontLegPosition = [
-1, -2,
0, -2,
0, -5,
-1, -5
]
drawRectangle(rightFrontLegPosition)
// 左前足
const leftFrontLegPosition = [
0.5, -2,
1.5, -2,
1.5, -5,
0.5, -5
]
drawRectangle(leftFrontLegPosition)
// 右後ろ足
const rightBackLegPosition = [
3.5, -2,
4.5, -2,
4.5, -5,
3.5, -5
]
drawRectangle(rightBackLegPosition)
// 左後ろ足
const leftBackLegPosition = [
5, -2,
6, -2,
6, -5,
5, -5
]
drawRectangle(leftBackLegPosition)
// 右耳
const rightEarPosition = [
-6, 6.5,
-4, 6.5,
-4, 4,
-6, 4
]
drawRectangle(rightEarPosition)
// 左耳
const leftEarPosition = [
-2, 6.5,
.5, 6.5,
.5, 4,
-2, 4
]
drawRectangle(leftEarPosition)
// 尻尾
const tailPosition = [
5.25, 1.75,
7.25, 2.75,
7.75, 2,
5.75, 1
]
drawRectangle(tailPosition)
// 鼻
const nousePosition = [
-7, 2.5,
-6, 2.5,
-7, 2,
-7, 2,
-6, 2.5,
-6, 2,
]
fillRectangle(nousePosition)
// 目
const eyesPosition = [
-4, 3,
-2.5, 3,
]
drawPoints(eyesPosition)
})
}
頂点シェーダー
composables/shader/framy-v-shader.ts
export const VSHADER_CODE = `
attribute vec4 a_position;
void main() {
gl_Position = a_position;
gl_PointSize = 7.0;
}
`
フラグメントシェーダー
composables/shader/framy-f-shader.ts
export const FSHADER_CODE = `
void main() {
gl_FragColor = vec4(.0, .0, .0, 1.);
}
`
WebGL 共通処理
composables/webgl.ts
/**
* createShader
*
* @param gl WebGL コンテキスト
* @param type gl.VERTEX_SAHDER あるいは gl.FRAGMENT_SHADER
* @param source シェーダーのソースコード
*/
const createShader = (gl: WebGL2RenderingContext, type: number, source: string) => {
const shader = gl.createShader(type)
if (shader === null) {
console.error('Faild to create a shader')
return null
}
gl.shaderSource(shader, source)
gl.compileShader(shader)
// check compile result
const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS)
if (!compiled) {
const log = gl.getShaderInfoLog(shader)
console.error('Faild to compile a shader\n' + log)
gl.deleteShader(shader)
return null
}
return shader
}
/**
* createProgram
*
* @param gl WebGL コンテキスト
* @param vshader 頂点シェーダー
* @param fshader フラグメントシェーダー
*/
const createProgram = (gl: WebGL2RenderingContext, vshader: WebGLShader, fshader: WebGLShader) => {
const program = gl.createProgram()
if (!program) {
return null
}
gl.attachShader(program, vshader)
gl.deleteShader(vshader)
gl.attachShader(program, fshader)
gl.deleteShader(fshader)
gl.linkProgram(program)
// check link error
const linked = gl.getProgramParameter(program, gl.LINK_STATUS)
if (!linked) {
const log = gl.getProgramInfoLog(program)
console.error('Faild to link a program\n' + log)
gl.deleteProgram(program)
return null
}
return program
}
/**
* createProgramFromCode
*
* @param gl WebGL コンテキスト
* @param vshaderCode 頂点シェーダーソース
* @param fshaderCode フラグメントシェーダーソース
*/
export const createProgramFromCode = (gl: WebGL2RenderingContext, vshaderCode: string, fshaderCode: string) => {
const vshader = createShader(gl, gl.VERTEX_SHADER, vshaderCode)
if (!vshader) {
console.log('hoge')
return null
}
const fshader = createShader(gl, gl.FRAGMENT_SHADER, fshaderCode)
if (!fshader) {
console.log('hoge2')
gl.deleteShader(vshader)
return null
}
return createProgram(gl, vshader, fshader)
}
おわりに
フレーミーを描くことによってWebGLの2D描画について少し詳しくなった。
ありがとうフレーミー
参考文献
この2つのサイトの記事を読み漁りました
Discussion