HTML preprocessors can make writing HTML more powerful or convenient. For instance, Markdown is designed to be easier to write and read for text documents and you could write a loop in Pug.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. So you don't have access to higher-up elements like the <html>
tag. If you want to add classes there that can affect the whole document, this is the place to do it.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. If you need things in the <head>
of the document, put that code here.
The resource you are linking to is using the 'http' protocol, which may not work when the browser is using https.
CSS preprocessors help make authoring CSS easier. All of them offer things like variables and mixins to provide convenient abstractions.
It's a common practice to apply CSS to a page that styles elements such that they are consistent across all browsers. We offer two of the most popular choices: normalize.css and a reset. Or, choose Neither and nothing will be applied.
To get the best cross-browser support, it is a common practice to apply vendor prefixes to CSS properties and values that require them to work. For instance -webkit-
or -moz-
.
We offer two popular choices: Autoprefixer (which processes your CSS server-side) and -prefix-free (which applies prefixes via a script, client-side).
Any URLs added here will be added as <link>
s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.
You can apply CSS to your Pen from any stylesheet on the web. Just put a URL to it here and we'll apply it, in the order you have them, before the CSS in the Pen itself.
You can also link to another Pen here (use the .css
URL Extension) and we'll pull the CSS from that Pen and include it. If it's using a matching preprocessor, use the appropriate URL Extension and we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
JavaScript preprocessors can help make authoring JavaScript easier and more convenient.
Babel includes JSX processing.
Any URL's added here will be added as <script>
s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.
You can apply a script from anywhere on the web to your Pen. Just put a URL to it here and we'll add it, in the order you have them, before the JavaScript in the Pen itself.
If the script you link to has the file extension of a preprocessor, we'll attempt to process it before applying.
You can also link to another Pen here, and we'll pull the JavaScript from that Pen and include it. If it's using a matching preprocessor, we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
Search for and use JavaScript packages from npm here. By selecting a package, an import
statement will be added to the top of the JavaScript editor for this package.
Using packages here is powered by esm.sh, which makes packages from npm not only available on a CDN, but prepares them for native JavaScript ESM usage.
All packages are different, so refer to their docs for how they work.
If you're using React / ReactDOM, make sure to turn on Babel for the JSX processing.
If active, Pens will autosave every 30 seconds after being saved once.
If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.
If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.
Visit your global Editor Settings.
<div id="info">
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - Cone Marching Demo<br />
by <a href="https://nabilmansour.com" target="_blank" rel="noopener">Nabil Mansour</a>
</div>
body {
margin: 0;
background-color: #000;
color: #fff;
font-family: Monospace;
font-size: 13px;
line-height: 24px;
overscroll-behavior: none;
}
a {
color: #ff0;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
button {
cursor: pointer;
text-transform: uppercase;
}
#info {
position: absolute;
top: 0px;
width: 100%;
padding: 10px;
box-sizing: border-box;
text-align: center;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
z-index: 1; /* TODO Solve this in HTML */
}
a, button, input, select {
pointer-events: auto;
}
.lil-gui {
z-index: 2 !important; /* TODO Solve this in HTML */
}
@media all and ( max-width: 640px ) {
.lil-gui.root {
right: auto;
top: auto;
max-height: 50%;
max-width: 80%;
bottom: 0;
left: 0;
}
}
#overlay {
position: absolute;
font-size: 16px;
z-index: 2;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background: rgba(0,0,0,0.7);
}
#overlay button {
background: transparent;
border: 0;
border: 1px solid rgb(255, 255, 255);
border-radius: 4px;
color: #ffffff;
padding: 12px 18px;
text-transform: uppercase;
cursor: pointer;
}
#notSupported {
width: 50%;
margin: auto;
background-color: #f00;
margin-top: 20px;
padding: 10px;
}
import * as THREE from "https://esm.sh/three";
import { OrbitControls } from "https://esm.sh/three/examples/jsm/controls/OrbitControls";
import GUI from "https://esm.sh/three/examples/jsm/libs/lil-gui.module.min.js";
import Stats from "https://esm.sh/three/examples/jsm/libs/stats.module.js";
const glsl = (x) => x[0]; // Dummy function to enable syntax highlighting for glsl code
// ----------------- Uniforms Code ----------------- //
const uniformsCode = glsl`
precision mediump float;
// From CPU
uniform vec3 u_clearColor;
uniform float u_eps;
uniform float u_maxDis;
uniform int u_maxSteps;
uniform vec3 u_camPos;
uniform mat4 u_camToWorldMat;
uniform mat4 u_camInvProjMat;
uniform float u_camTanFov;
uniform float u_camPlaneSubdivisions;
uniform vec3 u_lightDir;
uniform vec3 u_lightColor;
uniform float u_diffIntensity;
uniform float u_specIntensity;
uniform float u_shininess;
uniform float u_ambientIntensity;
uniform bool u_useConeMarching;
uniform int u_sdfLOD;
uniform bool u_showConeMarchingEdges;
`
// ----------------- Marching Code ----------------- //
const marchCode = glsl`
float sphereFold(vec4 z, float minR, float maxR, float bloatFactor) { // bloat = 1 will not change size.
float r2 = dot(z.xyz, z.xyz);
return max(maxR / max(minR, r2), bloatFactor);
}
void boxFold(inout vec4 z, vec3 r) {
z.xyz = clamp(z.xyz, -r, r) * 2.0 - z.xyz;
}
float de_box(vec4 p, vec3 s) {
vec3 a = abs(p.xyz) - s;
return (min(max(max(a.x, a.y), a.z), 0.0) + length(max(a, 0.0))) / p.w;
}
float mandleBox(vec3 pos) {
vec4 p = vec4(pos, 1);
p *= 4.;
vec4 o = p;
for (int i = 0; i < u_sdfLOD; ++i) {
boxFold(p, vec3(1., 1., 1.0));
p *= sphereFold(p, 0., 1., 1.0) * 2.;
p += o;
}
return de_box(p, vec3(10, 10, 10));
}
float scene(vec3 p) {
return mandleBox(p);
}
vec3 sceneNormal(vec3 p) // from https://iquilezles.org/articles/normalsSDF/
{
vec3 n = vec3(0, 0, 0);
vec3 e;
for(int i = 0; i < 4; i++) {
e = 0.5773 * (2.0 * vec3((((i + 3) >> 1) & 1), ((i >> 1) & 1), (i & 1)) - 1.0);
n += e * scene(p + e * u_eps);
}
return normalize(n);
}
vec3 sceneCol(vec3 p) {
return vec3(1.0, 0.5, 0.5);
}
float rayMarch(float startDis, int stepsTaken, vec3 ro, vec3 rd)
{
float d = startDis; // total distance travelled
float cd; // current scene distance
vec3 p; // current position of ray
for (int i = stepsTaken; i < u_maxSteps; ++i) { // main loop
p = ro + d * rd; // calculate new position
cd = scene(p); // get scene distance
// if we have hit anything or our distance is too big, break loop
if (cd < u_eps || d >= u_maxDis) break;
// otherwise, add new scene distance to total distance
d += cd;
}
return d; // finally, return scene distance
}
struct March {
float dis;
int steps;
};
March coneMarch(vec3 cro, vec3 crd)
{
float d = 0.; // total distance travelled
float cd; // current scene distance
float ccr; // current cone radius
vec3 p; // current position of ray
int i = 0; // steps iter
for (;i < u_maxSteps; ++i) { // main loop
p = cro + d * crd; // calculate new position
cd = scene(p); // get scene distance
ccr = (d * u_camTanFov)*2. / u_camPlaneSubdivisions; // calculate cone radius
// if current distance is less than cone radius with some padding or our distance is too big, break loop
if (cd < ccr*1.25 || d >= u_maxDis) break;
// otherwise, add new scene distance to total distance
d += cd;
}
return March(d, i); // finally, return scene distance
}
`
// ----------------- Vertex Shader ----------------- //
const vertCode = glsl`
// to send to fragment shader
out vec2 vUv;
out float vDisTravelled;
flat out int vSteps;
void main() {
// Compute view direction in world space
vec4 worldPos = modelViewMatrix * vec4(position, 1.0);
vec3 viewDir = normalize(-worldPos.xyz);
// Output vertex position
gl_Position = projectionMatrix * worldPos;
// Output UV
vUv = uv;
// Cone marching
vDisTravelled = 0.;
vSteps = 0;
if (u_useConeMarching) {
vec3 cro = u_camPos;
vec3 crd = (u_camInvProjMat * vec4(uv*2.-1., 0, 1)).xyz;
crd = (u_camToWorldMat * vec4(crd, 0)).xyz;
crd = normalize(crd);
March result = coneMarch(cro, crd);
vDisTravelled = result.dis;
vSteps = result.steps;
}
}`
// ----------------- Fragment Shader ----------------- //
const fragCode = glsl`
// From vertex shader
in vec2 vUv;
in float vDisTravelled;
flat in int vSteps;
void main() {
// If distance travelled is too big, clear color
if (u_showConeMarchingEdges && vDisTravelled >= u_maxDis) {
gl_FragColor = vec4(u_clearColor,1);
return;
}
// Get UV from vertex shader
vec2 uv = vUv.xy;
// Get ray origin and direction from camera uniforms
vec3 ro = u_camPos;
vec3 rd = (u_camInvProjMat * vec4(uv*2.-1., 0, 1)).xyz;
rd = (u_camToWorldMat * vec4(rd, 0)).xyz;
rd = normalize(rd);
// Ray marching and find total distance travelled
float disTravelled = rayMarch(vDisTravelled, vSteps, ro, rd); // use normalized ray
if (disTravelled >= u_maxDis) { // if ray doesn't hit anything
gl_FragColor = vec4(u_clearColor * (u_useConeMarching ? 2. : 1.),1);
} else { // if ray hits something
// Calculate Diffuse model
vec3 hp = ro + disTravelled * rd; // Find the hit position
vec3 n = sceneNormal(hp); // Get normal of hit point
float dotNL = dot(n, u_lightDir);
float diff = max(dotNL, 0.0) * u_diffIntensity;
float spec = pow(diff, u_shininess) * u_specIntensity;
float ambient = u_ambientIntensity;
vec3 color = u_lightColor * (sceneCol(hp) * (spec + ambient + diff));
gl_FragColor = vec4(color,1); // color output
}
}
`
// Create a scene
const scene = new THREE.Scene();
// Add stats
const stats = new Stats();
stats.showPanel(0);
document.body.appendChild(stats.dom);
// Create a camera
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
// Create a renderer
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Set background color
const backgroundColor = new THREE.Color(0x3399ee);
renderer.setClearColor(backgroundColor, 1);
// Add orbit controls
const controls = new OrbitControls(camera, renderer.domElement);
controls.maxDistance = 10;
controls.minDistance = 2;
controls.enableDamping = true;
// Add directional light
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(1, 1, 1);
scene.add(light);
// Create a ray marching plane
let marchingPlaneGeoValues = [1, 1, Math.trunc(camera.aspect*32), 32];
const marchingPlaneMat = new THREE.ShaderMaterial();
let marchingPlaneGeo = new THREE.PlaneGeometry(...marchingPlaneGeoValues);
let marchingPlane = new THREE.Mesh(marchingPlaneGeo, marchingPlaneMat);
// Get the wdith and height of the near plane
const nearPlaneWidth = camera.near * Math.tan(THREE.MathUtils.degToRad(camera.fov / 2)) * camera.aspect * 2;
const nearPlaneHeight = nearPlaneWidth / camera.aspect;
// Scale the ray marching plane
marchingPlane.scale.set(nearPlaneWidth, nearPlaneHeight, 1);
// Add uniforms
const uniforms = {
u_eps: { value: 0.001 },
u_maxDis: { value: 20 },
u_maxSteps: { value: 100 },
u_clearColor: { value: backgroundColor },
u_camPos: { value: camera.position },
u_camToWorldMat: { value: camera.matrixWorld },
u_camInvProjMat: { value: camera.projectionMatrixInverse },
u_camTanFov: { value: Math.tan(THREE.MathUtils.degToRad(camera.fov / 2)) },
u_camPlaneSubdivisions: { value: marchingPlaneGeoValues[marchingPlaneGeoValues.length - 1] },
u_lightDir: { value: light.position },
u_lightColor: { value: light.color },
u_diffIntensity: { value: 0.5 },
u_specIntensity: { value: 3 },
u_shininess: { value: 16 },
u_ambientIntensity: { value: 0.15 },
u_useConeMarching: { value: true },
u_sdfLOD: { value: 10 },
u_showConeMarchingEdges: { value: true },
};
marchingPlaneMat.uniforms = uniforms;
marchingPlaneMat.vertexShader = uniformsCode + marchCode + vertCode;
marchingPlaneMat.fragmentShader = uniformsCode + marchCode + fragCode;
// wireframe
const wireframeMat = new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true, transparent: true, opacity: 0. });
let wireframeGeo = new THREE.PlaneGeometry(...marchingPlaneGeoValues);
let wireframe = new THREE.Mesh(wireframeGeo, wireframeMat);
marchingPlane.add(wireframe);
// Add plane to scene
scene.add(marchingPlane);
// Needed inside update function
let cameraForwardPos = new THREE.Vector3(0, 0, -1);
const VECTOR3ZERO = new THREE.Vector3(0, 0, 0);
// Render the scene
const animate = () => {
stats.begin();
requestAnimationFrame(animate);
// Update screen plane position and rotation
cameraForwardPos = camera.position.clone().add(camera.getWorldDirection(VECTOR3ZERO).multiplyScalar(camera.near));
marchingPlane.position.copy(cameraForwardPos);
marchingPlane.rotation.copy(camera.rotation);
renderer.render(scene, camera);
controls.update();
stats.end();
}
animate();
// Handle window resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
const nearPlaneWidth = camera.near * Math.tan(THREE.MathUtils.degToRad(camera.fov / 2)) * camera.aspect * 2;
const nearPlaneHeight = nearPlaneWidth / camera.aspect;
marchingPlane.scale.set(nearPlaneWidth, nearPlaneHeight, 1);
if (renderer) renderer.setSize(window.innerWidth, window.innerHeight);
marchingPlaneGeoValues = [1, 1, Math.trunc(camera.aspect*32), 32]
marchingPlaneGeo.dispose();
marchingPlaneGeo = new THREE.PlaneGeometry(...marchingPlaneGeoValues);
marchingPlane.geometry = marchingPlaneGeo;
marchingPlaneMat.uniforms.u_camPlaneSubdivisions.value = marchingPlaneGeoValues[marchingPlaneGeoValues.length - 1];
wireframeGeo.dispose();
wireframeGeo = new THREE.PlaneGeometry(...marchingPlaneGeoValues);
wireframe.geometry = wireframeGeo;
});
// GUI
const gui = new GUI();
const guiParams = {
useConeMarching: uniforms.u_useConeMarching.value,
sdfLOD: uniforms.u_sdfLOD.value,
wireframe: false,
showConeMatchingEdges: uniforms.u_showConeMarchingEdges.value,
eps: uniforms.u_eps.value,
maxDis: uniforms.u_maxDis.value,
maxSteps: uniforms.u_maxSteps.value,
};
// ------------------------------------------------- //
const generalSettings = gui.addFolder('General Settings');
generalSettings.add(guiParams, 'useConeMarching', true, false).onChange((value) => {
uniforms.u_useConeMarching.value = value;
}).name('Use Cone Marching');
generalSettings.add(guiParams, 'sdfLOD', 5, 20).step(1).onChange((value) => {
uniforms.u_sdfLOD.value = value;
}).name('SDF Level of Detail');
generalSettings.add(guiParams, 'wireframe', true, false).onChange((value) => {
wireframeMat.opacity = value ? 0.1 : 0;
}).name('Show Subdivisions');
generalSettings.add(guiParams, 'showConeMatchingEdges', true, false).onChange((value) => {
uniforms.u_showConeMarchingEdges.value = value;
}).name('Show Cone Marching Edges');
// ------------------------------------------------- //
const renderingSettings = gui.addFolder('Rendering Settings');
renderingSettings.add(guiParams, 'eps', 0.0001, 0.01).onChange((value) => {
uniforms.u_eps.value = value;
}).name('Hit Epsilon');
renderingSettings.add(guiParams, 'maxSteps', 10, 200).step(1).onChange((value) => {
uniforms.u_maxSteps.value = value;
}).name('Max Steps');
// ------------------------------------------------- //
Also see: Tab Triggers