最近一直对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数学库
[第一篇完]