UNPKG

matrix-engine-wgpu

Version:

Networking implemented - based on kurento openvidu server. fix arcball camera,instanced draws added also effect pipeline blend with instancing option.Normalmap added, Fixed shadows casting vs camera/video texture, webGPU powered pwa application. Crazy fas

496 lines (444 loc) 16.8 kB
import {UNLIT_SHADER} from "../shaders/shaders"; import {mat4, vec3} from 'wgpu-matrix'; import {Position, Rotation} from "./matrix-class"; import {createInputHandler} from "./engine"; var SphereLayout = { vertexStride: 8 * 4, positionsOffset: 0, normalOffset: 3 * 4, uvOffset: 6 * 4, }; export default class MECube { constructor(canvas, device, context, o) { this.device = device; this.context = context; this.entityArgPass = o.entityArgPass; this.inputHandler = createInputHandler(window, canvas); this.cameras = o.cameras; console.log('passed : o.mainCameraParams.responseCoef ', o.mainCameraParams.responseCoef) this.mainCameraParams = { type: o.mainCameraParams.type, responseCoef: o.mainCameraParams.responseCoef } // | WASD 'arcball' }; this.lastFrameMS = 0; this.shaderModule = device.createShaderModule({ code: UNLIT_SHADER, }); this.texturesPaths = []; if(typeof o.raycast === 'undefined') { this.raycast = { enabled: false, radius: 2 }; } else { this.raycast = o.raycast; } // useUVShema4x2 pass this from top ! o.texturesPaths.forEach((t) => { this.texturesPaths.push(t) }) this.presentationFormat = navigator.gpu.getPreferredCanvasFormat(); this.position = new Position(o.position.x, o.position.y, o.position.z); console.log('cube added on pos : ', this.position) this.rotation = new Rotation(o.rotation.x, o.rotation.y, o.rotation.z); this.rotation.rotationSpeed.x = o.rotationSpeed.x; this.rotation.rotationSpeed.y = o.rotationSpeed.y; this.rotation.rotationSpeed.z = o.rotationSpeed.z; this.scale = o.scale; this.pipeline = device.createRenderPipeline({ layout: 'auto', vertex: { module: this.shaderModule, entryPoint: 'vertexMain', buffers: [ { arrayStride: SphereLayout.vertexStride, attributes: [ // position {shaderLocation: 0, offset: SphereLayout.positionsOffset, format: 'float32x3'}, // normal {shaderLocation: 1, offset: SphereLayout.normalOffset, format: 'float32x3'}, // uv {shaderLocation: 2, offset: SphereLayout.uvOffset, format: 'float32x2', }, ], }, ], }, fragment: { module: this.shaderModule, entryPoint: 'fragmentMain', targets: [{format: this.presentationFormat, },], }, primitive: { topology: 'triangle-list', // Backface culling since the sphere is solid piece of geometry. // Faces pointing away from the camera will be occluded by faces // pointing toward the camera. cullMode: 'back', }, // Enable depth testing so that the fragment closest to the camera // is rendered in front. depthStencil: { depthWriteEnabled: true, depthCompare: 'less', format: 'depth24plus', }, }); this.depthTexture = device.createTexture({ size: [canvas.width, canvas.height], format: 'depth24plus', usage: GPUTextureUsage.RENDER_ATTACHMENT, }); this.uniformBufferSize = 4 * 16; // 4x4 matrix this.uniformBuffer = device.createBuffer({ size: this.uniformBufferSize, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, }); // Fetch the images and upload them into a GPUTexture. this.texture0 = null; this.moonTexture = null; this.settings = { useRenderBundles: true, asteroidCount: 15, }; this.loadTex0(this.texturesPaths, device).then(() => { this.loadTex1(this.texturesPaths, device).then(() => { this.sampler = device.createSampler({ magFilter: 'linear', minFilter: 'linear', }); this.transform = mat4.create(); mat4.identity(this.transform); this.planet = this.createGeometry({ scale: this.scale, useUVShema4x2: false }); this.planet.bindGroup = this.createSphereBindGroup(this.texture0, this.transform); // can be used like instance draws var asteroids = [ // this.createGeometry(0.2, 8, 6, 0.15), ]; this.renderables = [this.planet]; // this.ensureEnoughAsteroids(asteroids, this.transform); this.renderPassDescriptor = { colorAttachments: [ { view: undefined, clearValue: {r: 0.0, g: 0.0, b: 0.0, a: 1.0}, loadOp: this.entityArgPass.loadOp, storeOp: this.entityArgPass.storeOp, }, ], depthStencilAttachment: { view: this.depthTexture.createView(), depthClearValue: 1.0, depthLoadOp: this.entityArgPass.depthLoadOp, depthStoreOp: this.entityArgPass.depthStoreOp, }, }; const aspect = canvas.width / canvas.height; this.projectionMatrix = mat4.perspective((2 * Math.PI) / 5, aspect, 1, 1000.0); this.modelViewProjectionMatrix = mat4.create(); this.frameBindGroup = device.createBindGroup({ layout: this.pipeline.getBindGroupLayout(0), entries: [ { binding: 0, resource: {buffer: this.uniformBuffer, }, }, ], }); // The render bundle can be encoded once and re-used as many times as needed. // Because it encodes all of the commands needed to render at the GPU level, // those commands will not need to execute the associated JavaScript code upon // execution or be re-validated, which can represent a significant time savings. // // However, because render bundles are immutable once created, they are only // appropriate for rendering content where the same commands will be executed // every time, with the only changes being the contents of the buffers and // textures used. Cases where the executed commands differ from frame-to-frame, // such as when using frustrum or occlusion culling, will not benefit from // using render bundles as much. this.renderBundle; this.updateRenderBundle(); }) }) } ensureEnoughAsteroids(asteroids, transform) { for(let i = this.renderables.length;i <= this.settings.asteroidCount;++i) { // Place copies of the asteroid in a ring. const radius = Math.random() * 1.7 + 1.25; const angle = Math.random() * Math.PI * 2; const x = Math.sin(angle) * radius; const y = (Math.random() - 0.5) * 0.015; const z = Math.cos(angle) * radius; mat4.identity(transform); mat4.translate(transform, [x, y, z], transform); mat4.rotateX(transform, Math.random() * Math.PI, transform); mat4.rotateY(transform, Math.random() * Math.PI, transform); this.renderables.push({ ...asteroids[i % asteroids.length], bindGroup: this.createSphereBindGroup(this.moonTexture, transform), }); } } updateRenderBundle() { console.log('[CUBE] updateRenderBundle') const renderBundleEncoder = this.device.createRenderBundleEncoder({ colorFormats: [this.presentationFormat], depthStencilFormat: 'depth24plus', }); this.renderScene(renderBundleEncoder); this.renderBundle = renderBundleEncoder.finish(); } createGeometry(options) { const mesh = this.createCubeVertices(options); // Create a vertex buffer from the sphere data. const vertices = this.device.createBuffer({ size: mesh.vertices.byteLength, usage: GPUBufferUsage.VERTEX, mappedAtCreation: true, }); new Float32Array(vertices.getMappedRange()).set(mesh.vertices); vertices.unmap(); const indices = this.device.createBuffer({ size: mesh.indices.byteLength, usage: GPUBufferUsage.INDEX, mappedAtCreation: true, }); new Uint16Array(indices.getMappedRange()).set(mesh.indices); indices.unmap(); return { vertices, indices, indexCount: mesh.indices.length, }; } createSphereBindGroup(texture, transform) { const uniformBufferSize = 4 * 16; // 4x4 matrix const uniformBuffer = this.device.createBuffer({ size: uniformBufferSize, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, mappedAtCreation: true, }); new Float32Array(uniformBuffer.getMappedRange()).set(transform); uniformBuffer.unmap(); const bindGroup = this.device.createBindGroup({ layout: this.pipeline.getBindGroupLayout(1), entries: [ { binding: 0, resource: { buffer: uniformBuffer, }, }, { binding: 1, resource: this.sampler, }, { binding: 2, resource: texture.createView(), }, ], }); return bindGroup; } // TEST getViewMatrix() { const camera = this.cameras[this.mainCameraParams.type]; const viewMatrix = camera.update(deltaTime, this.inputHandler()); return viewMatrix; } getTransformationMatrix(pos) { const now = Date.now(); const deltaTime = (now - this.lastFrameMS) / this.mainCameraParams.responseCoef; this.lastFrameMS = now; // const viewMatrix = mat4.identity(); ORI const camera = this.cameras[this.mainCameraParams.type]; const viewMatrix = camera.update(deltaTime, this.inputHandler()); mat4.translate(viewMatrix, vec3.fromValues(pos.x, pos.y, pos.z), viewMatrix); mat4.rotateX(viewMatrix, Math.PI * this.rotation.getRotX(), viewMatrix); mat4.rotateY(viewMatrix, Math.PI * this.rotation.getRotY(), viewMatrix); mat4.rotateZ(viewMatrix, Math.PI * this.rotation.getRotZ(), viewMatrix); mat4.multiply(this.projectionMatrix, viewMatrix, this.modelViewProjectionMatrix); return this.modelViewProjectionMatrix; } async loadTex1(textPath, device) { return new Promise(async (resolve) => { const response = await fetch(textPath[0]); const imageBitmap = await createImageBitmap(await response.blob()); this.moonTexture = device.createTexture({ size: [imageBitmap.width, imageBitmap.height, 1], format: 'rgba8unorm', usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, }); var moonTexture = this.moonTexture device.queue.copyExternalImageToTexture( {source: imageBitmap}, {texture: moonTexture}, [imageBitmap.width, imageBitmap.height] ); resolve() }) } async loadTex0(texturesPaths, device) { return new Promise(async (resolve) => { const response = await fetch(texturesPaths[0]); const imageBitmap = await createImageBitmap(await response.blob()); console.log('WHAT IS THIS ', this) this.texture0 = device.createTexture({ size: [imageBitmap.width, imageBitmap.height, 1], format: 'rgba8unorm', usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, }); var texture0 = this.texture0 device.queue.copyExternalImageToTexture( {source: imageBitmap}, {texture: texture0}, [imageBitmap.width, imageBitmap.height] ); resolve() }) } // Render bundles function as partial, limited render passes, so we can use the // same code both to render the scene normally and to build the render bundle. renderScene(passEncoder) { if(typeof this.renderables === 'undefined') return; passEncoder.setPipeline(this.pipeline); passEncoder.setBindGroup(0, this.frameBindGroup); // Loop through every renderable object and draw them individually. // (Because many of these meshes are repeated, with only the transforms // differing, instancing would be highly effective here. This sample // intentionally avoids using instancing in order to emulate a more complex // scene, which helps demonstrate the potential time savings a render bundle // can provide.) let count = 0; for(const renderable of this.renderables) { passEncoder.setBindGroup(1, renderable.bindGroup); passEncoder.setVertexBuffer(0, renderable.vertices); passEncoder.setIndexBuffer(renderable.indices, 'uint16'); passEncoder.drawIndexed(renderable.indexCount); if(++count > this.settings.asteroidCount) { break; } } } createCubeVertices(options) { if(typeof options === 'undefined') { var options = { scale: 1, useUVShema4x2: false } } if(typeof options.scale === 'undefined') options.scale = 1; let vertices; if(options.useUVShema4x2 == true) { vertices = new Float32Array([ // position | texture coordinate //-------------+---------------------- // front face select the top left image 1, 0.5, -1, 1, 1, 1, 0, 0, 0, 0, -1, -1, 1, 1, 0, 0, 0, 0.5, 1, 1, 1, 1, 0, 0, 0.25, 0, 1, -1, 1, 1, 0, 0, 0.25, 0.5, // right face select the top middle image 1, 1, -1, 1, 0, 0, 0.25, 0, 1, 1, 1, 1, 0, 0, 0.5, 0, 1, -1, -1, 1, 0, 0, 0.25, 0.5, 1, -1, 1, 1, 0, 0, 0.5, 0.5, // back face select to top right image 1, 1, -1, 1, 0, 0, 0.5, 0, 1, -1, -1, 1, 0, 0, 0.5, 0.5, -1, 1, -1, 1, 0, 0, 0.75, 0, -1, -1, -1, 1, 0, 0, 0.75, 0.5, // left face select the bottom left image -1, 1, 1, 1, 0, 0, 0, 0.5, -1, 1, -1, 1, 0, 0, 0.25, 0.5, -1, -1, 1, 1, 0, 0, 0, 1, -1, -1, -1, 1, 0, 0, 0.25, 1, // bottom face select the bottom middle image 1, -1, 1, 1, 0, 0, 0.25, 0.5, -1, -1, 1, 1, 0, 0, 0.5, 0.5, 1, -1, -1, 1, 0, 0, 0.25, 1, -1, -1, -1, 1, 0, 0, 0.5, 1, // top face select the bottom right image -1, 1, 1, 1, 0, 0, 0.5, 0.5, 1, 1, 1, 1, 0, 0, 0.75, 0.5, -1, 1, -1, 1, 0, 0, 0.5, 1, 1, 1, -1, 1, 0, 0, 0.75, 1, ]); } else { vertices = new Float32Array([ // position | texture coordinate //------------- +---------------------- // front face select the top left image 1, 0.5, -1 * options.scale, 1 * options.scale, 1 * options.scale, 1, 0, 0, 0, 0, -1 * options.scale, -1 * options.scale, 1 * options.scale, 1, 0, 0, 0, 1, 1 * options.scale, 1 * options.scale, 1 * options.scale, 1, 0, 0, 1, 0, 1 * options.scale, -1 * options.scale, 1 * options.scale, 1, 0, 0, 1, 1, // right face select the top middle image 1 * options.scale, 1 * options.scale, -1 * options.scale, 1, 0, 0, 0, 0, 1 * options.scale, 1 * options.scale, 1 * options.scale, 1, 0, 0, 0, 1, 1 * options.scale, -1 * options.scale, -1 * options.scale, 1, 0, 0, 1, 0, 1 * options.scale, -1 * options.scale, 1 * options.scale, 1, 0, 0, 1, 1, // back face select to top right image 1 * options.scale, 1 * options.scale, -1 * options.scale, 1, 0, 0, 0, 0, 1 * options.scale, -1 * options.scale, -1 * options.scale, 1, 0, 0, 0, 1, -1 * options.scale, 1 * options.scale, -1 * options.scale, 1, 0, 0, 1, 0, -1 * options.scale, -1 * options.scale, -1 * options.scale, 1, 0, 0, 1, 1, // left face select the bottom left image -1 * options.scale, 1 * options.scale, 1 * options.scale, 1, 0, 0, 0, 0, -1 * options.scale, 1 * options.scale, -1 * options.scale, 1, 0, 0, 0, 1, -1 * options.scale, -1 * options.scale, 1 * options.scale, 1, 0, 0, 1, 0, -1 * options.scale, -1 * options.scale, -1 * options.scale, 1, 0, 0, 1, 1, // bottom face select the bottom middle image 1 * options.scale, -1 * options.scale, 1 * options.scale, 1, 0, 0, 0, 0, -1 * options.scale, -1 * options.scale, 1 * options.scale, 1, 0, 0, 0, 1, 1 * options.scale, -1 * options.scale, -1 * options.scale, 1, 0, 0, 1, 0, -1 * options.scale, -1 * options.scale, -1 * options.scale, 1, 0, 0, 1, 1, // top face select the bottom right image -1 * options.scale, 1 * options.scale, 1 * options.scale, 1, 0, 0, 0, 0, 1 * options.scale, 1 * options.scale, 1 * options.scale, 1, 0, 0, 0, 1, -1 * options.scale, 1 * options.scale, -1 * options.scale, 1, 0, 0, 1, 0, 1 * options.scale, 1 * options.scale, -1 * options.scale, 1, 0, 0, 1, 1, ]) } const indices = new Uint16Array([ 0, 1, 2, 2, 1, 3, // front 4, 5, 6, 6, 5, 7, // right 8, 9, 10, 10, 9, 11, // back 12, 13, 14, 14, 13, 15, // left 16, 17, 18, 18, 17, 19, // bottom 20, 21, 22, 22, 21, 23, // top ]); return { vertices, indices, numVertices: indices.length, }; } draw = () => { if(this.moonTexture == null) { // console.log('not ready') return; } const transformationMatrix = this.getTransformationMatrix(this.position); this.device.queue.writeBuffer( this.uniformBuffer, 0, transformationMatrix.buffer, transformationMatrix.byteOffset, transformationMatrix.byteLength ); this.renderPassDescriptor.colorAttachments[0].view = this.context .getCurrentTexture() .createView(); } }