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

325 lines (287 loc) 9.63 kB
import {mat4, vec3} from 'wgpu-matrix'; import {vertexShadowWGSL} from '../shaders/vertexShadow.wgsl'; import Behavior from './behavior'; import {vertexShadowWGSLInstanced} from '../shaders/instanced/vertexShadow.instanced.wgsl'; /** * @description * Spot light with shodow cast. * @author Nikola Lukic * @email zlatnaspirala@gmail.com */ export class SpotLight { camera; inputHandler; position; target; up; direction; viewMatrix; projectionMatrix; viewProjMatrix; fov; aspect; near; far; innerCutoff; outerCutoff; spotlightUniformBuffer; constructor( camera, inputHandler, device, position = vec3.create(0, 10, -20), target = vec3.create(0, 0, -20), fov = 45, aspect = 1.0, near = 0.1, far = 200) { this.fov = fov; this.aspect = aspect; this.near = near; this.far = far; this.camera = camera; this.inputHandler = inputHandler; this.position = position; this.target = target; this.up = vec3.create(0, 0, -1); this.direction = vec3.normalize(vec3.subtract(target, position)); this.intensity = 1.0; this.color = vec3.create(1.0, 1.0, 1.0); // white this.viewMatrix = mat4.lookAt(position, target, this.up); this.projectionMatrix = mat4.perspective( (this.fov * Math.PI) / 180, this.aspect, this.near, this.far ); this.setProjection = function(fov = (2 * Math.PI) / 5, aspect = 1.0, near = 0.1, far = 200) { this.projectionMatrix = mat4.perspective(fov, aspect, near, far); } this.updateProjection = function() { this.projectionMatrix = mat4.perspective(this.fov, this.aspect, this.near, this.far); } this.device = device; this.viewProjMatrix = mat4.multiply(this.projectionMatrix, this.viewMatrix); this.fov = fov; this.aspect = aspect; this.near = near; this.far = far; this.innerCutoff = Math.cos((Math.PI / 180) * 20.0); this.outerCutoff = Math.cos((Math.PI / 180) * 30.0); this.ambientFactor = 0.5; this.range = 20.0; this.shadowBias = 0.01; this.SHADOW_RES = 1024; this.primitive = { topology: 'triangle-list', cullMode: 'back', // for front interest border drawen shadows ! frontFace: 'ccw' } this.shadowTexture = this.device.createTexture({ label: 'shadowTexture[light]', size: [this.SHADOW_RES, this.SHADOW_RES, 1], format: "depth32float", usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING, }); this.shadowSampler = device.createSampler({ label: 'shadowSampler[light]', compare: 'less', magFilter: 'linear', minFilter: 'linear', }); this.renderPassDescriptor = { label: "renderPassDescriptor shadowPass [per SpotLigth]", colorAttachments: [], depthStencilAttachment: { view: this.shadowTexture.createView(), depthClearValue: 1.0, depthLoadOp: "clear", depthStoreOp: "store", }, } this.uniformBufferBindGroupLayout = this.device.createBindGroupLayout({ label: 'uniformBufferBindGroupLayout in light', entries: [ { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: 'uniform', }, }, ], }); this.shadowBindGroupContainer = []; this.shadowBindGroup = []; this.getShadowBindGroup = (mesh, index) => { if(this.shadowBindGroupContainer[index]) { return this.shadowBindGroupContainer[index]; } this.shadowBindGroupContainer[index] = this.device.createBindGroup({ label: 'sceneBindGroupForShadow in light', layout: this.uniformBufferBindGroupLayout, entries: [ { binding: 0, resource: { buffer: mesh.sceneUniformBuffer, }, }, ], }); return this.shadowBindGroupContainer[index]; } // test this.getShadowBindGroup_bones = (index) => { if(this.shadowBindGroup[index]) { return this.shadowBindGroup[index]; } this.modelUniformBuffer = this.device.createBuffer({ size: 4 * 16, // 4x4 matrix usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, }); this.shadowBindGroup[index] = this.device.createBindGroup({ label: 'model BindGroupForShadow in light', layout: this.uniformBufferBindGroupLayout, entries: [ { binding: 0, resource: { buffer: this.modelUniformBuffer, }, }, ], }); return this.shadowBindGroup[index]; } this.modelBindGroupLayout = this.device.createBindGroupLayout({ label: 'modelBindGroupLayout in light [one bindings]', entries: [ {binding: 0, visibility: GPUShaderStage.VERTEX, buffer: {type: 'uniform'}}, {binding: 1, visibility: GPUShaderStage.VERTEX, buffer: {type: 'uniform', }} ] }); this.modelBindGroupLayoutInstanced = this.device.createBindGroupLayout({ label: 'modelBindGroupLayout in light [for skinned] [instanced]', entries: [ {binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: {type: "read-only-storage"}}, {binding: 1, visibility: GPUShaderStage.VERTEX, buffer: {type: 'uniform'}, }, ], }); this.shadowPipeline = this.device.createRenderPipeline({ label: 'shadowPipeline per light', layout: this.device.createPipelineLayout({ label: 'createPipelineLayout - uniformBufferBindGroupLayout light [regular]', bindGroupLayouts: [ this.uniformBufferBindGroupLayout, this.modelBindGroupLayout, ], }), vertex: { module: this.device.createShaderModule({ code: vertexShadowWGSL, }), buffers: [ { arrayStride: 12, // 3 * 4 bytes (vec3f) attributes: [ { shaderLocation: 0, // must match @location(0) in vertex shader offset: 0, format: "float32x3", }, ], }, ] }, depthStencil: { depthWriteEnabled: true, depthCompare: 'less', format: 'depth32float', }, primitive: this.primitive, }); this.shadowPipelineInstanced = this.device.createRenderPipeline({ label: 'shadowPipeline [instanced] per light', layout: this.device.createPipelineLayout({ label: 'createPipelineLayout - uniformBufferBindGroupLayout light [instanced]', bindGroupLayouts: [ this.uniformBufferBindGroupLayout, this.modelBindGroupLayoutInstanced, ], }), vertex: { module: this.device.createShaderModule({ code: vertexShadowWGSLInstanced, }), buffers: [ { arrayStride: 12, // 3 * 4 bytes (vec3f) attributes: [ { shaderLocation: 0, // must match @location(0) in vertex shader offset: 0, format: "float32x3", }, ], }, ] }, depthStencil: { depthWriteEnabled: true, depthCompare: 'less', format: 'depth32float', }, primitive: this.primitive, }); this.getMainPassBindGroup = function(mesh) { // You can cache it per mesh to avoid recreating each frame if(!this.mainPassBindGroupContainer) this.mainPassBindGroupContainer = []; const index = mesh._lightBindGroupIndex || 0; // assign unique per mesh if needed if(this.mainPassBindGroupContainer[index]) { return this.mainPassBindGroupContainer[index]; } this.mainPassBindGroupContainer[index] = this.device.createBindGroup({ label: `mainPassBindGroup for mesh`, layout: mesh.mainPassBindGroupLayout, // this should match the pipeline entries: [ { binding: 0, // must match @binding in shader for shadow texture resource: this.shadowTexture.createView(), }, { binding: 1, // must match @binding in shader for shadow sampler resource: this.shadowSampler, }, ], }); return this.mainPassBindGroupContainer[index]; } // Only osc values +- this.behavior = new Behavior(); // put here only func this.updater = []; } update() { this.updater.forEach((update) => {update(this)}) this.direction = vec3.normalize(vec3.subtract(this.target, this.position)); const target = vec3.add(this.position, this.direction); this.viewMatrix = mat4.lookAt(this.position, target, this.up); this.viewProjMatrix = mat4.multiply(this.projectionMatrix, this.viewMatrix); } getLightDataBuffer() { const m = this.viewProjMatrix; return new Float32Array([ ...this.position, 0.0, ...this.direction, 0.0, this.innerCutoff, this.outerCutoff, this.intensity, 0.0, ...this.color, 0.0, this.range, this.ambientFactor, this.shadowBias, // <<--- use shadowBias 0.0, // keep padding ...m ]); } }