Skip to content

Instantly share code, notes, and snippets.

@tbfleming
Last active January 25, 2018 15:48
Show Gist options
  • Save tbfleming/3e3b86b9d70d87a38cc61bfba2f12a79 to your computer and use it in GitHub Desktop.
Save tbfleming/3e3b86b9d70d87a38cc61bfba2f12a79 to your computer and use it in GitHub Desktop.
cib demo: webgl: flying bunnies
// Copyright 2018 Todd Fleming
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
// cib:{"fetch":"https://cdn.rawgit.com/tbfleming/cib-zips/6608cfad7bd9f9609a29f4e157fc390928772c6c/glm-0.9.8.5.zip","system_includes":["glm"], "unzip_compiler":true}
#include <GLES3/gl32.h>
#include <emscripten.h>
#include <glm/geometric.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/mat3x3.hpp>
#include <math.h>
#include <random>
#include <stdint.h>
#include <stdio.h>
#include <vector>
using namespace std;
extern "C" void fatal(const char *);
extern "C" double width();
extern "C" double height();
extern "C" GLuint webglCreateBuffer();
extern "C" GLuint webglCreateVertexArray();
extern "C" GLuint webglGetProgramParameter(GLuint, GLenum);
extern "C" GLuint webglGetShaderParameter(GLuint, GLenum);
extern "C" void webglDumpProgramInfoLog(GLuint);
extern "C" void webglDumpShaderInfoLog(GLuint);
extern "C" void webglShaderSource(GLuint, const char *);
extern "C" void webglUniformMatrix4fv(GLint, const glm::mat4 &);
auto bunnyUrl = "https://cdn.rawgit.com/tbfleming/cib-zips/cb248be35fb60d499736d7993b10bcf7168fbc7b/models/bunny-flatfoot.stl";
const float pi = acos(-1);
auto vertexSource = R"(#version 300 es
precision mediump float;
uniform mat4 view;
uniform mat4 camera;
uniform mat4 world;
uniform mat4 model;
in vec3 position;
in vec3 normal;
out vec3 vNormal;
void main() {
gl_Position = view * camera * world * model * vec4(position, 1.0);
mat4 t = camera * world * model;
t[3] = vec4(0.0, 0.0, 0.0, 1.0);
vNormal = normalize((t * vec4(normal, 0.0)).xyz);
}
)";
auto fragmentSource = R"(#version 300 es
precision mediump float;
uniform vec3 ambient;
uniform vec3 diffuse;
in vec3 vNormal;
out vec4 color;
vec3 lightDirection = normalize(vec3(1.0, 1.0, 1.0));
void main() {
color = vec4(ambient + diffuse * dot(lightDirection, vNormal), 1.0);
}
)";
GLuint createVAO(GLuint buffer);
struct Vertex {
glm::vec3 position{};
glm::vec3 normal{};
};
struct Program {
GLuint program;
GLint viewUniform;
GLint cameraUniform;
GLint worldUniform;
GLint modelUniform;
GLint ambientUniform;
GLint diffuseUniform;
};
struct Mesh {
const char *name{""};
const char *url{""};
GLuint buffer{webglCreateBuffer()};
GLuint vao = {createVAO(buffer)};
vector<Vertex> vertexes{};
glm::mat4 transform{};
};
struct Instance {
Mesh *mesh;
glm::vec3 ambient;
glm::vec3 diffuse;
glm::mat4 delta;
glm::mat4 transform;
};
struct StlHeader {
uint8_t header[80];
uint32_t numTriangles;
};
struct StlTriangle {
glm::vec3 normal;
glm::vec3 v1;
glm::vec3 v2;
glm::vec3 v3;
};
static const int stlTriangleSize = 50; // includes 16-bit garbage value
static_assert(sizeof(StlTriangle) < stlTriangleSize);
auto ok = true;
void check(bool cond, const char *msg) {
if (!cond) {
ok = false;
fatal(msg);
}
}
void dump(const glm::mat4 &m) {
printf("%+08.2f %+08.2f %+08.2f %+08.2f\n", m[0][0], m[1][0], m[2][0],
m[3][0]);
printf("%+08.2f %+08.2f %+08.2f %+08.2f\n", m[0][1], m[1][1], m[2][1],
m[3][1]);
printf("%+08.2f %+08.2f %+08.2f %+08.2f\n", m[0][2], m[1][2], m[2][2],
m[3][2]);
printf("%+08.2f %+08.2f %+08.2f %+08.2f\n", m[0][3], m[1][3], m[2][3],
m[3][3]);
}
auto compileShader(GLenum type, const char *source) {
auto shader = glCreateShader(type);
webglShaderSource(shader, source);
glCompileShader(shader);
if (!webglGetShaderParameter(shader, GL_COMPILE_STATUS)) {
webglDumpShaderInfoLog(shader);
check(false, "shader compile failed");
}
return shader;
}
Program createProgram(const char *vertexSource, const char *fragmentSource) {
auto vertexShader = compileShader(GL_VERTEX_SHADER, vertexSource);
auto fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentSource);
auto shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
if (!webglGetProgramParameter(shaderProgram, GL_LINK_STATUS)) {
webglDumpProgramInfoLog(shaderProgram);
check(false, "link failed");
}
return {
shaderProgram,
glGetUniformLocation(shaderProgram, "view"),
glGetUniformLocation(shaderProgram, "camera"),
glGetUniformLocation(shaderProgram, "world"),
glGetUniformLocation(shaderProgram, "model"),
glGetUniformLocation(shaderProgram, "ambient"),
glGetUniformLocation(shaderProgram, "diffuse"),
};
}
GLuint createVAO(GLuint buffer) {
auto vao = webglCreateVertexArray();
glBindVertexArray(vao);
{
glBindBuffer(GL_ARRAY_BUFFER, buffer);
{
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(Vertex),
(void *)offsetof(Vertex, position));
glVertexAttribPointer(1, 3, GL_FLOAT, false, sizeof(Vertex),
(void *)offsetof(Vertex, normal));
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
glBindVertexArray(0);
return vao;
}
void fillBuffer(GLuint buffer, const vector<Vertex> &vertexes) {
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, vertexes.size() * sizeof(vertexes[0]),
vertexes.data(), GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void initMeshTransform(Mesh &mesh) {
if (mesh.vertexes.empty())
return;
auto minBounds = mesh.vertexes[0].position;
auto maxBounds = minBounds;
for (auto &v : mesh.vertexes) {
minBounds.x = min(minBounds.x, v.position.x);
minBounds.y = min(minBounds.y, v.position.y);
minBounds.z = min(minBounds.z, v.position.z);
maxBounds.x = max(maxBounds.x, v.position.x);
maxBounds.y = max(maxBounds.y, v.position.y);
maxBounds.z = max(maxBounds.z, v.position.z);
}
auto size = maxBounds - minBounds;
auto scale = 1 / max(size.x, max(size.y, size.z));
mesh.transform =
glm::translate(glm::scale(glm::mat4{}, glm::vec3(scale, scale, scale)),
-(minBounds + maxBounds) * .5f);
}
void drawInst(Program &program, Instance &inst) {
webglUniformMatrix4fv(program.worldUniform, inst.transform);
webglUniformMatrix4fv(program.modelUniform, inst.mesh->transform);
glUniform3f(program.ambientUniform, inst.ambient.r, inst.ambient.g,
inst.ambient.b);
glUniform3f(program.diffuseUniform, inst.diffuse.r, inst.diffuse.g,
inst.diffuse.b);
glBindVertexArray(inst.mesh->vao);
glDrawArrays(GL_TRIANGLES, 0, inst.mesh->vertexes.size());
glBindVertexArray(0);
}
auto program = createProgram(vertexSource, fragmentSource);
Mesh bunnyMesh{"bunny", bunnyUrl};
std::vector<Instance> instances;
extern "C" void drawFrame() {
if (!ok)
return;
auto w = width();
auto h = height();
if (w < 10 || h < 10)
return;
glViewport(0, 0, w, h);
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glUseProgram(program.program);
auto view = glm::perspective<float>(pi / 4, width() / height(), .1, 10);
auto camera =
glm::lookAt(glm::vec3{0, -5, 5}, glm::vec3{0, 0, 0}, glm::vec3{0, 0, 1});
webglUniformMatrix4fv(program.viewUniform, view);
webglUniformMatrix4fv(program.cameraUniform, camera);
for (auto &inst : instances) {
drawInst(program, inst);
inst.transform = inst.delta * inst.transform;
}
glUseProgram(0);
}
void fetchStl(Mesh &mesh) {
printf("fetching %s\n", mesh.url);
emscripten_async_wget_data(
mesh.url, &mesh,
[](void *r, void *data, int size) {
Mesh &mesh = *(Mesh *)r;
StlHeader header;
if (size < sizeof(header)) {
printf("%s file too short for header\n", mesh.name);
return;
}
memcpy(&header, data, sizeof(header));
if (size != sizeof(header) + header.numTriangles * stlTriangleSize) {
printf("%s size doesn't match header\n", mesh.name);
return;
}
printf("%s has %u triangles\n", mesh.name, header.numTriangles);
mesh.vertexes.reserve(header.numTriangles * 3);
for (uint32_t i = 0; i < header.numTriangles; ++i) {
StlTriangle t;
memcpy(&t, (char *)data + sizeof(header) + i * stlTriangleSize,
sizeof(t));
auto normal = normalize(cross(t.v2 - t.v1, t.v3 - t.v1));
mesh.vertexes.push_back({t.v1, normal});
mesh.vertexes.push_back({t.v2, normal});
mesh.vertexes.push_back({t.v3, normal});
}
initMeshTransform(mesh);
fillBuffer(mesh.buffer, mesh.vertexes);
},
[](void *r) {
Mesh &mesh = *(Mesh *)r;
printf("%s download failed\n", mesh.name);
});
}
int main() {
fetchStl(bunnyMesh);
minstd_rand rnd;
uniform_real_distribution<float> dist;
for (int i = 0; i < 50; ++i) {
auto maxSpeed = .01;
auto t = glm::translate(glm::mat4{},
glm::vec3{dist(rnd) * maxSpeed * 2 - maxSpeed,
dist(rnd) * maxSpeed * 2 - maxSpeed,
dist(rnd) * maxSpeed * 2 - maxSpeed});
auto r = glm::rotate(
glm::mat4{}, dist(rnd) * pi / 60,
glm::normalize(glm::vec3{dist(rnd) * 2 - 1, dist(rnd) * 2 - 1,
dist(rnd) * 2 - 1}));
glm::vec3 ambient{dist(rnd), dist(rnd), dist(rnd)};
glm::vec3 diffuse{dist(rnd), dist(rnd), dist(rnd)};
instances.push_back({&bunnyMesh, ambient, diffuse, r * t});
}
}
<!--
Copyright 2018 Todd Fleming
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
-->
%init-scripts%
<style>
html,
body {
width: 100%;
height: 100%;
overflow: hidden;
}
pre,
canvas {
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
}
pre {
margin: 0px;
padding: 0px;
overflow: auto;
color: red;
}
</style>
<canvas id="c"></canvas>
<pre id="con"></pre>
<script>
'use strict';
// stderr, stdout
let con = document.getElementById('con');
emModule.print = emModule.printErr = msg => {
con.textContent += msg + '\n';
};
let canvas = document.getElementById('c');
let gl = canvas.getContext('webgl2');
let glObjects = [null];
function storeGLObject(o) {
if (!o)
return 0;
glObjects.push(o);
return glObjects.length - 1;
}
function glConstructor(f) {
f = f.bind(gl);
return (...args) => storeGLObject(f(...args));
};
wasmImports = {
fatal(s) { throw new Error(emModule.UTF8ToString(s)); },
width() { return canvas.width },
height() { return canvas.height },
// emscripten has an OpenGL emulation layer, but I don't have that working
// yet with cib. This simple adaptor set isn't compatible with existing
// OpenGL C++ code because of the webgl* functions below.
// These match OpenGL signatures
glAttachShader(i, j) { gl.attachShader(glObjects[i], glObjects[j]); },
glBindBuffer(i, j) { gl.bindBuffer(i, glObjects[j]); },
glBindVertexArray(i) { gl.bindVertexArray(glObjects[i]); },
glBufferData(target, size, data, usage) { gl.bufferData(target, new Uint8Array(emModule.buffer, data, size), usage); },
glClear: gl.clear.bind(gl),
glClearColor: gl.clearColor.bind(gl),
glCompileShader(i) { gl.compileShader(glObjects[i]); },
glCreateProgram: glConstructor(gl.createProgram),
glCreateShader: glConstructor(gl.createShader),
glDrawArrays: gl.drawArrays.bind(gl),
glEnable: gl.enable.bind(gl),
glEnableVertexAttribArray: gl.enableVertexAttribArray.bind(gl),
glGetUniformLocation(i, s) { return storeGLObject(gl.getUniformLocation(glObjects[i], emModule.UTF8ToString(s))); },
glLinkProgram(i) { gl.linkProgram(glObjects[i]); },
glUniform3f(i, v0, v1, v2) { gl.uniform3f(glObjects[i], v0, v1, v2); },
glUseProgram(i) { gl.useProgram(glObjects[i]); },
glVertexAttribPointer: gl.vertexAttribPointer.bind(gl),
glViewport: gl.viewport.bind(gl),
// These don't match OpenGL signatures
webglCreateBuffer: glConstructor(gl.createBuffer),
webglCreateVertexArray: glConstructor(gl.createVertexArray),
webglDumpProgramInfoLog(i) { emModule.printErr(gl.getProgramInfoLog(glObjects[i])); },
webglDumpShaderInfoLog(i) { emModule.printErr(gl.getShaderInfoLog(glObjects[i])); },
webglGetProgramParameter(i, j) { return gl.getProgramParameter(glObjects[i], j); },
webglGetShaderParameter(i, j) { return gl.getShaderParameter(glObjects[i], j); },
webglShaderSource(i, s) { gl.shaderSource(glObjects[i], emModule.UTF8ToString(s)); },
webglUniformMatrix4fv(i, p) { gl.uniformMatrix4fv(glObjects[i], false, new Float32Array(emModule.buffer, p, 16)); }
}
function update() {
try {
let bounds = canvas.getBoundingClientRect();
let w = Math.round(bounds.width * devicePixelRatio);
let h = Math.round(bounds.height * devicePixelRatio);
if (w !== canvas.width || h !== canvas.height) {
canvas.width = w;
canvas.height = h;
}
if (wasmExports.drawFrame)
wasmExports.drawFrame();
requestAnimationFrame(update);
} catch (e) {
emModule.printErr('Error: ' + e.message);
}
}
update();
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment