import { vec3, mat4, quat } from 'https://wgpu-matrix.org/dist/3.x/wgpu-matrix.module.js'; async function main() { const adapter = await navigator.gpu?.requestAdapter(); const device = await adapter?.requestDevice(); if (!device) { fail('need a browser that supports WebGPU'); return; } // Get a WebGPU context from the canvas and configure it const canvas = document.querySelector('canvas'); const context = canvas.getContext('webgpu'); const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); context.configure({ device, format: presentationFormat, }); const module = device.createShaderModule({ code: ` struct Star { ra: f32, dec: f32, d: f32, m: f32 }; struct InOut { @builtin(position) position: vec4f, @location(0) tex: vec2f, }; struct UniformData { mvp: mat4x4f, }; @group(0) @binding(0) var stars: array; @group(0) @binding(1) var uniformData: UniformData; @vertex fn vs( @builtin(vertex_index) vertexIndex : u32 ) -> InOut { // Quad let pos = array( vec2f( -1, -1), vec2f( -1, 1), vec2f( 1, -1), vec2f( 1, 1), vec2f( 1, -1), vec2f( -1, 1) ); var star = stars[vertexIndex/6]; let xyz = vec4f( sin(star.ra/180.0*3.1415) * cos(star.dec/180.0*3.1415), cos(star.ra/180.0*3.1415) * cos(star.dec/180.0*3.1415), sin(star.dec/180.0*3.1415), 1.0 ); let size = 0.15; var inOut: InOut; inOut.position = uniformData.mvp * xyz; inOut.position += vec4f(size*pos[vertexIndex%6].x,size*pos[vertexIndex%6].y,0,0); inOut.tex = pos[vertexIndex%6]; return inOut; } @fragment fn fs(inOut: InOut) -> @location(0) vec4f { let phi = atan2(inOut.tex.y,inOut.tex.x); var r = 1.0-length(inOut.tex); let a = pow(clamp(sin(phi*6.0),0.0,1.0),5.0/r); var f = pow(1.1*r,8.0) + r*a; var ext = 1.0-inOut.position.z; return vec4f(ext,ext,ext,f); } `, }); const pipeline = device.createRenderPipeline({ label: 'split storage buffer pipeline', layout: 'auto', vertex: { module, }, fragment: { module, targets: [ { format: presentationFormat, blend: { color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha' }, alpha: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha' }, }, } ], }, }); const uniformBufferSize = 4*16; // = one 4x4 single precision matrix const uniformBuffer = device.createBuffer({ size: uniformBufferSize, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, }); const uniformValues = new Float32Array(uniformBufferSize / 4); const staticStorageBufferSize = 4 * 4 * 100; const staticStorageBuffer = device.createBuffer({ label: 'static storage for objects', size: staticStorageBufferSize, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, }); const staticStorageValues = new Float32Array([ [217.39232147200883,-62.67607511676666,8.984749,1.3011], [269.44850252543836,4.739420051112412,8.1939745,1.8275], [165.83095967577933,35.948653032660104,6.551172,2.5453], [282.4587890175222,-23.83709744872712,9.126414,2.9762], [53.22829341517546,-9.458168216292322,3.465752,3.2179], [346.5039166796005,-35.8471642082214,6.5220323,3.2877], [176.93768799004127,0.7991199702364985,9.601,3.3731], [316.7484792940004,38.76386244649797,4.766713,3.4904], [316.753662752556,38.75607277205679,5.4506445,3.4947], [280.6830708352289,59.638357907754816,7.854393,3.522], [280.68308624583415,59.635145454818776,8.524704,3.5236], [4.613226257557736,44.02478674398518,7.218567,3.5625], [4.625300681217554,44.02874451664357,9.686646,3.5638], [330.8724078795965,-56.79725466122902,4.322904,3.6481], [111.85462844259372,5.209382701706996,8.576388,3.786], [77.9599373502188,-45.0438126993602,8.063552,3.9346], [1.3832841523481234,-37.36774402806293,7.6824937,4.3458], [264.1040525731971,68.3334976375916,8.02864,4.549], [262.1701639221161,-46.89910489864381,8.336431,4.5522], [343.3241110018443,-14.266689393516607,8.875059,4.665], [152.8329289582321,49.451988897100506,5.9611735,4.8722], [166.34205974427476,43.53094856108456,7.912665,4.9055], [323.3912518776402,-49.01263039681064,7.739947,4.9647], [154.89881370110675,19.869809905451323,8.204091,4.9694], [264.26088738973226,-44.323382243038424,9.602153,5.0035], [271.364453818457,2.495224095912567,3.987364,5.1193], [90.01597644159976,2.7063740799024933,9.901327,5.1996], [176.9394079814786,78.69329745305299,9.551337,5.2528], [206.4405658448607,14.885052704598573,7.6105833,5.4354], [103.70012922713916,33.266408018167255,8.863551,5.5781], [313.1361327596479,-16.974558468206272,10.081701,5.6148], [293.09759805108064,69.65345106254792,4.449041,5.7628], [85.547707594821,12.482359917793618,10.11378,5.7885], [224.37159427348143,-21.423140396261676,5.3640366,5.887], [266.6334444727895,-57.32506180218072,9.592273,5.8922], [357.3066038231343,2.3969179441959985,8.152917,5.9056], [289.2276515036191,5.162976302668586,8.099911,5.9095], [233.0469274248337,-41.28017408697192,8.2741785,5.9146], [12.285227381524425,57.81272812559009,3.3200667,5.9149], [258.83511696459266,-26.60789783205067,4.828591,5.9498], [259.05329410009625,-26.551146094884785,5.889863,5.9525], [258.83412017812816,-26.606806615399403,4.831335,5.9538], [116.1658359308822,3.5504844454883244,9.692206,5.9792], [50.00034361894219,-43.06655252783423,4.063915,6.0327], [302.19503988923026,-66.18709128441725,3.3641381,6.099], [303.4773905559996,-45.16473054247641,7.2426615,6.1659], [218.56843175696892,-12.516923853592898,9.894982,6.2554], [229.85630133329227,-7.722707024157902,9.421787,6.2984], [138.5835616152243,52.68408016691263,6.976026,6.3395], [246.35588833281682,54.30333866560462,9.139769,6.4746], [184.74174734459442,11.126952372027485,11.926109,6.476], [145.69216012443803,-68.87998537875298,11.133258,6.4933], [253.85142032580904,-8.32657626383978,10.444497,6.501], [222.8466426652314,19.101104107947574,6.4300914,6.7397], [165.0156786023955,22.831702302659366,8.977005,6.7458], [222.8480514738455,19.100269640072455,4.4804826,6.7505], [45.462424511757,-16.59453280551054,10.058402,6.8552], [344.1402217312129,16.55216937227734,7.795021,6.8717], [162.7129640681676,6.804489538122279,10.290781,6.9687], [157.22878342623204,0.8377468877660948,8.675959,7.0329], [313.3324563031005,62.150957701503515,7.756715,7.0383], [353.7970694377148,-2.3927993943668993,12.623748,7.1788], [39.07167733324421,6.8780898981735605,10.333128,7.2242], [259.75125274377365,-34.997795092684065,9.386162,7.2425], [12.099107075154986,5.27553944692346,5.467689,7.4303], [344.10194140173917,-31.56626896213439,6.0918646,7.6016], [282.0747587449577,7.690323894810538,12.296879,7.6114], [202.50420163284346,10.372389574126087,8.205292,7.6238], [25.62258568627586,20.26551918225611,4.99968,7.6485], [41.068881780380345,25.52175109435131,9.467294,7.6778], [153.07293731001045,-3.746747553020674,8.331761,7.7076], [90.29510135819429,59.59306278611283,10.417205,7.729], [271.2841138366001,-3.032798001841446,8.519395,7.7298], [270.57119282072466,64.26058361909148,11.758081,7.7782], [115.04626042327716,-42.958948811779244,12.030774,7.9706], [104.4373295461301,-44.29139605439596,10.418207,7.9993], [72.46212402119102,6.9613310085374644,3.0879216,8.016], [104.43647190771412,-44.291651313997995,10.381962,8.0414], [190.18807526858592,-43.563321977327206,10.983478,8.0494], [307.63826397516186,65.45081813440359,9.43527,8.0768], [191.98138882517026,9.749354230882224,10.105141,8.0817], [264.4766236517814,18.596087654354623,8.74288,8.1485], [24.951317415506175,-56.19325138476668,5.5083017,8.1748], [24.950656623757837,-56.19640006625107,5.6254873,8.198], [15.659011875595588,71.67812049454075,8.941875,8.2276], [345.06283531994274,-22.524089212751498,7.259579,8.2322], [188.43142838333532,41.358776694084185,4.094672,8.3934], [88.59482419468164,20.275852148021837,4.241686,8.4903], [245.01042208462846,-37.524610446713915,9.495382,8.5053], [5.0356095474274705,-64.86961713712722,4.07351,8.5803], [343.92147125181526,-75.4633768500233,9.292778,8.5921], [48.353258238212746,4.775200170986658,12.101368,8.6131], [175.34234990922278,-36.40821490897027,11.583742,8.6666], [254.27604768745735,-4.350649323558728,10.913422,8.706], [103.07260095888746,-5.19006973623282,9.097528,8.7448], [103.07278431613713,-5.17372829320013,6.231057,8.7464], [177.77908194992455,35.273139643103185,8.854014,8.757], [259.7726543146931,-46.63575837613547,5.3014293,8.7852], [332.4231515660494,-4.64083481515791,9.237196,8.8094], [303.82866493156223,-27.033780809464673,5.48135,8.8133] ].flat()); device.queue.writeBuffer(staticStorageBuffer, 0, staticStorageValues); const bindGroup = device.createBindGroup({ label: 'bind group for objects', layout: pipeline.getBindGroupLayout(0), entries: [ { binding: 0, resource: { buffer: staticStorageBuffer }}, { binding: 1, resource: { buffer: uniformBuffer }}, ], }); const renderPassDescriptor = { label: 'our basic canvas renderPass', colorAttachments: [ { clearValue: [0.0, 0.0, 0.0, 1], loadOp: 'clear', storeOp: 'store', }, ], }; var width = 100.0; var height = 100.0; var model = mat4.identity(); var view = mat4.translation([0,0,-5]); var projection = mat4.identity(); var time = 0.0; function render() { renderPassDescriptor.colorAttachments[0].view = context.getCurrentTexture().createView(); let rot1 = quat.fromAxisAngle([0,1,0],0); let axis = vec3.normalize([1,1,0]); let rot2 = quat.fromAxisAngle(axis,90); let rot = quat.slerp(rot1, rot2, Math.sin(time)/2+0.5); view = mat4.mul(mat4.translation([0,0,-5]), mat4.fromQuat(rot)); time += 0.01; const fov = 60 * Math.PI / 180; const near = 4; const far = 6; projection = mat4.perspective(fov, width/height, near, far); uniformValues.set(mat4.mul(projection, mat4.mul(view, model))); device.queue.writeBuffer(uniformBuffer, 0, uniformValues); const encoder = device.createCommandEncoder(); const pass = encoder.beginRenderPass(renderPassDescriptor); pass.setPipeline(pipeline); pass.setBindGroup(0, bindGroup); pass.draw(6*100); pass.end(); const commandBuffer = encoder.finish(); device.queue.submit([commandBuffer]); requestAnimationFrame(render); } requestAnimationFrame(render); const observer = new ResizeObserver(entries => { for (const entry of entries) { const canvas = entry.target; width = entry.contentBoxSize[0].inlineSize; height = entry.contentBoxSize[0].blockSize; canvas.width = Math.max(1, Math.min(width, device.limits.maxTextureDimension2D)); canvas.height = Math.max(1, Math.min(height, device.limits.maxTextureDimension2D)); } }); observer.observe(canvas); } function fail(msg) { alert(msg); } main();