Compare commits
	
		
			2 Commits
		
	
	
		
			1541d0900b
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | d28d7896de | ||
| 78683f6115 | 
							
								
								
									
										2
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| *.webm filter=lfs diff=lfs merge=lfs -text | ||||
| *.png filter=lfs diff=lfs merge=lfs -text | ||||
| @@ -1,7 +1,8 @@ | ||||
| # oktaeder | ||||
| 3D rendering library for WebGPU | ||||
|  | ||||
| [oktaeder.webm](https://github.com/iszn11/oktaeder/assets/7891270/5dbcb03a-608f-41b8-860e-8e9c8e09e242) | ||||
| <video src="https://gitea.renati.me/renati/oktaeder/media/branch/main/oktaeder.webm" autoplay controls loop> | ||||
| </video> | ||||
|  | ||||
| This project ships with [bun.lockb](https://bun.sh/docs/install/lockfile) | ||||
| lockfile for the [Bun](https://bun.sh/) JavaScript runtime. You should be able | ||||
|   | ||||
| @@ -1,4 +1,6 @@ | ||||
| import { Color, DirectionalLight, Mesh, Node, PerspectiveCamera, PointLight, Quaternion, Scene, Submesh, Vector3 } from "../src/data/index"; | ||||
| /// <reference types="../node_modules/@webgpu/types" /> | ||||
|  | ||||
| import { Color, Mesh, Node, PerspectiveCamera, PointLight, Quaternion, Scene, Submesh, Vector3 } from "../src/data/index"; | ||||
| import { Renderer, degToRad } from "../src/oktaeder"; | ||||
| import "./style.css"; | ||||
|  | ||||
| @@ -16,38 +18,74 @@ const camera = new PerspectiveCamera({ | ||||
| 	farPlane: Infinity, | ||||
| }); | ||||
|  | ||||
| const vertexBuffer = renderer.createVertexBuffer({ vertexCount: 6 }); | ||||
| const vertexBuffer = renderer.createVertexBuffer({ vertexCount: 12, texCoord: true }); | ||||
| vertexBuffer.writeTypedArray(0, { | ||||
| 	position: new Float32Array([ | ||||
| 		0, 0, 1, | ||||
| 		1, 0, 0, | ||||
| 		0, 1, 0, | ||||
| 		-1, 0, 0, | ||||
| 		0, 0, -1, | ||||
| 		0, 0, -1, | ||||
| 		0, 0, -1, | ||||
| 		1, 0, 0, | ||||
| 		0, -1, 0, | ||||
| 		0, 1, 0, | ||||
| 		-1, 0, 0, | ||||
| 		0, 0, -1, | ||||
| 		0, 0, 1, | ||||
| 	]), | ||||
| 	texCoord: new Float32Array([ | ||||
| 		0.5, 0.7113, | ||||
| 		0.333333, 1, | ||||
| 		0.166666, 0.7113, | ||||
| 		0.333333, 0.4226, | ||||
| 		0, 0.4226, | ||||
| 		0, 1, | ||||
| 		1, 1, | ||||
| 		0.666666, 1, | ||||
| 		0.833333, 0.7113, | ||||
| 		0.666666, 0.4226, | ||||
| 		1, 0.4226, | ||||
| 		0.5, 0.7113, | ||||
| 	]), | ||||
| }); | ||||
|  | ||||
| const indexBuffer = renderer.createIndexBuffer({ indexCount: 24, indexFormat: "uint16" }); | ||||
| indexBuffer.writeArray(0, [ | ||||
| 	0, 4, 3, | ||||
| 	4, 1, 3, | ||||
| 	1, 5, 3, | ||||
| 	5, 0, 3, | ||||
| 	4, 0, 2, | ||||
| 	1, 4, 2, | ||||
| 	0, 2, 1, | ||||
| 	3, 4, 2, | ||||
| 	5, 1, 2, | ||||
| 	0, 5, 2, | ||||
| 	2, 0, 3, | ||||
| 	6, 8, 7, | ||||
| 	9, 8, 10, | ||||
| 	7, 8, 11, | ||||
| 	11, 8, 9, | ||||
| ]); | ||||
|  | ||||
| const submesh: Submesh = { start: 0, length: 24 }; | ||||
|  | ||||
| const mesh = new Mesh({ vertexBuffer, indexBuffer, submeshes: [submesh] }); | ||||
|  | ||||
| const imageBitmap = await loadImageBitmap("/uvmap.png"); | ||||
|  | ||||
| const texture = renderer.createTexture({ | ||||
| 	format: "srgb", | ||||
| 	width: imageBitmap.width, | ||||
| 	height: imageBitmap.height, | ||||
| 	usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, | ||||
| }); | ||||
|  | ||||
| renderer._device.queue.copyExternalImageToTexture( | ||||
| 	{ source: imageBitmap, flipY: false }, | ||||
| 	{ texture: texture._texture }, | ||||
| 	{ width: imageBitmap.width, height: imageBitmap.height }, | ||||
| ); | ||||
|  | ||||
| const material = renderer.createMaterial({ | ||||
| 	baseColor: Color.white(), | ||||
| 	baseColorPartialCoverageTexture: texture, | ||||
| 	roughness: 0.5, | ||||
| 	metallic: 1, | ||||
| 	metallic: 0, | ||||
| }); | ||||
|  | ||||
| const node = new Node({ mesh, materials: [material] }); | ||||
| @@ -55,25 +93,13 @@ const node = new Node({ mesh, materials: [material] }); | ||||
| const scene = new Scene({ | ||||
| 	nodes: [ | ||||
| 		node, | ||||
| 		new Node({ | ||||
| 			translation: new Vector3(-1, 1, 0), | ||||
| 			light: new PointLight({ color: new Color(1, 0, 0) }), | ||||
| 		}), | ||||
| 		new Node({ | ||||
| 			translation: new Vector3(0, 1, -1), | ||||
| 			light: new PointLight({ color: new Color(0, 1, 0) }), | ||||
| 			light: new PointLight({ color: new Color(1, 1, 1) }), | ||||
| 		}), | ||||
| 		new Node({ | ||||
| 			translation: new Vector3(1, 1, 0), | ||||
| 			light: new PointLight({ color: new Color(0, 0, 1) }), | ||||
| 		}), | ||||
| 		new Node({ | ||||
| 			translation: new Vector3(0, 1, 1), | ||||
| 			light: new PointLight({ color: new Color(1, 1, 0) }), | ||||
| 		}), | ||||
| 		new Node({ | ||||
| 			rotation: Quaternion.fromRotationYZ(degToRad(-90)), | ||||
| 			light: new DirectionalLight({ color: new Color(0.5, 0.5, 0.5) }), | ||||
| 			translation: new Vector3(0, -1, -1), | ||||
| 			light: new PointLight({ color: new Color(1, 1, 1) }), | ||||
| 		}), | ||||
| 		new Node({ | ||||
| 			translation: new Vector3(0, 0.8, -3), | ||||
| @@ -91,6 +117,14 @@ function onResize(this: Window) { | ||||
|  | ||||
| const _quaternion = Quaternion.identity(); | ||||
|  | ||||
| async function loadImageBitmap(url: string) { | ||||
| 	const res = await fetch(url); | ||||
| 	const blob = await res.blob(); | ||||
| 	const imageBitmap = await createImageBitmap(blob, { colorSpaceConversion: "none" }); | ||||
|  | ||||
| 	return imageBitmap; | ||||
| } | ||||
|  | ||||
| function draw(timeMs: number) { | ||||
| 	const time = 0.001 * timeMs; | ||||
| 	node.setRotation(_quaternion.setRotationZX(-0.5 * time)); | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								example/uvmap.png
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								example/uvmap.png
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								oktaeder.webm
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								oktaeder.webm
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -22,12 +22,12 @@ | ||||
| 		"build": "tsc --build" | ||||
| 	}, | ||||
| 	"dependencies": { | ||||
| 		"tslib": "^2.6.1" | ||||
| 		"tslib": "^2.6.2" | ||||
| 	}, | ||||
| 	"devDependencies": { | ||||
| 		"@webgpu/types": "^0.1.34", | ||||
| 		"esbuild": "^0.19.2", | ||||
| 		"typescript": "5.1.6" | ||||
| 		"@webgpu/types": "^0.1.40", | ||||
| 		"esbuild": "^0.20.2", | ||||
| 		"typescript": "^5.4.2" | ||||
| 	}, | ||||
| 	"exports": { | ||||
| 		".": { | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
|  */ | ||||
|  | ||||
| import * as data from "./data"; | ||||
| import * as resources from "./resources"; | ||||
|  | ||||
| /* INITIAL SUPPORT PLAN | ||||
|  * | ||||
| @@ -94,7 +95,7 @@ import * as data from "./data"; | ||||
|  | ||||
| export interface ParseResult { | ||||
| 	readonly cameras: readonly data.Camera[]; | ||||
| 	readonly materials: readonly data.Material[]; | ||||
| 	readonly materials: readonly resources.Material[]; | ||||
| 	readonly lights: readonly data.Light[]; | ||||
| 	readonly scenes: readonly data.Scene[]; | ||||
| 	readonly scene: data.Scene | null; | ||||
| @@ -181,7 +182,7 @@ export async function parse(gltf: ArrayBufferView, { | ||||
| }: ParseOptions = {}): Promise<ParseResult> { | ||||
|  | ||||
| 	const cameras: data.Camera[] = []; | ||||
| 	const materials: data.Material[] = []; | ||||
| 	const materials: resources.Material[] = []; | ||||
| 	const lights: data.Light[] = []; | ||||
| 	const scenes: data.Scene[] = []; | ||||
| 	const scene: data.Scene | null = null; | ||||
| @@ -267,6 +268,10 @@ export async function parse(gltf: ArrayBufferView, { | ||||
|  | ||||
| 	// --- JSON CHUNK ---------------------------------------------------------- | ||||
|  | ||||
| 	void(stopOnFirstError); | ||||
| 	void(treatWarningsAsErrors); | ||||
| 	void(rest); | ||||
|  | ||||
| 	throw new Error("TODO"); | ||||
|  | ||||
| 	// --- BIN CHUNK ----------------------------------------------------------- | ||||
|   | ||||
| @@ -49,7 +49,7 @@ export class Renderer { | ||||
| 	_textureWhite: Texture2D; | ||||
| 	/** 1×1 rgba8unorm texture of [0, 0, 0, 255] */ | ||||
| 	_textureBlack: Texture2D; | ||||
| 	/** 1×1 rgba8unorm texture of [128, 128, 128, 255] */ | ||||
| 	/** 1×1 rgba8unorm texture of [128, 128, 255, 255] */ | ||||
| 	_textureNormal: Texture2D; | ||||
|  | ||||
| 	_depthBuffer: Texture2D; | ||||
| @@ -110,7 +110,7 @@ export class Renderer { | ||||
| 			height: 1, | ||||
| 			format: "linear", | ||||
| 		}); | ||||
| 		this._textureNormal.writeFull(new Uint8Array([128, 128, 128, 255])); | ||||
| 		this._textureNormal.writeFull(new Uint8Array([128, 128, 255, 255])); | ||||
|  | ||||
| 		const framebufferTexture = this._context.getCurrentTexture(); | ||||
| 		this._depthBuffer = new Texture2D(this, { | ||||
|   | ||||
| @@ -46,10 +46,10 @@ export function _createPipeline(renderer: Renderer, { | ||||
|  | ||||
| 	const shaderModule = renderer._device.createShaderModule({ | ||||
| 		code: shaderCode, | ||||
| 		hints: { | ||||
| 			"vert": { layout: renderer._pipelineLayout }, | ||||
| 			"frag": { layout: renderer._pipelineLayout }, | ||||
| 		}, | ||||
| 		compilationHints: [ | ||||
| 			{ entryPoint: "vert", layout: renderer._pipelineLayout }, | ||||
| 			{ entryPoint: "frag", layout: renderer._pipelineLayout }, | ||||
| 		], | ||||
| 	}); | ||||
|  | ||||
| 	let vertexLocation = 0; | ||||
| @@ -318,17 +318,17 @@ fn frag(fragment: Varyings) -> @location(0) vec4<f32> { | ||||
| 	var emissive = _Material.emissive; | ||||
| 	var ior = _Material.ior; | ||||
| 	${texCoord ? ` | ||||
| 		let baseColorPartialCoverageTexel = texture(_BaseColorPartialCoverageTexture, _Sampler, fragment.texCoord); | ||||
| 		let baseColorPartialCoverageTexel = textureSample(_BaseColorPartialCoverageTexture, _Sampler, fragment.texCoord); | ||||
| 		baseColor *= baseColorPartialCoverageTexel.rgb; | ||||
| 		partialCoverage *= baseColorPartialCoverageTexel.a; | ||||
| 		let roughnessMetallicTexel = texture(_RoughnessMetallicTexture, _Sampler, fragment.texCoord); | ||||
| 		let roughnessMetallicTexel = textureSample(_RoughnessMetallicTexture, _Sampler, fragment.texCoord); | ||||
| 		roughness *= roughnessMetallicTexel.g; | ||||
| 		metallic *= roughnessMetallicTexel.b; | ||||
| 		let emissiveTexel = texture(_EmissiveTexture, _Sampler, fragment.texCoord); | ||||
| 		let emissiveTexel = textureSample(_EmissiveTexture, _Sampler, fragment.texCoord); | ||||
| 		emissive *= emissiveTexel.rgb; | ||||
| 	` : ""} | ||||
| 	${lightTexCoord ? ` | ||||
| 		let occlusionTexel = texture(_OcclusionTexture, _Sampler, fragment.lightTexCoord); | ||||
| 		let occlusionTexel = textureSample(_OcclusionTexture, _Sampler, fragment.lightTexCoord); | ||||
| 		occlusion += _Material.occlusionTextureStrength * (occlusionTexel.r - 1.0); | ||||
| 	` : ""} | ||||
|  | ||||
| @@ -348,10 +348,10 @@ fn frag(fragment: Varyings) -> @location(0) vec4<f32> { | ||||
| 		` : ` | ||||
| 			let matrixTStoVS = screenSpaceMatrixTStoVS(positionVS, geometricNormalVS, fragment.texCoord); | ||||
| 		`} | ||||
| 		let normalTextureTexel = texture(_NormalTexture, _Sampler, fragment.texCoord); | ||||
| 		let normalTextureTexel = textureSample(_NormalTexture, _Sampler, fragment.texCoord); | ||||
| 		var normalTS = normalTextureTexel.xyz * 2.0 - 1.0; | ||||
| 		normalTS.xy *= _Material.normalScale; | ||||
| 		let actualNormalVS = normalize(matrixTStoVS * geometricNormalVS); | ||||
| 		normalTS = vec3(normalTS.xy * _Material.normalScale, normalTS.z); | ||||
| 		let actualNormalVS = normalize(matrixTStoVS * normalTS); | ||||
| 	` : ` | ||||
| 		let actualNormalVS = geometricNormalVS; | ||||
| 	`} | ||||
|   | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Reference in New Issue
	
	Block a user