2
0
Björn Ritzl 2 сар өмнө
parent
commit
346272426b

+ 18 - 0
model/skybox/all.texture_profiles

@@ -0,0 +1,18 @@
+path_settings {
+  path: "**"
+  profile: "Default"
+}
+profiles {
+  name: "Default"
+  platforms {
+    os: OS_ID_GENERIC
+    formats {
+      format: TEXTURE_FORMAT_RGBA
+      compression_level: BEST
+      compression_type: COMPRESSION_TYPE_DEFAULT
+    }
+    mipmaps: false
+    max_texture_size: 0
+    premultiply_alpha: true
+  }
+}

+ 14 - 0
model/skybox/example.md

@@ -0,0 +1,14 @@
+---
+tags: model
+title: Skybox
+brief: This example shows how to create a skybox using a cubemap texture.
+author: Defold Foundation
+scripts: skybox.fp, skybox.vp
+---
+
+This example shows how to create a skybox. A skybox is a technique that makes the scene look bigger and more impressive by wrapping the viewer with a texture that goes around the camera 360 degrees.
+
+An in-depth explanation of skybox rendering can be found in [Tutorial 25 of OGL Dev](https://www.ogldev.org/www/tutorial25/tutorial25.html).
+
+
+Skybox texture from Babylon.js Playground (Apache 2 license)

BIN
model/skybox/example/assets/larger.png


+ 6 - 0
model/skybox/example/assets/skybox.cubemap

@@ -0,0 +1,6 @@
+right: "/example/assets/skybox_px.jpg"
+left: "/example/assets/skybox_nx.jpg"
+top: "/example/assets/skybox_py.jpg"
+bottom: "/example/assets/skybox_ny.jpg"
+front: "/example/assets/skybox_pz.jpg"
+back: "/example/assets/skybox_nz.jpg"

BIN
model/skybox/example/assets/skybox_nx.jpg


BIN
model/skybox/example/assets/skybox_ny.jpg


BIN
model/skybox/example/assets/skybox_nz.jpg


BIN
model/skybox/example/assets/skybox_px.jpg


BIN
model/skybox/example/assets/skybox_py.jpg


BIN
model/skybox/example/assets/skybox_pz.jpg


+ 4 - 0
model/skybox/example/example.atlas

@@ -0,0 +1,4 @@
+images {
+  image: "/example/assets/larger.png"
+}
+extrude_borders: 2

+ 3 - 0
model/skybox/example/instructions.script

@@ -0,0 +1,3 @@
+function init(self)
+	go.animate("#sprite", "tint.w", go.PLAYBACK_ONCE_FORWARD, 0, go.EASING_INQUAD, 1, 3)
+end

+ 49 - 0
model/skybox/example/orbit_camera.script

@@ -0,0 +1,49 @@
+-- The initial zoom level
+go.property("zoom", 3)
+-- The speed of the zoom
+go.property("zoom_speed", 0)
+-- The speed of the rotation
+go.property("rotation_speed", 0.5)
+-- The offset of the camera from the origin
+go.property("offset", vmath.vector3(0, 0, 0))
+
+function init(self)
+	-- Acquire input focus to receive input events
+	msg.post(".", "acquire_input_focus")
+
+	-- Initialize start values
+	self.yaw = go.get(".", "euler.y")
+	self.pitch = go.get(".", "euler.x")
+	self.zoom_offset = 0
+	self.current_yaw = self.yaw
+	self.current_pitch = self.pitch
+	self.current_zoom = self.zoom_offset
+end
+
+function update(self, dt)
+	-- Animate camera rotation and zoom
+	self.current_yaw = vmath.lerp(0.15, self.current_yaw, self.yaw)
+	self.current_pitch = vmath.lerp(0.15, self.current_pitch, self.pitch)
+	self.current_zoom = vmath.lerp(0.15, self.current_zoom, self.zoom_offset)
+
+	-- Calculate rotation and position
+	local camera_yaw = vmath.quat_rotation_y(math.rad(self.current_yaw))
+	local camera_pitch = vmath.quat_rotation_x(math.rad(self.current_pitch))
+	local camera_rotation = camera_yaw * camera_pitch
+	local camera_position = self.offset + vmath.rotate(camera_rotation, vmath.vector3(0, 0, self.zoom + self.current_zoom))
+
+	-- Set camera position and rotation
+	go.set_position(camera_position)
+	go.set_rotation(camera_rotation)
+end
+
+function on_input(self, action_id, action)
+	if action_id == hash("touch") and not action.pressed then
+		self.yaw   = self.yaw   - action.dx * self.rotation_speed
+		self.pitch = self.pitch + action.dy * self.rotation_speed
+	elseif action_id == hash("mouse_wheel_up") then
+		self.zoom_offset = self.zoom_offset - self.zoom * self.zoom_speed
+	elseif action_id == hash("mouse_wheel_down") then
+		self.zoom_offset = self.zoom_offset + self.zoom * self.zoom_speed
+	end
+end

+ 66 - 0
model/skybox/example/skybox.collection

@@ -0,0 +1,66 @@
+name: "skybox"
+scale_along_z: 0
+embedded_instances {
+  id: "camera"
+  data: "components {\n"
+  "  id: \"orbit_camera\"\n"
+  "  component: \"/example/orbit_camera.script\"\n"
+  "}\n"
+  "components {\n"
+  "  id: \"instructions\"\n"
+  "  component: \"/example/instructions.script\"\n"
+  "}\n"
+  "embedded_components {\n"
+  "  id: \"camera\"\n"
+  "  type: \"camera\"\n"
+  "  data: \"aspect_ratio: 1.0\\n"
+  "fov: 0.7854\\n"
+  "near_z: 0.1\\n"
+  "far_z: 1000.0\\n"
+  "auto_aspect_ratio: 1\\n"
+  "\"\n"
+  "}\n"
+  "embedded_components {\n"
+  "  id: \"sprite\"\n"
+  "  type: \"sprite\"\n"
+  "  data: \"default_animation: \\\"larger\\\"\\n"
+  "material: \\\"/builtins/materials/sprite.material\\\"\\n"
+  "textures {\\n"
+  "  sampler: \\\"texture_sampler\\\"\\n"
+  "  texture: \\\"/example/example.atlas\\\"\\n"
+  "}\\n"
+  "\"\n"
+  "  position {\n"
+  "    z: -10.0\n"
+  "  }\n"
+  "  scale {\n"
+  "    x: 0.01\n"
+  "    y: 0.01\n"
+  "    z: 0.01\n"
+  "  }\n"
+  "}\n"
+  ""
+}
+embedded_instances {
+  id: "skybox"
+  data: "embedded_components {\n"
+  "  id: \"model\"\n"
+  "  type: \"model\"\n"
+  "  data: \"mesh: \\\"/builtins/assets/meshes/sphere.dae\\\"\\n"
+  "materials {\\n"
+  "  name: \\\"default\\\"\\n"
+  "  material: \\\"/example/skybox.material\\\"\\n"
+  "  textures {\\n"
+  "    sampler: \\\"cubemap\\\"\\n"
+  "    texture: \\\"/example/assets/skybox.cubemap\\\"\\n"
+  "  }\\n"
+  "}\\n"
+  "\"\n"
+  "}\n"
+  ""
+  scale3 {
+    x: 30.0
+    y: 30.0
+    z: 30.0
+  }
+}

+ 7 - 0
model/skybox/example/skybox.fp

@@ -0,0 +1,7 @@
+varying mediump vec3 var_texcoord0;
+
+uniform samplerCube cubemap;
+
+void main() {
+	gl_FragColor = textureCube(cubemap, var_texcoord0);
+}

+ 19 - 0
model/skybox/example/skybox.material

@@ -0,0 +1,19 @@
+name: "skybox"
+tags: "skybox"
+vertex_program: "/example/skybox.vp"
+fragment_program: "/example/skybox.fp"
+vertex_constants {
+  name: "view_proj"
+  type: CONSTANT_TYPE_VIEWPROJ
+}
+vertex_constants {
+  name: "world"
+  type: CONSTANT_TYPE_WORLD
+}
+samplers {
+  name: "cubemap"
+  wrap_u: WRAP_MODE_CLAMP_TO_EDGE
+  wrap_v: WRAP_MODE_CLAMP_TO_EDGE
+  filter_min: FILTER_MODE_MIN_LINEAR
+  filter_mag: FILTER_MODE_MAG_LINEAR
+}

+ 1 - 0
model/skybox/example/skybox.render

@@ -0,0 +1 @@
+script: "/example/skybox.render_script"

+ 296 - 0
model/skybox/example/skybox.render_script

@@ -0,0 +1,296 @@
+-- Copyright 2020-2025 The Defold Foundation
+-- Copyright 2014-2020 King
+-- Copyright 2009-2014 Ragnar Svensson, Christian Murray
+-- Licensed under the Defold License version 1.0 (the "License"); you may not use
+-- this file except in compliance with the License.
+--
+-- You may obtain a copy of the License, together with FAQs at
+-- https://www.defold.com/license
+--
+-- Unless required by applicable law or agreed to in writing, software distributed
+-- under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+-- CONDITIONS OF ANY KIND, either express or implied. See the License for the
+-- specific language governing permissions and limitations under the License.
+
+--
+-- message constants
+--
+local MSG_CLEAR_COLOR =         hash("clear_color")
+local MSG_WINDOW_RESIZED =      hash("window_resized")
+local MSG_SET_VIEW_PROJ =       hash("set_view_projection")
+local MSG_SET_CAMERA_PROJ =     hash("use_camera_projection")
+local MSG_USE_STRETCH_PROJ =    hash("use_stretch_projection")
+local MSG_USE_FIXED_PROJ =      hash("use_fixed_projection")
+local MSG_USE_FIXED_FIT_PROJ =  hash("use_fixed_fit_projection")
+
+local DEFAULT_NEAR = -1
+local DEFAULT_FAR =   1
+local DEFAULT_ZOOM =  1
+
+--
+-- projection that centers content with maintained aspect ratio and optional zoom
+--
+local function get_fixed_projection(camera, state)
+    camera.zoom = camera.zoom or DEFAULT_ZOOM
+    local projected_width = state.window_width / camera.zoom
+    local projected_height = state.window_height / camera.zoom
+    local left = -(projected_width - state.width) / 2
+    local bottom = -(projected_height - state.height) / 2
+    local right = left + projected_width
+    local top = bottom + projected_height
+    return vmath.matrix4_orthographic(left, right, bottom, top, camera.near, camera.far)
+end
+--
+-- projection that centers and fits content with maintained aspect ratio
+--
+local function get_fixed_fit_projection(camera, state)
+    camera.zoom = math.min(state.window_width / state.width, state.window_height / state.height)
+    return get_fixed_projection(camera, state)
+end
+--
+-- projection that stretches content
+--
+local function get_stretch_projection(camera, state)
+    return vmath.matrix4_orthographic(0, state.width, 0, state.height, camera.near, camera.far)
+end
+--
+-- projection for gui
+--
+local function get_gui_projection(camera, state)
+    return vmath.matrix4_orthographic(0, state.window_width, 0, state.window_height, camera.near, camera.far)
+end
+
+local function update_clear_color(state, color)
+    if color then
+        state.clear_buffers[graphics.BUFFER_TYPE_COLOR0_BIT] = color
+    end
+end
+
+local function update_camera(camera, state)
+    if camera.projection_fn then
+        camera.proj = camera.projection_fn(camera, state)
+        camera.options.frustum = camera.proj * camera.view
+    end
+end
+
+local function update_state(state)
+    state.window_width = render.get_window_width()
+    state.window_height = render.get_window_height()
+    state.valid = state.window_width > 0 and state.window_height > 0
+    if not state.valid then
+        return false
+    end
+    -- Make sure state updated only once when resize window
+    if state.window_width == state.prev_window_width and state.window_height == state.prev_window_height then
+        return true
+    end
+    state.prev_window_width = state.window_width
+    state.prev_window_height = state.window_height
+    state.width = render.get_width()
+    state.height = render.get_height()
+    for _, camera in pairs(state.cameras) do
+        update_camera(camera, state)
+    end
+    return true
+end
+
+local function init_camera(camera, projection_fn, near, far, zoom)
+    camera.view = vmath.matrix4()
+    camera.near = near == nil and DEFAULT_NEAR or near
+    camera.far = far == nil and DEFAULT_FAR or far
+    camera.zoom = zoom == nil and DEFAULT_ZOOM or zoom
+    camera.projection_fn = projection_fn
+end
+
+local function create_predicates(...)
+    local arg = {...}
+    local predicates = {}
+    for _, predicate_name in pairs(arg) do
+        predicates[predicate_name] = render.predicate({predicate_name})
+    end
+    return predicates
+end
+
+local function create_camera(state, name, is_main_camera)
+    local camera = {}
+    camera.options = {}
+    state.cameras[name] = camera
+    if is_main_camera then
+        state.main_camera = camera
+    end
+    return camera
+end
+
+local function create_state()
+    local state = {}
+    local color = vmath.vector4(0, 0, 0, 0)
+    color.x = sys.get_config_number("render.clear_color_red", 0)
+    color.y = sys.get_config_number("render.clear_color_green", 0)
+    color.z = sys.get_config_number("render.clear_color_blue", 0)
+    color.w = sys.get_config_number("render.clear_color_alpha", 0)
+    state.clear_buffers = {
+        [graphics.BUFFER_TYPE_COLOR0_BIT] = color,
+        [graphics.BUFFER_TYPE_DEPTH_BIT] = 1,
+        [graphics.BUFFER_TYPE_STENCIL_BIT] = 0
+    }
+    state.cameras = {}
+    return state
+end
+
+local function set_camera_world(state)
+    local camera_components = camera.get_cameras()
+
+    -- This will set the last enabled camera from the stack of camera components
+    if #camera_components > 0 then
+        for i = #camera_components, 1, -1 do
+            if camera.get_enabled(camera_components[i]) then
+                local camera_component = state.cameras.camera_component
+                camera_component.camera = camera_components[i]
+                render.set_camera(camera_component.camera, { use_frustum = true })
+                -- The frustum will be overridden by the render.set_camera call,
+                -- so we don't need to return anything here other than an empty table.
+                return camera_component.options
+            end
+        end
+    end
+
+    -- If no active camera was found, we use the default main "camera world" camera
+    local camera_world = state.cameras.camera_world
+    render.set_view(camera_world.view)
+    render.set_projection(camera_world.proj)
+    return camera_world.options
+end
+
+local function reset_camera_world(state)
+    -- unbind the camera if a camera component is used
+    if state.cameras.camera_component.camera then
+        state.cameras.camera_component.camera = nil
+        render.set_camera()
+    end
+end
+
+function init(self)
+    self.predicates = create_predicates("tile", "gui", "particle", "model", "skybox", "debug_text")
+
+    -- default is stretch projection. copy from builtins and change for different projection
+    -- or send a message to the render script to change projection:
+    -- msg.post("@render:", "use_stretch_projection", { near = -1, far = 1 })
+    -- msg.post("@render:", "use_fixed_projection", { near = -1, far = 1, zoom = 2 })
+    -- msg.post("@render:", "use_fixed_fit_projection", { near = -1, far = 1 })
+
+    local state = create_state()
+    self.state = state
+
+    local camera_world = create_camera(state, "camera_world", true)
+    init_camera(camera_world, get_stretch_projection)
+    local camera_gui = create_camera(state, "camera_gui")
+    init_camera(camera_gui, get_gui_projection)
+    -- Create a special camera that wraps camera components (if they exist)
+    -- It will take precedence over any other camera, and not change from messages
+    local camera_component = create_camera(state, "camera_component")
+    update_state(state)
+end
+
+function update(self)
+    local state = self.state
+    if not state.valid then
+        if not update_state(state) then
+            return
+        end
+    end
+
+    local predicates = self.predicates
+    -- clear screen buffers
+    --
+    -- turn on depth_mask before `render.clear()` to clear it as well
+    render.set_depth_mask(true)
+    render.set_stencil_mask(0xff)
+    render.clear(state.clear_buffers)
+
+    -- setup camera view and projection
+    --
+    local draw_options_world = set_camera_world(state)
+    render.set_viewport(0, 0, state.window_width, state.window_height)
+
+    -- set states used for all the world predicates
+    render.set_blend_func(graphics.BLEND_FACTOR_SRC_ALPHA, graphics.BLEND_FACTOR_ONE_MINUS_SRC_ALPHA)
+    render.enable_state(graphics.STATE_DEPTH_TEST)
+
+
+    -- render `model` predicate for default 3D material
+    --
+    render.enable_state(graphics.STATE_CULL_FACE)
+    render.draw(predicates.model, draw_options_world)
+    render.set_depth_mask(false)
+    render.disable_state(graphics.STATE_CULL_FACE)
+
+    
+    -- render `skybox`
+    -- https://www.ogldev.org/www/tutorial25/tutorial25.html
+    --
+    render.enable_state(graphics.STATE_CULL_FACE)
+    -- Usually we want to cull the triangles that are facing away from the camera
+    -- However in the case of a skybox the camera is placed inside of a box/sphere
+    -- so we want to see their front, rather than their back
+    render.set_cull_face(graphics.FACE_TYPE_FRONT)
+    -- By default, we tell OpenGL that an incoming fragment wins the depth test if
+    -- its Z value is less than the stored one. However, in the case of a skybox
+    -- the Z value is always the far Z. The far Z is clipped when the depth test
+    -- function is set to "less than". To make it part of the scene we change the
+    -- depth function to "less than or equal".
+    render.set_depth_func(graphics.COMPARE_FUNC_LEQUAL)
+    render.draw(predicates.skybox, draw_options_world)
+    render.set_depth_mask(false)
+    render.set_depth_func(graphics.COMPARE_FUNC_LESS)
+    render.set_cull_face(graphics.FACE_TYPE_BACK)
+    render.disable_state(graphics.STATE_CULL_FACE)
+
+    -- render the other components: sprites, tilemaps, particles etc
+    --
+    render.enable_state(graphics.STATE_BLEND)
+    render.draw(predicates.tile, draw_options_world)
+    render.draw(predicates.particle, draw_options_world)
+    render.disable_state(graphics.STATE_DEPTH_TEST)
+
+    render.draw_debug3d()
+
+    reset_camera_world(state)
+
+    -- render GUI
+    --
+    local camera_gui = state.cameras.camera_gui
+    render.set_view(camera_gui.view)
+    render.set_projection(camera_gui.proj)
+
+    render.enable_state(graphics.STATE_STENCIL_TEST)
+    render.draw(predicates.gui, camera_gui.options)
+    render.draw(predicates.debug_text, camera_gui.options)
+    render.disable_state(graphics.STATE_STENCIL_TEST)
+    render.disable_state(graphics.STATE_BLEND)
+end
+
+function on_message(self, message_id, message)
+    local state = self.state
+    local camera = state.main_camera
+
+    if message_id == MSG_CLEAR_COLOR then
+        update_clear_color(state, message.color)
+    elseif message_id == MSG_WINDOW_RESIZED then
+        update_state(state)
+    elseif message_id == MSG_SET_VIEW_PROJ then
+        camera.view = message.view
+        self.camera_projection = message.projection or vmath.matrix4()
+        update_camera(camera, state)
+    elseif message_id == MSG_SET_CAMERA_PROJ then
+        camera.projection_fn = function() return self.camera_projection end
+    elseif message_id == MSG_USE_STRETCH_PROJ then
+        init_camera(camera, get_stretch_projection, message.near, message.far)
+        update_camera(camera, state)
+    elseif message_id == MSG_USE_FIXED_PROJ then
+        init_camera(camera, get_fixed_projection, message.near, message.far, message.zoom)
+        update_camera(camera, state)
+    elseif message_id == MSG_USE_FIXED_FIT_PROJ then
+        init_camera(camera, get_fixed_fit_projection, message.near, message.far)
+        update_camera(camera, state)
+    end
+end

+ 36 - 0
model/skybox/example/skybox.vp

@@ -0,0 +1,36 @@
+uniform mediump mat4 view_proj;
+uniform mediump mat4 world;
+
+attribute mediump vec3 position;
+
+varying mediump vec3 var_texcoord0;
+
+void main()
+{
+	/*
+	 * Transform the position vector using the world view projection matrix
+	 * and override the Z component with the W component. After the vertex
+	 * shader is complete the rasterizer takes gl_Position vector and performs
+	 * perspective divide (division by W) in order to complete the projection.
+	 * When we set Z to W we guarantee that the final Z value of the position
+	 * will be 1.0. This Z value is always mapped to the far Z. This means that
+	 * the skybox will always fail the depth test against the other models in
+	 * the scene. That way the skybox will only take up the background left
+	 * between the models and everything else will be infront of it.
+	 */
+	mat4 wvp = world * view_proj;
+	vec4 wvp_pos = wvp * vec4(position, 1.0);
+	gl_Position = wvp_pos.xyww;
+
+	/*
+	 * Use the original position in object space as the 3D texture coordinate.
+	 * This makes sense because the way sampling from the cubemap works is by
+	 * shooting a vector from the origin through a point in the box or sphere.
+	 * So the position of the point actually becomes the texture coordinate.
+	 * The vertex shader passes the object space coordinate of each vertex as
+	 * the texture coordinate and it gets interpolated by the rasterizer for
+	 * each pixel. This gives us the position of the pixel which we can use for
+	 * sampling.
+	 */
+	var_texcoord0 = position;
+}

+ 66 - 0
model/skybox/game.project

@@ -0,0 +1,66 @@
+[project]
+title = Skybox
+version = 0.1
+
+[bootstrap]
+main_collection = /example/skybox.collectionc
+render = /example/skybox.renderc
+
+[input]
+game_binding = /builtins/input/all.input_bindingc
+repeat_interval = 0.05
+
+[display]
+width = 720
+height = 720
+high_dpi = 1
+
+[physics]
+scale = 0.02
+gravity_y = -500.0
+
+[script]
+shared_state = 1
+
+[collection_proxy]
+max_count = 256
+
+[label]
+subpixels = 1
+
+[sprite]
+subpixels = 1
+max_count = 32765
+
+[windows]
+iap_provider = 
+
+[android]
+package = com.defold.examples
+
+[ios]
+bundle_identifier = com.defold.examples
+
+[osx]
+bundle_identifier = com.defold.examples
+
+[html5]
+show_fullscreen_button = 0
+show_made_with_defold = 0
+scale_mode = no_scale
+heap_size = 64
+
+[graphics]
+texture_profiles = /all.texture_profiles
+
+[collection]
+max_instances = 32765
+
+[particle_fx]
+max_emitter_count = 1024
+
+[render]
+clear_color_blue = 1.0
+clear_color_green = 1.0
+clear_color_red = 1.0
+