There is a simple way to play with 3D stuff ---- WebGL! We can have cool 3D in every modernjavascript
web browser, in your brother's new android phone, or your old linux computer, the WebGL magiccss
can work.html
Honestly, I have nothing know about 3D, need learning from basic. If you are same with me, youjava
can learn together with me. I am 27 years old, sometimes I think I am too old to learning new things,linux
but think about it, we are youngest now in our remain lifetime. If not learning now, when can I learning?android
I woked as a backend engineer in last two years, that's a exciting job, my team built some great things.web
But I don't want limit myself in a virtual field, I want to explore more, because if someone know more, heredis
can create more. In other side, the experience in backend gave me some good thought fuction, thecanvas
most important is actor model. Many things in computer wolrd or real world can be modeled as actorless
model and message passing, it can help me draw an architecture map about how things work.
First, let me guess how 3D APIs like WebGL doing under the hood. We had better learning from a simple
example at beginning: darw a triangle. The introduce says WebGL is woking on GPU. So, the data
about the triangle should be sent from the javascript runtime to GPU's memory. If we this javascript
runtime and WebGL are two actor:
"""
J: hi! Mr.GPU, can you draw a triangle for me?
G: Sure, how the triangle draw with? What pen?
J: I think pencil is good.
G: look, I am a powerful API, so you need give me a precise specification about the "pencil" you want.
J: ok...
"""
In fact, the "pen" in WebGL wolrd is called the "program", which describes how WebGL use the
bytes in memory. We use two shaders, you can think they as function defines, in one program, they
are vertex sharer(how to get vertexs postitions) and fragment shader(about the color and etc.).
There are a two simple example shaders, written in GLSL:
# vertex shader
attribute vec4 a_position; void main() { gl_Position = a_position; }
The meaning is: please set the field "gl_Position" with "position" which is a vec4 data.
# fragment shader
void main() { gl_FragColor = vec4(1.0, 0, 0.5, 1.0); };
The meaning is: please set the field "gl_FragColor" with this vec4 data.
Ok, now we can decribe a "pen" percisely. Just pass it to WebGL:
const createShader = (gl, sourceCode, type) => { // Compiles either a shader of type gl.VERTEX_SHADER or gl.FRAGMENT_SHADER let shader = gl.createShader( type ) gl.shaderSource( shader, sourceCode ) gl.compileShader( shader ) if ( !gl.getShaderParameter(shader, gl.COMPILE_STATUS) ) { let info = gl.getShaderInfoLog( shader ) throw 'Could not compile WebGL program. \n\n' + info } return shader } const loadProgram = (gl, vertexShaderSource, fragmentShaderSource) => { // Create shader from GLSL source let vetexShader = createShader(gl, vertexShaderSource, gl.VERTEX_SHADER) let fragmentShader = createShader(gl, fragmentShaderSource, gl.FRAGMENT_SHADER) let program = gl.createProgram() // Attach pre-existing shaders gl.attachShader(program, vertexShader) gl.attachShader(program, fragmentShader) gl.linkProgram(program) if ( !gl.getProgramParameter( program, gl.LINK_STATUS) ) { let info = gl.getProgramInfoLog(program) throw 'Could not compile WebGL program. \n\n' + info } // Tell it to use our program (pair of shaders) gl.useProgram(program) } const enableAttribute = (program, attribute) => { // look up where the vertex data needs to go. let positionAttributeLocation = gl.getAttribLocation(program, attribute) // Turn on the attribute gl.enableVertexAttribArray(positionAttributeLocation) return positionAttributeLocation }
"""
G: That's a good pen. Excuse me, what you want to draw with this pen?
J: A triangle. The positions of three vertexs are 0, 0 .....
G: Wait, wait, I could not remember theses positions. Please write down in a note then
tell me where is the note, and how to read the data on that.
J: ok...
"""
We need a "note" used to write "positions" on, which in WebGL is called "buffer", and the location to put the
note is called "target". We use "ARRAY_BUFFER" as target here.
const putPosition = (gl, target, positions) => { // Create a buffer and put three 2d clip space points in it let positionBuffer = gl.createBuffer() // Bind it to target gl.bindBuffer(target, positionBuffer) gl.bufferData(target, new Float32Array(positions), gl.STATIC_DRAW) } let positions = [ 0, 0, 0, 0.5, 0.7, 0, ] let target = gl.ARRAY_BUFFER
Let's tell WebGL how to read the data in buffer:
const readBuffer = (gl, location) => // Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER) let size = 2 // 2 components per iteration let type = gl.FLOAT // the data is 32bit floats let normalize = false // don't normalize the data let stride = 0 // 0 = move forward size * sizeof(type) each iteration to get the next position let offset = 0 // start at the beginning of the buffer gl.vertexAttribPointer( location, size, type, normalize, stride, offset) }
Finally, ask WebGL to draw our triangle:
const draw = (gl) => { // draw let primitiveType = gl.TRIANGLES let offset = 0 let count = 3 gl.drawArrays(primitiveType, offset, count) }
Turn around, we have defined some most important part about how to draw a triangle with
WebGL:
- Shaders and Program (the pen be used to draw triangle)
- Buffer (the note about triangle positions)
Let's combine all these code together(some code edited):
"use strict"; class WebGL { constructor() { let canvas = document.getElementById("c") let gl = canvas.getContext("webgl") gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) if (!gl) { console.log('your browser not support webgl') } else { return gl } } } let vertexShaderSource = ` attribute vec4 a_position; void main() { gl_Position = a_position; } ` let fragmentShaderSource = ` void main() { gl_FragColor = vec4(1, 0, 0.5, 1); // return redish-purple } ` let createShader = (gl, type, source) => { let shader = gl.createShader(type) gl.shaderSource(shader, source) gl.compileShader(shader) let success = gl.getShaderParameter(shader, gl.COMPILE_STATUS) if (success) { return shader } else { console.log(gl.getShaderInfoLog(shader)) gl.deleteShader(shader) } } let createProgram = (gl, vertexShader, fragmentShader) => { let program = gl.createProgram() gl.attachShader(program, vertexShader) gl.attachShader(program, fragmentShader) gl.linkProgram(program) let success = gl.getProgramParameter(program, gl.LINK_STATUS) if (success) { return program } else { console.log(gl.getProgramInfoLog(program)) gl.deleteProgram(program) } } let attributeLocation = (gl, program, attribute) => { let a_positionLocation = gl.getAttribLocation(program, attribute) gl.enableVertexAttribArray(a_positionLocation) return a_positionLocation } let writeBuffer = (gl, positions) => { let positionBuffer = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer) gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW) } let readBuffer = (gl, program) => { let positionAttributeLocation = attributeLocation(gl, program, "a_position") let size = 2 // 2 components per iteration let type = gl.FLOAT // the data is 32bit floats let normalize = false // don't normalize the data let stride = 0 // 0 = move forward size * sizeof(type) each iteration to get the next position let offset = 0 // start at the beginning of the buffer gl.vertexAttribPointer( positionAttributeLocation, size, type, normalize, stride, offset) } let drawPrimitive = (gl, program, primitiveType, positions) => { gl.useProgram(program) writeBuffer(gl, positions) readBuffer(gl, program) let offset = 0 let count = 3 gl.drawArrays(primitiveType, offset, count) } let gl = new WebGL() let vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource) let fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource) let program = createProgram(gl, vertexShader, fragmentShader) let positions = [ 0, 0, 0, 0.5, 0.7, 0, ] drawPrimitive(gl, program, gl.TRIANGLES, positions)
You can view the result here: [](https://jsbin.com/sotowiv/2/edit?html,css,js,output)
More power, more response. Less power, less reponse. For a easier life, I will only use function "drawPrimitive"
in later posts :).