Skip to content

Instantly share code, notes, and snippets.

@mieki256
Created April 2, 2017 10:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mieki256/7f1f941f74fd9d62d2607d674465f380 to your computer and use it in GitHub Desktop.
Save mieki256/7f1f941f74fd9d62d2607d674465f380 to your computer and use it in GitHub Desktop.
DXRubyでフォンシェーディングのテスト
#!ruby -Ku
# -*- mode: ruby; coding: utf-8 -*-
# Last updated: <2017/04/02 16:12:04 +0900>
#
# DXRuby 1.5.21dev以降で追加された CustomRenderTarget の動作確認
# 公式サンプル spheretest.rb を改造
#
# tinydaeparser.rb や tinywavefrontobj.rb を使って
# COLLADA形式(.dae) や Wavefront形式(.obj .mtl)を直接読んで使ってみる
#
# フォンシェーディングにも対応…できたのだろうか…自信無い
#
# * カーソルキーでモデルを回転できるように変更
# * W,Sキーで前後に移動
# * A,Dキーで鏡面光の硬度を変更できる
#
# usage:
# ruby crt_test06_readdae.rb [0-5]
#
# DXRuby開発版の入手先
# Home - mirichi/dxruby-doc Wiki
# https://github.com/mirichi/dxruby-doc/wiki
require 'dxruby'
require 'json'
require 'pp'
require_relative 'tinydaeparser'
require_relative 'tinywavefrontobj'
# モデルデータの頂点配列やシェーダを格納するクラス
class Material
attr_accessor :vbuf0
attr_accessor :vbuf1
attr_accessor :vbuf2
attr_accessor :vbuf3
attr_accessor :shader
attr_accessor :m
# @return [true, false] テクスチャを使っているか否か
attr_accessor :use_uv
# @return [Image] テクスチャ画像
attr_accessor :image
# HLSLソースのリスト
HLSL_LIST = {
:lambert => "hlsl_lambert.fx",
:phong => "hlsl_phong.fx",
}
# SHADING = :lambert
SHADING = :phong
@@hlsl = nil
# initialize
#
# @param vertex [Array<Float>] 頂点座標配列
# @param normal [Array<Float>] 法線ベクトル配列
# @param uv [Array<Float>] uv座標配列
# @param color [Array<Integer>] 頂点カラー配列
# @param image [Image, nil] テクスチャ画像。nilならuvは使わない
# @param ambient [Vector] 環境光
# @param diffuse [Vector] 拡散光
# @param specular [Vector] 鏡面光
# @param shininess [Float] 鏡面強度(鏡面硬度?)、0.0-128.0まで
#
def initialize(vertex: nil, normal: nil, uv: nil, color: nil, image: nil,
ambient: nil, diffuse: nil, specular: nil, shininess: 0.0)
unless @@hlsl
# HLSソースの読み込み
@@hlsl = {}
HLSL_LIST.each { |k, fn| @@hlsl[k] = File.read(fn) }
end
@use_uv = (image != nil)? true : false
@vertex = vertex
@normal = normal
@uv = uv
@color = color
@image = image
@mat_ambient = (ambient)? ambient : Vector.new(1.0, 1.0, 1.0)
@mat_diffuse = (diffuse)? diffuse : Vector.new(1.0, 1.0, 1.0)
@mat_specular = (specular)? specular : Vector.new(0.0, 0.0, 0.0)
@mat_shininess = shininess
if shininess == 0.0 or !specular
# shininessが0なら鏡面光は無しにする
@mat_specular = Vector.new(0.0, 0.0, 0.0)
end
if @use_uv
# テクスチャ使用
init_use_tex(@vertex, @normal, @uv, @color, @image)
else
# テクスチャ未使用
init_not_use_tex(@vertex, @normal, @color)
end
@shader.vMatAmbient = @mat_ambient
@shader.vMatDiffuse = @mat_diffuse
@shader.vMatSpecular = @mat_specular
@shader.vMatShininess = @mat_shininess
end
# テクスチャ使用時の初期化処理
# @param vtx [Array<Float>] 頂点座標配列
# @param nml [Array<Float>] 法線ベクトル配列
# @param uv [Array<Float>] uv座標配列
# @param col [Array<Integer>] 頂点カラー配列
# @param img [Image] テクスチャ画像
def init_use_tex(vtx, nml, uv, col, img)
# Shader::Core生成
@core = Shader::Core.new(@@hlsl[SHADING],
mWorld: :float,
mView: :float,
mProj: :float,
vLightDir: :float,
vLightAmbient: :float,
vLightDiffuse: :float,
vMatAmbient: :float,
vMatDiffuse: :float,
vMatSpecular: :float,
vMatShininess: :float,
tex0: :texture
)
# 頂点座標、法線ベクトル、テクスチャ座標、頂点カラー用バッファ
@vbuf0 = VertexBuffer.new([[D3DDECLTYPE_FLOAT3, D3DDECLUSAGE_POSITION, 0],])
@vbuf1 = VertexBuffer.new([[D3DDECLTYPE_FLOAT3, D3DDECLUSAGE_NORMAL, 0],])
@vbuf2 = VertexBuffer.new([[D3DDECLTYPE_FLOAT2, D3DDECLUSAGE_TEXCOORD, 0],])
@vbuf3 = VertexBuffer.new([[D3DDECLTYPE_D3DCOLOR, D3DDECLUSAGE_COLOR, 0],])
@vbuf0.vertices = vtx
@vbuf1.vertices = nml
@vbuf2.vertices = uv
@vbuf3.vertices = col
@m = Matrix.new
@shader = Shader.new(@core, "WithTex")
@shader.tex0 = img
end
# テクスチャ未使用時の初期化処理
# @param vtx [Array<Float>] 頂点座標配列
# @param nml [Array<Float>] 法線ベクトル配列
# @param col [Array<Integer>] 頂点カラー配列
def init_not_use_tex(vtx, nml, col)
@core = Shader::Core.new(@@hlsl[SHADING],
mWorld: :float,
mView: :float,
mProj: :float,
vLightDir: :float,
vLightAmbient: :float,
vLightDiffuse: :float,
vMatAmbient: :float,
vMatDiffuse: :float,
vMatSpecular: :float,
vMatShininess: :float
)
# 頂点座標、法線ベクトル、頂点カラー用バッファ
@vbuf0 = VertexBuffer.new([[D3DDECLTYPE_FLOAT3, D3DDECLUSAGE_POSITION, 0],])
@vbuf1 = VertexBuffer.new([[D3DDECLTYPE_FLOAT3, D3DDECLUSAGE_NORMAL, 0],])
@vbuf2 = VertexBuffer.new([[D3DDECLTYPE_D3DCOLOR, D3DDECLUSAGE_COLOR, 0],])
@vbuf0.vertices = vtx
@vbuf1.vertices = nml
@vbuf2.vertices = col
@m = Matrix.new
@shader = Shader.new(@core, "WithoutTex")
end
# 鏡面強度の再設定
# @param shininess [Float] 鏡面強度 0から128まで。0なら鏡面光無しに設定
def set_shininess(shininess)
@mat_shininess = shininess
@shader.vMatShininess = @mat_shininess
if shininess == 0.0
@shader.vMatSpecular = Vector.new(0.0, 0.0, 0.0)
else
@shader.vMatSpecular = @mat_specular
end
end
# 鏡面強度を取得
def get_shininess
return @mat_shininess
end
end
# RenderTarget3Dクラス
class RenderTarget3D < CustomRenderTarget
attr_accessor :view
attr_accessor :proj
attr_accessor :light_dir
attr_accessor :light_diffuse
attr_accessor :light_ambient
def initialize(*)
super
@draw_data = [] # 描画予約的な配列
@light_ambient = Vector.new(0.1, 0.1, 0.1)
@light_diffuse = Vector.new(0.9, 0.9, 0.9)
end
# CustomRenderTargetの描画メソッド
def custom_render(o)
o.set_viewport(0, 0, width, height, 0, 1) # ビューポート設定
o.begin_scene # 描画開始
# レンダーステート設定
o.set_render_state(D3DRS_CULLMODE, D3DCULL_CW)
# o.set_render_state(D3DRS_CULLMODE, D3DCULL_NONE)
o.set_render_state(D3DRS_ZENABLE, D3DZB_TRUE)
o.set_render_state(D3DRS_ZWRITEENABLE, 1) # bool値 TRUE=1, FALSE=0
# 描画予約を順番に処理する
@draw_data.each do |mat|
# シェーダパラメータ設定
mat.shader.mWorld = mat.m
mat.shader.mView = @view
mat.shader.mProj = @proj
mat.shader.vLightDir = @light_dir
mat.shader.vLightAmbient = @light_ambient
mat.shader.vLightDiffuse = @light_diffuse
if mat.use_uv
# テクスチャ使用
# シェーダパラメータのDirectXへの設定など面倒なことはusing_shaderがやってくれる
o.using_shader(mat.shader) do
# 頂点バッファは複数指定できる
o.set_stream(mat.vbuf0, mat.vbuf1, mat.vbuf2, mat.vbuf3)
o.draw_primitive(D3DPT_TRIANGLELIST, mat.vbuf0.vertex_count / 3)
end
else
# テクスチャ未使用
o.using_shader(mat.shader) do
o.set_stream(mat.vbuf0, mat.vbuf1, mat.vbuf2)
o.draw_primitive(D3DPT_TRIANGLELIST, mat.vbuf0.vertex_count / 3)
end
end
end
o.end_scene # 描画終了
@draw_data.clear # 描画予約のクリア
end
# 描画予約
# @param material [Object] 描画したいモデル
def draw(material)
@draw_data << material
end
end
# ----------------------------------------
# initialize
model_list = [
# [モデルデータファイル名, テクスチャ画像ファイル名]
["res/sample_cube_tex.obj", "res/UVCheckerMap01-1024.png"], # 0 cube。テクスチャ使用
["res/suzanne.obj", "res/UVCheckerMap01-1024.png"], # 1 スザンヌ。テクスチャ使用
["res/sample_bg2.dae", "res/uv_mecha.png"], # 2 BGモデル。テクスチャ使用
["res/uv_vcol.dae", "res/uvchecker512.png"], # 3 cube。頂点カラー使用
["res/sample_cube_color.obj", ""], # 4 cube。テクスチャ未使用
["res/ball.dae", "res/uvchecker512.png"], # 5 ball。スムーズ使用
["res/plane.obj", "res/uvchecker512.png"], # 6 plane。只の板
]
mkind = 0
# コマンドライン引数で描画するモデルデータを変更
mkind = (ARGV[0].to_i % model_list.size) unless ARGV.empty?
model_name, tex_name = model_list[mkind]
if model_name =~ /\.dae$/
# モデルデータ(.dae)を読み込み
o = TinyDaeParser.new(model_name, dxruby: true)
matnames = o.get_material_name_list # マテリアル名リストを取得
matname = matnames[0] # 仮で一番最初のマテリアルのみ使う
matdata = o.get_material_data(matname) # マテリアル情報取得
# pp matdata
elsif model_name =~ /\.obj$/
# モデルデータ(.obj)を読み込み
o = TinyWaveFrontObj.new(model_name, dxruby: true)
end
# テクスチャ画像読み込み
img = (o.use_uv and tex_name != "")? Image.load(tex_name) : nil
# モデルデータクラスを生成
material = Material.new(
vertex: o.get_vertex_array, # 頂点座標配列
normal: o.get_normal_array, # 法線ベクトル配列
uv: o.get_uv_array, # UV座標配列
color: o.get_color_array, # 頂点カラー配列
image: img, # テクスチャ画像 or nil
ambient: Vector.new(1.0, 1.0, 1.0), # 環境光
diffuse: Vector.new(1.0, 1.0, 1.0), # 拡散光
specular: Vector.new(0.5, 0.5, 0.5), # 鏡面光
shininess: 50.0, # 鏡面強度(鏡面硬度?)
)
# 画面サイズ
Window.resize(640, 480)
Window.bgcolor = [64, 96, 128] # background color R,G,B
# モデル回転用
m_rot_x = 0
m_rot_y = 0
m_move_z = -2.5
# RenderTarget3Dオブジェクト生成
rt3d = RenderTarget3D.new(Window.width, Window.height)
rt3d.view = Matrix.look_at(
Vector.new(0, 0, m_move_z), # 視点位置
Vector.new(0, 0, 0), # 注視座標
Vector.new(0, 1, 0) # 上方向
)
rt3d.proj = Matrix.projection_fov(
60.0, # 視野角
Window.width.to_f / Window.height, # 画面比
0.5, # near clip
1000.0 # far clip
)
# ライト方向(頂点から光源に向けたベクトル)等を設定
rt3d.light_dir = Vector.new(0.5, 0.5, -1).normalize
rt3d.light_ambient = Vector.new(0.1, 0.1, 0.1)
rt3d.light_diffuse = Vector.new(0.9, 0.9, 0.9)
fnt = Font.new(12)
# メインループ
Window.loop do
break if Input.keyPush?(K_ESCAPE)
# ライト位置、じゃなくてライト方向をマウス座標で変更。
# 頂点から光源に向けたベクトルで、光源から頂点へ、ではないっぽい。
x = Input.mouse_x.fdiv(Window.width / 2) - 1
y = -(Input.mouse_y.fdiv(Window.height / 2) - 1)
rt3d.light_dir = Vector.new(x, y, -1).normalize
# phongシェーディングのshininess(鏡面強度、鏡面指数)を変更
if Input.keyDown?(K_D) or Input.keyDown?(K_A)
shininess = material.get_shininess + 2 * ((Input.keyDown?(K_D))? 1 : -1)
shininess = 0.0 if shininess < 0.0
shininess = 128.0 if shininess > 128.0
material.set_shininess(shininess)
end
# カメラ位置(奥行き)変更
if Input.keyDown?(K_W) or Input.keyDown?(K_S)
m_move_z += 0.1 * ((Input.keyDown?(K_W))? -1 : 1)
rt3d.view = Matrix.look_at(
Vector.new(0, 0, m_move_z),
Vector.new(0, 0, 0),
Vector.new(0, 1, 0)
)
end
# マテリアル(モデルデータ)をカーソルキーの入力で回転
d = 2.0
m_rot_x += Input.y.to_f * d
m_rot_y += Input.x.to_f * d
material.m = Matrix.new
material.m *= Matrix.rotation_x(m_rot_x)
material.m *= Matrix.rotation_y(m_rot_y)
# RenderTarget3Dにモデルを描画
rt3d.draw(material)
# 画面に描画
Window.draw(0, 0, rt3d)
# FPS等を描画
Window.drawFont(4, 4, sprintf("%02d fps", Window.fps), fnt)
Window.drawFont(4, 24, "Push Arrow Key or W,D", fnt)
Window.drawFont(4, 44, "shininess = #{material.get_shininess}", fnt)
end
// HLSL lambert shading (half lambert)
float4x4 mWorld;
float4x4 mView;
float4x4 mProj;
float4 vLightDir; // light direction
float3 vLightAmbient; // light ambient
float3 vLightDiffuse; // light diffuse
float3 vMatAmbient; // matrial ambient
float3 vMatDiffuse; // material diffuse
float3 vMatSpecular; // material specular
float vMatShininess; // material shininess
texture tex0; // texture image
sampler2D Samp = sampler_state {
Texture = <tex0>;
};
struct VS_IN {
float4 vPosition : POSITION0; // vertex position
float4 vNormal : NORMAL; // vertex normal
float2 vTexCoords : TEXCOORD0; // texture uv
float4 vVertexColor : COLOR0; // vertex color
};
struct VS_IN_WOT {
float4 vPosition : POSITION0; // vertex position
float4 vNormal : NORMAL; // vertex normal
float4 vVertexColor : COLOR0; // vertex color
};
struct VS_OUT {
float4 vPosition : POSITION; // vertex position
float3 vNormal : TEXCOORD0; // vertex normal
float2 vTexCoords : TEXCOORD1; // texture uv
float4 vVertexColor : COLOR0; // vertex color
};
struct VS_OUT_WOT {
float4 vPosition : POSITION; // vertex position
float3 vNormal : TEXCOORD0; // vertex normal
float4 vVertexColor : COLOR0; // vertex color
};
// vertex shader with txeture
VS_OUT VS(VS_IN v) {
VS_OUT output;
output.vPosition = mul(mul(mul(v.vPosition, mWorld), mView), mProj);
output.vNormal = mul(v.vNormal, mWorld).xyz;
output.vVertexColor = v.vVertexColor;
output.vTexCoords = v.vTexCoords.xy;
return output;
}
// vertex shader without txeture
VS_OUT_WOT VS_WOT(VS_IN_WOT v) {
VS_OUT_WOT output;
output.vPosition = mul(mul(mul(v.vPosition, mWorld), mView), mProj);
output.vNormal = mul(v.vNormal, mWorld).xyz;
output.vVertexColor = v.vVertexColor;
return output;
}
struct PS_OUT {
float4 vColor : COLOR0;
};
// pixel shader with tex
PS_OUT PS(VS_OUT p) {
PS_OUT output;
float4 tcolor = tex2D(Samp, p.vTexCoords);
float lambert = dot(vLightDir, normalize(p.vNormal)) * 0.5f + 0.5f;
lambert *= lambert;
float3 diffuse = vLightDiffuse * vMatDiffuse * p.vVertexColor * lambert * tcolor;
float3 ambient = vLightAmbient * vMatAmbient;
output.vColor.rgb = diffuse + ambient;
output.vColor.a = 1.0;
return output;
}
// pixel shader without tex
PS_OUT PS_WOT(VS_OUT_WOT p) {
PS_OUT output;
float lambert = dot(vLightDir, normalize(p.vNormal)) * 0.5f + 0.5f;
lambert *= lambert;
float3 diffuse = vLightDiffuse * vMatDiffuse * p.vVertexColor * lambert;
float3 ambient = vLightAmbient * vMatAmbient;
output.vColor.rgb = diffuse + ambient;
output.vColor.a = 1.0;
return output;
}
technique WithTex {
pass p0 {
VertexShader = compile vs_2_0 VS();
PixelShader = compile ps_2_0 PS();
}
}
technique WithoutTex {
pass p0 {
VertexShader = compile vs_2_0 VS_WOT();
PixelShader = compile ps_2_0 PS_WOT();
}
}
// HLSL phong shading
float4x4 mWorld;
float4x4 mView;
float4x4 mProj;
float4 vLightDir; // light direction
float3 vLightAmbient; // light ambient
float3 vLightDiffuse; // light diffuse
float3 vMatAmbient; // matrial ambient
float3 vMatDiffuse; // material diffuse
float3 vMatSpecular; // material specular
float vMatShininess; // material shininess
texture tex0; // texture image
sampler2D Samp = sampler_state {
Texture = <tex0>;
};
struct VS_IN {
float4 vPosition : POSITION0; // vertex position
float4 vNormal : NORMAL; // vertex normal
float2 vTexCoords : TEXCOORD0; // texture uv
float4 vVertexColor : COLOR0; // vertex color
};
struct VS_IN_WOT {
float4 vPosition : POSITION0; // vertex position
float4 vNormal : NORMAL; // vertex normal
float4 vVertexColor : COLOR0; // vertex color
};
struct VS_OUT {
float4 vPosition : POSITION; // vertex position
float3 vNormal : TEXCOORD0; // vertex normal
float2 vTexCoords : TEXCOORD1; // texture uv
float4 vVertexColor : COLOR0; // vertex color
float3 vWPos : TEXCOORD2;
float3 vEyePos : TEXCOORD3;
};
struct VS_OUT_WOT {
float4 vPosition : POSITION; // vertex position
float3 vNormal : TEXCOORD0; // vertex normal
float4 vVertexColor : COLOR0; // vertex color
float3 vWPos : TEXCOORD1;
float3 vEyePos : TEXCOORD2;
};
// vertex shader with texture
VS_OUT VS(VS_IN v) {
VS_OUT output;
output.vTexCoords = v.vTexCoords.xy;
output.vPosition = mul(mul(mul(v.vPosition, mWorld), mView), mProj);
output.vNormal = mul(v.vNormal, (float3x3)mWorld).xyz;
output.vVertexColor = v.vVertexColor;
output.vWPos = mul(v.vPosition, mWorld);
output.vEyePos = mView[3].xyz;
return output;
}
// vertex shader without texture
VS_OUT_WOT VS_WOT(VS_IN_WOT v) {
VS_OUT_WOT output;
output.vPosition = mul(mul(mul(v.vPosition, mWorld), mView), mProj);
output.vNormal = mul(v.vNormal, (float3x3)mWorld).xyz;
output.vVertexColor = v.vVertexColor;
output.vWPos = mul(v.vPosition, mWorld);
output.vEyePos = mView[3].xyz;
return output;
}
struct PS_OUT {
float4 vColor : COLOR0;
};
// pixel shader with texture
PS_OUT PS(VS_OUT p) {
PS_OUT output;
float4 tcolor = tex2D(Samp, p.vTexCoords);
float3 light = normalize(vLightDir.xyz);
float3 normal = normalize(p.vNormal);
// diffuse
// float lambert = clamp(dot(light, normal), 0.0f, 1.0f);
float lambert = dot(light, normal) * 0.5f + 0.5f;
lambert *= lambert;
float3 diffuse = vLightDiffuse * vMatDiffuse * p.vVertexColor * lambert * tcolor;
// specular
float3 view = normalize(p.vEyePos - p.vWPos);
float3 r = normalize(light - 2.0f * normal * dot(light, normal)).xyz;
float spe = pow(max(dot(r, view), 0.0f), vMatShininess);
// float3 halfway = normalize(light - view);
// float spe = pow(max(dot(normal, halfway), 0.0f), vMatShininess);
float3 specular = vMatSpecular * spe;
// ambient
float3 ambient = vLightAmbient * vMatAmbient;
output.vColor.rgb = diffuse + specular + ambient;
output.vColor.a = 1.0;
return output;
}
// pixel shader without texture
PS_OUT PS_WOT(VS_OUT_WOT p) {
PS_OUT output;
float3 light = normalize(vLightDir.xyz);
float3 normal = normalize(p.vNormal);
float lambert = dot(light, normal) * 0.5f + 0.5f;
lambert *= lambert;
float3 diffuse = vLightDiffuse * vMatDiffuse * p.vVertexColor * lambert;
float3 view = normalize(p.vEyePos - p.vWPos);
float3 r = normalize(light - 2.0f * normal * dot(light, normal)).xyz;
float spe = pow(max(dot(r, view), 0.0f), vMatShininess);
float3 specular = vMatSpecular * spe;
float3 ambient = vLightAmbient * vMatAmbient;
output.vColor.rgb = diffuse + specular + ambient;
output.vColor.a = 1.0;
return output;
}
// with texture
technique WithTex {
pass p0 {
VertexShader = compile vs_2_0 VS();
PixelShader = compile ps_2_0 PS();
}
}
// without texture
technique WithoutTex {
pass p0 {
VertexShader = compile vs_2_0 VS_WOT();
PixelShader = compile ps_2_0 PS_WOT();
}
}
@mieki256
Copy link
Author

mieki256 commented Apr 2, 2017

動作には tinydaeparser.rb と tinywavefrontobj.rb、サンプルデータ(.dae .obj .mtl)が必要。

mieki256/tinydaeparser
https://github.com/mieki256/tinydaeparser

mieki256/tinywavefrontobj
https://github.com/mieki256/tinywavefrontobj

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment