この記事は、3D数学について学ぶことを目的とした一連の記事の3番目です。各記事は前のレッスンを基にしているので、順番に読むと最も理解しやすいかもしれません。
スケーリングは、平行移動と同じくらい簡単です。
頂点位置に目的のスケールを乗算します。前の例のシェーダーへの変更は次のとおりです。
struct Uniforms { color: vec4f, resolution: vec2f, translation: vec2f, rotation: vec2f, scale: vec2f, }; struct Vertex { @location(0) position: vec2f, }; struct VSOutput { @builtin(position) position: vec4f, }; @group(0) @binding(0) var<uniform> uni: Uniforms; @vertex fn vs(vert: Vertex) -> VSOutput { var vsOut: VSOutput; + // 位置をスケーリングします + let scaledPosition = vert.position * uni.scale; // 位置を回転させます let rotatedPosition = vec2f( - vert.position.x * uni.rotation.y - vert.position.y * uni.rotation.x, - vert.position.x * uni.rotation.x + vert.position.y * uni.rotation.y + scaledPosition.x * uni.rotation.y - scaledPosition.y * uni.rotation.x, + scaledPosition.x * uni.rotation.x + scaledPosition.y * uni.rotation.y ); // 平行移動を追加します let position = rotatedPosition + uni.translation; // 位置をピクセルから0.0から1.0の値に変換します let zeroToOne = position / uni.resolution; // 0 <-> 1から0 <-> 2に変換します let zeroToTwo = zeroToOne * 2.0; // 0 <-> 2から-1 <-> +1(クリップ空間)に変換します let flippedClipSpace = zeroToTwo - 1.0; // Yを反転させます let clipSpace = flippedClipSpace * vec2f(1, -1); vsOut.position = vec4f(clipSpace, 0.0, 1.0); return vsOut; }
そして、以前と同様に、スケール値のためのスペースを確保するためにユニフォームバッファを更新する必要があります。
- // 色、解像度、平行移動、回転、パディング - const uniformBufferSize = (4 + 2 + 2 + 2) * 4 + 8; + // 色、解像度、平行移動、回転、スケール + const uniformBufferSize = (4 + 2 + 2 + 2 + 2) * 4; const uniformBuffer = device.createBuffer({ label: 'uniforms', size: uniformBufferSize, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, }); const uniformValues = new Float32Array(uniformBufferSize / 4); // float32インデックスでのさまざまなユニフォーム値へのオフセット const kColorOffset = 0; const kResolutionOffset = 4; const kTranslationOffset = 6; const kRotationOffset = 8; + const kScaleOffset = 10; const colorValue = uniformValues.subarray(kColorOffset, kColorOffset + 4); const resolutionValue = uniformValues.subarray(kResolutionOffset, kResolutionOffset + 2); const translationValue = uniformValues.subarray(kTranslationOffset, kTranslationOffset + 2); const rotationValue = uniformValues.subarray(kRotationOffset, kRotationOffset + 2); + const scaleValue = uniformValues.subarray(kScaleOffset, kScaleOffset + 2);
そして、レンダリング時にスケールを更新する必要があります。
const settings = { translation: [150, 100], rotation: degToRad(30), + scale: [1, 1], }; const radToDegOptions = { min: -360, max: 360, step: 1, converters: GUI.converters.radToDeg }; const gui = new GUI(); gui.onChange(render); gui.add(settings.translation, '0', 0, 1000).name('translation.x'); gui.add(settings.translation, '1', 0, 1000).name('translation.y'); gui.add(settings, 'rotation', radToDegOptions); + gui.add(settings.scale, '0', -5, 5).name('scale.x'); + gui.add(settings.scale, '1', -5, 5).name('scale.y'); function render() { ... // JavaScript側のFloat32Arrayでユニフォーム値を設定します resolutionValue.set([canvas.width, canvas.height]); translationValue.set(settings.translation); rotationValue.set([ Math.cos(settings.rotation), Math.sin(settings.rotation), ]); + scaleValue.set(settings.scale); // ユニフォーム値をユニフォームバッファにアップロードします device.queue.writeBuffer(uniformBuffer, 0, uniformValues);
そして、スケールができました。スライダーをドラッグしてください。
注意すべき点の1つは、負の値でスケーリングするとジオメトリが反転することです。
もう1つ注意すべき点は、0、0からスケーリングすることです。これは、Fの場合は左上隅です。位置をスケールで乗算しているため、0、0から離れて移動するのは理にかなっています。おそらく、それを修正する方法を想像できるでしょう。たとえば、スケーリングする前に別の平行移動、プレスケール平行移動を追加できます。別の解決策は、実際のFの位置データを変更することです。すぐに別の方法について説明します。
これらの最後の3つの投稿が、平行移動、回転、およびスケールの理解に役立ったことを願っています。次に、行列の魔法について説明します。これら3つすべてをはるかに単純で、多くの場合より便利な形式に組み合わせます。