From 5b8b8fa05719c860f9ed7da41b422c6999bbe1f5 Mon Sep 17 00:00:00 2001 From: Szymon Nowakowski Date: Sat, 29 Jul 2023 22:06:46 +0200 Subject: [PATCH] Shader code beginnings --- src/data/Matrix4x4.ts | 74 +++++++++++++++++++++ src/data/Node.ts | 8 ++- src/oktaeder.ts | 47 +++++++++++++ src/resources/Material.ts | 22 +++--- src/shader.ts | 136 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 277 insertions(+), 10 deletions(-) create mode 100644 src/shader.ts diff --git a/src/data/Matrix4x4.ts b/src/data/Matrix4x4.ts index be65b6d..f186531 100644 --- a/src/data/Matrix4x4.ts +++ b/src/data/Matrix4x4.ts @@ -127,6 +127,25 @@ export class Matrix4x4 { ); } + static fromTRS(t: Vector3Object, r: QuaternionObject, s: Vector3Object): Matrix4x4 { + const xx = r.x * r.x; + const xy = r.x * r.y; + const xz = r.x * r.z; + const xw = r.x * r.w; + const yy = r.y * r.y; + const yz = r.y * r.z; + const yw = r.y * r.w; + const zz = r.z * r.z; + const zw = r.z * r.w; + + return new Matrix4x4( + s.x * (1 - 2 * (yy + zz)), s.x * 2 * (xy + zw), s.x * 2 * (xz - yw), 0, + s.y * 2 * (xy - zw), s.y * (1 - 2 * (xx + zz)), s.y * 2 * (yz + xw), 0, + s.z * 2 * (xz + yw), s.z * 2 * (yz - xw), s.z * (1 - 2 * (xx + yy)), 0, + t.x, t.y, t.z, 1, + ); + } + setObject(object: Matrix4x4Object): Matrix4x4 { this.ix = object.ix; this.iy = object.iy; @@ -236,6 +255,61 @@ export class Matrix4x4 { this.tw = 1; return this; } + + setTRS(t: Vector3Object, r: QuaternionObject, s: Vector3Object): Matrix4x4 { + const xx = r.x * r.x; + const xy = r.x * r.y; + const xz = r.x * r.z; + const xw = r.x * r.w; + const yy = r.y * r.y; + const yz = r.y * r.z; + const yw = r.y * r.w; + const zz = r.z * r.z; + const zw = r.z * r.w; + + this.ix = s.x * (1 - 2 * (yy + zz)); + this.iy = s.x * 2 * (xy + zw); + this.iz = s.x * 2 * (xz - yw); + this.iw = 0; + this.jx = s.y * 2 * (xy - zw); + this.jy = s.y * (1 - 2 * (xx + zz)); + this.jz = s.y * 2 * (yz + xw); + this.jw = 0; + this.kx = s.z * 2 * (xz + yw); + this.ky = s.z * 2 * (yz - xw); + this.kz = s.z * (1 - 2 * (xx + yy)); + this.kw = 0; + this.tx = t.x; + this.ty = t.y; + this.tz = t.z; + this.tw = 1; + return this; + } + + add(m: Matrix4x4): Matrix4x4 { + throw new Error("TODO"); + return this; + } + + sub(m: Matrix4x4): Matrix4x4 { + throw new Error("TODO"); + return this; + } + + mulScalar(k: number): Matrix4x4 { + throw new Error("TODO"); + return this; + } + + mulMatrix(m: Matrix4x4): Matrix4x4 { + throw new Error("TODO"); + return this; + } + + premulMatrix(m: Matrix4x4): Matrix4x4 { + throw new Error("TODO"); + return this; + } } Object.defineProperty(Matrix4x4.prototype, "type", { value: "Matrix4x4" }); diff --git a/src/data/Node.ts b/src/data/Node.ts index 7ef7413..0e8d987 100644 --- a/src/data/Node.ts +++ b/src/data/Node.ts @@ -4,7 +4,7 @@ * obtain one at http://mozilla.org/MPL/2.0/. */ -import { Camera, Mesh, Quaternion, QuaternionObject, Vector3, Vector3Object } from "."; +import { Camera, Matrix4x4, Mesh, Quaternion, QuaternionObject, Vector3, Vector3Object } from "."; import { Material } from "../resources"; export interface NodeProps { @@ -44,6 +44,9 @@ export class Node { /** backreference */ _parent: Node | null; + _localMatrix: Matrix4x4; + _worldMatrix: Matrix4x4; + constructor({ name = "", translation, @@ -68,6 +71,9 @@ export class Node { this._parent = null; + this._localMatrix = Matrix4x4.fromTRS(this._translation, this._rotation, this._scale); + this._worldMatrix = Matrix4x4.fromObject(this._localMatrix); + if (this._camera !== null) { this._camera._node = this; } diff --git a/src/oktaeder.ts b/src/oktaeder.ts index d9d4ae6..911eaed 100644 --- a/src/oktaeder.ts +++ b/src/oktaeder.ts @@ -4,6 +4,7 @@ * obtain one at http://mozilla.org/MPL/2.0/. */ +import { Camera, Scene } from "./data"; import { IndexBuffer, IndexBufferProps, Material, MaterialProps, Texture2D, Texture2DProps, VertexBuffer, VertexBufferProps } from "./resources"; export class Renderer { @@ -20,6 +21,8 @@ export class Renderer { /** 1×1 rgba8unorm texture of [128, 128, 128, 255] */ _textureNormal: Texture2D; + _depthBuffer: Texture2D; + /** * This constructor is intended primarily for internal use. Consider using * `Renderer.createIndexBuffer` instead. @@ -36,6 +39,7 @@ export class Renderer { this._format = format; this._textureWhite = new Texture2D(this, { + name: "White", width: 1, height: 1, format: "linear", @@ -43,6 +47,7 @@ export class Renderer { this._textureWhite.writeFull(new Uint8Array([255, 255, 255, 255])); this._textureBlack = new Texture2D(this, { + name: "Black", width: 1, height: 1, format: "linear", @@ -50,11 +55,20 @@ export class Renderer { this._textureBlack.writeFull(new Uint8Array([0, 0, 0, 255])); this._textureNormal = new Texture2D(this, { + name: "Normal", width: 1, height: 1, format: "linear", }); this._textureNormal.writeFull(new Uint8Array([128, 128, 128, 255])); + + const framebufferTexture = this._context.getCurrentTexture(); + this._depthBuffer = new Texture2D(this, { + name: "Depth Buffer", + width: framebufferTexture.width, + height: framebufferTexture.height, + format: "depth", + }); } static async init(canvas: HTMLCanvasElement) { @@ -90,6 +104,7 @@ export class Renderer { this._textureWhite.dispose(); this._textureBlack.dispose(); this._textureNormal.dispose(); + this._depthBuffer.dispose(); return this; } @@ -108,4 +123,36 @@ export class Renderer { createVertexBuffer(props: VertexBufferProps): VertexBuffer { return new VertexBuffer(this, props); } + + render(scene: Scene, camera: Camera): Renderer { + const { width, height } = this._context.getCurrentTexture(); + if (this._depthBuffer.width !== width || this._depthBuffer.height !== height) { + this._depthBuffer.resizeDiscard({ + width, + height, + }); + } + + const encoder = this._device.createCommandEncoder(); + + const pass = encoder.beginRenderPass({ + colorAttachments: [{ + view: this._context.getCurrentTexture().createView(), + loadOp: "clear", + storeOp: "store", + }], + depthStencilAttachment: { + view: this._depthBuffer._textureView, + depthClearValue: 0, + depthLoadOp: "clear", + depthStoreOp: "store", + }, + }); + pass.end(); + + const commandBuffer = encoder.finish(); + this._device.queue.submit([commandBuffer]); + + return this; + } } diff --git a/src/resources/Material.ts b/src/resources/Material.ts index ef98c07..cf1c6b0 100644 --- a/src/resources/Material.ts +++ b/src/resources/Material.ts @@ -15,17 +15,18 @@ export interface MaterialProps { baseColor?: ColorObject; partialCoverage?: number; - occlusionTextureStrength?: number; - metallic?: number; - roughness?: number; - normalScale?: number; - emissive?: ColorObject; transmission?: ColorObject; collimation?: number; + occlusionTextureStrength?: number; + roughness?: number; + metallic?: number; + normalScale?: number; + emissive?: ColorObject; ior?: number; baseColorPartialCoverageTexture?: Texture2D | null; - occlusionMetallicRoughnessTexture?: Texture2D | null; + occlusionTexture?: Texture2D | null; + metallicRoughnessTexture?: Texture2D | null; normalTexture?: Texture2D | null; emissiveTexture?: Texture2D | null; transmissionCollimationTexture?: Texture2D | null; @@ -53,7 +54,8 @@ export class Material { _ior: number; _baseColorPartialCoverageTexture: Texture2D | null; - _occlusionMetallicRoughnessTexture: Texture2D | null; + _occlusionTexture: Texture2D | null; + _metallicRoughnessTexture: Texture2D | null; _normalTexture: Texture2D | null; _emissiveTexture: Texture2D | null; _transmissionCollimationTexture: Texture2D | null; @@ -74,7 +76,8 @@ export class Material { collimation = 1, ior = 1.45, baseColorPartialCoverageTexture = null, - occlusionMetallicRoughnessTexture = null, + occlusionTexture = null, + metallicRoughnessTexture = null, normalTexture = null, emissiveTexture = null, transmissionCollimationTexture = null, @@ -97,7 +100,8 @@ export class Material { this._ior = ior; this._baseColorPartialCoverageTexture = baseColorPartialCoverageTexture; - this._occlusionMetallicRoughnessTexture = occlusionMetallicRoughnessTexture; + this._occlusionTexture = occlusionTexture; + this._metallicRoughnessTexture = metallicRoughnessTexture; this._normalTexture = normalTexture; this._emissiveTexture = emissiveTexture; this._transmissionCollimationTexture = transmissionCollimationTexture; diff --git a/src/shader.ts b/src/shader.ts new file mode 100644 index 0000000..18e0c7e --- /dev/null +++ b/src/shader.ts @@ -0,0 +1,136 @@ +export interface ShaderFlags { + texCoord: boolean; + lightTexCoord: boolean; + normal: boolean; + tangent: boolean; +} + +export function createShaderCode({ + texCoord, + lightTexCoord, + normal, + tangent, +}: ShaderFlags): string { + let vertexLocation = 0; + let varyingLocation = 0; + + return ` +struct Vertex { + @location(${vertexLocation++}) positionOS: vec3, + ${texCoord ? `@location(${vertexLocation++}) texCoord: vec2,` : ""} + ${lightTexCoord ? `@location(${vertexLocation++}) lightTexCoord: vec2,` : ""} + ${normal ? `@location(${vertexLocation++}) normalOS: vec3,` : ""} + ${normal && tangent ? `@location(${vertexLocation++}) tangentOS: vec4,` : ""} +} + +struct Varyings { + @builtin(position) positionCS: vec4, + @location(${varyingLocation++}) positionVS: vec4, + ${texCoord ? `@location(${varyingLocation++}) texCoord: vec2,` : ""} + ${lightTexCoord ? `@location(${varyingLocation++}) lightTexCoord: vec2,` : ""} + ${normal ? `@location(${varyingLocation++}) normalVS: vec3,` : ""} + ${normal && tangent ? `@location(${varyingLocation++}) tangentVS: vec3,` : ""} + ${normal && tangent ? `@location(${varyingLocation++}) bitangentVS: vec3,` : ""} +} + +struct PointLight { + positionWS: vec3, + color: vec3, +} + +struct DirectionalLight { + directionWS: vec3, + color: vec3, +} + +struct GlobalUniforms { + matrixWStoVS: mat4x4, + matrixVStoCS: mat4x4, + ambientLight: vec3, + pointLightCount: u32, + directionalLightCount: u32, +} + +struct MaterialUniforms { + baseColor: vec3, + partialCoverage: f32, + transmission: vec3, + collimation: f32, + occlusionTextureStrength: f32, + roughness: f32, + metallic: f32, + normalScale: f32, + emissive: vec3, + ior: f32, +} + +struct ObjectUniforms { + matrixOStoWS: mat4x4, + matrixOStoWSNormal: mat4x4, +} + +@group(0) @binding(0) var _Global: GlobalUniforms; +@group(1) @binding(0) var _Material: MaterialUniforms; +@group(2) @binding(0) var _Object: ObjectUniforms; + +@group(0) @binding(1) var _PointLights: array; +@group(0) @binding(2) var _DirectionalLights: array; + +@group(1) @binding(1) var _Sampler: sampler; +@group(1) @binding(2) var _BaseColorPartialCoverageTexture: texture_2d; +@group(1) @binding(3) var _OcclusionTexture: texture_2d; +@group(1) @binding(4) var _RoughnessMetallicTexture: texture_2d; +@group(1) @binding(5) var _NormalTexture: texture_2d; +@group(1) @binding(6) var _EmissiveTexture: texture_2d; +@group(1) @binding(7) var _TransmissionCollimationTexture: texture_2d; + +@vertex +fn vert(vertex: Vertex) -> Varyings { + var output: Varyings; + let positionWS = (_Object.matrixOStoWS * vec4(vertex.positionOS, 1.0)).xyz; + let positionVS = (_Global.matrixWStoVS * vec4(positionWS, 1.0)).xyz; + let positionCS = _Global.matrixVStoCS * vec4(positionVS, 1.0); + output.positionCS = positionCS; + output.positionVS = positionVS; + ${normal ? ` + let normalWS = normalize((_Object.matrixOStoWSNormal * vec4(vertex.normalOS, 0.0)).xyz); + let normalVS = normalize((_Global.matrixWStoVS * vec4(normalWS, 0.0)).xyz); + output.normalVS = normalVS; + ${tangent ? ` + let tangentWS = normalize((_Object.matrixOStoWS * vec4(vertex.tangentOS.xyz, 0.0)).xyz); + let tangentVS = normalize((_Global.matrixWStoVS * vec4(tangentWS, 0.0)).xyz); + let bitangentVS = vertex.tangentOS.w * normalize(cross(normalVS, tangentVS)); + output.tangentVS = tangentVS; + output.bitangentVS = bitangentVS; + ` : ""} + ` : ""} + ${texCoord ? "output.texCoord = vertex.texCoord;" : ""} + ${lightTexCoord ? "output.lightTexCoord = vertex.lightTexCoord;" : ""} +} + +@fragment +fn frag(fragment: Varyings) -> @location(0) vec2 { + var baseColor = _Material.baseColor; + var partialCoverage = _Material.partialCoverage; + var occlusion = 1.0; + var roughness = _Material.roughness; + var metallic = _Material.metallic; + var normalScale = _Material.normalScale; + var emissive = _Material.emissive; + var ior = _Material.ior; + ${texCoord ? ` + let baseColorPartialCoverageTexel = texture(_BaseColorPartialCoverageTexture, _Sampler, fragment.texCoord); + baseColor *= baseColorPartialCoverageTexel.rgb; + partialCoverage *= baseColorPartialCoverageTexel.a; + let roughnessMetallicTexel = texture(_RoughnessMetallic, _Sampler, fragment.texCoord); + roughness *= roughnessMetallicTexel.g; + metallic *= roughnessMetallicTexel.b; + let emissiveTexel = texture(_EmissiveTexture, _Sampler, fragment.texCoord); + emissive *= emissiveTexel.rgb; + ` : ""} + ${lightTexCoord ? ` + let occlusionTexel = texture(_OcclusionTexture, _Sampler, fragment.lightTexCoord); + occlusion += _Material.occlusionTextureStrength * (occlusionTexel.r - 1.0); + ` : ""} +}`; +}