Giter VIP home page Giter VIP logo

webgl-fundamentals's Introduction

WebGL Fundamentals

This is a series of lessons or tutorials about WebGL.

Unlike most WebGL lessons these are not based off of OpenGL. OpenGL is 20 years old. The lessons of OpenGL don't match well with WebGL. The APIs have changed too much. The ideas of OpenGL and OpenGL tutorials are out of date with WebGL, OpenGL ES 2.0 and the land of shaders.

I would argue that WebGL is actually a very simple API. What makes it appear complicated is the way in which it's used. The complications are added by the programmer. WebGL itself is simple.

These lessons try to show that simplicity as well as teach the fundamentals of 2D math and 3D math so readers can hopefully have an easier time writing their own WebGL programs and understanding the complexity that other programmers pile on top of simple WebGL.

This is work in progress. Feel free to contribute.

Contributing

Of course bug fixes are always welcome.

If you'd like to write a new article please try to always take one step at a time. Don't do 2 or more things in a single step. Explain any new math in the simplest terms possible. Ideally with diagrams where possible.

Translating

Each translation goes in a folder under webgl/lessons/<country-code>.

Required files are

langinfo.hanson
index.md
toc.html

langinfo.hanson

Defines various language specific options. Hanson is a JSON like format but allows comments.

Current fields are

{
  // The language (will show up in the language selection menu)
  language: 'English',

  // Phrase that appears under examples
  defaultExampleCaption: "click here to open in a separate window",

  // Title that appears on each page
  title: 'WebGL Fundamentals',

  // Basic description that appears on each page
  description: 'Learn WebGL from the ground up. No magic',

  // Link to the language root.
  link: 'https://webglfundamentals.org/webgl/lessons/ja',  // replace `ja` with country code

  // html that appears after the article and before the comments
  commentSectionHeader: '<div>Issue/Bug? <a href="https://github.com/gfxfundamentals/webgl-fundamentals/issues">Create an issue on github</a>.</div>',

  // markdown that appears for untranslated articles
  missing: "Sorry this article has not been translated yet. [Translations Welcome](https://github.com/gfxfundamentals/webgl-fundamentals)! 😄\n\n[Here's the original English article for now]({{{origLink}}}).",

  // the phrase "Table of Contents"
  toc: "Table of Contents",

  // translation of categories for table of contents
  categoryMapping: {
    'fundamentals': "Fundamentals",
    'image-processing': "Image Processing",
    'matrices': "2D translation, rotation, scale, matrix math",
    '3d': "3D",
    'lighting': "Lighting",
    'organization': "Structure and Organization",
    'geometry': "Geometry",
    'textures': "Textures",
    'rendertargets': "Rendering To A Texture",
    '2d': "2D",
    'text': "Text",
    'misc': "Misc",
    'reference': "Reference",
  },

}

index.md

This is the template for the main page for each language

toc.html

This is template for the table of contents for the language. It is included on both the index and on each article. The only parts not auto-generated are the links ending links which you can translate if you want to. The build system will create a placeholder for every English article for which there is no corresponding article in that language. It will be filled with the missing message from above.

lang.css

This is included if and only if it exists. I'd strongly prefer not to have to use it. In particular I don't want people to get into arguments about fonts but basically it's a way to choose the fonts per language. You should only set the variables that are absolutely needed. Example

/* lessons/ko/lang.css */

/* Only comment in overrides as absolutely necessary! */
:root {
  --article-font-family: "best font for korean article text";
  --headline-font-family: "best font for korean headlines";
  /* a block of code */
  /* --code-block-font-family: "Lucida Console", Monaco, monospace; */
  /* a word in a sentence */
  /* --code-font-family: monospace; */
}

Notice 2 settings are not changed. It seems unlikely to me that code would need a different font per language.

PS: While we're here, I love code fonts with ligatures but they seem like a bad idea for a tutorial site because the ligatures hide the actual characters needed so please don't ask for or use a ligature code font here.

Translation notes

The build process will make a placeholder html file for each article that has an English .md file in webgl/lessons but no corresponding .md file for the language. This is to make it easy to include links in an article that links to another article but that other article has not yet been translated. This way you don't have to go back and fix already translated articles. Just translate one article at a time and leave the links as is. They'll link to placeholders until someone translates the missing articles.

Articles have front matter at the top

Title: Localized Title of article
Description: Localized description of article (used in RSS and social media tags)
TOC: Localized text for Table of Contents

DO NOT CHANGE LINKS : For example a link to a local resources might look like

[text](link)

or

<img src="somelink">

While you can add query parameters (see below) do not add "../" to try to make the link relative to the .md file. Links should stay as though the article exists at the same location as the original English.

UI localization

Some of the diagrams allow passing translations for the UI and other text.

For example if there is a slider named "rotation" you can add "?ui-rotation=girar" at the end of the URL for the diagram. For 2 or more translations separate them with a &. Certain characters are disallowed in URLs like =, #, & etc. For those use their uri encoding.

For diagram labels you'll have to look inside the code. For example for the directional lighting diagram near the start of the code it looks like this

const lang = {
  lightDir: opt.lightDir || "light direction",
  dot: opt.dot || "dot(reverseLightDirection,surfaceDirection) = ",
  surface1: opt.surface1 || "surface",
  surface2: opt.surface2 || "direction",
};

Which means you can localize the labels like this

{{{diagram url="resources/directional-lighting.html?lightDir=光線方向&surface1=オブジェクト&surface2=表面方向&dot=dot(光線反対方向,表面方向)%20%3D%20&ui-rotation=角度" caption="方向を回転してみて" width="500" height="400"}}}

For testing, reference the sample directly in your browser. For example

http://localhost:8080/webgl/lessons/resources/directional-lighting.html?lightDir=光線方向&surface1=オブジェクト&surface2=表面方向&dot=dot(光線反対方向,表面方向)%20%3D%20&ui-rotation=角度

To build

The site is built into the out folder

Steps

git clone https://github.com/gfxfundamentals/webgl-fundamentals.git
cd webgl-fundamentals
npm install
npm run build
npm start

now open your browser to http://localhost:8080

Continuous build

You can run npm run watch after you've built to get continuous building. Only the article .md files and files that are normally copied are watched. The index files (the top page with the table of contents) is not regenerated nor does changing a template rebuild all the articles.

Build options

This is mostly for debugging build.js. Since it takes a while to process all the files you can set ARTICLE_FILTER to a substring of the filenames to process. For example

ARTICLE_FILTER=rotation npm run build

Will build the site as though only articles with rotation in their filename exist.

TO DO

A list of articles I'd like to write or see written

  • lighting
    • normal maps
  • geometry
    • plane, cube, sphere, cone, disc, torus
      • lines vs triangles
      • vertex colors
    • other
    • pre-process (don't load .obj, .dae, .fbx etc at runtime)
    • pre-optimize (texture atlas, sizes, combine meshes, etc...)
  • animation
    • blendshapes
    • hierarchical animation
  • debugging
    • debugging JS WebGL
      • example (https://goo.gl/8U5whT)
      • CHECK THE GAWD DAMN CONSOLE!
        • actually read the error message
        • understand it.
          • INVALID_ENUM means one of your gl.XXX values is not valid period
          • INVALID_VALUE means one of the int or float values is probably off
          • INVALID_OPERATION means something you tried to do won't work for the given state
          • texture not renderable
          • attribute out of range
          • check your framebuffers
          • check your extensions
      • make shorter samples (MCVE)
        • remove any code you don't need
        • get rid of CSS
        • get rid of HTML
        • consider using a POINT (no attributes needed)
        • don't use images if they are not relevant. Use a canvas or a single and double pixel texture
        • While creating this MCVE you'll often find the bug
    • debugging a shader
      • set fragment shader to solid color.
      • render normals
      • render texcoords
      • render cube/sphere/plane
  • text
    • glyph cache
  • post processing
    • DOF
    • glow
    • light rays
    • RGB glitch, CRT distortion, scanlines
    • color mapping/tone mapping
  • Creative coding
    • color palettes
    • tilemaps
    • generated geometry
    • histogram
    • particles
    • toon/ramp shading
    • procedural textures
  • code organization
    • scene graph
      • putting lights and camera in scene graph
  • Engine Creation
    • culling
      • frustum culling
      • grid culling
      • quad tree / oct tree
      • portals (is this still a thing?)
      • PVS
    • materials
    • lighting DB
  • Physically based rendering

webgl-fundamentals's People

Contributors

afilahkle avatar ahaoboy avatar alicialics avatar anderspitman avatar astrak avatar bbbbx avatar billytrend avatar code945 avatar daiyi avatar dcrystalj avatar diska avatar francoisroyen avatar fuzhenn avatar greggman avatar jiebai avatar joaonnetonunes avatar kawaguchi1102 avatar kolosov-sergey avatar lolosssss avatar meglio avatar nikitait avatar nikolas avatar paulmasson avatar princessgod avatar ray-zero2 avatar technohippy avatar trusktr avatar vanzo16-github avatar vinci-mz avatar zanllp avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

webgl-fundamentals's Issues

webgl blendequation use error

(1st way) gl_FragData[0]=vec4(tempcolor*tempAlpha,1.0);
(2nd way) gl_FragData[0]=vec4(tempcolor,tempAlpha);

l write a shader, use up shorte words in fragment shader,test the webgl blendequation。
this webgl sets is:
webgl.enable(webgl.BLEND);
webgl.blendEquation(webgl.FUNC_ADD);
webgl.blendFunc(webgl.SRC_ALPHA,webgl.ONE);

l use those shader render one quad,the background is black, texture alphaPremultiply=false.
the rend result is diffrent between two way, but in this enviroment (l set) the render results should be the same, and the 1st way is agree with test result in unity3d。

can you help me abount this ?is there anything wrong l do? Thank you for the first.

----------- here is my fs shader:
uniform sampler2D _MainTex;
uniform mediump vec4 _Main_Color;

varying lowp vec4 v_color;
varying mediump vec2 _maintex_uv;
varying mediump vec2 _mask_uv;

void main()
{
highp vec4 v_color =_Main_Color2.0;
highp vec4 basecolor=texture2D(_MainTex,_maintex_uv);
lowp vec3 tempcolor=v_color.rgb
basecolor.rgb;
lowp float tempAlpha=v_color.a*basecolor.a;

//gl_FragData[0]=vec4(tempcolor*tempAlpha,1.0);
gl_FragData[0]=vec4(tempcolor,tempAlpha);

}

Passing varying color from vert to frag.

About varyings in the 3D ortho lesson, when we used them to pass colors from the vert shader to the frag shader, we had the same color for every three vertices. Does it only matter what color we pass from every third shader? f.e., I was thinking it would execute 3 vertex shaders then execute the frag shaders for the triangle made from the three vert shaders, so then the color would be passed from the third of the three vert shaders to all fragments after those three vert shaders?

Explain CORS

This sucks but I need to explain CORS issues with images.

2D matrix lesson - projectionMatrix makes hierarchical items disappear.

Hey hey, I tried to add projectMatrix in the 2d-matrix lesson, but then all my "child" items disappear.

Here's what I'm trying. How would I get the children to still be there, like in your example with the 5 extra F's, but using projectionMatrix?

function createWebGLContext(target) {
    const canvas = createCanvas(target, '100%', '100%')
    return getGl(canvas)
}

function setGlResolution(gl, width, height) {
    gl.canvas.width = width
    gl.canvas.height = height
    gl.viewport(0, 0, width, height);
}

function createCanvas(parent, width, height) {
    const canvas = document.createElement('canvas')
    canvas.style.width = width
    canvas.style.height = height
    parent.appendChild(canvas)
    return canvas
}

function getGl(canvasOrSelector) {
    let canvas

    if (canvasOrSelector instanceof HTMLCanvasElement)
        canvas = canvasOrSelector

    if (!canvas)
        canvas = document.querySelector(canvasOrSelector)

    if (!(canvas instanceof HTMLCanvasElement)) throw new TypeError('No canvas!')

    return canvas.getContext('webgl')
}

function createShader(gl, type, source) {
    // Create a vertex shader object
    const shader = gl.createShader(type)

    // Attach vertex shader source code
    gl.shaderSource(shader, source)

    // Compile the vertex shader
    gl.compileShader(shader)

    const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);

    if (success) return shader

    const error = new Error("*** Error compiling shader '" + shader + "':" + gl.getShaderInfoLog(shader));
    gl.deleteShader(shader)
    throw error
}

function createProgram(gl, vertexShader, fragmentShader) {
    // Create a shader program object to store
    // the combined shader program
    const program = gl.createProgram()

    // Attach a vertex shader
    gl.attachShader(program, vertexShader)

    // Attach a fragment shader
    gl.attachShader(program, fragmentShader)

    // Link both programs
    gl.linkProgram(program)

    const success = gl.getProgramParameter(program, gl.LINK_STATUS)
    if (success) {
        return program
    }

    console.log(gl.getProgramInfoLog(program))
    gl.deleteProgram(program)
}

class Quad {
    constructor(x, y, width, height) {
        this.x = x
        this.y = y
        this.width = width
        this.height = height
        this.verts = []

        this.calcVerts()
    }

    calcVerts() {
        const {x,y} = this
        const {width,height} = this
        const x2 = x + width
        const y2 = y + height
        const {verts} = this

        verts[0] = x
        verts[1] = y

        verts[2] = x2
        verts[3] = y

        verts[4] = x2
        verts[5] = y2

        verts[6] = x2
        verts[7] = y2

        verts[8] = x
        verts[9] = y2

        verts[10] = x
        verts[11] = y

        return verts
    }
}

var m3 = {
    identity: Object.freeze([
        1, 0, 0,
        0, 1, 0,
        0, 0, 1,
    ]),

    translate: function(tx, ty) {
        return [
            1, 0, 0,
            0, 1, 0,
            tx, ty, 1,
        ];
    },

    rotate: function(angleInRadians) {
        var c = Math.cos(angleInRadians);
        var s = Math.sin(angleInRadians);
        return [
            c,-s, 0,
            s, c, 0,
            0, 0, 1,
        ];
    },

    scale: function(sx, sy) {
        return [
            sx, 0, 0,
            0, sy, 0,
            0, 0, 1,
        ];
    },

    // Note: This matrix flips the Y axis so that 0 is at the top.
    project: function(width, height) {
        // longer version, multiple matrices
        let matrix = m3.identity
        matrix = m3.multiply(m3.scale(1/width, 1/height), matrix) // get the portion of clip space
        matrix = m3.multiply(m3.scale(2, 2), matrix) // convert to clip space units
        matrix = m3.multiply(m3.translate(-1, -1), matrix) // Move from the center to bottom left
        matrix = m3.multiply(m3.scale(1, -1), matrix) // move to the top left like DOM
        return matrix

        // shorter version, manual result of the longer version
        //return [
            //2 / width,        0,           0,
                //0,       -2 / height,      0,
               //-1,            1,           1
        //];
    },

    multiply: function(a, b) {
        var a00 = a[0];
        var a01 = a[1];
        var a02 = a[2];
        var a10 = a[3];
        var a11 = a[4];
        var a12 = a[5];
        var a20 = a[6];
        var a21 = a[7];
        var a22 = a[8];
        var b00 = b[0];
        var b01 = b[1];
        var b02 = b[2];
        var b10 = b[3];
        var b11 = b[4];
        var b12 = b[5];
        var b20 = b[6];
        var b21 = b[7];
        var b22 = b[8];

        return [
            b00 * a00 + b01 * a10 + b02 * a20,
            b00 * a01 + b01 * a11 + b02 * a21,
            b00 * a02 + b01 * a12 + b02 * a22,
            b10 * a00 + b11 * a10 + b12 * a20,
            b10 * a01 + b11 * a11 + b12 * a21,
            b10 * a02 + b11 * a12 + b12 * a22,
            b20 * a00 + b21 * a10 + b22 * a20,
            b20 * a01 + b21 * a11 + b22 * a21,
            b20 * a02 + b21 * a12 + b22 * a22,
        ];
    },
};

export default
function webglFundamentals() {

    const gl = createWebGLContext(document.querySelector('#app-root'))

    if (!gl) { console.log('no GL for you.') }

    // ------------------------------------------------------------------------------------------------------------------------
    const vertShader = createShader(gl, gl.VERTEX_SHADER, `
        attribute vec2 vertex;
        uniform mat3 matrix;

        void main() {
            vec2 clipSpace = (matrix * vec3(vertex, 1)).xy;
            gl_Position = vec4(clipSpace, 0, 1);
            //gl_PointSize = 10.0;
        }
    `)

    // ------------------------------------------------------------------------------------------------------------------------
    const fragShader = createShader(gl, gl.FRAGMENT_SHADER, `
        precision mediump float;
        uniform vec4 color;

        void main(void) {
            gl_FragColor = color;
            //gl_FragColor = vec4(0.8,0.3,1,1);
        }
    `)

    const quad = new Quad(0,0,100,100)

    const program = createProgram(gl, vertShader, fragShader)

    // Use our pair of shaders
    gl.useProgram(program)

    const matrixLocation = gl.getUniformLocation(program, "matrix")
    const colorUniformLocation = gl.getUniformLocation(program, 'color')
    const vertexAttributeLocation = gl.getAttribLocation(program, "vertex")

    const vertexBuffer = gl.createBuffer()
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(quad.verts), gl.STATIC_DRAW)

    // Tell the attribute how to get data out of vertexBuffer (ARRAY_BUFFER)
    const size = 2;          // 2 components per iteration
    const type = gl.FLOAT;   // the data is 32bit floats
    const normalize = false; // don't normalize the data
    const stride = 0;        // 0 = move forward size * sizeof(type) each iteration to get the next vertex
    const offset = 0;        // start at the beginning of the buffer
    const count = 6

    gl.enableVertexAttribArray(vertexAttributeLocation);
    gl.vertexAttribPointer(
        vertexAttributeLocation, size, type, normalize, stride, offset)

    const angle  = {theta: 0}
    const origin = [0.5, 0.5]

    const originMatrix      = m3.translate(-(quad.width * origin[0]), -(quad.height * origin[1]))
    let   rotationMatrix    = m3.rotate(angle.theta)
    const scaleMatrix       = m3.scale(1,1)
    const translationMatrix = m3.translate(100, 100)

    let resolution = [
        window.innerWidth * window.devicePixelRatio,
        window.innerHeight * window.devicePixelRatio,
    ]

    let projectionMatrix = m3.project(...resolution)

    setGlResolution(gl, ...resolution)

    // TODO: watch parent size instead of window.
    window.addEventListener('resize', () => {
        resolution = [
            window.innerWidth * window.devicePixelRatio,
            window.innerHeight * window.devicePixelRatio,
        ]
        setGlResolution(gl, ...resolution)
        projectionMatrix = m3.project(...resolution)
    })

    function draw(time) {
        gl.clearColor(0, 0, 0, 1)
        gl.clear(gl.COLOR_BUFFER_BIT)

        rotationMatrix = m3.rotate(angle.theta)

        let matrix = m3.identity
        matrix = m3.multiply(originMatrix, matrix);
        matrix = m3.multiply(scaleMatrix, matrix);
        matrix = m3.multiply(rotationMatrix, matrix);
        matrix = m3.multiply(translationMatrix, matrix);
        matrix = m3.multiply(projectionMatrix, matrix);
        gl.uniformMatrix3fv(matrixLocation, false, matrix)

        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(quad.verts), gl.STATIC_DRAW)
        gl.drawArrays(gl.TRIANGLES, offset, count)

        for (let i = 0; i < 5; ++i) {
          matrix = m3.multiply(originMatrix, matrix);
          matrix = m3.multiply(rotationMatrix, matrix);
          matrix = m3.multiply(scaleMatrix, matrix);
          matrix = m3.multiply(translationMatrix, matrix);
          matrix = m3.multiply(projectionMatrix, matrix);

          gl.uniformMatrix3fv(matrixLocation, false, matrix);

          gl.drawArrays(gl.TRIANGLES, offset, count)
        }

        gl.uniform4f(colorUniformLocation, Math.random(), Math.random(), Math.random(), 1)

    }

    draw()
}

shader att/color blend

1.if the shader have vertexcolor attribute,but the mesh data not have vertexcolor data, can we give the vertex color of shader a default value ,such as white color?

2.After src color blend with dst color,we get a new color,whether it will be clamp 0-1? maybe the new color will be use to blend with other,l confused with the blend result.

Translation

Hello,
I'd like to translate your "WebGL Fundamentals" guide and publish the translation as a part of my site with an information about your authorship of course. The idea is probably compatible with your licence, but let me confirm - would you like to agree for this kind of activity?

Kind regards,
M.

About 2D matrices

Hi, Greggman. I have some confuse about the article "WebGL 2D Matrices“.
As i know, OpenGL and WebGL usually use the column vector, and DirectX use row vector. The different of them is the multiplication order.
For example, if use row vector V , V' = V * M1 * M2 * M3, the M1, M2, M3 refer different transformation matrix, and they will apply in 1, 2, 3 order, if M1 is scale, M2 is rotation, M3 is translation. V will scale first,
then rotation, translation last. If use column vector V, V' = M3 * M2 * M1 * V, the transformation order will be same, but the matrix value maybe a little different from last one. So in the article you explain the math with row vector but use column vector in code, and finally the explain of transformation order really confused me.
Here are something related about.

Tracking needed translation updates

I'm thinking of trying to auto generate a page that shows each article and whether or not a given translation for that article is up to date. The easiest way would be to check at build time if the english article is newer than the translated article. The problem is even minor typo fixes would show up as a translated article being out of date.

Any thoughts?

Maybe I could pick some metric like more than N lines for changes? Or maybe this is not a useful feature.

2D Geometry Rotation: mine doesn't work like yours, but it is the same?

I made my own based on https://webglfundamentals.org/webgl/webgl-2d-geometry-rotation.html.

In your example, the rotation rotates the letter F, then it always translates horizontally when moving the X slider.

But in mine, if I animate the X translation, the translation is no longer horizontal after applying the rotation. If I rotate 90 degrees, then the X translation causes the square in my example to move vertically.

What's the difference? Why does my square move vertically instead of like yours where the F always moves horizontally when modifying X translation?

Here's my code, which is very similar to the first tutorial, but I made a square and am animating it:

    function createWebGLContext(target) {
        const canvas = createCanvas(target, '100%', '100%')
        return getGl(canvas)
    }

    function setResolution(gl, width, height, resolutionUniformLocation) {
        gl.canvas.width = width
        gl.canvas.height = height
        gl.viewport(0, 0, width, height);
        if (resolutionUniformLocation)
            gl.uniform2f(resolutionUniformLocation, width, height)
    }

    function createCanvas(parent, width, height) {
        const canvas = document.createElement('canvas')
        canvas.style.width = width
        canvas.style.height = height
        parent.appendChild(canvas)
        return canvas
    }

    function getGl(canvasOrSelector) {
        let canvas

        if (canvasOrSelector instanceof HTMLCanvasElement)
            canvas = canvasOrSelector

        if (!canvas)
            canvas = document.querySelector(canvasOrSelector)

        if (!(canvas instanceof HTMLCanvasElement)) throw new TypeError('No canvas!')

        return canvas.getContext('webgl')
    }

    function createShader(gl, type, source) {
        // Create a vertex shader object
        const shader = gl.createShader(type)

        // Attach vertex shader source code
        gl.shaderSource(shader, source)

        // Compile the vertex shader
        gl.compileShader(shader)

        const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);

        if (success) return shader

        const error = new Error("*** Error compiling shader '" + shader + "':" + gl.getShaderInfoLog(shader));
        gl.deleteShader(shader)
        throw error
    }

    function createProgram(gl, vertexShader, fragmentShader) {
        // Create a shader program object to store
        // the combined shader program
        const program = gl.createProgram()

        // Attach a vertex shader
        gl.attachShader(program, vertexShader)

        // Attach a fragment shader
        gl.attachShader(program, fragmentShader)

        // Link both programs
        gl.linkProgram(program)

        const success = gl.getProgramParameter(program, gl.LINK_STATUS)
        if (success) {
            return program
        }

        console.log(gl.getProgramInfoLog(program))
        gl.deleteProgram(program)
    }

    const gl = createWebGLContext(document.body)

    if (!gl) { console.log('no GL for you.') }

    // ------------------------------------------------------------------------------------------------------------------------
    const vertShader = createShader(gl, gl.VERTEX_SHADER, `
        attribute vec2 position;
        uniform vec2 resolution;
        uniform vec2 translation;
        uniform vec2 rotation;

        void main() {
            vec2 clipSpace;

            clipSpace = vec2(
                position.x * rotation.y + position.y * rotation.x,
                position.y * rotation.y - position.x * rotation.x
            );

            clipSpace = clipSpace + translation;

            clipSpace =
                (clipSpace / resolution) // get the portion of clip space
                * 2.0                   // convert to clip space units
                - 1.0;                  // Move from the center to bottom left

            // move to the top left like DOM
            clipSpace = clipSpace * vec2(1, -1);

            gl_Position = vec4(clipSpace, 0, 1);
            //gl_PointSize = 10.0;
        }
    `)

    // ------------------------------------------------------------------------------------------------------------------------
    const fragShader = createShader(gl, gl.FRAGMENT_SHADER, `
        precision mediump float;
        uniform vec4 color;

        void main(void) {
            gl_FragColor = color;
            //gl_FragColor = vec4(0.8,0.3,1,1);
        }
    `)

    const positionBuffer = gl.createBuffer()
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)

    class Quad {
        constructor(x, y, width, height) {
            this.x = x
            this.y = y
            this.width = width
            this.height = height
            this.verts = []

            this.calcVerts()
        }

        calcVerts() {
            const {x,y} = this
            const {width,height} = this
            const x2 = x + width
            const y2 = y + height
            const {verts} = this

            verts[0] = x
            verts[1] = y

            verts[2] = x2
            verts[3] = y

            verts[4] = x2
            verts[5] = y2

            verts[6] = x2
            verts[7] = y2

            verts[8] = x
            verts[9] = y2

            verts[10] = x
            verts[11] = y

            return verts
        }
    }

    const quad = new Quad(10,10,100,100)
    const squares = []
    squares.push(quad)

    // three 2d points
    let {verts} = quad

    const program = createProgram(gl, vertShader, fragShader)

    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW)

    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Use our pair of shaders
    gl.useProgram(program)

    const resolutionUniformLocation = gl.getUniformLocation(program, "resolution")

    const translationUniformLocation = gl.getUniformLocation(program, "translation")
    gl.uniform2f(translationUniformLocation, 300, 100)

    const rotationUniformLocation = gl.getUniformLocation(program, "rotation")
    gl.uniform2f(rotationUniformLocation, 1, 0)

    setResolution(
        gl,
        window.innerWidth * window.devicePixelRatio,
        window.innerHeight * window.devicePixelRatio,
        resolutionUniformLocation
    )

    // TODO: watch parent size instead of window.
    window.addEventListener('resize', function(e) {
        setResolution(
            gl,
            window.innerWidth * window.devicePixelRatio,
            window.innerHeight * window.devicePixelRatio,
            resolutionUniformLocation
        )
    })

    const positionAttributeLocation = gl.getAttribLocation(program, "position")
    gl.enableVertexAttribArray(positionAttributeLocation);

    const colorUniformLocation = gl.getUniformLocation(program, 'color')
    gl.uniform4f(colorUniformLocation, Math.random(), Math.random(), Math.random(), 1)

    // Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
    const size = 2;          // 2 components per iteration
    const type = gl.FLOAT;   // the data is 32bit floats
    const normalize = false; // don't normalize the data
    const stride = 0;        // 0 = move forward size * sizeof(type) each iteration to get the next position
    const offset = 0;        // start at the beginning of the buffer
    gl.vertexAttribPointer(
        positionAttributeLocation, size, type, normalize, stride, offset)

    const count = 6
    gl.drawArrays(gl.TRIANGLES, offset, count)

    let tween = new TWEEN.Tween(quad)
        .to({x:500}, 2000)
        .easing(TWEEN.Easing.Elastic.InOut)
        .start()

    let rotation = 0
    requestAnimationFrame(function loop(time) {
        tween.update(time)
        verts = quad.calcVerts()

        gl.clearColor(0, 0, 0, 1)
        gl.clear(gl.COLOR_BUFFER_BIT)

        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW)
        gl.drawArrays(gl.TRIANGLES, offset, count)

        //rotation += 0.01
        //gl.uniform2f(rotationUniformLocation, rotation, rotation)

        requestAnimationFrame(loop)
    })

Chapter webgl-3d-camera, Vector3 cross product is inversed?

The annotation under figure-6 is:

zAxis cross up = xAxis

but the code belowing is :
var xAxis = cross(up, zAxis); var yAxis = cross(zAxis, xAxis);

According the ‘The left hand rule applied to Cross(a, b).’ rule, I think the code is right, and the annotation is wrong, please check it!

My Y normals (for directional lighting) seems backwards?

When making directional lighting, I found that for normals of a face that faces upwards (which is in the positive Y direction right?) that my normal is 0, -1, 0, which seems to be backwards? I thought it would be +1 on the Y axis, since positive is up. For example, here's the normals for each side of my cube, (the cube sides are perpendicular to the XYZ axes), and it seems that "top" and "bottoms" face normals are backwards:

        const normals = [
            [0,0,1, ], // front face
            [-1,0,0, ], // left face
            [1,0,0,], // right face
            [0,0,-1,], // back face
            [0,-1,0, ], // top face
            [0,1,0,], // bottom face
        ]

Here's the full code.

Here's how the cubes are created, the sides are just perpenducular/parallel to axes.

As you can see, the top face is created 5th, and the top-face normal is also fifth in the above normals array, then the normals are mapped to the vertices.

I thought that the top face, which faces upwards, would have vertex normals of 0,1,0, and the bottom face would have vertex normals of 0,-1,0.

Why might it be opposite?

Texture coordinate mapping

The fundamental show how to draw a texture and to also use uv to warp a texture around a 3d object.

How would I draw from a spiritesheet in WebGL? Is it possible to to use uv texture coordinate system?

To me, no. Cause it sound more like a technique to position and alter the image in away that it can warp around a 3d object.

Frames with examples are not working in other languages

It seems that with the new editor.js examples are not working anymore in fr and pl translations. Though the link is correct and you can open example in separate window.

Check folllowing pages:
https://webglfundamentals.org/webgl/lessons/webgl-fundamentals.html
https://webglfundamentals.org/webgl/lessons/fr/webgl-fundamentals.html
https://webglfundamentals.org/webgl/lessons/pl/webgl-fundamentals.html

In fr and pl you see just loading indicator instead of working example.

Scaled Normals Diagram

This line:

  window.addEventListener('mousedown', function(e) {
      event.preventDefault();
  });

throws an error (event is not defined) since the name is "e" instead of "event".

The same issue is present here:

  function toggleNormals(e) {
    event.preventDefault();
    showNormals = !showNormals;
    render();
  }

Also, thanks for the amazing tutorial!

m4.js : Possible mistake in Orthographic's documentation

Computes a 4-by-4 orthographic projection matrix given the coordinates of the
planes defining the axis-aligned, box-shaped viewing volume. The matrix
generated sends that box to the unit box. Note that although left and right
are x coordinates and bottom and top are y coordinates, near and far
are not z coordinates, but rather they are distances along the negative
z-axis. We assume a unit box extending from -1 to 1 in the x and y
dimensions and from -1 to 1 in the z dimension.

There may be a mistake here, if z dimension used the same unit box as x and y wouldn't we have a phrase more like "We assume a unit box extending from -1 to 1 in the x, y and z dimensions"?

From reading the rest, it probably was meant to be:
We assume a unit box extending from -1 to 1 in the x and y dimensions and from -1 to 0 in the z dimension.

Same goes for frustum's description.

[idea] Update 3D texture tutorial on using textures in framebuffers.

That would be a nice tutorial.

Right now, in the 3D tutorial, we learn how to pull textures from a separate canvas very easily by modifying the tutorial to simply pass in the HTMLCanvasElement to gl.texImage2D. And we easily know how to render 3D scenes to a 3D canvas, so we can easily being the 3D drawing of one canvas and render it as a texture on the mesh in another canvas.

But I think drawing stuff in a single canvas webgl context may be faster? (I haven't tested). It'd be nice to also describe how to use frame buffers in the 3D texture tutorial.

I believe the Imaging tutorial probably has enough info, and I can piece it together, then maybe I can update the 3D Texture tutorial.

Make it available in a single page to save it in a reader (like Pocket)

When I read about this (in that case on Hacker News), I'm like "why not read about it when I have time".
The process becomes: save to Pocket, and then read it step by step.

But I don't want to spam my Pocket readlist with every article, but rather would save it as a long story.

Cheers

Tick mark wrong in texture sample

In this sample,
use this code in render part

       // Compute the projection matrix
132    var projectionMatrix =
133        m4.orthographic(0, gl.canvas.clientWidth, gl.canvas.clientHeight, 0, -1, 1);

and the m4.orthographic() set 0 -> gl.canvas.clientHeight form top to bottom

...
    function orthographic(left, right, bottom, top, near, far, dst) {
...

but set 0 -> positive (-1, 4) from bottom to top for tick marks ,
maybe it have to be (4, -1) from bottom to top.
I founded this when check both direction to CLAMP_TO_EDGE.

Why does the "count" variable change from 2D to 3D lesson? Only size needs to change?

I'm talking about the variable here, https://github.com/greggman/webgl-fundamentals/blob/master/webgl/webgl-3d-step1.html#L183.

Shouldn't it stay the same? And only the 'sizeshould change from 2 to 3? Because if everything is the same, thencount*size` gives the total number of vertices, and so only size changes from 2 to 3 because we're drawing the same thing, only each vertex has 3 points?

For example, in the 2D lesson size was 2 and count was 6, and in the 3D lesson size changes to 3 and count changes to 18.

Removed complicating library from http://webglfundamentals.org/webgl/webgl-2d-image.html

I refactored the basic webgl 2d image example. I removed the 1400 line library entirely and saved 2 lines in the main html file. I think as an example, this makes it much easier to follow since you don't have to trace deep into the external script file. I also made it throw proper errors now and not just strings. A little backwards-compat has been lost, mostly falling back to webgl-experimental if webgl isn't supported. At this date I don't think it's anything important.

Possible downsides: I removed some of the dumber comments, ones that stated what the next line stated just as clearly. I also reformatted things 'cause it was a mess, but some lines are a bit longer now.

The equivalent canvases are pixel-equivalent between old and new version. However, a header (WebGL - 2D Image) showed on the non-iframe version in chrome but not in firefox. I opted not to browser-detect, and left the header off in both browsers in mine. I've commented it out, so you can restore it if you want.

Also, where is this licence.html file you mention? I couldn't find it treating it as a relative or absolute url.

Here's the updated code for http://webglfundamentals.org/webgl/webgl-2d-image.html:

<!--  Licensed under a BSD license. See license.html for license  -->

<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <meta charset="utf-8">
    <title>WebGL - 2D Image Blend</title>
    <link type="text/css" href="WebGL%20-%202D%20Image%20Blend_files/webgl-tutorials.css" rel="stylesheet">
    <script>
        "use strict";

        window.onload = function main() {
            var image = new Image();
            image.onload = render.bind(null, image);
            image.src = "resources/leaves.jpg";
        }

        function render(image) {
            // Get A WebGL context
            var canvas = document.getElementById("canvas");
            var gl = canvas.getContext('webgl');

            function loadProgram(shaderScriptIDs) {
                var shaders = shaderScriptIDs.map(function(scriptId) {          
                    var shaderScript = document.getElementById(scriptId);
                    if (!shaderScript) { throw new Error("unknown script #" + scriptId); }

                    var shaderType = gl[{
                        'x-shader/x-vertex': 'VERTEX_SHADER', 
                        'x-shader/x-fragment': 'FRAGMENT_SHADER',
                    }[shaderScript.type]];
                    if (!shaderType) { throw new Error('#' + scriptId + ': unknown mimetype: ' + shaderScript.type); }

                    var shader = gl.createShader(shaderType);
                    gl.shaderSource(shader, shaderScript.text);
                    gl.compileShader(shader);
                    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 
                        throw new Error('#' + scriptId + ': WebGL compiler: ' + gl.getShaderInfoLog(shader)); 
                    }

                    return shader;
                })

                var program = gl.createProgram();
                shaders.forEach(gl.attachShader.bind(gl,program));
                gl.linkProgram(program);
                if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { 
                    throw new Error('WebGL linker: ' + gl.getProgramInfoLog(program)); 
                }
                gl.useProgram(program)

                return program;
            }

            var program = loadProgram(["2d-vertex-shader", "2d-fragment-shader"]);

            //Where the vertex data needs to go.
            var positionLocation = gl.getAttribLocation(program, "a_position");
            var texCoordLocation = gl.getAttribLocation(program, "a_texCoord");

            //Texture coordinates for the rectangle.
            var texCoordBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0,0, 1,0, 0,1, 0,1, 1,0, 1,1]), gl.STATIC_DRAW);
            gl.enableVertexAttribArray(texCoordLocation);
            gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);

            gl.bindTexture(gl.TEXTURE_2D, gl.createTexture());

            //Parameters to render any size image.
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

            //Upload the image into the texture.
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);

            //lookup uniforms
            var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
            var textureSizeLocation = gl.getUniformLocation(program, "u_textureSize");

            //set the resolution
            gl.uniform2f(resolutionLocation, canvas.width, canvas.height);

            //set the size of the image
            gl.uniform2f(textureSizeLocation, image.width, image.height);

            //Create a buffer for the position of the rectangle corners.
            var positionBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
            gl.enableVertexAttribArray(positionLocation);
            gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

            setDrawRectangle(gl, 0,0, image.width, image.height);

            //Draw the rectangle.
            gl.drawArrays(gl.TRIANGLES, 0, 6);
        }

        function setDrawRectangle(gl, x, y, width, height) {
            var x1 = x;
            var x2 = x + width;
            var y1 = y;
            var y2 = y + height;
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([x1,y1, x2,y1, x1,y2, x1,y2, x2,y1, x2,y2]), gl.STATIC_DRAW);
        }
    </script>



    <script id="2d-vertex-shader" type="x-shader/x-vertex">
        attribute vec2 a_position;
        uniform vec2 u_resolution;
        attribute vec2 a_texCoord;
        varying vec2 v_texCoord;

        void main() {
            //Convert coordinates in pixels to coordinates in percentages.
            vec2 clipSpace = a_position / u_resolution * 2.0 - 1.0;
            gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); //export the value; gl_position is a built-in magic var

            v_texCoord = a_texCoord; //can be multiplied for zoom in
        }
    </script>



    <script id="2d-fragment-shader" type="x-shader/x-fragment">
        precision mediump float; //Highp falls back to mediump on some mobile devices. See https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices for details.
        uniform sampler2D u_image; //The texture.
        varying vec2 v_texCoord; //From the vertex shader.

        void main() { gl_FragColor = texture2D(u_image, v_texCoord); }
    </script>
</head>



<body>
    <!-- <h1>WebGL - 2D Image</h1> -->
    <canvas id="canvas" width="400" height="300">
    </canvas>
</body>

</html>

Brazilian Portuguese Translation

Hello, i'm a student of Computer Science at Rio Grande do Norte State University in Brazil.

I've been working on a translation to Brazilian Portuguese with a friend. We'd like to contribute to your repo translating the whole articles. I've made a fork and if you check my github you'll see that we've already started translating.

Thanks in advance.

Why do we normalize the surfaceToView vector in the vertex shader, not the fragment shader?

In the vertex shader you have

  // compute the vector of the surface to the view/camera
  // and pass it to the fragment shader
  v_surfaceToView = normalize(u_viewWorldPosition - surfaceWorldPosition);

but then these values will be interpolated across the fragments. Don't we want to normalize in the fragment shader so we get a direction (unit) vector for each value?

EDIT: Oh I see, it is normalized again in the fragment. I think that means we don't have to normalize it also in the vertex shader, we can remove the normalize call from the vert shade right?

Share these tutorials in a china knowledge share site?

Hi greg :)

Your tutorials is perfect resources for newer who want learn WebGL from basic, really appreciate.

But a lot of china developers not know these tutorials, some of them not good at English, do not known how to learn WebGL from begining.

So I am thinking share the Simplify Chinese version of these tutorials in a china knowledge share site ZhiHu, is that legal to you? If not, i will not do that.

Thanks, BW.

Should I move to webgl2fundamentals?

I was reading your blog, and it seems like webgl2fundamentals is basically the same thing, but better. Maybe I should just stop where I am and restart on webgl2, rewriting my code until I catch up to where I was in webgl1. What's your recommendation?

Well, webgl2 isn't in Edge or Safari yet. Hmmm.

WebGL auto clear color?

Hi, @greggman .

I found in this sample, when comment these lines

72    //webglUtils.resizeCanvasToDisplaySize(gl.canvas);
73
74    // Tell WebGL how to convert from clip space to pixels
75    //gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
76
77    // Clear the canvas.
78    //gl.clear(gl.COLOR_BUFFER_BIT);

drawScene() still clear history draw, do you know the reason?

Thank you.

Why multiply camera radius by 1.5?

Why do you multiply the camera radius by 1.5?

cameraMatrix = m4.translate(cameraMatrix, 0, 0, radius * 1.5);

Instead of making the radius 200, you could just make it 300. So do we need the 1.5?

Why normalize normal vectors inside fragment shader?

For example, if we have three vertices each with normal 0,1,0, then when the normals are interpolated between the vertices for each fragment, I don't see why we need to normalize.

How is it interpolating from 0,1,0 to 0,1,0 will yield anything other than 0,1,0 if the values (normals) are exactly the same?

If we don't normalize, f.e.

vec3 normal = normalize(v_normal);

then what will the values look like across the fragments? Can you explain what to expect if we don't normalize?

Why applying zToDivideBy as W works on Z axis?

When we divided position.xy/zToDivideBy, we didnt' divide the z axis. But when we apply zToDivideBy as w, then it also divides the z value.

So the first one is

gl_Position = vec4(position.xy/zToDivideBy, position.zw)

but when we apply zToDivideBy to w,

gl_Position = vec4(position.xyz, zToDivideBy)

it is the same as

gl_Position = vec4(position.xyz/zToDivideBy, position.w)

Which is slightly different that the first one (we're also dividing the z value by zToDivideBy).

I just want to make sure, but I think it doesn't matter what we divide z by because it is the same in the orthogonal sense, and we already figured out how to adjust x and y based on the original z value, so any new z value doesn't really matter (and depth order will be still be the same)?

Chinese version?

Hi,gerggman.I've been reading webgl-fundamentals recently and find it's very useful.
I'm from china and there's no chinese version yet, so I am thinking about translate it.
So,have anyone already translate it into chinese or planned to?

Perspective lesson, would like to stay in top/left coordinates DOM-style coordinates.

I just finished the perspective lesson, but unfortunately when going from the shader math to perspective matrix, the coordinate system changed from top/left to center/center, and opposite direction for Y axis.

I would really love to keep it in the same coordinate system as with DOM, because one of my goals is to overlay DOM with WebGL and move them around in the same 3D space. This would be similar to (the now abandoned) Famous "Mixed Mode" if you've heard of that. It is also similar to these examples:

(inspect to see the DOM)

What I am aiming for is to easily make 3D content using the same coordinate system as DOM, so that I can decorate my DOM (f.e. I'd like to make some real-time shadows on a button or something).

So I was excited when the first tutorials were doing everything in top/left coordinates, with Y increasing downward, like DOM! :D

But then BAM, use this perspective matrix, no more DOM-like coordinates.

So I'd like to play with this so I can make my perspective matrix work like DOM.

I'm also going to have to figure out how CSS perspective property works, to make that also work the same in WebGL. What I've noticed is that if you set perspective:800px in CSS, then that means that an element inside that space can move towards the screen until 800px, and then it disappears. But when you move objects away from the screen (negative Z direction) then they seem to be able to move infinitely away, never clipped. That's something I'll have to figure out too (how to keep things never clipped, at least not until very very far).

If you have tips or if you know how to change the turorial to still use DOM coordinates, I'd be glad to hear. :)

Loading images to textures is trickier than described!

Apparently, loading a texture from an arbitrary image isn't that easy.

For example, I've followed the tutorial, but when I try to load a texture from a jpeg image like

        const image = new Image
        const isPowerOf2 = value => (value & (value - 1)) == 0
        image.addEventListener('load', () => {
            // Now that the image has loaded copy it to the texture.
            console.log('texture target????', gl.TEXTURE_2D)
            gl.bindTexture(gl.TEXTURE_2D, this.texture)
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image)

            // Mip maps can only be generated on images whose width and height are a power of 2.
            if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
                console.log('is power of two.')
                gl.generateMipmap(gl.TEXTURE_2D)
                // TODO make filters configurable?
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR)
            }
            else {
                console.log('is NOT power of two.')
                gl.texParameteri(gl.TETXURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) // INVALID_ENUM: texParameter: invalid texture target
                gl.texParameteri(gl.TETXURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) // INVALID_ENUM: texParameter: invalid texture target
                // TODO make filters configurable?
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
            }
        })
        image.src = imageUrl

where imageUrl is a data URL for a jpeg in my case, then I get this error in console:

scene.js:223 WebGL: INVALID_ENUM: texParameter: invalid texture target

This is happening when I try to do like in your tutorial for dealing with non-power-of-two images, on the lines above marked with INVALID_ENUM.

Unfortunately, googling around for that error has like no results!

So I am trying to figure out what is wrong, but it isn't as easy as most things web are.

Maybe we can update the tutorial with examples for specific types of popular image formats.

Why do we normalize the surfaceToView vector in both vertex and frag shaders?

In the tutorial, why do we do

  v_surfaceToView = normalize(u_viewWorldPosition - surfaceWorldPosition);

in the vertex shader, and

  vec3 surfaceToViewDirection = normalize(v_surfaceToView);

in the fragment shader? Can you explain why normalizing only in the frag shader does not have the same result?

Maybe an indirect question would help in general: how does the interpolation of vertices' varying vectors across the fragments work? Maybe that has something to do with it?

My rotation goes backwards?

I have some code like this:

    const angle = {theta: 0}

    const tween = new TWEEN.Tween(angle)
        .to({theta: 2*Math.PI}, 2000)
        .easing(TWEEN.Easing.Elastic.InOut)
        .start()

    requestAnimationFrame(function loop(time) {
        tween.update(time)

        gl.clearColor(0, 0, 0, 1)
        gl.clear(gl.COLOR_BUFFER_BIT)

        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(quad.verts), gl.STATIC_DRAW)
        gl.drawArrays(gl.TRIANGLES, offset, count)

        gl.uniform2f(rotationUniformLocation, Math.sin(angle.theta), Math.cos(angle.theta))

        requestAnimationFrame(loop)
    })

I think it is like your example, because I am using sin() on the X-axis, and cos() on the Y-axis, however my square (the quad from my other issue) rotates counter clockwise but your example rotates clockwise.

Why does mine rotate the opposite direction when I animate to 2pi?

Translation to Brazilian Portuguese

Hi, I'm a student of computer science here in Brazil. I'm starting to translate this documentation to Brazilian Portuguese with a friend for a final work of a discipline. I've made a fork of this project recently, for start making the translation. So, how can we make the pull requests? Is better to make regularly or just in the end of the translation?

Best regards, João Neto.

Clarification for makePerspective docs

https://github.com/greggman/webgl-fundamentals/blob/master/docs/module-webgl-3d-math.html#L1768-L1770

Note that near and far are not z coordinates, but rather they are distances along the negative z-axis.

IMHO, this could be more specific; If near and far are not coordinates, what are they?

I'm trying to understand the relationship of the frustum to clip space. From experimental testing, I can see that near fragments are always clipped somewhere between Z -1 and -2, regardless of how I set near in makePerspective, and I have been unable to find the far clipping plane, as even incredibly distant objects (Z -1e12) are still drawn (can be seen better when scaled appropriately).

I'm really just trying to get an understanding for how much play room I have within my 3D space. It seems like pretty much every number less than -2 will render.

In more concrete terms, when I use:

makePerspective(Math.PI / 2, width / height, 10, 50);

What is the expected valid range for the Z axis, before it exits clips space?

Combining lights.

It would be sweet if the guide showed how to combine lights (f.e. using both directional and point lighting).

I tried enabling both lights (as in the directional and point tutorials), but it seems that the multiplication of the values makes them too small (makes sense since they are less than 1), so the objects just get darker (except for the specular which is added, that is still relatively shiny).

How would you recommend using both a directional and a point light, or any combination of lights?

Ambient lighting would also be nice. I am guessing that is easy: just add a color to gl_FragColor.rgb (I haven't read about it, just guessing).

npm run build error

Hi @greggman , I am working on translate a simplify-chinese version, and comes a problem when I run npm run build, there are some error messages and log info, hope you can help me fix it. These tutorials really excellent and I hope I can do something to help more people can read it.
Actually I found it not interrupt the job. I can use http-server scan my translated pages. So it that means I can translate and preview at local and just pull request my "zh-cn" folder ? Thanks!

NPM Errors
powershell_2017-07-14_00-37-16

Log Info

0 info it worked if it ends with ok
1 verbose cli [ 'D:\\soft\\Nodejs\\node.exe',
1 verbose cli   'D:\\soft\\Nodejs\\node_modules\\npm\\bin\\npm-cli.js',
1 verbose cli   'run',
1 verbose cli   'build' ]
2 info using [email protected]
3 info using [email protected]
4 verbose run-script [ 'prebuild', 'build', 'postbuild' ]
5 info lifecycle [email protected]~prebuild: [email protected]
6 silly lifecycle [email protected]~prebuild: no script for prebuild, continuing
7 info lifecycle [email protected]~build: [email protected]
8 verbose lifecycle [email protected]~build: unsafe-perm in lifecycle true
9 verbose lifecycle [email protected]~build: PATH: D:\soft\Nodejs\node_modules\npm\bin\node-gyp-bin;E:\webgl-fundamentals-master\webgl-fundamentals-master\node_modules\.bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;D:\soft\Git\cmd;C:\ProgramData\chocolatey\bin;D:\soft\Sublime Text 3;D:\soft\Nodejs\;C:\Users\PrincessGod\AppData\Local\Microsoft\WindowsApps;C:\Users\PrincessGod\AppData\Roaming\npm
10 verbose lifecycle [email protected]~build: CWD: E:\webgl-fundamentals-master\webgl-fundamentals-master
11 silly lifecycle [email protected]~build: Args: [ '/d /s /c', 'grunt' ]
12 silly lifecycle [email protected]~build: Returned: code: 3  signal: null
13 info lifecycle [email protected]~build: Failed to exec build script
14 verbose stack Error: [email protected] build: `grunt`
14 verbose stack Exit status 3
14 verbose stack     at EventEmitter.<anonymous> (D:\soft\Nodejs\node_modules\npm\lib\utils\lifecycle.js:283:16)
14 verbose stack     at emitTwo (events.js:125:13)
14 verbose stack     at EventEmitter.emit (events.js:213:7)
14 verbose stack     at ChildProcess.<anonymous> (D:\soft\Nodejs\node_modules\npm\lib\utils\spawn.js:40:14)
14 verbose stack     at emitTwo (events.js:125:13)
14 verbose stack     at ChildProcess.emit (events.js:213:7)
14 verbose stack     at maybeClose (internal/child_process.js:897:16)
14 verbose stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:208:5)
15 verbose pkgid [email protected]
16 verbose cwd E:\webgl-fundamentals-master\webgl-fundamentals-master
17 verbose Windows_NT 10.0.15063
18 verbose argv "D:\\soft\\Nodejs\\node.exe" "D:\\soft\\Nodejs\\node_modules\\npm\\bin\\npm-cli.js" "run" "build"
19 verbose node v8.1.3
20 verbose npm  v5.0.3
21 error code ELIFECYCLE
22 error errno 3
23 error [email protected] build: `grunt`
23 error Exit status 3
24 error Failed at the [email protected] build script.
24 error This is probably not a problem with npm. There is likely additional logging output above.
25 verbose exit [ 3, true ]

halfVector in diagram seems to be off

In the diagram the "halfVector" doesn't visually seem right. If I complete the rectangle and get the long vector (surface2view + surface2light vectors), then half of that doesn't not align with the rendered "halfVector".

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.