Shannon Bonet, Ace Chen, Isabel Li, Bailey Segall
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.
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.
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.
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.
|
|
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.
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.
|
|
|
|
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.
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.
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.