Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

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.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

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.

+ add another resource

Packages

Add Packages

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.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <h4>Ray Tracing in One Weekend</h4>
参考:https://raytracing.github.io/<br>
FPS: <span id="fps"></span><br>
              
            
!

CSS

              
                
              
            
!

JS

              
                const vertexShaderSource = `
attribute vec4 a_Position;
void main() {
    gl_Position = a_Position;
}
`;

const fragmentShaderSource = `
precision highp float;
uniform float u_Time;
uniform vec2 u_Resolution;

const int samples_per_pixel = 40;
const float PI = 3.14159265359;
const int MAX_DEPTH = 10;

const int MATERIAL_LAMBERTIAN = 1; // 拡散
const int MATERIAL_METAL = 2; // 金属
const int MATERIAL_DIELECTRIC = 3; // ガラス

// ------------------------------------------------------------------------------------------------ Ray

struct Ray {
    vec3 orig;
    vec3 dir;
};

vec3 Ray_at(Ray r, float t) {
    return r.orig + t * r.dir;
}

struct Material {
    int type;
    vec3 albedo;
    float fuzz;
    float ref_idx;
};

struct HitRecord {
    vec3 p;
    vec3 normal;
    float t;
    bool front_face;
    Material material;
};

void HitRecord_setFaceNormal(inout HitRecord rec, Ray r, vec3 outward_normal) {
    rec.front_face = dot(r.dir, outward_normal) < 0.0;
    rec.normal = rec.front_face ? outward_normal : -outward_normal;
}

// ------------------------------------------------------------------------------------------------ utils

float rndNum;

// 0.0~1.0を返す疑似乱数生成
float random() {
    vec3 seed = vec3(gl_FragCoord.xy, rndNum);
    rndNum = rndNum + 1.0;
    float dt = dot(seed.xy, vec2(12.9898, 78.233)) + seed.z;
    return fract(sin(dt) * 43758.5453);
}

// [min,max) の実数乱数を返す
float random_range(float min, float max) {
    return min + (max - min) * random();
}

vec3 random_vec3() {
    return vec3(random(), random(), random());
}

vec3 random_vec3_range(float min, float max) {
    return vec3(random_range(min, max), random_range(min, max), random_range(min, max));
}

vec3 random_in_unit_sphere() {
    for (int i = 0; i < 100; i++) {
        vec3 p = random_vec3_range(-1.0, 1.0);
        if (dot(p, p) < 1.0) {
            return p;
        }
    }
    return vec3(0.0); // 諦める
}

vec3 random_unit_vector() {
    float a = random_range(0.0, 2.0 * PI);
    float z = random_range(-1.0, 1.0);
    float r = sqrt(1.0 - z * z);
    return vec3(r * cos(a), r * sin(a), z);
}

vec3 random_in_unit_disk() {
    for (int i = 0; i < 100; i++) {
        vec3 p = vec3(random_range(-1.0, 1.0), random_range(-1.0, 1.0), 0);
        if (dot(p, p) < 1.0) {
            return p;
        }
    }
    return vec3(0.0); // 諦める
}

float degrees_to_radians(float degrees) {
    return degrees * PI / 180.0;
}

// ------------------------------------------------------------------------------------------------ マテリアル

Material Material_initLambertian(vec3 albedo) {
    return Material(MATERIAL_LAMBERTIAN, albedo, 0.0, 0.0);
}

Material Material_initMetal(vec3 albedo, float fuzz) {
    return Material(MATERIAL_METAL, albedo, fuzz, 0.0);
}

Material Material_initDielectric(float ref_idx) {
    return Material(MATERIAL_DIELECTRIC, vec3(0.0), 0.0, ref_idx);
}

float schlick(float cosine, float ref_idx) {
    float r0 = (1.0 - ref_idx) / (1.0 + ref_idx);
    r0 = r0 * r0;
    return r0 + (1.0 - r0) * pow((1.0 - cosine), 5.0);
}

bool Material_scatter(Material mat, Ray r_in, HitRecord rec, inout vec3 attenuation, inout Ray scattered) {
    if (mat.type == MATERIAL_METAL) {
        vec3 reflected = reflect(normalize(r_in.dir), rec.normal);
        scattered = Ray(rec.p, reflected + mat.fuzz * random_in_unit_sphere());
        attenuation = mat.albedo;
        return (dot(scattered.dir, rec.normal) > 0.0);
    } else if (mat.type == MATERIAL_LAMBERTIAN) {
        vec3 scatter_direction = rec.normal + random_unit_vector();
        scattered = Ray(rec.p, scatter_direction);
        attenuation = mat.albedo;
        return true;
    } else if (mat.type == MATERIAL_DIELECTRIC) {
        attenuation = vec3(1.0, 1.0, 1.0);
        float etai_over_etat = (rec.front_face) ? (1.0 / mat.ref_idx) : (mat.ref_idx);

        vec3 unit_direction = normalize(r_in.dir);
        float cos_theta = min(dot(-unit_direction, rec.normal), 1.0);
        float sin_theta = sqrt(1.0 - cos_theta * cos_theta);
        if (etai_over_etat * sin_theta > 1.0) {
            vec3 reflected = reflect(unit_direction, rec.normal);
            scattered = Ray(rec.p, reflected);
            return true;
        }
        float reflect_prob = schlick(cos_theta, etai_over_etat);
        if (random() < reflect_prob) {
            vec3 reflected = reflect(unit_direction, rec.normal);
            scattered = Ray(rec.p, reflected);
            return true;
        }
        vec3 refracted = refract(unit_direction, rec.normal, etai_over_etat);
        scattered = Ray(rec.p, refracted);
        return true;
    }
}

// ------------------------------------------------------------------------------------------------ 球体

const int SPHERES_SIZE = 4;
struct Sphere {
    vec3 center;
    float radius;
    Material material;
};

Sphere spheres[SPHERES_SIZE];

bool Sphere_hit(Sphere sphere, Ray r, float ray_tmin, float ray_tmax, inout HitRecord rec) {
    vec3 oc = sphere.center - r.orig;
    float a = dot(r.dir, r.dir);
    float h = dot(r.dir, oc);
    float c = dot(oc, oc) - sphere.radius * sphere.radius;
    float discriminant = h * h - a * c;

    if (discriminant < 0.0) {
        return false;
    }

    float sqrtd = sqrt(discriminant);

    // Find the nearest root that lies in the acceptable range.
    float root = (h - sqrtd) / a;
    if (root <= ray_tmin || ray_tmax <= root) {
        root = (h + sqrtd) / a;
        if (root <= ray_tmin || ray_tmax <= root) {
            return false;
        }
    }

    rec.t = root;
    rec.p = Ray_at(r, rec.t);
    rec.material = sphere.material;
    vec3 outward_normal = (rec.p - sphere.center) / sphere.radius;
    HitRecord_setFaceNormal(rec, r, outward_normal);

    return true;
}

bool spheresHit(Ray r, float ray_tmin, float ray_tmax, inout HitRecord rec) {
    HitRecord temp_rec;
    bool hit_anything = false;
    float closest_so_far = ray_tmax;

    for (int i = 0; i < SPHERES_SIZE; i++) {
        if (Sphere_hit(spheres[i], r, ray_tmin, closest_so_far, temp_rec)) {
            hit_anything = true;
            closest_so_far = temp_rec.t;
            rec = temp_rec;
        }
    }

    return hit_anything;
}

// ------------------------------------------------------------------------------------------------ camera

struct Camera {
    vec3 origin;
    vec3 lower_left_corner;
    vec3 horizontal;
    vec3 vertical;
    vec3 u, v, w;
    float lens_radius;
};

void Camera_init(inout Camera camera, vec3 lookfrom, vec3 lookat, vec3 vup, float vfov, float aspect_ratio, float aperture, float focus_dist) {
    float theta = degrees_to_radians(vfov);
    float h = tan(theta / 2.0);
    float viewport_height = 2.0 * h;
    float viewport_width = aspect_ratio * viewport_height;

    camera.w = normalize(lookfrom - lookat);
    camera.u = normalize(cross(vup, camera.w));
    camera.v = cross(camera.w, camera.u);

    camera.origin = lookfrom;
    camera.horizontal = focus_dist * viewport_width * camera.u;
    camera.vertical = focus_dist * viewport_height * camera.v;
    camera.lower_left_corner = camera.origin - camera.horizontal / 2.0 - camera.vertical / 2.0 - focus_dist * camera.w;
    camera.lens_radius = aperture / 2.0;
}

Ray Camera_getRay(inout Camera camera, float u, float v) {
    vec3 rd = camera.lens_radius * random_in_unit_disk();
    vec3 offset = u * rd.xxx + v * rd.yyy;

    return Ray(camera.origin + offset, camera.lower_left_corner + u * camera.horizontal + v * camera.vertical - camera.origin - offset);
}

// ------------------------------------------------------------------------------------------------ render

vec3 ray_color(Ray r) {
    vec3 accumulated_color = vec3(1.0, 1.0, 1.0);

    for (int depth = 0; depth < MAX_DEPTH; depth++) {
        HitRecord rec;

        // レイがオブジェクトにヒットしたかを確認
        if (spheresHit(r, 0.001, 3.402823466e+38, rec)) {
            Ray scattered;
            vec3 attenuation;
            if (!Material_scatter(rec.material, r, rec, attenuation, scattered)) {
                return vec3(0.0);
            }
            r = scattered;
            accumulated_color *= attenuation;
        } else {
            // 背景色を計算
            vec3 unit_direction = normalize(r.dir);
            float a = 0.5 * (unit_direction.y + 1.0);
            accumulated_color *= mix(vec3(1.0, 1.0, 1.0), vec3(0.5, 0.7, 1.0), a);
            return accumulated_color;
        }
    }

    return vec3(0.0);
}

// ------------------------------------------------------------------------------------------------ main

void main() {
    rndNum = 0.0;

    spheres[0] = Sphere(vec3(0.0, 0.0, 0.0), 0.5, Material_initLambertian(vec3(0.7, 0.3, 0.3)));
    spheres[1] = Sphere(vec3(0.0, -100.5, 0.0), 100.0, Material_initLambertian(vec3(0.8, 0.8, 0.0))); // big

    float a = -u_Time / 600.0;
    float dis = cos(u_Time / 1200.0) * 0.3 + 1.3;
    spheres[2] = Sphere(vec3(cos(a) * dis, 0.0, sin(a) * dis), 0.5, Material_initMetal(vec3(.8, .8, .8), 0.0));
    a += PI;
    spheres[3] = Sphere(vec3(cos(a) * dis, 0.0, sin(a) * dis), 0.5, Material_initDielectric(1.5));

    Camera cam;
    vec3 lookfrom = vec3(3.0, 2.0 + sin(u_Time / 1300.0) * 2.0, 3.0);
    vec3 lookat = vec3(0.0, 0.0, 0.0);
    vec3 vup = vec3(0.0, 1.0, 0.0);
    float dist_to_focus = length(lookfrom - lookat);
    float aperture = 0.1;

    Camera_init(cam, lookfrom, lookat, vup, 20.0, u_Resolution.x / u_Resolution.y, aperture, dist_to_focus);

    vec3 pixel_color = vec3(0.0, 0.0, 0.0);

    for (int i = 0; i < samples_per_pixel; i++) {
        float u = (gl_FragCoord.x + random() - 0.5) / (u_Resolution.x - 1.0);
        float v = (gl_FragCoord.y + random() - 0.5) / (u_Resolution.y - 1.0);
        Ray r = Camera_getRay(cam, u, v);
        pixel_color += ray_color(r);
    }

    float scale = 1.0 / float(samples_per_pixel);
    gl_FragColor = vec4(sqrt(scale * pixel_color.r), sqrt(scale * pixel_color.g), sqrt(scale * pixel_color.b), 1.0);
    //gl_FragColor = vec4(gl_FragCoord.x / u_Resolution.x, random(), random(), 1.0);
}
`;

$(async () => {
    const aspect_ratio = 16.0 / 9.0;
    const image_width = 400;
    let image_height = Math.floor(image_width / aspect_ratio);
    image_height = (image_height < 1) ? 1 : image_height;

    const canvas = document.createElement("canvas") as HTMLCanvasElement;
    canvas.width = image_width;
    canvas.height = image_height;
    document.body.appendChild(canvas);
    const canvasWebGL = new CanvasWebGL(canvas, vertexShaderSource, fragmentShaderSource);

    let fps = 0;
    let lastSection = 0;
    const onFrame = (time: DOMHighResTimeStamp) => {
        const section = Math.floor(time / 1000);
        if (lastSection != section) {
            lastSection = section;
            $("#fps").text(fps);
            fps = 0;
        }
        fps++;
        canvasWebGL.render(time);
        requestAnimationFrame(onFrame);
    };
    requestAnimationFrame(onFrame);
});

class CanvasWebGL {
    private readonly gl: WebGLRenderingContext;
    private readonly uTimeLocation: WebGLUniformLocation | null;

    constructor(canvas: HTMLCanvasElement, vertexShaderSource: string, fragmentShaderSource: string) {
        const gl = canvas.getContext("webgl");
        if (!gl) {
            throw "WebGL is not supported!";
        }
        this.gl = gl;

        const vertexShader = this.createShader(gl.VERTEX_SHADER, vertexShaderSource);
        const fragmentShader = this.createShader(gl.FRAGMENT_SHADER, fragmentShaderSource);

        const program = gl.createProgram();
        gl.attachShader(program, vertexShader);
        gl.attachShader(program, fragmentShader);
        gl.linkProgram(program);

        if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
            console.error('Program link error:', gl.getProgramInfoLog(program));
        }

        gl.useProgram(program);

        const vertices = new Float32Array([
            -1.0, -1.0,
            1.0, -1.0,
            -1.0, 1.0,
            1.0, 1.0
        ]);

        const buffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

        const aPosition = gl.getAttribLocation(program, "a_Position");
        gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(aPosition);

        const uResolutionLocation = gl.getUniformLocation(program, "u_Resolution");
        gl.uniform2f(uResolutionLocation, canvas.width, canvas.height);

        this.uTimeLocation = gl.getUniformLocation(program, "u_Time");

        //this.render(0);
    }

    render(time: number) {
        const gl = this.gl;
        gl.uniform1f(this.uTimeLocation, time);
        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT);
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
    }

    private createShader(type: GLenum, source: string): WebGLShader {
        const shader = this.gl.createShader(type);
        if (!shader) {
            throw "shader can't created!"
        }
        this.gl.shaderSource(shader, source);
        this.gl.compileShader(shader);
        if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
            throw `Shader compile error: ${this.gl.getShaderInfoLog(shader)}`;
        }
        return shader;
    }
}
              
            
!
999px

Console