最近一直对3D编程蛮感兴趣,想自己写一个3D渲染库,便有了这篇文章。
首先推荐两本本书——《3D数学基础:图形与游戏开发》和《3D游戏编程大师技巧》。
诸如3D算法在这两本书上都有介绍。
3D渲染算法主要的数学利器就是线性代数和三角函数,数学方法不赘述,可以自行查看专业书籍查看。
在此之前,我们将首先编写一个大体框架,将一个3D渲染类的简单部分编写好在进行数学库的开发
首先我给这个3D渲染库的名字叫See3D。并且这个库是在canvas元素上渲染,所以可以编写如下代码
class See3D {
constructor(dom = document.createElement("canvas")) {
this.__dom = dom;
this.__ctx = dom.getContext("2d");
}
width(w) {
if (w === void(0)) {
return this.__dom.width;
}
this.__dom.width = w;
return this;
}
height(w) {
if (w === void(0)) {
return this.__dom.height;
}
this.__dom.height = w;
return this;
}
full() {
this
.width(window.innerWidth)
.height(window.innerHeight)
;
return this;
}
get dom() {
return this.__dom;
}
get ctx() {
return this.__ctx;
}
}
!function () {
See3D.version = "v0.0.1";
console.log("See3D engine (" + See3D.version + ") launched");
See3D.DEBUG = true;
}(); 那么,我们接下来就可以编写数学库的内容了吗?
当然不是,设计一个好项目,当然要有一个好架构,我们当然要有一个好的设计,在此,我设计了一个类来表示所有的库:
!function () {
class Library {
constructor(name) {
this.name = name;// 库名
this.defines = {};// 存储API
}
define(name, val) {
this.defines[name] = val;// 添加API
return this;
}
get(name) {// 获取API
if (this.defines[name].private) {// 如果是私有的
console.error(new Error("Error 201: It's a private value"));
}
return this.defines[name];
}
trans() {// 将defines中的所有API添加至该库的根下
for (let i in this.defines) {
this["$" + i] = this.defines[i];
}
}
global() {// 将库中的所有API添加到全局中
for (let i in this.defines) {
globalThis[i] = this.defines[i];
}
}
}
See3D.Library = Library;
}(); 但是,我们该这么存储这些库呢?放在全局?当然不是,See3D还要有一个static熟悉__libraries存储所有的库并且提供static函数来进行这些操作
添加后See3D类如下:
class See3D {
constructor(dom = document.createElement("canvas")) {
this.__dom = dom;
this.__ctx = dom.getContext("2d");
this.loadGlobal();
this.bindLibrary("IO");
this.bindLibrary("Math3D");
this.sout = new See3D.IO.$sostream(this);
}
width(w) {
if (w === void(0)) {
return this.__dom.width;
}
this.__dom.width = w;
return this;
}
height(w) {
if (w === void(0)) {
return this.__dom.height;
}
this.__dom.height = w;
return this;
}
full() {
this
.width(window.innerWidth)
.height(window.innerHeight)
;
return this;
}
/**
* @function loadGlobal
* @desc 将所有全局库加载入该See3D实例
*/
loadGlobal() {
for (let i of See3D.__loads) {
this.load(i);
}
return this;
}
bindLibrary(name) {// 将该库的所有API加入该See3D实例
let lib = this[name];
for (let i in lib.defines) {
this[i] = lib.defines[i];
}
}
/**
* @function load
* @param {string} name
* @desc 加载指定库到See3D对象中
*/
load(name) {
this[name] = See3D.__libraries.get(name);
return this;
}
get dom() {
return this.__dom;
}
get ctx() {
return this.__ctx;
}
/**
* @function library
* @param {See3D.Library} entry
* @desc 添加See3D库
*/
static library(entry) {
// entry
See3D.__libraries.set(entry.name, entry);
}
/**
* @function load
* @param {String} name
* @desc 添加应默认自带的库
*/
static load(name) {
See3D.__loads.push(name);
}
/**
* @function loadGlobal
* @param {String} name
* @desc 将该库导入到全局环境
*/
static loadGlobal(name) {
globalThis[name] = See3D.__libraries.get(name);
}
static lib(name) {
See3D[name] = See3D.__libraries.get(name);
return See3D.__libraries.get(name);
}
}
!function () {
See3D.version = "v0.0.1";
console.log("See3D engine (" + See3D.version + ") launched");
See3D.DEBUG = true;
/**
* @property
* @private
*/
See3D.__libraries = new Map();
/**
* @property
* @private
*/
See3D.__loads = [];
/**
* @class Library
* @constructor
* @desc Library类提供了制作See3D类的接口
*/
class Library {
constructor(name) {
this.name = name;
this.defines = {};
}
define(name, val) {
this.defines[name] = val;
return this;
}
get(name) {
if (this.defines[name].private) {
console.error(new Error("Error 201: It's a private value"));
}
return this.defines[name];
}
trans() {
for (let i in this.defines) {
this["$" + i] = this.defines[i];
}
}
global() {
for (let i in this.defines) {
globalThis[i] = this.defines[i];
}
}
}
See3D.Library = Library;
}(); 然后,由于JS是弱类型语言,对于类型检查几乎没有,在不适用TS的情况下,可以让所有库中的类都继承自一个LibraryDefineObject,该类十分简单,就只有一个type属性
class LibraryDefineObject {
constructor(type) {
this.type = type;
}
transType() {
return null;
}
} 接着添加一个类型检查和类型转换函数:
function checkType(obj, type) {
return obj.type == type;
}
function translate(type, obj) {
return obj.transType(type);
}最后,全部代码如下
/**
* @class See3D
* @constructor
* @desc See3D类是可以将一个canvas变为See3D画布的类,一切操作都依靠它 \n
* 也是所有See3D API的容器,相当于一个module
*/
class See3D {
constructor(dom = document.createElement("canvas")) {
this.__dom = dom;
this.__ctx = dom.getContext("2d");
this.loadGlobal();
this.bindLibrary("IO");
this.bindLibrary("Math3D");
this.sout = new See3D.IO.$sostream(this);
}
width(w) {
if (w === void(0)) {
return this.__dom.width;
}
this.__dom.width = w;
return this;
}
height(w) {
if (w === void(0)) {
return this.__dom.height;
}
this.__dom.height = w;
return this;
}
full() {
this
.width(window.innerWidth)
.height(window.innerHeight)
;
return this;
}
/**
* @function loadGlobal
* @desc 将所有全局库加载入该See3D实例
*/
loadGlobal() {
for (let i of See3D.__loads) {
this.load(i);
}
return this;
}
bindLibrary(name) {// 将该库的所有API加入该See3D实例
let lib = this[name];
for (let i in lib.defines) {
this[i] = lib.defines[i];
}
}
/**
* @function load
* @param {string} name
* @desc 加载指定库到See3D对象中
*/
load(name) {
this[name] = See3D.__libraries.get(name);
return this;
}
get dom() {
return this.__dom;
}
get ctx() {
return this.__ctx;
}
/**
* @function library
* @param {See3D.Library} entry
* @desc 添加See3D库
*/
static library(entry) {
// entry
See3D.__libraries.set(entry.name, entry);
}
/**
* @function load
* @param {String} name
* @desc 添加应默认自带的库
*/
static load(name) {
See3D.__loads.push(name);
}
/**
* @function loadGlobal
* @param {String} name
* @desc 将该库导入到全局环境
*/
static loadGlobal(name) {
globalThis[name] = See3D.__libraries.get(name);
}
static lib(name) {
See3D[name] = See3D.__libraries.get(name);
return See3D.__libraries.get(name);
}
}
!function () {
See3D.version = "v0.0.1";
console.log("See3D engine (" + See3D.version + ") launched");
See3D.DEBUG = true;
/**
* @property
* @private
*/
See3D.__libraries = new Map();
/**
* @property
* @private
*/
See3D.__loads = [];
/**
* @class Library
* @constructor
* @desc Library类提供了制作See3D类的接口
*/
class Library {
constructor(name) {
this.name = name;
this.defines = {};
}
define(name, val) {
this.defines[name] = val;
return this;
}
get(name) {
if (this.defines[name].private) {
console.error(new Error("Error 201: It's a private value"));
}
return this.defines[name];
}
trans() {
for (let i in this.defines) {
this["$" + i] = this.defines[i];
}
}
global() {
for (let i in this.defines) {
globalThis[i] = this.defines[i];
}
}
}
// 所有的See3D库类接口都必须继承自该类
class LibraryDefineObject {
constructor(type) {
this.type = type;
}
transType() {
return null;
}
}
function checkType(obj, type) {
return obj.type == type;
}
function translate(type, obj) {
return obj.transType(type);
}
See3D.Library = Library;
See3D.LibraryDefineObject = LibraryDefineObject;
See3D.checkType = checkType;
See3D.translate = translate;
}(); 接下来,下一篇手记就将正式进入3D渲染的算法上了——实现一个3D数学库
[第一篇完]