1 前言

Filament环境搭建中介绍了 Filament 的 Windows 和 Android 环境搭,绘制三角形中介绍了绘制纯色和彩色三角形,绘制矩形中介绍了绘制纯色和彩色矩形,本文将使用 Filament 绘制圆形。

2 绘制圆形

​ 本文项目结构如下,完整代码资源 → Filament绘制圆形

2.1 自定义基类

​ 为方便读者将注意力聚焦在 Filament 的输入上,轻松配置复杂的环境依赖逻辑,笔者仿照 OpenGL ES 的写法,抽出了 FLSurfaceView 和 BaseModel 类。FLSurfaceView 与 GLSurfaceView 的功能类似,承载了渲染环境配置;BaseModel 中提供了一些 VertexBuffer、IndexBuffer、Material、Renderable 相关的工具类,方便子类直接使用这些工具类。

​ build.gradle

...
android {
...
aaptOptions { // 在应用程序打包过程中不压缩的文件
noCompress 'filamat', 'ktx'
}
} dependencies {
implementation fileTree(dir: '../libs', include: ['*.aar'])
...
}

​ 说明:在项目根目录下的 libs 目录中,需要放入以下 aar 文件,它们源自Filament环境搭建中编译生成的 aar。

​ FLSurfaceView.java

package com.zhyan8.circle.filament;

import android.content.Context;
import android.graphics.Point;
import android.view.Choreographer;
import android.view.Surface;
import android.view.SurfaceView; import com.google.android.filament.Camera;
import com.google.android.filament.Engine;
import com.google.android.filament.EntityManager;
import com.google.android.filament.Filament;
import com.google.android.filament.Renderer;
import com.google.android.filament.Scene;
import com.google.android.filament.Skybox;
import com.google.android.filament.SwapChain;
import com.google.android.filament.View;
import com.google.android.filament.Viewport;
import com.google.android.filament.android.DisplayHelper;
import com.google.android.filament.android.FilamentHelper;
import com.google.android.filament.android.UiHelper; /*
* Filament中待渲染的SurfaceView
* 功能可以类比OpenGL ES中的GLSurfaceView
* 用于创建Filament的渲染环境
*/
public class FLSurfaceView extends SurfaceView {
public static int RENDERMODE_WHEN_DIRTY = 0; // 用户请求渲染才渲染一帧
public static int RENDERMODE_CONTINUOUSLY = 1; // 持续渲染
protected int mRenderMode = RENDERMODE_CONTINUOUSLY; // 渲染模式
protected Choreographer mChoreographer; // 消息控制
protected DisplayHelper mDisplayHelper; // 管理Display(可以监听分辨率或刷新率的变化)
protected UiHelper mUiHelper; // 管理SurfaceView、TextureView、SurfaceHolder
protected Engine mEngine; // 引擎(跟踪用户创建的资源, 管理渲染线程和硬件渲染器)
protected Renderer mRenderer; // 渲染器(用于操作系统窗口, 生成绘制命令, 管理帧延时)
protected Scene mScene; // 场景(管理渲染对象、灯光)
protected View mView; // 存储渲染数据(View是Renderer操作的对象)
protected Camera mCamera; // 相机(视角管理)
protected Point mDesiredSize; // 渲染分辨率
protected float[] mSkyboxColor; // 背景颜色
protected SwapChain mSwapChain; // 操作系统的本地可渲染表面(native renderable surface, 通常是一个window或view)
protected FrameCallback mFrameCallback = new FrameCallback(); // 帧回调 static {
Filament.init();
} public FLSurfaceView(Context context) {
super(context);
mChoreographer = Choreographer.getInstance();
mDisplayHelper = new DisplayHelper(context);
} public void init() { // 初始化
setupSurfaceView();
setupFilament();
setupView();
setupScene();
} public void setRenderMode(int renderMode) { // 设置渲染模式
mRenderMode = renderMode;
} public void requestRender() { // 请求渲染
mChoreographer.postFrameCallback(mFrameCallback);
} public void onResume() { // 恢复
mChoreographer.postFrameCallback(mFrameCallback);
} public void onPause() { // 暂停
mChoreographer.removeFrameCallback(mFrameCallback);
} public void onDestroy() { // 销毁Filament环境
mChoreographer.removeFrameCallback(mFrameCallback);
mUiHelper.detach();
mEngine.destroyRenderer(mRenderer);
mEngine.destroyView(mView);
mEngine.destroyScene(mScene);
mEngine.destroyCameraComponent(mCamera.getEntity());
EntityManager entityManager = EntityManager.get();
entityManager.destroy(mCamera.getEntity());
mEngine.destroy();
} protected void setupScene() { // 设置Scene参数
} protected void onResized(int width, int height) { // Surface尺寸变化时回调
double zoom = 1;
double aspect = (double) width / (double) height;
mCamera.setProjection(Camera.Projection.ORTHO,
-aspect * zoom, aspect * zoom, -zoom, zoom, 0, 1000);
} private void setupSurfaceView() { // 设置SurfaceView
mUiHelper = new UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK);
mUiHelper.setRenderCallback(new SurfaceCallback());
if (mDesiredSize != null) {
mUiHelper.setDesiredSize(mDesiredSize.x, mDesiredSize.y);
}
mUiHelper.attachTo(this);
} private void setupFilament() { // 设置Filament参数
mEngine = Engine.create();
// mEngine = (new Engine.Builder()).featureLevel(Engine.FeatureLevel.FEATURE_LEVEL_0).build();
mRenderer = mEngine.createRenderer();
mScene = mEngine.createScene();
mView = mEngine.createView();
mCamera = mEngine.createCamera(mEngine.getEntityManager().create());
} private void setupView() { // 设置View参数
float[] color = mSkyboxColor != null ? mSkyboxColor : new float[] {0, 0, 0, 1};
Skybox skybox = (new Skybox.Builder()).color(color).build(mEngine);
mScene.setSkybox(skybox);
if (mEngine.getActiveFeatureLevel() == Engine.FeatureLevel.FEATURE_LEVEL_0) {
mView.setPostProcessingEnabled(false); // FEATURE_LEVEL_0不支持post-processing
}
mView.setCamera(mCamera);
mView.setScene(mScene);
} /*
* 帧回调
*/
private class FrameCallback implements Choreographer.FrameCallback {
@Override
public void doFrame(long frameTimeNanos) { // 渲染每帧数据
if (mRenderMode == RENDERMODE_CONTINUOUSLY) {
mChoreographer.postFrameCallback(this); // 请求下一帧
}
if (mUiHelper.isReadyToRender()) {
if (mRenderer.beginFrame(mSwapChain, frameTimeNanos)) {
mRenderer.render(mView);
mRenderer.endFrame();
}
}
}
} /*
* Surface回调
*/
private class SurfaceCallback implements UiHelper.RendererCallback {
@Override
public void onNativeWindowChanged(Surface surface) { // Native窗口改变时回调
if (mSwapChain != null) {
mEngine.destroySwapChain(mSwapChain);
}
long flags = mUiHelper.getSwapChainFlags();
if (mEngine.getActiveFeatureLevel() == Engine.FeatureLevel.FEATURE_LEVEL_0) {
if (SwapChain.isSRGBSwapChainSupported(mEngine)) {
flags = flags | SwapChain.CONFIG_SRGB_COLORSPACE;
}
}
mSwapChain = mEngine.createSwapChain(surface, flags);
mDisplayHelper.attach(mRenderer, getDisplay());
} @Override
public void onDetachedFromSurface() { // 解绑Surface时回调
mDisplayHelper.detach();
if (mSwapChain != null) {
mEngine.destroySwapChain(mSwapChain);
mEngine.flushAndWait();
mSwapChain = null;
}
} @Override
public void onResized(int width, int height) { // Surface尺寸变化时回调
mView.setViewport(new Viewport(0, 0, width, height));
FilamentHelper.synchronizePendingFrames(mEngine);
FLSurfaceView.this.onResized(width, height);
}
}
}

​ BaseModel.java

package com.zhyan8.circle.filament;

import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.os.Handler;
import android.os.Looper;
import android.util.Log; import com.google.android.filament.Box;
import com.google.android.filament.Engine;
import com.google.android.filament.EntityManager;
import com.google.android.filament.IndexBuffer;
import com.google.android.filament.Material;
import com.google.android.filament.MaterialInstance;
import com.google.android.filament.RenderableManager;
import com.google.android.filament.RenderableManager.PrimitiveType;
import com.google.android.filament.VertexBuffer;
import com.google.android.filament.VertexBuffer.AttributeType;
import com.google.android.filament.VertexBuffer.VertexAttribute; import java.io.FileInputStream;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel; /*
* 模型基类
* 管理模型的材质、顶点属性、顶点索引、渲染id
*/
public class BaseModel {
private static String TAG = "BaseModel";
protected AssetManager mAssetManager; // 资源管理器
protected Engine mEngine; // Filament引擎
protected Material mMaterial; // 模型材质
protected MaterialInstance mMaterialInstance; // 模型材质实例
protected VertexBuffer mVertexBuffer; // 顶点属性缓存
protected IndexBuffer mIndexBuffer; // 顶点索引缓存
protected int mRenderable; // 渲染id
protected Box mBox; // 渲染区域 public BaseModel(AssetManager assetManager, Engine engine) {
mAssetManager = assetManager;
mEngine = engine;
} public Material getMaterial() { // 获取材质
return mMaterial;
} public VertexBuffer getVertexBuffer() { // 获取顶点属性缓存
return mVertexBuffer;
} public IndexBuffer getIndexBuffer() { // 获取顶点索引缓存
return mIndexBuffer;
} public int getRenderable() { // 获取渲染id
return mRenderable;
} public void destroy() { // 销毁模型
mEngine.destroyEntity(mRenderable);
mEngine.destroyVertexBuffer(mVertexBuffer);
mEngine.destroyIndexBuffer(mIndexBuffer);
mEngine.destroyMaterialInstance(mMaterialInstance);
mEngine.destroyMaterial(mMaterial);
EntityManager entityManager = EntityManager.get();
entityManager.destroy(mRenderable);
} protected Material loadMaterial(String materialPath) { // 加载材质
Buffer buffer = readUncompressedAsset(mAssetManager, materialPath);
if (buffer != null) {
Material material = (new Material.Builder()).payload(buffer, buffer.remaining()).build(mEngine);
mMaterialInstance = material.createInstance();
material.compile(
Material.CompilerPriorityQueue.HIGH,
Material.UserVariantFilterBit.ALL,
new Handler(Looper.getMainLooper()),
() -> Log.i(TAG, "Material " + material.getName() + " compiled."));
mEngine.flush();
return material;
}
return null;
} protected VertexBuffer getVertexBuffer(float[] values) { // 获取顶点属性缓存
ByteBuffer vertexData = getByteBuffer(values);
int vertexCount = values.length / 3;
int vertexSize = Float.BYTES * 3;
VertexBuffer vertexBuffer = new VertexBuffer.Builder()
.bufferCount(1)
.vertexCount(vertexCount)
.attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT3, 0, vertexSize)
.build(mEngine);
vertexBuffer.setBufferAt(mEngine, 0, vertexData);
return vertexBuffer;
} protected VertexBuffer getVertexBuffer(Vertex[] values) { // 获取顶点属性缓存
ByteBuffer vertexData = getByteBuffer(values);
int vertexCount = values.length;
int vertexSize = Vertex.BYTES;
VertexBuffer vertexBuffer = new VertexBuffer.Builder()
.bufferCount(1)
.vertexCount(vertexCount)
.attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT3, 0, vertexSize)
.attribute(VertexAttribute.COLOR, 0, AttributeType.UBYTE4, 3 * Float.BYTES, vertexSize)
.normalized(VertexAttribute.COLOR)
.build(mEngine);
vertexBuffer.setBufferAt(mEngine, 0, vertexData);
return vertexBuffer;
} protected IndexBuffer getIndexBuffer(short[] values) { // 获取顶点索引缓存
ByteBuffer indexData = getByteBuffer(values);
int indexCount = values.length;
IndexBuffer indexBuffer = new IndexBuffer.Builder()
.indexCount(indexCount)
.bufferType(IndexBuffer.Builder.IndexType.USHORT)
.build(mEngine);
indexBuffer.setBuffer(mEngine, indexData);
return indexBuffer;
} protected int getRenderable(PrimitiveType primitiveType, int vertexCount) { // 获取渲染id
int renderable = EntityManager.get().create();
new RenderableManager.Builder(1)
.boundingBox(mBox)
.geometry(0, primitiveType, mVertexBuffer, mIndexBuffer, 0, vertexCount)
.material(0, mMaterialInstance)
.build(mEngine, renderable);
return renderable;
} private Buffer readUncompressedAsset(AssetManager assetManager, String assetPath) { // 加载资源
ByteBuffer dist = null;
try {
AssetFileDescriptor fd = assetManager.openFd(assetPath);
try(FileInputStream fis = fd.createInputStream()) {
dist = ByteBuffer.allocate((int) fd.getLength());
try (ReadableByteChannel src = Channels.newChannel(fis)) {
src.read(dist);
}
}
} catch (IOException e) {
e.printStackTrace();
}
if (dist != null) {
return dist.rewind();
}
return null;
} private ByteBuffer getByteBuffer(float[] values) { // float数组转换为ByteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(values.length * Float.BYTES);
byteBuffer.order(ByteOrder.nativeOrder());
for (int i = 0; i < values.length; i++) {
byteBuffer.putFloat(values[i]);
}
byteBuffer.flip();
return byteBuffer;
} private ByteBuffer getByteBuffer(short[] values) { // short数组转换为ByteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(values.length * Short.BYTES);
byteBuffer.order(ByteOrder.nativeOrder());
for (int i = 0; i < values.length; i++) {
byteBuffer.putShort(values[i]);
}
byteBuffer.flip();
return byteBuffer;
} private ByteBuffer getByteBuffer(Vertex[] values) { // Vertex数组转换为ByteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(values.length * Vertex.BYTES);
byteBuffer.order(ByteOrder.nativeOrder());
for (int i = 0; i < values.length; i++) {
values[i].put(byteBuffer);
}
byteBuffer.flip();
return byteBuffer;
} /*
* 顶点数据
* 包含顶点位置和颜色
*/
public static class Vertex {
public static int BYTES = 16;
public float x;
public float y;
public float z;
public int color;
public Vertex() {}
public Vertex(float x, float y, float z, int color) {
this.x = x;
this.y = y;
this.z = z;
this.color = color;
} public ByteBuffer put(ByteBuffer buffer) { // Vertex转换为ByteBuffer
buffer.putFloat(x);
buffer.putFloat(y);
buffer.putFloat(z);
buffer.putInt(color);
return buffer;
}
}
}

2.2 绘制纯色圆形(固定材质颜色)

​ MainActivity.java

package com.zhyan8.circle;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

import com.zhyan8.circle.filament.FLSurfaceView;

public class MainActivity extends AppCompatActivity {
private FLSurfaceView mFLSurfaceView; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mFLSurfaceView = new MyFLSurfaceView(this);
setContentView(mFLSurfaceView);
mFLSurfaceView.init();
mFLSurfaceView.setRenderMode(FLSurfaceView.RENDERMODE_CONTINUOUSLY);
} @Override
public void onResume() {
super.onResume();
mFLSurfaceView.onResume();
} @Override
public void onPause() {
super.onPause();
mFLSurfaceView.onPause();
} @Override
public void onDestroy() {
super.onDestroy();
mFLSurfaceView.onDestroy();
}
}

​ MyFLSurfaceView.java

package com.zhyan8.circle;

import android.content.Context;

import com.google.android.filament.Camera;
import com.zhyan8.circle.filament.BaseModel;
import com.zhyan8.circle.filament.FLSurfaceView; public class MyFLSurfaceView extends FLSurfaceView {
private BaseModel mMyModel;
public MyFLSurfaceView(Context context) {
super(context);
} public void init() {
mSkyboxColor = new float[] {0.965f, 0.941f, 0.887f, 1};
super.init();
} @Override
public void onDestroy() {
mMyModel.destroy();
super.onDestroy();
} @Override
protected void setupScene() { // 设置Scene参数
mMyModel = new Circle1(getContext().getAssets(), mEngine);
mScene.addEntity(mMyModel.getRenderable());
} @Override
protected void onResized(int width, int height) {
double zoom = 1.5;
double aspect = (double) width / (double) height;
mCamera.setProjection(Camera.Projection.ORTHO,
-aspect * zoom, aspect * zoom, -zoom, zoom, 0, 10);
}
}

​ Circle1.java

package com.zhyan8.circle;

import android.content.res.AssetManager;

import com.google.android.filament.Box;
import com.google.android.filament.Engine;
import com.google.android.filament.RenderableManager.PrimitiveType;
import com.zhyan8.circle.filament.BaseModel; public class Circle1 extends BaseModel {
private String materialPath = "materials/circle1.filamat";
private float[] mVertices;
private short[] mIndex; public Circle1(AssetManager assetManager, Engine engine) {
super(assetManager, engine);
init();
} private void init() {
int num = 50;
mVertices = getCircle(0, 0, 0.5f, num);
mIndex = getIndices(num);
mBox = new Box(0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.01f);
mMaterial = loadMaterial(materialPath);
mVertexBuffer = getVertexBuffer(mVertices);
mIndexBuffer = getIndexBuffer(mIndex);
mRenderable = getRenderable(PrimitiveType.TRIANGLES, mIndex.length);
} private float[] getCircle(float centerX, float centerY, float radius, int num) {
float unit = (float) (2 * Math.PI / num);
float[] coords = new float[(num + 1) * 3];
int index = 0;
for (int i = 0; i < num; i++) {
coords[index++] = (float)(centerX + radius * Math.cos(unit * i));
coords[index++] = (float)(centerY + radius * Math.sin(unit * i));
coords[index++] = 0;
}
coords[index++] = centerX;
coords[index++] = centerY;
coords[index] = 0;
return coords;
} private short[] getIndices(int num) {
short[] indices = new short[num * 3];
short centerIndex = (short) num;
short index = 0;
for (short i = 0; i < num - 1; i++) {
indices[index++] = centerIndex;
indices[index++] = i;
indices[index++] = (short)(i + 1);
}
indices[index++] = centerIndex;
indices[index++] = (short) (num - 1);
indices[index] = 0;
return indices;
}
}

​ circle1.mat

material {
name : circle, // 禁用所有lighting
shadingModel : unlit,
featureLevel : 0
} fragment {
vec3 sRGB_to_linear(vec3 color) { // gamma解码, 转换到线下空间
color.x = pow(color.r, 2.2);
color.y = pow(color.g, 2.2);
color.z = pow(color.b, 2.2);
return color;
} void material(inout MaterialInputs material) {
prepareMaterial(material); // 在方法返回前必须回调该函数
vec3 color = vec3(0.455, 0.725, 1.0);
color = sRGB_to_linear(color);
material.baseColor = vec4(color, 1.0);
}
}

​ 说明:这里需要进行伽马解码处理,将颜色空间转换到线性空间中,否则显示的颜色将会偏亮,伽马编码原理详见 → 【Unity3D】伽马校正

​ transform.bat

@echo off
setlocal enabledelayedexpansion
set "srcFolder=../src/main/materials"
set "distFolder=../src/main/assets/materials" for %%f in ("%srcFolder%\*.mat") do (
set "matfile=%%~nf"
matc -p mobile -a opengl -o "!matfile!.filamat" "%%f"
move "!matfile!.filamat" "%distFolder%\!matfile!.filamat"
) echo Processing complete.
pause

​ 说明:需要将 matc.exe 文件与 transform.bat 文件放在同一个目录下面,matc.exe 源自Filament环境搭建中编译生成的 exe 文件。双击 transform.bat 文件,会自动将 /src/main/materials/ 下面的所有 mat 文件全部转换为 filamat 文件,并移到 /src/main/assets/materials/ 目录下面。

​ 运行效果如下。

2.3 绘制纯色圆形(传递材质颜色)

​ MyFLSurfaceView.java

package com.zhyan8.circle;

import android.content.Context;

import com.google.android.filament.Camera;
import com.zhyan8.circle.filament.BaseModel;
import com.zhyan8.circle.filament.FLSurfaceView; public class MyFLSurfaceView extends FLSurfaceView {
private BaseModel mMyModel;
public MyFLSurfaceView(Context context) {
super(context);
} public void init() {
mSkyboxColor = new float[] {0.965f, 0.941f, 0.887f, 1};
super.init();
} @Override
public void onDestroy() {
mMyModel.destroy();
super.onDestroy();
} @Override
protected void setupScene() { // 设置Scene参数
mMyModel = new Circle2(getContext().getAssets(), mEngine);
mScene.addEntity(mMyModel.getRenderable());
} @Override
protected void onResized(int width, int height) {
double zoom = 1.5;
double aspect = (double) width / (double) height;
mCamera.setProjection(Camera.Projection.ORTHO,
-aspect * zoom, aspect * zoom, -zoom, zoom, 0, 10);
}
}

​ Circle2.java

package com.zhyan8.circle;

import android.content.res.AssetManager;

import com.google.android.filament.Box;
import com.google.android.filament.Colors;
import com.google.android.filament.Engine;
import com.google.android.filament.RenderableManager.PrimitiveType;
import com.zhyan8.circle.filament.BaseModel; public class Circle2 extends BaseModel {
private String materialPath = "materials/circle2.filamat";
private float[] mVertices;
private short[] mIndex; public Circle2(AssetManager assetManager, Engine engine) {
super(assetManager, engine);
init();
} private void init() {
int num = 50;
mVertices = getCircle(0, 0, 0.5f, num);
mIndex = getIndices(num);
mBox = new Box(0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.01f);
mMaterial = loadMaterial(materialPath);
mMaterialInstance.setParameter("baseColor", Colors.RgbType.SRGB, 0.455f, 0.725f, 1.0f);
mVertexBuffer = getVertexBuffer(mVertices);
mIndexBuffer = getIndexBuffer(mIndex);
mRenderable = getRenderable(PrimitiveType.TRIANGLES, mIndex.length);
} private float[] getCircle(float centerX, float centerY, float radius, int num) {
float unit = (float) (2 * Math.PI / num);
float[] coords = new float[(num + 1) * 3];
int index = 0;
for (int i = 0; i < num; i++) {
coords[index++] = (float)(centerX + radius * Math.cos(unit * i));
coords[index++] = (float)(centerY + radius * Math.sin(unit * i));
coords[index++] = 0;
}
coords[index++] = centerX;
coords[index++] = centerY;
coords[index] = 0;
return coords;
} private short[] getIndices(int num) {
short[] indices = new short[num * 3];
short centerIndex = (short) num;
short index = 0;
for (short i = 0; i < num - 1; i++) {
indices[index++] = centerIndex;
indices[index++] = i;
indices[index++] = (short)(i + 1);
}
indices[index++] = centerIndex;
indices[index++] = (short) (num - 1);
indices[index] = 0;
return indices;
}
}

​ circle2.mat

material {
name : circle, // 禁用所有lighting
shadingModel : unlit,
featureLevel : 0,
parameters : [
{ // 颜色必须在线性空间中传递, 而不是sRGB空间
type : float3,
name : baseColor
}
]
} fragment {
void material(inout MaterialInputs material) {
prepareMaterial(material); // 在方法返回前必须回调该函数
material.baseColor.rgb = materialParams.baseColor;
}
}

​ 运行效果如下,可以看到,与 2.2 节中进行伽马校正后的颜色是一样的。

​ 声明:本文转自【Filament】绘制圆形

【Filament】绘制圆形的更多相关文章

  1. canvas快速绘制圆形、三角形、矩形、多边形

    想看前面整理的canvas常用API的同学可以点下面: canvas学习之API整理笔记(一) canvas学习之API整理笔记(二) 本系列文章涉及的所有代码都将上传至:项目代码github地址,喜 ...

  2. android绘制圆形图片的两种方式

    看下效果先 下面有完整的示例代码 使用BitmapShader(着色器) 我们在绘制view 的时候 就是小学上美术课 用水彩笔在本子上画画 使用着色器绘制圆形图片最简单的理解方式 就是把bitmap ...

  3. Css绘制圆形,环形,椭圆等图形

    转载自http://blog.csdn.net/gongstrong123/article/details/50888758 绘制圆形,环形,椭圆 <!DOCTYPE html> < ...

  4. 使用html5 canvas绘制圆形或弧线

    注意:本文属于<html5 Canvas绘制图形入门详解>系列文章中的一部分.如果你是html5初学者,仅仅阅读本文,可能无法较深入的理解canvas,甚至无法顺畅地通读本文.请点击上述链 ...

  5. html5 canvas绘制圆形印章,以及与页面交互

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  6. SVG绘制圆形简单示例分享

    今天分享“svg绘制圆形”部分 1.简单圆形 效果图如下: 关键代码: <svg xmlns="http://www.w3.org/2000/svg" version=&qu ...

  7. C#绘制圆形时钟

    本文由作者参考部分案例后加以修改完成: 参考链接如下: http://blog.csdn.net/xuemoyao/article/details/8001113 http://wenku.baidu ...

  8. canvas绘制圆形进度条(或显示当前已浏览网页百分比)

    使用canvas绘制圆形进度条,或者是网页加载进度条 或者是显示你浏览了本网页多少-- 由于个浏览器的计算差异,打开浏览器时 初始值有所不同,但是当拉倒网页底部时,均显示100%. 兼容性:测试浏览器 ...

  9. SkylineGlobe 如何实现绘制圆形Polygon和对图层的圆形范围选择查询

    //结束绘制圆形之前,得到Polygon var pos = gPolyObj.Position; var bufferR = gPolyObj.Radius; var cVerticesArray ...

  10. Breaseman算法绘制圆形|中点算法绘制圆形_程序片段

    Breaseman算法绘制圆形|中点算法绘制圆形_程序片段 1. Breaseman算法绘制圆形程序 由于算法的特殊性,限制绘制第一象限部分,其他部分通过旋转绘制. void CCGProjectWo ...

随机推荐

  1. [转帖]关于linux:NUMA架构下的内存延迟区别测试

    https://lequ7.com/guan-yu-linuxnuma-jia-gou-xia-de-nei-cun-yan-chi-qu-bie-ce-shi.html 当初的服务器物理机CPU个别 ...

  2. 时间片 线程切换 指令周期 流水线 TPS的初步了解

    时间片 线程切换 指令周期 流水线 TPS的初步了解 情况说明 Redis 单线程提供服务, 可以支撑十万级别的TPS 通过以个非常简单的测试 redis-benchmark -c 50 -n 500 ...

  3. [转帖]Mars II - Microarchitectures - Phytium

    https://en.wikichip.org/wiki/phytium/microarchitectures/mars_ii Edit Values Mars II µarch General In ...

  4. echarts去除坐标轴上的x和y轴

    通过 show:false控制手否显示 <!DOCTYPE html> <html lang="en"> <head> <meta cha ...

  5. vue报错:'XX' is defined but never used no-unused-vars

    参考地址:http://www.gold404.cn/info/87 导致这个报错的原因就是eslint校验, 就是你当初在vue创建脚手架的时候选择了eslint校验: 后面你绝对会碰到这样的报错, ...

  6. 微信小程序-获取用户位置

    首先我要提供几个文档的链接地址: 首先是官方文档的获取用户位置的API文档地址: 官方文档地址:https://developers.weixin.qq.com/miniprogram/dev/api ...

  7. 码云gitee创建仓库并用git上传文件

    相关文章链接: 码云(gitee)配置SSH密钥 码云gitee创建仓库并用git上传文件 git 上传错误This oplation equires one of the flowi vrsions ...

  8. C/C++ 通用ShellCode的编写与调用

    首先,我们的ShellCode代码需要自定位,因为我们的代码并不是一个完整的EXE可执行程序,他没有导入表无法定位到当前系统中每个函数的虚拟地址,所以我们直接获取到Kernel32.dll的基地址,里 ...

  9. 字节码编程,Javassist篇五《使用Bytecode指令码生成含有自定义注解的类和方法》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 到本章为止已经写了四篇关于字节码编程的内容,涉及了大部分的API方法.整体来说对 J ...

  10. MindSpore导入CUDA算子

    技术背景 当今众多的基于Python的AI框架(如MindSpore.PyTorch等)给了开发者非常便利的编程的条件,我们可以用Python的简单的语法写代码,然后由框架在后端自动编译成可以在GPU ...