Hello Cornflower
Every graphics concept that we discuss in this course will be made concrete in code. You need a place to write this code. Let's make that place right now as you write your first renderer, which will display a cornflower blue rectangle.
Repository
Your instructor has created a single Git repository for all your project and experimental code. Each of your project milestones will be stored in this single repository. The repository is currently only on GitHub. You must clone it to your local computer by following these steps:
You only need to clone your repository once. As the semester progresses, you will make changes to the folder you just made and push them up to GitHub.
Global Configuration
You will place many renderers in your repository throughout the semester. Let's perform some global configuration now that will impact all of these future renderers.
Ensure your empty top-level repository folder is open in Visual Studio Code. Each project that you create this semester will be a subfolder in this top-level folder.
Create a file named .gitignore
and add these lines:
.DS_Store
Thumbs.db
node_modules
.DS_Store Thumbs.db node_modules
These are the names of files that should never be added to version control. The first two are files created by your operating system to cache thumbnail versions of any image files. These thumbnails are shown in your operating system's file explorer. The folder node_modules
holds all the library code that your project depends on. Its content generally consumes a lot of disk space and can be downloaded afresh at any time.
By placing .gitignore
at the top level of your repository, it will recursively apply to all future renderers.
Renderer
Now it's time to add your first renderer to your repository. There are two ways we could walk you through this process. We could start you off with a project that is immediately runnable and then lead you through a series of errors and their fixes as you develop a functioning renderer. Or we could take a more manicured path that postpones running the code—and discovering errors—until the very end. Both approaches have their merit. Let's go with the second because bouncing from error to error is exhausting.
Create a new subfolder named hello-cornflower
. Open the terminal and enter this command so that your commands will be run in the context of your project folder instead of the course folder:
cd hello-cornflower
cd hello-cornflower
In your subfolder, create a file named index.html
and add this HTML code:
<!DOCTYPE html>
<html>
<head>
<title>Hello, Cornflower</title>
<link rel="icon" href="data:,">
<link rel="stylesheet" href="/style.css">
</head>
<body>
<canvas id="canvas"></canvas>
<script type="module" src="./src/main.ts"></script>
</body>
</html>
<!DOCTYPE html> <html> <head> <title>Hello, Cornflower</title> <link rel="icon" href="data:,"> <link rel="stylesheet" href="/style.css"> </head> <body> <canvas id="canvas"></canvas> <script type="module" src="./src/main.ts"></script> </body> </html>
The canvas
element is the rectangle that will get filled with pixels. The script
element will load in your JavaScript code. The link
tag loads in a stylesheet that alters the appearance of your page elements.
If you opened index.html
as a file in your web browser right now, the developer console would complain that style.css
and main.ts
can't be found. Let's add these files.
Create a folder named public
to store static assets like images and stylesheets. Create file public/style.css
with this CSS styling:
body {
margin: 0;
}
#canvas {
position: fixed;
left: 0;
right: 0;
width: 100%;
height: 100vh;
}
body { margin: 0; } #canvas { position: fixed; left: 0; right: 0; width: 100%; height: 100vh; }
This stylesheet removes the whitespace margin around the page's body
element and makes the canvas
element fill the browser window. The index.html
file you created earlier loads in this stylesheet using a link
tag.
Create a folder named src
to hold source files. Create file src/main.ts
and add this TypeScript:
let canvas: HTMLCanvasElement;
async function initialize() {
canvas = document.getElementById('canvas') as HTMLCanvasElement;
window.gl = canvas.getContext('webgl2') as WebGL2RenderingContext;
// Initialize other graphics state as needed.
// Event listeners
window.addEventListener('resize', () => resizeCanvas());
resizeCanvas();
}
function render() {
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clearColor(0.392, 0.584, 0.929, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
}
function resizeCanvas() {
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
render();
}
window.addEventListener('load', () => initialize());
let canvas: HTMLCanvasElement; async function initialize() { canvas = document.getElementById('canvas') as HTMLCanvasElement; window.gl = canvas.getContext('webgl2') as WebGL2RenderingContext; // Initialize other graphics state as needed. // Event listeners window.addEventListener('resize', () => resizeCanvas()); resizeCanvas(); } function render() { gl.viewport(0, 0, canvas.width, canvas.height); gl.clearColor(0.392, 0.584, 0.929, 1); gl.clear(gl.COLOR_BUFFER_BIT); } function resizeCanvas() { canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight; render(); } window.addEventListener('load', () => initialize());
The initialize
function grabs a reference to the page's canvas
element and gives it a WebGL context, an environment for executing WebGL commands. It runs exactly once, as the application starts up. The function render
colors in the pixels of the framebuffer and runs throughout the lifetime of the renderer. Currently render
only clears the canvas to the color cornflower. The first three numbers passed to clearColor
are the red, green, and blue intensities that mix to make cornflower. The fourth number is the color's opacity. A value of 1 means that the color is completely opaque, that you can't see anything behind it. The resizeCanvas
function is called whenever the window changes size. It resizes the canvas so that it matches the window and re-renders the scene.
We're using TypeScript in this course to discover errors early. The global variable gl
in main.ts
doesn't have a type declared, which will be a problem later on. We could declare it at the top of this file, but you are eventually going to refer to it in other files too. Let's instead declare it in a special file for global declarations. Create file src/globals.d.ts
with this content:
interface Window {
gl: WebGL2RenderingContext;
}
declare const gl: WebGL2RenderingContext;
interface Window { gl: WebGL2RenderingContext; } declare const gl: WebGL2RenderingContext;
The main content files are all now in place. But loading index.html
as a file in your browser will still fail. That's because this renderer isn't a standalone web page. The TypeScript needs to get translated into JavaScript, and all the code and assets need to be bundled together into a web app.
App
Before you can turn your renderer into a web app, it needs to be a Node.js project. Create file hello-cornflower/package.json
and add this configuration:
{
"name": "hello-cornflower",
"type": "module",
"scripts": {
"start": "vite --open",
"build": "tsc && vite build",
"driver": "tsc && node build/driver.js"
}
}
{ "name": "hello-cornflower", "type": "module", "scripts": { "start": "vite --open", "build": "tsc && vite build", "driver": "tsc && node build/driver.js" } }
All Node.js projects have this file; it describes the project, its dependencies, and any custom commands that you may want to run. This configuration provides two commands: start
and build
. The start
command is the most important one for this course. It starts up a local web server and opens your index.html
file in your browser.
The utility vite
(pronounced VEET) is a bundler that packs your code, HTML, stylesheets, and images into a standalone web page. The utility tsc
is the TypeScript compiler that translates TypeScript into JavaScript. Install your project's Vite and TypeScript dependencies by executing this command in your terminal:
npm install vite vite-plugin-checker typescript
npm install vite vite-plugin-checker typescript
Inspect package.json
. See the dependencies that have been added? Observe the node_modules
folder that has been created in the hello-cornflower
directory. It will be kept out of version control by the .gitignore
file you created earlier. The file package-lock.json
has also been created. It tracks the version numbers of the dependencies you are currently using.
The TypeScript compiler needs to be configured. Create hello-cornflower/tsconfig.json
with this JSON configuration:
{
"compilerOptions": {
"target": "es2020",
"module": "nodenext",
"skipLibCheck": true,
"moduleResolution": "nodenext",
"outDir": "./build",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noImplicitAny": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
{ "compilerOptions": { "target": "es2020", "module": "nodenext", "skipLibCheck": true, "moduleResolution": "nodenext", "outDir": "./build", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "noImplicitAny": true, "noFallthroughCasesInSwitch": true }, "include": ["src"] }
By design, Vite only shows type errors when you issue a build, but not as you iteratively develop your renderer—which is exactly when you want to see these errors. The vite-plugin-checker
makes these errors visible as you write code. Enable this checker by creating file hello-cornflower/vite.config.js
with this content:
import checker from 'vite-plugin-checker';
export default {
plugins: [
checker({
typescript: true,
}),
],
};
import checker from 'vite-plugin-checker'; export default { plugins: [ checker({ typescript: true, }), ], };
You've done it; there's no more setup. This hello-world of computer graphics and web apps isn't so straighforward, is it? Run a local web server that serves out your app with this command:
npm run start
npm run start
View the app in your browser, which should open automatically. Do you see a big cornflower rectangle filling the browser viewport? If not, check that your files match those listed above. Seek assistance as needed.
Try changing the clear color in main.ts
. As soon as you save the file, Vite will automatically reload the page in the browser.
Synchronizing to GitHub
Your files are only in your local clone of your repository. You need to commit your changes and then push them up to GitHub. You should do this after every work session, even if your code is broken or your task isn't done. By commiting and pushing regularly, your work is backed up and your instructor can see your code if you have a question.
Commit and push your code by following these steps.
Add cornflower renderer.
or Fix missing triangles.
or Refactor particle system.
Visit your repository on github.com and make sure you see your changes. If you can't see them, then your instructor can't either.
For future projects, either repeat the steps above, starting at the Renderer section, or copy the hello-cornflower
directory.