Open7
PlayCanvasでカスタムシェーダーをエンティティに追加する
実行URL
プロジェクトURL
custom-shader.js
// custom-shader.js
const CustomShader = pc.createScript('customshader');
CustomShader.attributes.add('vs', {
type: 'asset',
assetType: 'shader',
title: 'Vertex Shader'
});
CustomShader.attributes.add('fs', {
type: 'asset',
assetType: 'shader',
title: 'Fragment Shader'
});
CustomShader.prototype.initialize = function() {
// アトリビュートからバーテックスシェーダを取得
const vertexShader = this.vs.resource;
// アトリビュートからフラグメントシェーダーを取得
const fragmentShader = this.fs.resource;
const shaderDefinition = {
attributes: {
aVertexPosition: pc.SEMANTIC_POSITION, // aVertexPosition バーテックスシェーダーで使う値
},
vshader: vertexShader,
fshader: fragmentShader
};
// フラグメントシェーダーとバーテックスシェーダーを渡す
this.shader = new pc.Shader(this.app.graphicsDevice, shaderDefinition);
// あとは作ってマテリアルをシェーダーをマテリアルに設定するだけ
this.material = new pc.Material();
this.material.setShader(this.shader);
const render = this.entity.render;
render.meshInstances[0].material = this.material;
this.timer = 0; // 追加
this.material.setParameter("uTime", this.timer);
};
CustomShader.prototype.update = function(dt) {
this.timer += dt;
if(this.timer > 1) this.timer = 0;
this.material.setParameter("uTime", this.timer);
};
フラグメントシェーダー
fragment shader (custom-shader.frag)
precision highp float;
uniform float uTime;
void main(void)
{
gl_FragColor = vec4(0, 0, uTime, 1.0);
}
vertex shader(custom-shader.vert)
attribute vec3 aVertexPosition;
uniform mat4 matrix_model;
uniform mat4 matrix_viewProjection;
void main(void)
{
gl_Position = matrix_viewProjection * matrix_model * vec4(aVertexPosition, 1.0);
}
Uniformの一覧(シェーダーで利用できる値)の確認方法について
// this.app.graphicsDevice.scope.variables
pc.app.graphicsDevice.scope.variables
実行結果
uniform | value |
---|---|
textureBias | 0 |
source | null |
areaLightsLutTex1 | Texture |
areaLightsLutTex2 | Texture |
shadowAtlasTexture | Texture |
shadowAtlasParams | vec2 |
cookieAtlasTexture | Texture |
polygonOffset | null |
pixelOffset | null |
weight[0] | null |
light_radius | null |
matrix_projection | mat4 |
matrix_projectionSkybox | mat4 |
matrix_view | mat4 |
matrix_view3 | mat4 |
matrix_viewInverse | mat4 |
matrix_viewProjection | mat4 |
projectionFlipY | float |
view_position | vec3 |
camera_near | float |
camera_far | float |
camera_params | vec4 |
tbnBasis | 1 |
fog_color | null |
fog_start | null |
fog_end | null |
fog_density | null |
matrix_model | mat4 |
matrix_normal | mat3 |
matrix_pose[0] | null |
texture_poseMap | null |
texture_poseMapSize | null |
morph_weights_a | null |
morph_weights_b | null |
morphPositionTex | null |
morphNormalTex | null |
morph_tex_params | null |
alpha_ref | 0 |
texture_opacityMap | null |
light_globalAmbient | vec3 |
exposure | 1 |
skyboxIntensity | null |
uScreenSize | vec4 |
twoSidedLightingNegScaleFactor | null |
lightsTexture8 | Texture |
lightsTextureFloat | Texture |
lightsTextureInvSize | vec4 |
clusterWorldTexture | Texture |
clusterPixelsPerCell | float |
clusterTextureSize | vec3 |
clusterBoundsMin | vec3 |
clusterBoundsDelta | vec3 |
clusterCellsCountByBoundsSize | vec3 |
clusterCellsDot | vec3 |
clusterCellsMax | vec3 |
clusterCompressionLimit0 | vec2 |
POSITION | null |
TEXCOORD0 | null |
light0_color | vec3 |
light0_direction | vec3 |
light0_shadowMap | Texture |
light0_shadowMatrix | mat4 |
light0_shadowParams | vec3 |
light0_shadowIntensity | float |
light0_radius | null |
light0_position | null |
light0_halfWidth | null |
light0_halfHeight | null |
light0_innerConeAngle | null |
light0_outerConeAngle | null |
light0_cookie | null |
light0_cookieIntensity | null |
light0_cookieMatrix | null |
light0_cookieOffset | null |
light0_shadowMatrixPalette[0] | mat8 |
light0_shadowCascadeDistances[0] | vec4 |
light0_shadowCascadeCount | int |
materia_diffuse | vec3 |
material_specular | vec3 |
material_shininess | float |
material_emissive | vec3 |
material_opacity | float |
material_occludeSpecularIntensity | float |
material_reflectivity | float |
Shader Chunk
pc.script.attribute('materials', 'asset', [], {
type: 'material'
});
pc.script.attribute('shader', 'asset', [], {
type: 'shader',
max: 1
});
var Plasma = pc.createScript('plasma');
Plasma.attributes.add('materials', {type: 'asset', array: true});
Plasma.attributes.add('shader', {type: 'asset'});
Plasma.prototype.initialize = function () {
// get the shader asset
var fs = this.shader.resource;
// update all the materials with the chunk
for (var i = 0; i < this.materials.length; i++) {
var material = this.materials[i].resource;
material.chunks.APIVersion = pc.CHUNKAPI_1_55;
material.chunks.emissivePS = fs;
// Force the shader generator to generate UV processing code
material.diffuseMap = new pc.Texture(this.app.graphicsDevice, {
width: 1,
height: 1,
format: pc.PIXELFORMAT_R8_G8_B8
});
material.setParameter('iGlobalTime', 0);
material.update();
}
this.time = 0;
};
Plasma.prototype.update = function (dt) {
// update the time uniform in the new shader chunk
this.time += dt;
for (var i = 0; i < this.materials.length; i++) {
var material = this.materials[i].resource;
material.setParameter('iGlobalTime', this.time);
}
};
fragment shader
uniform float iGlobalTime;
void getEmission() {
vec2 p = -1.0 + 2.0 * vUv0;
// main code, *original shader by: 'Plasma' by Viktor Korsun (2011)
float x = p.x;
float y = p.y;
float mov0 = x+y+cos(sin(iGlobalTime)*2.0)*100.+sin(x/100.)*1000.;
float mov1 = y / 0.9 + iGlobalTime;
float mov2 = x / 0.2;
float c1 = abs(sin(mov1+iGlobalTime)/2.+mov2/2.-mov1-mov2+iGlobalTime);
float c2 = abs(sin(c1+sin(mov0/1000.+iGlobalTime)+sin(y/40.+iGlobalTime)+sin((x+y)/100.)*3.));
float c3 = abs(sin(c2+cos(mov1+mov2+c2)+cos(mov2)+sin(x/1000.)));
dEmission = vec3(c1, c2, c3);
}
カスタムポストエフェクトを作成する
class PostEffectRenderer extends pc.PostEffect {
constructor(props: {
graphicsDevice: pc.GraphicsDevice;
fshader: string;
vshader: string;
}) {
const { graphicsDevice, fshader, vshader } = props;
super(graphicsDevice);
var attributes = {
aVertexPosition: pc.SEMANTIC_POSITION,
};
this.customShader = new pc.Shader(graphicsDevice, {
attributes: attributes,
vshader: vshader,
fshader: fshader,
});
}
render(inputTarget, outputTarget, rect) {
var device = this.device;
var scope = device.scope;
scope.resolve("uColorBuffer").setValue(inputTarget.colorBuffer);
pc.drawFullscreenQuad(
device,
outputTarget,
this.vertexBuffer,
this.customShader,
rect
);
}
}
export class CustomPostEffect extends pc.ScriptType {
initialize() {
const vshader = this.vert;
const fshader = this.frag;
this.time = 0;
this.effect = new PostEffectRenderer({
graphicsDevice: this.app.graphicsDevice,
vshader,
fshader,
});
const queue = this.entity.camera.postEffects;
queue.addEffect(this.effect);
this.app.mouse.on(pc.EVENT_MOUSEMOVE, (e) => {
this.app.graphicsDevice.scope.resolve("iMouse").setValue([e.x, e.y]);
});
this.on("attr:frag", () => {
queue.removeEffect(this.effect);
console.log(this.vert, this.frag);
this.effect = new PostEffectRenderer({
graphicsDevice: this.app.graphicsDevice,
vshader: this.vert,
fshader: this.frag,
});
queue.addEffect(this.effect);
});
this.on("state", function (enabled) {
if (enabled) {
queue.addEffect(this.effect);
} else {
queue.removeEffect(this.effect);
}
});
this.on("destroy", function () {
queue.removeEffect(this.effect);
});
}
update(dt) {
this.time += dt;
this.app.graphicsDevice.scope.resolve("iTime").setValue(this.time);
this.app.graphicsDevice.scope.resolve("iTimeDelta").setValue(dt);
}
}
CustomPostEffect.attributes.add("frag", { type: "string" });
CustomPostEffect.attributes.add("vert", { type: "string" });
v1.58.0〜
class PostEffectRenderer extends pc.PostEffect {
constructor(props: {
graphicsDevice: pc.GraphicsDevice;
fshader: string;
vshader: string;
}) {
const { graphicsDevice, fshader, vshader } = props;
super(graphicsDevice);
var attributes = {
aVertexPosition: pc.SEMANTIC_POSITION,
};
this.customShader = new pc.Shader(graphicsDevice, {
attributes: attributes,
vshader: vshader,
fshader: fshader,
});
}
// @ts-ignore
render(inputTarget, outputTarget, rect) {
pc.drawFullscreenQuad(
this.device,
outputTarget,
this.vertexBuffer,
this.customShader,
rect
);
}
}
export class CustomPostEffect extends pc.ScriptType {
initialize() {
const vshader = this.vert;
const fshader = this.frag;
this.time = 0;
this.effect = new PostEffectRenderer({
graphicsDevice: this.app.graphicsDevice,
vshader,
fshader,
});
const queue = this.entity.camera.postEffects;
queue.addEffect(this.effect);
this.app.mouse.on(pc.EVENT_MOUSEMOVE, (e) => {
this.app.graphicsDevice.scope.resolve("iMouse").setValue([e.x, e.y]);
});
this.on("attr:frag", () => {
queue.removeEffect(this.effect);
console.log(this.vert, this.frag);
this.effect = new PostEffectRenderer({
graphicsDevice: this.app.graphicsDevice,
vshader: this.vert,
fshader: this.frag,
});
queue.addEffect(this.effect);
});
this.on("state", function (enabled) {
if (enabled) {
queue.addEffect(this.effect);
} else {
queue.removeEffect(this.effect);
}
});
this.on("destroy", function () {
queue.removeEffect(this.effect);
});
}
update(dt) {
this.time += dt;
this.app.graphicsDevice.scope.resolve("iTime").setValue(this.time);
this.app.graphicsDevice.scope.resolve("iTimeDelta").setValue(dt);
}
}
pc.registerScript(CustomPostEffect)
CustomPostEffect.attributes.add("frag", { type: "string" });
CustomPostEffect.attributes.add("vert", { type: "string" });
シェーダーチャンクを設定できるスクリプト
export class ShaderChunk extends pc.ScriptType {
initialize() {
this.time = 0;
if (!this.fs || !this.chunk) return;
const material = this.entity.render.material;
material.chunks[this.chunk] = this.fs;
if (this.isGenerateDiffuseMap) {
this.app.assets.loadFromUrl( "/images/bg-rect-512.png", "texture", (err, asset) => {
const texture = asset?.resource;
material.diffuseMap = texture
material.update();
} );
//material.diffuseMap = new pc.Texture(this.app.graphicsDevice, {
// width: 1,
// height: 1,
// format: pc.PIXELFORMAT_R8_G8_B8,
// });
}
material.update();
}
update(dt) {
this.time += dt;
const material = this.entity.render?.material;
material.setParameter("iTime", this.time);
material.setParameter("iTimeDelta", dt);
}
}
ShaderChunk.attributes.add("fs", { type: "string" });
ShaderChunk.attributes.add("isGenerateDiffuseMap", {
type: "boolean",
default: true,
});
ShaderChunk.attributes.add("chunk", {
type: "string",
enum: [
{ emissivePS: "emissivePS" },
{ diffusePS: "diffusePS" },
{ normalMapPS: "normalMapPS" },
],
default: "emissivePS",
});
pc.registerScript(ShaderChunk);