精灵渲染器

上次修改时间:2021-05-14 11:38:64

SpriteRenderer 组件用于在 3D/2D 场景中显示图片。

import { AssetType, Camera, Script, Sprite, SpriteRenderer, Texture2D, Vector3, WebGLEngine } from "oasis-engine";
import { OrbitControl } from "@oasis-engine/controls";

// Create engine object
const engine = new WebGLEngine("canvas");
engine.canvas.resizeByClientSize();

const scene = engine.sceneManager.activeScene;
const rootEntity = scene.createRootEntity();

// Create camera
const cameraEntity = rootEntity.createChild("camera_entity");
cameraEntity.transform.setPosition(0, 0, 50);
cameraEntity.addComponent(Camera);
cameraEntity.addComponent(OrbitControl);

// Create sprite renderer
engine.resourceManager
  .load<Texture2D>({
    url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*ApFPTZSqcMkAAAAAAAAAAAAAARQnAQ",
    type: AssetType.Texture2D
  })
  .then((texture) => {
    for (let i = 0; i < 10; ++i) {
      setTimeout(() => {
        const spriteEntity = rootEntity.createChild(`sprite_${i}`);
        spriteEntity.transform.position = new Vector3(0, 0, 0);
        const spriteRenderer = spriteEntity.addComponent(SpriteRenderer);
        const sprite = new Sprite(engine, texture);
        spriteRenderer.sprite = sprite;
        // spriteRenderer.flipX = true;
        // spriteRenderer.flipY = true;
        const rect = spriteRenderer.sprite.rect;
        const scaleX = 100.0 / rect.width;
        const scaleY = 100.0 / rect.height;
        spriteEntity.transform.setScale(scaleX, scaleY, 1);
        spriteEntity.addComponent(SpriteController);
      }, 2000 * i);
    }
  });

engine.run();

// Script for sprite
class SpriteController extends Script {
  static _curRotation: number = 0;

  private _radius: number = 1.5;
  private _curRadian: number;
  private _scale: number;
  private _scaleFlag: boolean;

  onAwake() {
    this._curRadian = 0;
    this._radius = 15;
    this._scale = 0.5;
    this._scaleFlag = true;
  }

  onUpdate() {
    // Update position.
    this._curRadian += 0.005;
    const { _radius, _curRadian, entity } = this;
    const { transform } = entity;
    const posX = Math.cos(_curRadian) * _radius;
    const posY = Math.sin(_curRadian) * _radius;
    transform.setPosition(posX, posY, 0);

    // Update scale.
    this._scale += this._scaleFlag ? 0.005 : -0.005;
    const { _scale } = this;
    transform.setScale(_scale, _scale, _scale);
    if (this._scale >= 0.6) {
      this._scaleFlag = false;
    } else if (this._scale <= 0.4) {
      this._scaleFlag = true;
    }

    // Update rotation.
    SpriteController._curRotation += 0.05;
    const { _curRotation } = SpriteController;
    transform.setRotation(0, 0, _curRotation);
  }
}

基本使用

1、下载图片纹理(Texture),下载方法请参考资源加载
2、通过 texture 创建 Sprite 对象
3、创建 SpriteRenderer 组件显示图片

import { AssetType, Camera, Script, Sprite, SpriteRenderer, Texture2D, Vector3, WebGLEngine } from "oasis-engine";

engine.resourceManager
  .load<Texture2D>({
    url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*d3N9RYpcKncAAAAAAAAAAAAAARQnAQ",
    type: AssetType.Texture2D
  })
  .then((texture) => {
    const spriteEntity = rootEntity.createChild(`sprite`);
    // 给实体添加 SpriteRenderer 组件
    const spriteRenderer = spriteEntity.addComponent(SpriteRenderer);
    // 通过 texture 创建 sprite 对象
    const sprite = new Sprite(engine, texture);
    // 设置 sprite
    spriteRenderer.sprite = sprite;
  });

图片翻转

除了基本的图片显示,SpriteRenderer 还支持图片的翻转,只需要通过设置属性 flipX/flipY 即可完成翻转,如下:

// 翻转图片
spriteRenderer.flipX = true;
spriteRenderer.flipY = true;
import { AssetType, Camera, Entity, Sprite, SpriteRenderer, Texture2D, WebGLEngine } from "oasis-engine";
import { OrbitControl } from "@oasis-engine/controls";

// Create engine object.
const engine = new WebGLEngine("canvas");
engine.canvas.resizeByClientSize();

// Create root entity.
const rootEntity = engine.sceneManager.activeScene.createRootEntity();

// Create camera.
const cameraEntity = rootEntity.createChild("Camera");
cameraEntity.transform.setPosition(0, 0, 50);
cameraEntity.addComponent(Camera);
cameraEntity.addComponent(OrbitControl);

engine.resourceManager
  .load<Texture2D>({
    url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*KjnzTpE8LdAAAAAAAAAAAAAAARQnAQ",
    type: AssetType.Texture2D
  })
  .then((texture) => {
    // Create origin sprite entity.
    const spriteEntity = new Entity(engine, "spriteFlip");
    const spriteRenderer = spriteEntity.addComponent(SpriteRenderer);
    spriteRenderer.sprite = new Sprite(engine, texture);

    // Display mormal.
    addFlipEntity(spriteEntity, -15, false, false);
    // Display flip x.
    addFlipEntity(spriteEntity.clone(), -5, true, false);
    // Display flip y.
    addFlipEntity(spriteEntity.clone(), 5, false, true);
    // Display flip x and y.
    addFlipEntity(spriteEntity.clone(), 15, true, true);
  });

engine.run();

/**
 * Add flip entity.
 */
function addFlipEntity(entity: Entity, posX: number, flipX: boolean, flipY: boolean): void {
  rootEntity.addChild(entity);
  entity.transform.setPosition(posX, 0, 0);
  const flipRenderer = entity.getComponent(SpriteRenderer);
  flipRenderer.flipX = flipX;
  flipRenderer.flipY = flipY;
}

设置颜色

可以通过设置 color 属性来调整颜色,从而实现一些淡入淡出的效果,如下:

spriteRenderer.color.setValue(1, 0, 0, 1);
import { AssetType, Camera, Color, Entity, Sprite, SpriteRenderer, Texture2D, Vector3, WebGLEngine } from "oasis-engine";
import { OrbitControl } from "@oasis-engine/controls";

// Create engine object
const engine = new WebGLEngine("canvas");
engine.canvas.resizeByClientSize();

const scene = engine.sceneManager.activeScene;
const rootEntity = scene.createRootEntity();

// Create camera
const cameraEntity = rootEntity.createChild("Camera");
cameraEntity.transform.position = new Vector3(0, 0, 50);
cameraEntity.addComponent(Camera);
cameraEntity.addComponent(OrbitControl);

engine.resourceManager
  .load<Texture2D>({
    url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*KjnzTpE8LdAAAAAAAAAAAAAAARQnAQ",
    type: AssetType.Texture2D
  })
  .then((texture) => {
    // Create origin sprite entity.
    const spriteEntity = new Entity(engine, "spriteColor");
    const spriteRenderer = spriteEntity.addComponent(SpriteRenderer);
    spriteRenderer.sprite = new Sprite(engine, texture);
    const color = new Color();
    // Display normal
    addColorEntity(spriteEntity, -20, color.setValue(1, 1, 1, 1));
    // Display red
    addColorEntity(spriteEntity.clone(), -10, color.setValue(1, 0, 0, 1));
    // Display green
    addColorEntity(spriteEntity.clone(), 0, color.setValue(0, 1, 0, 1));
    // Display blue
    addColorEntity(spriteEntity.clone(), 10, color.setValue(0, 0, 1, 1));
    // Display alpha
    addColorEntity(spriteEntity.clone(), 20, color.setValue(1, 1, 1, 0.2));
  });

engine.run();

function addColorEntity(entity: Entity, posX: number, color: Color): void {
  rootEntity.addChild(entity);
  entity.transform.setPosition(posX, 0, 0);
  entity.getComponent(SpriteRenderer).color = color;
}

自定义材质

SpriteRenderer 的自定义材质的使用方法和 MeshRenderer 的一样,请参考自定义材质文档。

import { AssetType, BlendFactor, BlendOperation, Camera, CullMode, Entity, Material, RenderQueueType, Shader, Sprite, SpriteRenderer, Texture2D, TextureWrapMode, Vector2, Vector3, WebGLEngine } from "oasis-engine";
import { OrbitControl } from "@oasis-engine/controls";

// Create engine object
const engine = new WebGLEngine("canvas");
engine.canvas.resizeByClientSize();

const scene = engine.sceneManager.activeScene;
const rootEntity = scene.createRootEntity();

// Create camera
const cameraEntity = rootEntity.createChild("Camera");
cameraEntity.transform.position = new Vector3(0, 0, 50);
cameraEntity.addComponent(Camera);
cameraEntity.addComponent(OrbitControl);

engine.resourceManager
  .load<Texture2D>({
    url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*L2GNRLWn9EAAAAAAAAAAAAAAARQnAQ",
    type: AssetType.Texture2D
  })
  .then((texture) => {
    // Create origin sprite entity.
    const texSize = new Vector2(texture.width, texture.height);
    const spriteEntity = rootEntity.createChild("spriteBlur");

    spriteEntity.addComponent(SpriteRenderer).sprite = new Sprite(engine, texture);
    spriteEntity.transform.setScale(4, 4, 4);
    // The blur algorithm will sample the edges of the texture.
    // Set the clamp warp mode to avoid mis-sampling caused by repeate warp mode.
    texture.wrapModeU = texture.wrapModeV = TextureWrapMode.Clamp;

    // Display normal
    addCustomMaterialSpriteEntity(spriteEntity, -22.5, texSize, 0.0);
    // Display low blur
    addCustomMaterialSpriteEntity(spriteEntity.clone(), -7.5, texSize, 1.0);
    // Display moderate blur
    addCustomMaterialSpriteEntity(spriteEntity.clone(), 7.5, texSize, 2.0);
    // Display highly blur
    addCustomMaterialSpriteEntity(spriteEntity.clone(), 22.5, texSize, 3.0);
  });

engine.run();

function addCustomMaterialSpriteEntity(entity: Entity, posX: number, texSize: Vector2, blurSize: number): void {
  rootEntity.addChild(entity);
  entity.transform.setPosition(posX, 0, 0);
  // Create material
  const material = new Material(engine, Shader.find("SpriteBlur"));
  entity.getComponent(SpriteRenderer).setMaterial(material);
  // Init state
  const target = material.renderState.blendState.targetBlendState;
  target.enabled = true;
  target.sourceColorBlendFactor = BlendFactor.SourceAlpha;
  target.destinationColorBlendFactor = BlendFactor.OneMinusSourceAlpha;
  target.sourceAlphaBlendFactor = BlendFactor.One;
  target.destinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha;
  target.colorBlendOperation = target.alphaBlendOperation = BlendOperation.Add;
  material.renderState.depthState.writeEnabled = false;
  material.renderQueueType = RenderQueueType.Transparent;
  material.renderState.rasterState.cullMode = CullMode.Off;
  // Set uniform
  material.shaderData.setVector2("u_texSize", texSize);
  material.shaderData.setFloat("u_blurSize", blurSize);
}

// Custom shader
const spriteVertShader = `
  precision highp float;

  uniform mat4 u_VPMat;

  attribute vec3 POSITION;
  attribute vec2 TEXCOORD_0;
  attribute vec4 COLOR_0;

  varying vec4 v_color;
  varying vec2 v_uv;

  void main()
  {
    gl_Position = u_VPMat * vec4(POSITION, 1.0);
    v_color = COLOR_0;
    v_uv = TEXCOORD_0;
  }
`;

const spriteFragmentShader = `
  precision mediump float;
  precision mediump int;

  uniform sampler2D u_spriteTexture;
  uniform float u_blurSize;
  uniform vec2 u_texSize;

  varying vec2 v_uv;
  varying vec4 v_color;

  float normpdf(float x, float sigma) {
    return 0.39894 * exp(-0.5 * x * x / (sigma * sigma)) / sigma;
  }

  void main() {
    vec4 color = texture2D(u_spriteTexture, v_uv);
    const int mSize = 11;
    const int kSize = (mSize - 1) / 2;
    float kernel[mSize];
    vec3 final_colour = vec3(0.0);

    // create the 1-D kernel
    float sigma = 7.0;
    float Z = 0.0;
    for (int i = 0; i <= kSize; ++i) {
      kernel[kSize+i] = kernel[kSize - i] = normpdf(float(i), sigma);
    }

    // get the normalization factor (as the gaussian has been clamped)
    for (int i = 0; i < mSize; ++i) {
      Z += kernel[i];
    }

    // read out the texels
    float offsetX = u_blurSize / u_texSize.x;
    float offsetY = u_blurSize / u_texSize.y;
    vec2 uv;
    for (int i = -kSize; i <= kSize; ++i) {
      for (int j = -kSize; j <= kSize; ++j) {
        uv = v_uv.xy + vec2(float(i) * offsetX, float(j) * offsetY);
        final_colour += kernel[kSize + j] * kernel[kSize + i] * texture2D(u_spriteTexture, uv).rgb;
      }
    }

    gl_FragColor = vec4(final_colour / (Z * Z), color.a) * v_color;
  }
`;

Shader.create("SpriteBlur", spriteVertShader, spriteFragmentShader);