ティラノスクリプトの3D その6:光源
初めに
ティラノスクリプトの光源は、[3d_scene_new] と [3d_scene] で設定する環境光だけです。理由は多彩な光源は、高負荷になりがちなことと、光源を用意する代わりにオブジェクトのマテリアルに光の照射効果自体を加味することで同様な効果が得られるからと思われます。とは言え three.js には、せっかく多彩な光源が用意されていますので、機能としては用意出来た方がいいと思います。下の画像を見てください。
この部屋は、3Dソフトで作られたごく普通シーンですが、環境光のみのリアルタイムレンダリングでは、のっぺりした感じになります。一方この男性の場合、マテリアルの工夫でそれなりの表現になってます。このシーンに二つのスポットライトを付加したのが、表題の画像です。一目で質感がよくなっているのが分かります。
下の例は、平行光源の例です。
斜め前から投光してます。お分かりのように、明るさの抑揚がしっかり出てます。一番光が当たる部分の光量の合計が1になるようにするのが適切なようです。今回は、three.js で可能な光源を自由に配置する事が出来るタグを作りました。
実現法
作り方は、スカイドームの場合と異なり、簡単です。[3d_light_new] で任意の光源を作り、[3d_light] でそのプロパティを編集するようなコードを作るだけです。この光源のオンオフは、これらのタグの visible 属性を "true" または、"false " に設定するか、[3d_show] で点けて [3d_hide] 消すことができます。唯一注意する点は、保存したシーンの再ロードで、光源を再現できるように ThreeModel にちゃんと情報を保存することを忘れないようにすることです。もう一つは、光源だけに限りませんが、既にあるオブジェクトの子として、光源を生成出来るようにしました。
タグ説明
[3d_light_new]
光源を作ります。以下パラメータです。
name [必須] この光源の名前です。光源を生成した後、この名前で指定して編集、削除を行います。
type [必須] 光源の種類を指定します。種類は以下の文字列で指定します。
"direct": 平行光源です。外での太陽光のようなものです。光の減衰はしません。
"hemisphere": 半球光源です。上空の色と地面の色を指定してグラデーション付きの環境光と考えるといいでしょう。
"point": 点光源です。発光する場所から全方向に光を放ちます。室内での電球のような効果を出します。指定すると減衰します。
"spot": 方向付き光源です。懐中電灯のようなものと考えてください。指定すると減衰します。
"rectarea": 矩形平面光源です。広がりを持った光源で、窓から差し込む光のような効果を出します。
intensity [選択] この光源の明るさです。無指定の場合 "1" です。
visible [選択] この光源をすぐに有効にするかどうか指定します。無指定の場合 "false" で、有効にしたい場合は、"true" を指定してください。
parent [選択] この光源を格納する親のオブジェクト名を指定します。オブジェクトは、基本何でも可能です。無指定の場合、親はないと見なします。
以上は、共通のパラメーターです。以下は、光源の種類で使われる場合があるとか、意味が少し変わるパラメータです。
color [選択] 光源の色です。6桁の16進文字列で指定します。無指定の場合、"ffffff" で白です。"hemisphere" タイプの場合、上空の色になります。他の光源では、光源そのものの色になります。
groundcolor [選択] 光源が "hemisphere" タイプのみで、地上の光の色を指定します。無指定の場合 "4040ff" にしています。
position [選択] 光源が、"direct" と "hemisphere" の場合は意味を持ちません。他の光源の場合は、光源の位置になります。x,y,z の順で数値をコンマで区切った文字列で示します。. と , は、間違い安いので注意してください。無指定の場合は、"0.0.0" になります。
direction [選択] 光源の差し込む方向です。光源が、"direct" と、"spot" と、"rectarea" の場合のみ意味があります。position と同じ記述方法で指定します。無指定の場合は、"0,1,0" になります。
distance [選択] 光源の到達距離です。光源が、"point" と、"spot" と、"rectarea" の場合のみ意味があります。この値が、"0" の場合は、到達距離は無限大と判断します。具体的数値が入ると光は、その距離までは減衰しながら、到達します。"0" の場合は、逆2乗の係数で減少します。
decay [選択] distance が有限の値の場合の光の減衰の仕方を指定します。無指定の場合、"2" です。この値がいくつの時どんな減衰になるのかよく分かりません。詳細は three.js のドキュメントを参照してください。
angle [選択] 光源が広がる角度です。光源が、"spot" の場合のみ意味があります。無指定の場合、約1.04(60度)です。数字はラジアンなので面倒です。
penumbra [選択] 光源が、"spot" の場合のみ意味があります。光が照射方向から角度を増すにつれてどの程度減衰するかを指定します。"0" で減衰なし、"1" で angle の角度で 0 になります。無指定の場合、"0" です。
width , height [選択] 光源が、"rectarea" の場合のみ意味があります。この値は、矩形の幅と高さを指定します。無指定の場合は、両方共 "10" です。
shadow [選択] この光で、影を作るかを指定します。光源が、"direct"、"point"、"spot" の場合のみ有効です。ただ、これを有効にするとそれなりにマシンパワーが必要になるようですので、注意が必要です。
[3d_light]
光源のパラメータを変更します。visible だけを明示的に指定することで、光源のオンオフの切替にも使えます。パラメータは、type と parent 以外は全て [3d_light_new] と同じパラメータが指定できます。name でどの光源かを指定して、他のパラメータを指定して値を変更します。無指定のパラメータは、変更しません。
コード
TYRANO.kag.tag["3d_light_new"] = {
vital : ["name","type"],
pm : {
name : "",
type : "",
intensity:"1",
color:"0xffffff",
groundcolor:"0x4040ff",
position:"0,0,0",
direction:"0,1,0",
distance: "0",
decay: "2",
angle: "1.0471975512",
penumbra: "0",
width: "10",
height: "10",
visible: "false",
shadow: "false",
parent: "",
},
start : function(pm) {
function create_light(pm ) {
let type = pm.type.toLowerCase();
let color = parseInt(pm.color.toLowerCase());
let pos = $.three_pos( pm.position );
let dir = $.three_pos( pm.direction);
let dis = parseFloat(pm.distance );
let intensity = parseFloat(pm.intensity);
let dec = parseFloat(pm.decay);
if( type =="direct" ){
let light = new THREE.DirectionalLight( color, intensity );
light.position.set( dir.x, dir.y, dir.z );
if( pm.shadow == "true"){
light.castShadow = true;
}
return light;
}
else if( type == "hemisphere"){
let gcolor = parseInt(pm.goundcolor.toLowerCase());
let light = new THREE.HemisphereLight( color, gcolor, intensity );
return light;
}
else if( type == "point"){
let light = new THREE.PointLight( color, intensity, dis. dec )
light.position.set( pos.x, pos.y, pos.z );
if( pm.shadow == "true"){
light.castShadow = true ;
}
return light;
}
else if( type == "spot" ){
let angle = ParseFloat(pm.angle);
let penumbra = ParseFloat(pm.penumbra);
let light = new THREE.SpotLight( color, intensity, dis, angle, penumbra, dec );
light.position.set( dir.x, dir.y, dir.z );
if( pm.shadow == "true"){
light.castShadow = true ;
}
return light;
}
else if( type == "rectarea"){
let width = ParseFloat(pm.width);
let height = ParseFloat(pm.height);
let light = new THREE.RectAreaLight( color, intensity, width, height );
light.position.set( pos.x, pos.y, pos.z );
light.LookAt( dir.x, dir.y, dir.z );
return light;
}
}
let light = create_light(pm);
pm.visible = (pm.visible == "true" || pm.visible == true )? true : false ;
let visible = pm.visible ;
light.visible = visible;
if (0 != $.checkThreeModel(pm.parent)) {
let parent = this.kag.tmp.three.models[pm.parent];
parent.model.add( light );
if( visible == true) pm.visible = false;
}
let three = this.kag.tmp.three;
this.kag.tmp.three.models[pm.name] = new ThreeModel(
{ name: pm.name, model: light, pm: pm },three);
if( visible == true ) pm.visible = true;
this.kag.ftag.nextOrder();
},
};
TYRANO.kag.ftag.master_tag["3d_light_new"] = TYRANO.kag.tag["3d_light_new"];
TYRANO.kag.ftag.master_tag["3d_light_new"].kag = TYRANO.kag;
TYRANO.kag.tag["3d_light"] = {
vital : ["name"],
pm : {
name : "",
parent : "",
intensity:"",
color:"",
skycolor:"",
position:"",
direction:"",
distance: "",
decay: "",
angle: "",
penumbra: "",
width: "",
height: "",
visible: "",
},
start : function(pm) {
function get_light(pm, three){
if (0 != $.checkThreeModel(pm.parent)){
let parent = three.models[pm.parent];
for(let child in parent.children ){
if(child.name == pm.name ) {
return child;
}
}
}
else if (0 != $.checkThreeModel(pm.name)) {
let light = three.models[pm.name];
return light;
}
return undefined;
}
let three = this.kag.tmp.three;
let light = (0 != $.checkThreeModel(pm.name)) ? three.models[pm.name] : undefined ;
if( light !== undefined ){
if( pm.intensity !== "" ){
light.pm.intensity = pm.intensity ;
let intensity = parseFloat(pm.intensity);
light.model.intensity = intensity;
}
if( pm.color !== "" ){
light.pm.color = pm.color;
let color = parseInt(pm.color.toLowerCase());
light.model.color = color;
}
if( pm.skycolor !== "" ){
light.pm.skycolor = pm.skycolor;
let gcolor = parseInt(pm.groundcolor.toLowerCase());
light.model.goundColor = gcolor;
}
if( pm.position !== "" ){
light.pm.position = pm.position;
let pos = $.three_pos( pm.position );
light.model.position.set( dir.x, dir.y, dir.z );
}
if( pm.direction !== "" ){
light.pm.direction = pm.direction;
let dir = $.three_pos( pm.direction);
light.model.LookAt( dir.x, dir.y, dir.z );
}
if( pm.distance !== "" ){
light.pm.distance = pm.distance;
let dis = parseFloat(pm.distance );
light.model.distance = dis;
}
if( pm.decay !== "" ){
light.pm.decay = pm.decay;
let dec = parseFloat(pm.decay);
light.model.decay = dec;
}
if( pm.angle !== "" ){
light.pm.angle = pm.angle;
let angle = ParseFloat(pm.angle);
light.model.angle = angle ;
}
if( pm.penumbra !== "" ){
light.pm.penumbra = pm.penumbra;
let penumbra = ParseFloat(pm.penumbra);
light.model.penumbra = penumbra ;
}
if( pm.width !== "" ){
light.pm.width = pm.width;
let width = ParseFloat(pm.width);
light.model.width = width;
}
if( pm.height !== "" ){
light.pm.height = pm.height;
let height = ParseFloat(pm.height);
light.model.height = height ;
}
if( pm.visible !== "" ){
light.pm.visible = pm.visible;
if( pm.visible == "true"){
light.model.visible = true;
light.pm.visible=true;
}
else{
light.model.visible = false;
light.pm.visible=false;
}
}
}
this.kag.ftag.nextOrder();
},
};
TYRANO.kag.ftag.master_tag["3d_light"] = TYRANO.kag.tag["3d_light"];
TYRANO.kag.ftag.master_tag["3d_light"].kag = TYRANO.kag;
内容は、特に奇をてらったものではありません。指定のパラメータに沿って、three.js の光源を生成しているだけです。唯一の注意点は、[3d_light_new] のコードで、光源で ThreeModel のオブジェクトを生成するとき、パラメータの visible が、"true" の場合、そのままコンストラクターに渡すと、光源をシーンに 追加してしまいます。しかし光源に親がある場合、それをすると不都合なので、生成時は、pm.visible を false にして、直後、true に戻している点だけです。
[3d_light] の方は、パラメータの修正時に、three.js のオブジェクトのプロパティを変更するだけでなく、 ThreeModel のpm プロパティも変更しています。
次回からは、各種3Dモデルのロードとモーション機能に入ります。