Blenderでレンダリング画面にぴったりフィットするプレーンを作る

すでにこの機能を実現するスクリプトがあったのですがカメラとプレーンとの距離を自由に設定できなかったり色々不便だったので自分でやることにしました。

f:id:kaiware-daikon:20150114230727p:plain f:id:kaiware-daikon:20150114230736p:plain

カメラタイプがPerspectiveの場合

  1. 長辺を1とした時のレンダリング画面のアスペクト比を計算

    レンダリング画面の大きさが1920x1080なら1:0.5625
  2. 1で計算したアスペクト比と同じ大きさのプレーンを作成

    要するに大きさ1x0.5625のプレーンを作成するということ
  3. (カメラのSize)÷(カメラのFocal length)を計算、これをRとする

  4. プレーンの大きさをR倍する(エディットモードで)

  5. カメラの位置と回転をプレーンにコピー

  6. プレーンを選択しローカルZ軸方向に適当に移動する

    g,z,zと入力するとローカルZ軸に沿って移動できます
  7. 6の移動距離と同じ値だけプレーンをスケール

    移動距離が5ならプレーンのスケールも5にする

カメラタイプがOrthographicの場合

  1. 同上
  2. 同上
  3. R=(Orthographic Scale)
  4. 同上
  5. 同上
  6. 同上
  7. スケーリングは不要

スクリプト化してみた

Zはカメラとプレーンの距離、NAMEはプレーンの名前。 カメラを動かしたりカメラタイプを変更した場合は再実行する必要があります。 プレーンをカメラの動きに追従させるにはプレーンの親にカメラを指定します。残念ながらテクスチャを貼り付けてもドットバイドット表示にはならず少しぼやけます。

import bpy
import bmesh
from mathutils import Euler, Matrix, Vector

Z = 10
NAME = 'Screen'

def main():
	scene = bpy.context.scene
	camera = scene.camera
	cd = camera.data
	if cd.type == 'PERSP':
		screen_r = cd.sensor_width / cd.lens 
		screen_s = [Z,Z,Z]
	elif cd.type == 'ORTHO':
		screen_r = cd.ortho_scale
		screen_s = [1,1,1]
	else:
		print('Unsupported camera type')
		exit()
	screen_v, screen_f, screen_m = calc_screen_geometry(
		scene.render.resolution_x,
		scene.render.resolution_y,
		screen_r)
	screen = bpy.data.objects.get(NAME, None)	
	if screen:
		set_mesh_data(screen.data, screen_v, screen_f, screen_m)
	else:
		screen_mesh = bpy.data.meshes.new(NAME)
		set_mesh_data(screen_mesh, screen_v, screen_f, screen_m)
		screen = bpy.data.objects.new(NAME, screen_mesh)
		scene.objects.link(screen)
	camera_e = Euler(camera.rotation_euler, camera.rotation_mode)
	z_offset = camera_e.to_matrix()*Vector([0,0,Z])
	screen.location = camera.location - z_offset
	screen.scale = screen_s
	screen.rotation_euler = camera.rotation_euler

def calc_screen_geometry(res_x, res_y, r):
	base_verts = [[0.5,0.5,0],[-0.5,0.5,0],[-0.5,-0.5,0],[0.5,-0.5,0]]
	res_max = max(res_x, res_y)
	sx = r * res_x / res_max
	sy = r * res_y / res_max
	verts = [[v[0]*sx, v[1]*sy, 0] for v in base_verts]
	faces = [[0,1,2,3]]
	uvmaps = [['UVMap',[[1,1],[0,1],[0,0],[1,0]]]]
	return verts, faces, uvmaps

def set_mesh_data(mesh, verts, faces, uvmaps):
	def add_uv(bm, uv_name, uv_coords):
		uv_layer = bm.loops.layers.uv.new(uv_name)
		tex_layer = bm.faces.layers.tex.new(uv_name)
		for face in bm.faces:
			for loop in face.loops:
				loop[uv_layer].uv = uv_coords[loop.vert.index]
	bm = bmesh.new()
	for v in verts:
		bm.verts.new(v)
	bm.verts.ensure_lookup_table()
	for f in faces:
		bm.faces.new([bm.verts[vi] for vi in f])
	bm.faces.ensure_lookup_table()
	bm.to_mesh(mesh)
	mesh.update()
	for uv_name, uv_coords in uvmaps:
		add_uv(bm, uv_name, uv_coords)
	bm.to_mesh(mesh)
	mesh.update()
	bm.free()

if __name__ == '__main__':
    main()

動作確認: Blender 2.73

参考資料