ToonTown Cel Central

Team Members

Shannon Bonet, Ace Chen, Isabel Li, Bailey Segall



Abstract

Over the course of this project, we built a web-based non-photo realistic cel ("toon") shader with an interactive interface for varying core parameters. Using Three.js, we implemented a basic cel shader with diffuse lighting, ambient lighting, rim lighting, and specular highlighting. We reached some of our stretch goals, including to to support outlining with various thickness levels, producing a custom shader preset in the style of the Hades video game, and exploring bump mapping in three.js.


Technical Approach

In setting up a web framework, we decided to go with using Three.js instead of React Three Fiber due to online resource availability and unfamiliarity with experimenting with a new framework / library. Using a Three.js boilerplate, we were able to display a shapes like spheres, torus knots, and cylinders using a custom vertex shader to set position and fragment shader for color with our own .vert and .frag files. We used dat.GUI to produce an interactive control panel for the objects, mostly passing in constants to vary the different lighting components from the frontend using uniforms (global GLSL variables).


Our shader was inspired by the Phong shader we previously implemented in this class, with ambient, diffuse, and specular lighting components. We calculated directional lighting by first taking the dot product of a surface normal and the direction of light at a given point. Next, we used a smoothstep function to perform Hermite interpolation between points to specify a cutoff point between shadows and non-shadows to achieve a smooth transition. After accounting for ambient lighting, we implemented specular reflection using the Blinn Phong specular term , which is the angle between the half-angle vector and surface normal, to calculate specular intensity. We implemented rim lighting by multiplying a) the dot product of the surface normal and view direction and b) the dot product of light direction and surface normal to determine areas perpendicular to the viewer.


Basic cel shader

To implement receiving shadows, we enabled shadowmaps in the renderer and set the directional light to cast shadows. Each object then had to be set to both receive and cast shadows. Three.js has built-in utilities for shadows that we then included in our vert and frag shaders, which let us use the array directionalLightShadows and the function getShadow. Within our frag shader, we used getShadow to generate a shadow float based on the directional light's generated shadowmaps. This float was factored into the `lightIntensity` calculations to make sure areas receiving shadows would be appropriately darkened.


Sphere receiving torus shadow

We implemented outlining by creating an additional shape using the same geometry and a solid color as the material. We scaled this by a constant factor and used the properties of Three.js to always render it behind the other objects. We then scaled the y coordinate of both the original shape and the outline shape by the same scale factor to ensure the outline would not render below the ‘floor’ aka when z=0. While the references below used the same technique for outlining, we were able to take this one step further by integrating the stroke size into the interactive controls and allow the user to designate the outline width.


scale factor of 1.05
scale factor of 1.02

We were able to explore light banding effects by adding cut-offs at different intensities of the diffuse light component, and multiplying each by a constant. We also explored color banding effects by also modifying the directional light color at each of these cut-offs with an added color constant. The final GUI allows the user to view the rendered objects with different numbers of light bands.


Banding with 1 to 4 bands

To produce the custom preset for the Hades shader, we first identified what we thought to be the most iconic effects of the shader style: minimal banding, black shadows, a creative color accent, as well as eye-catching specular and rim effects. We then modified the existing shader to produce these effects. We built the black shadows by removing ambient light from the fragment shader, and then creating cut-offs for the diffuse lighting component to produce a thin band of a bright creative accent. The rim and specular effects could be achieved with our existing shader model. This implementation was entirely from our own creativity and inspecting reference images of Hades. We chose to add this stretch goal because it was a fun rendering style which we felt personally inspired by.


Some problems we encountered included initially setting up the dat.GUI controls to connect the parameters on the panel with the rendered output, since we were generally unfamiliar with the library and unsure how to make sure the rendered output would update automatically after changing the parameters. There were also some bugs introduced and uncovered once we added more objects to the scene, such as realising the directional light's shadow camera wasn't large enough to capture all the objects' shadows (and subsequently left them cut out of the scene) or some general mistakes in our code managing multiple objects. We were able to debug by looking at documentation or forums to do with Three.js and dat.GUI, or by inspecting other codebases which had also used these technologies in their work.


Our primary learnings were in implementing a shader for web and learning Three.js from scratch. Initially we weren't even certain which library or framework would be the most appropriate for our web-based cel shader, and also explored React Three Fiber. Over time, we were able to identify how to translate the work we learned in class into Three.js shaders, as well as set up a scene and objects. We also learnt the difference between using WebGL with Three.js to build a shader and using Three.js's post-processing for more creative effects. We also learnt how to create interactive controls with dat.GUI without prior experience. Overall, we became more comfortable with this new framework for web-based shaders, and also gained a better understanding of implementing toon shading and shaders as a whole.



Results

Demo gif of our lighting model

Demo gif of banding


our Hades-style shader verses the official game art style

We had some time to attempt an implemention bump mapping, one of our stretch goals. We didn’t have enough time to experiment with a custom implementation. Instead, we ended up testing out Three’s build in MeshToonShader to use its bumpMap property to see what the effects of a texture look like.


scale 0.5
scale 0.05
scale 0.005

The `bumpScale` property allows us to change the density of the applied texture. Lower scale values result in sparser pattern, and gives a smoother cartonoonish appearance.


References

Basic Cel Shading: Outlining: Bump Mapping: Shadows:

Timeline Results


Contributions

Our project was conceptualized as a team and we all collaborated on writing the contents of each report, as well as finding and sharing resources to produce our final results.



Special thanks to CS184 course staff for your feedback and support in this project.