Globe Shader Demo.zip (229.4 KB)
@sim @John can anyone help me fix this one small problem with a Craft shader?
With ChatGPT I’ve been trying to make a Craft shader that turns a heightmap into colors, and we’ve actually succeeded with 2D textures—and even partially succeeded with cubetextures—but there’s one snag—the colors from the cubetexture don’t rotate with the sphere.
It must be a small tweak of some kind, right, to get the colors from the cubetexture heightmap properly rotating with the sphere?
--# Main
function setup()
sceneSetup()
makeBlock()
makeSphere()
end
function draw()
scene:update(DeltaTime)
scene:draw()
end
function touched(touch)
touches.touched(touch)
end
function sceneSetup()
require(asset.documents.Craft.Cameras)
require(asset.documents.Craft.Touches)
viewer.mode = FULLSCREEN
scene = craft.scene()
scene.sky.material.sky = color(72, 235, 215)
scene.sky.material.horizon = color(34, 30, 66)
scene.sky.material.ground = color(114, 31, 31)
camera = scene.camera:get(craft.camera)
camera.z = -10
viewer = scene.camera:add(OrbitViewer, vec3(0,0,0), 23, 6, 800)
end
--# HeightShader2D
function makeBlock()
craft.shader.add(HeightShader2D)
block = scene:entity()
block.position = vec3(-3, 0, 0)
block.model = craft.model.cube(vec3(2,2,2))
block.material = craft.material("Height Shader 2D")
block.material.inTexture = readImage(asset.builtin.Surfaces.Desert_Cliff_Height)
block.material.ramp = createTerrainRamp()
end
HeightShader2D = {
name = "Height Shader 2D",
options = {
USE_COLOR = { true },
},
properties = {
inTexture = {"texture2D", nil},
ramp = {"texture2D", nil},
tiling = {"float", 1.0},
time = {"float", 0.0},
},
pass = {
base = "Surface",
blendMode = "disabled",
depthWrite = true,
depthFunc = "lessEqual",
renderQueue = "solid",
colorMask = {"rgba"},
cullFace = "back",
vertex = [[
uniform mat4 modelViewProjection;
void vertex(inout Vertex v, out Input o) {
o.uv = v.uv;
o.color = v.color;
gl_Position = modelViewProjection * vec4(v.position, 1.0);
}
]],
surface = [[
uniform sampler2D inTexture;
uniform sampler2D ramp;
uniform float tiling;
uniform float time;
lowp vec4 col;
lowp vec4 rampCol;
void surface(in Input IN, inout SurfaceOutput o) {
lowp vec4 col = texture(inTexture, IN.uv * tiling) * IN.color;
lowp vec4 rampCol = texture(ramp, vec2(clamp(col.r, 0.01, 0.99), 0.5));
o.diffuse = rampCol.rgb;
o.emissive = 0.0;
o.emission = vec3(0.0);
}
]]
}}
--# HeightShaderCubetexture
function makeSphere()
craft.shader.add(HeightShaderCubetexture)
testPlanet = scene:entity()
testPlanet.model = craft.model.icosphere(2, 4)
testPlanet.material = craft.material("Height Shader Cubetexture")
testPlanet.material.heightMap = cubeMapFromImage(readImage(asset.builtin.Surfaces.Desert_Cliff_Height))
testPlanet.material.ramp = createTerrainRamp()
testPlanet.material.tiling = 1.0
testPlanet.material.time = 0.0
testPlanet.position = vec3(0, 0, 0)
end
HeightShaderCubetexture = {
name = "Height Shader Cubetexture",
options = {},
properties = {
heightMap = {"cubeTexture", nil}, -- the cube texture with height data
ramp = {"texture2D", nil}, -- the ramp texture for mapping height to color
tiling = {"float", 1.0},
time = {"float", 0.0}
},
pass = {
base = "Surface",
blendMode = "disabled",
depthWrite = true,
depthFunc = "lessEqual",
renderQueue = "solid",
colorMask = {"rgba"},
cullFace = "back",
vertex = [[
uniform mat4 modelViewProjection;
void vertex(inout Vertex v, out Input o) {
o.uv = v.uv;
o.color = v.color;
// Transform the object-space normal into world space.
// We use vec4(v.normal, 0.0) so that translation is ignored.
vec3 worldNormal = normalize((modelMatrix * vec4(v.normal, 0.0)).xyz);
// Pass world normal in the Input structure.
o.normal = worldNormal;
gl_Position = modelViewProjection * vec4(v.position, 1.0);
}
]],
surface = [[
precision highp float;
uniform samplerCube heightMap;
uniform sampler2D ramp;
uniform float tiling;
uniform float time;
vec3 vNormal; // not strictly needed if IN.normal is provided
// In the surface function, use IN.normal (which should now be world-space):
void surface(in Input IN, inout SurfaceOutput o) {
vec3 lookup = normalize(IN.normal);
vec4 h = texture(heightMap, lookup);
float heightVal = h.r;
lowp vec4 rampCol = texture(ramp, vec2(clamp(heightVal, 0.0, 1.0), 0.5));
o.diffuse = rampCol.rgb;
o.emissive = 0.0;
o.emission = vec3(0.0);
}
]]
}
}
--# HelperFunctions
function cubeMapFromImage(img)
local faces = { img, img, img, img, img, img }
return craft.cubeTexture(faces)
end
function createTerrainRamp()
local width, height = 256, 16
local img = image(width, height)
setContext(img)
for x = 0, width - 1 do
local p = x / (width - 1)
local c
if p <= 0.22 then
c = color(30, 47, 70, 255)
elseif p <= 0.25 then
local t = (p - 0.22) / (0.25 - 0.22)
c = color(
(1 - t) * 30 + t * 220,
(1 - t) * 47 + t * 209,
(1 - t) * 70 + t * 172,
255
)
elseif p <= 0.3 then
local t = (p - 0.25) / (0.3 - 0.25)
c = color(
(1 - t) * 220 + t * 27,
(1 - t) * 209 + t * 85,
(1 - t) * 172 + t * 33,
255
)
elseif p <= 0.5 then
c = color(27, 85, 33, 255)
elseif p <= 0.8 then
local t = (p - 0.5) / (0.8 - 0.5)
c = color(
(1 - t) * 27 + t * 156,
(1 - t) * 85 + t * 93,
(1 - t) * 33 + t * 62,
255
)
elseif p <= 0.9 then
local t = (p - 0.8) / (0.9 - 0.8)
c = color(
(1 - t) * 156 + t * 129,
(1 - t) * 93 + t * 149,
(1 - t) * 62 + t * 126,
255
)
elseif p <= 0.99 then
c = color(129, 149, 126, 255)
else
c = color(255, 255, 255, 255)
end
for y = 0, height - 1 do
img:set(x, y, c)
end
end
setContext()
return img
end