@antv/g-webgpu-raytracer
Version:
A simple ray tracer implemented with WebGPU
675 lines (624 loc) • 33.3 kB
JavaScript
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
import _regeneratorRuntime from "@babel/runtime/regenerator";
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
import _createClass from "@babel/runtime/helpers/createClass";
import { isSafari } from '@antv/g-webgpu-core';
import { Camera } from '@antv/g-webgpu';
import { WebGPUEngine } from '@antv/g-webgpu-engine';
import * as WebGPUConstants from '@webgpu/types/dist/constants';
import { mat4, vec3 } from 'gl-matrix';
import { InteractionSystem } from './interaction';
import { Mesh } from './Mesh';
import { cornellBoxScene } from './scene/cornell';
/* babel-plugin-inline-import './shaders/blit.frag.glsl' */
var blitFragCode = "layout(origin_upper_left) in vec4 gl_FragCoord;\n\nlayout(set = 0, binding = 0, rgba32f) readonly uniform highp image2D output_image;\n\nlayout(location = 0) out vec4 output_color;\n\nvoid main() {\n output_color = vec4(imageLoad(output_image, ivec2(gl_FragCoord.x, gl_FragCoord.y)).xyz, 1.0);\n}";
/* babel-plugin-inline-import './shaders/blit.vert.glsl' */
var blitVertCode = "// https://www.saschawillems.de/blog/2016/08/13/vulkan-tutorial-on-rendering-a-fullscreen-quad-without-buffers/\n\nout gl_PerVertex {\n vec4 gl_Position;\n};\n\nvoid main() {\n vec2 outUV = vec2(((gl_VertexIndex) << 1) & 2, (gl_VertexIndex) & 2);\n gl_Position = vec4(outUV * 2.0 - 1.0, 0.0, 1.0);\n}\n"; // import rayComputeCode from './shaders/raycasting.comp.glsl';
/* babel-plugin-inline-import './shaders/whitted.comp.glsl' */
var rayComputeCode = "layout (local_size_x = 16, local_size_y = 16, local_size_z = 1) in;\n\nlayout(std140, set = 0, binding = 0) uniform Globals {\n mat4 uCameraToWorld;\n mat4 uCameraInverseProjection;\n float uInitialSeed;\n float uSamples;\n} globals;\n\nlayout(set = 0, binding = 1, rgba32f) writeonly uniform highp image2D outputTex;\nlayout(set = 0, binding = 2, rgba32f) readonly uniform highp image2D accumulatedTex;\n\nstruct Mesh\n{\n int offset;\n int triangle_count;\n vec3 diffuse;\n vec3 emission;\n};\n\nlayout(std430, set = 0, binding = 3) readonly buffer Vertices {\n vec3 vertices[];\n};\n\nlayout(std430, set = 0, binding = 4) readonly buffer Triangles {\n int triangles[];\n};\n\nlayout(std430, set = 0, binding = 5) readonly buffer Meshes {\n Mesh meshes[];\n};\n\n#define M_PI 3.14159265358979323846\n#define M_TWO_PI 6.28318530718\n#define EPSILON 1e-3\n#define zero3 vec3(0.0)\n#define MAX_FLOAT 3.402823466e+30F\n\n//\n// Pseudo random numbers generator.\n//\n// References:\n// - http://blog.three-eyed-games.com/2018/05/12/gpu-path-tracing-in-unity-part-2/\n//\nfloat rand(inout float seed, vec2 pixel) {\n float result = fract(sin(seed / 100.0f * dot(pixel, vec2(12.9898f, 78.233f))) * 43758.5453f);\n seed += 1.0f;\n return result;\n}\n\nvec2 rand2(inout float seed, vec2 pixel) {\n return vec2(rand(seed, pixel), rand(seed, pixel));\n}\n\nmat3 get_tangent_space(vec3 normal) {\n // Choose a helper vector for the cross product\n vec3 helper = vec3(1.0, 0.0, 0.0);\n if (abs(normal.x) > 0.99) {\n helper = vec3(0.0, 0.0, 1.0);\n }\n\n // Generate vectors\n vec3 tangent = normalize(cross(normal, helper));\n vec3 binormal = normalize(cross(normal, tangent));\n return mat3(tangent, binormal, normal);\n}\n\nvec3 sample_sphere_uniform(vec3 normal, vec2 s, float alpha) {\n // Sample the hemisphere, where alpha determines the kind of the sampling\n float cos_theta = pow(s.x, 1.0 / (alpha + 1.0));\n float sin_theta = sqrt(1.0 - cos_theta * cos_theta);\n float phi = M_TWO_PI * s.y;\n vec3 tangentSpaceDir = vec3(cos(phi) * sin_theta, sin(phi) * sin_theta, cos_theta);\n\n // Transform direction to world space\n return get_tangent_space(normal) * tangentSpaceDir;\n}\n\nstruct Ray {\n vec3 origin;\n vec3 direction;\n float t_max;\n float t_min;\n vec3 energy;\n};\n\nstruct RayHit {\n float distance;\n vec3 normal;\n int mesh_indice;\n};\n\nvec3 ray_at(Ray r, float t) {\n return r.origin + r.direction * t;\n}\n\nfloat energy(vec3 color) {\n return dot(color, vec3(1.0 / 3.0));\n}\n\n// @see https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/moller-trumbore-ray-triangle-intersection\nbool hit_triangle_mt(Ray r, vec3 v0, vec3 v1, vec3 v2, out float t) {\n vec3 e1 = v1 - v0;\n vec3 e2 = v2 - v0;\n vec3 h = cross(r.direction, e2);\n float a = dot(e1, h);\n\n if (a < EPSILON && a > EPSILON)\n return false;\n\n float f = 1.0 / a;\n vec3 s = r.origin - v0;\n float u = f * dot(s, h);\n\n if (u < 0.0 || u > 1.0)\n return false;\n\n vec3 q = cross(s, e1);\n float v = f * dot(r.direction, q);\n if (v < 0.0 || u + v > 1.0)\n return false;\n \n t = f * dot(e2, q);\n if (t > EPSILON)\n {\n return true;\n }\n\n return false;\n}\n\nbool hit_world(Ray r, inout RayHit hit) {\n bool does_hit = false;\n float t = 0.0;\n hit.distance = 0.0;\n hit.mesh_indice = -1;\n float best_min_t = r.t_max;\n\n for (int i = 0; i < meshes.length(); ++i) {\n Mesh mesh = meshes[i];\n\n for (int j = 0; j < mesh.triangle_count * 3; j += 3) {\n vec3 v0 = vertices[triangles[mesh.offset + j]];\n vec3 v1 = vertices[triangles[mesh.offset + j + 1]];\n vec3 v2 = vertices[triangles[mesh.offset + j + 2]];\n\n if (hit_triangle_mt(r, v0, v1, v2, t) && t >= r.t_min && t < r.t_max && t < best_min_t) {\n best_min_t = t;\n does_hit = true;\n hit.mesh_indice = i;\n hit.normal = normalize(cross(v1 - v0, v2 - v0));\n }\n\n }\n }\n\n if (does_hit) {\n hit.distance = best_min_t;\n }\n\n return does_hit;\n}\n\nvec3 random_point_on_mesh(Mesh m, inout float seed, vec2 pixel, out float p) {\n // Pick a random triangle.\n int triangle = min(int(rand(seed, pixel) * float(m.triangle_count)), m.triangle_count - 1);\n\n // Pick vertices.\n vec3 v0 = vertices[triangles[m.offset + triangle]];\n vec3 v1 = vertices[triangles[m.offset + triangle + 1]];\n vec3 v2 = vertices[triangles[m.offset + triangle + 2]];\n\n float r = rand(seed, pixel);\n float s = rand(seed, pixel);\n\n if (r + s > 1.0)\n {\n r = 1.0 - r;\n s = 1.0 - s;\n }\n\n float t = 1.0 - r - s;\n \n float triangle_area = length(cross(v1 - v0, v2 - v0)) * 0.5;\n\n p = (1.0 / float(m.triangle_count)) / triangle_area;\n\n return v0 * r + v1 * s + v2 * t;\n}\n\nvec3 trace(Ray r, inout float seed, vec2 pixel) {\n int max_depth = 5;\n int depth = 0;\n RayHit hit;\n\n vec3 res = vec3(0.0);\n\n int light_mesh_indice = 0;\n Mesh light = meshes[light_mesh_indice];\n int light_count = light.triangle_count;\n\n while (depth < max_depth\n && hit_world(r, hit))\n {\n Mesh mesh = meshes[hit.mesh_indice];\n vec3 surface_normal = hit.normal;\n\n // \u51FB\u4E2D\u4E86\u5149\u6E90\uFF0C\u6E32\u67D3\u5149\u6E90\u989C\u8272\uFF0C\u7EC8\u6B62\u5FAA\u73AF\n if (mesh.emission != vec3(0.0) && depth == 0) {\n return mesh.emission;\n }\n\n vec3 hit_point = ray_at(r, hit.distance);\n\n if (mesh.emission == vec3(0.0)) {\n float light_pdf = 0.0;\n\n // Generate a point on the light.\n vec3 light_point = random_point_on_mesh(light, seed, pixel, light_pdf);\n\n vec3 lh = light_point - hit_point;\n float dist = length(lh);\n\n // \u7EE7\u7EED\u8FFD\u8E2A shadow ray\n Ray shadow_ray;\n shadow_ray.origin = hit_point;\n shadow_ray.direction = normalize(lh);\n shadow_ray.t_min = EPSILON;\n shadow_ray.t_max = dist;\n\n if (!hit_world(shadow_ray, hit) \n || hit.mesh_indice == light_mesh_indice) {\n // Diffuse\n res += 2.0 * light_pdf * mesh.diffuse * light.emission * abs(dot(surface_normal, shadow_ray.direction));\n }\n }\n\n vec2 s = rand2(seed, pixel);\n r.origin = hit_point + hit.normal * EPSILON;\n r.direction = normalize(sample_sphere_uniform(hit.normal, s, 1.0));\n depth++;\n }\n\n return res / float(depth);\n}\n\nRay create_camera_ray(vec2 uv) {\n vec3 rayOriginWorld = (globals.uCameraToWorld * vec4(0.0, 0.0, 0.0, 1.0)).xyz;\n\n vec3 direction = (globals.uCameraInverseProjection * vec4(uv, 0.0, 1.0)).xyz;\n direction = (globals.uCameraToWorld * vec4(direction, 0.0)).xyz;\n direction = normalize(direction);\n\n Ray cameraRay;\n cameraRay.origin = rayOriginWorld;\n cameraRay.direction = direction;\n cameraRay.t_min = EPSILON;\n cameraRay.t_max = MAX_FLOAT;\n cameraRay.energy = vec3(1.0);\n return cameraRay;\n}\n\nvoid main() {\n ivec2 imageSize = ivec2(gl_NumWorkGroups.xy * gl_WorkGroupSize.xy);\n ivec2 storePos = ivec2(gl_GlobalInvocationID.xy);\n vec2 uv = vec2(storePos) / vec2(imageSize);\n float seed = float(globals.uInitialSeed);\n\n vec2 sample_pos = (vec2(storePos) + rand2(seed, uv)) / vec2(imageSize);\n\n Ray r = create_camera_ray(sample_pos);\n\n vec3 finalColor = trace(r, seed, uv);\n\n vec4 color;\n // flipY (0, 0) is top-left\n // @see https://gpuweb.github.io/gpuweb/#coordinate-systems\n storePos.y = imageSize.y - storePos.y;\n if (globals.uSamples == 0.0) {\n color = vec4(finalColor, 1.0);\n } else {\n vec3 initial = imageLoad(accumulatedTex, storePos).rgb;\n color = vec4(initial + (finalColor - initial) / float(globals.uSamples), 1.0);\n }\n\n imageStore(outputTex, storePos, color);\n}";
export var RayTracer = /*#__PURE__*/function () {
function RayTracer(options) {
var _this = this;
_classCallCheck(this, RayTracer);
this.options = options;
this.engine = void 0;
this.inited = false;
this.rafHandle = void 0;
this.computeBindGroupLayout = void 0;
this.computePipeline = void 0;
this.renderBindGroupLayout = void 0;
this.renderPipeline = void 0;
this.outputTexture = void 0;
this.accumulatedTexture = void 0;
this.verticesBuffer = void 0;
this.meshesBuffer = void 0;
this.trianglesBuffer = void 0;
this.camera = void 0;
this.sampleCount = 0;
this.randomSeed = 0;
this.interactionSystem = void 0;
this.update = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
return _regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return _this.render();
case 2:
// 考虑运行在 Worker 中,不能使用 window.requestAnimationFrame
_this.rafHandle = requestAnimationFrame(_this.update);
case 3:
case "end":
return _context.stop();
}
}
}, _callee);
}));
}
_createClass(RayTracer, [{
key: "init",
value: function () {
var _init = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
return _regeneratorRuntime.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
this.engine = new WebGPUEngine();
_context2.next = 3;
return this.engine.init({
canvas: this.options.canvas,
swapChainFormat: WebGPUConstants.TextureFormat.BGRA8Unorm,
antialiasing: false,
supportCompute: true
});
case 3:
_context2.next = 5;
return this.update();
case 5:
case "end":
return _context2.stop();
}
}
}, _callee2, this);
}));
function init() {
return _init.apply(this, arguments);
}
return init;
}()
}, {
key: "setSize",
value: function setSize(width, height) {
var canvas = this.engine.getCanvas();
var pixelRatio = window.devicePixelRatio;
canvas.width = Math.floor(width * pixelRatio);
canvas.height = Math.floor(height * pixelRatio);
canvas.style.width = "".concat(width, "px");
canvas.style.height = "".concat(height, "px");
this.engine.viewport({
x: 0,
y: 0,
width: canvas.width,
height: canvas.height
});
}
}, {
key: "destroy",
value: function destroy() {
this.engine.destroy();
this.interactionSystem.tearDown();
cancelAnimationFrame(this.rafHandle);
}
}, {
key: "render",
value: function () {
var _render = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
return _regeneratorRuntime.wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
this.engine.beginFrame();
this.engine.clear({
color: [1, 1, 1, 1],
depth: 1
});
if (this.inited) {
_context3.next = 13;
break;
}
if (this.options.onInit) {
this.options.onInit();
}
this.createCamera();
this.createInteraction();
this.createMeshes();
this.createOutputTexture();
_context3.next = 10;
return this.createComputePipeline();
case 10:
_context3.next = 12;
return this.createRenderPipeline();
case 12:
this.inited = true;
case 13:
if (this.options.onUpdate) {
this.options.onUpdate();
}
this.runComputePipeline();
this.runRenderPipeline();
this.engine.endFrame();
case 17:
case "end":
return _context3.stop();
}
}
}, _callee3, this);
}));
function render() {
return _render.apply(this, arguments);
}
return render;
}()
}, {
key: "createInteraction",
value: function createInteraction() {
var _this2 = this;
this.interactionSystem = new InteractionSystem(this.options.canvas, this.camera, function () {
_this2.sampleCount = 0;
_this2.randomSeed = 0;
});
this.interactionSystem.initialize();
}
}, {
key: "createCamera",
value: function createCamera() {
this.camera = new Camera();
this.camera.position = vec3.fromValues(-2.75, 2.75, 8.35);
this.camera.setFocalPoint(vec3.fromValues(-2.75, 2.75, 0));
this.camera.setPerspective(0.1, 100, 40, 1);
}
}, {
key: "createMeshes",
value: function createMeshes() {
var totalVerticeCount = 0;
var totalTriangleCount = 0;
cornellBoxScene.forEach(function (mesh) {
totalVerticeCount += mesh.vertices.length;
totalTriangleCount += mesh.indices.length;
});
var verticesBuffer = new Float32Array(totalVerticeCount / 3 * 4);
var index = 0;
var trianglesArray = new Array();
var indicesOffset = 0;
var accumulatingTriangleCount = 0;
cornellBoxScene.forEach(function (mesh) {
var vertices = mesh.vertices;
for (var i = 0; i < vertices.length; i += 3) {
verticesBuffer[index++] = vertices[i];
verticesBuffer[index++] = vertices[i + 1];
verticesBuffer[index++] = vertices[i + 2];
verticesBuffer[index++] = 0.0;
}
trianglesArray = trianglesArray.concat(mesh.indices.map(function (i) {
return i + indicesOffset;
}));
mesh.offset = accumulatingTriangleCount;
accumulatingTriangleCount += mesh.triangleCount * 3;
indicesOffset += mesh.verticeCount;
});
var trianglesBuffer = new Int32Array(trianglesArray);
var meshesBuffer = new Float32Array(Mesh.createMeshesBuffer(cornellBoxScene));
this.verticesBuffer = verticesBuffer;
this.trianglesBuffer = trianglesBuffer;
this.meshesBuffer = meshesBuffer;
}
}, {
key: "createOutputTexture",
value: function createOutputTexture() {
this.outputTexture = this.engine.device.createTexture({
label: 'Output Texture',
size: {
width: this.options.canvas.width,
height: this.options.canvas.height,
depth: 1
},
// TODO: arrayLayerCount is deprecated: use size.depth
// arrayLayerCount: 1,
mipLevelCount: 1,
sampleCount: 1,
dimension: WebGPUConstants.TextureDimension.E2d,
format: WebGPUConstants.TextureFormat.RGBA32Float,
usage: WebGPUConstants.TextureUsage.Storage
});
this.accumulatedTexture = this.engine.device.createTexture({
label: 'Accumulated Texture',
size: {
width: this.options.canvas.width,
height: this.options.canvas.height,
depth: 1
},
// TODO: arrayLayerCount is deprecated: use size.depth
// arrayLayerCount: 1,
mipLevelCount: 1,
sampleCount: 1,
dimension: WebGPUConstants.TextureDimension.E2d,
format: WebGPUConstants.TextureFormat.RGBA32Float,
usage: WebGPUConstants.TextureUsage.Storage
});
}
}, {
key: "createRenderPipeline",
value: function () {
var _createRenderPipeline = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4() {
var _yield$this$compilePi, vertexStage, fragmentStage, bindGroupLayoutEntries, pipelineLayout;
return _regeneratorRuntime.wrap(function _callee4$(_context4) {
while (1) {
switch (_context4.prev = _context4.next) {
case 0:
_context4.next = 2;
return this.compilePipelineStageDescriptor(blitVertCode, blitFragCode);
case 2:
_yield$this$compilePi = _context4.sent;
vertexStage = _yield$this$compilePi.vertexStage;
fragmentStage = _yield$this$compilePi.fragmentStage;
bindGroupLayoutEntries = [{
binding: 0,
visibility: WebGPUConstants.ShaderStage.Fragment,
type: 'readonly-storage-texture',
viewDimension: WebGPUConstants.TextureViewDimension.E2d,
storageTextureFormat: WebGPUConstants.TextureFormat.RGBA32Float
}];
this.renderBindGroupLayout = this.engine.device.createBindGroupLayout(isSafari ? // @ts-ignore
{
bindings: bindGroupLayoutEntries
} : {
entries: bindGroupLayoutEntries
});
pipelineLayout = this.engine.device.createPipelineLayout({
bindGroupLayouts: [this.renderBindGroupLayout]
});
this.renderPipeline = this.engine.device.createRenderPipeline({
sampleCount: this.engine.mainPassSampleCount,
primitiveTopology: WebGPUConstants.PrimitiveTopology.TriangleList,
rasterizationState: {
frontFace: WebGPUConstants.FrontFace.CCW,
cullMode: WebGPUConstants.CullMode.Back,
depthBias: 0,
depthBiasClamp: 0,
depthBiasSlopeScale: 0
},
depthStencilState: {
depthWriteEnabled: false,
// depthCompare: depthFuncMap[depth?.func || gl.ALWAYS],
format: WebGPUConstants.TextureFormat.Depth24PlusStencil8,
// stencilFront: stencilFrontBack,
// stencilBack: stencilFrontBack,
stencilReadMask: 0xffffffff,
stencilWriteMask: 0xffffffff
},
colorStates: [{
format: this.engine.options.swapChainFormat,
alphaBlend: {
srcFactor: WebGPUConstants.BlendFactor.One,
dstFactor: WebGPUConstants.BlendFactor.One,
operation: WebGPUConstants.BlendOperation.Add
},
colorBlend: {
srcFactor: WebGPUConstants.BlendFactor.SrcAlpha,
dstFactor: WebGPUConstants.BlendFactor.OneMinusDstAlpha,
operation: WebGPUConstants.BlendOperation.Add
},
writeMask: WebGPUConstants.ColorWrite.All
}],
alphaToCoverageEnabled: false,
layout: pipelineLayout,
vertexStage: vertexStage,
fragmentStage: fragmentStage,
vertexState: {
indexFormat: WebGPUConstants.IndexFormat.Uint32,
vertexBuffers: []
}
});
case 9:
case "end":
return _context4.stop();
}
}
}, _callee4, this);
}));
function createRenderPipeline() {
return _createRenderPipeline.apply(this, arguments);
}
return createRenderPipeline;
}()
}, {
key: "createComputePipeline",
value: function () {
var _createComputePipeline = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5() {
var _yield$this$compileCo, computeStage, bindGroupLayoutEntries, pipelineLayout;
return _regeneratorRuntime.wrap(function _callee5$(_context5) {
while (1) {
switch (_context5.prev = _context5.next) {
case 0:
_context5.next = 2;
return this.compileComputePipelineStageDescriptor(rayComputeCode);
case 2:
_yield$this$compileCo = _context5.sent;
computeStage = _yield$this$compileCo.computeStage;
bindGroupLayoutEntries = [{
binding: 0,
visibility: WebGPUConstants.ShaderStage.Compute,
type: 'uniform-buffer'
}, {
binding: 1,
visibility: WebGPUConstants.ShaderStage.Compute,
type: 'writeonly-storage-texture',
viewDimension: WebGPUConstants.TextureViewDimension.E2d,
storageTextureFormat: WebGPUConstants.TextureFormat.RGBA32Float
}, {
binding: 2,
visibility: WebGPUConstants.ShaderStage.Compute,
type: 'readonly-storage-texture',
viewDimension: WebGPUConstants.TextureViewDimension.E2d,
storageTextureFormat: WebGPUConstants.TextureFormat.RGBA32Float
}, {
binding: 3,
visibility: WebGPUConstants.ShaderStage.Compute,
type: 'readonly-storage-buffer'
}, {
binding: 4,
visibility: WebGPUConstants.ShaderStage.Compute,
type: 'readonly-storage-buffer'
}, {
binding: 5,
visibility: WebGPUConstants.ShaderStage.Compute,
type: 'readonly-storage-buffer'
}];
this.computeBindGroupLayout = this.engine.device.createBindGroupLayout(isSafari ? // @ts-ignore
{
bindings: bindGroupLayoutEntries
} : {
entries: bindGroupLayoutEntries
});
pipelineLayout = this.engine.device.createPipelineLayout({
bindGroupLayouts: [this.computeBindGroupLayout]
});
this.computePipeline = this.engine.device.createComputePipeline({
layout: pipelineLayout,
computeStage: computeStage
});
case 8:
case "end":
return _context5.stop();
}
}
}, _callee5, this);
}));
function createComputePipeline() {
return _createComputePipeline.apply(this, arguments);
}
return createComputePipeline;
}()
}, {
key: "runComputePipeline",
value: function runComputePipeline() {
if (this.engine.currentComputePass) {
var cameraWorldMatrix = this.camera.matrix;
var projectionInverseMatrix = mat4.invert(mat4.create(), this.camera.getPerspective());
var NDCToScreenMatrix = mat4.create();
mat4.identity(NDCToScreenMatrix);
mat4.translate(NDCToScreenMatrix, NDCToScreenMatrix, [-1, -1, 0]);
mat4.scale(NDCToScreenMatrix, NDCToScreenMatrix, [2, 2, 1]);
mat4.multiply(projectionInverseMatrix, projectionInverseMatrix, NDCToScreenMatrix);
var mergedUniformData = [].concat(_toConsumableArray(cameraWorldMatrix), _toConsumableArray(projectionInverseMatrix), [this.randomSeed, this.sampleCount, 0, 0]);
var uniformBuffer = this.engine.createBuffer({
data: new Float32Array(mergedUniformData),
usage: WebGPUConstants.BufferUsage.Uniform | WebGPUConstants.BufferUsage.CopyDst
});
var verticeBuffer = this.engine.createBuffer({
data: this.verticesBuffer,
// @ts-ignore
usage: WebGPUConstants.BufferUsage.Storage | WebGPUConstants.BufferUsage.CopyDst
});
var trianglesBuffer = this.engine.createBuffer({
data: this.trianglesBuffer,
// @ts-ignore
usage: WebGPUConstants.BufferUsage.Storage | WebGPUConstants.BufferUsage.CopyDst
});
var meshesBuffer = this.engine.createBuffer({
data: this.meshesBuffer,
// @ts-ignore
usage: WebGPUConstants.BufferUsage.Storage | WebGPUConstants.BufferUsage.CopyDst
});
var bindGroupEntries = [{
binding: 0,
// https://gpuweb.github.io/gpuweb/#typedefdef-gpubindingresource
resource: {
// @ts-ignore
buffer: uniformBuffer.get()
}
}, {
binding: 1,
resource: isSafari ? // @ts-ignore
this.outputTexture.createDefaultView() : this.outputTexture.createView()
}, {
binding: 2,
resource: isSafari ? // @ts-ignore
this.accumulatedTexture.createDefaultView() : this.accumulatedTexture.createView()
}, {
binding: 3,
resource: {
// @ts-ignore
buffer: verticeBuffer.get()
}
}, {
binding: 4,
resource: {
// @ts-ignore
buffer: trianglesBuffer.get()
}
}, {
binding: 5,
resource: {
// @ts-ignore
buffer: meshesBuffer.get()
}
}];
var computeBindGroup = this.engine.device.createBindGroup(isSafari ? {
label: 'Compute Bind Group',
layout: this.computeBindGroupLayout,
// @ts-ignore
bindings: bindGroupEntries
} : {
label: 'Compute Bind Group',
layout: this.computeBindGroupLayout,
entries: bindGroupEntries
});
this.engine.currentComputePass.setPipeline(this.computePipeline);
this.engine.currentComputePass.setBindGroup(0, computeBindGroup);
this.engine.currentComputePass.dispatch(this.options.canvas.width / 16, this.options.canvas.height / 16, 1);
this.sampleCount++;
this.randomSeed++;
}
}
}, {
key: "runRenderPipeline",
value: function runRenderPipeline() {
var renderPass = this.engine.bundleEncoder || this.engine.currentRenderPass;
if (this.renderPipeline) {
renderPass.setPipeline(this.renderPipeline);
}
var bindGroupEntries = [{
binding: 0,
resource: isSafari ? // @ts-ignore
this.outputTexture.createDefaultView() : this.outputTexture.createView()
}];
var renderBindGroup = this.engine.device.createBindGroup(isSafari ? {
label: 'Render Bind Group',
layout: this.renderBindGroupLayout,
// @ts-ignore
bindings: bindGroupEntries
} : {
label: 'Render Bind Group',
layout: this.renderBindGroupLayout,
entries: bindGroupEntries
});
renderPass.setBindGroup(0, renderBindGroup);
renderPass.draw(3); // swap
var tmp = this.accumulatedTexture;
this.accumulatedTexture = this.outputTexture;
this.outputTexture = tmp;
}
}, {
key: "compileShaderToSpirV",
value: function compileShaderToSpirV(source, type, shaderVersion) {
return this.compileRawShaderToSpirV(shaderVersion + source, type);
}
}, {
key: "compileRawShaderToSpirV",
value: function compileRawShaderToSpirV(source, type) {
return this.engine.glslang.compileGLSL(source, type);
}
}, {
key: "compilePipelineStageDescriptor",
value: function () {
var _compilePipelineStageDescriptor = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6(vertexCode, fragmentCode) {
var shaderVersion, vertexShader, fragmentShader;
return _regeneratorRuntime.wrap(function _callee6$(_context6) {
while (1) {
switch (_context6.prev = _context6.next) {
case 0:
shaderVersion = '#version 450\n';
_context6.next = 3;
return this.compileShaderToSpirV(vertexCode, 'vertex', shaderVersion);
case 3:
vertexShader = _context6.sent;
_context6.next = 6;
return this.compileShaderToSpirV(fragmentCode, 'fragment', shaderVersion);
case 6:
fragmentShader = _context6.sent;
return _context6.abrupt("return", this.createPipelineStageDescriptor(vertexShader, fragmentShader));
case 8:
case "end":
return _context6.stop();
}
}
}, _callee6, this);
}));
function compilePipelineStageDescriptor(_x, _x2) {
return _compilePipelineStageDescriptor.apply(this, arguments);
}
return compilePipelineStageDescriptor;
}()
}, {
key: "createPipelineStageDescriptor",
value: function createPipelineStageDescriptor(vertexShader, fragmentShader) {
return {
vertexStage: {
module: this.engine.device.createShaderModule({
code: vertexShader,
// @ts-ignore
isWHLSL: isSafari
}),
entryPoint: 'main'
},
fragmentStage: {
module: this.engine.device.createShaderModule({
code: fragmentShader,
// @ts-ignore
isWHLSL: isSafari
}),
entryPoint: 'main'
}
};
}
}, {
key: "compileComputePipelineStageDescriptor",
value: function () {
var _compileComputePipelineStageDescriptor = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee7(computeCode) {
var computeShader, shaderVersion;
return _regeneratorRuntime.wrap(function _callee7$(_context7) {
while (1) {
switch (_context7.prev = _context7.next) {
case 0:
if (!isSafari) {
_context7.next = 4;
break;
}
computeShader = computeCode;
_context7.next = 8;
break;
case 4:
shaderVersion = '#version 450\n';
_context7.next = 7;
return this.compileShaderToSpirV(computeCode, 'compute', shaderVersion);
case 7:
computeShader = _context7.sent;
case 8:
return _context7.abrupt("return", {
computeStage: {
module: this.engine.device.createShaderModule({
code: computeShader,
// @ts-ignore
isWHLSL: isSafari
}),
entryPoint: 'main'
}
});
case 9:
case "end":
return _context7.stop();
}
}
}, _callee7, this);
}));
function compileComputePipelineStageDescriptor(_x3) {
return _compileComputePipelineStageDescriptor.apply(this, arguments);
}
return compileComputePipelineStageDescriptor;
}()
}]);
return RayTracer;
}();
//# sourceMappingURL=index.js.map