摘要
CocosCreator的文本组件,输入文字后是怎么将其渲染到网页上面的?阔阔带你从头开始分析!
正文
使用工具
- 谷歌浏览器
- CocosCreator 版本 2.4.7
先说说怎么在网页上显示文字
1.最基本的 html 内文本显示:
浏览器中:
2.然后出现了 canvas 绘制,在 canvas 里显示文字:
你会发现渲染有点模糊,解决办法可以采用增大渲染分辨率,保持 DOM 大小,就清晰了。
3.再后来流行了 WebGL,要想在 WebGL 中显示文本,有一种办法就是先在 canvas 下渲染 2d 的文字,然后将其作为纹理传入 WebGL 进行渲染。CocosCreator 也是这么搞的,这样的方法适合动态生成文字。举个例子(可以 copy 到一个 HTML 里自己跑一下):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<canvas id="webgl" width="200" height="200"></canvas>
<script type="text/javascript">
const canvasDom = document.createElement("canvas")
canvasDom.width = 200
canvasDom.height = 200
const ctx = canvasDom.getContext("2d")
ctx.textBaseline = "top"
ctx.font="20px 微软雅黑"
ctx.fillText("显示文本-KUOKUO", 0, 0)
const webglDom = document.getElementById('webgl')
const gl = webglDom.getContext("webgl")
const fs = gl.createShader(gl.FRAGMENT_SHADER)
const fsText = `
precision mediump float;
uniform sampler2D u_texture;
varying vec2 v_texCoord;
void main() {
gl_FragColor = texture2D(u_texture, v_texCoord);
}
`
gl.shaderSource(fs, fsText)
gl.compileShader(fs)
const vs = gl.createShader(gl.VERTEX_SHADER)
const vsText = `
attribute vec2 a_position;
attribute vec2 a_texCoord;
uniform vec2 u_resolution;
varying vec2 v_texCoord;
void main() {
vec2 rate = a_position / u_resolution;
// 转换坐标空间到 -1 -> +1
vec2 clip = (rate * 2.0) - 1.0;
gl_Position = vec4(clip * vec2(1, -1), 0, 1);
v_texCoord = a_texCoord;
}
`
gl.shaderSource(vs, vsText)
gl.compileShader(vs)
const program = gl.createProgram()
gl.attachShader(program, fs)
gl.attachShader(program, vs)
gl.linkProgram(program)
const positionLocation = gl.getAttribLocation(program, "a_position")
const texcoordLocation = gl.getAttribLocation(program, "a_texCoord")
const positionBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0, 0,
200, 0,
0, 200,
0, 200,
200, 0,
200, 200,
]), gl.STATIC_DRAW)
const texcoordBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0,
]), gl.STATIC_DRAW)
const texture = gl.createTexture()
gl.bindTexture(gl.TEXTURE_2D, texture)
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)
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvasDom)
gl.viewport(0, 0, 200, 200)
gl.clearColor(0, 0, 0, 0)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.useProgram(program)
const resolutionLocation = gl.getUniformLocation(program, "u_resolution")
gl.uniform2f(resolutionLocation, 200, 200)
gl.enableVertexAttribArray(positionLocation)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(texcoordLocation)
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer)
gl.vertexAttribPointer(texcoordLocation, 2, gl.FLOAT, false, 0, 0)
gl.drawArrays(gl.TRIANGLES, 0, 6)
</script>
</body>
</html>
浏览器中的效果:
CocosCreator 是怎么做的
观察上面的代码,首先文字的大小应该是根据内容进行变化的,而不是像我这样定死一个 200 长度,对于文字大小的测量方法:
打开 CocosCreator 的 v2.4.7 的源码,在 text-utils.js 脚本中有着 safeMeasureText 这个方法,其中就是测量方法,然后还加了缓存,同样的 key 就不用再测了,见下图:
再转到源码中的 letter-font.js 脚本,看下 updateRenderData 这个方法,这个就是装载文字显示数据的方法。在这个方法里执行了 _updateProperties 和 _updateTexture 这两个方法,分别看一看:
先看第一个,对照下面的代码,Label._canvasPool.get() 其实就是 document.createElement("canvas") 的一层封装,获取到了一个 canvas 对象。然后,调用测量方法传入文本获得占据宽高,最后执行 this._texture.initWithElement(this._canvas) 创建了纹理。
再看看 _updateTexture 干了啥:
不难发现,就是对文字的样式、颜色等进行修改,最终调用了 fillText 绘制文字,然后调用了 this._texture.handleLoadedTexture(); ,再转到源码 CCTexture2D.js 中可以看到这个方法:
在上面这个方法了装入纹理参数,像素格式。
再深入点!!!
看到这里,大概的流程通了,但是 CocosCreator 怎么把文本显示出来的?那些 shader 中的 useProgram 还有 buffer 相关的其他操作呢?
在上面的 handleLoadedTexture 方法中有这样一句代码:
this._texture = new renderer.Texture2D(renderer.device, opts);
renderer.device 是什么?打开源码目录中 cocos2d/core/renderer/index.js 脚本,其中的 initWebGL 方法就是环境初始化。在 CCGame.js 的 _initRenderer 方法中判断了 canvas 环境还是 webgl 环境!
然后,在 initWebGL 这个方法中有这么一行代码:
this.device = new gfx.Device(canvas, opts);
挖到 device.js 脚本中看看,在 Device 类的构造函数中也终于找到了我们想要的东西:
然后你会发现 device.js 这个脚本其实就是对 gl 一系列方法的封装,在最后面的 draw 方法中有 shader 的 buffer 操作等一系列方法的封装以及最终绘制方法:
最终调用的绘制方法:
好了,gl 的渲染方法找到了,还差最后一步,它是怎么被调用到的呢?
官方文档有 渲染流 这么一块介绍,渲染流(RenderFlow)是 v2.0 新加的流程,它的作用是可以剔除无用的渲染分支,只进入预先创建好的渲染分支,这样可以有效减少非常多的动态判断。
在 CocosCreator 中的渲染会走到 RenderFlow,在 render-flow.js 中需要渲染的会走入 render 方法!
回过来,在 base-renderer.js 源码中,_draw 正是在设置好一系列 gl 参数后调用了 device 的 draw 方法之中。而 ForwardRenderer 继承自 BaseRenderer ,在 render 时就会触发 _draw 方法,将文字渲染出来。
一切豁然开朗!!!
更多文章与分享
个人网站:www.kuokuo666.com
2022!Day Day Up!
|