document.querySelector('canvas') canvas.width = innerWidth canvas.height = innerHeight const gl = canvas.getContext('webgl') // 頂点シェーダー:座標を変換する const vsSource = ` attribute vec3 aPosition; attribute vec3 aNormal; uniform mat4 uModel; uniform mat4 uView; uniform mat4 uProjection; varying vec3 vNormal; void main() { vNormal = mat3(uModel) * aNormal; gl_Position = uProjection * uView * uModel * vec4(aPosition, 1.0); }` // フラグメントシェーダー:画素の色を決める(光の計算) const fsSource = ` precision mediump float; uniform vec3 uColor; uniform vec3 uLightDir; uniform float uAmbient; varying vec3 vNormal; void main() { float diffuse = max(dot(normalize(vNormal), normalize(uLightDir)), 0.0); vec3 color = uColor * (uAmbient + diffuse); gl_FragColor = vec4(color, 1.0); }` function compile(type, source) { const shader = gl.createShader(type) gl.shaderSource(shader, source) gl.compileShader(shader) if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { throw new Error(gl.getShaderInfoLog(shader)) } return shader } const program = gl.createProgram() gl.attachShader(program, compile(gl.VERTEX_SHADER, vsSource)) gl.attachShader(program, compile(gl.FRAGMENT_SHADER, fsSource)) gl.linkProgram(program) if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { throw new Error(gl.getProgramInfoLog(program)) } gl.useProgram(program) // 立方体:6面 × 4頂点。面ごとに法線が違うので頂点を共有できない const s = 0.1 const positions = new Float32Array([ -s, -s, s, s, -s, s, s, s, s, -s, s, s, // 前 -s, -s, -s, -s, s, -s, s, s, -s, s, -s, -s, // 後 -s, s, -s, -s, s, s, s, s, s, s, s, -s, // 上 -s, -s, -s, s, -s, -s, s, -s, s, -s, -s, s, // 下 s, -s, -s, s, s, -s, s, s, s, s, -s, s, // 右 -s, -s, -s, -s, -s, s, -s, s, s, -s, s, -s, // 左 ]) const normals = new Float32Array([ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, ]) const indices = new Uint16Array([ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23, ]) function attribute(name, data) { const buffer = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, buffer) gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW) const location = gl.getAttribLocation(program, name) gl.enableVertexAttribArray(location) gl.vertexAttribPointer(location, 3, gl.FLOAT, false, 0, 0) } attribute('aPosition', positions) attribute('aNormal', normals) const indexBuffer = gl.createBuffer() gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer) gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW) // 行列:カメラの遠近も、位置も自分で計算する function perspective(fovy, aspect, near, far) { const f = 1 / Math.tan(fovy / 2) const nf = 1 / (near - far) return new Float32Array([ f / aspect, 0, 0, 0, 0, f, 0, 0, 0, 0, (far + near) * nf, -1, 0, 0, 2 * far * near * nf, 0, ]) } function translation(x, y, z) { return new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1]) } const uModel = gl.getUniformLocation(program, 'uModel') const uView = gl.getUniformLocation(program, 'uView') const uProjection = gl.getUniformLocation(program, 'uProjection') gl.uniform3fv(gl.getUniformLocation(program, 'uColor'), [0.26, 0.72, 0.51]) gl.uniform3fv(gl.getUniformLocation(program, 'uLightDir'), [5, 8, 5]) gl.uniform1f(gl.getUniformLocation(program, 'uAmbient'), 0.3) gl.uniformMatrix4fv(uView, false, translation(0, -1.2, 0)) gl.uniformMatrix4fv(uProjection, false, perspective(75 * Math.PI / 180, canvas.width / canvas.height, 0.1, 100)) gl.enable(gl.DEPTH_TEST) // 描画ループ:画面を消して、描く function render() { gl.viewport(0, 0, canvas.width, canvas.height) gl.clearColor(0, 0, 0, 1) gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) gl.uniformMatrix4fv(uModel, false, translation(0, 1.2, -1.4)) gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0) requestAnimationFrame(render) } requestAnimationFrame(render) 色分け=パイプラインの段階 36行 頂点を用意する 33行 座標を変換する 15行 色を決める・ライト 17行 シェーダーをGPUへ 14行 canvasの用意・描く 画素に分ける所(ラスタライズ)だけGPUが自動 このコードで描画できるもの localhost:5173 光の当たった緑の箱が1つ浮かぶだけ 31 / 62