Compare commits
2 Commits
6e3d68e984
...
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
|
# oktaeder
|
||||||
3D rendering library for WebGPU
|
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)
|
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
|
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 { Renderer, degToRad } from "../src/oktaeder";
|
||||||
import "./style.css";
|
import "./style.css";
|
||||||
|
|
||||||
@@ -16,38 +18,74 @@ const camera = new PerspectiveCamera({
|
|||||||
farPlane: Infinity,
|
farPlane: Infinity,
|
||||||
});
|
});
|
||||||
|
|
||||||
const vertexBuffer = renderer.createVertexBuffer({ vertexCount: 6 });
|
const vertexBuffer = renderer.createVertexBuffer({ vertexCount: 12, texCoord: true });
|
||||||
vertexBuffer.writeTypedArray(0, {
|
vertexBuffer.writeTypedArray(0, {
|
||||||
position: new Float32Array([
|
position: new Float32Array([
|
||||||
|
0, 0, 1,
|
||||||
|
1, 0, 0,
|
||||||
|
0, 1, 0,
|
||||||
-1, 0, 0,
|
-1, 0, 0,
|
||||||
|
0, 0, -1,
|
||||||
|
0, 0, -1,
|
||||||
|
0, 0, -1,
|
||||||
1, 0, 0,
|
1, 0, 0,
|
||||||
0, -1, 0,
|
0, -1, 0,
|
||||||
0, 1, 0,
|
-1, 0, 0,
|
||||||
0, 0, -1,
|
0, 0, -1,
|
||||||
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" });
|
const indexBuffer = renderer.createIndexBuffer({ indexCount: 24, indexFormat: "uint16" });
|
||||||
indexBuffer.writeArray(0, [
|
indexBuffer.writeArray(0, [
|
||||||
0, 4, 3,
|
0, 2, 1,
|
||||||
4, 1, 3,
|
3, 4, 2,
|
||||||
1, 5, 3,
|
|
||||||
5, 0, 3,
|
|
||||||
4, 0, 2,
|
|
||||||
1, 4, 2,
|
|
||||||
5, 1, 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 submesh: Submesh = { start: 0, length: 24 };
|
||||||
|
|
||||||
const mesh = new Mesh({ vertexBuffer, indexBuffer, submeshes: [submesh] });
|
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({
|
const material = renderer.createMaterial({
|
||||||
baseColor: Color.white(),
|
baseColor: Color.white(),
|
||||||
|
baseColorPartialCoverageTexture: texture,
|
||||||
roughness: 0.5,
|
roughness: 0.5,
|
||||||
metallic: 1,
|
metallic: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const node = new Node({ mesh, materials: [material] });
|
const node = new Node({ mesh, materials: [material] });
|
||||||
@@ -55,25 +93,13 @@ const node = new Node({ mesh, materials: [material] });
|
|||||||
const scene = new Scene({
|
const scene = new Scene({
|
||||||
nodes: [
|
nodes: [
|
||||||
node,
|
node,
|
||||||
new Node({
|
|
||||||
translation: new Vector3(-1, 1, 0),
|
|
||||||
light: new PointLight({ color: new Color(1, 0, 0) }),
|
|
||||||
}),
|
|
||||||
new Node({
|
new Node({
|
||||||
translation: new Vector3(0, 1, -1),
|
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({
|
new Node({
|
||||||
translation: new Vector3(1, 1, 0),
|
translation: new Vector3(0, -1, -1),
|
||||||
light: new PointLight({ color: new Color(0, 0, 1) }),
|
light: new PointLight({ color: new Color(1, 1, 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) }),
|
|
||||||
}),
|
}),
|
||||||
new Node({
|
new Node({
|
||||||
translation: new Vector3(0, 0.8, -3),
|
translation: new Vector3(0, 0.8, -3),
|
||||||
@@ -91,6 +117,14 @@ function onResize(this: Window) {
|
|||||||
|
|
||||||
const _quaternion = Quaternion.identity();
|
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) {
|
function draw(timeMs: number) {
|
||||||
const time = 0.001 * timeMs;
|
const time = 0.001 * timeMs;
|
||||||
node.setRotation(_quaternion.setRotationZX(-0.5 * time));
|
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"
|
"build": "tsc --build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.6.1"
|
"tslib": "^2.6.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@webgpu/types": "^0.1.34",
|
"@webgpu/types": "^0.1.40",
|
||||||
"esbuild": "^0.19.2",
|
"esbuild": "^0.20.2",
|
||||||
"typescript": "5.1.6"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as data from "./data";
|
import * as data from "./data";
|
||||||
|
import * as resources from "./resources";
|
||||||
|
|
||||||
/* INITIAL SUPPORT PLAN
|
/* INITIAL SUPPORT PLAN
|
||||||
*
|
*
|
||||||
@@ -94,7 +95,7 @@ import * as data from "./data";
|
|||||||
|
|
||||||
export interface ParseResult {
|
export interface ParseResult {
|
||||||
readonly cameras: readonly data.Camera[];
|
readonly cameras: readonly data.Camera[];
|
||||||
readonly materials: readonly data.Material[];
|
readonly materials: readonly resources.Material[];
|
||||||
readonly lights: readonly data.Light[];
|
readonly lights: readonly data.Light[];
|
||||||
readonly scenes: readonly data.Scene[];
|
readonly scenes: readonly data.Scene[];
|
||||||
readonly scene: data.Scene | null;
|
readonly scene: data.Scene | null;
|
||||||
@@ -181,7 +182,7 @@ export async function parse(gltf: ArrayBufferView, {
|
|||||||
}: ParseOptions = {}): Promise<ParseResult> {
|
}: ParseOptions = {}): Promise<ParseResult> {
|
||||||
|
|
||||||
const cameras: data.Camera[] = [];
|
const cameras: data.Camera[] = [];
|
||||||
const materials: data.Material[] = [];
|
const materials: resources.Material[] = [];
|
||||||
const lights: data.Light[] = [];
|
const lights: data.Light[] = [];
|
||||||
const scenes: data.Scene[] = [];
|
const scenes: data.Scene[] = [];
|
||||||
const scene: data.Scene | null = null;
|
const scene: data.Scene | null = null;
|
||||||
@@ -267,6 +268,10 @@ export async function parse(gltf: ArrayBufferView, {
|
|||||||
|
|
||||||
// --- JSON CHUNK ----------------------------------------------------------
|
// --- JSON CHUNK ----------------------------------------------------------
|
||||||
|
|
||||||
|
void(stopOnFirstError);
|
||||||
|
void(treatWarningsAsErrors);
|
||||||
|
void(rest);
|
||||||
|
|
||||||
throw new Error("TODO");
|
throw new Error("TODO");
|
||||||
|
|
||||||
// --- BIN CHUNK -----------------------------------------------------------
|
// --- BIN CHUNK -----------------------------------------------------------
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export class Renderer {
|
|||||||
_textureWhite: Texture2D;
|
_textureWhite: Texture2D;
|
||||||
/** 1×1 rgba8unorm texture of [0, 0, 0, 255] */
|
/** 1×1 rgba8unorm texture of [0, 0, 0, 255] */
|
||||||
_textureBlack: Texture2D;
|
_textureBlack: Texture2D;
|
||||||
/** 1×1 rgba8unorm texture of [128, 128, 128, 255] */
|
/** 1×1 rgba8unorm texture of [128, 128, 255, 255] */
|
||||||
_textureNormal: Texture2D;
|
_textureNormal: Texture2D;
|
||||||
|
|
||||||
_depthBuffer: Texture2D;
|
_depthBuffer: Texture2D;
|
||||||
@@ -110,7 +110,7 @@ export class Renderer {
|
|||||||
height: 1,
|
height: 1,
|
||||||
format: "linear",
|
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();
|
const framebufferTexture = this._context.getCurrentTexture();
|
||||||
this._depthBuffer = new Texture2D(this, {
|
this._depthBuffer = new Texture2D(this, {
|
||||||
|
|||||||
@@ -46,10 +46,10 @@ export function _createPipeline(renderer: Renderer, {
|
|||||||
|
|
||||||
const shaderModule = renderer._device.createShaderModule({
|
const shaderModule = renderer._device.createShaderModule({
|
||||||
code: shaderCode,
|
code: shaderCode,
|
||||||
hints: {
|
compilationHints: [
|
||||||
"vert": { layout: renderer._pipelineLayout },
|
{ entryPoint: "vert", layout: renderer._pipelineLayout },
|
||||||
"frag": { layout: renderer._pipelineLayout },
|
{ entryPoint: "frag", layout: renderer._pipelineLayout },
|
||||||
},
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
let vertexLocation = 0;
|
let vertexLocation = 0;
|
||||||
@@ -318,17 +318,17 @@ fn frag(fragment: Varyings) -> @location(0) vec4<f32> {
|
|||||||
var emissive = _Material.emissive;
|
var emissive = _Material.emissive;
|
||||||
var ior = _Material.ior;
|
var ior = _Material.ior;
|
||||||
${texCoord ? `
|
${texCoord ? `
|
||||||
let baseColorPartialCoverageTexel = texture(_BaseColorPartialCoverageTexture, _Sampler, fragment.texCoord);
|
let baseColorPartialCoverageTexel = textureSample(_BaseColorPartialCoverageTexture, _Sampler, fragment.texCoord);
|
||||||
baseColor *= baseColorPartialCoverageTexel.rgb;
|
baseColor *= baseColorPartialCoverageTexel.rgb;
|
||||||
partialCoverage *= baseColorPartialCoverageTexel.a;
|
partialCoverage *= baseColorPartialCoverageTexel.a;
|
||||||
let roughnessMetallicTexel = texture(_RoughnessMetallicTexture, _Sampler, fragment.texCoord);
|
let roughnessMetallicTexel = textureSample(_RoughnessMetallicTexture, _Sampler, fragment.texCoord);
|
||||||
roughness *= roughnessMetallicTexel.g;
|
roughness *= roughnessMetallicTexel.g;
|
||||||
metallic *= roughnessMetallicTexel.b;
|
metallic *= roughnessMetallicTexel.b;
|
||||||
let emissiveTexel = texture(_EmissiveTexture, _Sampler, fragment.texCoord);
|
let emissiveTexel = textureSample(_EmissiveTexture, _Sampler, fragment.texCoord);
|
||||||
emissive *= emissiveTexel.rgb;
|
emissive *= emissiveTexel.rgb;
|
||||||
` : ""}
|
` : ""}
|
||||||
${lightTexCoord ? `
|
${lightTexCoord ? `
|
||||||
let occlusionTexel = texture(_OcclusionTexture, _Sampler, fragment.lightTexCoord);
|
let occlusionTexel = textureSample(_OcclusionTexture, _Sampler, fragment.lightTexCoord);
|
||||||
occlusion += _Material.occlusionTextureStrength * (occlusionTexel.r - 1.0);
|
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 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;
|
var normalTS = normalTextureTexel.xyz * 2.0 - 1.0;
|
||||||
normalTS.xy *= _Material.normalScale;
|
normalTS = vec3(normalTS.xy * _Material.normalScale, normalTS.z);
|
||||||
let actualNormalVS = normalize(matrixTStoVS * geometricNormalVS);
|
let actualNormalVS = normalize(matrixTStoVS * normalTS);
|
||||||
` : `
|
` : `
|
||||||
let actualNormalVS = geometricNormalVS;
|
let actualNormalVS = geometricNormalVS;
|
||||||
`}
|
`}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user