From f3936e5ba525e1a30a330a06ed8971b4a482abb7 Mon Sep 17 00:00:00 2001 From: YoanWithY Date: Tue, 24 Sep 2024 21:34:44 +0200 Subject: [PATCH] add shadow mapping --- frontend/src/ts/Material/AbstractPipeline.ts | 6 +- frontend/src/ts/Viewport/Camera.ts | 3 +- frontend/src/ts/Viewport/ViewportCamera.ts | 7 +- .../heightField/HeightFieldPipeline.ts | 3 +- .../heightField/HeightFieldR3.ts | 46 ++++--- .../HeightFieldSelectionPipeline.ts | 2 +- .../heightField/HeightFieldShadowPipeline.ts | 61 ++++++++++ .../heightField/heightField.wgsl | 35 +++++- frontend/src/ts/lights/SunLight.ts | 115 ++++++++++++++++++ frontend/src/ts/project/Project.ts | 7 +- frontend/src/ts/project/R3Object.ts | 5 +- frontend/src/ts/project/Scene.ts | 2 + .../ts/projection/OrthographicProjection.ts | 11 +- frontend/src/ts/projection/Projection.ts | 2 + frontend/src/ts/rendering/Renderer.ts | 44 +++++++ frontend/src/ts/rendering/ViewportCache.ts | 14 +-- frontend/src/ts/ui/panes/ViewportPane.ts | 2 +- 17 files changed, 323 insertions(+), 42 deletions(-) create mode 100644 frontend/src/ts/dynamicObject/heightField/HeightFieldShadowPipeline.ts create mode 100644 frontend/src/ts/lights/SunLight.ts diff --git a/frontend/src/ts/Material/AbstractPipeline.ts b/frontend/src/ts/Material/AbstractPipeline.ts index 09a9989..c1820a7 100644 --- a/frontend/src/ts/Material/AbstractPipeline.ts +++ b/frontend/src/ts/Material/AbstractPipeline.ts @@ -51,7 +51,7 @@ export abstract class AbstractPipeline { }; abstract readonly vertexEntryPoint: string; - abstract readonly fragmentEntryPoint: string; + abstract readonly fragmentEntryPoint?: string; constructor(label: string) { this.label = label; @@ -68,7 +68,9 @@ export abstract class AbstractPipeline { }; } - protected createFragmentState(): GPUFragmentState { + protected createFragmentState(): GPUFragmentState | undefined { + if (!this.fragmentEntryPoint) + return undefined; return { module: this.shaderModule, entryPoint: this.fragmentEntryPoint, diff --git a/frontend/src/ts/Viewport/Camera.ts b/frontend/src/ts/Viewport/Camera.ts index ee109c1..9b020b4 100644 --- a/frontend/src/ts/Viewport/Camera.ts +++ b/frontend/src/ts/Viewport/Camera.ts @@ -1,5 +1,6 @@ import mat4 from "../math/mat4"; import vec3 from "../math/vec3"; +import Projection from "../projection/Projection"; import TransformationStack from "../transformation/TransformationStack"; /** @@ -7,7 +8,7 @@ import TransformationStack from "../transformation/TransformationStack"; */ export default interface Camera { transformationStack: TransformationStack; - getProjectionMatrix(width: number, height: number): mat4; + getProjection(): Projection; getViewMatrix(): mat4; getWorldLocation(): vec3; } \ No newline at end of file diff --git a/frontend/src/ts/Viewport/ViewportCamera.ts b/frontend/src/ts/Viewport/ViewportCamera.ts index 4d8b8f5..2edcdca 100644 --- a/frontend/src/ts/Viewport/ViewportCamera.ts +++ b/frontend/src/ts/Viewport/ViewportCamera.ts @@ -1,6 +1,7 @@ import mat4 from "../math/mat4"; import vec3 from "../math/vec3"; import PerspectiveProjection from "../projection/PerspectiveProjection"; +import Projection from "../projection/Projection"; import TransformationStack from "../transformation/TransformationStack"; import Camera from "./Camera"; @@ -11,14 +12,14 @@ export default class ViewportCamera implements Camera { projection = new PerspectiveProjection(); transformationStack = new TransformationStack(); - getProjectionMatrix(width: number, height: number): mat4 { - return this.projection.getProjectionMatrix(width, height); - } getViewMatrix(): mat4 { return this.transformationStack.getInverseTransformationMatrix(); } getWorldLocation(): vec3 { return this.transformationStack.getTransformationMatrix().getTranslation(); } + getProjection(): Projection { + return this.projection; + } } \ No newline at end of file diff --git a/frontend/src/ts/dynamicObject/heightField/HeightFieldPipeline.ts b/frontend/src/ts/dynamicObject/heightField/HeightFieldPipeline.ts index 1f293f8..d2ee5be 100644 --- a/frontend/src/ts/dynamicObject/heightField/HeightFieldPipeline.ts +++ b/frontend/src/ts/dynamicObject/heightField/HeightFieldPipeline.ts @@ -4,6 +4,7 @@ import staticShaderCode from "./heightField.wgsl?raw"; import { resolveShader } from "../../rendering/Shader"; import Renderer from "../../rendering/Renderer"; import { Project } from "../../project/Project"; +import { sunBindGroupLayout } from "../../lights/SunLight"; export const heightFieldDataLayout = gpuDevice.createBindGroupLayout( { @@ -108,7 +109,7 @@ export class HeightFieldPipeline extends AbstractPipeline { const renderer = this.project.renderer; return gpuDevice.createPipelineLayout({ label: "Height field pipeline layout", - bindGroupLayouts: [renderer.bindGroup0Layout, renderer.bindGroupR3Layout, heightFieldDataLayout], + bindGroupLayouts: [renderer.bindGroup0Layout, renderer.bindGroupR3Layout, heightFieldDataLayout, sunBindGroupLayout], }); } diff --git a/frontend/src/ts/dynamicObject/heightField/HeightFieldR3.ts b/frontend/src/ts/dynamicObject/heightField/HeightFieldR3.ts index d4bbd89..71f385e 100644 --- a/frontend/src/ts/dynamicObject/heightField/HeightFieldR3.ts +++ b/frontend/src/ts/dynamicObject/heightField/HeightFieldR3.ts @@ -5,10 +5,10 @@ import { HeightFieldSelectionPipeline } from "./HeightFieldSelectionPipeline"; import { gpuDevice } from "../../GPUX"; import { resolveShader } from "../../rendering/Shader"; import heightFieldComputeCode from "./heightFieldCompute.wgsl?raw"; +import { HeightFieldShadowPipeline as HeightFieldDepthPipeline } from "./HeightFieldShadowPipeline"; export default class HeightFieldR3 extends R3Object { pipeline: HeightFieldPipeline; - selectionPipeline: HeightFieldSelectionPipeline; computePipeline: GPUComputePipeline; private _xVerts: number; private _yVerts: number; @@ -29,7 +29,6 @@ export default class HeightFieldR3 extends R3Object { usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING }); this.pipeline = new HeightFieldPipeline(project, "Height Field Pipeline"); - this.selectionPipeline = new HeightFieldSelectionPipeline(project, "Height Field Selection Pieline"); this.dataBuffer = gpuDevice.createBuffer({ label: "height field data buffer", size: 10 * 4, @@ -87,15 +86,17 @@ export default class HeightFieldR3 extends R3Object { } ] }); - const computeModule = gpuDevice.createShaderModule({ - label: "height field compute shader module", - code: resolveShader(heightFieldComputeCode, { heightCode: heightFunction }), - compilationHints: [{ entryPoint: "computeHeight" }], - }) - const computePipelineLayout = gpuDevice.createPipelineLayout({ - label: "height field compute pipeline layout", - bindGroupLayouts: [project.renderer.bindGroup0Layout, heightFieldComputeBindGroupLayout], - }) + const computeModule = gpuDevice.createShaderModule( + { + label: "height field compute shader module", + code: resolveShader(heightFieldComputeCode, { heightCode: heightFunction }), + compilationHints: [{ entryPoint: "computeHeight" }], + }) + const computePipelineLayout = gpuDevice.createPipelineLayout( + { + label: "height field compute pipeline layout", + bindGroupLayouts: [project.renderer.bindGroup0Layout, heightFieldComputeBindGroupLayout], + }) this.computePipeline = gpuDevice.createComputePipeline( { label: "Height field compute pipeline", @@ -106,20 +107,31 @@ export default class HeightFieldR3 extends R3Object { }); } + static selectionPipeline: HeightFieldSelectionPipeline; + static depthPipeline: HeightFieldDepthPipeline; + static init(project: Project) { + this.selectionPipeline = new HeightFieldSelectionPipeline(project, "height field selection pipeline"); + this.depthPipeline = new HeightFieldDepthPipeline(project, "height field depth pipeline"); + } + getVerts(): number { return this._xVerts * this._yVerts + this._xVerts * (this._yVerts - 2) + 2 * this._yVerts - 2; } render(renderPassEncoder: GPURenderPassEncoder): void { - renderPassEncoder.setPipeline(this.pipeline.gpuPipeline); - this.updateUniforms(); - renderPassEncoder.setBindGroup(1, this.defaultBindGroup); - renderPassEncoder.setBindGroup(2, this.dataBindGroup); - renderPassEncoder.draw(this.getVerts()); + this.renderWithPipeline(renderPassEncoder, this.pipeline.gpuPipeline); } renderSelection(renderPassEncoder: GPURenderPassEncoder): void { - renderPassEncoder.setPipeline(this.selectionPipeline.gpuPipeline); + this.renderWithPipeline(renderPassEncoder, HeightFieldR3.selectionPipeline.gpuPipeline); + } + + renderDepth(renderPassEncoder: GPURenderPassEncoder): void { + this.renderWithPipeline(renderPassEncoder, HeightFieldR3.depthPipeline.gpuPipeline); + } + + private renderWithPipeline(renderPassEncoder: GPURenderPassEncoder, pipeline: GPURenderPipeline) { + renderPassEncoder.setPipeline(pipeline); this.updateUniforms(); renderPassEncoder.setBindGroup(1, this.defaultBindGroup); renderPassEncoder.setBindGroup(2, this.dataBindGroup); diff --git a/frontend/src/ts/dynamicObject/heightField/HeightFieldSelectionPipeline.ts b/frontend/src/ts/dynamicObject/heightField/HeightFieldSelectionPipeline.ts index fd18c27..a6c4a18 100644 --- a/frontend/src/ts/dynamicObject/heightField/HeightFieldSelectionPipeline.ts +++ b/frontend/src/ts/dynamicObject/heightField/HeightFieldSelectionPipeline.ts @@ -56,7 +56,7 @@ export class HeightFieldSelectionPipeline extends AbstractPipeline { createPipelineLayout(): GPUPipelineLayout | "auto" { const renderer = this.project.renderer; return gpuDevice.createPipelineLayout({ - label: "Height field geometry pipeline layout", + label: "Height field selection pipeline layout", bindGroupLayouts: [renderer.bindGroup0Layout, renderer.bindGroupR3Layout, heightFieldDataLayout], }); } diff --git a/frontend/src/ts/dynamicObject/heightField/HeightFieldShadowPipeline.ts b/frontend/src/ts/dynamicObject/heightField/HeightFieldShadowPipeline.ts new file mode 100644 index 0000000..349ec78 --- /dev/null +++ b/frontend/src/ts/dynamicObject/heightField/HeightFieldShadowPipeline.ts @@ -0,0 +1,61 @@ +import { gpuDevice } from "../../GPUX"; +import { AbstractPipeline, vertexGeometryEntryPoint } from "../../Material/AbstractPipeline"; +import staticShaderCode from "./heightField.wgsl?raw"; +import { resolveShader } from "../../rendering/Shader"; +import { Project } from "../../project/Project"; +import { heightFieldDataLayout } from "./HeightFieldPipeline"; + +export class HeightFieldShadowPipeline extends AbstractPipeline { + vertexEntryPoint = vertexGeometryEntryPoint; + fragmentEntryPoint = undefined; + gpuPipeline: GPURenderPipeline; + readonly isDisplayOutputPipeline = false; + readonly shaderCode: string; + readonly preProzessedShaderCoder; + readonly shaderModule: GPUShaderModule; + vertexConstants: Record; + vertexBufferLayout: GPUVertexBufferLayout[]; + fragmentConstants: Record; + fragmentTargets: GPUColorTargetState[]; + topology: GPUPrimitiveTopology; + cullMode: GPUCullMode; + stripIndexFormat?: GPUIndexFormat; + depthStencilFormat: GPUTextureFormat; + depthCompare: GPUCompareFunction; + depthWriteEnabled: boolean; + project: Project; + + constructor(project: Project, label: string) { + super(label); + this.project = project; + this.shaderCode = staticShaderCode; + this.preProzessedShaderCoder = resolveShader(this.shaderCode); + this.vertexConstants = {}; + this.vertexBufferLayout = []; + this.fragmentConstants = {}; + this.topology = "triangle-strip"; + this.cullMode = "none"; + this.depthCompare = "less"; + this.depthWriteEnabled = true; + this.depthStencilFormat = "depth24plus"; + this.fragmentTargets = []; + + this.shaderModule = gpuDevice.createShaderModule( + { + label: `${label} shader module`, + code: this.preProzessedShaderCoder, + compilationHints: [ + { entryPoint: vertexGeometryEntryPoint }, + ] + }); + this.gpuPipeline = this.buildPipeline(); + } + + createPipelineLayout(): GPUPipelineLayout | "auto" { + const renderer = this.project.renderer; + return gpuDevice.createPipelineLayout({ + label: "Height field shadow pipeline layout", + bindGroupLayouts: [renderer.bindGroup0Layout, renderer.bindGroupR3Layout, heightFieldDataLayout], + }); + } +} \ No newline at end of file diff --git a/frontend/src/ts/dynamicObject/heightField/heightField.wgsl b/frontend/src/ts/dynamicObject/heightField/heightField.wgsl index 60028e6..d3783f4 100644 --- a/frontend/src/ts/dynamicObject/heightField/heightField.wgsl +++ b/frontend/src/ts/dynamicObject/heightField/heightField.wgsl @@ -1,7 +1,8 @@ struct VertexOut { @builtin(position) position: vec4f, @location(0) ls_pos: vec3f, - @location(1) normal: vec3f + @location(1) normal: vec3f, + @location(2) ws_pos: vec3f, } #include @@ -69,6 +70,7 @@ fn vertex_main(@builtin(vertex_index) index: u32) -> VertexOut { vertexUniform.transformation[2].xyz ); output.normal = normMat * data.xyz; + output.ws_pos = ws_pos; return output; } @@ -89,11 +91,38 @@ fn steps(v: vec3f, stepSize: f32) -> vec3f { return floor(v / stepSize) * stepSize; } +struct Sun { + matrix: mat4x4f, + light: vec4f +} + @group(3) @binding(0) var sun: Sun; + @group(3) @binding(1) var shadowMap: texture_depth_2d; + @group(3) @binding(2) var shadowSampler: sampler_comparison; +fn getShadow(ws_pos: vec3f) -> f32 { + var shadowNDC = (sun.matrix * vec4f(ws_pos, 1.0)); + var shadowUV = shadowNDC.xy * vec2f(0.5, -0.5) + 0.5; + var shadow = 0.0; + for(var y = -1; y<=1; y++) { + for(var x = -1; x<=1; x++) { + shadow += textureSampleCompare(shadowMap, shadowSampler, shadowUV + vec2f(f32(x), f32(y)) / 4096, shadowNDC.z - 0.001); + } + } + if(any(shadowUV < vec2f(0)) || any(shadowUV > vec2f(1))) { + return 1; + } + return shadow / 9; +} + + @fragment fn fragment_main(@builtin(front_facing) front_facing: bool, vertexData: VertexOut) -> R3FragmentOutput { let n = normalize(vertexData.normal) * select(-1.0, 1.0, front_facing); - let color = vec3(clamp(dot(n, normalize(vec3(1,1,1))), 0.0, 1.0)); - let outColor = vec4f(createOutputFragment(color), 1); + let l = sun.light.xyz; + let strength = sun.light.w; + var light = vec3(clamp(dot(n, l), 0.0, 1.0)) * strength; + light *= getShadow(vertexData.ws_pos); + let color = vec3(cos(vertexData.ws_pos) *0.5 + 0.5); + let outColor = vec4f(createOutputFragment(light * color), 1); return R3FragmentOutput(outColor, fragmentUniform.id); } diff --git a/frontend/src/ts/lights/SunLight.ts b/frontend/src/ts/lights/SunLight.ts new file mode 100644 index 0000000..8adc97d --- /dev/null +++ b/frontend/src/ts/lights/SunLight.ts @@ -0,0 +1,115 @@ +import { gpuDevice } from "../GPUX"; +import mat4 from "../math/mat4"; +import { Project } from "../project/Project"; +import R3Object from "../project/R3Object"; +import OrthographicProjection from "../projection/OrthographicProjection"; +import Projection from "../projection/Projection"; +import Camera from "../Viewport/Camera"; + +export const sunBindGroupLayout = gpuDevice.createBindGroupLayout({ + label: "sun bind group layout", + entries: [ + { + binding: 0, + visibility: GPUShaderStage.FRAGMENT, + buffer: { type: "uniform" } + }, + { + binding: 1, + visibility: GPUShaderStage.FRAGMENT, + texture: { + multisampled: false, + sampleType: "depth", + viewDimension: "2d" + } + }, + { + binding: 2, + visibility: GPUShaderStage.FRAGMENT, + sampler: { + type: "comparison" + } + }, + ] +}); + +export class SunLight extends R3Object implements Camera { + projection: OrthographicProjection + shadowMap: GPUTexture; + renderPass: GPURenderPassDescriptor; + resolution: number; + bindGroup: GPUBindGroup; + buffer: GPUBuffer; + constructor(project: Project, rangeWidth = 1000, distance = 1000, resolution = 4096) { + super(project); + this.projection = new OrthographicProjection(rangeWidth, 0, distance); + this.resolution = resolution; + this.shadowMap = gpuDevice.createTexture({ + label: "Sun Light shadow map", + format: "depth24plus", + size: [resolution, resolution, 1], + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING + }); + this.renderPass = { + label: "sun shadow render pass descriptor", + colorAttachments: [], + depthStencilAttachment: + { + view: this.shadowMap.createView(), + depthClearValue: 1.0, + depthLoadOp: "clear", + depthStoreOp: "store", + } + }; + this.buffer = gpuDevice.createBuffer({ + label: "sun buffer", + size: 20 * 4, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM + }) + this.bindGroup = gpuDevice.createBindGroup( + { + label: "sun bind group", + layout: sunBindGroupLayout, + entries: [ + { + binding: 0, + resource: { buffer: this.buffer } + }, + { + binding: 1, + resource: this.shadowMap.createView() + }, + { + binding: 2, + resource: gpuDevice.createSampler( + { + compare: "less", + magFilter: "linear" + }) + } + ] + }); + } + getProjection(): Projection { + return this.projection; + } + getViewMatrix(): mat4 { + return this.transformationStack.getInverseTransformationMatrix(); + } + render(renderPassEncoder: GPURenderPassEncoder): void { + renderPassEncoder; + } + renderSelection(renderPassEncoder: GPURenderPassEncoder): void { + renderPassEncoder; + } + renderDepth(renderPassEncoder: GPURenderPassEncoder): void { + renderPassEncoder; + } + floatData = new Float32Array(20); + updateSunUniforms() { + const mat = this.transformationStack.getTransformationMatrix(); + this.projection.getProjectionMatrix(this.resolution, this.resolution).mult(this.getViewMatrix()).pushInFloat32ArrayColumnMajor(this.floatData); + this.floatData.set([mat[2], mat[6], mat[10], 1], 16); + gpuDevice.queue.writeBuffer(this.buffer, 0, this.floatData); + } +} \ No newline at end of file diff --git a/frontend/src/ts/project/Project.ts b/frontend/src/ts/project/Project.ts index 743dc61..bd9a890 100644 --- a/frontend/src/ts/project/Project.ts +++ b/frontend/src/ts/project/Project.ts @@ -7,6 +7,7 @@ import { ViewportPane } from "../ui/panes/ViewportPane"; import { gpu } from "../GPUX"; import { GridPipeline } from "../debug/GridPipeline"; import HeightFieldR3 from "../dynamicObject/heightField/HeightFieldR3"; +import { SunLight } from "../lights/SunLight"; export class Project { config: ProjectConfig; @@ -48,7 +49,11 @@ export class Project { this.scene.heightFieldObjects.add(h); } } - + HeightFieldR3.init(this); + const s = new SunLight(this); + s.transformationStack.translate.setValues(500, 0, 500); + s.transformationStack.rotate.setValues(1.2, 0, 1); + this.scene.sunlights.add(s); this.scene.gridPipeline = new GridPipeline(this); } diff --git a/frontend/src/ts/project/R3Object.ts b/frontend/src/ts/project/R3Object.ts index 230bbb0..33ff7ad 100644 --- a/frontend/src/ts/project/R3Object.ts +++ b/frontend/src/ts/project/R3Object.ts @@ -31,9 +31,9 @@ export default abstract class R3Object { fragmentUniformData = new Uint32Array(2); updateUniforms() { this.transformationStack.getTransformationMatrix().pushInFloat32ArrayColumnMajor(this.vertexUniformData); - + this.fragmentUniformData[0] = this._id; - this.fragmentUniformData[1] = this.isActive ? 2 : (this.isSelected ? 1 : 0); + this.fragmentUniformData[1] = this.isActive ? 2 : (this.isSelected ? 1 : 0); gpuDevice.queue.writeBuffer(this.vertexUniformBuffer, 0, this.vertexUniformData); gpuDevice.queue.writeBuffer(this.fragmentUniformBuffer, 0, this.fragmentUniformData); } @@ -44,4 +44,5 @@ export default abstract class R3Object { abstract render(renderPassEncoder: GPURenderPassEncoder): void; abstract renderSelection(renderPassEncoder: GPURenderPassEncoder): void; + abstract renderDepth(renderPassEncoder: GPURenderPassEncoder): void; } diff --git a/frontend/src/ts/project/Scene.ts b/frontend/src/ts/project/Scene.ts index 966d104..0867a55 100644 --- a/frontend/src/ts/project/Scene.ts +++ b/frontend/src/ts/project/Scene.ts @@ -1,7 +1,9 @@ import { GridPipeline } from "../debug/GridPipeline"; import HeightFieldR3 from "../dynamicObject/heightField/HeightFieldR3"; +import { SunLight } from "../lights/SunLight"; export default class Scene { heightFieldObjects = new Set; + sunlights = new Set; gridPipeline?: GridPipeline; } \ No newline at end of file diff --git a/frontend/src/ts/projection/OrthographicProjection.ts b/frontend/src/ts/projection/OrthographicProjection.ts index 5081995..283b487 100644 --- a/frontend/src/ts/projection/OrthographicProjection.ts +++ b/frontend/src/ts/projection/OrthographicProjection.ts @@ -2,9 +2,14 @@ import mat4 from "../math/mat4"; import Projection from "./Projection"; export default class OrthographicProjection implements Projection { - height = 100; - near = 0.0; - far = 10000; + height: number; + near: number; + far: number; + constructor(height: number = 100, near: number = 0.0, far: number = 1000) { + this.height = height; + this.near = near; + this.far = far; + } getProjectionMatrix(width: number, height: number): mat4 { const AR = width / height; const t = this.height / 2; diff --git a/frontend/src/ts/projection/Projection.ts b/frontend/src/ts/projection/Projection.ts index 19234bf..af66c82 100644 --- a/frontend/src/ts/projection/Projection.ts +++ b/frontend/src/ts/projection/Projection.ts @@ -5,6 +5,8 @@ export const scene = new Scene(); export default interface Projection { + near: number; + far: number; /** * Returns a projection matrix. * @param width the width of the target viewport diff --git a/frontend/src/ts/rendering/Renderer.ts b/frontend/src/ts/rendering/Renderer.ts index f4fe2df..11c6202 100644 --- a/frontend/src/ts/rendering/Renderer.ts +++ b/frontend/src/ts/rendering/Renderer.ts @@ -6,6 +6,7 @@ import { ViewportCache } from "./ViewportCache"; import { OutputForwardRenderConfig } from "../project/Config"; import { CompositingPipeline } from "./CompositingPipeline"; import { ResolvePipeline } from "./ResolvePipeline"; +import Camera from "../Viewport/Camera"; export default class Renderer { project: Project; @@ -35,6 +36,8 @@ export default class Renderer { r16ResolvePipeline!: ResolvePipeline; r16ResolveBindGroupLayout: GPUBindGroupLayout; heightFieldComputePassDescriptor: GPUComputePassDescriptor; + shadowViewUBO: GPUBuffer; + shadowBindGroup0: GPUBindGroup; constructor(project: Project) { @@ -45,6 +48,11 @@ export default class Renderer { size: (3 * 16 + 12) * 4, usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM, }); + this.shadowViewUBO = gpuDevice.createBuffer({ + label: "shadow view UBO", + size: (3 * 16 + 12) * 4, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM, + }); this.bindGroup0Layout = gpuDevice.createBindGroupLayout({ label: "Global default bind group 0 layout", entries: [ @@ -129,6 +137,13 @@ export default class Renderer { ], layout: this.bindGroup0Layout, }); + this.shadowBindGroup0 = gpuDevice.createBindGroup({ + label: "Global default shadow bind group 0", + entries: [ + { binding: 0, resource: { buffer: this.shadowViewUBO } } + ], + layout: this.bindGroup0Layout, + }); this.r3renderPassDescriptor = { label: "Render Pass", colorAttachments: [ @@ -315,9 +330,22 @@ export default class Renderer { } heightFieldComputeEncoder.end(); + for (const sun of this.project.scene.sunlights) { + this.updateView(this.shadowViewUBO, this.frame, sun, sun.resolution, sun.resolution); + sun.updateSunUniforms(); + const shadowRenderPassEncoder = commandEncoder.beginRenderPass(sun.renderPass); + shadowRenderPassEncoder.setViewport(0, 0, sun.resolution, sun.resolution, 0, 1); + shadowRenderPassEncoder.setBindGroup(0, this.shadowBindGroup0); + for (const hf of this.project.scene.heightFieldObjects) { + hf.renderDepth(shadowRenderPassEncoder); + } + shadowRenderPassEncoder.end(); + } + const r3renderPassEncoder = commandEncoder.beginRenderPass(this.r3renderPassDescriptor); r3renderPassEncoder.setViewport(0, 0, w, h, 0, 1); r3renderPassEncoder.setBindGroup(0, this.bindGroup0); + r3renderPassEncoder.setBindGroup(3, Array.from(this.project.scene.sunlights)[0].bindGroup); for (const hf of this.project.scene.heightFieldObjects) { hf.render(r3renderPassEncoder); } @@ -448,4 +476,20 @@ export default class Renderer { static getDepthStencilFormat(): GPUTextureFormat { return "depth24plus"; } + + private viewBuffer = new Float32Array(3 * 16 + 4); + private viewTimeBuffer = new Uint32Array(8); + updateView(viewUBO: GPUBuffer, frame: number, camera: Camera, width: number, height: number): void { + const projection = camera.getProjection(); + const near = projection.near; + const far = projection.far; + camera.getViewMatrix().pushInFloat32ArrayColumnMajor(this.viewBuffer); + projection.getProjectionMatrix(width, height).pushInFloat32ArrayColumnMajor(this.viewBuffer, 16); + camera.transformationStack.getTransformationMatrix().pushInFloat32ArrayColumnMajor(this.viewBuffer, 2 * 16); + this.viewBuffer.set([near, far, window.devicePixelRatio, 0], 3 * 16); + gpuDevice.queue.writeBuffer(viewUBO, 0, this.viewBuffer); + + this.viewTimeBuffer.set([0, 0, width, height, frame, 0, 0, 0], 0); + gpuDevice.queue.writeBuffer(viewUBO, this.viewBuffer.byteLength, this.viewTimeBuffer); + } } \ No newline at end of file diff --git a/frontend/src/ts/rendering/ViewportCache.ts b/frontend/src/ts/rendering/ViewportCache.ts index 5aeafdc..d90a26c 100644 --- a/frontend/src/ts/rendering/ViewportCache.ts +++ b/frontend/src/ts/rendering/ViewportCache.ts @@ -372,13 +372,13 @@ export class ViewportCache { const selectionTime = Number(times[5] - times[4]); const overlayTime = Number(times[7] - times[6]); const compositingTime = Number(times[9] - times[8]); - console.log( - "R3:", r3Time / 1000000, - "R16 Resolve:", r16Time / 1000000, - "Selection", selectionTime / 1000000, - "Overlay", overlayTime / 1000000, - "Compositing", compositingTime / 1000000, - "total:", (r3Time + r16Time + selectionTime + overlayTime + compositingTime) / 1000000); + // console.log( + // "R3:", r3Time / 1000000, + // "R16 Resolve:", r16Time / 1000000, + // "Selection", selectionTime / 1000000, + // "Overlay", overlayTime / 1000000, + // "Compositing", compositingTime / 1000000, + // "total:", (r3Time + r16Time + selectionTime + overlayTime + compositingTime) / 1000000); this.timeStempMapBuffer.unmap(); gpuDevice.queue.writeBuffer(this.timeStempBufferResolve, 0, this.resetBuffer, 0, this.resetBuffer.length) diff --git a/frontend/src/ts/ui/panes/ViewportPane.ts b/frontend/src/ts/ui/panes/ViewportPane.ts index 40a095d..cbca760 100644 --- a/frontend/src/ts/ui/panes/ViewportPane.ts +++ b/frontend/src/ts/ui/panes/ViewportPane.ts @@ -141,7 +141,7 @@ export class ViewportPane extends HTMLElement implements Viewport { const near = this.camera.projection.near; const far = this.camera.projection.far; this.camera.getViewMatrix().pushInFloat32ArrayColumnMajor(this.viewBuffer); - this.camera.getProjectionMatrix(this.canvas.width, this.canvas.height).pushInFloat32ArrayColumnMajor(this.viewBuffer, 16); + this.camera.getProjection().getProjectionMatrix(this.canvas.width, this.canvas.height).pushInFloat32ArrayColumnMajor(this.viewBuffer, 16); this.camera.transformationStack.getTransformationMatrix().pushInFloat32ArrayColumnMajor(this.viewBuffer, 2 * 16); this.viewBuffer.set([near, far, window.devicePixelRatio, 0], 3 * 16); gpuDevice.queue.writeBuffer(viewUBO, 0, this.viewBuffer);