文章转载自:https://blog.csdn.net/xufeng0991/article/details/72973664
为了减少游戏资源,游戏中免不了会使用一些shader,下面是一个将图片变灰shader,以此来看看如何在cocos creator中使用和管理shader。
在assets中新建一个文件夹resources,然后在resources中新建一个Shader文件夹。将所shader文件放在Shader文件夹中。
现在在Shader文件夹中新建两个文件gray.vert.js和gray.frag.js,内容如下:
// gray.vert.js
module.exports =
`
attribute vec4 a_position;
attribute vec2 a_texCoord;
attribute vec4 a_color;
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
void main()
{
gl_Position = CC_PMatrix * a_position;
v_fragmentColor = a_color;
v_texCoord = a_texCoord;
}
`
// gray.frag.js
module.exports =
`
#ifdef GL_ES
precision lowp float;
#endif
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
void main()
{
vec4 c = v_fragmentColor * texture2D(CC_Texture0, v_texCoord);
gl_FragColor.xyz = vec3(0.2126*c.r + 0.7152*c.g + 0.0722*c.b);
gl_FragColor.w = c.w;
}
`
为了方便使用,写一个ShaderUtils,使用时只需要传入修改shader的Sprite和shader的名字。
// ShaderUtils.js
var ShaderUtils = {
shaderPrograms: {},
setShader: function(sprite, shaderName) {
var glProgram = this.shaderPrograms[shaderName];
if (!glProgram) {
glProgram = new cc.GLProgram();
var vert = require(cc.js.formatStr("%s.vert", shaderName));
var frag = require(cc.js.formatStr("%s.frag", shaderName));
glProgram.initWithString(vert, frag);
if (!cc.sys.isNative) {
glProgram.initWithVertexShaderByteArray(vert, frag);
glProgram.addAttribute(cc.macro.ATTRIBUTE_NAME_POSITION, cc.macro.VERTEX_ATTRIB_POSITION);
glProgram.addAttribute(cc.macro.ATTRIBUTE_NAME_COLOR, cc.macro.VERTEX_ATTRIB_COLOR);
glProgram.addAttribute(cc.macro.ATTRIBUTE_NAME_TEX_COORD, cc.macro.VERTEX_ATTRIB_TEX_COORDS);
}
glProgram.link();
glProgram.updateUniforms();
this.shaderPrograms[shaderName] = glProgram;
}
sprite._sgNode.setShaderProgram(glProgram);
return glProgram;
},
};
module.exports = ShaderUtils;
为了避免同一个shader程序多次创建,这里做了一个缓冲,若是已有的就不再创建,直接使用。
方便对比,在HelloWorld的场景中再加一个Sprite,绑定到spGray,在spGray上使用上面写的shader。
// HelloWorld.js
var ShaderUtils = require("ShaderUtils");
cc.Class({
extends: cc.Component,
properties: {
spGray: cc.Sprite
},
onLoad: function () {
ShaderUtils.setShader(this.spGray, "gray");
},
update: function (dt) {
},
});
效果如图:
有人在论坛里问,怎么把shader用在脊骨上,这里补充下。
var ShaderUtils = require("ShaderUtils");
cc.Class({
extends: cc.Component,
properties: {
sp: sp.Skeleton
},
onLoad: function () {
ShaderUtils.setShader(this.sp, "gray");
},
});
运行效果:
2018,06.25更新
之前的方法是把shader放在js文件中,好处是可以同步加载;不好的地方就是,没法使用通用的glsl文件,书写时无法利于ide对glsl语言的语法提示,下面是扩展了一种异步的加载方式,可以使用标准的glsl的shader文件。
通用的glsl shader 文件:
attribute vec4 a_position;
attribute vec2 a_texCoord;
attribute vec4 a_color;
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
void main()
{
gl_Position = CC_PMatrix * a_position;
v_fragmentColor = a_color;
v_texCoord = a_texCoord;
}
#ifdef GL_ES
precision lowp float;
#endif
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
void main()
{
vec4 c = v_fragmentColor * texture2D(CC_Texture0, v_texCoord);
gl_FragColor.xyz = vec3(0.2126*c.r + 0.7152*c.g + 0.0722*c.b);
gl_FragColor.w = c.w;
}
扩展之后的ShaderUtils文件:
/**
* used to change the shader of sprite
* e.g. ShaderUtils.useShader(cc.Sprite, shader name);
*/
var ShaderUtils = {
shaderMap: {}, // shader string map
shaderPrograms: {}, //shader propgram map
getGlPropgram: function(vert, frag) {
var glProgram = new cc.GLProgram();
glProgram.initWithString(vert, frag);
if (!cc.sys.isNative) {
glProgram.addAttribute(cc.macro.ATTRIBUTE_NAME_POSITION, cc.macro.VERTEX_ATTRIB_POSITION);
glProgram.addAttribute(cc.macro.ATTRIBUTE_NAME_COLOR, cc.macro.VERTEX_ATTRIB_COLOR);
glProgram.addAttribute(cc.macro.ATTRIBUTE_NAME_TEX_COORD, cc.macro.VERTEX_ATTRIB_TEX_COORDS);
}
glProgram.link();
glProgram.updateUniforms();
return glProgram;
},
// sync
setShader: function(sprite, shaderName) {
var glProgram = this.shaderPrograms[shaderName];
if (!glProgram) {
var vert = require(cc.js.formatStr("%s.vert", shaderName));
var frag = require(cc.js.formatStr("%s.frag", shaderName));
glProgram = this.getGlPropgram(vert, frag);
this.shaderPrograms[shaderName] = glProgram;
}
sprite._sgNode.setShaderProgram(glProgram);
return glProgram;
},
// async
useShader: function(sprite, shaderName, cb) {
var glProgram = this.shaderPrograms[shaderName];
if (glProgram) {
sprite._sgNode.setShaderProgram(glProgram);
if (cb) {
cb(glProgram);
}
return;
}
// vart shader
var vert = cc.js.formatStr("shader/%s.vert", shaderName);
if (this.shaderMap[vert]) {
this.onLoadShader(vert, shaderName, cb);
} else {
cc.loader.loadRes(vert, function (err, shaderStr){
if (err) {
console.error('load %s fail, error: ', err);
return;
}
this.shaderMap[vert] = shaderStr;
this.onLoadShader(sprite, shaderName, cb);
}.bind(this));
}
// frag shader
var frag = cc.js.formatStr("shader/%s.frag", shaderName);
if (this.shaderMap[frag]) {
this.onLoadShader(frag, shaderName, cb);
} else {
cc.loader.loadRes(frag, function (err, shaderStr){
if (err) {
console.error('load %s fail, error: ', err);
return;
}
this.shaderMap[frag] = shaderStr;
this.onLoadShader(sprite, shaderName, cb);
}.bind(this));
}
},
onLoadShader: function(sprite, shaderName, cb) {
var vert = cc.js.formatStr("shader/%s.vert", shaderName);
var frag = cc.js.formatStr("shader/%s.frag", shaderName);
if (this.shaderMap[vert] && this.shaderMap[frag]) {
var glProgram = this.getGlPropgram(this.shaderMap[vert], this.shaderMap[frag]);
this.shaderPrograms[shaderName] = glProgram;
sprite._sgNode.setShaderProgram(glProgram);
if (cb) {
cb(glProgram);
}
}
}
};
module.exports = ShaderUtils;
使用跟之前的一直,不过如果要获得创建的 glProgram, 在 update 更新数据,需要使用穿入一个函数,gl将作为函数参数返回,例如:
onLoad: function() {
this.startTime = Date.now();
this.atkAreaInfo = {
color: {x: 1, y: 0, z: 0},
minR: 0.05,
maxR: 0.3,
dura: 1,
};
ShaderUtils.useShader(this.sp, "atk_circle", function(glPropgram){
this.aaShaderPro= glPropgram;
}.bind(this));
},
update: function(dt) {
this.aaShaderPro.use();
this.time = (Date.now() - this.startTime) / 1000;
if (cc.sys.isNative) {
var state = cc.GLProgramState.getOrCreateWithGLProgram(this.aaShaderPro);
state.setUniformFloat('saturation', this.atkAreaInfo.dura);
state.setUniformFloat('maxRadius', this.atkAreaInfo.maxR);
state.setUniformFloat('minRadius', this.atkAreaInfo.minR);
state.setUniformVec3('areaColor', this.atkAreaInfo.color);
glState.setUniformFloat("time", this.time);
} else {
var saturation = this.aaShaderPro.getUniformLocationForName('saturation');
var maxRadius = this.aaShaderPro.getUniformLocationForName('maxRadius');
var minRadius = this.aaShaderPro.getUniformLocationForName('minRadius');
var areaColor = this.aaShaderPro.getUniformLocationForName('areaColor');
var time = this.program.getUniformLocationForName("time");
this.aaShaderPro.setUniformLocationWith1f(saturation, this.atkAreaInfo.dura);
this.aaShaderPro.setUniformLocationWith1f(maxRadius, this.atkAreaInfo.maxR);
this.aaShaderPro.setUniformLocationWith1f(minRadius, this.atkAreaInfo.minR);
this.aaShaderPro.setUniformLocationWith3f(areaColor, this.atkAreaInfo.color.x, this.atkAreaInfo.color.y, this.atkAreaInfo.color.z);
this.program.setUniformLocationWith1f(time, this.time);
}
}