diff --git a/sample/wireframe/index.html b/sample/wireframe/index.html new file mode 100644 index 00000000..5ab83229 --- /dev/null +++ b/sample/wireframe/index.html @@ -0,0 +1,30 @@ + + + + + + webgpu-samples: wireframe + + + + + + + + diff --git a/sample/wireframe/main.ts b/sample/wireframe/main.ts new file mode 100644 index 00000000..d65b3029 --- /dev/null +++ b/sample/wireframe/main.ts @@ -0,0 +1,336 @@ +/* eslint-disable prettier/prettier */ +import { mat4, mat3 } from 'wgpu-matrix'; +import { modelData } from './models'; +import solidColorLitWGSL from './solidColorLit.wgsl'; +import wireframeWGSL from './wireframe.wgsl'; + +type TypedArrayView = Float32Array | Uint32Array; + +function createBufferWithData( + device: GPUDevice, + data: TypedArrayView, + usage: number +) { + const buffer = device.createBuffer({ + size: data.byteLength, + usage: usage, + }); + device.queue.writeBuffer(buffer, 0, data); + return buffer; +} + +type Model = { + vertexBuffer: GPUBuffer; + indexBuffer: GPUBuffer; + indexFormat: GPUIndexFormat; + vertexCount: number; +}; + +function createVertexAndIndexBuffer( + device: GPUDevice, + { vertices, indices }: { vertices: Float32Array, indices: Uint32Array }, +): Model { + const vertexBuffer = createBufferWithData( + device, + vertices, + GPUBufferUsage.VERTEX | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST + ); + const indexBuffer = createBufferWithData( + device, + indices, + GPUBufferUsage.INDEX | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST + ); + return { + vertexBuffer, + indexBuffer, + indexFormat: 'uint32', + vertexCount: indices.length, + }; +} + +const adapter = await navigator.gpu.requestAdapter(); +const device = await adapter.requestDevice(); +const canvas = document.querySelector('canvas') as HTMLCanvasElement; +const context = canvas.getContext('webgpu') as GPUCanvasContext; +const devicePixelRatio = window.devicePixelRatio; +canvas.width = canvas.clientWidth * devicePixelRatio; +canvas.height = canvas.clientHeight * devicePixelRatio; +const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); +context.configure({ + device, + format: presentationFormat, + alphaMode: 'premultiplied', +}); +const depthFormat = 'depth24plus'; + + +const models = Object.values(modelData).map(data => createVertexAndIndexBuffer(device, data)); + +function rand(min?: number, max?: number) { + if (min === undefined) { + max = 1; + min = 0; + } else if (max === undefined) { + max = min; + min = 0; + } + return Math.random() * (max - min) + min; +} + +function randInt(min: number, max?: number) { + return Math.floor(rand(min, max)); +} + +function randColor() { + return [rand(), rand(), rand(), 1]; +} + +const litModule = device.createShaderModule({ + code: solidColorLitWGSL, +}); + +const wireframeModule = device.createShaderModule({ + code: wireframeWGSL, +}); + +const litPipeline = device.createRenderPipeline({ + label: 'lit pipeline', + layout: 'auto', + vertex: { + module: litModule, + buffers: [ + { + arrayStride: 6 * 4, // position, normal + attributes: [ + { + // position + shaderLocation: 0, + offset: 0, + format: 'float32x3', + }, + { + // normal + shaderLocation: 1, + offset: 3 * 4, + format: 'float32x3', + }, + ], + }, + ], + }, + fragment: { + module: litModule, + targets: [{ format: presentationFormat }], + }, + primitive: { + cullMode: 'back', + }, + depthStencil: { + depthWriteEnabled: true, + depthCompare: 'less', + format: depthFormat, + }, +}); + +const wireframePipeline = device.createRenderPipeline({ + label: 'wireframe pipeline', + layout: 'auto', + vertex: { + module: wireframeModule, + entryPoint: 'vsIndexedU32', + }, + fragment: { + module: wireframeModule, + targets: [{ format: presentationFormat }], + }, + primitive: { + topology: 'line-list', + }, + depthStencil: { + depthWriteEnabled: true, + depthCompare: 'less-equal', + format: depthFormat, + }, +}); + +type ObjectInfo = { + worldViewProjectionMatrixValue: Float32Array; + worldMatrixValue: Float32Array; + uniformValues: Float32Array; + uniformBuffer: GPUBuffer; + litBindGroup: GPUBindGroup; + wireframeBindGroup: GPUBindGroup; + model: Model; +}; + +const objectInfos: ObjectInfo[] = []; + +const numObjects = 200; +for (let i = 0; i < numObjects; ++i) { + // Make a uniform buffer and type array views + // for our uniforms. + const uniformValues = new Float32Array(16 + 16 + 4); + const uniformBuffer = device.createBuffer({ + size: uniformValues.byteLength, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }); + const kWorldViewProjectionMatrixOffset = 0; + const kWorldMatrixOffset = 16; + const kColorOffset = 32; + const worldViewProjectionMatrixValue = uniformValues.subarray( + kWorldViewProjectionMatrixOffset, + kWorldViewProjectionMatrixOffset + 16); + const worldMatrixValue = uniformValues.subarray( + kWorldMatrixOffset, + kWorldMatrixOffset + 15 + ); + const colorValue = uniformValues.subarray(kColorOffset, kColorOffset + 4); + colorValue.set(randColor()); + + const model = models[randInt(models.length)]; + + // Make a bind group for this uniform + const litBindGroup = device.createBindGroup({ + layout: litPipeline.getBindGroupLayout(0), + entries: [{ binding: 0, resource: { buffer: uniformBuffer } }], + }); + + const strideValues = new Uint32Array(1 + 3); + const strideBuffer = device.createBuffer({ + size: strideValues.byteLength, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }); + strideValues[0] = 6; + device.queue.writeBuffer(strideBuffer, 0, strideValues); + + const wireframeBindGroup = device.createBindGroup({ + layout: wireframePipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: { buffer: uniformBuffer } }, + { binding: 1, resource: { buffer: model.vertexBuffer } }, + { binding: 2, resource: { buffer: model.indexBuffer } }, + { binding: 3, resource: { buffer: strideBuffer } }, + ], + }); + + objectInfos.push({ + worldViewProjectionMatrixValue, + worldMatrixValue, + uniformValues, + uniformBuffer, + litBindGroup, + wireframeBindGroup, + model, + }); +} + +const renderPassDescriptor: GPURenderPassDescriptor = { + label: 'our basic canvas renderPass', + colorAttachments: [ + { + view: undefined, // <- to be filled out when we render + clearValue: [0.3, 0.3, 0.3, 1], + loadOp: 'clear', + storeOp: 'store', + }, + ], + depthStencilAttachment: { + view: undefined, // <- to be filled out when we render + depthClearValue: 1.0, + depthLoadOp: 'clear', + depthStoreOp: 'store', + }, +}; + +let depthTexture; + +function render(time: number) { + time *= 0.001; // convert to seconds; + + // Get the current texture from the canvas context and + // set it as the texture to render to. + const canvasTexture = context.getCurrentTexture(); + renderPassDescriptor.colorAttachments[0].view = canvasTexture.createView(); + + // If we don't have a depth texture OR if its size is different + // from the canvasTexture when make a new depth texture + if ( + !depthTexture || + depthTexture.width !== canvasTexture.width || + depthTexture.height !== canvasTexture.height + ) { + if (depthTexture) { + depthTexture.destroy(); + } + depthTexture = device.createTexture({ + size: [canvasTexture.width, canvasTexture.height], + format: 'depth24plus', + usage: GPUTextureUsage.RENDER_ATTACHMENT, + }); + } + renderPassDescriptor.depthStencilAttachment.view = depthTexture.createView(); + + const fov = (60 * Math.PI) / 180; + const aspect = canvas.clientWidth / canvas.clientHeight; + const projection = mat4.perspective(fov, aspect, 0.1, 1000); + + const view = mat4.lookAt( + [-300, 0, 300], // eye + [0, 0, 0], // target + [0, 1, 0] // up + ); + + const viewProjection = mat4.multiply(projection, view); + + // make a command encoder to start encoding commands + const encoder = device.createCommandEncoder(); + + // make a render pass encoder to encode render specific commands + const pass = encoder.beginRenderPass(renderPassDescriptor); + pass.setPipeline(litPipeline); + + objectInfos.forEach(({ + uniformBuffer, + uniformValues, + worldViewProjectionMatrixValue, + worldMatrixValue, + litBindGroup, + model: { vertexBuffer, indexBuffer, indexFormat, vertexCount }, + }, i) => { + + const world = mat4.identity(); + mat4.translate(world, [0, 0, Math.sin(i * 3.721 + time * 0.1) * 200], world); + mat4.rotateX(world, i * 4.567, world); + mat4.rotateY(world, i * 2.967, world); + mat4.translate(world, [0, 0, Math.sin(i * 9.721 + time * 0.1) * 200], world); + mat4.rotateX(world, time * 0.53 + i, world); + + mat4.multiply(viewProjection, world, worldViewProjectionMatrixValue); + mat3.fromMat4(world, worldMatrixValue); + + // Upload our uniform values. + device.queue.writeBuffer(uniformBuffer, 0, uniformValues); + + pass.setVertexBuffer(0, vertexBuffer); + pass.setIndexBuffer(indexBuffer, indexFormat); + pass.setBindGroup(0, litBindGroup); + pass.drawIndexed(vertexCount); + }); + + objectInfos.forEach(({ + wireframeBindGroup, + model: { vertexCount }, + }) => { + pass.setPipeline(wireframePipeline); + pass.setBindGroup(0, wireframeBindGroup) + pass.draw(vertexCount * 2); + }); + + pass.end(); + + const commandBuffer = encoder.finish(); + device.queue.submit([commandBuffer]); + + requestAnimationFrame(render); +} +requestAnimationFrame(render); diff --git a/sample/wireframe/meta.ts b/sample/wireframe/meta.ts new file mode 100644 index 00000000..8e0cacbd --- /dev/null +++ b/sample/wireframe/meta.ts @@ -0,0 +1,20 @@ +export default { + name: 'Wireframe', + description: ` + This example demonstrates drawing a wireframe from triangles by using the + vertex buffers as storage buffers and then using \`@builtin(vertex_index)\` + to index the vertex data to generate 3 lines per triangle. It uses + array in the vertex shader so it can pull out \`vec3f\` positions + at any valid stride. + `, + filename: __DIRNAME__, + sources: [ + { path: 'main.ts' }, + { path: 'wireframe.wgsl' }, + { path: 'solidColorLit.wgsl' }, + { path: 'models.ts' }, + { path: '../../meshes/box.ts' }, + { path: '../../meshes/mesh.ts' }, + { path: 'utils.ts' }, + ], +}; diff --git a/sample/wireframe/models.ts b/sample/wireframe/models.ts new file mode 100644 index 00000000..4b433e35 --- /dev/null +++ b/sample/wireframe/models.ts @@ -0,0 +1,112 @@ +// Ideally all the models would be the same format +// and we'd determine that format at build time or before +// but, we want to reuse the model data in this repo +// so we'll normalize it here + +import { vec3 } from 'wgpu-matrix'; +import { mesh as teapot } from '../../meshes/teapot'; +import { createSphereMesh } from '../../meshes/sphere'; + +type Mesh = { + positions: [number, number, number][]; + triangles: [number, number, number][]; + normals: [number, number, number][]; +}; + +export function convertMeshToTypedArrays( + mesh: Mesh, + scale: number, + offset = [0, 0, 0] +) { + const { positions, normals, triangles } = mesh; + const scaledPositions = positions.map((p) => + p.map((v, i) => v * scale + offset[i % 3]) + ); + const vertices = new Float32Array(scaledPositions.length * 6); + for (let i = 0; i < scaledPositions.length; ++i) { + vertices.set(scaledPositions[i], 6 * i); + vertices.set(normals[i], 6 * i + 3); + } + const indices = new Uint32Array(triangles.length * 3); + for (let i = 0; i < triangles.length; ++i) { + indices.set(triangles[i], 3 * i); + } + + return { + vertices, + indices, + }; +} + +function createSphereTypedArrays( + radius: number, + widthSegments = 32, + heightSegments = 16, + randomness = 0 +) { + const { vertices: verticesWithUVs, indices } = createSphereMesh( + radius, + widthSegments, + heightSegments, + randomness + ); + const numVertices = verticesWithUVs.length / 8; + const vertices = new Float32Array(numVertices * 6); + for (let i = 0; i < numVertices; ++i) { + const srcNdx = i * 8; + const dstNdx = i * 6; + vertices.set(verticesWithUVs.subarray(srcNdx, srcNdx + 6), dstNdx); + } + return { + vertices, + indices: new Uint32Array(indices), + }; +} + +function flattenNormals({ + vertices, + indices, +}: { + vertices: Float32Array; + indices: Uint32Array; +}) { + const newVertices = new Float32Array(indices.length * 6); + const newIndices = new Uint32Array(indices.length); + for (let i = 0; i < indices.length; i += 3) { + const positions = []; + for (let j = 0; j < 3; ++j) { + const ndx = indices[i + j]; + const srcNdx = ndx * 6; + const dstNdx = (i + j) * 6; + // copy position + const pos = vertices.subarray(srcNdx, srcNdx + 3); + newVertices.set(pos, dstNdx); + positions.push(pos); + newIndices[i + j] = i + j; + } + + const normal = vec3.normalize( + vec3.cross( + vec3.normalize(vec3.subtract(positions[1], positions[0])), + vec3.normalize(vec3.subtract(positions[2], positions[1])) + ) + ); + + for (let j = 0; j < 3; ++j) { + const dstNdx = (i + j) * 6; + newVertices.set(normal, dstNdx + 3); + } + } + + return { + vertices: newVertices, + indices: newIndices, + }; +} + +export const modelData = { + teapot: convertMeshToTypedArrays(teapot, 1.5), + sphere: createSphereTypedArrays(20), + jewel: flattenNormals(createSphereTypedArrays(20, 5, 3)), + rock: flattenNormals(createSphereTypedArrays(20, 32, 16, 0.1)), +}; diff --git a/sample/wireframe/solidColorLit.wgsl b/sample/wireframe/solidColorLit.wgsl new file mode 100644 index 00000000..c29c3f6c --- /dev/null +++ b/sample/wireframe/solidColorLit.wgsl @@ -0,0 +1,30 @@ +struct Uniforms { + worldViewProjectionMatrix: mat4x4f, + worldMatrix: mat4x4f, + color: vec4f, +}; + +struct Vertex { + @location(0) position: vec4f, + @location(1) normal: vec3f, +}; + +struct VSOut { + @builtin(position) position: vec4f, + @location(0) normal: vec3f, +}; + +@group(0) @binding(0) var uni: Uniforms; + +@vertex fn vs(vin: Vertex) -> VSOut { + var vOut: VSOut; + vOut.position = uni.worldViewProjectionMatrix * vin.position; + vOut.normal = (uni.worldMatrix * vec4f(vin.normal, 0)).xyz; + return vOut; +} + +@fragment fn fs(vin: VSOut) -> @location(0) vec4f { + let lightDirection = normalize(vec3f(4, 10, 6)); + let light = dot(normalize(vin.normal), lightDirection) * 0.5 + 0.5; + return vec4f(uni.color.rgb * light, uni.color.a); +} diff --git a/sample/wireframe/utils.ts b/sample/wireframe/utils.ts new file mode 100644 index 00000000..50bdc2f1 --- /dev/null +++ b/sample/wireframe/utils.ts @@ -0,0 +1,205 @@ +type BindGroupBindingLayout = + | GPUBufferBindingLayout + | GPUTextureBindingLayout + | GPUSamplerBindingLayout + | GPUStorageTextureBindingLayout + | GPUExternalTextureBindingLayout; + +export type BindGroupsObjectsAndLayout = { + bindGroups: GPUBindGroup[]; + bindGroupLayout: GPUBindGroupLayout; +}; + +type ResourceTypeName = + | 'buffer' + | 'texture' + | 'sampler' + | 'externalTexture' + | 'storageTexture'; + +/** + * @param {number[]} bindings - The binding value of each resource in the bind group. + * @param {number[]} visibilities - The GPUShaderStage visibility of the resource at the corresponding index. + * @param {ResourceTypeName[]} resourceTypes - The resourceType at the corresponding index. + * @returns {BindGroupsObjectsAndLayout} An object containing an array of bindGroups and the bindGroupLayout they implement. + */ +export const createBindGroupDescriptor = ( + bindings: number[], + visibilities: number[], + resourceTypes: ResourceTypeName[], + resourceLayouts: BindGroupBindingLayout[], + resources: GPUBindingResource[][], + label: string, + device: GPUDevice +): BindGroupsObjectsAndLayout => { + // Create layout of each entry within a bindGroup + const layoutEntries: GPUBindGroupLayoutEntry[] = []; + for (let i = 0; i < bindings.length; i++) { + layoutEntries.push({ + binding: bindings[i], + visibility: visibilities[i % visibilities.length], + [resourceTypes[i]]: resourceLayouts[i], + }); + } + + // Apply entry layouts to bindGroupLayout + const bindGroupLayout = device.createBindGroupLayout({ + label: `${label}.bindGroupLayout`, + entries: layoutEntries, + }); + + // Create bindGroups that conform to the layout + const bindGroups: GPUBindGroup[] = []; + for (let i = 0; i < resources.length; i++) { + const groupEntries: GPUBindGroupEntry[] = []; + for (let j = 0; j < resources[0].length; j++) { + groupEntries.push({ + binding: j, + resource: resources[i][j], + }); + } + const newBindGroup = device.createBindGroup({ + label: `${label}.bindGroup${i}`, + layout: bindGroupLayout, + entries: groupEntries, + }); + bindGroups.push(newBindGroup); + } + + return { + bindGroups, + bindGroupLayout, + }; +}; + +export type ShaderKeyInterface = { + [K in T[number]]: number; +}; + +interface AttribAcc { + attributes: GPUVertexAttribute[]; + arrayStride: number; +} + +/** + * @param {GPUVertexFormat} vf - A valid GPUVertexFormat, representing a per-vertex value that can be passed to the vertex shader. + * @returns {number} The number of bytes present in the value to be passed. + */ +export const convertVertexFormatToBytes = (vf: GPUVertexFormat): number => { + const splitFormat = vf.split('x'); + const bytesPerElement = parseInt(splitFormat[0].replace(/[^0-9]/g, '')) / 8; + + const bytesPerVec = + bytesPerElement * + (splitFormat[1] !== undefined ? parseInt(splitFormat[1]) : 1); + + return bytesPerVec; +}; + +/** Creates a GPUVertexBuffer Layout that maps to an interleaved vertex buffer. + * @param {GPUVertexFormat[]} vertexFormats - An array of valid GPUVertexFormats. + * @returns {GPUVertexBufferLayout} A GPUVertexBufferLayout representing an interleaved vertex buffer. + */ +export const createVBuffer = ( + vertexFormats: GPUVertexFormat[] +): GPUVertexBufferLayout => { + const initialValue: AttribAcc = { attributes: [], arrayStride: 0 }; + + const vertexBuffer = vertexFormats.reduce( + (acc: AttribAcc, curr: GPUVertexFormat, idx: number) => { + const newAttribute: GPUVertexAttribute = { + shaderLocation: idx, + offset: acc.arrayStride, + format: curr, + }; + const nextOffset: number = + acc.arrayStride + convertVertexFormatToBytes(curr); + + const retVal: AttribAcc = { + attributes: [...acc.attributes, newAttribute], + arrayStride: nextOffset, + }; + return retVal; + }, + initialValue + ); + + const layout: GPUVertexBufferLayout = { + arrayStride: vertexBuffer.arrayStride, + attributes: vertexBuffer.attributes, + }; + + return layout; +}; + +export const create3DRenderPipeline = ( + device: GPUDevice, + label: string, + bgLayouts: GPUBindGroupLayout[], + vertexShader: string, + vBufferFormats: GPUVertexFormat[], + fragmentShader: string, + presentationFormat: GPUTextureFormat, + depthTest = false, + topology: GPUPrimitiveTopology = 'triangle-list', + cullMode: GPUCullMode = 'back' +) => { + const pipelineDescriptor: GPURenderPipelineDescriptor = { + label: `${label}.pipeline`, + layout: device.createPipelineLayout({ + label: `${label}.pipelineLayout`, + bindGroupLayouts: bgLayouts, + }), + vertex: { + module: device.createShaderModule({ + label: `${label}.vertexShader`, + code: vertexShader, + }), + buffers: + vBufferFormats.length !== 0 ? [createVBuffer(vBufferFormats)] : [], + }, + fragment: { + module: device.createShaderModule({ + label: `${label}.fragmentShader`, + code: fragmentShader, + }), + targets: [ + { + format: presentationFormat, + }, + ], + }, + primitive: { + topology: topology, + cullMode: cullMode, + }, + }; + if (depthTest) { + pipelineDescriptor.depthStencil = { + depthCompare: 'less', + depthWriteEnabled: true, + format: 'depth24plus', + }; + } + return device.createRenderPipeline(pipelineDescriptor); +}; + +export const createTextureFromImage = ( + device: GPUDevice, + bitmap: ImageBitmap +) => { + const texture: GPUTexture = device.createTexture({ + size: [bitmap.width, bitmap.height, 1], + format: 'rgba8unorm', + usage: + GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.COPY_DST | + GPUTextureUsage.RENDER_ATTACHMENT, + }); + device.queue.copyExternalImageToTexture( + { source: bitmap }, + { texture: texture }, + [bitmap.width, bitmap.height] + ); + return texture; +}; diff --git a/sample/wireframe/wireframe.wgsl b/sample/wireframe/wireframe.wgsl new file mode 100644 index 00000000..64521c55 --- /dev/null +++ b/sample/wireframe/wireframe.wgsl @@ -0,0 +1,74 @@ +struct Uniforms { + worldViewProjectionMatrix: mat4x4f, + worldMatrix: mat4x4f, + color: vec4f, +}; + +struct VSOut { + @builtin(position) position: vec4f, +}; + +@group(0) @binding(0) var uni: Uniforms; +@group(0) @binding(1) var positions: array; +@group(0) @binding(2) var indices: array; +@group(0) @binding(3) var stride: u32; + +@vertex fn vsIndexedU32(@builtin(vertex_index) vNdx: u32) -> VSOut { + // indices make a triangle so for every 3 indices we need to output + // 6 values + let triNdx = vNdx / 6; + // 0 1 0 1 0 1 0 1 0 1 0 1 vNdx % 2 + // 0 0 1 1 2 2 3 3 4 4 5 5 vNdx / 2 + // 0 1 1 2 2 3 3 4 4 5 5 6 vNdx % 2 + vNdx / 2 + // 0 1 1 2 2 0 0 1 1 2 2 0 (vNdx % 2 + vNdx / 2) % 3 + let vertNdx = (vNdx % 2 + vNdx / 2) % 3; + let index = indices[triNdx * 3 + vertNdx]; + let pNdx = index * stride; + let position = vec4f(positions[pNdx], positions[pNdx + 1], positions[pNdx + 2], 1); + + var vOut: VSOut; + vOut.position = uni.worldViewProjectionMatrix * position; + return vOut; +} + +@vertex fn vsIndexedU16(@builtin(vertex_index) vNdx: u32) -> VSOut { + // indices make a triangle so for every 3 indices we need to output + // 6 values + let triNdx = vNdx / 6; + // 0 1 0 1 0 1 0 1 0 1 0 1 vNdx % 2 + // 0 0 1 1 2 2 3 3 4 4 5 5 vNdx / 2 + // 0 1 1 2 2 3 3 4 4 5 5 6 vNdx % 2 + vNdx / 2 + // 0 1 1 2 2 0 0 1 1 2 2 0 (vNdx % 2 + vNdx / 2) % 3 + let vertNdx = (vNdx % 2 + vNdx / 2) % 3; + let indexNdx = triNdx * 3 + vertNdx; + let twoIndices = indices[indexNdx / 2]; // indices is u32 but we want u16 + let index = (twoIndices >> ((indexNdx & 1) * 16)) & 0xFFFF; + let pNdx = index * stride; + let position = vec4f(positions[pNdx], positions[pNdx + 1], positions[pNdx + 2], 1); + + var vOut: VSOut; + vOut.position = uni.worldViewProjectionMatrix * position; + return vOut; +} + +@vertex fn vsUnindexed(@builtin(vertex_index) vNdx: u32) -> VSOut { + // indices make a triangle so for every 3 indices we need to output + // 6 values + let triNdx = vNdx / 6; + // 0 1 0 1 0 1 0 1 0 1 0 1 vNdx % 2 + // 0 0 1 1 2 2 3 3 4 4 5 5 vNdx / 2 + // 0 1 1 2 2 3 3 4 4 5 5 6 vNdx % 2 + vNdx / 2 + // 0 1 1 2 2 0 0 1 1 2 2 0 (vNdx % 2 + vNdx / 2) % 3 + let vertNdx = (vNdx % 2 + vNdx / 2) % 3; + let index = triNdx * 3 + vertNdx; + let pNdx = index * stride; + let position = vec4f(positions[pNdx], positions[pNdx + 1], positions[pNdx + 2], 1); + + var vOut: VSOut; + vOut.position = uni.worldViewProjectionMatrix * position; + return vOut; +} + +@fragment fn fs() -> @location(0) vec4f { + return uni.color + vec4f(0.5); +} diff --git a/src/samples.ts b/src/samples.ts index 378bcf49..45db7cb2 100644 --- a/src/samples.ts +++ b/src/samples.ts @@ -34,6 +34,7 @@ import texturedCube from '../sample/texturedCube/meta'; import twoCubes from '../sample/twoCubes/meta'; import videoUploading from '../sample/videoUploading/meta'; import volumeRenderingTexture3D from '../sample/volumeRenderingTexture3D/meta'; +import wireframe from '../sample/wireframe/meta'; import worker from '../sample/worker/meta'; import workloadSimulator from '../sample/workloadSimulator/meta'; @@ -123,6 +124,7 @@ export const pageCategories: PageCategory[] = [ skinnedMesh, textRenderingMsdf, volumeRenderingTexture3D, + wireframe, }, },