javascript
JavaScript OpenGL接口再设计
Web OpenGL封裝
- 簡介
- 上下文GLContext
- 基類GLObject
- 著色器GLShader
- 著色器程序GLProgram
- 著色器程序屬性GLAttribute、GLUniform
- 數據緩存GLBuffer
- GLTexture和GLFrameBuffer
- html標簽
- Canvas
- vertex shader
- fragment shader
- iChannels
- 主程序ShaderToyGame
- 主函數
簡介
將OpenGL提供的庫函數接口進行一層封裝。
上下文GLContext
OpenGL自身是一個巨大的狀態機(State Machine):一系列的變量描述OpenGL此刻應當如何運行。OpenGL的狀態通常被稱為OpenGL上下文(Context)。我們通常使用如下途徑去更改OpenGL狀態:設置選項,操作緩沖。最后,我們使用當前OpenGL上下文來渲染。
<!--GLContext單例GL上下文對象--> <script type="text/javascript">class GLContext {static width = 0static height = 0constructor() {this.gl = null}static getWebGL() {if (!this.gl) {// 獲取html中的glCanvaslet canvas = document.getElementById('glCanvas') //.transferControlToOffscreen()GLContext.width = canvas.widthGLContext.height = canvas.height// 獲取glCanvas中的gl上下文this.gl = canvas.getContext('webgl2')}return this.gl}} </script>基類GLObject
由于大部分GL相關的封裝類,都有著共同的方法,因此寫一個基類。
<!--GLObjectGL對象的基類--> <script type="text/javascript">class GLObject {constructor() {this.gl = GLContext.getWebGL()}bind() {}unbind() {}} </script>著色器GLShader
OpenGL在創建一個“筆刷”的時候需要先創建vertex shader和fragment shader。
<!--用于創建:頂點著色器 or 片元著色器GLShaderGL著色器--> <script type="text/javascript">class GLShader extends GLObject {/*** @param sourceCode 元素id、shaderCode字符串、元素對象* @param type shader的類型*/constructor(sourceCode, type) {super()if (sourceCode == undefined || type.constructor != Number) {// sourceCode為空 或者 type著色器類型不是數字throw "sourceCode is undefined or type not number. \n";} else if (sourceCode.constructor == String) {// sourceCode本身是一個字符串, 要么是shader源碼, 要么是標簽的idif (sourceCode[0] == '#') {// sourceCode是一個idsourceCode = $(sourceCode)[0].innerHTML}} else if (sourceCode.constructor == HTMLScriptElement) {// sourceCode是一個元素對象sourceCode = sourceCode.innerHTML} else {// 其他情況直接錯誤throw "Create Shader Error. \n";}// 創建對應type的著色器this.id = GLShader.createShader(this.gl, sourceCode, type)}/*** @param gl gl上下文* @param sourceCode shaderCode字符串* @param type shader的類型*/static createShader(gl, sourceCode, type) {// 創建對應type的shader句柄let shader = gl.createShader(type);// 綁定sourceCode和typegl.shaderSource(shader, sourceCode);// 編譯shadergl.compileShader(shader);// 檢查錯誤if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {// 獲取shader編譯信息let info = gl.getShaderInfoLog(shader);// 釋放shadergl.deleteShader(shader)// 拋出錯誤throw "Could not compile WebGL program. \n" + (type == gl.VERTEX_SHADER ? "vertex" : "fragment") + " shader error. \n" + info;}// 創建完成return shader;}delete() {// 釋放shaderthis.gl.deleteShader(this.id)}} </script>著色器程序GLProgram
真正的“筆刷(Program)”,需要傳入vertex shader和fragment shader,才能成為一個program。
<!--用于創建:筆刷(著色器程序)GLProgram著色器程序--> <script type="text/javascript">class GLProgram extends GLObject {/*** @param vsCode vs元素id、vsCode字符串、元素對象(頂點著色器)* @param fsCode fs元素id、fsCode字符串、元素對象(片元著色器)*/constructor(vsCode, fsCode) {super()if (vsCode == undefined || fsCode == undefined) {// vsCode為空 或者 fsCode為空throw "vsCode is undefined or fsCode is undefined. \n";}// gl句柄let gl = this.gl// 創建vs和fslet vertexShader = new GLShader(vsCode, gl.VERTEX_SHADER)let fragmentShader = new GLShader(fsCode, gl.FRAGMENT_SHADER)// 創建shader programlet program = gl.createProgram()// 將vs、fs與program綁定gl.attachShader(program, vertexShader.id)gl.attachShader(program, fragmentShader.id)// 鏈接shader programgl.linkProgram(program)// 檢查錯誤if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {// 獲取program鏈接信息let info = gl.getProgramInfoLog(program)// 釋放programthis.delete()// 拋出錯誤throw 'WebGL program compile error. \n' + info}// 保存vs、fs、program的句柄this.vertexShader = vertexShaderthis.fragmentShader = fragmentShaderthis.id = program}/*** 使用program腳本*/use() {this.gl.useProgram(this.id)}/*** 刪除program腳本,釋放內存*/delete() {this.vertexShader.delete()this.fragmentShader.delete()this.gl.deleteProgram(this.id)}} </script>著色器程序屬性GLAttribute、GLUniform
在use program后,program還可能需要顏料(attribute/uniform),為此包裝對應的attribute和uniform。
<!--GLAtributeGLUniform--> <script type="text/javascript">class GLAttribute extends GLObject {/*** @param program 著色器對象、著色器id* @param name 屬性名*/constructor(program, name) {super()if (program == undefined || name == undefined) {// program為空 或者 name為空throw "program is undefined or name is undefined. \n";} else if (program.constructor != GLProgram) {// program不是GLProgram類型的throw "typeof(" + program + ") != GLProgram. \n";}// program idprogram = program.id// gl句柄let gl = this.gl// 獲取program中name的idlet id = gl.getAttribLocation(program, name)if (id < 0) {// 在gl中, 獲取到的id不可能小于0 (小于0說明name錯誤)throw "attribute " + name + " id < 0. \n";}// 保存idthis.id = id}/*** 綁定Attribute*/bind() {this.gl.enableVertexAttribArray(this.id)}/*** 解綁定Attribute*/unbind() {this.gl.disableVertexAttribArray(this.id)}/*** 設置Attribute屬性*/vertexAttribPointer(size, type, normalized, stride, offset) {this.gl.vertexAttribPointer(this.id, size, type, normalized, stride, offset)}}class GLUniform extends GLObject {/*** @param program 著色器對象、著色器id* @param name 屬性名*/constructor(program, name) {super()if (program == undefined || name == undefined) {// program為空 或者 name為空throw "program is undefined or name is undefined \n";} else if (program.constructor != GLProgram) {// program不是GLProgram類型的throw "typeof(" + program + ") != GLProgram. \n";}// program idprogram = program.id// gl句柄let gl = this.gl// 獲取program中name的idlet id = gl.getUniformLocation(program, name)if (id < 0) {// 在gl中, 獲取到的id不可能小于0 (小于0說明name錯誤)throw "uniform " + name + " id < 0 \n";}// 保存idthis.id = id}uniform1f(v0) {this.gl.uniform1f(this.id, v0)}uniform1fv(value) {this.gl.uniform1fv(this.id, value)}uniform1i(v0) {this.gl.uniform1i(this.id, v0)}uniform1iv(value) {this.gl.uniform1iv(this.id, v0)}uniform2f(v0, v1) {this.gl.uniform2f(this.id, v0, v1)}uniform2fv(value) {this.gl.uniform2fv(this.id, value)}uniform2i(v0, v1) {this.gl.uniform2i(this.id, v0, v1)}uniform2iv(value) {this.gl.uniform2iv(this.id, value)}uniform3f(v0, v1, v2) {this.gl.uniform3f(this.id, v0, v1, v2)}uniform3fv(value) {this.gl.uniform3fv(this.id, value)}uniform3i(v0, v1, v2) {this.gl.uniform3i(this.id, v0, v1, v2)}uniform3iv(value) {this.gl.uniform3iv(this.id, value)}uniform4f(v0, v1, v2, v3) {this.gl.uniform4f(this.id, v0, v1, v2, v3)}uniform4fv(value) {this.gl.uniform4fv(this.id, value)}uniform4i(v0, v1, v2, v3) {this.gl.uniform4i(this.id, v0, v1, v2, v3)}uniform4iv(value) {this.gl.uniform4iv(this.id, value)}uniformMatrix2fv(location, transpose, value) {this.gl.uniformMatrix2fv(this.id, transpose, value)}uniformMatrix3fv(location, transpose, value) {this.gl.uniformMatrix2fv(this.id, transpose, value)}uniformMatrix4fv(location, transpose, value) {this.gl.uniformMatrix2fv(this.id, transpose, value)}} </script>數據緩存GLBuffer
OpenGL在繪制時需要知道畫在**哪里(vertex/uv)**也就是buffer。
<!--GLBuffer--> <script type="text/javascript">class GLBuffer extends GLObject {/*** @param buffer 數據信息*/constructor(buffer) {super()if (buffer == undefined) {// buffer為空throw "buffer is undefined \n";}// gl句柄let gl = this.gl// vbolet vbo = gl.createBuffer()// 綁定vbogl.bindBuffer(gl.ARRAY_BUFFER, vbo)// vbo數據設置gl.bufferData(gl.ARRAY_BUFFER, buffer, gl.STATIC_DRAW)// 保存vbo idthis.id = vbo}/*** 綁定vbo*/bind() {this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.id)}/*** 釋放vbo*/delete() {this.gl.deleteBuffer(this.id)}} </script>GLTexture和GLFrameBuffer
已經準備好筆刷、顏料、畫什么東西,最后還差畫在哪里(fbo)。當然我們還可以直接拿一張圖貼到fbo上,也就是直接拿一張**貼紙(texture)**畫在fbo上。
<!--GLTextureGLFramebuffer--> <script type="text/javascript">class GLTexture extends GLObject {/*** @param imageUrl 圖片*/constructor(imageUrl) {super()if (imageUrl == undefined) {// 路徑為空throw "imageUrl is undefined. \n";} else if (imageUrl.constructor == String) {// 路徑不為空if (imageUrl[0] == '#') {// 獲取srcimageUrl = $($(imageUrl)[0]).attr("src")}}// gl句柄let gl = this.gl// this句柄let that = this// 創建imagelet image = new Image()// 服務器不支持跨域訪問的話就會被攔截image.crossOrigin = "Anonymous"// image加載成功后的回調 (ps: 這里可能會出現加載的時序問題)image.onload = function() {// 創建texture idlet textureId = gl.createTexture()// 綁定texturegl.bindTexture(gl.TEXTURE_2D, textureId)// 設置texture屬性gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)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.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true)// 設置texture格式gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image)if (textureId <= 0) {// 創建成功的texture id必然大于0, 小于說明失敗, 因此釋放texturegl.deleteTexture(textureId)// 拋出錯誤throw "texture create error. \n";}// 保存idthat.id = textureId// 解綁定 (ps: 最好做, 為了保持一致, 這個原因是因為后面可能會有繪制也會用到gl.bindTexture(gl.TEXTURE_2D, textureId))gl.bindTexture(gl.TEXTURE_2D, null)}// image加載失敗后的回調image.onerror = function() {alert("get image error")}// 設置image的src, 設置后將加載image.src = imageUrl}/*** 綁定texture*/bind() {this.gl.bindTexture(this.gl.TEXTURE_2D, this.id)}/*** 釋放texture*/delete() {this.gl.deleteTexture(this.id)}}class GLFramebuffer extends GLObject {/*** 一般來說, FBO是framebuffer和texture綁定* @param texture 紋理*/constructor(texture) {// 創建fbolet framebuffer = gl.createFramebuffer()// 綁定fbogl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer)// 保存texture對象if (texture == undefined) {this.texture = undefined} else if (texture.constructor == GLTexture) {this.texture = texture} else if (texture.constructor == String) {this.texture = new GLTexture(texture)}// 將fbo和texture綁定if (texture != undefined) {gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture.id, 0)}// 保存fbo idthis.id = framebuffer}/*** 綁定fbo*/bind() {this.gl.bindFramebuffer(this.FRAMEBUFFER, this.id)}/** * 釋放fbo*/delete() {this.texture.delete()this.deleteFramebuffer(this.id)}} </script>html標簽
Canvas
畫布,WebGL需要先創建一個Canvas,通過Canvas拿到WebGLContext。
<div style="display: flex;"><canvas id="glCanvas" width="680" height="640">你的瀏覽器似乎不支持或者禁用了HTML5 <code><canvas></code> 元素.</canvas> </div>vertex shader
頂點著色器的代碼。
<!-- shader腳本 --> <script id="shader-vs" type="x-shader/x-vertex">attribute vec4 iPosition;attribute vec2 iTexcoord;uniform float iTime;varying highp vec2 vTexcoord;void main(void) {gl_Position = iPosition;vTexcoord = iTexcoord;} </script>fragment shader
片段著色器的代碼。
<script id="shader-fs" type="x-shader/x-fragment">uniform sampler2D iChannel0;uniform highp float iTime;varying highp vec2 vTexcoord;void main(void) {gl_FragColor = texture2D(iChannel0, vTexcoord);} </script>iChannels
紋理圖片。
<img id="iChannel0" src="images/src0.jpg" class="hidden"/> <img id="iChannel1" src="images/src1.jpg" class="hidden"/> <!-- ..................<img id="iChannel7" src="images/src7.jpg" class="hidden"/> -->主程序ShaderToyGame
這只是一個調用以上封裝好GL接口的實例。
<script type="text/javascript">class ShaderToyGame {constructor() {let gl = GLContext.getWebGL()// 確認WebGL支持性if (!gl) {alert("無法初始化WebGL,你的瀏覽器、操作系統或硬件等可能不支持WebGL。")return}// 使用完全不透明的黑色清除所有圖像gl.clearColor(0.0, 1.0, 0.0, 1.0)// 用上面指定的顏色清除緩沖區gl.clear(gl.COLOR_BUFFER_BIT)// 設置視口gl.viewport(0, 0, GLContext.width, GLContext.height)}run(isRun) {if (this.isRun == isRun) {// 狀態一樣, 直接返回return;}// 是否運行, false會停止運行this.isRun = isRun;if (isRun) {// 開啟requestrequestAnimationFrame(renderGL)}}renderGL(now) {// gl上下文let gl = GLContext.getWebGL()// 使用programthis.program.use()// 綁定和輸入頂點數據this.verteBuffer.bind()this.vertexPosition.bind()this.vertexPosition.vertexAttribPointer(3, gl.FLOAT, false, 0, 0)// 綁定和輸入紋理數據this.texcoordBuffer.bind()this.texcoordPosition.bind()this.texcoordPosition.vertexAttribPointer(2, gl.FLOAT, false, 0, 0)// 輸入時間數據this.timeUniform.uniform1f(now * 0.001)// 輸入紋理數據for(let i = 0; i < this.maxSize; i++) {if(this.textures[i]) {gl.activeTexture(gl.TEXTURE0+i)this.textures[i].bind()this.iChannels[i].uniform1i(i)}}// 繪制gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)// 遞歸調用if(this.isRun)requestAnimationFrame(renderGL)}render() {let gl = GLContext.getWebGL()// 頂點坐標let vertices = [1.0, 1.0, 0.0, -1.0, 1.0, 0.0,1.0, -1.0, 0.0, -1.0, -1.0, 0.0]// 紋理坐標let texcoord = [1.0, 1.0,0.0, 1.0,1.0, 0.0,0.0, 0.0]// 頂點bufferthis.verteBuffer = new GLBuffer(new Float32Array(vertices))// 紋理bufferthis.texcoordBuffer = new GLBuffer(new Float32Array(texcoord))// 著色器programlet program = new GLProgram("#shader-vs", "#shader-fs")this.program = program// 頂點屬性let vertexPosition = new GLAttribute(program, "iPosition")// 紋理屬性let texcoordPosition = new GLAttribute(program, "iTexcoord")// 時間屬性let timeUniform = new GLUniform(program, "iTime")// 紋理數組let iChannels = []this.textures = []// texture最大數量this.maxSize = 8// 創建iChannels屬性數組和texture數組for(let i = 0; i < this.maxSize; i++) {let iChannel = new GLUniform(program, 'iChannel'+i)let texture = undefinedif(iChannel.id) { texture = new GLTexture('#iChannel'+i) }iChannels.push(iChannel)this.textures.push(texture)}// 開始渲染run(true);}destory() {this.isRun = falsethis.program.delete()this.verteBuffer.delete()this.texcoordBuffer.delete()for(let i = 0; i < this.maxSize; i++) {if(this.textures[i]) { this.textures[i].delete() }}}} </script>主函數
<script type="text/javascript">// 主函數function main() {let stg = new ShaderToyGame()stg.render()}main() </script>總結
以上是生活随笔為你收集整理的JavaScript OpenGL接口再设计的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: App 运营的指标具体都有哪些?
- 下一篇: 男人的不安全感