render gltf model
This commit is contained in:
parent
e5cacffaee
commit
2dfd749180
11
common.js
Normal file
11
common.js
Normal file
@ -0,0 +1,11 @@
|
||||
function getPath(path)
|
||||
{
|
||||
return fetch(path).then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`${path}: ${response.status}`)
|
||||
}
|
||||
return response.blob()
|
||||
}).then((blob) => { return blob.text() })
|
||||
}
|
||||
|
||||
export { getPath };
|
||||
162
gltf.js
Normal file
162
gltf.js
Normal file
@ -0,0 +1,162 @@
|
||||
function jsonChunk(buffer, offset, length)
|
||||
{
|
||||
const decoder = new TextDecoder();
|
||||
const array = new Uint8Array(buffer);
|
||||
const object = JSON.parse(decoder.decode(array.subarray(offset, offset + length)));
|
||||
//console.log(object);
|
||||
return object;
|
||||
}
|
||||
|
||||
function parseGltfChunks(buffer)
|
||||
{
|
||||
const view = new DataView(buffer);
|
||||
|
||||
const magic = view.getUint32(0, true);
|
||||
const version = view.getUint32(4, true);
|
||||
const length = view.getUint32(8, true);
|
||||
console.assert(magic === 0x46546C67);
|
||||
console.assert(version === 2);
|
||||
|
||||
const gltf = {};
|
||||
|
||||
var offset = 12;
|
||||
while ((length - offset) > 0) {
|
||||
const chunkLength = view.getUint32(offset + 0, true);
|
||||
const chunkType = view.getUint32(offset + 4, true);
|
||||
console.log("chunkLength", chunkLength);
|
||||
console.log("chunkType", chunkType);
|
||||
offset += 8;
|
||||
|
||||
console.assert(chunkType === 0x4E4F534A || chunkType === 0x004E4942);
|
||||
if (chunkType === 0x4E4F534A) {
|
||||
const object = jsonChunk(buffer, offset, chunkLength);
|
||||
console.assert(!("json" in gltf));
|
||||
gltf.json = object;
|
||||
}
|
||||
if (chunkType === 0x004E4942) {
|
||||
console.assert(!("bin" in gltf));
|
||||
gltf.bin = {buffer: buffer, offset: offset, length: chunkLength};
|
||||
}
|
||||
|
||||
offset += chunkLength;
|
||||
}
|
||||
|
||||
console.assert(offset == length);
|
||||
return gltf;
|
||||
}
|
||||
|
||||
function componentCount(componentType)
|
||||
{
|
||||
switch (componentType) {
|
||||
case "SCALAR": return 1;
|
||||
case "VEC2": return 2;
|
||||
case "VEC3": return 3;
|
||||
case "VEC4": return 4;
|
||||
default:
|
||||
console.assert(false, componentType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function typeStride(type)
|
||||
{
|
||||
switch (type) {
|
||||
case 5120: // signed byte
|
||||
return 1;
|
||||
case 5121: // unsigned byte
|
||||
return 1;
|
||||
case 5122: // signed short
|
||||
return 2;
|
||||
case 5123: // unsigned short
|
||||
return 2;
|
||||
case 5125: // unsigned int
|
||||
return 4;
|
||||
case 5126: // float
|
||||
return 4;
|
||||
default:
|
||||
console.assert(false, type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function attributeStride(componentType, type)
|
||||
{
|
||||
const t = typeStride(componentType);
|
||||
const c = componentCount(type);
|
||||
return c * t;
|
||||
}
|
||||
|
||||
function vertexFormat(componentType, type)
|
||||
{
|
||||
switch (componentType) {
|
||||
case 5120: // signed byte
|
||||
if (type === "SCALAR") return "sint8";
|
||||
else if (type === "VEC2") return "sint8x2";
|
||||
else if (type === "VEC4") return "sint8x4";
|
||||
else console.assert(false, [componentType, type]);
|
||||
case 5121: // unsigned byte
|
||||
if (type === "SCALAR") return "uint8";
|
||||
else if (type === "VEC2") return "uint8x2";
|
||||
else if (type === "VEC4") return "uint8x4";
|
||||
else console.assert(false, [componentType, type]);
|
||||
case 5122: // signed short
|
||||
if (type === "SCALAR") return "sint16";
|
||||
else if (type === "VEC2") return "sint16x2";
|
||||
else if (type === "VEC4") return "sint16x4";
|
||||
else console.assert(false, [componentType, type]);
|
||||
case 5123: // unsigned short
|
||||
if (type === "SCALAR") return "uint16";
|
||||
else if (type === "VEC2") return "uint16x2";
|
||||
else if (type === "VEC4") return "uint16x4";
|
||||
else console.assert(false, [componentType, type]);
|
||||
case 5125: // unsigned int
|
||||
if (type === "SCALAR") return "uint32";
|
||||
else if (type === "VEC2") return "uint32x2";
|
||||
else if (type === "VEC3") return "uint32x3";
|
||||
else if (type === "VEC4") return "uint32x4";
|
||||
else console.assert(false, [componentType, type]);
|
||||
case 5126: // float
|
||||
if (type === "SCALAR") return "float32";
|
||||
else if (type === "VEC2") return "float32x2";
|
||||
else if (type === "VEC3") return "float32x3";
|
||||
else if (type === "VEC4") return "float32x4";
|
||||
else console.assert(false, [componentType, type]);
|
||||
default:
|
||||
console.assert(false, [componentType, type]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function primitiveVertexBufferLayouts(gltf, primitive)
|
||||
{
|
||||
const accessors = [
|
||||
gltf.json.accessors[primitive.attributes["POSITION"]],
|
||||
gltf.json.accessors[primitive.attributes["NORMAL"]],
|
||||
gltf.json.accessors[primitive.attributes["TEXCOORD_0"]],
|
||||
];
|
||||
|
||||
const layouts = [];
|
||||
|
||||
for (let i = 0; i < accessors.length; i++) {
|
||||
const componentType = accessors[i].componentType;
|
||||
const type = accessors[i].type;
|
||||
|
||||
const bufferView = gltf.json.bufferViews[accessors[i].bufferView];
|
||||
|
||||
const arrayStride = "byteStride" in bufferView ? bufferView.byteStride : attributeStride(componentType, type);
|
||||
const offset = "byteOffset" in accessors[i] ? accessors[i].byteOffset : 0;
|
||||
const layout = {
|
||||
arrayStride: arrayStride,
|
||||
attributes: [{
|
||||
format: vertexFormat(componentType, type),
|
||||
offset: offset,
|
||||
shaderLocation: i,
|
||||
}],
|
||||
stepMode: "vertex",
|
||||
};
|
||||
layouts.push(layout);
|
||||
}
|
||||
return layouts;
|
||||
}
|
||||
|
||||
export { parseGltfChunks, primitiveVertexBufferLayouts };
|
||||
82
gltf.wgsl
Normal file
82
gltf.wgsl
Normal file
@ -0,0 +1,82 @@
|
||||
struct Configuration {
|
||||
matrix: mat4x4f,
|
||||
matrixWorld: mat4x4f,
|
||||
};
|
||||
|
||||
@group(0) @binding(0) var<uniform> config: Configuration;
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec3f,
|
||||
@location(1) normal: vec3f,
|
||||
@location(2) texture: vec2f,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) positionWorld: vec3f,
|
||||
@location(1) normal: vec3f,
|
||||
};
|
||||
|
||||
struct FragmentInput {
|
||||
@location(0) positionWorld: vec3f,
|
||||
@location(1) normal: vec3f,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vertexMain(input: VertexInput) -> VertexOutput
|
||||
{
|
||||
let position = vec4f(input.position, 1);
|
||||
let normal = vec4f(input.normal, 0.0f);
|
||||
var output: VertexOutput;
|
||||
output.position = config.matrix * position;
|
||||
output.positionWorld = (config.matrixWorld * position).xyz;
|
||||
output.normal = (config.matrixWorld * normal).xyz;
|
||||
return output;
|
||||
}
|
||||
|
||||
fn schlickFresnel(R0: vec3f, normal: vec3f, lightVector: vec3f) -> vec3f
|
||||
{
|
||||
let incidentAngle = saturate(dot(normal, lightVector));
|
||||
|
||||
let f0 = 1.0 - incidentAngle;
|
||||
let reflectPercent = R0 + (1.0f - R0) * (f0 * f0 * f0 * f0 * f0);
|
||||
|
||||
return reflectPercent;
|
||||
}
|
||||
|
||||
fn blinnPhong(ndotl: f32, lightVector: vec3f, normal: vec3f, toEye: vec3f) -> vec3f
|
||||
{
|
||||
let roughness = 0.1;
|
||||
|
||||
let m = roughness * 256.0;
|
||||
let halfVec = normalize(toEye + lightVector);
|
||||
let roughnessFactor = (m + 8.0) * pow(ndotl, m) / 8.0f;
|
||||
|
||||
let fresnelR0 = vec3f(0.95f, 0.95f, 0.95f);
|
||||
let fresnelFactor = schlickFresnel(fresnelR0, normal, lightVector);
|
||||
|
||||
let specular = fresnelFactor * roughnessFactor;
|
||||
|
||||
return specular;
|
||||
}
|
||||
|
||||
fn lighting(position: vec3f, normal: vec3f) -> vec3f
|
||||
{
|
||||
let d = 0.9;
|
||||
let eyePosition = vec3f(d, d / 2, d);
|
||||
let lightPosition = vec3f(1, 1, 1);
|
||||
let lightVector = normalize(lightPosition - position);
|
||||
let ndotl = max(dot(lightVector, normal), 0.0);
|
||||
let intensity = (ndotl + 0.3) / 1.3;
|
||||
|
||||
let toEye = normalize(eyePosition - position);
|
||||
|
||||
return (1.0 + blinnPhong(ndotl, lightVector, normal, toEye)) * intensity;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragmentMain(input: FragmentInput) -> @location(0) vec4f
|
||||
{
|
||||
let intensity = lighting(input.positionWorld, normalize(input.normal));
|
||||
return vec4(intensity, 1.0);
|
||||
}
|
||||
@ -59,6 +59,7 @@
|
||||
}
|
||||
</style>
|
||||
<script src="index.js" type="module"></script>
|
||||
<!--<script src="index2.js" type="module"></script>-->
|
||||
</head>
|
||||
<body>
|
||||
<canvas></canvas>
|
||||
|
||||
39
index.js
39
index.js
@ -1,3 +1,6 @@
|
||||
import { getPath } from "./common.js";
|
||||
import { loadGltf } from "./index2.js";
|
||||
|
||||
if (!navigator.gpu) {
|
||||
throw new Error("WebGPU not supported on this browser.");
|
||||
}
|
||||
@ -154,16 +157,6 @@ const vertexBufferLayouts = [
|
||||
},
|
||||
];
|
||||
|
||||
function getPath(path)
|
||||
{
|
||||
return fetch(path).then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`${path}: ${response.status}`)
|
||||
}
|
||||
return response.blob()
|
||||
}).then((blob) => { return blob.text() })
|
||||
}
|
||||
|
||||
const indexWgsl = getPath("index.wgsl");
|
||||
const computeWgsl = getPath("compute.wgsl");
|
||||
|
||||
@ -190,7 +183,7 @@ const bindGroupLayouts = [
|
||||
}]
|
||||
}),
|
||||
device.createBindGroupLayout({
|
||||
label: "bind group layout 0",
|
||||
label: "bind group layout 1",
|
||||
entries: [{
|
||||
binding: 0,
|
||||
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
||||
@ -360,7 +353,7 @@ modelSelect.value = currentModel;
|
||||
const lightingSelect = document.getElementById("lighting-select");
|
||||
lightingSelect.value = "unlit";
|
||||
|
||||
function render()
|
||||
function recreateDepth()
|
||||
{
|
||||
if (canvas.clientWidth !== canvas.width || canvas.clientWidth !== canvas.height) {
|
||||
canvas.width = canvas.clientWidth;
|
||||
@ -371,6 +364,11 @@ function render()
|
||||
}
|
||||
depthTexture = createDepthTexture();
|
||||
}
|
||||
}
|
||||
|
||||
function render()
|
||||
{
|
||||
recreateDepth();
|
||||
|
||||
const encoder = device.createCommandEncoder();
|
||||
|
||||
@ -432,4 +430,19 @@ function render()
|
||||
|
||||
requestAnimationFrame(render)
|
||||
}
|
||||
requestAnimationFrame(render)
|
||||
//requestAnimationFrame(render);
|
||||
|
||||
const gltfRenderer = await loadGltf(device, canvasFormat);
|
||||
function render2()
|
||||
{
|
||||
recreateDepth();
|
||||
const colorView = context.getCurrentTexture().createView();
|
||||
|
||||
const aspect = canvas.width / canvas.height;
|
||||
rotate.instance.exports.rotate(tick * 0.01, aspect, 0);
|
||||
device.queue.writeBuffer(gltfRenderer.uniformBuffer, 0, uniformArray, 0, gltfRenderer.uniformBufferSize / 4);
|
||||
|
||||
gltfRenderer.render(device, colorView, depthTexture);
|
||||
requestAnimationFrame(render2);
|
||||
}
|
||||
requestAnimationFrame(render2);
|
||||
|
||||
161
index2.js
Normal file
161
index2.js
Normal file
@ -0,0 +1,161 @@
|
||||
import { getPath } from "./common.js";
|
||||
import { parseGltfChunks, primitiveVertexBufferLayouts } from "./gltf.js";
|
||||
|
||||
class GltfRenderer {
|
||||
constructor(device, canvasFormat, shaderModule, gltf, primitive, bufferLayouts)
|
||||
{
|
||||
this.gltf = gltf;
|
||||
this.primitive = primitive;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// buffer
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
console.assert(gltf.json.buffers[0].byteLength == gltf.bin.length);
|
||||
this.vertexBuffer = device.createBuffer({
|
||||
label: "gltf vertex buffer 0",
|
||||
size: gltf.bin.length,
|
||||
usage: GPUBufferUsage.INDEX | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
device.queue.writeBuffer(this.vertexBuffer, 0, gltf.bin.buffer, gltf.bin.offset, gltf.bin.length);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// pipeline
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
const bindGroupLayouts = [
|
||||
device.createBindGroupLayout({
|
||||
label: "gltf bind group layout 0",
|
||||
entries: [{
|
||||
binding: 0,
|
||||
visibility: GPUShaderStage.VERTEX,
|
||||
buffer: { type: "uniform" }
|
||||
}]
|
||||
}),
|
||||
];
|
||||
|
||||
const pipelineLayout = device.createPipelineLayout({
|
||||
label: "gltf pipeline layout",
|
||||
bindGroupLayouts: bindGroupLayouts,
|
||||
});
|
||||
|
||||
this.renderPipeline = device.createRenderPipeline({
|
||||
label: "gltf pipeline",
|
||||
layout: pipelineLayout,
|
||||
vertex: {
|
||||
module: shaderModule,
|
||||
entryPoint: "vertexMain",
|
||||
buffers: bufferLayouts,
|
||||
},
|
||||
fragment: {
|
||||
module: shaderModule,
|
||||
entryPoint: "fragmentMain",
|
||||
targets: [{
|
||||
format: canvasFormat,
|
||||
}]
|
||||
},
|
||||
primitive: {
|
||||
topology: 'triangle-list',
|
||||
},
|
||||
depthStencil: {
|
||||
depthWriteEnabled: true,
|
||||
depthCompare: 'less',
|
||||
format: 'depth24plus',
|
||||
},
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// bind group
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
this.uniformBufferSize = (4 * 4 * 4) * 2;
|
||||
this.uniformBuffer = device.createBuffer({
|
||||
label: "gltf uniform 0",
|
||||
size: this.uniformBufferSize,
|
||||
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
|
||||
this.bindGroups = [
|
||||
device.createBindGroup({
|
||||
label: "cell bind group 0",
|
||||
layout: bindGroupLayouts[0],
|
||||
entries: [{
|
||||
binding: 0,
|
||||
resource: { buffer: this.uniformBuffer },
|
||||
}]
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
render(device, colorView, depthTexture)
|
||||
{
|
||||
const encoder = device.createCommandEncoder();
|
||||
const renderPass = encoder.beginRenderPass({
|
||||
colorAttachments: [{
|
||||
view: colorView,
|
||||
loadOp: "clear",
|
||||
clearValue: { r: 0.2, g: 0.2, b: 0.4, a: 1 },
|
||||
storeOp: "store",
|
||||
}],
|
||||
depthStencilAttachment: {
|
||||
view: depthTexture.createView(),
|
||||
depthClearValue: 1.0,
|
||||
depthLoadOp: 'clear',
|
||||
depthStoreOp: 'store',
|
||||
},
|
||||
});
|
||||
|
||||
renderPass.setPipeline(this.renderPipeline);
|
||||
|
||||
const attributes = ["POSITION", "NORMAL", "TEXCOORD_0"];
|
||||
for (let i = 0; i < attributes.length; i++) {
|
||||
const attributeIndex = this.primitive.attributes[attributes[i]];
|
||||
const bufferViewIndex = this.gltf.json.accessors[attributeIndex].bufferView;
|
||||
const bufferView = this.gltf.json.bufferViews[bufferViewIndex];
|
||||
|
||||
renderPass.setVertexBuffer(i, this.vertexBuffer, bufferView.byteOffset, bufferView.byteLength);
|
||||
}
|
||||
|
||||
const indicesAccessor = this.gltf.json.accessors[this.primitive.indices];
|
||||
const indicesBufferView = this.gltf.json.bufferViews[indicesAccessor.bufferView];
|
||||
renderPass.setIndexBuffer(this.vertexBuffer,
|
||||
"uint16",
|
||||
indicesBufferView.byteOffset,
|
||||
indicesBufferView.byteLength);
|
||||
|
||||
renderPass.setBindGroup(0, this.bindGroups[0]);
|
||||
renderPass.drawIndexed(indicesAccessor.count);
|
||||
|
||||
renderPass.end();
|
||||
|
||||
const commandBuffer = encoder.finish();
|
||||
device.queue.submit([commandBuffer]);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadGltf(device, canvasFormat)
|
||||
{
|
||||
const response = await fetch("monkey.glb");
|
||||
const buffer = await response.arrayBuffer();
|
||||
const gltf = parseGltfChunks(buffer);
|
||||
console.log(gltf);
|
||||
|
||||
const primitive = gltf.json.meshes[0].primitives[0];
|
||||
//console.log(primitive.indices);
|
||||
//console.log(primitive.attributes);
|
||||
|
||||
const bufferLayouts = primitiveVertexBufferLayouts(gltf, primitive);
|
||||
console.log(bufferLayouts);
|
||||
|
||||
const code = await getPath("gltf.wgsl");
|
||||
const shaderModule = device.createShaderModule({
|
||||
label: "gltf module",
|
||||
code: await code,
|
||||
});
|
||||
|
||||
const renderer = new GltfRenderer(device, canvasFormat, shaderModule, gltf, primitive, bufferLayouts);
|
||||
|
||||
return renderer;
|
||||
}
|
||||
|
||||
export { loadGltf };
|
||||
BIN
monkey.glb
Normal file
BIN
monkey.glb
Normal file
Binary file not shown.
2
rotate.c
2
rotate.c
@ -9,7 +9,7 @@ void rotate(float angle, float aspect, float * buffer)
|
||||
//XMMATRIX mat2 = XMMatrixTranslation(0, 0, 1);
|
||||
//XMStoreFloat4x4((XMFLOAT4X4*)buffer, mat * mat2);
|
||||
|
||||
float d = 0.9;
|
||||
float d = 1.9;
|
||||
XMVECTOR eye = XMVectorSet(d, d / 2, d, 0);
|
||||
XMVECTOR at = XMVectorSet(0, 0, 0, 0);
|
||||
XMVECTOR up = XMVectorSet(0, 1, 0, 0);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user