JUMP INtO 3D WORLD

There is a simple way to play with 3D stuff ---- WebGL! We can have cool 3D in every modern

web browser, in your brother's new android phone, or your old linux computer, the WebGL magic

can work.

Honestly, I have nothing know about 3D, need learning from basic. If you are same with me, you

can learn together with me. I am 27 years old, sometimes I think I am too old to learning new things,

but think about it, we are youngest now in our remain lifetime. If not learning now, when can I learning?

I woked as a backend engineer in last two years, that's a exciting job, my team built some great things.

But I don't want limit myself in a virtual field, I want to explore more, because if someone know more, he

can create more. In other side, the experience in backend gave me some good thought fuction, the

most important is actor model. Many things in computer wolrd or real world can be modeled as actor

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 :).

猜你喜欢

转载自www.cnblogs.com/yc2019/p/10423137.html